From b50c208f467b2b1395314a64a2811f440a7b0e01 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Tue, 25 Sep 2018 12:10:34 +0200 Subject: [PATCH] SEBWIN-220: Finally found a solution to run keyboard & mouse hooks in sepearate threads. --- SafeExamBrowser.Client/CompositionRoot.cs | 12 +++--- ...cs => LazyInitializationOperationTests.cs} | 14 +++--- .../SafeExamBrowser.Core.UnitTests.csproj | 2 +- ...tion.cs => LazyInitializationOperation.cs} | 10 ++--- .../SafeExamBrowser.Core.csproj | 2 +- SafeExamBrowser.WindowsApi/ExplorerShell.cs | 10 ++--- .../Monitoring/KeyboardHook.cs | 11 ++++- .../Monitoring/MouseHook.cs | 11 ++++- SafeExamBrowser.WindowsApi/NativeMethods.cs | 43 ++++++++++++++++--- SafeExamBrowser.WindowsApi/Process.cs | 9 +++- 10 files changed, 87 insertions(+), 37 deletions(-) rename SafeExamBrowser.Core.UnitTests/Operations/{DelayedInitializationOperationTests.cs => LazyInitializationOperationTests.cs} (86%) rename SafeExamBrowser.Core/Operations/{DelayedInitializationOperation.cs => LazyInitializationOperation.cs} (69%) diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index d39cfcf6..0c934145 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -96,16 +96,16 @@ namespace SafeExamBrowser.Client operations.Enqueue(new RuntimeConnectionOperation(logger, runtimeProxy, startupToken)); operations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeProxy)); operations.Enqueue(new DelegateOperation(UpdateAppConfig)); - operations.Enqueue(new DelayedInitializationOperation(BuildCommunicationHostOperation)); + operations.Enqueue(new LazyInitializationOperation(BuildCommunicationHostOperation)); + operations.Enqueue(new LazyInitializationOperation(BuildKeyboardInterceptorOperation)); // TODO - //operations.Enqueue(new DelayedInitializationOperation(BuildKeyboardInterceptorOperation)); //operations.Enqueue(new WindowMonitorOperation(logger, windowMonitor)); - operations.Enqueue(new DelayedInitializationOperation(BuildProcessMonitorOperation)); + operations.Enqueue(new LazyInitializationOperation(BuildProcessMonitorOperation)); operations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, Taskbar)); - operations.Enqueue(new DelayedInitializationOperation(BuildTaskbarOperation)); - operations.Enqueue(new DelayedInitializationOperation(BuildBrowserOperation)); + operations.Enqueue(new LazyInitializationOperation(BuildTaskbarOperation)); + operations.Enqueue(new LazyInitializationOperation(BuildBrowserOperation)); operations.Enqueue(new ClipboardOperation(logger, nativeMethods)); - //operations.Enqueue(new DelayedInitializationOperation(BuildMouseInterceptorOperation)); + operations.Enqueue(new LazyInitializationOperation(BuildMouseInterceptorOperation)); operations.Enqueue(new DelegateOperation(UpdateClientControllerDependencies)); var sequence = new OperationSequence(logger, operations); diff --git a/SafeExamBrowser.Core.UnitTests/Operations/DelayedInitializationOperationTests.cs b/SafeExamBrowser.Core.UnitTests/Operations/LazyInitializationOperationTests.cs similarity index 86% rename from SafeExamBrowser.Core.UnitTests/Operations/DelayedInitializationOperationTests.cs rename to SafeExamBrowser.Core.UnitTests/Operations/LazyInitializationOperationTests.cs index db84d032..39419e62 100644 --- a/SafeExamBrowser.Core.UnitTests/Operations/DelayedInitializationOperationTests.cs +++ b/SafeExamBrowser.Core.UnitTests/Operations/LazyInitializationOperationTests.cs @@ -16,7 +16,7 @@ using SafeExamBrowser.Core.Operations; namespace SafeExamBrowser.Core.UnitTests.Operations { [TestClass] - public class DelayedInitializationOperationTests + public class LazyInitializationOperationTests { private Mock operationMock; @@ -37,7 +37,7 @@ namespace SafeExamBrowser.Core.UnitTests.Operations return operationMock.Object; }; - var sut = new DelayedInitializationOperation(initialize); + var sut = new LazyInitializationOperation(initialize); sut.Perform(); @@ -53,7 +53,7 @@ namespace SafeExamBrowser.Core.UnitTests.Operations return operationMock.Object; }; - var sut = new DelayedInitializationOperation(initialize); + var sut = new LazyInitializationOperation(initialize); sut.Repeat(); } @@ -67,7 +67,7 @@ namespace SafeExamBrowser.Core.UnitTests.Operations return operationMock.Object; }; - var sut = new DelayedInitializationOperation(initialize); + var sut = new LazyInitializationOperation(initialize); sut.Revert(); } @@ -83,7 +83,7 @@ namespace SafeExamBrowser.Core.UnitTests.Operations operationMock.Setup(o => o.Perform()).Returns(OperationResult.Success); operationMock.Setup(o => o.Repeat()).Returns(OperationResult.Failed); - var sut = new DelayedInitializationOperation(initialize); + var sut = new LazyInitializationOperation(initialize); var perform = sut.Perform(); var repeat = sut.Repeat(); @@ -101,7 +101,7 @@ namespace SafeExamBrowser.Core.UnitTests.Operations return operationMock.Object; }; - var sut = new DelayedInitializationOperation(initialize) + var sut = new LazyInitializationOperation(initialize) { ProgressIndicator = new Mock().Object }; @@ -128,7 +128,7 @@ namespace SafeExamBrowser.Core.UnitTests.Operations return new Mock().Object; }; - var sut = new DelayedInitializationOperation(initialize); + var sut = new LazyInitializationOperation(initialize); sut.Perform(); sut.Repeat(); diff --git a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj index 67d8cc24..3f86c61c 100644 --- a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj +++ b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj @@ -79,7 +79,7 @@ - + diff --git a/SafeExamBrowser.Core/Operations/DelayedInitializationOperation.cs b/SafeExamBrowser.Core/Operations/LazyInitializationOperation.cs similarity index 69% rename from SafeExamBrowser.Core/Operations/DelayedInitializationOperation.cs rename to SafeExamBrowser.Core/Operations/LazyInitializationOperation.cs index 0ac3bdae..1c3a791f 100644 --- a/SafeExamBrowser.Core/Operations/DelayedInitializationOperation.cs +++ b/SafeExamBrowser.Core/Operations/LazyInitializationOperation.cs @@ -13,18 +13,18 @@ using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Core.Operations { /// - /// A wrapper operation to allow for a delayed (just-in-time) instantiation of an operation. Is useful when e.g. dependencies for a - /// certain operation are not available during execution of the composition root, but rather only after a preceding operation within - /// an has finished. + /// A wrapper operation to allow for a lazy (just-in-time) instantiation of an operation, initialized on . + /// Is useful when e.g. dependencies for a certain operation are not available during execution of the composition root, but rather + /// only after a preceding operation within an has finished. /// - public class DelayedInitializationOperation : IOperation + public class LazyInitializationOperation : IOperation { private Func initialize; private IOperation operation; public IProgressIndicator ProgressIndicator { get; set; } - public DelayedInitializationOperation(Func initialize) + public LazyInitializationOperation(Func initialize) { this.initialize = initialize; } diff --git a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj index 761050ee..a0843402 100644 --- a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj +++ b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj @@ -55,7 +55,7 @@ - + diff --git a/SafeExamBrowser.WindowsApi/ExplorerShell.cs b/SafeExamBrowser.WindowsApi/ExplorerShell.cs index 2ba83ca9..9cad87ad 100644 --- a/SafeExamBrowser.WindowsApi/ExplorerShell.cs +++ b/SafeExamBrowser.WindowsApi/ExplorerShell.cs @@ -48,11 +48,11 @@ namespace SafeExamBrowser.WindowsApi if (success) { - logger.Debug($"Successfully resumed thread #{thread.Id} of explorer shell process."); + logger.Debug($"Successfully resumed explorer shell thread with ID = {thread.Id}."); } else { - logger.Warn($"Failed to resume thread #{thread.Id} of explorer shell process within {MAX_ATTEMPTS} attempts!"); + logger.Warn($"Failed to resume explorer shell thread with ID = {thread.Id} within {MAX_ATTEMPTS} attempts!"); } } } @@ -89,7 +89,7 @@ namespace SafeExamBrowser.WindowsApi if (shellProcess != null) { - logger.Debug($"Found explorer shell processes with PID = {processId}."); + logger.Debug($"Found explorer shell processes with PID = {processId} and {shellProcess.Threads.Count} threads."); foreach (ProcessThread thread in shellProcess.Threads) { @@ -98,11 +98,11 @@ namespace SafeExamBrowser.WindowsApi if (success) { suspendedThreads.Add(thread); - logger.Debug($"Successfully suspended thread #{thread.Id} of explorer shell process."); + logger.Debug($"Successfully suspended explorer shell thread with ID = {thread.Id}."); } else { - logger.Warn($"Failed to suspend thread #{thread.Id} of explorer shell process!"); + logger.Warn($"Failed to suspend explorer shell thread with ID = {thread.Id}!"); } } diff --git a/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs b/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs index ceb97ad1..36c3a386 100644 --- a/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs +++ b/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs @@ -8,6 +8,7 @@ using System; using System.Runtime.InteropServices; +using System.Threading; using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.WindowsApi.Constants; using SafeExamBrowser.WindowsApi.Delegates; @@ -27,23 +28,27 @@ namespace SafeExamBrowser.WindowsApi.Monitoring private HookDelegate hookProc; internal IntPtr Handle { get; private set; } + internal AutoResetEvent InputEvent { get; private set; } internal IKeyboardInterceptor Interceptor { get; private set; } internal KeyboardHook(IKeyboardInterceptor interceptor) { + InputEvent = new AutoResetEvent(false); Interceptor = interceptor; } internal void Attach() { - var module = Kernel32.GetModuleHandle(null); + var process = System.Diagnostics.Process.GetCurrentProcess(); + var module = process.MainModule; + var moduleHandle = Kernel32.GetModuleHandle(module.ModuleName); // IMORTANT: // Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. // Not doing so will result in a CallbackOnCollectedDelegate error and subsequent application crash! hookProc = new HookDelegate(LowLevelKeyboardProc); - Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookProc, module, 0); + Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookProc, moduleHandle, 0); } internal bool Detach() @@ -53,6 +58,8 @@ namespace SafeExamBrowser.WindowsApi.Monitoring private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam) { + InputEvent.Set(); + if (nCode >= 0) { var keyData = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT)); diff --git a/SafeExamBrowser.WindowsApi/Monitoring/MouseHook.cs b/SafeExamBrowser.WindowsApi/Monitoring/MouseHook.cs index f738d1e8..28371f66 100644 --- a/SafeExamBrowser.WindowsApi/Monitoring/MouseHook.cs +++ b/SafeExamBrowser.WindowsApi/Monitoring/MouseHook.cs @@ -8,6 +8,7 @@ using System; using System.Runtime.InteropServices; +using System.Threading; using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.WindowsApi.Constants; using SafeExamBrowser.WindowsApi.Delegates; @@ -20,23 +21,27 @@ namespace SafeExamBrowser.WindowsApi.Monitoring private HookDelegate hookProc; internal IntPtr Handle { get; private set; } + internal AutoResetEvent InputEvent { get; private set; } internal IMouseInterceptor Interceptor { get; private set; } internal MouseHook(IMouseInterceptor interceptor) { + InputEvent = new AutoResetEvent(false); Interceptor = interceptor; } internal void Attach() { - var module = Kernel32.GetModuleHandle(null); + var process = System.Diagnostics.Process.GetCurrentProcess(); + var module = process.MainModule; + var moduleHandle = Kernel32.GetModuleHandle(module.ModuleName); // IMORTANT: // Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. // Not doing so will result in a CallbackOnCollectedDelegate error and subsequent application crash! hookProc = new HookDelegate(LowLevelMouseProc); - Handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookProc, module, 0); + Handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookProc, moduleHandle, 0); } internal bool Detach() @@ -46,6 +51,8 @@ namespace SafeExamBrowser.WindowsApi.Monitoring private IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam) { + InputEvent.Set(); + if (nCode >= 0 && !Ignore(wParam.ToInt32())) { var mouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); diff --git a/SafeExamBrowser.WindowsApi/NativeMethods.cs b/SafeExamBrowser.WindowsApi/NativeMethods.cs index c8197c2c..744efa78 100644 --- a/SafeExamBrowser.WindowsApi/NativeMethods.cs +++ b/SafeExamBrowser.WindowsApi/NativeMethods.cs @@ -13,6 +13,7 @@ using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.WindowsApi; using SafeExamBrowser.WindowsApi.Constants; @@ -231,20 +232,50 @@ namespace SafeExamBrowser.WindowsApi public void RegisterKeyboardHook(IKeyboardInterceptor interceptor) { - var hook = new KeyboardHook(interceptor); + var hookReadyEvent = new AutoResetEvent(false); + var hookThread = new Thread(() => + { + var hook = new KeyboardHook(interceptor); - hook.Attach(); + hook.Attach(); + KeyboardHooks[hook.Handle] = hook; + hookReadyEvent.Set(); - KeyboardHooks[hook.Handle] = hook; + while (true) + { + hook.InputEvent.WaitOne(); + } + }); + + hookThread.SetApartmentState(ApartmentState.STA); + hookThread.IsBackground = true; + hookThread.Start(); + + hookReadyEvent.WaitOne(); } public void RegisterMouseHook(IMouseInterceptor interceptor) { - var hook = new MouseHook(interceptor); + var hookReadyEvent = new AutoResetEvent(false); + var hookThread = new Thread(() => + { + var hook = new MouseHook(interceptor); - hook.Attach(); + hook.Attach(); + MouseHooks[hook.Handle] = hook; + hookReadyEvent.Set(); - MouseHooks[hook.Handle] = hook; + while (true) + { + hook.InputEvent.WaitOne(); + } + }); + + hookThread.SetApartmentState(ApartmentState.STA); + hookThread.IsBackground = true; + hookThread.Start(); + + hookReadyEvent.WaitOne(); } public IntPtr RegisterSystemForegroundEvent(Action callback) diff --git a/SafeExamBrowser.WindowsApi/Process.cs b/SafeExamBrowser.WindowsApi/Process.cs index c09b9c62..70c29487 100644 --- a/SafeExamBrowser.WindowsApi/Process.cs +++ b/SafeExamBrowser.WindowsApi/Process.cs @@ -24,7 +24,7 @@ namespace SafeExamBrowser.WindowsApi public bool HasTerminated { - get { return process.HasExited; } + get { process.Refresh(); return process.HasExited; } } public Process(int id) @@ -36,7 +36,12 @@ namespace SafeExamBrowser.WindowsApi public void Kill() { - process.Kill(); + process.Refresh(); + + if (!process.HasExited) + { + process.Kill(); + } } private void Process_Exited(object sender, System.EventArgs e)