SEBWIN-220: Finally found a solution to run keyboard & mouse hooks in sepearate threads.

This commit is contained in:
dbuechel 2018-09-25 12:10:34 +02:00
parent b31ba1c2d5
commit b50c208f46
10 changed files with 87 additions and 37 deletions

View file

@ -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);

View file

@ -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();

View file

@ -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" />

View file

@ -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;
}

View file

@ -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" />

View file

@ -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}!");
}
}

View file

@ -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));

View file

@ -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));

View file

@ -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)

View file

@ -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)