diff --git a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj
index 7f770954..e263f7dc 100644
--- a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj
+++ b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj
@@ -63,6 +63,7 @@
+
diff --git a/SafeExamBrowser.Configuration/Settings/MouseSettings.cs b/SafeExamBrowser.Configuration/Settings/MouseSettings.cs
new file mode 100644
index 00000000..f911db24
--- /dev/null
+++ b/SafeExamBrowser.Configuration/Settings/MouseSettings.cs
@@ -0,0 +1,19 @@
+/*
+ * 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 MouseSettings : IMouseSettings
+ {
+ public bool AllowMiddleButton => false;
+
+ public bool AllowRightButton => false;
+ }
+}
diff --git a/SafeExamBrowser.Configuration/Settings/SettingsImpl.cs b/SafeExamBrowser.Configuration/Settings/SettingsImpl.cs
index c0b95eb7..acce0f86 100644
--- a/SafeExamBrowser.Configuration/Settings/SettingsImpl.cs
+++ b/SafeExamBrowser.Configuration/Settings/SettingsImpl.cs
@@ -25,6 +25,7 @@ namespace SafeExamBrowser.Configuration
{
Browser = new BrowserSettings(this);
Keyboard = new KeyboardSettings();
+ Mouse = new MouseSettings();
}
public string AppDataFolderName => "SafeExamBrowser";
@@ -38,6 +39,8 @@ namespace SafeExamBrowser.Configuration
public IKeyboardSettings Keyboard { get; private set; }
+ public IMouseSettings Mouse { get; private set; }
+
public string LogFolderPath
{
get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppDataFolderName, "Logs"); }
diff --git a/SafeExamBrowser.Contracts/Configuration/Settings/IMouseSettings.cs b/SafeExamBrowser.Contracts/Configuration/Settings/IMouseSettings.cs
new file mode 100644
index 00000000..3697e0c0
--- /dev/null
+++ b/SafeExamBrowser.Contracts/Configuration/Settings/IMouseSettings.cs
@@ -0,0 +1,23 @@
+/*
+ * 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 IMouseSettings
+ {
+ ///
+ /// Determines whether the user may use the middle mouse button.
+ ///
+ bool AllowMiddleButton { get; }
+
+ ///
+ /// Determines whether the user may use the right mouse button.
+ ///
+ bool AllowRightButton { get; }
+ }
+}
diff --git a/SafeExamBrowser.Contracts/Configuration/Settings/ISettings.cs b/SafeExamBrowser.Contracts/Configuration/Settings/ISettings.cs
index 2129ac26..855f291e 100644
--- a/SafeExamBrowser.Contracts/Configuration/Settings/ISettings.cs
+++ b/SafeExamBrowser.Contracts/Configuration/Settings/ISettings.cs
@@ -30,6 +30,11 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
///
IKeyboardSettings Keyboard { get; }
+ ///
+ /// All mouse-related settings.
+ ///
+ IMouseSettings Mouse { get; }
+
///
/// The path where the log files are to be stored.
///
diff --git a/SafeExamBrowser.Contracts/Monitoring/IMouseInterceptor.cs b/SafeExamBrowser.Contracts/Monitoring/IMouseInterceptor.cs
index a69d7f2e..fb74aa9a 100644
--- a/SafeExamBrowser.Contracts/Monitoring/IMouseInterceptor.cs
+++ b/SafeExamBrowser.Contracts/Monitoring/IMouseInterceptor.cs
@@ -10,5 +10,9 @@ namespace SafeExamBrowser.Contracts.Monitoring
{
public interface IMouseInterceptor
{
+ ///
+ /// Returns true if the given button should be blocked, otherwise false.
+ ///
+ bool Block(MouseButton button, KeyState state);
}
}
diff --git a/SafeExamBrowser.Contracts/Monitoring/MouseButton.cs b/SafeExamBrowser.Contracts/Monitoring/MouseButton.cs
new file mode 100644
index 00000000..9b80cea5
--- /dev/null
+++ b/SafeExamBrowser.Contracts/Monitoring/MouseButton.cs
@@ -0,0 +1,18 @@
+/*
+ * 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 MouseButton
+ {
+ None = 0,
+ Left,
+ Middle,
+ Right
+ }
+}
diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
index 2b11f3c8..87709b55 100644
--- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
+++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
@@ -68,6 +68,7 @@
+
@@ -87,6 +88,7 @@
+
diff --git a/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs b/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs
index 8cd0145c..2cf66181 100644
--- a/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs
+++ b/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs
@@ -14,6 +14,30 @@ namespace SafeExamBrowser.Contracts.WindowsApi
{
public interface INativeMethods
{
+ ///
+ /// Deregisters the system hook for the given keyboard interceptor.
+ ///
+ ///
+ /// If the hook for the given interceptor could not be successfully removed.
+ ///
+ void DeregisterKeyboardHook(IKeyboardInterceptor interceptor);
+
+ ///
+ /// Deregisters the system hook for the given mouse interceptor.
+ ///
+ ///
+ /// If the hook for the given interceptor could not be successfully removed.
+ ///
+ void DeregisterMouseHook(IMouseInterceptor interceptor);
+
+ ///
+ /// Deregisters a previously registered system event.
+ ///
+ ///
+ /// If the event hook could not be successfully removed.
+ ///
+ void DeregisterSystemEvent(IntPtr handle);
+
///
/// Retrieves a collection of handles to all currently open (i.e. visible) windows.
///
@@ -76,6 +100,11 @@ namespace SafeExamBrowser.Contracts.WindowsApi
///
void RegisterKeyboardHook(IKeyboardInterceptor interceptor);
+ ///
+ /// Registers a system hook for the given mouse interceptor.
+ ///
+ void RegisterMouseHook(IMouseInterceptor interceptor);
+
///
/// Registers a system event which will invoke the specified callback when the foreground window has changed.
/// Returns a handle to the newly registered Windows event hook.
@@ -105,21 +134,5 @@ namespace SafeExamBrowser.Contracts.WindowsApi
/// If the working area could not be set.
///
void SetWorkingArea(IBounds bounds);
-
- ///
- /// Unregisters the system hook for the given keyboard interceptor.
- ///
- ///
- /// If the hook for the given interceptor could not be successfully removed.
- ///
- void UnregisterKeyboardHook(IKeyboardInterceptor interceptor);
-
- ///
- /// Unregisters a previously registered system event.
- ///
- ///
- /// If the event hook could not be successfully removed.
- ///
- void UnregisterSystemEvent(IntPtr handle);
}
}
diff --git a/SafeExamBrowser.Core/Behaviour/Operations/DeviceInterceptionOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/DeviceInterceptionOperation.cs
index 7315d799..79ba4663 100644
--- a/SafeExamBrowser.Core/Behaviour/Operations/DeviceInterceptionOperation.cs
+++ b/SafeExamBrowser.Core/Behaviour/Operations/DeviceInterceptionOperation.cs
@@ -18,14 +18,20 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
{
private IKeyboardInterceptor keyboardInterceptor;
private ILogger logger;
+ private IMouseInterceptor mouseInterceptor;
private INativeMethods nativeMethods;
public ISplashScreen SplashScreen { private get; set; }
- public DeviceInterceptionOperation(IKeyboardInterceptor keyboardInterceptor, ILogger logger, INativeMethods nativeMethods)
+ public DeviceInterceptionOperation(
+ IKeyboardInterceptor keyboardInterceptor,
+ ILogger logger,
+ IMouseInterceptor mouseInterceptor,
+ INativeMethods nativeMethods)
{
this.keyboardInterceptor = keyboardInterceptor;
this.logger = logger;
+ this.mouseInterceptor = mouseInterceptor;
this.nativeMethods = nativeMethods;
}
@@ -34,13 +40,15 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
logger.Info("Starting keyboard and mouse interception...");
nativeMethods.RegisterKeyboardHook(keyboardInterceptor);
+ nativeMethods.RegisterMouseHook(mouseInterceptor);
}
public void Revert()
{
logger.Info("Stopping keyboard and mouse interception...");
- nativeMethods.UnregisterKeyboardHook(keyboardInterceptor);
+ nativeMethods.DeregisterMouseHook(mouseInterceptor);
+ nativeMethods.DeregisterKeyboardHook(keyboardInterceptor);
}
}
}
diff --git a/SafeExamBrowser.Monitoring/Mouse/MouseInterceptor.cs b/SafeExamBrowser.Monitoring/Mouse/MouseInterceptor.cs
new file mode 100644
index 00000000..77d3beef
--- /dev/null
+++ b/SafeExamBrowser.Monitoring/Mouse/MouseInterceptor.cs
@@ -0,0 +1,41 @@
+/*
+ * 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;
+using SafeExamBrowser.Contracts.Logging;
+using SafeExamBrowser.Contracts.Monitoring;
+
+namespace SafeExamBrowser.Monitoring.Mouse
+{
+ public class MouseInterceptor : IMouseInterceptor
+ {
+ private ILogger logger;
+ private IMouseSettings settings;
+
+ public MouseInterceptor(ILogger logger, IMouseSettings settings)
+ {
+ this.logger = logger;
+ this.settings = settings;
+ }
+
+ public bool Block(MouseButton button, KeyState state)
+ {
+ var block = false;
+
+ block |= !settings.AllowMiddleButton && button == MouseButton.Middle;
+ block |= !settings.AllowRightButton && button == MouseButton.Right;
+
+ if (block)
+ {
+ logger.Info($"Blocked {button.ToString().ToLower()} mouse button when {state.ToString().ToLower()}.");
+ }
+
+ return block;
+ }
+ }
+}
diff --git a/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj b/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
index c2e4117b..6f4187fe 100644
--- a/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
+++ b/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
@@ -61,6 +61,7 @@
+
@@ -71,8 +72,6 @@
SafeExamBrowser.Contracts
-
-
-
+
\ No newline at end of file
diff --git a/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs b/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs
index 1f170474..84256968 100644
--- a/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs
+++ b/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs
@@ -102,13 +102,13 @@ namespace SafeExamBrowser.Monitoring.Windows
{
if (captureStartHookHandle != IntPtr.Zero)
{
- nativeMethods.UnregisterSystemEvent(captureStartHookHandle);
+ nativeMethods.DeregisterSystemEvent(captureStartHookHandle);
logger.Info($"Unregistered system capture start event with handle = {captureStartHookHandle}.");
}
if (foregroundHookHandle != IntPtr.Zero)
{
- nativeMethods.UnregisterSystemEvent(foregroundHookHandle);
+ nativeMethods.DeregisterSystemEvent(foregroundHookHandle);
logger.Info($"Unregistered system foreground event with handle = {foregroundHookHandle}.");
}
}
diff --git a/SafeExamBrowser.WindowsApi/Constants/Constant.cs b/SafeExamBrowser.WindowsApi/Constants/Constant.cs
index cedc121f..a531b98a 100644
--- a/SafeExamBrowser.WindowsApi/Constants/Constant.cs
+++ b/SafeExamBrowser.WindowsApi/Constants/Constant.cs
@@ -65,6 +65,77 @@ namespace SafeExamBrowser.WindowsApi.Constants
///
internal const int WM_KEYUP = 0x101;
+ ///
+ /// Posted when the user presses the left mouse button while the cursor is in the client area of a window. If the mouse is not
+ /// captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
+ /// captured the mouse.
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms645607(v=vs.85).aspx.
+ ///
+ internal const int WM_LBUTTONDOWN = 0x201;
+
+ ///
+ /// Posted when the user releases the left mouse button while the cursor is in the client area of a window. If the mouse is not
+ /// captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
+ /// captured the mouse.
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms645608(v=vs.85).aspx.
+ ///
+ internal const int WM_LBUTTONUP = 0x202;
+
+ ///
+ /// Posted when the user presses the middle mouse button while the cursor is in the client area of a window. If the mouse is not
+ /// captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
+ /// captured the mouse.
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms645610(v=vs.85).aspx.
+ ///
+ internal const int WM_MBUTTONDOWN = 0x207;
+
+ ///
+ /// Posted when the user releases the middle mouse button while the cursor is in the client area of a window. If the mouse is not
+ /// captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
+ /// captured the mouse.
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms645611(v=vs.85).aspx.
+ ///
+ internal const int WM_MBUTTONUP = 0x208;
+
+ ///
+ /// Posted to a window when the cursor moves. If the mouse is not captured, the message is posted to the window that contains the
+ /// cursor. Otherwise, the message is posted to the window that has captured the mouse.
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms645616(v=vs.85).aspx.
+ ///
+ internal const int WM_MOUSEMOVE = 0x200;
+
+ ///
+ /// Sent to the focus window when the mouse wheel is rotated. The DefWindowProc function propagates the message to the window's
+ /// parent. There should be no internal forwarding of the message, since DefWindowProc propagates it up the parent chain until i
+ /// finds a window that processes it.
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms645617(v=vs.85).aspx.
+ ///
+ internal const int WM_MOUSEWHEEL = 0x20A;
+
+ ///
+ /// Posted when the user presses the right mouse button while the cursor is in the client area of a window. If the mouse is not
+ /// captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
+ /// captured the mouse.
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646242(v=vs.85).aspx.
+ ///
+ internal const int WM_RBUTTONDOWN = 0x204;
+
+ ///
+ /// Posted when the user releases the right mouse button while the cursor is in the client area of a window. If the mouse is not
+ /// captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
+ /// captured the mouse.
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646243(v=vs.85).aspx.
+ ///
+ internal const int WM_RBUTTONUP = 0x205;
+
///
/// A window receives this message when the user chooses a command from the Window menu (formerly known as the system or control
/// menu) or when the user chooses the maximize button, minimize button, restore button, or close button.
diff --git a/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs b/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs
index c9c5a837..f1a9c02d 100644
--- a/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs
+++ b/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs
@@ -54,9 +54,9 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
{
if (nCode >= 0)
{
- var state = GetState(wParam.ToInt32());
var keyData = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
var modifier = GetModifiers(keyData, wParam.ToInt32());
+ var state = GetState(wParam.ToInt32());
if (Interceptor.Block((int) keyData.KeyCode, modifier, state))
{
diff --git a/SafeExamBrowser.WindowsApi/Monitoring/MouseHook.cs b/SafeExamBrowser.WindowsApi/Monitoring/MouseHook.cs
new file mode 100644
index 00000000..3aa74f08
--- /dev/null
+++ b/SafeExamBrowser.WindowsApi/Monitoring/MouseHook.cs
@@ -0,0 +1,104 @@
+/*
+ * 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.Runtime.InteropServices;
+using SafeExamBrowser.Contracts.Monitoring;
+using SafeExamBrowser.WindowsApi.Constants;
+using SafeExamBrowser.WindowsApi.Types;
+
+namespace SafeExamBrowser.WindowsApi.Monitoring
+{
+ internal class MouseHook
+ {
+ private HookProc hookProc;
+
+ internal IntPtr Handle { get; private set; }
+ internal IMouseInterceptor Interceptor { get; private set; }
+
+ internal MouseHook(IMouseInterceptor interceptor)
+ {
+ Interceptor = interceptor;
+ }
+
+ internal void Attach()
+ {
+ var module = Kernel32.GetModuleHandle(null);
+
+ // 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 HookProc(LowLevelMouseProc);
+
+ Handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookProc, module, 0);
+ }
+
+ internal bool Detach()
+ {
+ return User32.UnhookWindowsHookEx(Handle);
+ }
+
+ private IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
+ {
+ if (nCode >= 0 && !Ignore(wParam.ToInt32()))
+ {
+ var mouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
+ var button = GetButton(wParam.ToInt32());
+ var state = GetState(wParam.ToInt32());
+
+ if (Interceptor.Block(button, state))
+ {
+ return (IntPtr) 1;
+ }
+ }
+
+ return User32.CallNextHookEx(Handle, nCode, wParam, lParam);
+ }
+
+ private bool Ignore(int wParam)
+ {
+ // For performance reasons, ignore mouse movement and wheel rotation...
+ return wParam == Constant.WM_MOUSEMOVE || wParam == Constant.WM_MOUSEWHEEL;
+ }
+
+ private MouseButton GetButton(int wParam)
+ {
+ switch (wParam)
+ {
+ case Constant.WM_LBUTTONDOWN:
+ case Constant.WM_LBUTTONUP:
+ return MouseButton.Left;
+ case Constant.WM_MBUTTONDOWN:
+ case Constant.WM_MBUTTONUP:
+ return MouseButton.Middle;
+ case Constant.WM_RBUTTONDOWN:
+ case Constant.WM_RBUTTONUP:
+ return MouseButton.Right;
+ default:
+ return MouseButton.None;
+ }
+ }
+
+ private KeyState GetState(int wParam)
+ {
+ switch (wParam)
+ {
+ case Constant.WM_LBUTTONDOWN:
+ case Constant.WM_MBUTTONDOWN:
+ case Constant.WM_RBUTTONDOWN:
+ return KeyState.Pressed;
+ case Constant.WM_LBUTTONUP:
+ case Constant.WM_MBUTTONUP:
+ case Constant.WM_RBUTTONUP:
+ return KeyState.Released;
+ default:
+ return KeyState.None;
+ }
+ }
+ }
+}
diff --git a/SafeExamBrowser.WindowsApi/NativeMethods.cs b/SafeExamBrowser.WindowsApi/NativeMethods.cs
index 0914e39b..aac9aff1 100644
--- a/SafeExamBrowser.WindowsApi/NativeMethods.cs
+++ b/SafeExamBrowser.WindowsApi/NativeMethods.cs
@@ -25,6 +25,7 @@ namespace SafeExamBrowser.WindowsApi
{
private ConcurrentDictionary EventDelegates = new ConcurrentDictionary();
private ConcurrentDictionary KeyboardHooks = new ConcurrentDictionary();
+ private ConcurrentDictionary MouseHooks = new ConcurrentDictionary();
///
/// Upon finalization, unregister all active system events and hooks...
@@ -40,6 +41,57 @@ namespace SafeExamBrowser.WindowsApi
{
User32.UnhookWindowsHookEx(handle);
}
+
+ foreach (var handle in MouseHooks.Keys)
+ {
+ User32.UnhookWindowsHookEx(handle);
+ }
+ }
+
+ public void DeregisterKeyboardHook(IKeyboardInterceptor interceptor)
+ {
+ var hook = KeyboardHooks.Values.FirstOrDefault(h => h.Interceptor == interceptor);
+
+ if (hook != null)
+ {
+ var success = hook.Detach();
+
+ if (!success)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ KeyboardHooks.TryRemove(hook.Handle, out KeyboardHook h);
+ }
+ }
+
+ public void DeregisterMouseHook(IMouseInterceptor interceptor)
+ {
+ var hook = MouseHooks.Values.FirstOrDefault(h => h.Interceptor == interceptor);
+
+ if (hook != null)
+ {
+ var success = hook.Detach();
+
+ if (!success)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ MouseHooks.TryRemove(hook.Handle, out MouseHook h);
+ }
+ }
+
+ public void DeregisterSystemEvent(IntPtr handle)
+ {
+ var success = User32.UnhookWinEvent(handle);
+
+ if (!success)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ EventDelegates.TryRemove(handle, out EventProc d);
}
public IEnumerable GetOpenWindows()
@@ -147,6 +199,15 @@ namespace SafeExamBrowser.WindowsApi
KeyboardHooks[hook.Handle] = hook;
}
+ public void RegisterMouseHook(IMouseInterceptor interceptor)
+ {
+ var hook = new MouseHook(interceptor);
+
+ hook.Attach();
+
+ MouseHooks[hook.Handle] = hook;
+ }
+
public IntPtr RegisterSystemForegroundEvent(Action callback)
{
EventProc eventProc = (IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) =>
@@ -201,34 +262,5 @@ namespace SafeExamBrowser.WindowsApi
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
-
- public void UnregisterKeyboardHook(IKeyboardInterceptor interceptor)
- {
- var hook = KeyboardHooks.Values.FirstOrDefault(h => h.Interceptor == interceptor);
-
- if (hook != null)
- {
- var success = hook.Detach();
-
- if (!success)
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
-
- KeyboardHooks.TryRemove(hook.Handle, out KeyboardHook h);
- }
- }
-
- public void UnregisterSystemEvent(IntPtr handle)
- {
- var success = User32.UnhookWinEvent(handle);
-
- if (!success)
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
-
- EventDelegates.TryRemove(handle, out EventProc d);
- }
}
}
diff --git a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj
index f7476236..900b809a 100644
--- a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj
+++ b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj
@@ -60,6 +60,7 @@
+
@@ -71,6 +72,8 @@
+
+
diff --git a/SafeExamBrowser.WindowsApi/Types/KBDLLHOOKSTRUCT.cs b/SafeExamBrowser.WindowsApi/Types/KBDLLHOOKSTRUCT.cs
index 06fb3cc2..ad66e4d0 100644
--- a/SafeExamBrowser.WindowsApi/Types/KBDLLHOOKSTRUCT.cs
+++ b/SafeExamBrowser.WindowsApi/Types/KBDLLHOOKSTRUCT.cs
@@ -15,17 +15,17 @@ namespace SafeExamBrowser.WindowsApi.Types
/// See http://www.pinvoke.net/default.aspx/Structures/KBDLLHOOKSTRUCT.html.
///
[StructLayout(LayoutKind.Sequential)]
- public struct KBDLLHOOKSTRUCT
+ internal struct KBDLLHOOKSTRUCT
{
///
/// A virtual-key code. The code must be a value in the range 1 to 254.
///
- public uint KeyCode;
+ internal uint KeyCode;
///
/// A hardware scan code for the key.
///
- public uint ScanCode;
+ internal uint ScanCode;
///
/// The extended-key flag, event-injected flags, context code, and transition-state flag. This member is specified as follows. An
@@ -33,16 +33,16 @@ namespace SafeExamBrowser.WindowsApi.Types
/// event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you whether or not the event was injected
/// from a process running at lower integrity level.
///
- public KBDLLHOOKSTRUCTFlags Flags;
+ internal KBDLLHOOKSTRUCTFlags Flags;
///
/// The time stamp for this message, equivalent to what GetMessageTime would return for this message.
///
- public uint Time;
+ internal uint Time;
///
/// Additional information associated with the message.
///
- public IntPtr DwExtraInfo;
+ internal IntPtr DwExtraInfo;
}
}
diff --git a/SafeExamBrowser.WindowsApi/Types/KBDLLHOOKSTRUCTFlags.cs b/SafeExamBrowser.WindowsApi/Types/KBDLLHOOKSTRUCTFlags.cs
index 0e38ac8f..1b2342fe 100644
--- a/SafeExamBrowser.WindowsApi/Types/KBDLLHOOKSTRUCTFlags.cs
+++ b/SafeExamBrowser.WindowsApi/Types/KBDLLHOOKSTRUCTFlags.cs
@@ -11,7 +11,7 @@ namespace SafeExamBrowser.WindowsApi.Types
///
/// See http://www.pinvoke.net/default.aspx/Structures/KBDLLHOOKSTRUCT.html.
///
- public enum KBDLLHOOKSTRUCTFlags
+ internal enum KBDLLHOOKSTRUCTFlags
{
///
/// Test the extended-key flag.
diff --git a/SafeExamBrowser.WindowsApi/Types/MSLLHOOKSTRUCT.cs b/SafeExamBrowser.WindowsApi/Types/MSLLHOOKSTRUCT.cs
new file mode 100644
index 00000000..c3cf36b6
--- /dev/null
+++ b/SafeExamBrowser.WindowsApi/Types/MSLLHOOKSTRUCT.cs
@@ -0,0 +1,23 @@
+/*
+ * 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.Runtime.InteropServices;
+
+namespace SafeExamBrowser.WindowsApi.Types
+{
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct MSLLHOOKSTRUCT
+ {
+ internal POINT Point;
+ internal int MouseData;
+ internal int Flags;
+ internal int Time;
+ internal UIntPtr DwExtraInfo;
+ }
+}
diff --git a/SafeExamBrowser.WindowsApi/Types/POINT.cs b/SafeExamBrowser.WindowsApi/Types/POINT.cs
new file mode 100644
index 00000000..ceaf74f1
--- /dev/null
+++ b/SafeExamBrowser.WindowsApi/Types/POINT.cs
@@ -0,0 +1,19 @@
+/*
+ * 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.Runtime.InteropServices;
+
+namespace SafeExamBrowser.WindowsApi.Types
+{
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct POINT
+ {
+ internal int X;
+ internal int Y;
+ }
+}
diff --git a/SafeExamBrowser/CompositionRoot.cs b/SafeExamBrowser/CompositionRoot.cs
index 78c5d2a6..a844b607 100644
--- a/SafeExamBrowser/CompositionRoot.cs
+++ b/SafeExamBrowser/CompositionRoot.cs
@@ -22,6 +22,7 @@ using SafeExamBrowser.Core.Behaviour.Operations;
using SafeExamBrowser.Core.I18n;
using SafeExamBrowser.Core.Logging;
using SafeExamBrowser.Monitoring.Keyboard;
+using SafeExamBrowser.Monitoring.Mouse;
using SafeExamBrowser.Monitoring.Processes;
using SafeExamBrowser.Monitoring.Windows;
using SafeExamBrowser.UserInterface;
@@ -35,6 +36,7 @@ namespace SafeExamBrowser
private IApplicationInfo browserInfo;
private IKeyboardInterceptor keyboardInterceptor;
private ILogger logger;
+ private IMouseInterceptor mouseInterceptor;
private INativeMethods nativeMethods;
private IProcessMonitor processMonitor;
private IRuntimeController runtimeController;
@@ -65,6 +67,7 @@ namespace SafeExamBrowser
text = new Text(textResource);
browserController = new BrowserApplicationController(settings, text, uiFactory);
keyboardInterceptor = new KeyboardInterceptor(settings.Keyboard, new ModuleLogger(logger, typeof(KeyboardInterceptor)));
+ mouseInterceptor = new MouseInterceptor(new ModuleLogger(logger, typeof(MouseInterceptor)), settings.Mouse);
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);
@@ -74,7 +77,7 @@ namespace SafeExamBrowser
StartupController = new StartupController(logger, settings, text, uiFactory);
StartupOperations = new Queue();
- StartupOperations.Enqueue(new DeviceInterceptionOperation(keyboardInterceptor, logger, nativeMethods));
+ StartupOperations.Enqueue(new DeviceInterceptionOperation(keyboardInterceptor, logger, mouseInterceptor, nativeMethods));
StartupOperations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
StartupOperations.Enqueue(new ProcessMonitorOperation(logger, processMonitor));
StartupOperations.Enqueue(new WorkingAreaOperation(logger, Taskbar, workingArea));