/* * Copyright (c) 2024 ETH Zürich, IT Services * * 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.WindowsApi.Constants; using SafeExamBrowser.WindowsApi.Contracts.Events; using SafeExamBrowser.WindowsApi.Delegates; using SafeExamBrowser.WindowsApi.Types; namespace SafeExamBrowser.WindowsApi.Hooks { internal class MouseHook { private MouseHookCallback callback; private IntPtr handle; private HookDelegate hookDelegate; internal Guid Id { get; private set; } internal MouseHook(MouseHookCallback callback) { this.callback = callback; this.Id = Guid.NewGuid(); } internal void Attach() { var process = System.Diagnostics.Process.GetCurrentProcess(); var module = process.MainModule; var moduleHandle = Kernel32.GetModuleHandle(module.ModuleName); // IMPORTANT: // 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! hookDelegate = new HookDelegate(LowLevelMouseProc); handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookDelegate, moduleHandle, 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()); var info = GetInfo(mouseData); if (callback(button, state, info)) { 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; case Constant.WM_XBUTTONDOWN: case Constant.WM_XBUTTONUP: return MouseButton.Auxiliary; default: return MouseButton.Unknown; } } private MouseInformation GetInfo(MSLLHOOKSTRUCT mouseData) { var info = new MouseInformation(); var extraInfo = mouseData.DwExtraInfo.ToUInt32(); info.IsTouch = (extraInfo & Constant.MOUSEEVENTF_MASK) == Constant.MOUSEEVENTF_FROMTOUCH; info.X = mouseData.Point.X; info.Y = mouseData.Point.Y; return info; } private MouseButtonState GetState(int wParam) { switch (wParam) { case Constant.WM_LBUTTONDOWN: case Constant.WM_MBUTTONDOWN: case Constant.WM_RBUTTONDOWN: case Constant.WM_XBUTTONDOWN: return MouseButtonState.Pressed; case Constant.WM_LBUTTONUP: case Constant.WM_MBUTTONUP: case Constant.WM_RBUTTONUP: case Constant.WM_XBUTTONUP: return MouseButtonState.Released; default: return MouseButtonState.Unknown; } } } }