diff --git a/widget/windows/InputFilter.cpp b/widget/windows/InputFilter.cpp new file mode 100644 index 0000000000000..09c6ce0089d80 --- /dev/null +++ b/widget/windows/InputFilter.cpp @@ -0,0 +1,281 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "InputFilter.h" + +namespace mozilla { +namespace widget { + +// Static member definitions +std::map InputFilter::sEnabledWindows; +std::mutex InputFilter::sMutex; +std::map InputFilter::sCursorPositions; +std::mutex InputFilter::sCursorMutex; +std::map InputFilter::sKeyboardStates; +std::mutex InputFilter::sKeyboardMutex; +std::map InputFilter::sMouseButtonStates; +std::mutex InputFilter::sMouseButtonMutex; + +HWND InputFilter::GetTopLevelWindow(HWND hwnd) { + if (!hwnd) return nullptr; + HWND parent = hwnd; + HWND next; + while ((next = ::GetParent(parent)) != nullptr) { + parent = next; + } + return parent; +} + +// Window enable/disable +void InputFilter::EnableForWindow(HWND hwnd) { + HWND topLevel = GetTopLevelWindow(hwnd); + if (!topLevel) topLevel = hwnd; + std::lock_guard lock(sMutex); + sEnabledWindows[topLevel] = true; +} + +void InputFilter::DisableForWindow(HWND hwnd) { + HWND topLevel = GetTopLevelWindow(hwnd); + if (!topLevel) topLevel = hwnd; + std::lock_guard lock(sMutex); + sEnabledWindows[topLevel] = false; +} + +bool InputFilter::IsEnabledForWindow(HWND hwnd) { + HWND topLevel = GetTopLevelWindow(hwnd); + if (!topLevel) topLevel = hwnd; + std::lock_guard lock(sMutex); + auto it = sEnabledWindows.find(topLevel); + return (it != sEnabledWindows.end()) ? it->second : false; +} + +void InputFilter::RemoveWindow(HWND hwnd) { + HWND topLevel = GetTopLevelWindow(hwnd); + if (!topLevel) topLevel = hwnd; + { + std::lock_guard lock(sMutex); + sEnabledWindows.erase(topLevel); + } + { + std::lock_guard lock(sCursorMutex); + sCursorPositions.erase(topLevel); + } + { + std::lock_guard lock(sKeyboardMutex); + sKeyboardStates.erase(topLevel); + } + { + std::lock_guard lock(sMouseButtonMutex); + sMouseButtonStates.erase(topLevel); + } +} + +// Message type detection +bool InputFilter::IsKeyboardMessage(UINT msg) { + switch (msg) { + case WM_KEYDOWN: + case WM_KEYUP: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + case WM_UNICHAR: + case WM_HOTKEY: + case WM_IME_KEYDOWN: + case WM_IME_KEYUP: + case WM_IME_CHAR: + case WM_IME_COMPOSITION: + case WM_IME_STARTCOMPOSITION: + case WM_IME_ENDCOMPOSITION: + return true; + default: + return false; + } +} + +bool InputFilter::IsMouseMessage(UINT msg) { + switch (msg) { + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MBUTTONDBLCLK: + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: + case WM_XBUTTONDBLCLK: + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + case WM_NCMOUSEMOVE: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONUP: + case WM_NCLBUTTONDBLCLK: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONUP: + case WM_NCRBUTTONDBLCLK: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONUP: + case WM_NCMBUTTONDBLCLK: + return true; + default: + return false; + } +} + +bool InputFilter::IsInputMessage(UINT msg) { + return IsKeyboardMessage(msg) || IsMouseMessage(msg); +} + +bool InputFilter::ShouldBlockNativeInput(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + // If filtering not enabled for this window, don't block anything + if (!IsEnabledForWindow(hwnd)) { + return false; + } + + // Only block input messages (keyboard and mouse) + if (!IsInputMessage(msg)) { + return false; + } + + // Block all native input when filter is enabled + // MouseMux will inject its own events which bypass this filter + return true; +} + +// Cursor position tracking +void InputFilter::SetCursorPosForWindow(HWND hwnd, int screenX, int screenY) { + HWND topLevel = GetTopLevelWindow(hwnd); + if (!topLevel) topLevel = hwnd; + std::lock_guard lock(sCursorMutex); + CursorPos& pos = sCursorPositions[topLevel]; + pos.screenX = screenX; + pos.screenY = screenY; + pos.valid = true; +} + +bool InputFilter::GetCursorPosForWindow(HWND hwnd, POINT* outPos) { + HWND topLevel = GetTopLevelWindow(hwnd); + if (!topLevel) topLevel = hwnd; + std::lock_guard lock(sCursorMutex); + auto it = sCursorPositions.find(topLevel); + if (it != sCursorPositions.end() && it->second.valid) { + outPos->x = it->second.screenX; + outPos->y = it->second.screenY; + return true; + } + return false; +} + +// Keyboard state management +void InputFilter::SetKeyStateForWindow(HWND hwnd, BYTE* keyState) { + HWND topLevel = GetTopLevelWindow(hwnd); + if (!topLevel) topLevel = hwnd; + std::lock_guard lock(sKeyboardMutex); + KeyboardState& state = sKeyboardStates[topLevel]; + memcpy(state.keys, keyState, 256); + state.valid = true; +} + +bool InputFilter::GetKeyStateForWindow(HWND hwnd, BYTE* outKeyState) { + HWND topLevel = GetTopLevelWindow(hwnd); + if (!topLevel) topLevel = hwnd; + std::lock_guard lock(sKeyboardMutex); + auto it = sKeyboardStates.find(topLevel); + if (it != sKeyboardStates.end() && it->second.valid) { + memcpy(outKeyState, it->second.keys, 256); + return true; + } + return false; +} + +void InputFilter::SetSingleKeyState(HWND hwnd, int vkey, bool down, bool toggled) { + if (vkey < 0 || vkey > 255) return; + + HWND topLevel = GetTopLevelWindow(hwnd); + if (!topLevel) topLevel = hwnd; + std::lock_guard lock(sKeyboardMutex); + KeyboardState& state = sKeyboardStates[topLevel]; + + // High bit (0x80) = key is down + // Low bit (0x01) = key is toggled (for toggle keys like CapsLock) + BYTE val = 0; + if (down) val |= 0x80; + if (toggled) val |= 0x01; + state.keys[vkey] = val; + state.valid = true; +} + +// Mouse button state management +void InputFilter::SetMouseButtonState(HWND hwnd, bool left, bool right, bool middle) { + HWND topLevel = GetTopLevelWindow(hwnd); + if (!topLevel) topLevel = hwnd; + std::lock_guard lock(sMouseButtonMutex); + MouseButtonState& state = sMouseButtonStates[topLevel]; + state.left = left; + state.right = right; + state.middle = middle; +} + +WORD InputFilter::GetMouseButtonState(HWND hwnd) { + HWND topLevel = GetTopLevelWindow(hwnd); + if (!topLevel) topLevel = hwnd; + std::lock_guard lock(sMouseButtonMutex); + auto it = sMouseButtonStates.find(topLevel); + if (it == sMouseButtonStates.end()) { + return 0; + } + WORD flags = 0; + if (it->second.left) flags |= MK_LBUTTON; + if (it->second.right) flags |= MK_RBUTTON; + if (it->second.middle) flags |= MK_MBUTTON; + return flags; +} + +// Thread-local current window for KeyboardLayout to query +static thread_local HWND sCurrentProcessingWindow = nullptr; + +void InputFilter::SetCurrentWindow(HWND hwnd) { + sCurrentProcessingWindow = hwnd; +} + +HWND InputFilter::GetCurrentWindow() { + return sCurrentProcessingWindow; +} + +void InputFilter::ClearCurrentWindow() { + sCurrentProcessingWindow = nullptr; +} + +bool InputFilter::GetCurrentMouseButtons(uint16_t* outButtons) { + HWND hwnd = sCurrentProcessingWindow; + if (!hwnd || !outButtons) { + return false; + } + + if (!IsEnabledForWindow(hwnd)) { + return false; // Use native GetKeyState + } + + // Get button state from our tracked state + WORD flags = GetMouseButtonState(hwnd); + *outButtons = 0; + + // Convert MK_* flags to MouseButtonsFlag values + // MouseButtonsFlag::ePrimaryFlag = 1, eSecondaryFlag = 2, eMiddleFlag = 4 + if (flags & MK_LBUTTON) *outButtons |= 1; // ePrimaryFlag + if (flags & MK_RBUTTON) *outButtons |= 2; // eSecondaryFlag + if (flags & MK_MBUTTON) *outButtons |= 4; // eMiddleFlag + + return true; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/InputFilter.h b/widget/windows/InputFilter.h new file mode 100644 index 0000000000000..a8af0e27f83c8 --- /dev/null +++ b/widget/windows/InputFilter.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef widget_windows_InputFilter_h +#define widget_windows_InputFilter_h + +#include +#include +#include + +namespace mozilla { +namespace widget { + +// Per-window input filtering for MouseMux multi-user support. +// When enabled for a window: +// - Native Windows mouse/keyboard input is blocked +// - Only MouseMux-injected input is processed +// - Each window tracks its own cursor position and keyboard state +class InputFilter { + public: + // Enable/disable native input blocking per window + static void EnableForWindow(HWND hwnd); + static void DisableForWindow(HWND hwnd); + static bool IsEnabledForWindow(HWND hwnd); + static void RemoveWindow(HWND hwnd); + + // Message type detection - used to identify what to block + static bool IsKeyboardMessage(UINT msg); + static bool IsMouseMessage(UINT msg); + static bool IsInputMessage(UINT msg); + + // Check if a native input message should be blocked + // Returns true if the message should NOT be processed (blocked) + static bool ShouldBlockNativeInput(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + + // Per-window cursor position tracking (MouseMux cursor, not system cursor) + static void SetCursorPosForWindow(HWND hwnd, int screenX, int screenY); + static bool GetCursorPosForWindow(HWND hwnd, POINT* outPos); + + // Per-window keyboard state from MouseMux + // This replaces native GetKeyboardState() calls when filtering is enabled + static void SetKeyStateForWindow(HWND hwnd, BYTE* keyState); + static bool GetKeyStateForWindow(HWND hwnd, BYTE* outKeyState); + static void SetSingleKeyState(HWND hwnd, int vkey, bool down, bool toggled = false); + + // Per-window mouse button state from MouseMux + static void SetMouseButtonState(HWND hwnd, bool left, bool right, bool middle); + static WORD GetMouseButtonState(HWND hwnd); + + // Current window being processed (for use by KeyboardLayout) + // Set this before calling ModifierKeyState functions + static void SetCurrentWindow(HWND hwnd); + static HWND GetCurrentWindow(); + static void ClearCurrentWindow(); + + // Get mouse button state for current window (called by KeyboardLayout) + // Returns flags compatible with MouseButtonsFlag enum + static bool GetCurrentMouseButtons(uint16_t* outButtons); + + private: + static std::map sEnabledWindows; + static std::mutex sMutex; + + // Per-window cursor position storage + struct CursorPos { + int screenX = 0; + int screenY = 0; + bool valid = false; + }; + static std::map sCursorPositions; + static std::mutex sCursorMutex; + + // Per-window keyboard state (256 bytes, same as Windows keyboard state) + struct KeyboardState { + BYTE keys[256] = {0}; + bool valid = false; + }; + static std::map sKeyboardStates; + static std::mutex sKeyboardMutex; + + // Per-window mouse button state + struct MouseButtonState { + bool left = false; + bool right = false; + bool middle = false; + }; + static std::map sMouseButtonStates; + static std::mutex sMouseButtonMutex; + + static HWND GetTopLevelWindow(HWND hwnd); +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_windows_InputFilter_h diff --git a/widget/windows/KeyboardLayout.cpp b/widget/windows/KeyboardLayout.cpp index 77007b03ba06d..8d945576bd71b 100644 --- a/widget/windows/KeyboardLayout.cpp +++ b/widget/windows/KeyboardLayout.cpp @@ -28,6 +28,7 @@ #include "WidgetUtils.h" #include "WinUtils.h" +#include "InputFilter.h" #include "npapi.h" @@ -910,6 +911,16 @@ void ModifierKeyState::InitMouseEvent(WidgetInputEvent& aMouseEvent) const { WidgetMouseEventBase& mouseEvent = *aMouseEvent.AsMouseEventBase(); mouseEvent.mButtons = 0; + + // Try to get button state from InputFilter (MouseMux per-window state) + uint16_t mouseMuxButtons = 0; + if (InputFilter::GetCurrentMouseButtons(&mouseMuxButtons)) { + // Use MouseMux tracked button state instead of native GetKeyState + mouseEvent.mButtons = static_cast(mouseMuxButtons); + return; + } + + // Fall back to native GetKeyState for non-MouseMux windows if (::GetKeyState(VK_LBUTTON) < 0) { mouseEvent.mButtons |= MouseButtonsFlag::ePrimaryFlag; } diff --git a/widget/windows/MOUSEMUX_ARCHITECTURE.md b/widget/windows/MOUSEMUX_ARCHITECTURE.md new file mode 100644 index 0000000000000..0b1bd0e4bce50 --- /dev/null +++ b/widget/windows/MOUSEMUX_ARCHITECTURE.md @@ -0,0 +1,116 @@ +# MouseMux Architecture (v5.0) + +## Overview +MouseMux provides multi-mouse/keyboard support for Firefox on Windows. It connects +to a MouseMux server (ws://localhost:41001) and receives input events for multiple +input devices, injecting them into Firefox windows via PostMessage. + +## Key Principle +**NEVER use Windows input APIs**: No SendInput, GetKeyState, GetCursorPos, etc. +All input comes from MouseMux server and is injected via PostMessage to target HWNDs. + +## Components + +### 1. InputFilter (InputFilter.h/cpp) +Simple global flag to block native input. +- `InputFilter::Enable()` - Block native mouse/keyboard to Firefox windows +- `InputFilter::Disable()` - Allow native input +- `InputFilter::IsEnabled()` - Check if blocking is active + +Used in nsWindow::ProcessMessage to skip native input when enabled. + +### 2. MouseMuxClient (MouseMuxClient.h/cpp) - **PRIMARY** +Per-window client that handles MouseMux connection and input injection. +Each nsWindow creates one MouseMuxClient instance. + +Key features: +- WebSocket connection to MouseMux server +- Receives pointer.motion, pointer.button, keyboard.key events +- Filters events to only process those within window bounds +- Tracks ownership (which hwid clicked on this window) +- Built-in debug dialog (F11 to toggle) +- Version: defined as MOUSEMUX_CLIENT_VERSION + +Methods: +- Connect/Disconnect to server +- ShowDebugDialog/HideDebugDialog +- Log() for debug output + +### 3. MouseMuxService (MouseMuxService.h/cpp) - **UNUSED/LEGACY** +Singleton service - appears to be a separate implementation not currently used. +MouseMuxDebugDialog uses this, but the actual nsWindow uses MouseMuxClient. + +### 4. MouseMuxDebugDialog (MouseMuxDebugDialog.h/cpp) - **LEGACY** +Singleton debug dialog that uses MouseMuxService. +Not used by current implementation (nsWindow uses MouseMuxClient's built-in dialog). + +## nsWindow Integration + +### Initialization (nsWindow::Create) +```cpp +InitMouseMux(); // Creates MouseMuxClient for the window +``` + +### Input Blocking (nsWindow::ProcessMessage) +At the start of ProcessMessage, checks InputFilter: +```cpp +if (InputFilter::IsEnabled()) { + switch (msg) { + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + // ... etc + if (!(wParam & MOUSEMUX_MARKER)) { + return true; // Block native input + } + wParam &= ~MOUSEMUX_MARKER; // Strip marker from MouseMux events + } +} +``` + +### Keyboard Forwarding (nsWindow::ProcessMessage WM_KEYDOWN/WM_KEYUP) +When connected to MouseMux, keyboard events arriving at top-level window +are forwarded to the focused child window: +```cpp +if (mMouseMuxClient && mMouseMuxClient->IsConnected()) { + nsWindow* focusedWnd = IMEHandler::GetFocusedWindow(); + if (focusedWnd && focusedWnd != this) { + ::PostMessage(focusedWnd->mWnd, msg, wParam, lParam); + } +} +``` + +### Hotkeys +- F11: Toggle debug dialog +- F12: Emergency exit (disable blocking, disconnect) + +## Message Injection + +MouseMux events are injected via PostMessage with MOUSEMUX_MARKER: +```cpp +::PostMessage(hwnd, WM_MOUSEMOVE, wParam | MOUSEMUX_MARKER, MAKELPARAM(x, y)); +``` + +The marker (0x80000000 in wParam high bit) identifies MouseMux-injected messages +so they pass through InputFilter when blocking is enabled. + +## Current Issues + +### Keyboard Not Working +- Keyboard events from MouseMux are received and logged +- PostMessage is called to inject WM_KEYDOWN/WM_KEYUP +- But keys don't appear in content areas +- Need investigation (focus handling, lParam construction, etc.) + +## File List +- InputFilter.h/cpp - Simple blocking flag +- MouseMuxClient.h/cpp - Per-window client (USED) +- MouseMuxService.h/cpp - Singleton service (UNUSED) +- MouseMuxDebugDialog.h/cpp - Singleton dialog (UNUSED) +- nsWindow.cpp - Integration point + +## Version History +- v4.3: Per-window MouseMuxClient +- v4.5: Fix keyboard handling, add MouseMux rules +- v4.6: Reduce logging (skip motion events) +- v4.7: Forward keyboard to focused window +- v5.0: Restore independent Block Input toggle diff --git a/widget/windows/MouseMux-rules.md b/widget/windows/MouseMux-rules.md new file mode 100644 index 0000000000000..d48962525de21 --- /dev/null +++ b/widget/windows/MouseMux-rules.md @@ -0,0 +1,35 @@ +# MouseMux Integration Rules + +## CRITICAL: Input Handling Rules + +### PROHIBITED Windows APIs +The following Windows input APIs are **STRICTLY PROHIBITED**: +- `SendInput()` - Sends input to the system, affects all applications +- `keybd_event()` - Legacy keyboard input, affects all applications +- `mouse_event()` - Legacy mouse input, affects all applications +- `GetKeyState()` - Gets global keyboard state +- `GetAsyncKeyState()` - Gets global async keyboard state +- `SetCursorPos()` - Moves the global cursor +- Any API that generates or queries **system-wide** input + +### ALLOWED Input Methods +Input from MouseMux MUST be delivered using **window-targeted** methods: +- `PostMessage()` - Posts messages directly to a specific window +- `SendMessage()` - Sends messages directly to a specific window +- Direct window message injection via HWND + +### Rationale +MouseMux is designed for multi-user scenarios where each user has their own +mouse/keyboard. Using system-wide input APIs would: +1. Affect ALL applications, not just the target window +2. Interfere with other users' input +3. Defeat the purpose of per-window input isolation + +### Implementation Notes +- All mouse events (WM_MOUSEMOVE, WM_LBUTTONDOWN, etc.) → PostMessage to target HWND +- All keyboard events (WM_KEYDOWN, WM_KEYUP, etc.) → PostMessage to target HWND +- Use MOUSEMUX_MARKER in message parameters to identify injected input +- Never block or intercept input from other MouseMux devices + +## Version History +- v4.5: Added these rules after incorrectly attempting to use SendInput diff --git a/widget/windows/MouseMuxClient.cpp b/widget/windows/MouseMuxClient.cpp new file mode 100644 index 0000000000000..b5f0383fb82ae --- /dev/null +++ b/widget/windows/MouseMuxClient.cpp @@ -0,0 +1,970 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "MouseMuxClient.h" +#include "InputFilter.h" +#include +#include +#include +#include +#include + +#pragma comment(lib, "ws2_32.lib") + +#define MOUSEMUX_CLIENT_VERSION "5.19" +#define MOUSEMUX_BUILD_TIME __DATE__ " " __TIME__ + +namespace mozilla { +namespace widget { + +static bool sWinsockInitialized = false; +static std::mutex sWinsockMutex; + +static bool EnsureWinsockInitialized() { + std::lock_guard lock(sWinsockMutex); + if (!sWinsockInitialized) { + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + return false; + } + sWinsockInitialized = true; + } + return true; +} + +MouseMuxClient::MouseMuxClient(HWND aOwnerHwnd) : mOwnerHwnd(aOwnerHwnd) { + Log("MouseMuxClient v%s created for HWND %p", MOUSEMUX_CLIENT_VERSION, aOwnerHwnd); +} + +MouseMuxClient::~MouseMuxClient() { + Log("MouseMuxClient destroying"); + mShouldStop.store(true); + + { + std::lock_guard sockLock(mSocketMutex); + if (mSocket != INVALID_SOCKET) { + ::shutdown(mSocket, SD_BOTH); + ::closesocket(mSocket); + mSocket = INVALID_SOCKET; + } + } + + // Wait for thread with timeout + if (mWorkerThread.joinable()) { + auto start = std::chrono::steady_clock::now(); + while (mThreadRunning.load()) { + auto elapsed = std::chrono::steady_clock::now() - start; + if (std::chrono::duration_cast(elapsed).count() > 500) { + Log("Worker thread timeout, detaching"); + mWorkerThread.detach(); + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + if (mWorkerThread.joinable()) { + mWorkerThread.join(); + } + } + + if (mDebugDialog && ::IsWindow(mDebugDialog)) { + ::DestroyWindow(mDebugDialog); + } + mDebugDialog = nullptr; +} + +bool MouseMuxClient::Connect(const wchar_t* aUrl) { + std::lock_guard lock(mConnectMutex); + + if (mConnected.load()) { + Log("Already connected"); + return true; + } + + // Wait for any previous thread + if (mWorkerThread.joinable()) { + if (mThreadRunning.load()) { + Log("Previous thread still running"); + return false; + } + mWorkerThread.join(); + } + + if (!EnsureWinsockInitialized()) { + Log("WSAStartup failed"); + return false; + } + + mServerUrl = aUrl ? aUrl : L"ws://localhost:41001"; + mShouldStop.store(false); + + mWorkerThread = std::thread(&MouseMuxClient::WebSocketThread, this); + Log("Worker thread started"); + return true; +} + +void MouseMuxClient::Disconnect() { + mShouldStop.store(true); + mConnected.store(false); + + { + std::lock_guard sockLock(mSocketMutex); + if (mSocket != INVALID_SOCKET) { + ::shutdown(mSocket, SD_BOTH); + ::closesocket(mSocket); + mSocket = INVALID_SOCKET; + } + } + + // Don't join here - would block UI. Thread will exit on its own. + if (mWorkerThread.joinable()) { + mWorkerThread.detach(); + } + + UpdateDebugStatusSafe(); +} + +void MouseMuxClient::WebSocketThread() { + mThreadRunning.store(true); + Log("WebSocket thread started"); + + SOCKET sock = INVALID_SOCKET; + + auto cleanup = [&]() { + if (sock != INVALID_SOCKET) { + ::closesocket(sock); + } + { + std::lock_guard lock(mSocketMutex); + mSocket = INVALID_SOCKET; + } + mConnected.store(false); + Log("WebSocket thread exiting"); + mThreadRunning.store(false); + UpdateDebugStatusSafe(); + }; + + std::wstring url = mServerUrl; + std::wstring host = L"localhost"; + int port = 41001; + + size_t hostStart = url.find(L"://"); + if (hostStart != std::wstring::npos) { + hostStart += 3; + size_t portStart = url.find(L":", hostStart); + if (portStart != std::wstring::npos) { + host = url.substr(hostStart, portStart - hostStart); + port = _wtoi(url.substr(portStart + 1).c_str()); + } else { + host = url.substr(hostStart); + } + } + + char hostA[256] = {0}; + char portA[16] = {0}; + wcstombs(hostA, host.c_str(), sizeof(hostA) - 1); + snprintf(portA, sizeof(portA), "%d", port); + + struct addrinfo hints = {0}, *result = nullptr; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + if (getaddrinfo(hostA, portA, &hints, &result) != 0) { + Log("getaddrinfo failed for %s:%s", hostA, portA); + cleanup(); + return; + } + + sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol); + if (sock == INVALID_SOCKET) { + Log("socket() failed: %d", WSAGetLastError()); + freeaddrinfo(result); + cleanup(); + return; + } + + // Set connect timeout + DWORD connTimeout = 3000; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&connTimeout, sizeof(connTimeout)); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&connTimeout, sizeof(connTimeout)); + + if (mShouldStop.load()) { + freeaddrinfo(result); + cleanup(); + return; + } + + if (connect(sock, result->ai_addr, (int)result->ai_addrlen) == SOCKET_ERROR) { + Log("connect() failed: %d", WSAGetLastError()); + freeaddrinfo(result); + cleanup(); + return; + } + freeaddrinfo(result); + + { + std::lock_guard lock(mSocketMutex); + mSocket = sock; + } + + char request[512]; + snprintf(request, sizeof(request), + "GET / HTTP/1.1\r\n" + "Host: %s:%d\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n\r\n", + hostA, port); + + if (send(sock, request, (int)strlen(request), 0) == SOCKET_ERROR) { + Log("send() handshake failed: %d", WSAGetLastError()); + cleanup(); + return; + } + + char response[1024]; + int recvLen = recv(sock, response, sizeof(response) - 1, 0); + if (recvLen <= 0) { + Log("Handshake failed - no response: %d", WSAGetLastError()); + cleanup(); + return; + } + response[recvLen] = '\0'; + + if (strstr(response, "101") == nullptr) { + Log("Handshake failed - expected 101, got: %.100s", response); + cleanup(); + return; + } + + mConnected.store(true); + Log("Connected to MouseMux server at %s:%d", hostA, port); + UpdateDebugStatusSafe(); + + // Request user list for keyboard-to-mouse mapping + { + const char* userListReq = "{\"type\":\"user.list.request.A2M\"}"; + size_t len = strlen(userListReq); + // Build WebSocket frame: FIN=1, opcode=1 (text), masked + unsigned char frame[256]; + frame[0] = 0x81; // FIN + text opcode + frame[1] = 0x80 | (unsigned char)len; // Mask bit + length + // Simple mask (could be random, but server doesn't care) + frame[2] = 0x12; frame[3] = 0x34; frame[4] = 0x56; frame[5] = 0x78; + for (size_t i = 0; i < len; i++) { + frame[6 + i] = userListReq[i] ^ frame[2 + (i % 4)]; + } + send(sock, (char*)frame, (int)(6 + len), 0); + Log("Requested user list from server"); + } + + DWORD timeout = 100; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)); + + std::string messageBuffer; + while (!mShouldStop.load()) { + { + std::lock_guard lock(mSocketMutex); + if (mSocket == INVALID_SOCKET) break; + } + + unsigned char header[2]; + int headerLen = recv(sock, (char*)header, 2, 0); + + if (headerLen <= 0) { + int err = WSAGetLastError(); + if (err == WSAETIMEDOUT) continue; + if (err == WSAEINTR) continue; + Log("recv() header failed: %d", err); + break; + } + + if (headerLen < 2) continue; + + bool fin = (header[0] & 0x80) != 0; + int opcode = header[0] & 0x0F; + bool masked = (header[1] & 0x80) != 0; + uint64_t payloadLen = header[1] & 0x7F; + + if (payloadLen == 126) { + unsigned char ext[2]; + if (recv(sock, (char*)ext, 2, 0) != 2) break; + payloadLen = (ext[0] << 8) | ext[1]; + } else if (payloadLen == 127) { + unsigned char ext[8]; + if (recv(sock, (char*)ext, 8, 0) != 8) break; + payloadLen = 0; + for (int i = 0; i < 8; i++) { + payloadLen = (payloadLen << 8) | ext[i]; + } + } + + unsigned char mask[4] = {0}; + if (masked) { + if (recv(sock, (char*)mask, 4, 0) != 4) break; + } + + if (payloadLen > 65536) { + Log("Payload too large: %llu bytes", (unsigned long long)payloadLen); + break; + } + + std::string payload; + payload.resize((size_t)payloadLen); + size_t received = 0; + while (received < payloadLen && !mShouldStop.load()) { + int chunk = recv(sock, &payload[received], (int)(payloadLen - received), 0); + if (chunk <= 0) { + int err = WSAGetLastError(); + if (err == WSAETIMEDOUT) continue; + break; + } + received += chunk; + } + + if (received < payloadLen) break; + + if (masked) { + for (size_t i = 0; i < payloadLen; i++) { + payload[i] ^= mask[i % 4]; + } + } + + if (opcode == 0x08) { + Log("Server sent close frame"); + break; + } + + if (opcode == 0x01 || opcode == 0x02) { + messageBuffer += payload; + if (fin) { + HandleMessage(messageBuffer); + messageBuffer.clear(); + } + } + } + + cleanup(); +} + +void MouseMuxClient::UpdateDebugStatusSafe() { + if (mDebugDialog && ::IsWindow(mDebugDialog)) { + ::PostMessage(mDebugDialog, WM_MOUSEMUX_UPDATE, 0, 0); + } +} + +void MouseMuxClient::HandleMessage(const std::string& aMessage) { + auto getString = [&](const char* key) -> std::string { + std::string search = std::string("\"") + key + "\":\""; + size_t pos = aMessage.find(search); + if (pos == std::string::npos) return ""; + pos += search.length(); + size_t end = aMessage.find("\"", pos); + if (end == std::string::npos) return ""; + return aMessage.substr(pos, end - pos); + }; + + auto getInt = [&](const char* key) -> int { + std::string search = std::string("\"") + key + "\":"; + size_t pos = aMessage.find(search); + if (pos == std::string::npos) return 0; + pos += search.length(); + return atoi(aMessage.c_str() + pos); + }; + + auto getUint = [&](const char* key) -> uint32_t { + std::string search = std::string("\"") + key + "\":"; + size_t pos = aMessage.find(search); + if (pos == std::string::npos) return 0; + pos += search.length(); + return (uint32_t)strtoul(aMessage.c_str() + pos, nullptr, 10); + }; + + std::string type = getString("type"); + + if (type == "pointer.motion.notify.M2A") { + HandlePointerMotion(getUint("hwid"), getInt("x"), getInt("y")); + } else if (type == "pointer.button.notify.M2A") { + HandlePointerButton(getUint("hwid"), getInt("x"), getInt("y"), getUint("data")); + } else if (type == "pointer.scroll.notify.M2A") { + bool horiz = aMessage.find("\"horizontal\":true") != std::string::npos; + HandlePointerWheel(getUint("hwid"), getInt("x"), getInt("y"), getInt("delta"), horiz); + } else if (type == "keyboard.key.notify.M2A") { + HandleKeyboard(getUint("hwid"), getUint("vkey"), getUint("message"), + getUint("scan"), getUint("flags")); + } else if (type == "user.list.notify.M2A") { + ParseUserList(aMessage); + } else if (type == "user.changed.notify.M2A") { + // Handle incremental user updates (has hwid_ms and hwid_kb directly) + uint32_t mouseHwid = getUint("hwid_ms"); + uint32_t keyboardHwid = getUint("hwid_kb"); + std::string action = getString("action"); + + if (action == "create" || action == "map") { + if (mouseHwid && keyboardHwid) { + std::lock_guard lock(mMappingMutex); + mMouseToKeyboard[mouseHwid] = keyboardHwid; + Log("User %s: mouse 0x%X -> keyboard 0x%X", action.c_str(), mouseHwid, keyboardHwid); + } + } else if (action == "dispose") { + std::lock_guard lock(mMappingMutex); + mMouseToKeyboard.erase(mouseHwid); + Log("User disposed: mouse 0x%X", mouseHwid); + } + } +} + +void MouseMuxClient::ParseUserList(const std::string& aMessage) { + std::lock_guard lock(mMappingMutex); + mMouseToKeyboard.clear(); + + // Format: {"type":"user.list.notify.M2A","users":[{devices:[{hwid,type},...]},...]} + // Each user has devices array with type "pointer" or "keyboard" + size_t pos = aMessage.find("\"users\":"); + if (pos == std::string::npos) { + Log("ParseUserList: no users array found"); + return; + } + + // Parse each user object + size_t searchPos = pos; + while (true) { + // Find next "devices" array + size_t devicesPos = aMessage.find("\"devices\":", searchPos); + if (devicesPos == std::string::npos) break; + + // Find the devices array bounds + size_t devArrayStart = aMessage.find("[", devicesPos); + if (devArrayStart == std::string::npos) break; + + // Find matching closing bracket (handle nested objects) + int depth = 1; + size_t devArrayEnd = devArrayStart + 1; + while (depth > 0 && devArrayEnd < aMessage.length()) { + if (aMessage[devArrayEnd] == '[') depth++; + else if (aMessage[devArrayEnd] == ']') depth--; + devArrayEnd++; + } + + std::string devicesStr = aMessage.substr(devArrayStart, devArrayEnd - devArrayStart); + + // Extract pointer and keyboard hwids from this user's devices + uint32_t pointerHwid = 0; + uint32_t keyboardHwid = 0; + + size_t devPos = 0; + while ((devPos = devicesStr.find("\"hwid\":", devPos)) != std::string::npos) { + uint32_t hwid = (uint32_t)strtoul(devicesStr.c_str() + devPos + 7, nullptr, 10); + + // Find type for this device (look backwards for "type" before this hwid, or forwards) + size_t typePos = devicesStr.rfind("\"type\":", devPos); + size_t nextTypePos = devicesStr.find("\"type\":", devPos); + + // Use whichever is closer/more relevant to this device object + std::string devType; + size_t checkPos = (nextTypePos != std::string::npos && nextTypePos < devPos + 50) + ? nextTypePos : typePos; + if (checkPos != std::string::npos) { + size_t quoteStart = devicesStr.find("\"", checkPos + 7); + size_t quoteEnd = devicesStr.find("\"", quoteStart + 1); + if (quoteStart != std::string::npos && quoteEnd != std::string::npos) { + devType = devicesStr.substr(quoteStart + 1, quoteEnd - quoteStart - 1); + } + } + + if (devType == "pointer" && hwid) { + pointerHwid = hwid; + } else if (devType == "keyboard" && hwid) { + keyboardHwid = hwid; + } + + devPos += 7; + } + + if (pointerHwid && keyboardHwid) { + mMouseToKeyboard[pointerHwid] = keyboardHwid; + Log("User mapping: mouse 0x%X -> keyboard 0x%X", pointerHwid, keyboardHwid); + } + + searchPos = devArrayEnd; + } + + Log("User list updated: %zu mappings", mMouseToKeyboard.size()); +} + +bool MouseMuxClient::IsPointInWindow(int aScreenX, int aScreenY) { + HWND hwnd = mOwnerHwnd; + if (!hwnd || !::IsWindow(hwnd)) return false; + + RECT rect; + if (!::GetWindowRect(hwnd, &rect)) return false; + + return aScreenX >= rect.left && aScreenX < rect.right && + aScreenY >= rect.top && aScreenY < rect.bottom; +} + +POINT MouseMuxClient::ScreenToClient(int aScreenX, int aScreenY) { + POINT pt = {aScreenX, aScreenY}; + if (mOwnerHwnd) { + ::ScreenToClient(mOwnerHwnd, &pt); + } + return pt; +} + +WPARAM MouseMuxClient::BuildMouseWParam(uint32_t aHwid) { + WPARAM wParam = MOUSEMUX_MARKER; + + std::lock_guard lock(mButtonStateMutex); + auto it = mButtonState.find(aHwid); + if (it != mButtonState.end()) { + uint32_t state = it->second; + if (state & 0x01) wParam |= MK_LBUTTON; + if (state & 0x04) wParam |= MK_RBUTTON; + if (state & 0x10) wParam |= MK_MBUTTON; + } + + return wParam; +} + +void MouseMuxClient::HandlePointerMotion(uint32_t aHwid, int aScreenX, int aScreenY) { + { + std::lock_guard lock(mMousePosMutex); + mLastMousePos[aHwid] = {aScreenX, aScreenY}; + } + + uint32_t owner = mOwnerHwid.load(); + bool isOwner = (aHwid == owner); + bool inWindow = IsPointInWindow(aScreenX, aScreenY); + + // Log every 100th motion for debugging + static int motionCount = 0; + if (++motionCount % 100 == 0) { + Log("MOTION[%d] hwid=0x%X pos=(%d,%d) owner=0x%X isOwner=%d inWin=%d", + motionCount, aHwid, aScreenX, aScreenY, owner, isOwner, inWindow); + } + + // Only process from owner - no hover (prevents interference) + if (!isOwner) return; + + + + + + if (!mOwnerHwnd) return; + + POINT clientPt = ScreenToClient(aScreenX, aScreenY); + LPARAM lParam = MAKELPARAM(clientPt.x, clientPt.y); + WPARAM wParam = BuildMouseWParam(aHwid); + + ::PostMessage(mOwnerHwnd, WM_MOUSEMOVE, wParam, lParam); +} + +void MouseMuxClient::HandlePointerButton(uint32_t aHwid, int aScreenX, int aScreenY, + uint32_t aEventFlags) { + { + std::lock_guard lock(mMousePosMutex); + mLastMousePos[aHwid] = {aScreenX, aScreenY}; + } + + bool leftDown = (aEventFlags & 0x01) != 0; + bool leftUp = (aEventFlags & 0x02) != 0; + bool rightDown = (aEventFlags & 0x04) != 0; + bool rightUp = (aEventFlags & 0x08) != 0; + bool middleDown = (aEventFlags & 0x10) != 0; + bool middleUp = (aEventFlags & 0x20) != 0; + + { + std::lock_guard lock(mButtonStateMutex); + uint32_t& state = mButtonState[aHwid]; + if (leftDown) state |= 0x01; + if (leftUp) state &= ~0x01; + if (rightDown) state |= 0x04; + if (rightUp) state &= ~0x04; + if (middleDown) state |= 0x10; + if (middleUp) state &= ~0x10; + } + + bool isButtonDown = leftDown || rightDown || middleDown; + uint32_t owner = mOwnerHwid.load(); + bool isOwner = (aHwid == owner); + bool inWindow = IsPointInWindow(aScreenX, aScreenY); + + // Release ownership if user clicked outside this window + if (isButtonDown && !inWindow && isOwner) { + mOwnerHwid.store(0); + Log("Released owner: hwid=0x%X (clicked outside)", aHwid); + UpdateDebugStatusSafe(); + return; + } + + // Only allow setting owner if there's no current owner (lock ownership) + if (isButtonDown && inWindow && owner == 0) { + mOwnerHwid.store(aHwid); + Log("New owner: hwid=0x%X (locked)", aHwid); + UpdateDebugStatusSafe(); + isOwner = true; + } + + // Only process from owner (strict isolation) + if (!isOwner) return; + if (!mOwnerHwnd) return; + + // Get current button state for this device + uint32_t currentState = 0; + { + std::lock_guard lock(mButtonStateMutex); + auto it = mButtonState.find(aHwid); + if (it != mButtonState.end()) { + currentState = it->second; + } + } + + // Sync button state to InputFilter for this window + bool leftHeld = (currentState & 0x01) != 0; + bool rightHeld = (currentState & 0x04) != 0; + bool middleHeld = (currentState & 0x10) != 0; + InputFilter::SetMouseButtonState(mOwnerHwnd, leftHeld, rightHeld, middleHeld); + + POINT clientPt = ScreenToClient(aScreenX, aScreenY); + LPARAM lParam = MAKELPARAM(clientPt.x, clientPt.y); + WPARAM wParam = BuildMouseWParam(aHwid); + + if (leftDown) { + Log("LBUTTONDOWN hwid=0x%X at (%d,%d)", aHwid, aScreenX, aScreenY); + ::PostMessage(mOwnerHwnd, WM_LBUTTONDOWN, wParam, lParam); + } + if (leftUp) ::PostMessage(mOwnerHwnd, WM_LBUTTONUP, wParam, lParam); + if (rightDown) ::PostMessage(mOwnerHwnd, WM_RBUTTONDOWN, wParam, lParam); + if (rightUp) ::PostMessage(mOwnerHwnd, WM_RBUTTONUP, wParam, lParam); + if (middleDown) ::PostMessage(mOwnerHwnd, WM_MBUTTONDOWN, wParam, lParam); + if (middleUp) ::PostMessage(mOwnerHwnd, WM_MBUTTONUP, wParam, lParam); +} + +void MouseMuxClient::HandlePointerWheel(uint32_t aHwid, int aScreenX, int aScreenY, + int aDelta, bool aIsHorizontal) { + uint32_t owner = mOwnerHwid.load(); + bool isOwner = (aHwid == owner); + + // Only process from owner (strict isolation) + if (!isOwner) return; + if (!mOwnerHwnd) return; + + POINT clientPt = ScreenToClient(aScreenX, aScreenY); + LPARAM lParam = MAKELPARAM(clientPt.x, clientPt.y); + WPARAM wParam = BuildMouseWParam(aHwid); + wParam |= ((aDelta & 0xFFFF) << 16); + + UINT msg = aIsHorizontal ? WM_MOUSEHWHEEL : WM_MOUSEWHEEL; + ::PostMessage(mOwnerHwnd, msg, wParam, lParam); +} + +void MouseMuxClient::HandleKeyboard(uint32_t aHwid, uint32_t aVkey, uint32_t aMessage, + uint32_t aScanCode, uint32_t aFlags) { + uint32_t owner = mOwnerHwid.load(); + + // Debug: log all keyboard events + const char* msgName = (aMessage == WM_KEYDOWN) ? "KEYDOWN" : + (aMessage == WM_KEYUP) ? "KEYUP" : + (aMessage == WM_SYSKEYDOWN) ? "SYSKEYDOWN" : + (aMessage == WM_SYSKEYUP) ? "SYSKEYUP" : "OTHER"; + + if (owner == 0) { + Log("KEY REJECTED: %s vk=0x%X kbd_hwid=0x%X - no owner", msgName, aVkey, aHwid); + return; + } + + // Find which mouse this keyboard belongs to + uint32_t mouseHwid = 0; + { + std::lock_guard lock(mMappingMutex); + for (const auto& pair : mMouseToKeyboard) { + if (pair.second == aHwid) { + mouseHwid = pair.first; + break; + } + } + } + + // Only accept keyboard input from the owner's paired keyboard + // Strict isolation: must positively identify keyboard belongs to owner's mouse + if (mouseHwid != owner) { + Log("KEY REJECTED: %s vk=0x%X kbd_hwid=0x%X mouse_hwid=0x%X owner=0x%X - %s", + msgName, aVkey, aHwid, mouseHwid, owner, + mouseHwid == 0 ? "unknown keyboard" : "wrong owner"); + return; + } + if (!mOwnerHwnd) { + Log("KEY REJECTED: %s vk=0x%X - no owner hwnd", msgName, aVkey); + return; + } + + Log("KEY ACCEPTED: %s vk=0x%X kbd_hwid=0x%X mouse_hwid=0x%X owner=0x%X -> HWND %p", + msgName, aVkey, aHwid, mouseHwid, owner, mOwnerHwnd); + + // Determine if key is pressed or released + bool isKeyDown = (aMessage == WM_KEYDOWN || aMessage == WM_SYSKEYDOWN); + bool isKeyUp = (aMessage == WM_KEYUP || aMessage == WM_SYSKEYUP); + + // Sync key state to InputFilter for this window + if (isKeyDown || isKeyUp) { + // Handle toggle keys (CapsLock, NumLock, ScrollLock) + bool isToggleKey = (aVkey == VK_CAPITAL || aVkey == VK_NUMLOCK || aVkey == VK_SCROLL); + if (isToggleKey && isKeyDown) { + // Toggle keys flip their toggle state on keydown + // For now, just set as pressed - full toggle tracking would need state + InputFilter::SetSingleKeyState(mOwnerHwnd, aVkey, true, false); + } else { + InputFilter::SetSingleKeyState(mOwnerHwnd, aVkey, isKeyDown, false); + } + + // Also update modifier keys state (Shift, Ctrl, Alt) + if (aVkey == VK_SHIFT || aVkey == VK_LSHIFT || aVkey == VK_RSHIFT) { + InputFilter::SetSingleKeyState(mOwnerHwnd, VK_SHIFT, isKeyDown, false); + } + if (aVkey == VK_CONTROL || aVkey == VK_LCONTROL || aVkey == VK_RCONTROL) { + InputFilter::SetSingleKeyState(mOwnerHwnd, VK_CONTROL, isKeyDown, false); + } + if (aVkey == VK_MENU || aVkey == VK_LMENU || aVkey == VK_RMENU) { + InputFilter::SetSingleKeyState(mOwnerHwnd, VK_MENU, isKeyDown, false); + } + } + + // Build lParam for the message + LPARAM lParam = 1; // repeat count + lParam |= (aScanCode & 0xFF) << 16; + if (aFlags & 0x01) lParam |= (1 << 24); // extended key + + if (isKeyUp) { + lParam |= (1 << 30); // previous key state + lParam |= (1 << 31); // transition state + } + + WPARAM markedVkey = aVkey | MOUSEMUX_MARKER; + ::PostMessage(mOwnerHwnd, aMessage, markedVkey, lParam); +} + +void MouseMuxClient::Log(const char* aFormat, ...) { + char buf[512]; + va_list args; + va_start(args, aFormat); + vsnprintf(buf, sizeof(buf), aFormat, args); + va_end(args); + + FILE* f = fopen("D:/scratch/firefox/mousemux_client.log", "a"); + if (f) { + SYSTEMTIME st; + GetLocalTime(&st); + fprintf(f, "[v%s %02d:%02d:%02d.%03d HWND=%p] %s\n", + MOUSEMUX_CLIENT_VERSION, + st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, + mOwnerHwnd, buf); + fclose(f); + } + + AppendLog(buf); +} + +void MouseMuxClient::AppendLog(const char* text) { + { + std::lock_guard lock(mLogMutex); + mLogLines.push_back(text); + while (mLogLines.size() > 100) { + mLogLines.erase(mLogLines.begin()); + } + } + if (mDebugDialog && ::IsWindow(mDebugDialog)) { + ::PostMessage(mDebugDialog, WM_MOUSEMUX_LOG, 0, 0); + } +} + +void MouseMuxClient::FlushLogToUI() { + if (!mLogEdit || !::IsWindow(mLogEdit)) return; + + std::string fullText; + { + std::lock_guard lock(mLogMutex); + for (const auto& line : mLogLines) { + fullText += line; + fullText += "\r\n"; + } + } + ::SetWindowTextA(mLogEdit, fullText.c_str()); + int lineCount = (int)::SendMessage(mLogEdit, EM_GETLINECOUNT, 0, 0); + ::SendMessage(mLogEdit, EM_LINESCROLL, 0, lineCount); +} + +void MouseMuxClient::ShowDebugDialog() { + if (!mDebugDialog) { + CreateDebugDialog(); + } + if (mDebugDialog) { + ::ShowWindow(mDebugDialog, SW_SHOWNORMAL); + ::SetForegroundWindow(mDebugDialog); + mDebugDialogVisible = true; + UpdateDebugStatus(); + } +} + +void MouseMuxClient::HideDebugDialog() { + if (mDebugDialog && ::IsWindow(mDebugDialog)) { + ::ShowWindow(mDebugDialog, SW_HIDE); + } + mDebugDialogVisible = false; +} + +void MouseMuxClient::CreateDebugDialog() { + static std::once_flag classRegisterFlag; + std::call_once(classRegisterFlag, []() { + WNDCLASSEXW wc = {0}; + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = DebugDialogProc; + wc.hInstance = ::GetModuleHandle(nullptr); + wc.hCursor = ::LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wc.lpszClassName = L"MouseMuxClientDebug"; + ::RegisterClassExW(&wc); + }); + + RECT ownerRect = {100, 100, 500, 450}; + if (mOwnerHwnd && ::IsWindow(mOwnerHwnd)) { + ::GetWindowRect(mOwnerHwnd, &ownerRect); + } + + wchar_t title[256]; + wchar_t winTitle[128] = L"(unknown)"; + if (mOwnerHwnd && ::IsWindow(mOwnerHwnd)) { + ::GetWindowTextW(mOwnerHwnd, winTitle, 128); + } + swprintf(title, 256, L"MouseMux v%S - %s [%p]", MOUSEMUX_CLIENT_VERSION, winTitle, mOwnerHwnd); + + mDebugDialog = ::CreateWindowExW( + WS_EX_TOPMOST | WS_EX_APPWINDOW, L"MouseMuxClientDebug", title, + WS_OVERLAPPEDWINDOW, ownerRect.right + 10, ownerRect.top, 800, 400, + nullptr, nullptr, ::GetModuleHandle(nullptr), this); + + if (!mDebugDialog) { + Log("Failed to create debug dialog: %d", GetLastError()); + return; + } + + mStatusLabel = ::CreateWindowW(L"STATIC", L"Status: Disconnected", + WS_CHILD | WS_VISIBLE, 10, 10, 780, 20, + mDebugDialog, (HMENU)ID_STATUS, nullptr, nullptr); + + mConnectBtn = ::CreateWindowW(L"BUTTON", L"Connect", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 10, 40, + 120, 25, mDebugDialog, (HMENU)ID_CONNECT, nullptr, nullptr); + + mBlockBtn = ::CreateWindowW(L"BUTTON", L"Block Input", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 140, 40, + 120, 25, mDebugDialog, (HMENU)ID_BLOCK, nullptr, nullptr); + + mLogEdit = ::CreateWindowExW( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY, + 10, 75, 770, 280, mDebugDialog, (HMENU)ID_LOG, nullptr, nullptr); + + HFONT hFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT); + if (mStatusLabel) ::SendMessage(mStatusLabel, WM_SETFONT, (WPARAM)hFont, TRUE); + if (mConnectBtn) ::SendMessage(mConnectBtn, WM_SETFONT, (WPARAM)hFont, TRUE); + if (mBlockBtn) ::SendMessage(mBlockBtn, WM_SETFONT, (WPARAM)hFont, TRUE); + if (mLogEdit) ::SendMessage(mLogEdit, WM_SETFONT, (WPARAM)hFont, TRUE); + + Log("Debug dialog created"); +} + +void MouseMuxClient::UpdateDebugStatus() { + if (!mStatusLabel || !::IsWindow(mStatusLabel)) return; + + wchar_t buf[512]; + wchar_t winTitle[128] = L""; + if (mOwnerHwnd && ::IsWindow(mOwnerHwnd)) { + ::GetWindowTextW(mOwnerHwnd, winTitle, 128); + } + uint32_t owner = mOwnerHwid.load(); + bool connected = mConnected.load(); + bool blocked = InputFilter::IsEnabledForWindow(mOwnerHwnd); + + swprintf(buf, 512, L"%s [%p] | %s | %s | Owner: %s", + winTitle, mOwnerHwnd, + connected ? L"Connected" : L"Disconnected", + blocked ? L"BLOCKED" : L"Normal", + owner ? std::to_wstring(owner).c_str() : L"None"); + ::SetWindowTextW(mStatusLabel, buf); + + if (mConnectBtn && ::IsWindow(mConnectBtn)) { + ::SetWindowTextW(mConnectBtn, connected ? L"Disconnect" : L"Connect"); + } + if (mBlockBtn && ::IsWindow(mBlockBtn)) { + ::SetWindowTextW(mBlockBtn, blocked ? L"Unblock" : L"Block Input"); + } +} + +LRESULT CALLBACK MouseMuxClient::DebugDialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + MouseMuxClient* self = nullptr; + + if (msg == WM_CREATE) { + CREATESTRUCT* cs = (CREATESTRUCT*)lParam; + self = (MouseMuxClient*)cs->lpCreateParams; + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)self); + } else { + self = (MouseMuxClient*)::GetWindowLongPtr(hwnd, GWLP_USERDATA); + } + + if (self) { + return self->HandleDebugMessage(hwnd, msg, wParam, lParam); + } + return ::DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT MouseMuxClient::HandleDebugMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_MOUSEMUX_UPDATE: + UpdateDebugStatus(); + return 0; + + case WM_MOUSEMUX_LOG: + FlushLogToUI(); + return 0; + + case WM_COMMAND: + if (LOWORD(wParam) == ID_CONNECT) { + if (mConnected.load()) { + Disconnect(); + } else { + Connect(); + } + return 0; + } + if (LOWORD(wParam) == ID_BLOCK) { + if (InputFilter::IsEnabledForWindow(mOwnerHwnd)) { + InputFilter::DisableForWindow(mOwnerHwnd); + Log("Input filter DISABLED"); + } else { + InputFilter::EnableForWindow(mOwnerHwnd); + Log("Input filter ENABLED"); + } + UpdateDebugStatus(); + return 0; + } + break; + + case WM_CLOSE: + HideDebugDialog(); + return 0; + + case WM_DESTROY: + mDebugDialog = nullptr; + mStatusLabel = nullptr; + mConnectBtn = nullptr; + mBlockBtn = nullptr; + mLogEdit = nullptr; + return 0; + } + return ::DefWindowProc(hwnd, msg, wParam, lParam); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/MouseMuxClient.h b/widget/windows/MouseMuxClient.h new file mode 100644 index 0000000000000..628b2c833265d --- /dev/null +++ b/widget/windows/MouseMuxClient.h @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef widget_windows_MouseMuxClient_h +#define widget_windows_MouseMuxClient_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Custom window messages for MouseMux events +#define WM_MOUSEMUX_MOTION (WM_USER + 100) +#define WM_MOUSEMUX_BUTTON (WM_USER + 101) +#define WM_MOUSEMUX_WHEEL (WM_USER + 102) +#define WM_MOUSEMUX_KEY (WM_USER + 103) +#define WM_MOUSEMUX_UPDATE (WM_USER + 104) +#define WM_MOUSEMUX_LOG (WM_USER + 105) + +// Marker in wParam high bit to identify MouseMux-injected messages +#define MOUSEMUX_MARKER 0x80000000 + +namespace mozilla { +namespace widget { + +/** + * MouseMuxClient - Per-window MouseMux connection. + * + * Each Firefox window (nsWindow) owns one MouseMuxClient instance. + * The client connects to the MouseMux server, receives all events, + * filters them to only handle events within its window bounds, + * and tracks ownership (which hwid clicked on this window). + * + * Thread safety: + * - Connect/Disconnect are protected by mConnectMutex + * - Socket access is protected by mSocketMutex + * - All UI updates go through PostMessage to the UI thread + */ +class MouseMuxClient { + public: + explicit MouseMuxClient(HWND aOwnerHwnd); + ~MouseMuxClient(); + + // Non-copyable + MouseMuxClient(const MouseMuxClient&) = delete; + MouseMuxClient& operator=(const MouseMuxClient&) = delete; + + bool Connect(const wchar_t* aUrl = L"ws://localhost:41001"); + void Disconnect(); + bool IsConnected() const { return mConnected.load(); } + + // Ownership - which hwid clicked on this window + uint32_t GetOwnerHwid() const { return mOwnerHwid.load(); } + void ClearOwner() { mOwnerHwid.store(0); } + + // Debug dialog + void ShowDebugDialog(); + void HideDebugDialog(); + bool IsDebugDialogVisible() const { return mDebugDialogVisible; } + + // Logging (thread-safe) + void Log(const char* aFormat, ...); + + private: + // Worker thread + void WebSocketThread(); + void StopWorkerThread(); + + // Message handling + void HandleMessage(const std::string& aMessage); + void ParseUserList(const std::string& aMessage); + void HandlePointerMotion(uint32_t aHwid, int aScreenX, int aScreenY); + void HandlePointerButton(uint32_t aHwid, int aScreenX, int aScreenY, + uint32_t aEventFlags); + void HandlePointerWheel(uint32_t aHwid, int aScreenX, int aScreenY, + int aDelta, bool aIsHorizontal); + void HandleKeyboard(uint32_t aHwid, uint32_t aVkey, uint32_t aMessage, + uint32_t aScanCode, uint32_t aFlags); + + // Helpers + bool IsPointInWindow(int aScreenX, int aScreenY); + WPARAM BuildMouseWParam(uint32_t aHwid); + POINT ScreenToClient(int aScreenX, int aScreenY); + + // Owner window + HWND mOwnerHwnd; + std::atomic mOwnerHwid{0}; + + // Connection state + std::wstring mServerUrl; + SOCKET mSocket = INVALID_SOCKET; + std::atomic mConnected{false}; + std::atomic mShouldStop{false}; + + // Thread management + std::thread mWorkerThread; + std::atomic mThreadRunning{false}; // True while worker thread is running + std::mutex mConnectMutex; // Protects Connect/Disconnect + std::mutex mSocketMutex; // Protects mSocket access + + // Per-device state + std::map mButtonState; + std::mutex mButtonStateMutex; + + struct MousePos { + int screenX = 0; + int screenY = 0; + }; + std::map mLastMousePos; + std::mutex mMousePosMutex; + + std::map mMouseToKeyboard; + std::mutex mMappingMutex; + + // Debug dialog + HWND mDebugDialog = nullptr; + HWND mStatusLabel = nullptr; + HWND mLogEdit = nullptr; + HWND mConnectBtn = nullptr; + HWND mBlockBtn = nullptr; + std::vector mLogLines; + std::mutex mLogMutex; + bool mDebugDialogVisible = false; + + void CreateDebugDialog(); + void UpdateDebugStatus(); // Call only from UI thread + void UpdateDebugStatusSafe(); // Safe from any thread (posts message) + void AppendLog(const char* text); + void FlushLogToUI(); + + static LRESULT CALLBACK DebugDialogProc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam); + LRESULT HandleDebugMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + + enum DebugControls { + ID_STATUS = 1001, + ID_CONNECT = 1002, + ID_BLOCK = 1003, + ID_LOG = 1004 + }; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_windows_MouseMuxClient_h diff --git a/widget/windows/MouseMuxDebugDialog.cpp b/widget/windows/MouseMuxDebugDialog.cpp new file mode 100644 index 0000000000000..1eeb3865a2677 --- /dev/null +++ b/widget/windows/MouseMuxDebugDialog.cpp @@ -0,0 +1,260 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "MouseMuxDebugDialog.h" +#include "MouseMuxService.h" +#include "InputFilter.h" +#include +#include + +#define MOUSEMUX_VERSION "3.0" + +namespace mozilla { +namespace widget { + +MouseMuxDebugDialog* MouseMuxDebugDialog::sInstance = nullptr; + +MouseMuxDebugDialog* MouseMuxDebugDialog::GetInstance() { + if (!sInstance) { + sInstance = new MouseMuxDebugDialog(); + } + return sInstance; +} + +void MouseMuxDebugDialog::Shutdown() { + if (sInstance) { + sInstance->Hide(); + delete sInstance; + sInstance = nullptr; + } +} + +MouseMuxDebugDialog::MouseMuxDebugDialog() {} + +MouseMuxDebugDialog::~MouseMuxDebugDialog() { + if (mDialog) { + ::DestroyWindow(mDialog); + mDialog = nullptr; + } +} + +void MouseMuxDebugDialog::CreateDialogWindow() { + if (mDialog) return; + + WNDCLASSEXW wc = {0}; + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = DialogProc; + wc.hInstance = ::GetModuleHandle(nullptr); + wc.hCursor = ::LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wc.lpszClassName = L"MouseMuxDebugDialog"; + ::RegisterClassExW(&wc); + + // Create as normal overlapped window with taskbar entry, always on top + wchar_t title[64]; + swprintf(title, 64, L"MouseMux Debug v%S", MOUSEMUX_VERSION); + mDialog = ::CreateWindowExW( + WS_EX_TOPMOST | WS_EX_APPWINDOW, L"MouseMuxDebugDialog", title, + WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 400, 350, + nullptr, nullptr, ::GetModuleHandle(nullptr), this); + + // Log to file for debugging + FILE* f = fopen("D:/scratch/firefox/mousemux_debug.log", "a"); + if (f) { + fprintf(f, "[Dialog] CreateWindowExW returned %p, GetLastError=%lu\n", mDialog, ::GetLastError()); + fflush(f); + fclose(f); + } + + mStatusLabel = ::CreateWindowW(L"STATIC", L"Status: Disconnected", + WS_CHILD | WS_VISIBLE, 10, 10, 380, 20, + mDialog, (HMENU)ID_STATUS, nullptr, nullptr); + + mConnectBtn = ::CreateWindowW(L"BUTTON", L"Connect", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 10, 40, + 120, 25, mDialog, (HMENU)ID_CONNECT, nullptr, nullptr); + + mBlockBtn = ::CreateWindowW(L"BUTTON", L"Block Input", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 140, 40, + 120, 25, mDialog, (HMENU)ID_BLOCK, nullptr, nullptr); + + mLogEdit = ::CreateWindowExW( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY, + 10, 75, 370, 230, mDialog, (HMENU)ID_LOG, nullptr, nullptr); + + HFONT hFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT); + ::SendMessage(mStatusLabel, WM_SETFONT, (WPARAM)hFont, TRUE); + ::SendMessage(mConnectBtn, WM_SETFONT, (WPARAM)hFont, TRUE); + ::SendMessage(mBlockBtn, WM_SETFONT, (WPARAM)hFont, TRUE); + ::SendMessage(mLogEdit, WM_SETFONT, (WPARAM)hFont, TRUE); + + MouseMuxService::GetInstance()->SetLogCallback( + [this](const char* msg) { this->AppendLog(msg); }); + + UpdateStatus(); +} + +void MouseMuxDebugDialog::Show() { + FILE* f = fopen("D:/scratch/firefox/mousemux_debug.log", "a"); + if (f) { + fprintf(f, "[Dialog] Show() called, mDialog=%p\n", mDialog); + fflush(f); + fclose(f); + } + + if (!mDialog) { + CreateDialogWindow(); + } + + if (mDialog) { + ::ShowWindow(mDialog, SW_SHOWNORMAL); + ::SetForegroundWindow(mDialog); + ::BringWindowToTop(mDialog); + mVisible = true; + UpdateStatus(); + + f = fopen("D:/scratch/firefox/mousemux_debug.log", "a"); + if (f) { + fprintf(f, "[Dialog] ShowWindow done, mDialog=%p visible=%d\n", mDialog, ::IsWindowVisible(mDialog)); + fflush(f); + fclose(f); + } + } +} + +void MouseMuxDebugDialog::Hide() { + if (mDialog) { + ::ShowWindow(mDialog, SW_HIDE); + } + mVisible = false; +} + +LRESULT CALLBACK MouseMuxDebugDialog::DialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + MouseMuxDebugDialog* self = nullptr; + + if (msg == WM_CREATE) { + CREATESTRUCT* cs = (CREATESTRUCT*)lParam; + self = (MouseMuxDebugDialog*)cs->lpCreateParams; + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)self); + } else { + self = (MouseMuxDebugDialog*)::GetWindowLongPtr(hwnd, GWLP_USERDATA); + } + + if (self) { + return self->HandleMessage(msg, wParam, lParam); + } + return ::DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT MouseMuxDebugDialog::HandleMessage(UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_COMMAND: + switch (LOWORD(wParam)) { + case ID_CONNECT: OnToggleConnect(); return 0; + case ID_BLOCK: OnToggleBlock(); return 0; + } + break; + case WM_CLOSE: + Hide(); + return 0; + case WM_DESTROY: + mDialog = nullptr; + return 0; + } + return ::DefWindowProc(mDialog, msg, wParam, lParam); +} + +void MouseMuxDebugDialog::OnToggleConnect() { + auto* service = MouseMuxService::GetInstance(); + if (service->IsConnected()) { + Log("Disconnecting..."); + service->Disconnect(); + } else { + Log("Connecting..."); + service->Connect(); + } + UpdateStatus(); +} + +void MouseMuxDebugDialog::OnToggleBlock() { + if (InputFilter::IsEnabled()) { + InputFilter::Disable(); + Log("Input filter DISABLED"); + } else { + InputFilter::Enable(); + Log("Input filter ENABLED"); + } + UpdateStatus(); +} + +void MouseMuxDebugDialog::UpdateStatus() { + if (!mStatusLabel) return; + + auto* service = MouseMuxService::GetInstance(); + const char* statusText = "Unknown"; + bool connected = false; + + switch (service->GetConnectionState()) { + case MouseMuxService::ConnectionState::Disconnected: statusText = "Disconnected"; break; + case MouseMuxService::ConnectionState::Connecting: statusText = "Connecting..."; break; + case MouseMuxService::ConnectionState::Connected: statusText = "Connected"; connected = true; break; + case MouseMuxService::ConnectionState::Reconnecting: statusText = "Reconnecting..."; break; + } + + bool blocked = InputFilter::IsEnabled(); + uint32_t ownerHwid = service->GetOwnerHwid(); + + wchar_t buf[256]; + if (ownerHwid) { + swprintf(buf, 256, L"%S | %s | Owner: 0x%X", + statusText, blocked ? L"BLOCKED" : L"Normal", ownerHwid); + } else { + swprintf(buf, 256, L"%S | %s | Owner: None", + statusText, blocked ? L"BLOCKED" : L"Normal"); + } + ::SetWindowTextW(mStatusLabel, buf); + + if (mConnectBtn) { + ::SetWindowTextW(mConnectBtn, connected ? L"Disconnect" : L"Connect"); + } + if (mBlockBtn) { + ::SetWindowTextW(mBlockBtn, blocked ? L"Unblock" : L"Block Input"); + } +} + +void MouseMuxDebugDialog::Log(const char* aFormat, ...) { + char buf[512]; + va_list args; + va_start(args, aFormat); + vsnprintf(buf, sizeof(buf), aFormat, args); + va_end(args); + AppendLog(buf); +} + +void MouseMuxDebugDialog::AppendLog(const char* text) { + if (!mLogEdit) return; + + std::lock_guard lock(mLogMutex); + + mLogLines.push_back(text); + while (mLogLines.size() > 100) { + mLogLines.erase(mLogLines.begin()); + } + + std::string fullText; + for (const auto& line : mLogLines) { + fullText += line; + fullText += "\r\n"; + } + + ::SetWindowTextA(mLogEdit, fullText.c_str()); + int lineCount = (int)::SendMessage(mLogEdit, EM_GETLINECOUNT, 0, 0); + ::SendMessage(mLogEdit, EM_LINESCROLL, 0, lineCount); + UpdateStatus(); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/MouseMuxDebugDialog.h b/widget/windows/MouseMuxDebugDialog.h new file mode 100644 index 0000000000000..66f61a982a9e8 --- /dev/null +++ b/widget/windows/MouseMuxDebugDialog.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef widget_windows_MouseMuxDebugDialog_h +#define widget_windows_MouseMuxDebugDialog_h + +#include +#include +#include +#include + +namespace mozilla { +namespace widget { + +class MouseMuxDebugDialog { + public: + static MouseMuxDebugDialog* GetInstance(); + static void Shutdown(); + + void Show(); + void Hide(); + bool IsVisible() const { return mVisible; } + + private: + MouseMuxDebugDialog(); + ~MouseMuxDebugDialog(); + + void CreateDialogWindow(); + static LRESULT CALLBACK DialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + LRESULT HandleMessage(UINT msg, WPARAM wParam, LPARAM lParam); + + void OnToggleConnect(); + void OnToggleBlock(); + void UpdateStatus(); + void Log(const char* aFormat, ...); + void AppendLog(const char* text); + + static MouseMuxDebugDialog* sInstance; + + HWND mDialog = nullptr; + HWND mStatusLabel = nullptr; + HWND mConnectBtn = nullptr; + HWND mBlockBtn = nullptr; + HWND mLogEdit = nullptr; + + bool mVisible = false; + std::vector mLogLines; + std::mutex mLogMutex; + + enum { ID_STATUS = 100, ID_CONNECT, ID_BLOCK, ID_LOG }; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_windows_MouseMuxDebugDialog_h diff --git a/widget/windows/MouseMuxService.cpp b/widget/windows/MouseMuxService.cpp new file mode 100644 index 0000000000000..c8076d77299dc --- /dev/null +++ b/widget/windows/MouseMuxService.cpp @@ -0,0 +1,586 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "MouseMuxService.h" +#include "MouseMuxDebugDialog.h" +#include "nsWindow.h" + +#include +#include +#include +#include +#include + +#pragma comment(lib, "ws2_32.lib") + +// Marker in wParam high bit to identify MouseMux-injected messages +#define MOUSEMUX_MARKER 0x80000000 + +namespace mozilla { +namespace widget { + +MouseMuxService* MouseMuxService::sInstance = nullptr; + +static bool ExtractString(const std::string& json, const char* key, + std::string& value) { + std::string searchKey = std::string("\"") + key + "\":"; + size_t pos = json.find(searchKey); + if (pos == std::string::npos) return false; + pos += searchKey.length(); + while (pos < json.length() && (json[pos] == ' ' || json[pos] == '"')) pos++; + if (json[pos - 1] != '"') return false; + size_t end = json.find('"', pos); + if (end == std::string::npos) return false; + value = json.substr(pos, end - pos); + return true; +} + +static bool ExtractInt(const std::string& json, const char* key, int& value) { + std::string searchKey = std::string("\"") + key + "\":"; + size_t pos = json.find(searchKey); + if (pos == std::string::npos) return false; + pos += searchKey.length(); + while (pos < json.length() && json[pos] == ' ') pos++; + value = std::atoi(json.c_str() + pos); + return true; +} + +static bool ExtractUint(const std::string& json, const char* key, uint32_t& value) { + int intVal; + if (!ExtractInt(json, key, intVal)) return false; + value = static_cast(intVal); + return true; +} + +MouseMuxService* MouseMuxService::GetInstance() { + if (!sInstance) { + sInstance = new MouseMuxService(); + } + return sInstance; +} + +void MouseMuxService::Shutdown() { + if (sInstance) { + sInstance->Disconnect(); + delete sInstance; + sInstance = nullptr; + } +} + +static DWORD WINAPI DialogThreadProc(LPVOID) { + MouseMuxDebugDialog::GetInstance()->Show(); + MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + return 0; +} + +MouseMuxService::MouseMuxService() { + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); + ::CreateThread(nullptr, 0, DialogThreadProc, nullptr, 0, nullptr); +} + +MouseMuxService::~MouseMuxService() { + Disconnect(); + WSACleanup(); +} + +bool MouseMuxService::Connect(const wchar_t* aUrl) { + if (mConnectionState == ConnectionState::Connected || + mConnectionState == ConnectionState::Connecting) { + return false; + } + + mServerUrl = aUrl; + mShouldStop = false; + mConnectionState = ConnectionState::Connecting; + + Log("Connecting..."); + + mWorkerThread = std::thread(&MouseMuxService::WebSocketThread, this); + return true; +} + +void MouseMuxService::Disconnect() { + if (mConnectionState == ConnectionState::Disconnected) return; + + Log("Disconnecting..."); + mShouldStop = true; + + if (mSocket != INVALID_SOCKET) { + closesocket(mSocket); + mSocket = INVALID_SOCKET; + } + + if (mWorkerThread.joinable()) { + mWorkerThread.join(); + } + + mConnectionState = ConnectionState::Disconnected; +} + +void MouseMuxService::WebSocketThread() { + Log("WS thread start"); + + mSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (mSocket == INVALID_SOCKET) { + Log("Socket create failed"); + mConnectionState = ConnectionState::Disconnected; + return; + } + + struct sockaddr_in serverAddr; + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(41001); + inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr); + + if (connect(mSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { + Log("TCP connect failed"); + closesocket(mSocket); + mSocket = INVALID_SOCKET; + mConnectionState = ConnectionState::Disconnected; + return; + } + + Log("TCP connected"); + + std::string handshake = + "GET / HTTP/1.1\r\n" + "Host: localhost:41001\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"; + + send(mSocket, handshake.c_str(), (int)handshake.length(), 0); + + char buffer[4096]; + int bytesRead = recv(mSocket, buffer, sizeof(buffer) - 1, 0); + if (bytesRead <= 0) { + Log("Handshake recv failed"); + closesocket(mSocket); + mSocket = INVALID_SOCKET; + mConnectionState = ConnectionState::Disconnected; + return; + } + buffer[bytesRead] = '\0'; + + if (strstr(buffer, "101") == nullptr) { + Log("WS handshake rejected"); + closesocket(mSocket); + mSocket = INVALID_SOCKET; + mConnectionState = ConnectionState::Disconnected; + return; + } + + Log("WS connected!"); + mConnectionState = ConnectionState::Connected; + + std::string messageBuffer; + + while (!mShouldStop) { + bytesRead = recv(mSocket, buffer, sizeof(buffer), 0); + if (bytesRead <= 0) { + if (!mShouldStop) Log("Connection lost"); + break; + } + + size_t pos = 0; + while (pos < (size_t)bytesRead) { + if (pos + 2 > (size_t)bytesRead) break; + + uint8_t byte0 = buffer[pos]; + uint8_t byte1 = buffer[pos + 1]; + bool fin = (byte0 & 0x80) != 0; + int opcode = byte0 & 0x0F; + bool masked = (byte1 & 0x80) != 0; + uint64_t payloadLen = byte1 & 0x7F; + pos += 2; + + if (payloadLen == 126) { + if (pos + 2 > (size_t)bytesRead) break; + payloadLen = (uint8_t)buffer[pos] << 8 | (uint8_t)buffer[pos + 1]; + pos += 2; + } else if (payloadLen == 127) { + if (pos + 8 > (size_t)bytesRead) break; + payloadLen = 0; + for (int i = 0; i < 8; i++) { + payloadLen = (payloadLen << 8) | (uint8_t)buffer[pos + i]; + } + pos += 8; + } + + uint8_t maskKey[4] = {0}; + if (masked) { + if (pos + 4 > (size_t)bytesRead) break; + memcpy(maskKey, buffer + pos, 4); + pos += 4; + } + + if (pos + payloadLen > (size_t)bytesRead) break; + + std::string payload(buffer + pos, payloadLen); + if (masked) { + for (size_t i = 0; i < payloadLen; i++) { + payload[i] ^= maskKey[i % 4]; + } + } + pos += payloadLen; + + if (opcode == 0x01) { + messageBuffer += payload; + if (fin) { + HandleMessage(messageBuffer); + messageBuffer.clear(); + } + } else if (opcode == 0x08) { + Log("Server close"); + mShouldStop = true; + break; + } else if (opcode == 0x09) { + uint8_t pong[2] = {0x8A, 0x00}; + send(mSocket, (char*)pong, 2, 0); + } + } + } + + Log("WS thread end"); + if (mSocket != INVALID_SOCKET) { + closesocket(mSocket); + mSocket = INVALID_SOCKET; + } + mConnectionState = ConnectionState::Disconnected; +} + +void MouseMuxService::HandleMessage(const std::string& aMessage) { + std::string type; + if (!ExtractString(aMessage, "type", type)) return; + + uint32_t hwid; + int x, y; + uint32_t data; + + if (type == "pointer.motion.notify.M2A") { + if (!ExtractUint(aMessage, "hwid", hwid)) return; + if (!ExtractInt(aMessage, "x", x)) return; + if (!ExtractInt(aMessage, "y", y)) return; + + mLastMousePos[hwid] = {x, y}; + HandlePointerMotion(hwid, x, y); + + } else if (type == "pointer.button.notify.M2A") { + if (!ExtractUint(aMessage, "hwid", hwid)) return; + if (!ExtractInt(aMessage, "x", x)) return; + if (!ExtractInt(aMessage, "y", y)) return; + if (!ExtractUint(aMessage, "data", data)) return; + + mLastMousePos[hwid] = {x, y}; + HandlePointerButton(hwid, x, y, data); + + } else if (type == "pointer.wheel.v.notify.M2A") { + if (!ExtractUint(aMessage, "hwid", hwid)) return; + int delta; + if (!ExtractInt(aMessage, "delta", delta)) return; + auto& pos = mLastMousePos[hwid]; + HandlePointerWheel(hwid, pos.screenX, pos.screenY, delta, false); + + } else if (type == "pointer.wheel.h.notify.M2A") { + if (!ExtractUint(aMessage, "hwid", hwid)) return; + int delta; + if (!ExtractInt(aMessage, "delta", delta)) return; + auto& pos = mLastMousePos[hwid]; + HandlePointerWheel(hwid, pos.screenX, pos.screenY, delta, true); + + } else if (type == "keyboard.key.notify.M2A") { + if (!ExtractUint(aMessage, "hwid", hwid)) return; + uint32_t vkey, msg, scancode, flags; + if (!ExtractUint(aMessage, "vkey", vkey)) return; + if (!ExtractUint(aMessage, "message", msg)) return; + if (!ExtractUint(aMessage, "scan", scancode)) return; + if (!ExtractUint(aMessage, "flags", flags)) return; + HandleKeyboard(hwid, vkey, msg, scancode, flags); + + } else if (type == "user.list.notify.M2A") { + HandleUserList(aMessage); + } +} + +void MouseMuxService::HandlePointerMotion(uint32_t aHwid, int aScreenX, int aScreenY) { + nsWindow* window = nullptr; + + // If this hwid is the owner, always send to owned window + if (aHwid == mOwnerHwid && mOwnedWindow) { + window = mOwnedWindow; + } else { + window = FindWindowAtPoint(aScreenX, aScreenY); + } + + if (!window) return; + + HWND hwnd = window->GetWindowHandle(); + if (!hwnd) return; + + // Convert screen coords to client coords + POINT pt = {aScreenX, aScreenY}; + ::ScreenToClient(hwnd, &pt); + + // Build wParam from tracked button state (from MouseMux events only) + WPARAM wParam = BuildMouseWParam(aHwid); + + // Add marker to identify this as MouseMux message + wParam |= MOUSEMUX_MARKER; + + LPARAM lParam = MAKELPARAM(pt.x, pt.y); + + ::PostMessage(hwnd, WM_MOUSEMOVE, wParam, lParam); +} + +void MouseMuxService::HandlePointerButton(uint32_t aHwid, int aScreenX, + int aScreenY, uint32_t aEventFlags) { + // SDK v2.2.32 event flags: + // 0x01=LeftDown, 0x02=LeftUp, 0x04=RightDown, 0x08=RightUp, + // 0x10=MiddleDown, 0x20=MiddleUp + + nsWindow* window = nullptr; + bool isButtonDown = (aEventFlags & 0x01) || (aEventFlags & 0x04) || (aEventFlags & 0x10); + + // If this hwid is the owner, always send to owned window + if (aHwid == mOwnerHwid && mOwnedWindow) { + window = mOwnedWindow; + } else { + window = FindWindowAtPoint(aScreenX, aScreenY); + } + + if (!window) return; + + // On button down inside a window, this hwid becomes the owner + if (isButtonDown && window) { + if (aHwid != mOwnerHwid) { + mOwnerHwid = aHwid; + mOwnedWindow = window; + Log("New owner: hwid=0x%x window=%p", aHwid, window); + } + } + + HWND hwnd = window->GetWindowHandle(); + if (!hwnd) return; + + // Convert screen coords to client coords + POINT pt = {aScreenX, aScreenY}; + ::ScreenToClient(hwnd, &pt); + + LPARAM lParam = MAKELPARAM(pt.x, pt.y); + + // Update tracked button state and post messages + if (aEventFlags & 0x01) { // Left down + mButtonState[aHwid] |= MK_LBUTTON; + WPARAM wParam = BuildMouseWParam(aHwid) | MOUSEMUX_MARKER; + ::PostMessage(hwnd, WM_LBUTTONDOWN, wParam, lParam); + } + if (aEventFlags & 0x02) { // Left up + mButtonState[aHwid] &= ~MK_LBUTTON; + WPARAM wParam = BuildMouseWParam(aHwid) | MOUSEMUX_MARKER; + ::PostMessage(hwnd, WM_LBUTTONUP, wParam, lParam); + } + if (aEventFlags & 0x04) { // Right down + mButtonState[aHwid] |= MK_RBUTTON; + WPARAM wParam = BuildMouseWParam(aHwid) | MOUSEMUX_MARKER; + ::PostMessage(hwnd, WM_RBUTTONDOWN, wParam, lParam); + } + if (aEventFlags & 0x08) { // Right up + mButtonState[aHwid] &= ~MK_RBUTTON; + WPARAM wParam = BuildMouseWParam(aHwid) | MOUSEMUX_MARKER; + ::PostMessage(hwnd, WM_RBUTTONUP, wParam, lParam); + } + if (aEventFlags & 0x10) { // Middle down + mButtonState[aHwid] |= MK_MBUTTON; + WPARAM wParam = BuildMouseWParam(aHwid) | MOUSEMUX_MARKER; + ::PostMessage(hwnd, WM_MBUTTONDOWN, wParam, lParam); + } + if (aEventFlags & 0x20) { // Middle up + mButtonState[aHwid] &= ~MK_MBUTTON; + WPARAM wParam = BuildMouseWParam(aHwid) | MOUSEMUX_MARKER; + ::PostMessage(hwnd, WM_MBUTTONUP, wParam, lParam); + } +} + +void MouseMuxService::HandlePointerWheel(uint32_t aHwid, int aScreenX, + int aScreenY, int aDelta, bool aIsHorizontal) { + nsWindow* window = nullptr; + + // If this hwid is the owner, always send to owned window + if (aHwid == mOwnerHwid && mOwnedWindow) { + window = mOwnedWindow; + } else { + window = FindWindowAtPoint(aScreenX, aScreenY); + } + + if (!window) return; + + HWND hwnd = window->GetWindowHandle(); + if (!hwnd) return; + + // Wheel messages use screen coordinates in lParam + LPARAM lParam = MAKELPARAM(aScreenX, aScreenY); + + // wParam: HIWORD = wheel delta, LOWORD = key state + WORD keys = (WORD)BuildMouseWParam(aHwid); + WPARAM wParam = MAKEWPARAM(keys, (short)aDelta); + + // Add marker in high bit of low word (keys area has unused bits) + wParam |= MOUSEMUX_MARKER; + + ::PostMessage(hwnd, aIsHorizontal ? WM_MOUSEHWHEEL : WM_MOUSEWHEEL, wParam, lParam); +} + +void MouseMuxService::HandleKeyboard(uint32_t aHwid, uint32_t aVkey, + uint32_t aMessage, uint32_t aScanCode, + uint32_t aFlags) { + // For now, send to focused window + // TODO: Track focus per MouseMux user + nsWindow* window = nullptr; + { + std::lock_guard lock(mWindowsMutex); + if (!mWindows.empty()) { + window = mWindows[0]; // Use first registered window for now + } + } + if (!window) return; + + HWND hwnd = window->GetWindowHandle(); + if (!hwnd) return; + + // Build lParam for key message + LPARAM lParam = 1; // repeat count + lParam |= (aScanCode & 0xFF) << 16; + lParam |= (aFlags & 0x01) << 24; // extended key + + bool isUp = (aMessage == WM_KEYUP || aMessage == WM_SYSKEYUP); + if (isUp) { + lParam |= (1 << 30); // previous key state + lParam |= (1 << 31); // transition state + } + + // Add marker to wParam so nsWindow knows this is from MouseMux + WPARAM wParam = aVkey | MOUSEMUX_MARKER; + ::PostMessage(hwnd, aMessage, wParam, lParam); +} + +void MouseMuxService::HandleUserList(const std::string& aMessage) { + Log("User list received"); +} + +WPARAM MouseMuxService::BuildMouseWParam(uint32_t aHwid) { + // Build wParam from our tracked button state only + // No GetKeyState or other Win32 calls + WPARAM wParam = 0; + auto it = mButtonState.find(aHwid); + if (it != mButtonState.end()) { + wParam = it->second; + } + return wParam; +} + +nsWindow* MouseMuxService::FindWindowAtPoint(int aScreenX, int aScreenY) { + std::lock_guard lock(mWindowsMutex); + for (nsWindow* window : mWindows) { + if (!window) continue; + HWND hwnd = window->GetWindowHandle(); + if (!hwnd || !::IsWindowVisible(hwnd)) continue; + RECT rect; + if (::GetWindowRect(hwnd, &rect)) { + if (aScreenX >= rect.left && aScreenX < rect.right && + aScreenY >= rect.top && aScreenY < rect.bottom) { + return window; + } + } + } + return nullptr; +} + +void MouseMuxService::RegisterWindow(nsWindow* aWindow) { + std::lock_guard lock(mWindowsMutex); + mWindows.push_back(aWindow); + Log("RegisterWindow %p", aWindow); +} + +void MouseMuxService::UnregisterWindow(nsWindow* aWindow) { + { + std::lock_guard lock(mWindowsMutex); + mWindows.erase(std::remove(mWindows.begin(), mWindows.end(), aWindow), mWindows.end()); + } + { + std::lock_guard lock(mActiveUsersMutex); + mActiveUsers.erase(aWindow); + } + Log("UnregisterWindow %p", aWindow); +} + +void MouseMuxService::SetActiveHwid(nsWindow* aWindow, uint32_t aMouseHwid, uint32_t aKeyboardHwid) { + std::lock_guard lock(mActiveUsersMutex); + mActiveUsers[aWindow] = {aMouseHwid, aKeyboardHwid}; +} + +uint32_t MouseMuxService::GetActiveMouseHwid(nsWindow* aWindow) const { + std::lock_guard lock(mActiveUsersMutex); + auto it = mActiveUsers.find(aWindow); + return (it != mActiveUsers.end()) ? it->second.mouseHwid : 0; +} + +void MouseMuxService::ClearActiveHwid(nsWindow* aWindow) { + std::lock_guard lock(mActiveUsersMutex); + mActiveUsers.erase(aWindow); +} + +void MouseMuxService::SetUserMapping(uint32_t aMouseHwid, uint32_t aKeyboardHwid) { + std::lock_guard lock(mUserMappingMutex); + mMouseToKeyboard[aMouseHwid] = aKeyboardHwid; + mKeyboardToMouse[aKeyboardHwid] = aMouseHwid; +} + +uint32_t MouseMuxService::GetKeyboardHwidForMouse(uint32_t aMouseHwid) const { + std::lock_guard lock(mUserMappingMutex); + auto it = mMouseToKeyboard.find(aMouseHwid); + return (it != mMouseToKeyboard.end()) ? it->second : 0; +} + +void MouseMuxService::SetLogCallback(LogCallback aCallback) { + std::lock_guard lock(mLogMutex); + mLogCallback = aCallback; +} + +// Version for tracking builds +#define MOUSEMUX_VERSION "3.0" + +void MouseMuxService::Log(const char* aFormat, ...) { + char buf[512]; + va_list args; + va_start(args, aFormat); + vsnprintf(buf, sizeof(buf), aFormat, args); + va_end(args); + + // Get current time + SYSTEMTIME st; + GetLocalTime(&st); + char timeBuf[64]; + snprintf(timeBuf, sizeof(timeBuf), "%02d:%02d:%02d.%03d", + st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); + + FILE* f = fopen("D:\\scratch\\firefox\\mousemux_debug.log", "a"); + if (f) { + fprintf(f, "[MM v%s %s] %s\n", MOUSEMUX_VERSION, timeBuf, buf); + fclose(f); + } + + std::lock_guard lock(mLogMutex); + if (mLogCallback) mLogCallback(buf); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/MouseMuxService.h b/widget/windows/MouseMuxService.h new file mode 100644 index 0000000000000..488cb52163724 --- /dev/null +++ b/widget/windows/MouseMuxService.h @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef widget_windows_MouseMuxService_h +#define widget_windows_MouseMuxService_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Marker in wParam high bit to identify MouseMux-injected messages +#define MOUSEMUX_MARKER 0x80000000 + +// Forward declaration +class nsWindow; + +namespace mozilla { +namespace widget { + +/** + * MouseMuxService - Connects to MouseMux server and injects input via + * PostMessage directly to Firefox HWNDs. + * + * No SendInput, no GetKeyState - only PostMessage with data from MouseMux. + */ +class MouseMuxService { + public: + enum class ConnectionState { + Disconnected, + Connecting, + Connected, + Reconnecting + }; + + static MouseMuxService* GetInstance(); + static void Shutdown(); + + bool Connect(const wchar_t* aUrl = L"ws://localhost:41001"); + void Disconnect(); + ConnectionState GetConnectionState() const { return mConnectionState; } + bool IsConnected() const { + return mConnectionState == ConnectionState::Connected; + } + + void RegisterWindow(nsWindow* aWindow); + void UnregisterWindow(nsWindow* aWindow); + + void SetActiveHwid(nsWindow* aWindow, uint32_t aMouseHwid, + uint32_t aKeyboardHwid); + uint32_t GetActiveMouseHwid(nsWindow* aWindow) const; + void ClearActiveHwid(nsWindow* aWindow); + + void SetUserMapping(uint32_t aMouseHwid, uint32_t aKeyboardHwid); + uint32_t GetKeyboardHwidForMouse(uint32_t aMouseHwid) const; + + using LogCallback = std::function; + void SetLogCallback(LogCallback aCallback); + + // Window ownership - the hwid that clicked on the window owns it + uint32_t GetOwnerHwid() const { return mOwnerHwid; } + nsWindow* GetOwnedWindow() const { return mOwnedWindow; } + + private: + MouseMuxService(); + ~MouseMuxService(); + + void WebSocketThread(); + void HandleMessage(const std::string& aMessage); + void HandlePointerMotion(uint32_t aHwid, int aScreenX, int aScreenY); + void HandlePointerButton(uint32_t aHwid, int aScreenX, int aScreenY, + uint32_t aEventFlags); + void HandlePointerWheel(uint32_t aHwid, int aScreenX, int aScreenY, + int aDelta, bool aIsHorizontal); + void HandleKeyboard(uint32_t aHwid, uint32_t aVkey, uint32_t aMessage, + uint32_t aScanCode, uint32_t aFlags); + void HandleUserList(const std::string& aMessage); + + nsWindow* FindWindowAtPoint(int aScreenX, int aScreenY); + WPARAM BuildMouseWParam(uint32_t aHwid); + void Log(const char* aFormat, ...); + + static MouseMuxService* sInstance; + + std::atomic mConnectionState{ConnectionState::Disconnected}; + std::wstring mServerUrl; + SOCKET mSocket = INVALID_SOCKET; + + std::thread mWorkerThread; + std::atomic mShouldStop{false}; + + std::vector mWindows; + mutable std::mutex mWindowsMutex; + + struct ActiveUser { + uint32_t mouseHwid = 0; + uint32_t keyboardHwid = 0; + }; + std::map mActiveUsers; + mutable std::mutex mActiveUsersMutex; + + std::map mMouseToKeyboard; + std::map mKeyboardToMouse; + mutable std::mutex mUserMappingMutex; + + // Track button state per device from MouseMux events only + std::map mButtonState; + + struct MousePos { + int screenX = 0; + int screenY = 0; + }; + std::map mLastMousePos; + + // Window ownership tracking + uint32_t mOwnerHwid = 0; + nsWindow* mOwnedWindow = nullptr; + + LogCallback mLogCallback; + std::mutex mLogMutex; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_windows_MouseMuxService_h diff --git a/widget/windows/moz.build b/widget/windows/moz.build index 9830323e2b89e..c219113b09b53 100644 --- a/widget/windows/moz.build +++ b/widget/windows/moz.build @@ -129,7 +129,9 @@ UNIFIED_SOURCES += [ SOURCES += [ "CompositorWidgetParent.cpp", "InProcessWinCompositorWidget.cpp", + "InputFilter.cpp", "MediaKeysEventSourceFactory.cpp", + "MouseMuxClient.cpp", "nsBidiKeyboard.cpp", "nsFilePicker.cpp", "nsSharePicker.cpp", @@ -214,6 +216,7 @@ OS_LIBS += [ "shcore", "urlmon", "winmm", + "ws2_32", ] # mingw is missing Windows toast notification definitions. diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp index b12e87f9fc236..358a5259cecc1 100644 --- a/widget/windows/nsWindow.cpp +++ b/widget/windows/nsWindow.cpp @@ -193,6 +193,8 @@ #include "WindowsUIUtils.h" +#include "InputFilter.h" + #include "nsWindowDefs.h" #include "nsCrashOnException.h" @@ -855,6 +857,8 @@ nsWindow::nsWindow() nsWindow::~nsWindow() { mInDtor = true; + // Clean up per-window InputFilter state + InputFilter::RemoveWindow(mWnd); // If the widget was released without calling Destroy() then the native window // still exists, and we need to destroy it. Destroy() will early-return if it @@ -1281,6 +1285,9 @@ nsresult nsWindow::Create(nsIWidget* aParent, const LayoutDeviceIntRect& aRect, RecreateDirectManipulationIfNeeded(); + // Initialize per-window MouseMux client for multi-mouse support + InitMouseMux(); + return NS_OK; } @@ -1310,6 +1317,9 @@ void nsWindow::Destroy() { DestroyDirectManipulation(); + // Shutdown MouseMux client before window destruction + ShutdownMouseMux(); + /** * On windows the LayerManagerOGL destructor wants the widget to be around for * cleanup. It also would like to have the HWND intact, so we nullptr it here. @@ -3977,13 +3987,20 @@ void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) { if (nullptr == aPoint) { // use the point from the event // get the message position in client coordinates if (mWnd != nullptr) { - DWORD pos = ::GetMessagePos(); POINT cpos; - - cpos.x = GET_X_LPARAM(pos); - cpos.y = GET_Y_LPARAM(pos); - - ::ScreenToClient(mWnd, &cpos); + + // MouseMux: Use stored cursor position instead of GetMessagePos + if (InputFilter::IsEnabledForWindow(mWnd) && + InputFilter::GetCursorPosForWindow(mWnd, &cpos)) { + // cpos now contains screen coordinates from MouseMux + ::ScreenToClient(mWnd, &cpos); + } else { + // Fall back to native GetMessagePos + DWORD pos = ::GetMessagePos(); + cpos.x = GET_X_LPARAM(pos); + cpos.y = GET_Y_LPARAM(pos); + ::ScreenToClient(mWnd, &cpos); + } event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y); } else { event.mRefPoint = LayoutDeviceIntPoint(0, 0); @@ -4344,14 +4361,19 @@ bool nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam, if (rect.Contains(mouseOrPointerEvent.mRefPoint)) { if (sCurrentWindow == nullptr || sCurrentWindow != this) { - if ((nullptr != sCurrentWindow) && (!sCurrentWindow->mInDtor)) { + // MouseMux: Skip cross-window exit/enter when InputFilter is enabled + // Each MouseMux window is independent and shouldn't affect others + bool skipCrossWindow = InputFilter::IsEnabledForWindow(mWnd); + if (!skipCrossWindow && (nullptr != sCurrentWindow) && (!sCurrentWindow->mInDtor)) { LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam)); sCurrentWindow->DispatchMouseEvent( eMouseExitFromWidget, wParam, pos, false, MouseButton::ePrimary, aInputSource, aPointerInfo); } - sCurrentWindow = this; - if (!mInDtor) { + if (!skipCrossWindow) { + sCurrentWindow = this; + } + if (!mInDtor && !skipCrossWindow) { LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam)); sCurrentWindow->DispatchMouseEvent( eMouseEnterIntoWidget, wParam, pos, false, @@ -4424,16 +4446,22 @@ void nsWindow::DispatchFocusToTopLevelWindow(bool aIsActivate) { } } -HWND nsWindow::WindowAtMouse() { - DWORD pos = ::GetMessagePos(); +HWND nsWindow::WindowAtMouse(HWND aForWnd) { POINT mp; - mp.x = GET_X_LPARAM(pos); - mp.y = GET_Y_LPARAM(pos); + // MouseMux: Use stored cursor position if available + if (aForWnd && InputFilter::IsEnabledForWindow(aForWnd) && + InputFilter::GetCursorPosForWindow(aForWnd, &mp)) { + // mp now has screen coords from MouseMux + } else { + DWORD pos = ::GetMessagePos(); + mp.x = GET_X_LPARAM(pos); + mp.y = GET_Y_LPARAM(pos); + } return ::WindowFromPoint(mp); } bool nsWindow::IsTopLevelMouseExit(HWND aWnd) { - HWND mouseWnd = WindowAtMouse(); + HWND mouseWnd = WindowAtMouse(aWnd); // WinUtils::GetTopLevelHWND() will return a HWND for the window frame // (which includes the non-client area). If the mouse has moved into @@ -4756,6 +4784,12 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam, return true; } + // Set current window for InputFilter so KeyboardLayout can query button state + InputFilter::SetCurrentWindow(mWnd); + auto clearCurrentWindow = mozilla::MakeScopeExit([]() { + InputFilter::ClearCurrentWindow(); + }); + // The preference whether to use a different keyboard layout for each // window is cached, and updating it will not take effect until the // next restart. We read the preference here and not upon WM_ACTIVATE to make @@ -4766,6 +4800,107 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam, Preferences::GetBool("intl.keyboard.per_window_layout", false); AppShutdownReason shutdownReason = AppShutdownReason::Unknown; + // MouseMux: Skip native input when blocking is enabled + // Allow MouseMux injected messages (marked with MOUSEMUX_MARKER in wParam) + if (InputFilter::IsEnabledForWindow(mWnd)) { + switch (msg) { + // Mouse events with CLIENT coordinates + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MBUTTONDBLCLK: + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: + case WM_XBUTTONDBLCLK: { + if (!(wParam & MOUSEMUX_MARKER)) { + return true; // Block native mouse + } + wParam &= ~MOUSEMUX_MARKER; // Strip marker + // Store cursor position (convert client -> screen) + { + POINT pt; + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + ::ClientToScreen(mWnd, &pt); + InputFilter::SetCursorPosForWindow(mWnd, pt.x, pt.y); + } + break; + } + // Mouse events with SCREEN coordinates + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + case WM_NCMOUSEMOVE: { + if (!(wParam & MOUSEMUX_MARKER)) { + return true; // Block native mouse + } + wParam &= ~MOUSEMUX_MARKER; // Strip marker + // Store cursor position (already screen coords) + { + POINT pt; + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + InputFilter::SetCursorPosForWindow(mWnd, pt.x, pt.y); + } + break; + } + // Leave events have no meaningful coordinates + case WM_MOUSELEAVE: + case WM_NCMOUSELEAVE: { + if (!(wParam & MOUSEMUX_MARKER)) { + return true; // Block native mouse + } + wParam &= ~MOUSEMUX_MARKER; // Strip marker + // Don't update cursor pos - leave events have no coords + break; + } + // Keyboard events + case WM_KEYDOWN: + case WM_KEYUP: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_CHAR: + case WM_SYSCHAR: { + // Allow F12 through for emergency exit + if ((wParam & 0xFF) == VK_F12) { + break; + } + if (!(wParam & MOUSEMUX_MARKER)) { + // Log blocked native keyboard + static int blockCount = 0; + if (++blockCount % 10 == 1) { + FILE* f = fopen("D:/scratch/firefox/mousemux_client.log", "a"); + if (f) { + fprintf(f, "[nsWindow] BLOCKED native keyboard: msg=0x%X wParam=0x%llX HWND=%p (count=%d)\n", + msg, (unsigned long long)wParam, mWnd, blockCount); + fclose(f); + } + } + return true; // Block native keyboard + } + // Log accepted MouseMux keyboard + { + static int acceptCount = 0; + if (++acceptCount % 10 == 1) { + FILE* f = fopen("D:/scratch/firefox/mousemux_client.log", "a"); + if (f) { + fprintf(f, "[nsWindow] ACCEPTED MouseMux keyboard: msg=0x%X wParam=0x%llX HWND=%p (count=%d)\n", + msg, (unsigned long long)wParam, mWnd, acceptCount); + fclose(f); + } + } + } + wParam &= ~MOUSEMUX_MARKER; // Strip marker + break; + } + } + } + // (Large blocks of code should be broken out into OnEvent handlers.) switch (msg) { // WM_QUERYENDSESSION must be handled by all windows. @@ -5087,6 +5222,15 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam, case WM_SYSKEYUP: case WM_KEYUP: { + // MouseMux: Forward keyboard to focused window if this is top-level + if (mMouseMuxClient && mMouseMuxClient->IsConnected()) { + nsWindow* focusedWnd = IMEHandler::GetFocusedWindow(); + if (focusedWnd && focusedWnd != this && focusedWnd->mWnd) { + ::PostMessage(focusedWnd->mWnd, msg, wParam | MOUSEMUX_MARKER, lParam); // Re-add marker + result = true; + break; + } + } MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd); nativeMsg.time = ::GetMessageTime(); result = ProcessKeyUpMessage(nativeMsg, nullptr); @@ -5095,6 +5239,36 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam, case WM_SYSKEYDOWN: case WM_KEYDOWN: { + // MouseMux: Forward keyboard to focused window if this is top-level + if (mMouseMuxClient && mMouseMuxClient->IsConnected()) { + nsWindow* focusedWnd = IMEHandler::GetFocusedWindow(); + if (focusedWnd && focusedWnd != this && focusedWnd->mWnd) { + mMouseMuxClient->Log("Forwarding WM_KEYDOWN vk=%u to focused HWND %p", + (unsigned)wParam, focusedWnd->mWnd); + ::PostMessage(focusedWnd->mWnd, msg, wParam | MOUSEMUX_MARKER, lParam); // Re-add marker + result = true; + break; + } + } + // MouseMux: F11 = toggle debug dialog for this window + if (wParam == VK_F11 && mMouseMuxClient) { + if (mMouseMuxClient->IsDebugDialogVisible()) { + mMouseMuxClient->HideDebugDialog(); + } else { + mMouseMuxClient->ShowDebugDialog(); + } + result = true; + break; + } + // MouseMux: F12 = emergency exit (disable blocking, disconnect) + if (wParam == VK_F12 && InputFilter::IsEnabledForWindow(mWnd)) { + InputFilter::DisableForWindow(mWnd); + if (mMouseMuxClient) { + mMouseMuxClient->Disconnect(); + } + result = true; + break; + } MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd); result = ProcessKeyDownMessage(nativeMsg, nullptr); DispatchPendingEvents(); @@ -5217,7 +5391,7 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam, // clear LastMouseMoveData. This way the WM_MOUSEMOVE we get after the // transition window disappears will not be ignored, even if the mouse // hasn't moved. - if (mTransitionWnd && WindowAtMouse() == mTransitionWnd) { + if (mTransitionWnd && WindowAtMouse(mWnd) == mTransitionWnd) { LastMouseMoveData::Clear(); } @@ -5228,7 +5402,16 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam, (GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0); // Synthesize an event position because we don't get one from // WM_MOUSELEAVE. - LPARAM pos = lParamToClient(::GetMessagePos()); + LPARAM pos; + POINT cursorPos; + // MouseMux: Use stored cursor position if available + if (InputFilter::IsEnabledForWindow(mWnd) && + InputFilter::GetCursorPosForWindow(mWnd, &cursorPos)) { + ::ScreenToClient(mWnd, &cursorPos); + pos = MAKELPARAM(cursorPos.x, cursorPos.y); + } else { + pos = lParamToClient(::GetMessagePos()); + } DispatchMouseEvent(eMouseExitFromWidget, mouseState, pos, false, MouseButton::ePrimary, MOUSE_INPUT_SOURCE()); } break; @@ -7473,9 +7656,15 @@ bool nsWindow::EventIsInsideWindow(nsWindow* aWindow, if (aEventPoint) { mp = *aEventPoint; } else { - DWORD pos = ::GetMessagePos(); - mp.x = GET_X_LPARAM(pos); - mp.y = GET_Y_LPARAM(pos); + // MouseMux: Use stored cursor position if available + if (aWindow && InputFilter::IsEnabledForWindow(aWindow->mWnd) && + InputFilter::GetCursorPosForWindow(aWindow->mWnd, &mp)) { + // mp now has screen coords from MouseMux + } else { + DWORD pos = ::GetMessagePos(); + mp.x = GET_X_LPARAM(pos); + mp.y = GET_Y_LPARAM(pos); + } } auto margin = aWindow->mInputRegion.mMargin; @@ -8855,3 +9044,35 @@ void nsWindow::ContextMenuPreventer::Update( aEvent.mInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE && aEventStatus.mApzStatus == nsEventStatus_eConsumeNoDefault; } + +/************************************************************** + * MouseMux - Per-window multi-mouse/keyboard support + **************************************************************/ + +void nsWindow::InitMouseMux() { + if (!mWnd) return; + + // Only create for top-level windows + if (mWindowType != WindowType::TopLevel) return; + + mMouseMuxClient = mozilla::MakeUnique(mWnd); + + // Auto-open debug dialog on window creation + mMouseMuxClient->ShowDebugDialog(); +} + +void nsWindow::ShutdownMouseMux() { + mMouseMuxClient.reset(); +} + +void nsWindow::ShowMouseMuxDebugDialog() { + if (mMouseMuxClient) { + mMouseMuxClient->ShowDebugDialog(); + } +} + +void nsWindow::HideMouseMuxDebugDialog() { + if (mMouseMuxClient) { + mMouseMuxClient->HideDebugDialog(); + } +} diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h index 52d577a5f132c..2d0e191320c97 100644 --- a/widget/windows/nsWindow.h +++ b/widget/windows/nsWindow.h @@ -1,920 +1,933 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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/. */ - -#ifndef WIDGET_WINDOWS_NSWINDOW_H_ -#define WIDGET_WINDOWS_NSWINDOW_H_ - -/* - * nsWindow - Native window management and event handling. - */ - -#include "mozilla/RefPtr.h" -#include "nsIWidget.h" -#include "CompositorWidget.h" -#include "mozilla/EventForwards.h" -#include "nsClassHashtable.h" -#include -#include "touchinjection_sdk80.h" -#include "nsdefs.h" -#include "nsUserIdleService.h" -#include "nsToolkit.h" -#include "nsString.h" -#include "nsTArray.h" -#include "gfxWindowsPlatform.h" -#include "gfxWindowsSurface.h" -#include "nsWindowDbg.h" -#include "cairo.h" -#include "nsRegion.h" -#include "mozilla/EnumeratedArray.h" -#include "mozilla/Maybe.h" -#include "mozilla/MouseEvents.h" -#include "mozilla/TimeStamp.h" -#include "mozilla/webrender/WebRenderTypes.h" -#include "mozilla/dom/MouseEventBinding.h" -#include "mozilla/DataMutex.h" -#include "mozilla/UniquePtr.h" -#include "nsMargin.h" -#include "nsRegionFwd.h" - -#include "nsWinGesture.h" -#include "WinPointerEvents.h" -#include "WinUtils.h" -#include "WindowHook.h" -#include "TaskbarWindowPreview.h" - -#ifdef ACCESSIBILITY -# include "oleacc.h" -# include "mozilla/a11y/LocalAccessible.h" -#endif - -#include "nsIUserIdleServiceInternal.h" - -#include "IMMHandler.h" -#include "CheckInvariantWrapper.h" - -/** - * Forward class definitions - */ - -class nsNativeDragTarget; -class nsIRollupListener; -class imgIContainer; - -namespace mozilla { -class WidgetMouseEvent; -class InputData; -namespace widget { -class NativeKey; -class InProcessWinCompositorWidget; -struct MSGResult; -class DirectManipulationOwner; -} // namespace widget -} // namespace mozilla - -/** - * Forward Windows-internal definitions of otherwise incomplete ones provided by - * the SDK. - */ -const CLSID CLSID_ImmersiveShell = { - 0xC2F03A33, - 0x21F5, - 0x47FA, - {0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39}}; - -/** - * Native WIN32 window wrapper. - */ - -namespace mozilla::widget { - -// Data manipulation: styles + ex-styles, and bitmasking operations thereupon. -struct WindowStyles { - LONG_PTR style = 0; - LONG_PTR ex = 0; - - static WindowStyles FromHWND(HWND); - - constexpr bool operator==(WindowStyles const& that) const { - return style == that.style && ex == that.ex; - } - constexpr bool operator!=(WindowStyles const& that) const { - return !(*this == that); - } - constexpr WindowStyles operator|(WindowStyles const& that) const { - return WindowStyles{.style = style | that.style, .ex = ex | that.ex}; - } - constexpr WindowStyles operator&(WindowStyles const& that) const { - return WindowStyles{.style = style & that.style, .ex = ex & that.ex}; - } - constexpr WindowStyles operator~() const { - return WindowStyles{.style = ~style, .ex = ~ex}; - } - - explicit constexpr operator bool() const { return style || ex; } - - // Compute a style-set which matches `zero` where the bits of `this` are 0 - // and `one` where the bits of `this` are 1. - constexpr WindowStyles merge(WindowStyles zero, WindowStyles one) const { - WindowStyles const& mask = *this; - return (~mask & zero) | (mask & one); - } - - // The dual of `merge`, above: returns a pair [zero, one] satisfying - // `a.merge(a.split(b)...) == b`. (Or its equivalent in valid C++.) - constexpr std::tuple split( - WindowStyles data) const { - WindowStyles const& mask = *this; - return {~mask & data, mask & data}; - } -}; - -void SetWindowStyles(HWND, const WindowStyles&); - -} // namespace mozilla::widget - -class nsWindow final : public nsIWidget { - public: - using Styles = mozilla::widget::WindowStyles; - using WindowHook = mozilla::widget::WindowHook; - using IMEContext = mozilla::widget::IMEContext; - using WidgetEventTime = mozilla::WidgetEventTime; - - NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsIWidget) - - nsWindow(); - - void SendAnAPZEvent(mozilla::InputData& aEvent); - - /* - * Init a standard gecko event for this widget. - * @param aEvent the event to initialize. - * @param aPoint message position in physical coordinates. - */ - void InitEvent(mozilla::WidgetGUIEvent& aEvent, - LayoutDeviceIntPoint* aPoint = nullptr); - - /* - * Returns WidgetEventTime instance which is initialized with current message - * time. - */ - WidgetEventTime CurrentMessageWidgetEventTime() const; - - /* - * Dispatch a gecko keyboard event for this widget. This - * is called by KeyboardLayout to dispatch gecko events. - * Returns true if it's consumed. Otherwise, false. - */ - bool DispatchKeyboardEvent(mozilla::WidgetKeyboardEvent* aEvent); - - /* - * Dispatch a gecko wheel event for this widget. This - * is called by ScrollHandler to dispatch gecko events. - * Returns true if it's consumed. Otherwise, false. - */ - bool DispatchWheelEvent(mozilla::WidgetWheelEvent* aEvent); - - /* - * Dispatch a gecko content command event for this widget. This - * is called by ScrollHandler to dispatch gecko events. - * Returns true if it's consumed. Otherwise, false. - */ - bool DispatchContentCommandEvent(mozilla::WidgetContentCommandEvent* aEvent); - - /* - * Return the parent window, if it exists. - */ - nsWindow* GetParentWindowBase(bool aIncludeOwner); - - // nsIWidget interface - using nsIWidget::Create; // for Create signature not overridden here - [[nodiscard]] nsresult Create(nsIWidget* aParent, const LayoutDeviceIntRect&, - const InitData&) override; - void Destroy() override; - float GetDPI() override; - double GetDefaultScaleInternal() override; - void DidClearParent(nsIWidget* aOldParent) override; - int32_t LogToPhys(double aValue); - - void Show(bool aState) override; - bool IsVisible() const override; - void ConstrainPosition(DesktopIntPoint&) override; - void SetSizeConstraints(const SizeConstraints& aConstraints) override; - void LockAspectRatio(bool aShouldLock) override; - const SizeConstraints GetSizeConstraints() override; - void SetInputRegion(const InputRegion&) override; - void Move(const DesktopPoint&) override; - void Resize(const DesktopSize&, bool aRepaint) override; - void Resize(const DesktopRect&, bool aRepaint) override; - void SetSizeMode(nsSizeMode aMode) override; - nsSizeMode SizeMode() override; - void GetWorkspaceID(nsAString& workspaceID) override; - void MoveToWorkspace(const nsAString& workspaceID) override; - void SuppressAnimation(bool aSuppress) override; - void Enable(bool aState) override; - bool IsEnabled() const override; - void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override; - LayoutDeviceIntRect GetBounds() override; - LayoutDeviceIntRect GetScreenBounds() override; - [[nodiscard]] nsresult GetRestoredBounds(LayoutDeviceIntRect& aRect) override; - LayoutDeviceIntRect GetClientBounds() override; - LayoutDeviceIntPoint GetClientOffset() override; - LayoutDeviceIntSize GetSize() const; - void SetCursor(const Cursor&) override; - bool PrepareForFullscreenTransition(nsISupports** aData) override; - void PerformFullscreenTransition(FullscreenTransitionStage aStage, - uint16_t aDuration, nsISupports* aData, - nsIRunnable* aCallback) override; - void CleanupFullscreenTransition() override; - nsresult MakeFullScreen(bool aFullScreen) override; - void HideWindowChrome(bool aShouldHide) override; - void Invalidate(bool aEraseBackground = false, bool aUpdateNCArea = false, - bool aIncludeChildren = false); - void Invalidate(const LayoutDeviceIntRect& aRect) override; - void* GetNativeData(uint32_t aDataType) override; - nsresult SetTitle(const nsAString& aTitle) override; - void SetIcon(const nsAString& aIconSpec) override; - LayoutDeviceIntPoint WidgetToScreenOffset() override; - LayoutDeviceIntMargin NormalSizeModeClientToWindowMargin() override; - void EnableDragDrop(bool aEnable) override; - void CaptureMouse(bool aCapture); - void CaptureRollupEvents(bool aDoCapture) override; - [[nodiscard]] nsresult GetAttention(int32_t aCycleCount) override; - bool HasPendingInputEvent() override; - WindowRenderer* GetWindowRenderer() override; - void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override; - [[nodiscard]] nsresult OnDefaultButtonLoaded( - const LayoutDeviceIntRect& aButtonRect) override; - nsresult SynthesizeNativeKeyEvent( - int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode, - uint32_t aModifierFlags, const nsAString& aCharacters, - const nsAString& aUnmodifiedCharacters, - nsISynthesizedEventCallback* aCallback) override; - nsresult SynthesizeNativeMouseEvent( - LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage, - mozilla::MouseButton aButton, nsIWidget::Modifiers aModifierFlags, - nsISynthesizedEventCallback* aCallback) override; - - nsresult SynthesizeNativeMouseMove( - LayoutDeviceIntPoint aPoint, - nsISynthesizedEventCallback* aCallback) override { - return SynthesizeNativeMouseEvent( - aPoint, NativeMouseMessage::Move, mozilla::MouseButton::eNotPressed, - nsIWidget::Modifiers::NO_MODIFIERS, aCallback); - } - - nsresult SynthesizeNativeMouseScrollEvent( - LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX, - double aDeltaY, double aDeltaZ, uint32_t aModifierFlags, - uint32_t aAdditionalFlags, - nsISynthesizedEventCallback* aCallback) override; - - nsresult SynthesizeNativeTouchpadPan( - TouchpadGesturePhase aEventPhase, LayoutDeviceIntPoint aPoint, - double aDeltaX, double aDeltaY, int32_t aModifierFlagsn, - nsISynthesizedEventCallback* aCallback) override; - - void SetInputContext(const InputContext& aContext, - const InputContextAction& aAction) override; - InputContext GetInputContext() override; - TextEventDispatcherListener* GetNativeTextEventDispatcherListener() override; - void SetTransparencyMode(TransparencyMode aMode) override; - TransparencyMode GetTransparencyMode() override { return mTransparencyMode; } - void SetCustomTitlebar(bool) override; - void SetResizeMargin(mozilla::LayoutDeviceIntCoord aResizeMargin) override; - void UpdateWindowDraggingRegion( - const LayoutDeviceIntRegion& aRegion) override; - - uint32_t GetMaxTouchPoints() const override; - void SetIsEarlyBlankWindow(bool) override; - - /** - * Event helpers - */ - enum class IsNonclient : bool { No = false, Yes = true }; - bool DispatchMouseEvent(mozilla::EventMessage aEventMessage, WPARAM wParam, - LPARAM lParam, bool aIsContextMenuKey, - int16_t aButton, uint16_t aInputSource, - WinPointerInfo* aPointerInfo = nullptr, - IsNonclient aIgnoreAPZ = IsNonclient::No); - void DispatchPendingEvents(); - void DispatchCustomEvent(const nsString& eventName); - -#ifdef ACCESSIBILITY - /** - * Return an accessible associated with the window. - */ - mozilla::a11y::LocalAccessible* GetAccessible(); -#endif // ACCESSIBILITY - - /** - * Window utilities - */ - nsWindow* GetTopLevelWindow(bool aStopOnDialogOrPopup); - WNDPROC GetPrevWindowProc() { return mPrevWndProc.valueOr(nullptr); } - WindowHook& GetWindowHook() { return mWindowHook; } - nsWindow* GetParentWindow(bool aIncludeOwner); - - bool WidgetTypeSupportsAcceleration() override; - - void ForcePresent(); - bool TouchEventShouldStartDrag(mozilla::EventMessage aEventMessage, - LayoutDeviceIntPoint aEventPoint); - - void SetSmallIcon(HICON aIcon); - void SetBigIcon(HICON aIcon); - void SetSmallIconNoData(); - void SetBigIconNoData(); - - void UpdateMicaBackdrop(bool aForce = false); - - static void SetIsRestoringSession(const bool aIsRestoringSession) { - sIsRestoringSession = aIsRestoringSession; - } - - bool IsRTL() const { return mIsRTL; } - - bool ShouldAssociateWithWinAppSDK() const; - - /** - * AssociateDefaultIMC() associates or disassociates the default IMC for - * the window. - * - * @param aAssociate TRUE, associates the default IMC with the window. - * Otherwise, disassociates the default IMC from the - * window. - * @return TRUE if this method associated the default IMC with - * disassociated window or disassociated the default IMC - * from associated window. - * Otherwise, i.e., if this method did nothing actually, - * FALSE. - */ - bool AssociateDefaultIMC(bool aAssociate); - - bool HasTaskbarIconBeenCreated() { return mHasTaskbarIconBeenCreated; } - // Called when either the nsWindow or an nsITaskbarTabPreview receives the - // notification that this window has its icon placed on the taskbar. - void SetHasTaskbarIconBeenCreated(bool created = true) { - mHasTaskbarIconBeenCreated = created; - } - - // Getter/setter for the nsITaskbarWindowPreview for this nsWindow - already_AddRefed GetTaskbarPreview() { - nsCOMPtr preview( - do_QueryReferent(mTaskbarPreview)); - return preview.forget(); - } - void SetTaskbarPreview(nsITaskbarWindowPreview* preview) { - mTaskbarPreview = do_GetWeakReference(preview); - } - - // Open file picker tracking - void PickerOpen(); - void PickerClosed(); - - bool DestroyCalled() { return mDestroyCalled; } - - bool IsPopup(); - bool ShouldUseOffMainThreadCompositing() override; - - const IMEContext& DefaultIMC() const { return mDefaultIMC; } - - void GetCompositorWidgetInitData( - mozilla::widget::CompositorWidgetInitData* aInitData) override; - bool IsTouchWindow() const { return mTouchWindow; } - bool SynchronouslyRepaintOnResize() override; - void MaybeDispatchInitialFocusEvent() override; - - void LocalesChanged() override; - - void NotifyOcclusionState(mozilla::widget::OcclusionState aState) override; - void MaybeEnableWindowOcclusion(bool aEnable); - - /* - * Return the HWND or null for this widget. - */ - HWND GetWindowHandle() { - return static_cast(GetNativeData(NS_NATIVE_WINDOW)); - } - - /* - * Touch input injection apis - */ - nsresult SynthesizeNativeTouchPoint( - uint32_t aPointerId, TouchPointerState aPointerState, - LayoutDeviceIntPoint aPoint, double aPointerPressure, - uint32_t aPointerOrientation, - nsISynthesizedEventCallback* aCallback) override; - - nsresult SynthesizeNativePenInput( - uint32_t aPointerId, TouchPointerState aPointerState, - LayoutDeviceIntPoint aPoint, double aPressure, uint32_t aRotation, - int32_t aTiltX, int32_t aTiltY, int32_t aButton, - nsISynthesizedEventCallback* aCallback) override; - - /* - * WM_APPCOMMAND common handler. - * Sends events via NativeKey::HandleAppCommandMessage(). - */ - bool HandleAppCommandMsg(const MSG& aAppCommandMsg, LRESULT* aRetValue); - - const InputContext& InputContextRef() const { return mInputContext; } - - private: - using TimeStamp = mozilla::TimeStamp; - using TimeDuration = mozilla::TimeDuration; - using TaskbarWindowPreview = mozilla::widget::TaskbarWindowPreview; - using NativeKey = mozilla::widget::NativeKey; - using MSGResult = mozilla::widget::MSGResult; - using PlatformCompositorWidgetDelegate = - mozilla::widget::PlatformCompositorWidgetDelegate; - - class PointerInfo { - public: - enum class PointerType : uint8_t { - TOUCH, - PEN, - }; - - PointerInfo(int32_t aPointerId, LayoutDeviceIntPoint& aPoint, - PointerType aType) - : mPointerId(aPointerId), mPosition(aPoint), mType(aType) {} - - int32_t mPointerId; - LayoutDeviceIntPoint mPosition; - PointerType mType; - }; - - class FrameState { - public: - explicit FrameState(nsWindow* aWindow); - - void ConsumePreXULSkeletonState(bool aWasMaximized); - - // Whether we should call ShowWindow with the relevant size mode if needed. - // We want to avoid that when Windows is already performing the change for - // us (via the SWP_FRAMECHANGED messages). - enum class DoShowWindow : bool { No, Yes }; - - void EnsureSizeMode(nsSizeMode, DoShowWindow = DoShowWindow::Yes); - void EnsureFullscreenMode(bool, DoShowWindow = DoShowWindow::Yes); - void OnFrameChanging(); - void OnFrameChanged(); - - nsSizeMode GetSizeMode() const; - - void CheckInvariant() const; - - private: - void SetSizeModeInternal(nsSizeMode, DoShowWindow); - - nsSizeMode mSizeMode = nsSizeMode_Normal; - // The old size mode before going into fullscreen mode. This should never - // be nsSizeMode_Fullscreen. - nsSizeMode mPreFullscreenSizeMode = nsSizeMode_Normal; - // Whether we're in fullscreen. We need to keep this state out of band, - // rather than just using mSizeMode, because a window can be minimized - // while fullscreen, and we don't store the fullscreen state anywhere else. - bool mFullscreenMode = false; - nsWindow* mWindow; - }; - - // Manager for taskbar-hiding. No persistent state. - class TaskbarConcealer; - - // A magic number to identify the FAKETRACKPOINTSCROLLABLE window created - // when the trackpoint hack is enabled. - enum { eFakeTrackPointScrollableID = 0x46545053 }; - - // Used for displayport suppression during window resize - enum ResizeState { NOT_RESIZING, IN_SIZEMOVE, RESIZING, MOVING }; - - ~nsWindow() override; - - void WindowUsesOMTC() override; - void RegisterTouchWindow() override; - - /** - * Callbacks - */ - static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, - LPARAM lParam); - static LRESULT CALLBACK WindowProcInternal(HWND hWnd, UINT msg, WPARAM wParam, - LPARAM lParam); - - static BOOL CALLBACK DispatchStarvedPaints(HWND aTopWindow, LPARAM aMsg); - static BOOL CALLBACK RegisterTouchForDescendants(HWND aTopWindow, - LPARAM aMsg); - static BOOL CALLBACK UnregisterTouchForDescendants(HWND aTopWindow, - LPARAM aMsg); - static LRESULT CALLBACK MozSpecialMsgFilter(int code, WPARAM wParam, - LPARAM lParam); - static LRESULT CALLBACK MozSpecialWndProc(int code, WPARAM wParam, - LPARAM lParam); - static LRESULT CALLBACK MozSpecialMouseProc(int code, WPARAM wParam, - LPARAM lParam); - static VOID CALLBACK HookTimerForPopups(HWND hwnd, UINT uMsg, UINT idEvent, - DWORD dwTime); - - /** - * Window utilities - */ - LPARAM lParamToScreen(LPARAM lParam); - LPARAM lParamToClient(LPARAM lParam); - - WPARAM wParamFromGlobalMouseState(); - - bool AssociateWithNativeWindow(); - void DissociateFromNativeWindow(); - bool CanTakeFocus(); - bool UpdateNonClientMargins(bool aReflowWindow = true); - void UpdateDarkModeToolbar(); - void ResetLayout(); - HWND GetOwnerWnd() const { return ::GetWindow(mWnd, GW_OWNER); } - bool IsOwnerForegroundWindow() const { - HWND owner = GetOwnerWnd(); - return owner && owner == ::GetForegroundWindow(); - } - bool IsForegroundWindow() const { return mWnd == ::GetForegroundWindow(); } - bool IsPopup() const { return mWindowType == WindowType::Popup; } - bool IsCloaked() const override { return mIsCloaked; } - - /** - * Event processing helpers - */ - HWND GetTopLevelForFocus(HWND aCurWnd); - void DispatchFocusToTopLevelWindow(bool aIsActivate); - bool DispatchStandardEvent(mozilla::EventMessage aMsg); - void RelayMouseEvent(UINT aMsg, WPARAM wParam, LPARAM lParam); - bool ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam, - LRESULT* aRetValue); - // We wrap this in ProcessMessage so we can log the return value - bool ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam, - LRESULT* aRetValue); - bool ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam, - LPARAM& aLParam, MSGResult& aResult); - LRESULT ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched); - LRESULT ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched); - LRESULT ProcessKeyDownMessage(const MSG& aMsg, bool* aEventDispatched); - static bool EventIsInsideWindow( - nsWindow* aWindow, - mozilla::Maybe aEventPoint = mozilla::Nothing()); - static void PostSleepWakeNotification(const bool aIsSleepMode); - int32_t ClientMarginHitTestPoint(int32_t aX, int32_t aY); - void SetWindowButtonRect(WindowButtonType aButtonType, - const LayoutDeviceIntRect& aClientRect) override { - mWindowBtnRect[aButtonType] = aClientRect; - } - TimeStamp GetMessageTimeStamp(LONG aEventTime) const; - static void UpdateFirstEventTime(DWORD aEventTime); - void FinishLiveResizing(ResizeState aNewState); - mozilla::Maybe ConvertTouchToPanGesture( - const mozilla::MultiTouchInput& aTouchInput, PTOUCHINPUT aOriginalEvent); - void DispatchTouchOrPanGestureInput(mozilla::MultiTouchInput& aTouchInput, - PTOUCHINPUT aOSEvent); - - /** - * Event handlers - */ - void OnDestroy() override; - void OnResize(const LayoutDeviceIntSize& aSize); - void OnSizeModeChange(); - bool OnGesture(WPARAM wParam, LPARAM lParam); - bool OnTouch(WPARAM wParam, LPARAM lParam); - bool OnHotKey(WPARAM wParam, LPARAM lParam); - bool OnPaint(); - void OnWindowPosChanging(WINDOWPOS* info); - void OnWindowPosChanged(WINDOWPOS* wp); - void OnSysColorChanged(); - void OnDPIChanged(int32_t x, int32_t y, int32_t width, int32_t height); - bool OnPointerEvents(UINT msg, WPARAM wParam, LPARAM lParam); - bool OnPenPointerEvents(uint32_t aPointerId, UINT aMsg, WPARAM aWParam, - LPARAM aLParam); - bool OnTouchPointerEvents(uint32_t aPointerId, UINT aMsg, WPARAM aWParam, - LPARAM aLParam); - - /** - * Function that registers when the user has been active (used for detecting - * when the user is idle). - */ - void UserActivity(); - - DWORD WindowStyle(); - DWORD WindowExStyle(); - - /** - * Popup hooks - */ - static void ScheduleHookTimer(HWND aWnd, UINT aMsgId); - static void RegisterSpecialDropdownHooks(); - static void UnregisterSpecialDropdownHooks(); - static bool GetPopupsToRollup( - nsIRollupListener* aRollupListener, uint32_t* aPopupsToRollup, - mozilla::Maybe aEventPoint = mozilla::Nothing()); - static bool DealWithPopups(HWND inWnd, UINT inMsg, WPARAM inWParam, - LPARAM inLParam, LRESULT* outResult); - bool IsSimulatedClientArea(int32_t clientX, int32_t clientY); - bool IsWindowButton(int32_t hitTestResult); - - void UpdateOpaqueRegion(const LayoutDeviceIntRegion&) override; - void UpdateOpaqueRegionInternal(); - LayoutDeviceIntRegion GetOpaqueRegionForTesting() const override { - return mOpaqueRegion; - } - // Gets the translucent region, in client coordinates. - LayoutDeviceIntRegion GetTranslucentRegion(); - void MaybeInvalidateTranslucentRegion(); - - void SetColorScheme(const mozilla::Maybe&) override; - void SetMicaBackdrop(bool) override; - - bool DispatchTouchEventFromWMPointer(UINT msg, LPARAM aLParam, - const WinPointerInfo& aPointerInfo, - mozilla::MouseButton aButton); - - static bool IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult); - void IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam); - - /** - * Misc. - */ - void StopFlashing(); - static HWND WindowAtMouse(); - static bool IsTopLevelMouseExit(HWND aWnd); - LayoutDeviceIntRegion GetRegionToPaint(const PAINTSTRUCT& ps, HDC aDC) const; - - void CreateCompositor() override; - void DestroyCompositor() override; - void RequestFxrOutput() override; - - void RecreateDirectManipulationIfNeeded(); - void ResizeDirectManipulationViewport(); - void DestroyDirectManipulation(); - - bool NeedsToTrackWindowOcclusionState(); - - void AsyncUpdateWorkspaceID(); - - // See bug 603793 - static bool HasBogusPopupsDropShadowOnMultiMonitor(); - - static void InitMouseWheelScrollData(); - - void ChangedDPI(); - - static bool InitTouchInjection(); - - bool InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint, - POINTER_FLAGS aFlags, uint32_t aPressure = 1024, - uint32_t aOrientation = 90); - - void OnFullscreenChanged(nsSizeMode aOldSizeMode, bool aFullScreen); - void TryDwmResizeHack(); - - static void OnCloakEvent(HWND aWnd, bool aCloaked); - void OnCloakChanged(bool aCloaked); - -#ifdef DEBUG - nsresult SetHiDPIMode(bool aHiDPI) override; - nsresult RestoreHiDPIMode() override; -#endif - - // Get the orientation of the hidden taskbar, on the screen that this window - // is on, or Nothing if taskbar isn't hidden. - mozilla::Maybe GetHiddenTaskbarEdge(); - - static bool sTouchInjectInitialized; - static InjectTouchInputPtr sInjectTouchFuncPtr; - static uint32_t sInstanceCount; - static nsWindow* sCurrentWindow; - static bool sIsOleInitialized; - static Cursor sCurrentCursor; - static bool sJustGotDeactivate; - static bool sJustGotActivate; - static bool sIsInMouseCapture; - static bool sIsRestoringSession; - - // Message postponement hack. See the definition-site of - // WndProcUrgentInvocation::sDepth for details. - struct MOZ_STACK_CLASS WndProcUrgentInvocation { - struct Marker { - Marker() { ++sDepth; } - ~Marker() { --sDepth; } - }; - inline static bool IsActive() { return sDepth > 0; } - static size_t sDepth; - }; - - // Hook Data Members for Dropdowns. sProcessHook Tells the - // hook methods whether they should be processing the hook - // messages. - static HHOOK sMsgFilterHook; - static HHOOK sCallProcHook; - static HHOOK sCallMouseHook; - static bool sProcessHook; - static UINT sRollupMsgId; - static HWND sRollupMsgWnd; - static UINT sHookTimerId; - - // Handle the last mouse point and the last non-mouse pointer point to stop - // dispatching redundant eMouseMove events. - class LastMouseMoveData; - - nsClassHashtable mActivePointers; - - // This is used by SynthesizeNativeTouchPoint to maintain state between - // multiple synthesized points, in the case where we can't call InjectTouch - // directly. - mozilla::UniquePtr mSynthesizedTouchInput; - - InputContext mInputContext; - - nsIntSize mLastSize = nsIntSize(0, 0); - nsIntPoint mLastPoint; - HWND mWnd = nullptr; - HWND mTransitionWnd = nullptr; - mozilla::Maybe mPrevWndProc; - IMEContext mDefaultIMC; - HDEVNOTIFY mDeviceNotifyHandle = nullptr; - bool mInDtor = false; - bool mIsVisible = false; - bool mIsCloaked = false; - bool mTouchWindow = false; - bool mDisplayPanFeedback = false; - bool mHideChrome = false; - bool mIsRTL = false; - bool mMousePresent = false; - bool mSimulatedClientArea = false; - bool mDestroyCalled = false; - bool mOpeningAnimationSuppressed = false; - bool mAlwaysOnTop = false; - bool mIsEarlyBlankWindow = false; - bool mIsShowingPreXULSkeletonUI = false; - bool mResizable = false; - bool mHasBeenShown = false; - // Whether we're an alert window. Alert windows don't have taskbar icons and - // don't steal focus from other windows when opened. They're also expected to - // be of type WindowType::Dialog. - bool mIsAlert = false; - bool mIsPerformingDwmFlushHack = false; - bool mDraggingWindowWithMouse = false; - mozilla::Maybe mOldStyles; - nsNativeDragTarget* mNativeDragTarget = nullptr; - HKL mLastKeyboardLayout = 0; - mozilla::CheckInvariantWrapper mFrameState; - WindowHook mWindowHook; - uint32_t mPickerDisplayCount = 0; - HICON mIconSmall = nullptr; - HICON mIconBig = nullptr; - HWND mLastKillFocusWindow = nullptr; - PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate = nullptr; - - LayoutDeviceIntMargin NonClientSizeMargin() const { - return NonClientSizeMargin(mCustomNonClientMetrics.mOffset); - } - LayoutDeviceIntMargin NonClientSizeMargin( - const LayoutDeviceIntMargin& aNonClientOffset) const; - LayoutDeviceIntMargin NormalWindowNonClientOffset() const; - - struct CustomNonClientMetrics { - // Width of the left and right portions of the resize region - mozilla::LayoutDeviceIntCoord mHorResizeMargin; - // Height of the top and bottom portions of the resize region - mozilla::LayoutDeviceIntCoord mVertResizeMargin; - // Height of the caption plus border - mozilla::LayoutDeviceIntCoord mCaptionHeight; - // Pre-calculated outward offset applied to the default frame - LayoutDeviceIntMargin mOffset; - - LayoutDeviceIntMargin ResizeMargins() const { - return {mVertResizeMargin, mHorResizeMargin, mVertResizeMargin, - mHorResizeMargin}; - } - - LayoutDeviceIntMargin DefaultMargins() const { - auto margins = ResizeMargins(); - margins.top += mCaptionHeight; - return margins; - } - } mCustomNonClientMetrics; - - // Indicates the custom titlebar is enabled. - bool mCustomNonClient = false; - // Whether we want to draw to the titlebar once the chrome shows. (Always - // Nothing if mHideChrome is false.) - mozilla::Maybe mCustomTitlebarOnceChromeShows; - // Custom extra resize margin width. - mozilla::LayoutDeviceIntCoord mCustomResizeMargin{0}; - - // not yet set, will be calculated on first use - double mDefaultScale = -1.0; - - // not yet set, will be calculated on first use - float mAspectRatio = 0.0; - - nsCOMPtr mIdleService; - - // Draggable titlebar region maintained by UpdateWindowDraggingRegion - LayoutDeviceIntRegion mDraggableRegion; - // Opaque region maintained by UpdateOpaqueRegion (relative to the client - // area). - LayoutDeviceIntRegion mOpaqueRegion; - - LayoutDeviceIntRect mBounds; - - // Graphics - LayoutDeviceIntRect mLastPaintBounds; - // The region of the window we know is cleared to transparent already, - // in client coords. - LayoutDeviceIntRegion mClearedRegion; - - ResizeState mResizeState = NOT_RESIZING; - - // Transparency - TransparencyMode mTransparencyMode = TransparencyMode::Opaque; - - // Win7 Gesture processing and management - nsWinGesture mGesture; - - // Weak ref to the nsITaskbarWindowPreview associated with this window - nsWeakPtr mTaskbarPreview = nullptr; - - // The input region that determines whether mouse events should be ignored - // and pass through to the window below. This is currently only used for - // popups. - InputRegion mInputRegion; - - // True if the taskbar (possibly through the tab preview) tells us that the - // icon has been created on the taskbar. - bool mHasTaskbarIconBeenCreated = false; - - // Whether we're in the process of sending a WM_SETTEXT ourselves - bool mSendingSetText = false; - - // Whether we are asked to render a mica backdrop. - bool mMicaBackdrop : 1; - - int32_t mCachedHitTestResult = 0; - - // The point in time at which the last paint completed. We use this to avoid - // painting too rapidly in response to frequent input events. - TimeStamp mLastPaintEndTime; - - // Caching for hit test results (in client coordinates) - LayoutDeviceIntPoint mCachedHitTestPoint; - TimeStamp mCachedHitTestTime; - - RefPtr mBasicLayersSurface; - - double mSizeConstraintsScale; // scale in effect when setting constraints - - // Will be calculated when layer manager is created. - int32_t mMaxTextureSize = -1; - - // Pointer events processing and management - WinPointerEvents mPointerEvents; - - ScreenPoint mLastPanGestureFocus; - - // When true, used to indicate an async call to RequestFxrOutput to the GPU - // process after the Compositor is created - bool mRequestFxrOutputPending = false; - - // A stack based class used in DispatchMouseEvent() to tell whether we should - // NOT open context menu when we receives WM_CONTEXTMENU after the - // DispatchMouseEvent calls. - // This class now works only in the case where a mouse up event happened in - // the overscroll gutter. - class MOZ_STACK_CLASS ContextMenuPreventer final { - public: - explicit ContextMenuPreventer(nsWindow* aWindow) - : mWindow(aWindow), mNeedsToPreventContextMenu(false) {}; - ~ContextMenuPreventer() { - mWindow->mNeedsToPreventContextMenu = mNeedsToPreventContextMenu; - } - void Update(const mozilla::WidgetMouseEvent& aEvent, - const nsIWidget::ContentAndAPZEventStatus& aEventStatus); - - private: - nsWindow* mWindow; - bool mNeedsToPreventContextMenu = false; - }; - friend class ContextMenuPreventer; - bool mNeedsToPreventContextMenu = false; - - mozilla::UniquePtr mDmOwner; - - // Client rect for minimize, maximize and close buttons. - mozilla::EnumeratedArray - mWindowBtnRect; - - nsString mDesktopId MOZ_GUARDED_BY(mozilla::sMainThreadCapability); - - friend class nsWindowGfx; - - static constexpr int kHiddenTaskbarSize = 2; -}; - -#endif // WIDGET_WINDOWS_NSWINDOW_H_ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef WIDGET_WINDOWS_NSWINDOW_H_ +#define WIDGET_WINDOWS_NSWINDOW_H_ + +/* + * nsWindow - Native window management and event handling. + */ + +#include "mozilla/RefPtr.h" +#include "nsIWidget.h" +#include "CompositorWidget.h" +#include "mozilla/EventForwards.h" +#include "nsClassHashtable.h" +#include +#include "touchinjection_sdk80.h" +#include "nsdefs.h" +#include "nsUserIdleService.h" +#include "nsToolkit.h" +#include "nsString.h" +#include "nsTArray.h" +#include "gfxWindowsPlatform.h" +#include "gfxWindowsSurface.h" +#include "nsWindowDbg.h" +#include "cairo.h" +#include "nsRegion.h" +#include "mozilla/EnumeratedArray.h" +#include "mozilla/Maybe.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/DataMutex.h" +#include "mozilla/UniquePtr.h" +#include "nsMargin.h" +#include "nsRegionFwd.h" + +#include "nsWinGesture.h" +#include "WinPointerEvents.h" +#include "WinUtils.h" +#include "WindowHook.h" +#include "TaskbarWindowPreview.h" + +#ifdef ACCESSIBILITY +# include "oleacc.h" +# include "mozilla/a11y/LocalAccessible.h" +#endif + +#include "nsIUserIdleServiceInternal.h" + +#include "IMMHandler.h" +#include "CheckInvariantWrapper.h" +#include "MouseMuxClient.h" + +/** + * Forward class definitions + */ + +class nsNativeDragTarget; +class nsIRollupListener; +class imgIContainer; + +namespace mozilla { +class WidgetMouseEvent; +class InputData; +namespace widget { +class NativeKey; +class InProcessWinCompositorWidget; +struct MSGResult; +class DirectManipulationOwner; +} // namespace widget +} // namespace mozilla + +/** + * Forward Windows-internal definitions of otherwise incomplete ones provided by + * the SDK. + */ +const CLSID CLSID_ImmersiveShell = { + 0xC2F03A33, + 0x21F5, + 0x47FA, + {0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39}}; + +/** + * Native WIN32 window wrapper. + */ + +namespace mozilla::widget { + +// Data manipulation: styles + ex-styles, and bitmasking operations thereupon. +struct WindowStyles { + LONG_PTR style = 0; + LONG_PTR ex = 0; + + static WindowStyles FromHWND(HWND); + + constexpr bool operator==(WindowStyles const& that) const { + return style == that.style && ex == that.ex; + } + constexpr bool operator!=(WindowStyles const& that) const { + return !(*this == that); + } + constexpr WindowStyles operator|(WindowStyles const& that) const { + return WindowStyles{.style = style | that.style, .ex = ex | that.ex}; + } + constexpr WindowStyles operator&(WindowStyles const& that) const { + return WindowStyles{.style = style & that.style, .ex = ex & that.ex}; + } + constexpr WindowStyles operator~() const { + return WindowStyles{.style = ~style, .ex = ~ex}; + } + + explicit constexpr operator bool() const { return style || ex; } + + // Compute a style-set which matches `zero` where the bits of `this` are 0 + // and `one` where the bits of `this` are 1. + constexpr WindowStyles merge(WindowStyles zero, WindowStyles one) const { + WindowStyles const& mask = *this; + return (~mask & zero) | (mask & one); + } + + // The dual of `merge`, above: returns a pair [zero, one] satisfying + // `a.merge(a.split(b)...) == b`. (Or its equivalent in valid C++.) + constexpr std::tuple split( + WindowStyles data) const { + WindowStyles const& mask = *this; + return {~mask & data, mask & data}; + } +}; + +void SetWindowStyles(HWND, const WindowStyles&); + +} // namespace mozilla::widget + +class nsWindow final : public nsIWidget { + public: + using Styles = mozilla::widget::WindowStyles; + using WindowHook = mozilla::widget::WindowHook; + using IMEContext = mozilla::widget::IMEContext; + using WidgetEventTime = mozilla::WidgetEventTime; + + NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsIWidget) + + nsWindow(); + + void SendAnAPZEvent(mozilla::InputData& aEvent); + + /* + * Init a standard gecko event for this widget. + * @param aEvent the event to initialize. + * @param aPoint message position in physical coordinates. + */ + void InitEvent(mozilla::WidgetGUIEvent& aEvent, + LayoutDeviceIntPoint* aPoint = nullptr); + + /* + * Returns WidgetEventTime instance which is initialized with current message + * time. + */ + WidgetEventTime CurrentMessageWidgetEventTime() const; + + /* + * Dispatch a gecko keyboard event for this widget. This + * is called by KeyboardLayout to dispatch gecko events. + * Returns true if it's consumed. Otherwise, false. + */ + bool DispatchKeyboardEvent(mozilla::WidgetKeyboardEvent* aEvent); + + /* + * Dispatch a gecko wheel event for this widget. This + * is called by ScrollHandler to dispatch gecko events. + * Returns true if it's consumed. Otherwise, false. + */ + bool DispatchWheelEvent(mozilla::WidgetWheelEvent* aEvent); + + /* + * Dispatch a gecko content command event for this widget. This + * is called by ScrollHandler to dispatch gecko events. + * Returns true if it's consumed. Otherwise, false. + */ + bool DispatchContentCommandEvent(mozilla::WidgetContentCommandEvent* aEvent); + + /* + * Return the parent window, if it exists. + */ + nsWindow* GetParentWindowBase(bool aIncludeOwner); + + // nsIWidget interface + using nsIWidget::Create; // for Create signature not overridden here + [[nodiscard]] nsresult Create(nsIWidget* aParent, const LayoutDeviceIntRect&, + const InitData&) override; + void Destroy() override; + float GetDPI() override; + double GetDefaultScaleInternal() override; + void DidClearParent(nsIWidget* aOldParent) override; + int32_t LogToPhys(double aValue); + + void Show(bool aState) override; + bool IsVisible() const override; + void ConstrainPosition(DesktopIntPoint&) override; + void SetSizeConstraints(const SizeConstraints& aConstraints) override; + void LockAspectRatio(bool aShouldLock) override; + const SizeConstraints GetSizeConstraints() override; + void SetInputRegion(const InputRegion&) override; + void Move(const DesktopPoint&) override; + void Resize(const DesktopSize&, bool aRepaint) override; + void Resize(const DesktopRect&, bool aRepaint) override; + void SetSizeMode(nsSizeMode aMode) override; + nsSizeMode SizeMode() override; + void GetWorkspaceID(nsAString& workspaceID) override; + void MoveToWorkspace(const nsAString& workspaceID) override; + void SuppressAnimation(bool aSuppress) override; + void Enable(bool aState) override; + bool IsEnabled() const override; + void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override; + LayoutDeviceIntRect GetBounds() override; + LayoutDeviceIntRect GetScreenBounds() override; + [[nodiscard]] nsresult GetRestoredBounds(LayoutDeviceIntRect& aRect) override; + LayoutDeviceIntRect GetClientBounds() override; + LayoutDeviceIntPoint GetClientOffset() override; + LayoutDeviceIntSize GetSize() const; + void SetCursor(const Cursor&) override; + bool PrepareForFullscreenTransition(nsISupports** aData) override; + void PerformFullscreenTransition(FullscreenTransitionStage aStage, + uint16_t aDuration, nsISupports* aData, + nsIRunnable* aCallback) override; + void CleanupFullscreenTransition() override; + nsresult MakeFullScreen(bool aFullScreen) override; + void HideWindowChrome(bool aShouldHide) override; + void Invalidate(bool aEraseBackground = false, bool aUpdateNCArea = false, + bool aIncludeChildren = false); + void Invalidate(const LayoutDeviceIntRect& aRect) override; + void* GetNativeData(uint32_t aDataType) override; + nsresult SetTitle(const nsAString& aTitle) override; + void SetIcon(const nsAString& aIconSpec) override; + LayoutDeviceIntPoint WidgetToScreenOffset() override; + LayoutDeviceIntMargin NormalSizeModeClientToWindowMargin() override; + void EnableDragDrop(bool aEnable) override; + void CaptureMouse(bool aCapture); + void CaptureRollupEvents(bool aDoCapture) override; + [[nodiscard]] nsresult GetAttention(int32_t aCycleCount) override; + bool HasPendingInputEvent() override; + WindowRenderer* GetWindowRenderer() override; + void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override; + [[nodiscard]] nsresult OnDefaultButtonLoaded( + const LayoutDeviceIntRect& aButtonRect) override; + nsresult SynthesizeNativeKeyEvent( + int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode, + uint32_t aModifierFlags, const nsAString& aCharacters, + const nsAString& aUnmodifiedCharacters, + nsISynthesizedEventCallback* aCallback) override; + nsresult SynthesizeNativeMouseEvent( + LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage, + mozilla::MouseButton aButton, nsIWidget::Modifiers aModifierFlags, + nsISynthesizedEventCallback* aCallback) override; + + nsresult SynthesizeNativeMouseMove( + LayoutDeviceIntPoint aPoint, + nsISynthesizedEventCallback* aCallback) override { + return SynthesizeNativeMouseEvent( + aPoint, NativeMouseMessage::Move, mozilla::MouseButton::eNotPressed, + nsIWidget::Modifiers::NO_MODIFIERS, aCallback); + } + + nsresult SynthesizeNativeMouseScrollEvent( + LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX, + double aDeltaY, double aDeltaZ, uint32_t aModifierFlags, + uint32_t aAdditionalFlags, + nsISynthesizedEventCallback* aCallback) override; + + nsresult SynthesizeNativeTouchpadPan( + TouchpadGesturePhase aEventPhase, LayoutDeviceIntPoint aPoint, + double aDeltaX, double aDeltaY, int32_t aModifierFlagsn, + nsISynthesizedEventCallback* aCallback) override; + + void SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) override; + InputContext GetInputContext() override; + TextEventDispatcherListener* GetNativeTextEventDispatcherListener() override; + void SetTransparencyMode(TransparencyMode aMode) override; + TransparencyMode GetTransparencyMode() override { return mTransparencyMode; } + void SetCustomTitlebar(bool) override; + void SetResizeMargin(mozilla::LayoutDeviceIntCoord aResizeMargin) override; + void UpdateWindowDraggingRegion( + const LayoutDeviceIntRegion& aRegion) override; + + uint32_t GetMaxTouchPoints() const override; + void SetIsEarlyBlankWindow(bool) override; + + /** + * Event helpers + */ + enum class IsNonclient : bool { No = false, Yes = true }; + bool DispatchMouseEvent(mozilla::EventMessage aEventMessage, WPARAM wParam, + LPARAM lParam, bool aIsContextMenuKey, + int16_t aButton, uint16_t aInputSource, + WinPointerInfo* aPointerInfo = nullptr, + IsNonclient aIgnoreAPZ = IsNonclient::No); + void DispatchPendingEvents(); + void DispatchCustomEvent(const nsString& eventName); + +#ifdef ACCESSIBILITY + /** + * Return an accessible associated with the window. + */ + mozilla::a11y::LocalAccessible* GetAccessible(); +#endif // ACCESSIBILITY + + /** + * Window utilities + */ + nsWindow* GetTopLevelWindow(bool aStopOnDialogOrPopup); + WNDPROC GetPrevWindowProc() { return mPrevWndProc.valueOr(nullptr); } + WindowHook& GetWindowHook() { return mWindowHook; } + nsWindow* GetParentWindow(bool aIncludeOwner); + + bool WidgetTypeSupportsAcceleration() override; + + void ForcePresent(); + bool TouchEventShouldStartDrag(mozilla::EventMessage aEventMessage, + LayoutDeviceIntPoint aEventPoint); + + void SetSmallIcon(HICON aIcon); + void SetBigIcon(HICON aIcon); + void SetSmallIconNoData(); + void SetBigIconNoData(); + + void UpdateMicaBackdrop(bool aForce = false); + + static void SetIsRestoringSession(const bool aIsRestoringSession) { + sIsRestoringSession = aIsRestoringSession; + } + + bool IsRTL() const { return mIsRTL; } + + bool ShouldAssociateWithWinAppSDK() const; + + /** + * AssociateDefaultIMC() associates or disassociates the default IMC for + * the window. + * + * @param aAssociate TRUE, associates the default IMC with the window. + * Otherwise, disassociates the default IMC from the + * window. + * @return TRUE if this method associated the default IMC with + * disassociated window or disassociated the default IMC + * from associated window. + * Otherwise, i.e., if this method did nothing actually, + * FALSE. + */ + bool AssociateDefaultIMC(bool aAssociate); + + bool HasTaskbarIconBeenCreated() { return mHasTaskbarIconBeenCreated; } + // Called when either the nsWindow or an nsITaskbarTabPreview receives the + // notification that this window has its icon placed on the taskbar. + void SetHasTaskbarIconBeenCreated(bool created = true) { + mHasTaskbarIconBeenCreated = created; + } + + // Getter/setter for the nsITaskbarWindowPreview for this nsWindow + already_AddRefed GetTaskbarPreview() { + nsCOMPtr preview( + do_QueryReferent(mTaskbarPreview)); + return preview.forget(); + } + void SetTaskbarPreview(nsITaskbarWindowPreview* preview) { + mTaskbarPreview = do_GetWeakReference(preview); + } + + // Open file picker tracking + void PickerOpen(); + void PickerClosed(); + + bool DestroyCalled() { return mDestroyCalled; } + + bool IsPopup(); + bool ShouldUseOffMainThreadCompositing() override; + + const IMEContext& DefaultIMC() const { return mDefaultIMC; } + + void GetCompositorWidgetInitData( + mozilla::widget::CompositorWidgetInitData* aInitData) override; + bool IsTouchWindow() const { return mTouchWindow; } + bool SynchronouslyRepaintOnResize() override; + void MaybeDispatchInitialFocusEvent() override; + + void LocalesChanged() override; + + void NotifyOcclusionState(mozilla::widget::OcclusionState aState) override; + void MaybeEnableWindowOcclusion(bool aEnable); + + /* + * Return the HWND or null for this widget. + */ + HWND GetWindowHandle() { + return static_cast(GetNativeData(NS_NATIVE_WINDOW)); + } + + /* + * Touch input injection apis + */ + nsresult SynthesizeNativeTouchPoint( + uint32_t aPointerId, TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, double aPointerPressure, + uint32_t aPointerOrientation, + nsISynthesizedEventCallback* aCallback) override; + + nsresult SynthesizeNativePenInput( + uint32_t aPointerId, TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, double aPressure, uint32_t aRotation, + int32_t aTiltX, int32_t aTiltY, int32_t aButton, + nsISynthesizedEventCallback* aCallback) override; + + /* + * WM_APPCOMMAND common handler. + * Sends events via NativeKey::HandleAppCommandMessage(). + */ + bool HandleAppCommandMsg(const MSG& aAppCommandMsg, LRESULT* aRetValue); + + const InputContext& InputContextRef() const { return mInputContext; } + + private: + using TimeStamp = mozilla::TimeStamp; + using TimeDuration = mozilla::TimeDuration; + using TaskbarWindowPreview = mozilla::widget::TaskbarWindowPreview; + using NativeKey = mozilla::widget::NativeKey; + using MSGResult = mozilla::widget::MSGResult; + using PlatformCompositorWidgetDelegate = + mozilla::widget::PlatformCompositorWidgetDelegate; + + class PointerInfo { + public: + enum class PointerType : uint8_t { + TOUCH, + PEN, + }; + + PointerInfo(int32_t aPointerId, LayoutDeviceIntPoint& aPoint, + PointerType aType) + : mPointerId(aPointerId), mPosition(aPoint), mType(aType) {} + + int32_t mPointerId; + LayoutDeviceIntPoint mPosition; + PointerType mType; + }; + + class FrameState { + public: + explicit FrameState(nsWindow* aWindow); + + void ConsumePreXULSkeletonState(bool aWasMaximized); + + // Whether we should call ShowWindow with the relevant size mode if needed. + // We want to avoid that when Windows is already performing the change for + // us (via the SWP_FRAMECHANGED messages). + enum class DoShowWindow : bool { No, Yes }; + + void EnsureSizeMode(nsSizeMode, DoShowWindow = DoShowWindow::Yes); + void EnsureFullscreenMode(bool, DoShowWindow = DoShowWindow::Yes); + void OnFrameChanging(); + void OnFrameChanged(); + + nsSizeMode GetSizeMode() const; + + void CheckInvariant() const; + + private: + void SetSizeModeInternal(nsSizeMode, DoShowWindow); + + nsSizeMode mSizeMode = nsSizeMode_Normal; + // The old size mode before going into fullscreen mode. This should never + // be nsSizeMode_Fullscreen. + nsSizeMode mPreFullscreenSizeMode = nsSizeMode_Normal; + // Whether we're in fullscreen. We need to keep this state out of band, + // rather than just using mSizeMode, because a window can be minimized + // while fullscreen, and we don't store the fullscreen state anywhere else. + bool mFullscreenMode = false; + nsWindow* mWindow; + }; + + // Manager for taskbar-hiding. No persistent state. + class TaskbarConcealer; + + // A magic number to identify the FAKETRACKPOINTSCROLLABLE window created + // when the trackpoint hack is enabled. + enum { eFakeTrackPointScrollableID = 0x46545053 }; + + // Used for displayport suppression during window resize + enum ResizeState { NOT_RESIZING, IN_SIZEMOVE, RESIZING, MOVING }; + + ~nsWindow() override; + + void WindowUsesOMTC() override; + void RegisterTouchWindow() override; + + /** + * Callbacks + */ + static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, + LPARAM lParam); + static LRESULT CALLBACK WindowProcInternal(HWND hWnd, UINT msg, WPARAM wParam, + LPARAM lParam); + + static BOOL CALLBACK DispatchStarvedPaints(HWND aTopWindow, LPARAM aMsg); + static BOOL CALLBACK RegisterTouchForDescendants(HWND aTopWindow, + LPARAM aMsg); + static BOOL CALLBACK UnregisterTouchForDescendants(HWND aTopWindow, + LPARAM aMsg); + static LRESULT CALLBACK MozSpecialMsgFilter(int code, WPARAM wParam, + LPARAM lParam); + static LRESULT CALLBACK MozSpecialWndProc(int code, WPARAM wParam, + LPARAM lParam); + static LRESULT CALLBACK MozSpecialMouseProc(int code, WPARAM wParam, + LPARAM lParam); + static VOID CALLBACK HookTimerForPopups(HWND hwnd, UINT uMsg, UINT idEvent, + DWORD dwTime); + + /** + * Window utilities + */ + LPARAM lParamToScreen(LPARAM lParam); + LPARAM lParamToClient(LPARAM lParam); + + WPARAM wParamFromGlobalMouseState(); + + bool AssociateWithNativeWindow(); + void DissociateFromNativeWindow(); + bool CanTakeFocus(); + bool UpdateNonClientMargins(bool aReflowWindow = true); + void UpdateDarkModeToolbar(); + void ResetLayout(); + HWND GetOwnerWnd() const { return ::GetWindow(mWnd, GW_OWNER); } + bool IsOwnerForegroundWindow() const { + HWND owner = GetOwnerWnd(); + return owner && owner == ::GetForegroundWindow(); + } + bool IsForegroundWindow() const { return mWnd == ::GetForegroundWindow(); } + bool IsPopup() const { return mWindowType == WindowType::Popup; } + bool IsCloaked() const override { return mIsCloaked; } + + /** + * Event processing helpers + */ + HWND GetTopLevelForFocus(HWND aCurWnd); + void DispatchFocusToTopLevelWindow(bool aIsActivate); + bool DispatchStandardEvent(mozilla::EventMessage aMsg); + void RelayMouseEvent(UINT aMsg, WPARAM wParam, LPARAM lParam); + bool ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam, + LRESULT* aRetValue); + // We wrap this in ProcessMessage so we can log the return value + bool ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam, + LRESULT* aRetValue); + bool ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam, + LPARAM& aLParam, MSGResult& aResult); + LRESULT ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched); + LRESULT ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched); + LRESULT ProcessKeyDownMessage(const MSG& aMsg, bool* aEventDispatched); + static bool EventIsInsideWindow( + nsWindow* aWindow, + mozilla::Maybe aEventPoint = mozilla::Nothing()); + static void PostSleepWakeNotification(const bool aIsSleepMode); + int32_t ClientMarginHitTestPoint(int32_t aX, int32_t aY); + void SetWindowButtonRect(WindowButtonType aButtonType, + const LayoutDeviceIntRect& aClientRect) override { + mWindowBtnRect[aButtonType] = aClientRect; + } + TimeStamp GetMessageTimeStamp(LONG aEventTime) const; + static void UpdateFirstEventTime(DWORD aEventTime); + void FinishLiveResizing(ResizeState aNewState); + mozilla::Maybe ConvertTouchToPanGesture( + const mozilla::MultiTouchInput& aTouchInput, PTOUCHINPUT aOriginalEvent); + void DispatchTouchOrPanGestureInput(mozilla::MultiTouchInput& aTouchInput, + PTOUCHINPUT aOSEvent); + + /** + * Event handlers + */ + void OnDestroy() override; + void OnResize(const LayoutDeviceIntSize& aSize); + void OnSizeModeChange(); + bool OnGesture(WPARAM wParam, LPARAM lParam); + bool OnTouch(WPARAM wParam, LPARAM lParam); + bool OnHotKey(WPARAM wParam, LPARAM lParam); + bool OnPaint(); + void OnWindowPosChanging(WINDOWPOS* info); + void OnWindowPosChanged(WINDOWPOS* wp); + void OnSysColorChanged(); + void OnDPIChanged(int32_t x, int32_t y, int32_t width, int32_t height); + bool OnPointerEvents(UINT msg, WPARAM wParam, LPARAM lParam); + bool OnPenPointerEvents(uint32_t aPointerId, UINT aMsg, WPARAM aWParam, + LPARAM aLParam); + bool OnTouchPointerEvents(uint32_t aPointerId, UINT aMsg, WPARAM aWParam, + LPARAM aLParam); + + /** + * Function that registers when the user has been active (used for detecting + * when the user is idle). + */ + void UserActivity(); + + DWORD WindowStyle(); + DWORD WindowExStyle(); + + /** + * Popup hooks + */ + static void ScheduleHookTimer(HWND aWnd, UINT aMsgId); + static void RegisterSpecialDropdownHooks(); + static void UnregisterSpecialDropdownHooks(); + static bool GetPopupsToRollup( + nsIRollupListener* aRollupListener, uint32_t* aPopupsToRollup, + mozilla::Maybe aEventPoint = mozilla::Nothing()); + static bool DealWithPopups(HWND inWnd, UINT inMsg, WPARAM inWParam, + LPARAM inLParam, LRESULT* outResult); + bool IsSimulatedClientArea(int32_t clientX, int32_t clientY); + bool IsWindowButton(int32_t hitTestResult); + + void UpdateOpaqueRegion(const LayoutDeviceIntRegion&) override; + void UpdateOpaqueRegionInternal(); + LayoutDeviceIntRegion GetOpaqueRegionForTesting() const override { + return mOpaqueRegion; + } + // Gets the translucent region, in client coordinates. + LayoutDeviceIntRegion GetTranslucentRegion(); + void MaybeInvalidateTranslucentRegion(); + + void SetColorScheme(const mozilla::Maybe&) override; + void SetMicaBackdrop(bool) override; + + bool DispatchTouchEventFromWMPointer(UINT msg, LPARAM aLParam, + const WinPointerInfo& aPointerInfo, + mozilla::MouseButton aButton); + + static bool IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult); + void IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam); + + /** + * Misc. + */ + void StopFlashing(); + static HWND WindowAtMouse(HWND aForWnd = nullptr); + static bool IsTopLevelMouseExit(HWND aWnd); + LayoutDeviceIntRegion GetRegionToPaint(const PAINTSTRUCT& ps, HDC aDC) const; + + void CreateCompositor() override; + void DestroyCompositor() override; + void RequestFxrOutput() override; + + void RecreateDirectManipulationIfNeeded(); + void ResizeDirectManipulationViewport(); + void DestroyDirectManipulation(); + + bool NeedsToTrackWindowOcclusionState(); + + void AsyncUpdateWorkspaceID(); + + // See bug 603793 + static bool HasBogusPopupsDropShadowOnMultiMonitor(); + + static void InitMouseWheelScrollData(); + + void ChangedDPI(); + + static bool InitTouchInjection(); + + bool InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint, + POINTER_FLAGS aFlags, uint32_t aPressure = 1024, + uint32_t aOrientation = 90); + + void OnFullscreenChanged(nsSizeMode aOldSizeMode, bool aFullScreen); + void TryDwmResizeHack(); + + static void OnCloakEvent(HWND aWnd, bool aCloaked); + void OnCloakChanged(bool aCloaked); + +#ifdef DEBUG + nsresult SetHiDPIMode(bool aHiDPI) override; + nsresult RestoreHiDPIMode() override; +#endif + + // Get the orientation of the hidden taskbar, on the screen that this window + // is on, or Nothing if taskbar isn't hidden. + mozilla::Maybe GetHiddenTaskbarEdge(); + + static bool sTouchInjectInitialized; + static InjectTouchInputPtr sInjectTouchFuncPtr; + static uint32_t sInstanceCount; + static nsWindow* sCurrentWindow; + static bool sIsOleInitialized; + static Cursor sCurrentCursor; + static bool sJustGotDeactivate; + static bool sJustGotActivate; + static bool sIsInMouseCapture; + static bool sIsRestoringSession; + + // Message postponement hack. See the definition-site of + // WndProcUrgentInvocation::sDepth for details. + struct MOZ_STACK_CLASS WndProcUrgentInvocation { + struct Marker { + Marker() { ++sDepth; } + ~Marker() { --sDepth; } + }; + inline static bool IsActive() { return sDepth > 0; } + static size_t sDepth; + }; + + // Hook Data Members for Dropdowns. sProcessHook Tells the + // hook methods whether they should be processing the hook + // messages. + static HHOOK sMsgFilterHook; + static HHOOK sCallProcHook; + static HHOOK sCallMouseHook; + static bool sProcessHook; + static UINT sRollupMsgId; + static HWND sRollupMsgWnd; + static UINT sHookTimerId; + + // Handle the last mouse point and the last non-mouse pointer point to stop + // dispatching redundant eMouseMove events. + class LastMouseMoveData; + + nsClassHashtable mActivePointers; + + // This is used by SynthesizeNativeTouchPoint to maintain state between + // multiple synthesized points, in the case where we can't call InjectTouch + // directly. + mozilla::UniquePtr mSynthesizedTouchInput; + + InputContext mInputContext; + + nsIntSize mLastSize = nsIntSize(0, 0); + nsIntPoint mLastPoint; + HWND mWnd = nullptr; + HWND mTransitionWnd = nullptr; + mozilla::Maybe mPrevWndProc; + IMEContext mDefaultIMC; + HDEVNOTIFY mDeviceNotifyHandle = nullptr; + bool mInDtor = false; + bool mIsVisible = false; + bool mIsCloaked = false; + bool mTouchWindow = false; + bool mDisplayPanFeedback = false; + bool mHideChrome = false; + bool mIsRTL = false; + bool mMousePresent = false; + bool mSimulatedClientArea = false; + bool mDestroyCalled = false; + bool mOpeningAnimationSuppressed = false; + bool mAlwaysOnTop = false; + bool mIsEarlyBlankWindow = false; + bool mIsShowingPreXULSkeletonUI = false; + bool mResizable = false; + bool mHasBeenShown = false; + // Whether we're an alert window. Alert windows don't have taskbar icons and + // don't steal focus from other windows when opened. They're also expected to + // be of type WindowType::Dialog. + bool mIsAlert = false; + bool mIsPerformingDwmFlushHack = false; + bool mDraggingWindowWithMouse = false; + mozilla::Maybe mOldStyles; + nsNativeDragTarget* mNativeDragTarget = nullptr; + HKL mLastKeyboardLayout = 0; + mozilla::CheckInvariantWrapper mFrameState; + WindowHook mWindowHook; + uint32_t mPickerDisplayCount = 0; + HICON mIconSmall = nullptr; + HICON mIconBig = nullptr; + HWND mLastKillFocusWindow = nullptr; + PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate = nullptr; + + LayoutDeviceIntMargin NonClientSizeMargin() const { + return NonClientSizeMargin(mCustomNonClientMetrics.mOffset); + } + LayoutDeviceIntMargin NonClientSizeMargin( + const LayoutDeviceIntMargin& aNonClientOffset) const; + LayoutDeviceIntMargin NormalWindowNonClientOffset() const; + + struct CustomNonClientMetrics { + // Width of the left and right portions of the resize region + mozilla::LayoutDeviceIntCoord mHorResizeMargin; + // Height of the top and bottom portions of the resize region + mozilla::LayoutDeviceIntCoord mVertResizeMargin; + // Height of the caption plus border + mozilla::LayoutDeviceIntCoord mCaptionHeight; + // Pre-calculated outward offset applied to the default frame + LayoutDeviceIntMargin mOffset; + + LayoutDeviceIntMargin ResizeMargins() const { + return {mVertResizeMargin, mHorResizeMargin, mVertResizeMargin, + mHorResizeMargin}; + } + + LayoutDeviceIntMargin DefaultMargins() const { + auto margins = ResizeMargins(); + margins.top += mCaptionHeight; + return margins; + } + } mCustomNonClientMetrics; + + // Indicates the custom titlebar is enabled. + bool mCustomNonClient = false; + // Whether we want to draw to the titlebar once the chrome shows. (Always + // Nothing if mHideChrome is false.) + mozilla::Maybe mCustomTitlebarOnceChromeShows; + // Custom extra resize margin width. + mozilla::LayoutDeviceIntCoord mCustomResizeMargin{0}; + + // not yet set, will be calculated on first use + double mDefaultScale = -1.0; + + // not yet set, will be calculated on first use + float mAspectRatio = 0.0; + + nsCOMPtr mIdleService; + + // Draggable titlebar region maintained by UpdateWindowDraggingRegion + LayoutDeviceIntRegion mDraggableRegion; + // Opaque region maintained by UpdateOpaqueRegion (relative to the client + // area). + LayoutDeviceIntRegion mOpaqueRegion; + + LayoutDeviceIntRect mBounds; + + // Graphics + LayoutDeviceIntRect mLastPaintBounds; + // The region of the window we know is cleared to transparent already, + // in client coords. + LayoutDeviceIntRegion mClearedRegion; + + ResizeState mResizeState = NOT_RESIZING; + + // Transparency + TransparencyMode mTransparencyMode = TransparencyMode::Opaque; + + // Win7 Gesture processing and management + nsWinGesture mGesture; + + // Weak ref to the nsITaskbarWindowPreview associated with this window + nsWeakPtr mTaskbarPreview = nullptr; + + // The input region that determines whether mouse events should be ignored + // and pass through to the window below. This is currently only used for + // popups. + InputRegion mInputRegion; + + // True if the taskbar (possibly through the tab preview) tells us that the + // icon has been created on the taskbar. + bool mHasTaskbarIconBeenCreated = false; + + // Whether we're in the process of sending a WM_SETTEXT ourselves + bool mSendingSetText = false; + + // Whether we are asked to render a mica backdrop. + bool mMicaBackdrop : 1; + + int32_t mCachedHitTestResult = 0; + + // The point in time at which the last paint completed. We use this to avoid + // painting too rapidly in response to frequent input events. + TimeStamp mLastPaintEndTime; + + // Caching for hit test results (in client coordinates) + LayoutDeviceIntPoint mCachedHitTestPoint; + TimeStamp mCachedHitTestTime; + + RefPtr mBasicLayersSurface; + + double mSizeConstraintsScale; // scale in effect when setting constraints + + // Will be calculated when layer manager is created. + int32_t mMaxTextureSize = -1; + + // Pointer events processing and management + WinPointerEvents mPointerEvents; + + ScreenPoint mLastPanGestureFocus; + + // When true, used to indicate an async call to RequestFxrOutput to the GPU + // process after the Compositor is created + bool mRequestFxrOutputPending = false; + + // A stack based class used in DispatchMouseEvent() to tell whether we should + // NOT open context menu when we receives WM_CONTEXTMENU after the + // DispatchMouseEvent calls. + // This class now works only in the case where a mouse up event happened in + // the overscroll gutter. + class MOZ_STACK_CLASS ContextMenuPreventer final { + public: + explicit ContextMenuPreventer(nsWindow* aWindow) + : mWindow(aWindow), mNeedsToPreventContextMenu(false) {}; + ~ContextMenuPreventer() { + mWindow->mNeedsToPreventContextMenu = mNeedsToPreventContextMenu; + } + void Update(const mozilla::WidgetMouseEvent& aEvent, + const nsIWidget::ContentAndAPZEventStatus& aEventStatus); + + private: + nsWindow* mWindow; + bool mNeedsToPreventContextMenu = false; + }; + friend class ContextMenuPreventer; + bool mNeedsToPreventContextMenu = false; + + mozilla::UniquePtr mDmOwner; + + // Client rect for minimize, maximize and close buttons. + mozilla::EnumeratedArray + mWindowBtnRect; + + nsString mDesktopId MOZ_GUARDED_BY(mozilla::sMainThreadCapability); + + // MouseMux multi-mouse/keyboard support - per-window client + mozilla::UniquePtr mMouseMuxClient; + + public: + // MouseMux methods + void InitMouseMux(); + void ShutdownMouseMux(); + void ShowMouseMuxDebugDialog(); + void HideMouseMuxDebugDialog(); + mozilla::widget::MouseMuxClient* GetMouseMuxClient() { return mMouseMuxClient.get(); } + + private: + friend class nsWindowGfx; + + static constexpr int kHiddenTaskbarSize = 2; +}; + +#endif // WIDGET_WINDOWS_NSWINDOW_H_