Restore SEBPatch
This commit is contained in:
541
SafeExamBrowser.WindowsApi/NativeMethods.cs
Normal file
541
SafeExamBrowser.WindowsApi/NativeMethods.cs
Normal file
@@ -0,0 +1,541 @@
|
||||
/*
|
||||
* 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.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using SafeExamBrowser.WindowsApi.Constants;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts.Events;
|
||||
using SafeExamBrowser.WindowsApi.Hooks;
|
||||
using SafeExamBrowser.WindowsApi.Types;
|
||||
|
||||
namespace SafeExamBrowser.WindowsApi
|
||||
{
|
||||
public class NativeMethods : INativeMethods
|
||||
{
|
||||
private readonly ConcurrentDictionary<Guid, KeyboardHook> KeyboardHooks = new ConcurrentDictionary<Guid, KeyboardHook>();
|
||||
private readonly ConcurrentDictionary<Guid, MouseHook> MouseHooks = new ConcurrentDictionary<Guid, MouseHook>();
|
||||
private readonly ConcurrentDictionary<Guid, SystemHook> SystemHooks = new ConcurrentDictionary<Guid, SystemHook>();
|
||||
|
||||
/// <summary>
|
||||
/// Upon finalization, unregister all active system events and hooks...
|
||||
/// </summary>
|
||||
~NativeMethods()
|
||||
{
|
||||
foreach (var hook in SystemHooks.Values)
|
||||
{
|
||||
hook.Detach();
|
||||
}
|
||||
|
||||
foreach (var hook in KeyboardHooks.Values)
|
||||
{
|
||||
hook.Detach();
|
||||
}
|
||||
|
||||
foreach (var hook in MouseHooks.Values)
|
||||
{
|
||||
hook.Detach();
|
||||
}
|
||||
}
|
||||
|
||||
public void ActivateWindow(IntPtr handle)
|
||||
{
|
||||
var placement = new WINDOWPLACEMENT();
|
||||
|
||||
User32.BringWindowToTop(handle);
|
||||
User32.SetForegroundWindow(handle);
|
||||
|
||||
placement.length = Marshal.SizeOf(placement);
|
||||
User32.GetWindowPlacement(handle, ref placement);
|
||||
|
||||
if (placement.showCmd == (int) ShowWindowCommand.ShowMinimized)
|
||||
{
|
||||
User32.ShowWindowAsync(handle, (int) ShowWindowCommand.Restore);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeregisterKeyboardHook(Guid hookId)
|
||||
{
|
||||
var hook = KeyboardHooks.Values.FirstOrDefault(h => h.Id == hookId);
|
||||
|
||||
if (hook != default)
|
||||
{
|
||||
var success = hook.Detach();
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
KeyboardHooks.TryRemove(hookId, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeregisterMouseHook(Guid hookId)
|
||||
{
|
||||
var hook = MouseHooks.Values.FirstOrDefault(h => h.Id == hookId);
|
||||
|
||||
if (hook != default)
|
||||
{
|
||||
var success = hook.Detach();
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
MouseHooks.TryRemove(hookId, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeregisterSystemEventHook(Guid hookId)
|
||||
{
|
||||
var hook = SystemHooks.Values.FirstOrDefault(h => h.Id == hookId);
|
||||
|
||||
if (hook != default)
|
||||
{
|
||||
var success = hook.Detach();
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
SystemHooks.TryRemove(hookId, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public bool DisableStickyKeys()
|
||||
{
|
||||
var success = TryGetStickyKeys(out var state);
|
||||
|
||||
if (success)
|
||||
{
|
||||
(state as StickyKeysState).Flags &= ~StickyKeysFlags.AVAILABLE;
|
||||
(state as StickyKeysState).Flags &= ~StickyKeysFlags.HOTKEYACTIVE;
|
||||
(state as StickyKeysState).Flags &= ~StickyKeysFlags.ON;
|
||||
|
||||
success = TrySetStickyKeys(state);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public void EmptyClipboard()
|
||||
{
|
||||
var success = true;
|
||||
|
||||
success &= User32.OpenClipboard(IntPtr.Zero);
|
||||
success &= User32.EmptyClipboard();
|
||||
success &= User32.CloseClipboard();
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnableStickyKeys()
|
||||
{
|
||||
var success = TryGetStickyKeys(out var state);
|
||||
|
||||
if (success)
|
||||
{
|
||||
(state as StickyKeysState).Flags |= StickyKeysFlags.AVAILABLE;
|
||||
(state as StickyKeysState).Flags |= StickyKeysFlags.HOTKEYACTIVE;
|
||||
(state as StickyKeysState).Flags |= StickyKeysFlags.ON;
|
||||
|
||||
success = TrySetStickyKeys(state);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public (int x, int y) GetCursorPosition()
|
||||
{
|
||||
var position = new POINT();
|
||||
|
||||
User32.GetCursorPos(ref position);
|
||||
|
||||
return (position.X, position.Y);
|
||||
}
|
||||
|
||||
public IEnumerable<IntPtr> GetOpenWindows()
|
||||
{
|
||||
var windows = new List<IntPtr>();
|
||||
|
||||
bool EnumWindows(IntPtr hWnd, IntPtr lParam)
|
||||
{
|
||||
if (hWnd != GetShellWindowHandle() && User32.IsWindowVisible(hWnd) && User32.GetWindowTextLength(hWnd) > 0)
|
||||
{
|
||||
windows.Add(hWnd);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var success = User32.EnumWindows(EnumWindows, IntPtr.Zero);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
return windows;
|
||||
}
|
||||
|
||||
public uint GetProcessIdFor(IntPtr window)
|
||||
{
|
||||
User32.GetWindowThreadProcessId(window, out var processId);
|
||||
|
||||
return processId;
|
||||
}
|
||||
|
||||
public IntPtr GetShellWindowHandle()
|
||||
{
|
||||
return User32.FindWindow("Shell_TrayWnd", default);
|
||||
}
|
||||
|
||||
public uint GetShellProcessId()
|
||||
{
|
||||
var handle = GetShellWindowHandle();
|
||||
var threadId = User32.GetWindowThreadProcessId(handle, out var processId);
|
||||
|
||||
return processId;
|
||||
}
|
||||
|
||||
public string GetWallpaperPath()
|
||||
{
|
||||
const int MAX_PATH = 260;
|
||||
var buffer = new String('\0', MAX_PATH);
|
||||
var success = User32.SystemParametersInfo(SPI.GETDESKWALLPAPER, buffer.Length, buffer, SPIF.NONE);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
var path = buffer.Substring(0, buffer.IndexOf('\0'));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public IntPtr GetWindowIcon(IntPtr window)
|
||||
{
|
||||
var icon = User32.SendMessage(window, Constant.WM_GETICON, new IntPtr(Constant.ICON_BIG), IntPtr.Zero);
|
||||
|
||||
if (icon == IntPtr.Zero)
|
||||
{
|
||||
icon = User32.SendMessage(window, Constant.WM_GETICON, new IntPtr(Constant.ICON_SMALL), IntPtr.Zero);
|
||||
}
|
||||
|
||||
if (icon == IntPtr.Zero)
|
||||
{
|
||||
icon = User32.SendMessage(window, Constant.WM_GETICON, new IntPtr(Constant.ICON_SMALL2), IntPtr.Zero);
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
public string GetWindowTitle(IntPtr window)
|
||||
{
|
||||
var length = User32.GetWindowTextLength(window);
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
var builder = new StringBuilder(length);
|
||||
|
||||
User32.GetWindowText(window, builder, length + 1);
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public IBounds GetWorkingArea()
|
||||
{
|
||||
var workingArea = new RECT();
|
||||
var success = User32.SystemParametersInfo(SPI.GETWORKAREA, 0, ref workingArea, SPIF.NONE);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
return workingArea.ToBounds();
|
||||
}
|
||||
|
||||
public bool HasInternetConnection()
|
||||
{
|
||||
return WinInet.InternetGetConnectedState(out _, 0);
|
||||
}
|
||||
|
||||
public bool HideWindow(IntPtr window)
|
||||
{
|
||||
return User32.ShowWindow(window, (int) ShowWindowCommand.Hide);
|
||||
}
|
||||
|
||||
public void MinimizeAllOpenWindows()
|
||||
{
|
||||
var handle = GetShellWindowHandle();
|
||||
|
||||
User32.SendMessage(handle, Constant.WM_COMMAND, (IntPtr) Constant.MIN_ALL, IntPtr.Zero);
|
||||
}
|
||||
|
||||
public void PostCloseMessageToShell()
|
||||
{
|
||||
// NOTE: The close message 0x5B4 posted to the shell is undocumented and not officially supported:
|
||||
// https://stackoverflow.com/questions/5689904/gracefully-exit-explorer-programmatically/5705965#5705965
|
||||
|
||||
var handle = GetShellWindowHandle();
|
||||
var success = User32.PostMessage(handle, 0x5B4, IntPtr.Zero, IntPtr.Zero);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
|
||||
public Guid RegisterKeyboardHook(KeyboardHookCallback callback)
|
||||
{
|
||||
var hookId = default(Guid);
|
||||
var hookReadyEvent = new AutoResetEvent(false);
|
||||
var hookThread = new Thread(() =>
|
||||
{
|
||||
var hook = new KeyboardHook(callback);
|
||||
var sleepEvent = new AutoResetEvent(false);
|
||||
|
||||
hook.Attach();
|
||||
hookId = hook.Id;
|
||||
KeyboardHooks[hookId] = hook;
|
||||
hookReadyEvent.Set();
|
||||
|
||||
while (true)
|
||||
{
|
||||
sleepEvent.WaitOne();
|
||||
}
|
||||
});
|
||||
|
||||
hookThread.SetApartmentState(ApartmentState.STA);
|
||||
hookThread.IsBackground = true;
|
||||
hookThread.Start();
|
||||
|
||||
hookReadyEvent.WaitOne();
|
||||
|
||||
return hookId;
|
||||
}
|
||||
|
||||
public Guid RegisterMouseHook(MouseHookCallback callback)
|
||||
{
|
||||
var hookId = default(Guid);
|
||||
var hookReadyEvent = new AutoResetEvent(false);
|
||||
var hookThread = new Thread(() =>
|
||||
{
|
||||
var hook = new MouseHook(callback);
|
||||
var sleepEvent = new AutoResetEvent(false);
|
||||
|
||||
hook.Attach();
|
||||
hookId = hook.Id;
|
||||
MouseHooks[hookId] = hook;
|
||||
hookReadyEvent.Set();
|
||||
|
||||
while (true)
|
||||
{
|
||||
sleepEvent.WaitOne();
|
||||
}
|
||||
});
|
||||
|
||||
hookThread.SetApartmentState(ApartmentState.STA);
|
||||
hookThread.IsBackground = true;
|
||||
hookThread.Start();
|
||||
|
||||
hookReadyEvent.WaitOne();
|
||||
|
||||
return hookId;
|
||||
}
|
||||
|
||||
public Guid RegisterSystemCaptureStartEvent(SystemEventCallback callback)
|
||||
{
|
||||
return RegisterSystemEvent(callback, Constant.EVENT_SYSTEM_CAPTURESTART);
|
||||
}
|
||||
|
||||
public Guid RegisterSystemForegroundEvent(SystemEventCallback callback)
|
||||
{
|
||||
return RegisterSystemEvent(callback, Constant.EVENT_SYSTEM_FOREGROUND);
|
||||
}
|
||||
|
||||
internal Guid RegisterSystemEvent(SystemEventCallback callback, uint eventId)
|
||||
{
|
||||
var hookId = default(Guid);
|
||||
var hookReadyEvent = new AutoResetEvent(false);
|
||||
var hookThread = new Thread(() =>
|
||||
{
|
||||
var hook = new SystemHook(callback, eventId);
|
||||
|
||||
hook.Attach();
|
||||
hookId = hook.Id;
|
||||
SystemHooks[hookId] = hook;
|
||||
hookReadyEvent.Set();
|
||||
hook.AwaitDetach();
|
||||
});
|
||||
|
||||
hookThread.SetApartmentState(ApartmentState.STA);
|
||||
hookThread.IsBackground = true;
|
||||
hookThread.Start();
|
||||
|
||||
hookReadyEvent.WaitOne();
|
||||
|
||||
return hookId;
|
||||
}
|
||||
|
||||
public void RemoveWallpaper()
|
||||
{
|
||||
SetWallpaper(string.Empty);
|
||||
}
|
||||
|
||||
public void RestoreWindow(IntPtr window)
|
||||
{
|
||||
User32.ShowWindow(window, (int) ShowWindowCommand.Restore);
|
||||
}
|
||||
|
||||
public bool ResumeThread(int threadId)
|
||||
{
|
||||
const int FAILURE = -1;
|
||||
var handle = Kernel32.OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint) threadId);
|
||||
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = Kernel32.ResumeThread(handle);
|
||||
var success = result != FAILURE;
|
||||
|
||||
return success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Kernel32.CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendCloseMessageTo(IntPtr window)
|
||||
{
|
||||
User32.SendMessage(window, Constant.WM_SYSCOMMAND, (IntPtr) SystemCommand.CLOSE, IntPtr.Zero);
|
||||
}
|
||||
|
||||
public void SetAlwaysOnState(bool display = true, bool system = true)
|
||||
{
|
||||
if (display || system)
|
||||
{
|
||||
var state = EXECUTION_STATE.CONTINUOUS;
|
||||
|
||||
state |= display ? EXECUTION_STATE.DISPLAY_REQUIRED : 0x0;
|
||||
state |= system ? EXECUTION_STATE.SYSTEM_REQUIRED : 0x0;
|
||||
|
||||
Kernel32.SetThreadExecutionState(state);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetWallpaper(string filePath)
|
||||
{
|
||||
var success = User32.SystemParametersInfo(SPI.SETDESKWALLPAPER, 0, filePath, SPIF.NONE);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
|
||||
public void SetWorkingArea(IBounds bounds)
|
||||
{
|
||||
var workingArea = new RECT { Left = bounds.Left, Top = bounds.Top, Right = bounds.Right, Bottom = bounds.Bottom };
|
||||
var success = User32.SystemParametersInfo(SPI.SETWORKAREA, 0, ref workingArea, SPIF.NONE);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
|
||||
public bool SuspendThread(int threadId)
|
||||
{
|
||||
const int FAILURE = -1;
|
||||
var handle = Kernel32.OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint) threadId);
|
||||
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = Kernel32.SuspendThread(handle);
|
||||
var success = result != FAILURE;
|
||||
|
||||
return success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Kernel32.CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetStickyKeys(out IStickyKeysState state)
|
||||
{
|
||||
var stickyKeys = new STICKYKEYS();
|
||||
|
||||
state = default;
|
||||
stickyKeys.cbSize = Marshal.SizeOf(typeof(STICKYKEYS));
|
||||
|
||||
var success = User32.SystemParametersInfo(SPI.GETSTICKYKEYS, stickyKeys.cbSize, ref stickyKeys, SPIF.NONE);
|
||||
|
||||
if (success)
|
||||
{
|
||||
state = new StickyKeysState
|
||||
{
|
||||
Flags = stickyKeys.dwFlags,
|
||||
IsAvailable = stickyKeys.dwFlags.HasFlag(StickyKeysFlags.AVAILABLE),
|
||||
IsEnabled = stickyKeys.dwFlags.HasFlag(StickyKeysFlags.ON),
|
||||
IsHotkeyActive = stickyKeys.dwFlags.HasFlag(StickyKeysFlags.HOTKEYACTIVE)
|
||||
};
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public bool TrySetStickyKeys(IStickyKeysState state)
|
||||
{
|
||||
var success = false;
|
||||
var stickyKeys = new STICKYKEYS();
|
||||
|
||||
if (state is StickyKeysState stateWithFlags)
|
||||
{
|
||||
stickyKeys.cbSize = Marshal.SizeOf(typeof(STICKYKEYS));
|
||||
stickyKeys.dwFlags = stateWithFlags.Flags;
|
||||
|
||||
success = User32.SystemParametersInfo(SPI.SETSTICKYKEYS, stickyKeys.cbSize, ref stickyKeys, SPIF.NONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
success = state.IsEnabled ? EnableStickyKeys() : DisableStickyKeys();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user