453 lines
10 KiB
C#
453 lines
10 KiB
C#
/*
|
|
* Copyright (c) 2021 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.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 ConcurrentDictionary<Guid, KeyboardHook> KeyboardHooks = new ConcurrentDictionary<Guid, KeyboardHook>();
|
|
private ConcurrentDictionary<Guid, MouseHook> MouseHooks = new ConcurrentDictionary<Guid, MouseHook>();
|
|
private 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 != null)
|
|
{
|
|
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 != null)
|
|
{
|
|
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 != null)
|
|
{
|
|
var success = hook.Detach();
|
|
|
|
if (!success)
|
|
{
|
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
}
|
|
|
|
SystemHooks.TryRemove(hookId, out _);
|
|
}
|
|
}
|
|
|
|
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 (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 uint processId);
|
|
|
|
return processId;
|
|
}
|
|
|
|
public IntPtr GetShellWindowHandle()
|
|
{
|
|
return User32.FindWindow("Shell_TrayWnd", null);
|
|
}
|
|
|
|
public uint GetShellProcessId()
|
|
{
|
|
var handle = GetShellWindowHandle();
|
|
var threadId = User32.GetWindowThreadProcessId(handle, out uint 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, 0);
|
|
|
|
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 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 void PreventSleepMode()
|
|
{
|
|
Kernel32.SetThreadExecutionState(EXECUTION_STATE.CONTINUOUS | EXECUTION_STATE.DISPLAY_REQUIRED | EXECUTION_STATE.SYSTEM_REQUIRED);
|
|
}
|
|
|
|
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 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);
|
|
}
|
|
}
|
|
}
|
|
}
|