Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9cecaeb
Add InputFilter for native mouse input blocking
MouseMux Jan 8, 2026
40df9fd
Add MouseMuxService WebSocket client
MouseMux Jan 8, 2026
45d8a58
Add MouseMuxDebugDialog for testing
MouseMux Jan 8, 2026
af98e59
Update moz.build to include MouseMux sources
MouseMux Jan 8, 2026
87cc980
Integrate MouseMux into nsWindow
MouseMux Jan 8, 2026
829faf9
Add keyboard and mousewheel blocking
MouseMux Jan 8, 2026
70d27b0
Add window ownership tracking
MouseMux Jan 8, 2026
a3a9f0d
Add version and timestamp to MouseMux logging
MouseMux Jan 9, 2026
42beb22
Implement per-window MouseMuxClient v4.3
MouseMux Jan 9, 2026
958244e
Fix keyboard handling and add MouseMux rules v4.5
MouseMux Jan 9, 2026
7ca6416
v4.6: Reduce logging - skip motion events
MouseMux Jan 9, 2026
142c46e
v4.7: Forward keyboard to focused window
MouseMux Jan 10, 2026
73dc6f7
v5.0: Restore independent Block Input toggle
MouseMux Jan 10, 2026
71fda2a
Add MouseMux architecture documentation
MouseMux Jan 10, 2026
0142157
v5.1: Fix keyboard - add MOUSEMUX_MARKER to keyboard events
MouseMux Jan 10, 2026
f23096d
v5.2: Fix thread safety - use PostMessage for UI updates
MouseMux Jan 10, 2026
1b75a20
v5.8: Per-window isolation and multi-window ownership fix
MouseMux Jan 11, 2026
b01729f
v5.9: Fix motion isolation - only owner's events processed
MouseMux Jan 11, 2026
d800453
v5.19: Strict owner isolation - first click claims, no coordinate lea…
MouseMux Jan 13, 2026
3d13a04
v5.20: Keyboard isolation - only owner's paired keyboard can type
MouseMux Jan 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
281 changes: 281 additions & 0 deletions widget/windows/InputFilter.cpp
Original file line number Diff line number Diff line change
@@ -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<HWND, bool> InputFilter::sEnabledWindows;
std::mutex InputFilter::sMutex;
std::map<HWND, InputFilter::CursorPos> InputFilter::sCursorPositions;
std::mutex InputFilter::sCursorMutex;
std::map<HWND, InputFilter::KeyboardState> InputFilter::sKeyboardStates;
std::mutex InputFilter::sKeyboardMutex;
std::map<HWND, InputFilter::MouseButtonState> 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<std::mutex> lock(sMutex);
sEnabledWindows[topLevel] = true;
}

void InputFilter::DisableForWindow(HWND hwnd) {
HWND topLevel = GetTopLevelWindow(hwnd);
if (!topLevel) topLevel = hwnd;
std::lock_guard<std::mutex> lock(sMutex);
sEnabledWindows[topLevel] = false;
}

bool InputFilter::IsEnabledForWindow(HWND hwnd) {
HWND topLevel = GetTopLevelWindow(hwnd);
if (!topLevel) topLevel = hwnd;
std::lock_guard<std::mutex> 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<std::mutex> lock(sMutex);
sEnabledWindows.erase(topLevel);
}
{
std::lock_guard<std::mutex> lock(sCursorMutex);
sCursorPositions.erase(topLevel);
}
{
std::lock_guard<std::mutex> lock(sKeyboardMutex);
sKeyboardStates.erase(topLevel);
}
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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
98 changes: 98 additions & 0 deletions widget/windows/InputFilter.h
Original file line number Diff line number Diff line change
@@ -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 <windows.h>
#include <map>
#include <mutex>

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<HWND, bool> sEnabledWindows;
static std::mutex sMutex;

// Per-window cursor position storage
struct CursorPos {
int screenX = 0;
int screenY = 0;
bool valid = false;
};
static std::map<HWND, CursorPos> 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<HWND, KeyboardState> sKeyboardStates;
static std::mutex sKeyboardMutex;

// Per-window mouse button state
struct MouseButtonState {
bool left = false;
bool right = false;
bool middle = false;
};
static std::map<HWND, MouseButtonState> sMouseButtonStates;
static std::mutex sMouseButtonMutex;

static HWND GetTopLevelWindow(HWND hwnd);
};

} // namespace widget
} // namespace mozilla

#endif // widget_windows_InputFilter_h
Loading