mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Fix WPF app thread handling to allow process to close on app exit event.
This commit is contained in:
parent
c5ad58433e
commit
effb27cdd9
@ -5,6 +5,7 @@ namespace Remotely.Desktop.Core.Interfaces
|
||||
{
|
||||
public interface IKeyboardMouseInput
|
||||
{
|
||||
void Init();
|
||||
void SendKeyDown(string key);
|
||||
void SendKeyUp(string key);
|
||||
void SendMouseMove(double percentX, double percentY, Services.Viewer viewer);
|
||||
|
||||
@ -66,6 +66,7 @@ namespace Remotely.Desktop.Linux
|
||||
await casterSocket.NotifyRequesterUnattendedReady(Conductor.RequesterID);
|
||||
Services.GetRequiredService<IdleTimer>().Start();
|
||||
Services.GetRequiredService<IClipboardService>().BeginWatching();
|
||||
Services.GetRequiredService<IKeyboardMouseInput>().Init();
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
@ -11,14 +11,21 @@ namespace Remotely.Desktop.Linux.Services
|
||||
{
|
||||
private IntPtr Display { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Nothing to do here. The Windows implementation needs to start
|
||||
// a processing queue to keep all input simulation on the same
|
||||
// thread. Linux doesn't.
|
||||
}
|
||||
|
||||
public void SendKeyDown(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
Init();
|
||||
InitDisplay();
|
||||
key = ConvertJavaScriptKeyToX11Key(key);
|
||||
var keySim = LibX11.XStringToKeysym(key);
|
||||
if (keySim == null)
|
||||
if (keySim == IntPtr.Zero)
|
||||
{
|
||||
Logger.Write($"Key not mapped: {key}");
|
||||
return;
|
||||
@ -38,10 +45,10 @@ namespace Remotely.Desktop.Linux.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
Init();
|
||||
InitDisplay();
|
||||
key = ConvertJavaScriptKeyToX11Key(key);
|
||||
var keySim = LibX11.XStringToKeysym(key);
|
||||
if (keySim == null)
|
||||
if (keySim == IntPtr.Zero)
|
||||
{
|
||||
Logger.Write($"Key not mapped: {key}");
|
||||
return;
|
||||
@ -59,11 +66,30 @@ namespace Remotely.Desktop.Linux.Services
|
||||
}
|
||||
|
||||
|
||||
public void SendMouseButtonAction(int button, ButtonAction buttonAction, double percentX, double percentY, Viewer viewer)
|
||||
{
|
||||
try
|
||||
{
|
||||
var isPressed = buttonAction == ButtonAction.Down;
|
||||
// Browser buttons start at 0. XTest starts at 1.
|
||||
var mouseButton = (uint)(button + 1);
|
||||
|
||||
InitDisplay();
|
||||
SendMouseMove(percentX, percentY, viewer);
|
||||
LibXtst.XTestFakeButtonEvent(Display, mouseButton, isPressed, 0);
|
||||
LibX11.XSync(Display, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Write(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendMouseMove(double percentX, double percentY, Viewer viewer)
|
||||
{
|
||||
try
|
||||
{
|
||||
Init();
|
||||
InitDisplay();
|
||||
LibXtst.XTestFakeMotionEvent(Display,
|
||||
viewer.Capturer.GetSelectedScreenIndex(),
|
||||
(int)(viewer.Capturer.CurrentScreenBounds.Width * percentX),
|
||||
@ -81,7 +107,7 @@ namespace Remotely.Desktop.Linux.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
Init();
|
||||
InitDisplay();
|
||||
if (deltaY > 0)
|
||||
{
|
||||
LibXtst.XTestFakeButtonEvent(Display, 4, true, 0);
|
||||
@ -104,7 +130,7 @@ namespace Remotely.Desktop.Linux.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
Init();
|
||||
InitDisplay();
|
||||
SendMouseMove(percentX, percentY, viewer);
|
||||
LibXtst.XTestFakeButtonEvent(Display, 3, true, 0);
|
||||
LibX11.XSync(Display, false);
|
||||
@ -119,7 +145,7 @@ namespace Remotely.Desktop.Linux.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
Init();
|
||||
InitDisplay();
|
||||
SendMouseMove(percentX, percentY, viewer);
|
||||
LibXtst.XTestFakeButtonEvent(Display, 3, false, 0);
|
||||
LibX11.XSync(Display, false);
|
||||
@ -129,26 +155,6 @@ namespace Remotely.Desktop.Linux.Services
|
||||
Logger.Write(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendMouseButtonAction(int button, ButtonAction buttonAction, double percentX, double percentY, Viewer viewer)
|
||||
{
|
||||
try
|
||||
{
|
||||
var isPressed = buttonAction == ButtonAction.Down;
|
||||
// Browser buttons start at 0. XTest starts at 1.
|
||||
var mouseButton = (uint)(button + 1);
|
||||
|
||||
Init();
|
||||
SendMouseMove(percentX, percentY, viewer);
|
||||
LibXtst.XTestFakeButtonEvent(Display, mouseButton, isPressed, 0);
|
||||
LibX11.XSync(Display, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Write(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendText(string transferText)
|
||||
{
|
||||
foreach (var key in transferText)
|
||||
@ -228,8 +234,7 @@ namespace Remotely.Desktop.Linux.Services
|
||||
};
|
||||
return keySym;
|
||||
}
|
||||
|
||||
private void Init()
|
||||
private void InitDisplay()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -243,5 +248,6 @@ namespace Remotely.Desktop.Linux.Services
|
||||
Logger.Write(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,9 @@ namespace Remotely.Desktop.Linux.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
Services.GetRequiredService<IClipboardService>().BeginWatching();
|
||||
Services.GetRequiredService<IKeyboardMouseInput>().Init();
|
||||
|
||||
Conductor = Services.GetRequiredService<Conductor>();
|
||||
CasterSocket = Services.GetRequiredService<CasterSocket>();
|
||||
|
||||
|
||||
@ -78,6 +78,8 @@ namespace Remotely.Desktop.Win
|
||||
{
|
||||
StartUiThreads(true);
|
||||
}
|
||||
|
||||
WaitForAppExit();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -86,6 +88,21 @@ namespace Remotely.Desktop.Win
|
||||
}
|
||||
}
|
||||
|
||||
private static void WaitForAppExit()
|
||||
{
|
||||
var appExitEvent = new ManualResetEventSlim();
|
||||
|
||||
App.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
App.Current.Exit += (s, a) =>
|
||||
{
|
||||
appExitEvent.Set();
|
||||
};
|
||||
});
|
||||
|
||||
appExitEvent.Wait();
|
||||
}
|
||||
|
||||
private static void BuildServices()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection();
|
||||
@ -173,6 +190,7 @@ namespace Remotely.Desktop.Win
|
||||
Services.GetRequiredService<IdleTimer>().Start();
|
||||
CursorIconWatcher.OnChange += CursorIconWatcher_OnChange;
|
||||
Services.GetRequiredService<IClipboardService>().BeginWatching();
|
||||
Services.GetRequiredService<IKeyboardMouseInput>().Init();
|
||||
}
|
||||
|
||||
private static void StartUiThreads(bool createMainWindow)
|
||||
@ -192,12 +210,14 @@ namespace Remotely.Desktop.Win
|
||||
}
|
||||
});
|
||||
wpfUiThread.TrySetApartmentState(ApartmentState.STA);
|
||||
wpfUiThread.IsBackground = true;
|
||||
wpfUiThread.Start();
|
||||
|
||||
var winformsThread = new Thread(() =>
|
||||
{
|
||||
System.Windows.Forms.Application.Run(BackgroundForm);
|
||||
});
|
||||
winformsThread.IsBackground = true;
|
||||
winformsThread.TrySetApartmentState(ApartmentState.STA);
|
||||
winformsThread.Start();
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ namespace Remotely.Desktop.Win.Services
|
||||
public class ClipboardServiceWin : IClipboardService
|
||||
{
|
||||
|
||||
private CancellationTokenSource cancelTokenSource;
|
||||
private CancellationTokenSource _cancelTokenSource;
|
||||
|
||||
public event EventHandler<string> ClipboardTextChanged;
|
||||
|
||||
@ -19,15 +19,16 @@ namespace Remotely.Desktop.Win.Services
|
||||
|
||||
public void BeginWatching()
|
||||
{
|
||||
try
|
||||
StopWatching();
|
||||
_cancelTokenSource = new CancellationTokenSource();
|
||||
|
||||
App.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
StopWatching();
|
||||
}
|
||||
finally
|
||||
{
|
||||
cancelTokenSource = new CancellationTokenSource();
|
||||
WatchClipboard(cancelTokenSource.Token);
|
||||
}
|
||||
App.Current.Exit -= App_Exit;
|
||||
App.Current.Exit += App_Exit;
|
||||
});
|
||||
|
||||
WatchClipboard(_cancelTokenSource.Token);
|
||||
}
|
||||
|
||||
public Task SetText(string clipboardText)
|
||||
@ -61,19 +62,22 @@ namespace Remotely.Desktop.Win.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
cancelTokenSource?.Cancel();
|
||||
cancelTokenSource?.Dispose();
|
||||
_cancelTokenSource?.Cancel();
|
||||
_cancelTokenSource?.Dispose();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void App_Exit(object sender, System.Windows.ExitEventArgs e)
|
||||
{
|
||||
_cancelTokenSource?.Cancel();
|
||||
}
|
||||
private void WatchClipboard(CancellationToken cancelToken)
|
||||
{
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
|
||||
while (!cancelToken.IsCancellationRequested &&
|
||||
!Environment.HasShutdownStarted)
|
||||
while (!cancelToken.IsCancellationRequested)
|
||||
{
|
||||
|
||||
try
|
||||
|
||||
@ -15,12 +15,8 @@ namespace Remotely.Desktop.Win.Services
|
||||
{
|
||||
private volatile bool inputBlocked;
|
||||
|
||||
public KeyboardMouseInputWin()
|
||||
{
|
||||
StartInputProcessingThread();
|
||||
}
|
||||
|
||||
private CancellationTokenSource CancelTokenSource { get; set; }
|
||||
|
||||
private ConcurrentQueue<Action> InputActions { get; } = new ConcurrentQueue<Action>();
|
||||
|
||||
public Tuple<double, double> GetAbsolutePercentFromRelativePercent(double percentX, double percentY, IScreenCapturer capturer)
|
||||
@ -37,6 +33,11 @@ namespace Remotely.Desktop.Win.Services
|
||||
return new Tuple<double, double>(absoluteX, absoluteY);
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
StartInputProcessingThread();
|
||||
}
|
||||
|
||||
public void SendKeyDown(string key)
|
||||
{
|
||||
TryOnInputDesktop(() =>
|
||||
@ -223,10 +224,13 @@ namespace Remotely.Desktop.Win.Services
|
||||
});
|
||||
}
|
||||
|
||||
private void App_Exit(object sender, System.Windows.ExitEventArgs e)
|
||||
{
|
||||
CancelTokenSource?.Cancel();
|
||||
}
|
||||
private void CheckQueue(CancellationToken cancelToken)
|
||||
{
|
||||
while (!Environment.HasShutdownStarted &&
|
||||
!cancelToken.IsCancellationRequested)
|
||||
while (!cancelToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -293,10 +297,20 @@ namespace Remotely.Desktop.Win.Services
|
||||
CancelTokenSource?.Cancel();
|
||||
CancelTokenSource?.Dispose();
|
||||
|
||||
// After BlockInput is enabled, only simulated input coming from the same thread
|
||||
// will work. So we have to start a new thread that runs continuously and
|
||||
// processes a queue of input events.
|
||||
var newThread = new Thread(() =>
|
||||
{
|
||||
Logger.Write($"New input processing thread started on thread {Thread.CurrentThread.ManagedThreadId}.");
|
||||
CancelTokenSource = new CancellationTokenSource();
|
||||
|
||||
App.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
App.Current.Exit -= App_Exit;
|
||||
App.Current.Exit += App_Exit;
|
||||
});
|
||||
|
||||
if (inputBlocked)
|
||||
{
|
||||
ToggleBlockInput(true);
|
||||
|
||||
@ -40,9 +40,10 @@ namespace Remotely.Desktop.Win.ViewModels
|
||||
|
||||
CursorIconWatcher = Services?.GetRequiredService<ICursorIconWatcher>();
|
||||
CursorIconWatcher.OnChange += CursorIconWatcher_OnChange;
|
||||
Services?.GetRequiredService<IClipboardService>().BeginWatching();
|
||||
Conductor = Services?.GetRequiredService<Conductor>();
|
||||
CasterSocket = Services?.GetRequiredService<CasterSocket>();
|
||||
Services.GetRequiredService<IClipboardService>().BeginWatching();
|
||||
Services.GetRequiredService<IKeyboardMouseInput>().Init();
|
||||
Conductor = Services.GetRequiredService<Conductor>();
|
||||
CasterSocket = Services.GetRequiredService<CasterSocket>();
|
||||
Conductor.SessionIDChanged += SessionIDChanged;
|
||||
Conductor.ViewerRemoved += ViewerRemoved;
|
||||
Conductor.ViewerAdded += ViewerAdded;
|
||||
@ -191,7 +192,7 @@ namespace Remotely.Desktop.Win.ViewModels
|
||||
public async Task Init()
|
||||
{
|
||||
SessionID = "Retrieving...";
|
||||
|
||||
|
||||
Host = Config.GetConfig().Host;
|
||||
|
||||
while (string.IsNullOrWhiteSpace(Host))
|
||||
@ -205,7 +206,6 @@ namespace Remotely.Desktop.Win.ViewModels
|
||||
{
|
||||
await CasterSocket.Connect(Conductor.Host);
|
||||
|
||||
|
||||
CasterSocket.Connection.Closed += async (ex) =>
|
||||
{
|
||||
await App.Current.Dispatcher.InvokeAsync(() =>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user