SEBWIN-220: Finally found a solution to run keyboard & mouse hooks in sepearate threads.
This commit is contained in:
parent
b31ba1c2d5
commit
b50c208f46
10 changed files with 87 additions and 37 deletions
|
@ -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);
|
||||
|
|
|
@ -16,7 +16,7 @@ using SafeExamBrowser.Core.Operations;
|
|||
namespace SafeExamBrowser.Core.UnitTests.Operations
|
||||
{
|
||||
[TestClass]
|
||||
public class DelayedInitializationOperationTests
|
||||
public class LazyInitializationOperationTests
|
||||
{
|
||||
private Mock<IOperation> 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<IProgressIndicator>().Object
|
||||
};
|
||||
|
@ -128,7 +128,7 @@ namespace SafeExamBrowser.Core.UnitTests.Operations
|
|||
return new Mock<IOperation>().Object;
|
||||
};
|
||||
|
||||
var sut = new DelayedInitializationOperation(initialize);
|
||||
var sut = new LazyInitializationOperation(initialize);
|
||||
|
||||
sut.Perform();
|
||||
sut.Repeat();
|
|
@ -79,7 +79,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Operations\CommunicationOperationTests.cs" />
|
||||
<Compile Include="Operations\DelayedInitializationOperationTests.cs" />
|
||||
<Compile Include="Operations\LazyInitializationOperationTests.cs" />
|
||||
<Compile Include="Operations\I18nOperationTests.cs" />
|
||||
<Compile Include="Operations\DelegateOperationTests.cs" />
|
||||
<Compile Include="OperationModel\OperationSequenceTests.cs" />
|
||||
|
|
|
@ -13,18 +13,18 @@ using SafeExamBrowser.Contracts.UserInterface;
|
|||
namespace SafeExamBrowser.Core.Operations
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 <see cref="IOperationSequence"/> has finished.
|
||||
/// A wrapper operation to allow for a lazy (just-in-time) instantiation of an operation, initialized on <see cref="Perform"/>.
|
||||
/// 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 <see cref="IOperationSequence"/> has finished.
|
||||
/// </summary>
|
||||
public class DelayedInitializationOperation : IOperation
|
||||
public class LazyInitializationOperation : IOperation
|
||||
{
|
||||
private Func<IOperation> initialize;
|
||||
private IOperation operation;
|
||||
|
||||
public IProgressIndicator ProgressIndicator { get; set; }
|
||||
|
||||
public DelayedInitializationOperation(Func<IOperation> initialize)
|
||||
public LazyInitializationOperation(Func<IOperation> initialize)
|
||||
{
|
||||
this.initialize = initialize;
|
||||
}
|
|
@ -55,7 +55,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Operations\CommunicationOperation.cs" />
|
||||
<Compile Include="Operations\DelayedInitializationOperation.cs" />
|
||||
<Compile Include="Operations\LazyInitializationOperation.cs" />
|
||||
<Compile Include="Operations\I18nOperation.cs" />
|
||||
<Compile Include="Operations\DelegateOperation.cs" />
|
||||
<Compile Include="OperationModel\OperationSequence.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}!");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <c>CallbackOnCollectedDelegate</c> 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));
|
||||
|
|
|
@ -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 <c>CallbackOnCollectedDelegate</c> 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));
|
||||
|
|
|
@ -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<IntPtr> callback)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue