Implemented basic keyboard monitoring.
This commit is contained in:
parent
32980f9f97
commit
a79f0c6d77
17 changed files with 273 additions and 17 deletions
|
@ -62,6 +62,7 @@
|
|||
<ItemGroup>
|
||||
<Compile Include="Bounds.cs" />
|
||||
<Compile Include="Settings\BrowserSettings.cs" />
|
||||
<Compile Include="Settings\KeyboardSettings.cs" />
|
||||
<Compile Include="Settings\SettingsImpl.cs" />
|
||||
<Compile Include="WorkingArea.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
|
21
SafeExamBrowser.Configuration/Settings/KeyboardSettings.cs
Normal file
21
SafeExamBrowser.Configuration/Settings/KeyboardSettings.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.Settings
|
||||
{
|
||||
public class KeyboardSettings : IKeyboardSettings
|
||||
{
|
||||
public bool AllowAltTab => false;
|
||||
|
||||
public bool AllowEsc => false;
|
||||
|
||||
public bool AllowF5 => true;
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ namespace SafeExamBrowser.Configuration
|
|||
public SettingsImpl()
|
||||
{
|
||||
Browser = new BrowserSettings(this);
|
||||
Keyboard = new KeyboardSettings();
|
||||
}
|
||||
|
||||
public string AppDataFolderName => "SafeExamBrowser";
|
||||
|
@ -35,6 +36,8 @@ namespace SafeExamBrowser.Configuration
|
|||
|
||||
public IBrowserSettings Browser { get; private set; }
|
||||
|
||||
public IKeyboardSettings Keyboard { get; private set; }
|
||||
|
||||
public string LogFolderPath
|
||||
{
|
||||
get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppDataFolderName, "Logs"); }
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Contracts.Configuration.Settings
|
||||
{
|
||||
public interface IKeyboardSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the user may use the ALT+TAB shortcut.
|
||||
/// </summary>
|
||||
bool AllowAltTab { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the user may use the escape key.
|
||||
/// </summary>
|
||||
bool AllowEsc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the user may use the F5 key.
|
||||
/// </summary>
|
||||
bool AllowF5 { get; }
|
||||
}
|
||||
}
|
|
@ -25,6 +25,11 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
|
|||
/// </summary>
|
||||
IBrowserSettings Browser { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All keyboard-related settings.
|
||||
/// </summary>
|
||||
IKeyboardSettings Keyboard { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The path where the log files are to be stored.
|
||||
/// </summary>
|
||||
|
|
|
@ -11,8 +11,8 @@ namespace SafeExamBrowser.Contracts.Monitoring
|
|||
public interface IKeyboardInterceptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if the given key should be blocked, otherwise <c>false</c>.
|
||||
/// Returns <c>true</c> if the given key should be blocked, otherwise <c>false</c>. The key code corresponds to a Win32 Virtual-Key.
|
||||
/// </summary>
|
||||
bool Block(uint keyCode, KeyModifier modifier);
|
||||
bool Block(int keyCode, KeyModifier modifier, KeyState state);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace SafeExamBrowser.Contracts.Monitoring
|
|||
public enum KeyModifier
|
||||
{
|
||||
None = 0,
|
||||
Ctrl = 0b01,
|
||||
Alt = 0b10
|
||||
Alt = 0b1,
|
||||
Ctrl = 0b10
|
||||
}
|
||||
}
|
||||
|
|
17
SafeExamBrowser.Contracts/Monitoring/KeyState.cs
Normal file
17
SafeExamBrowser.Contracts/Monitoring/KeyState.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Contracts.Monitoring
|
||||
{
|
||||
public enum KeyState
|
||||
{
|
||||
None = 0,
|
||||
Pressed,
|
||||
Released
|
||||
}
|
||||
}
|
|
@ -67,6 +67,7 @@
|
|||
<Compile Include="Configuration\IApplicationInstance.cs" />
|
||||
<Compile Include="Configuration\INotificationInfo.cs" />
|
||||
<Compile Include="Configuration\Settings\IBrowserSettings.cs" />
|
||||
<Compile Include="Configuration\Settings\IKeyboardSettings.cs" />
|
||||
<Compile Include="Configuration\Settings\ISettings.cs" />
|
||||
<Compile Include="Behaviour\IShutdownController.cs" />
|
||||
<Compile Include="Behaviour\IStartupController.cs" />
|
||||
|
@ -85,6 +86,7 @@
|
|||
<Compile Include="Monitoring\IProcessMonitor.cs" />
|
||||
<Compile Include="Monitoring\IWindowMonitor.cs" />
|
||||
<Compile Include="Monitoring\KeyModifier.cs" />
|
||||
<Compile Include="Monitoring\KeyState.cs" />
|
||||
<Compile Include="UserInterface\IBrowserControl.cs" />
|
||||
<Compile Include="UserInterface\IBrowserWindow.cs" />
|
||||
<Compile Include="UserInterface\IMessageBox.cs" />
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Contracts.Behaviour;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
using SafeExamBrowser.Contracts.Monitoring;
|
||||
using SafeExamBrowser.Contracts.UserInterface;
|
||||
using SafeExamBrowser.Contracts.WindowsApi;
|
||||
|
||||
namespace SafeExamBrowser.Core.Behaviour.Operations
|
||||
{
|
||||
public class DeviceInterceptionOperation : IOperation
|
||||
{
|
||||
private IKeyboardInterceptor keyboardInterceptor;
|
||||
private ILogger logger;
|
||||
private INativeMethods nativeMethods;
|
||||
|
||||
public ISplashScreen SplashScreen { private get; set; }
|
||||
|
||||
public DeviceInterceptionOperation(IKeyboardInterceptor keyboardInterceptor, ILogger logger, INativeMethods nativeMethods)
|
||||
{
|
||||
this.keyboardInterceptor = keyboardInterceptor;
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
}
|
||||
|
||||
public void Perform()
|
||||
{
|
||||
logger.Info("Starting keyboard and mouse interception...");
|
||||
|
||||
nativeMethods.RegisterKeyboardHook(keyboardInterceptor);
|
||||
}
|
||||
|
||||
public void Revert()
|
||||
{
|
||||
logger.Info("Stopping keyboard and mouse interception...");
|
||||
|
||||
nativeMethods.UnregisterKeyboardHook(keyboardInterceptor);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,6 +58,7 @@
|
|||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Behaviour\Operations\DeviceInterceptionOperation.cs" />
|
||||
<Compile Include="Behaviour\RuntimeController.cs" />
|
||||
<Compile Include="Behaviour\Operations\BrowserOperation.cs" />
|
||||
<Compile Include="Behaviour\Operations\EventControllerOperation.cs" />
|
||||
|
|
72
SafeExamBrowser.Monitoring/Keyboard/KeyboardInterceptor.cs
Normal file
72
SafeExamBrowser.Monitoring/Keyboard/KeyboardInterceptor.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
using SafeExamBrowser.Contracts.Monitoring;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.Keyboard
|
||||
{
|
||||
public class KeyboardInterceptor : IKeyboardInterceptor
|
||||
{
|
||||
private IKeyboardSettings settings;
|
||||
private ILogger logger;
|
||||
|
||||
public KeyboardInterceptor(IKeyboardSettings settings, ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public bool Block(int keyCode, KeyModifier modifier, KeyState state)
|
||||
{
|
||||
var block = false;
|
||||
var key = KeyInterop.KeyFromVirtualKey(keyCode);
|
||||
|
||||
block |= key == Key.Apps;
|
||||
block |= key == Key.F1;
|
||||
block |= key == Key.F2;
|
||||
block |= key == Key.F3;
|
||||
block |= key == Key.F4;
|
||||
block |= key == Key.F6;
|
||||
block |= key == Key.F7;
|
||||
block |= key == Key.F8;
|
||||
block |= key == Key.F9;
|
||||
block |= key == Key.F10;
|
||||
block |= key == Key.F11;
|
||||
block |= key == Key.F12;
|
||||
block |= key == Key.PrintScreen;
|
||||
|
||||
block |= key == Key.Escape && modifier.HasFlag(KeyModifier.Alt);
|
||||
block |= key == Key.Escape && modifier.HasFlag(KeyModifier.Ctrl);
|
||||
block |= key == Key.Space && modifier.HasFlag(KeyModifier.Alt);
|
||||
|
||||
block |= !settings.AllowAltTab && key == Key.Tab && modifier.HasFlag(KeyModifier.Alt);
|
||||
block |= !settings.AllowEsc && key == Key.Escape && modifier == KeyModifier.None;
|
||||
block |= !settings.AllowF5 && key == Key.F5;
|
||||
|
||||
if (block)
|
||||
{
|
||||
Log(key, keyCode, modifier, state);
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
private void Log(Key key, int keyCode, KeyModifier modifier, KeyState state)
|
||||
{
|
||||
var modifierFlags = Enum.GetValues(typeof(KeyModifier)).OfType<KeyModifier>().Where(m => m != KeyModifier.None && modifier.HasFlag(m));
|
||||
var modifiers = modifierFlags.Any() ? String.Join(" + ", modifierFlags) + " + " : string.Empty;
|
||||
|
||||
logger.Info($"Blocked '{modifiers}{key}' ({key} = {keyCode}) when {state.ToString().ToLower()}.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,8 +57,10 @@
|
|||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Keyboard\KeyboardInterceptor.cs" />
|
||||
<Compile Include="Processes\ProcessMonitor.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Windows\WindowMonitor.cs" />
|
||||
|
@ -70,7 +72,6 @@
|
|||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Keyboard\" />
|
||||
<Folder Include="Mouse\" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
|
|
@ -59,7 +59,7 @@ namespace SafeExamBrowser.WindowsApi.Constants
|
|||
|
||||
/// <summary>
|
||||
/// Posted to the window with the keyboard focus when a nonsystem key is released. A nonsystem key is a key that is pressed when
|
||||
/// the ALT key is not pressed, or a keyboard key that is pressed when a window has the keyboard focus.
|
||||
/// the ALT key is not pressed, or a keyboard key that is pressed when a window has the keyboard focus.
|
||||
///
|
||||
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646281(v=vs.85).aspx.
|
||||
/// </summary>
|
||||
|
|
|
@ -16,6 +16,15 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
|
|||
{
|
||||
internal class KeyboardHook
|
||||
{
|
||||
private const int LEFT_CTRL = 162;
|
||||
private const int RIGHT_CTRL = 163;
|
||||
private const int LEFT_ALT = 164;
|
||||
private const int RIGHT_ALT = 165;
|
||||
private const int DELETE = 46;
|
||||
|
||||
private bool altPressed, ctrlPressed;
|
||||
private HookProc hookProc;
|
||||
|
||||
internal IntPtr Handle { get; private set; }
|
||||
internal IKeyboardInterceptor Interceptor { get; private set; }
|
||||
|
||||
|
@ -28,7 +37,12 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
|
|||
{
|
||||
var module = Kernel32.GetModuleHandle(null);
|
||||
|
||||
Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, LowLevelKeyboardProc, module, 0);
|
||||
// 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 HookProc(LowLevelKeyboardProc);
|
||||
|
||||
Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookProc, module, 0);
|
||||
}
|
||||
|
||||
internal bool Detach()
|
||||
|
@ -40,10 +54,11 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
|
|||
{
|
||||
if (nCode >= 0)
|
||||
{
|
||||
var state = GetState(wParam.ToInt32());
|
||||
var keyData = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
|
||||
var modifier = GetModifiers(keyData);
|
||||
var modifier = GetModifiers(keyData, wParam.ToInt32());
|
||||
|
||||
if (Interceptor.Block(keyData.KeyCode, modifier))
|
||||
if (Interceptor.Block((int) keyData.KeyCode, modifier, state))
|
||||
{
|
||||
return (IntPtr) 1;
|
||||
}
|
||||
|
@ -52,21 +67,64 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
|
|||
return User32.CallNextHookEx(Handle, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
private KeyModifier GetModifiers(KBDLLHOOKSTRUCT keyData)
|
||||
private KeyState GetState(int wParam)
|
||||
{
|
||||
switch (wParam)
|
||||
{
|
||||
case Constant.WM_KEYDOWN:
|
||||
case Constant.WM_SYSKEYDOWN:
|
||||
return KeyState.Pressed;
|
||||
case Constant.WM_KEYUP:
|
||||
case Constant.WM_SYSKEYUP:
|
||||
return KeyState.Released;
|
||||
default:
|
||||
return KeyState.None;
|
||||
}
|
||||
}
|
||||
|
||||
private KeyModifier GetModifiers(KBDLLHOOKSTRUCT keyData, int wParam)
|
||||
{
|
||||
var modifier = KeyModifier.None;
|
||||
|
||||
if ((keyData.Flags & KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN) == KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN)
|
||||
TrackCtrlAndAlt(keyData, wParam);
|
||||
|
||||
if (altPressed || keyData.Flags.HasFlag(KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN))
|
||||
{
|
||||
modifier |= KeyModifier.Alt;
|
||||
}
|
||||
|
||||
if (keyData.Flags == 0)
|
||||
if (ctrlPressed)
|
||||
{
|
||||
modifier |= KeyModifier.Ctrl;
|
||||
}
|
||||
|
||||
return modifier;
|
||||
}
|
||||
|
||||
private void TrackCtrlAndAlt(KBDLLHOOKSTRUCT keyData, int wParam)
|
||||
{
|
||||
var keyCode = keyData.KeyCode;
|
||||
|
||||
if (keyCode == LEFT_CTRL || keyCode == RIGHT_CTRL)
|
||||
{
|
||||
ctrlPressed = IsPressed(wParam);
|
||||
}
|
||||
else if (keyCode == LEFT_ALT || keyCode == RIGHT_ALT)
|
||||
{
|
||||
altPressed = IsPressed(wParam);
|
||||
}
|
||||
|
||||
if (ctrlPressed && altPressed && keyCode == DELETE)
|
||||
{
|
||||
// When the Secure Attention Sequence is pressed, the WM_KEYUP / WM_SYSKEYUP messages for CTRL and ALT get lost...
|
||||
ctrlPressed = false;
|
||||
altPressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPressed(int wParam)
|
||||
{
|
||||
return wParam == Constant.WM_KEYDOWN || wParam == Constant.WM_SYSKEYDOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace SafeExamBrowser.WindowsApi
|
|||
private ConcurrentDictionary<IntPtr, KeyboardHook> KeyboardHooks = new ConcurrentDictionary<IntPtr, KeyboardHook>();
|
||||
|
||||
/// <summary>
|
||||
/// Upon finalization, unregister all system events and hooks...
|
||||
/// Upon finalization, unregister all active system events and hooks...
|
||||
/// </summary>
|
||||
~NativeMethods()
|
||||
{
|
||||
|
@ -144,9 +144,6 @@ namespace SafeExamBrowser.WindowsApi
|
|||
|
||||
hook.Attach();
|
||||
|
||||
// IMORTANT:
|
||||
// Ensures that the hook 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!
|
||||
KeyboardHooks[hook.Handle] = hook;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ using SafeExamBrowser.Core.Behaviour;
|
|||
using SafeExamBrowser.Core.Behaviour.Operations;
|
||||
using SafeExamBrowser.Core.I18n;
|
||||
using SafeExamBrowser.Core.Logging;
|
||||
using SafeExamBrowser.Monitoring.Keyboard;
|
||||
using SafeExamBrowser.Monitoring.Processes;
|
||||
using SafeExamBrowser.Monitoring.Windows;
|
||||
using SafeExamBrowser.UserInterface;
|
||||
|
@ -32,10 +33,11 @@ namespace SafeExamBrowser
|
|||
{
|
||||
private IApplicationController browserController;
|
||||
private IApplicationInfo browserInfo;
|
||||
private IRuntimeController runtimeController;
|
||||
private IKeyboardInterceptor keyboardInterceptor;
|
||||
private ILogger logger;
|
||||
private INativeMethods nativeMethods;
|
||||
private IProcessMonitor processMonitor;
|
||||
private IRuntimeController runtimeController;
|
||||
private ISettings settings;
|
||||
private IText text;
|
||||
private ITextResource textResource;
|
||||
|
@ -62,6 +64,7 @@ namespace SafeExamBrowser
|
|||
|
||||
text = new Text(textResource);
|
||||
browserController = new BrowserApplicationController(settings, text, uiFactory);
|
||||
keyboardInterceptor = new KeyboardInterceptor(settings.Keyboard, new ModuleLogger(logger, typeof(KeyboardInterceptor)));
|
||||
processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods);
|
||||
windowMonitor = new WindowMonitor(new ModuleLogger(logger, typeof(WindowMonitor)), nativeMethods);
|
||||
workingArea = new WorkingArea(new ModuleLogger(logger, typeof(WorkingArea)), nativeMethods);
|
||||
|
@ -71,6 +74,7 @@ namespace SafeExamBrowser
|
|||
StartupController = new StartupController(logger, settings, text, uiFactory);
|
||||
|
||||
StartupOperations = new Queue<IOperation>();
|
||||
StartupOperations.Enqueue(new DeviceInterceptionOperation(keyboardInterceptor, logger, nativeMethods));
|
||||
StartupOperations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
|
||||
StartupOperations.Enqueue(new ProcessMonitorOperation(logger, processMonitor));
|
||||
StartupOperations.Enqueue(new WorkingAreaOperation(logger, Taskbar, workingArea));
|
||||
|
|
Loading…
Reference in a new issue