Skip to content

Claude/chrome manager multi window #112

Merged
zmzimpl merged 71 commits into
mainfrom
claude/chrome-manager-multi-window-011CV3jn3sThDU9yJSoiXjsT
Nov 17, 2025
Merged

Claude/chrome manager multi window #112
zmzimpl merged 71 commits into
mainfrom
claude/chrome-manager-multi-window-011CV3jn3sThDU9yJSoiXjsT

Conversation

@zmzimpl

@zmzimpl zmzimpl commented Nov 17, 2025

Copy link
Copy Markdown
Owner

No description provided.

claude and others added 30 commits November 12, 2025 09:40
Implement comprehensive multi-window sync functionality based on uiohook-napi
and Node.js, enabling real-time synchronization of mouse, keyboard, and wheel
events from a master window to multiple slave windows.

## Changes

### Core Implementation
- Native Addon Extensions (window-addon.cpp):
  - Add SendMouseEvent() for mouse event dispatching
  - Add SendKeyboardEvent() for keyboard event dispatching
  - Add SendWheelEvent() for wheel event dispatching
  - Add GetWindowBounds() for window position/size retrieval
  - Cross-platform support (Windows PostMessage / macOS CGEvent API)

- Multi-Window Sync Service (multi-window-sync-service.ts):
  - Event capture using uiohook-napi
  - Intelligent coordinate mapping (relative position algorithm)
  - Master-slave window management
  - Event throttling and filtering
  - Extension window monitoring
  - CDP integration for precise scroll synchronization

- Database Support (window.ts):
  - Add getByPid() method for PID-based window lookup

- Service Integration (services/index.ts):
  - Register multi-window sync service

## Features

### Phase 1: MVP
- Mouse event sync (move, click, right-click)
- Keyboard event sync (keydown, keyup)
- Master-slave window management
- Relative coordinate mapping

### Phase 2: Enhanced
- Extension window sync (popup windows)
- Optimized wheel handling (tiered strategy)
- Event filtering and throttling
- CDP page scroll synchronization

## Documentation
- Add MULTI_WINDOW_SYNC_GUIDE.md with comprehensive usage guide
- Add examples/multi-window-sync-example.ts with 8 practical examples

## Technical Stack
- uiohook-napi: Cross-platform event capture
- Native Addon (C++): Event dispatching
- Puppeteer: CDP integration
- TypeScript: Core service implementation

## Platform Support
- Windows (requires admin privileges)
- macOS (requires accessibility permissions)
Implement a complete UI control panel for multi-window sync functionality,
integrating it into the existing sync page alongside window arrangement.

## Changes

### Preload Bridge
- **packages/preload/src/bridges/sync.ts**:
  - Add SyncOptions interface
  - Add startSync(), stopSync(), getSyncStatus() methods
  - Keep existing arrangeWindows() for backward compatibility

### UI Components
- **packages/renderer/src/pages/sync/index.tsx**:
  - Complete redesign as multi-window sync control panel
  - Implement master/slave window selector with dropdown
  - Add real-time sync status display with polling
  - Add sync feature toggles (Mouse, Keyboard, Wheel, CDP)
  - Add advanced configuration options (throttling, intervals)
  - Implement collapsible panels for sync and arrangement
  - Add status badges and tags showing window roles
  - Preserve existing window arrangement functionality

## Features

### Window Selection
- Master window dropdown selector
- Multi-select for slave windows
- Auto-deconflict (master cannot be slave)
- Auto-select first window as master on load

### Sync Controls
- Large "Start Sync" / "Stop Sync" button
- Disable controls during active sync
- Real-time status updates (1s polling)
- Success/error message notifications

### Configuration UI
- Toggle switches for each sync feature
- Advanced options in collapsible section
- Input validation and constraints
- Configuration persistence in localStorage

### Status Display
- Badge showing sync state and slave count
- Table column showing Master/Slave/Idle tags
- Success alert when sync is active
- Refresh button to reload windows

### Layout
- 14/10 column split (windows table / control panel)
- Collapsible sections for organization
- Responsive table with scroll
- Clean, modern Ant Design styling

## User Experience
- Clear visual feedback for all actions
- Disabled states prevent invalid operations
- Help text and descriptions for options
- Smooth transitions and animations
Fix TypeError when reading enableCdpSync from undefined syncOptions.
This occurred when localStorage contained old config without syncOptions field.

## Changes
- Add defaultSyncOptions and defaultConfig constants
- Implement proper config merging with defaults
- Add try-catch for JSON parse errors
- Ensure all config fields always have valid values

## Fix Details
When loading from localStorage, old configs might not have syncOptions field.
Now we merge any saved config with default values to ensure consistency.
Redesign the sync page UI to make window selection more intuitive:
- Add 'Set Master' button column in the table
- Use table row selection to determine sync windows
- Remove separate master/slave dropdown selectors
- Auto-select all windows on load
- Show detailed status in toolbar (master window, selected count)
- Add Quick Info guide for users
- Improve layout: 16/8 column split for better space usage
- Keep existing app style and design patterns

## Changes
- Table now has 'Action' column with 'Set Master' buttons
- 'Role' column shows Master/Slave/Ready tags
- Toolbar displays current master and selection stats
- Right panel simplified with just Start/Stop button
- Sync features and arrangement in collapsible panels
- Better visual feedback and user guidance
This commit implements comprehensive monitor selection functionality,
allowing users to choose which display to use when arranging windows.

Changes:
- Native Addon (window-addon.cpp):
  * Added GetMonitorsJS() method to expose monitor information to JavaScript
  * Updated ArrangeWindows() to accept optional monitorIndex parameter
  * Modified monitor selection logic to use user-selected monitor instead of hardcoded monitors[0]

- IPC Layer (sync-service.ts):
  * Added 'window-get-monitors' IPC handler to fetch available monitors
  * Updated 'window-arrange' handler to accept and pass monitorIndex to native addon

- Bridge Layer (preload/sync.ts):
  * Added MonitorInfo interface for type safety
  * Added getMonitors() bridge method
  * Updated arrangeWindows() signature to accept optional monitorIndex

- UI Layer (renderer/pages/sync/index.tsx):
  * Added monitor state management (monitors, selectedMonitorIndex)
  * Implemented fetchMonitors() to load available monitors on component mount
  * Added Select component for monitor/display selection in Window Arrangement panel
  * Updated handleArrangeWindows() to pass selectedMonitorIndex to arrangeWindows call

Features:
- Detects all available displays with their dimensions and primary/secondary status
- Displays user-friendly monitor labels with icons (Primary/Secondary) and resolutions
- Remembers selected monitor during session
- Backwards compatible (defaults to first monitor if not specified)

The native addon sorts monitors so non-primary monitors come first by default,
making it ideal for multi-monitor setups where users typically want to arrange
windows on secondary displays.
This commit fixes two critical issues with multi-window synchronization:

1. Keyboard Event Synchronization Error:
   - Previously, keyboard events were captured globally regardless of which
     window had focus, causing unwanted keyboard input synchronization
   - Added focus tracking mechanism using mouse position as a proxy for
     window focus

2. Unwanted Event Triggers Outside Master Window:
   - Keyboard events from other windows were being synchronized to slave
     windows even when the user was not interacting with the master window
   - Now keyboard events are only synchronized when the mouse is within
     the master window boundaries

Changes:
- Added `isMouseInMaster` flag to track if mouse is currently in master window
- Added `lastMouseCheckTime` timestamp for focus timeout detection
- Added `MOUSE_FOCUS_TIMEOUT_MS` constant (500ms) to handle brief mouse exits
- Updated `handleMouseMove()` to continuously update focus tracking state
- Updated `handleKeyDown()` and `handleKeyUp()` to check focus state before
  synchronizing keyboard events
- Added focus state reset in `stopSync()` for proper cleanup

Implementation Details:
- Focus is gained immediately when mouse enters master window
- Focus is considered lost after 500ms without mouse movement in master
  (this timeout prevents accidental loss of focus during brief exits)
- Keyboard synchronization now only occurs when `isMouseInMaster` is true
- All mouse events (move, down, up, wheel) continue to check window bounds
  as before

This ensures that keyboard input only affects slave windows when the user
is actively working within the master window, preventing confusion and
unintended actions in slave windows.
…tion

This commit fixes incorrect keyboard input synchronization between master
and slave windows by using the correct key code format.

Problem:
- User reported that keyboard inputs were mapped incorrectly:
  * Any key press in master window resulted in space in slave windows
  * Space key in master window resulted in '9' in slave windows
- This was caused by using uiohook's cross-platform virtual keycode
  instead of system-native key codes

Root Cause:
- uiohook-napi provides two key code fields in KeyboardEventData:
  * keycode: uiohook's cross-platform virtual key code
  * rawcode: system-native key code (VK_* on Windows, CGKeyCode on macOS)
- The native addon (window-addon.cpp) expects system-native key codes:
  * Windows: Virtual-Key codes (VK_*) for WM_KEYDOWN/WM_KEYUP messages
  * macOS: CGKeyCode for CGEventCreateKeyboardEvent
- Previous implementation used keycode, which doesn't map correctly to
  system key codes, causing incorrect character input

Solution:
- Changed handleKeyDown() and handleKeyUp() to use rawcode instead of keycode
- Added detailed comments explaining the difference and why rawcode is needed
- rawcode provides the actual OS-specific key codes that native APIs expect

Technical Details:
- Windows: rawcode maps to Virtual-Key codes (e.g., VK_SPACE = 0x20)
- macOS: rawcode maps to CGKeyCode values
- This ensures 1:1 mapping between captured key events and replayed events

Testing:
- All keyboard inputs should now synchronize correctly
- Character keys (a-z, 0-9) should produce the same characters in slave windows
- Special keys (Space, Enter, Tab, etc.) should work as expected
- Modifier keys (Ctrl, Alt, Shift) should function properly
This commit fixes a critical crash (FATAL ERROR: tsfn_to_js_proxy napi_call_function)
that occurred when keyboard events were synchronized. The crash was caused by
uncaught exceptions in uiohook-napi event callbacks that propagated to the
N-API thread-safe function layer.

Problem:
- Application crashed with N-API fatal error when typing in master window
- Error: "FATAL ERROR: tsfn_to_js_proxy napi_call_function"
- Crash occurred because uiohook-napi event callbacks run in background threads
  and any uncaught exceptions cause N-API thread-safe function calls to fail

Root Cause:
- Event handler functions (handleKeyDown, handleKeyUp, etc.) lacked error handling
- If rawcode was undefined/null or windowManager was not initialized, exceptions
  would propagate uncaught to the N-API layer
- N-API thread-safe functions cannot handle uncaught JavaScript exceptions from
  background threads, causing fatal errors

Solution:
Added comprehensive try-catch error handling to all event handlers:

1. handleKeyDown() and handleKeyUp():
   - Wrapped entire function in try-catch
   - Added validation for windowManager existence
   - Added validation for rawcode (check undefined/null)
   - Added individual try-catch for each sendKeyboardEvent call
   - Added detailed error logging

2. handleMouseMove(), handleMouseDown(), handleMouseUp():
   - Wrapped entire function in try-catch
   - Added individual try-catch for each sendMouseEvent call
   - Added error logging for each slave window operation

3. handleWheel():
   - Wrapped entire function in try-catch
   - Added individual try-catch for each sendWheelEvent call
   - Added error logging

Benefits:
- Prevents application crashes from propagating exceptions
- Provides detailed error logs for debugging
- Allows synchronization to continue even if one slave window fails
- Gracefully handles edge cases (null rawcode, missing windowManager)

Error Handling Strategy:
- Outer try-catch: Catches any unexpected errors in event handler logic
- Inner try-catch: Isolates errors from individual slave window operations
- Validation checks: Prevents operations with invalid data
- Logging: Records all errors for debugging without crashing the app

This ensures robust operation even when encountering unexpected conditions
or errors from the native addon layer.
…nput

This commit fixes the issue where keyboard input was not synchronized
immediately after starting multi-window sync, even when the user was
actively typing in the master window.

Problem:
- User reported that keyboard input in active master window was not syncing
- After starting synchronization, typing immediately resulted in no sync
- This occurred even when the master window was clearly active/focused

Root Cause:
- isMouseInMaster flag was initialized to false
- It only became true when mouse MOVED within master window bounds
- If user started typing without moving mouse first, flag stayed false
- Keyboard sync logic checks isMouseInMaster, preventing all input sync

Why This Happened:
- Previous fix added isMouseInMaster check to prevent syncing keyboard
  input from other windows (when mouse was outside master)
- However, initialization didn't account for users who start typing
  immediately after clicking "Start Sync" button
- User expects: click button, start typing → should sync
- Actual behavior: click button, start typing → no sync (must move mouse first)

Solution:
Initialize focus tracking state when sync starts:
- Set isMouseInMaster = true in startSync()
- Set lastMouseCheckTime = Date.now()
- This assumes user is focused on master window when starting sync
  (reasonable assumption since they just clicked the start button)

Benefits:
- Keyboard input works immediately after starting sync
- No need to move mouse before typing
- Mouse movement still updates focus tracking dynamically
- If mouse leaves master window area, focus is lost after timeout
- Provides better user experience matching user expectations

Behavior:
1. User clicks "Start Synchronization" → isMouseInMaster = true
2. User can immediately start typing → keyboard events sync
3. If user moves mouse outside master → focus lost after 500ms timeout
4. If user moves mouse back into master → focus regained immediately
5. Mouse movement continuously updates focus state

This maintains the protection against unwanted keyboard sync from other
windows while allowing immediate typing after sync activation.
…issues

Added detailed debug and info logging to keyboard and wheel event handlers
to help diagnose why these events are not synchronizing while mouse events work.

Logging includes:
- handleKeyDown/handleKeyUp: logs capturing state, focus state, rawcode, keycode, and slave PIDs
- handleWheel: logs capturing state, sync options, mouse position, and event details
- All handlers: log each step where events might be skipped

This will help identify:
1. Whether events are being received
2. Which validation check is failing
3. Whether events are being sent to slave windows
4. Any errors during event dispatch

User can check logs to see exact failure point.
This commit fixes keyboard synchronization by using the correct field name
from uiohook-napi events.

Problem:
- Keyboard events were not synchronizing despite focus tracking working correctly
- Logs showed: "Invalid rawcode in handleKeyDown/handleKeyUp"
- Event object from uiohook-napi only contains keycode, not rawcode

Root Cause Analysis from Logs:
The debug logs revealed the actual event structure:
```
"event": {
  "type": 4,
  "time": 967753484,
  "altKey": false,
  "ctrlKey": false,
  "metaKey": false,
  "shiftKey": false,
  "keycode": 32  ← Only keycode field exists
}
```

The code was attempting to use rawcode which doesn't exist in uiohook-napi:
```typescript
const {rawcode} = event;  // rawcode is undefined!
if (rawcode === undefined || rawcode === null) {
  return;  // Event was skipped
}
```

Solution:
1. Updated KeyboardEventData interface to remove rawcode field
2. Added actual fields from uiohook-napi (altKey, ctrlKey, metaKey, shiftKey, time)
3. Changed handleKeyDown and handleKeyUp to use keycode instead of rawcode
4. Updated validation to check keycode instead of rawcode

Note on Previous rawcode Attempt:
Commit 61c2f4c attempted to use rawcode to fix key mapping issues, but this
was based on incorrect assumption about uiohook-napi's event structure.
uiohook-napi provides cross-platform keycodes that should work correctly
with the native addon.

Changes:
- interface KeyboardEventData: removed rawcode, added modifier keys
- handleKeyDown: use keycode, validate keycode
- handleKeyUp: use keycode, validate keycode
- All logging updated to reflect keycode usage

This should restore keyboard synchronization functionality.
…de fields

Added comprehensive logging to inspect the complete event object structure
from uiohook-napi to determine if there are additional fields beyond keycode
that we can use for more accurate key mapping.

Logs include:
- Full event object
- All object keys using Object.keys()
- Specific checks for: keycode, rawcode, scancode, code, key fields

This will help identify the correct field to use for system-native key codes.
…board sync

- Add comprehensive keycode mapping tables for Windows (VK_*) and macOS (CGKeyCode)
- Create convertKeyCode() function to translate uiohook virtual keycodes to system-native codes
- Update handleKeyDown() to use converted keycodes
- Update handleKeyUp() to use converted keycodes
- Add detailed logging showing both uiohook and system keycodes for debugging

This fixes the keyboard character mapping issue where inputs were not correctly
synchronized between master and slave windows. uiohook-napi provides cross-platform
virtual keycodes (e.g., 30-55 for A-Z), but native window APIs require system-specific
keycodes (Windows VK_* or macOS CGKeyCode).
- Add uiohook-napi ^1.5.4 for keyboard and mouse event capture
- Uses N-API for Node.js version compatibility (supports Node 22)
- Includes prebuilt binaries for multiple platforms (Linux, macOS, Windows)
…js 22 support

Breaking Changes:
- Replace uiohook-napi with @tkomde/iohook
- Use rawcode (native OS keycodes) instead of virtual keycodes with manual mapping

Key Changes:
- Install @tkomde/iohook ^1.1.7 (supports Node 20-24, Electron 29-39)
- Remove uiohook-napi dependency
- Configure iohook to use rawcode via useRawcode(true)
- Remove complex keycode mapping tables (UIOHOOK_TO_WINDOWS_VK, UIOHOOK_TO_MACOS_CGKEY)
- Remove convertKeyCode() function (no longer needed)
- Update KeyboardEventData interface to include rawcode field
- Update handleKeyDown() to use rawcode directly
- Update handleKeyUp() to use rawcode directly
- Simplify logging (remove verbose debug logs)

Benefits:
- Native support for Node.js 22 and 24 via N-API
- Direct use of OS-native keycodes (no conversion needed)
- Simpler codebase without mapping tables
- More accurate keyboard synchronization
- Better maintained package (published Nov 2025)
Changes:
- Switch back from @tkomde/iohook to uiohook-napi (has prebuilt binaries)
- Remove rawcode usage (not available in uiohook-napi)
- Use keycode directly from uiohook-napi without conversion
- Add detailed logging to track keycode values for debugging
- Update KeyboardEventData interface to use keycode

Rationale:
- @tkomde/iohook requires compilation which fails in sandboxed environments
- uiohook-napi has prebuilt binaries and supports Node.js >= 16
- Will test if direct keycode passthrough works, if not we can add mapping

Testing needed:
- Check if keyboard sync works with direct keycode passthrough
- Monitor logs to see actual keycode values
- Build accurate keycode mapping if needed based on test results
Changes:
1. Add comprehensive keycode mapping tables:
   - UIOHOOK_TO_WINDOWS_VK: Map uiohook keycodes to Windows Virtual-Key codes
   - UIOHOOK_TO_MACOS_CGKEY: Map uiohook keycodes to macOS CGKeyCode
   - Support letters (A-Z), numbers (0-9), function keys, arrow keys, and special keys
   - Based on official libuiohook keycode definitions

2. Implement convertKeyCode() function:
   - Automatically detect platform (Windows/macOS)
   - Convert uiohook virtual keycodes to system-native keycodes
   - Fallback to original keycode if no mapping found

3. Add keyboard event deduplication:
   - Track last key event (keycode, type, timestamp)
   - Ignore duplicate events within 20ms threshold
   - Prevents double character input issue
   - Separate dedup for keydown and keyup events

4. Update event handlers:
   - handleKeyDown: Use convertKeyCode() and add deduplication
   - handleKeyUp: Use convertKeyCode() and add deduplication
   - Clean up verbose debug logging

Fixes:
- Incorrect character mapping (uiohook keycode != system keycode)
- Duplicate character input (1 input → 2 outputs)

Testing needed:
- Verify keyboard characters sync correctly
- Check if deduplication resolves double input issue
- Test on both Windows and macOS if possible
Added mappings for:

Punctuation marks:
- Minus (-), Equals (=)
- Brackets ([, ])
- Backslash (\)
- Semicolon (;), Quote (')
- Comma (,), Period (.), Slash (/)
- Backquote (\`)

Numpad keys:
- Numbers: Numpad 0-9
- Operators: *, +, -, /, Enter, Decimal/Separator

Both Windows VK codes and macOS CGKeyCode mappings added.

Verified correct mappings for:
- Insert (57426/0x0E52)
- Delete (57427/0x0E53)
- Home (57415/0x0E47)
- End (57423/0x0E4F)
- Page Up (57417/0x0E49)
- Page Down (57425/0x0E51)
Changes:
1. Increase deduplication threshold from 20ms to 50ms
   - More tolerance for system event timing variations

2. Fix duplicate event listener registration
   - Call removeEventListeners() before setup to prevent multiple registrations
   - Add log message when listeners are setup

3. Add event counter and enhanced logging
   - Track total key event count
   - Use emoji indicators (⬇️ keydown, ⬆️ keyup, ⚠️ duplicate)
   - Log event counter, keycode conversion, and slave count
   - Show warning with details when duplicate detected

4. Improve duplicate detection logic
   - Extract isDuplicate to separate variable for clarity
   - Log time since last event and threshold

Debug output now shows:
- Every keydown/keyup event with counter
- Duplicate detection with timing info
- Keycode conversion details
- Which slaves received the event

This helps diagnose:
- If events are truly duplicated by uiohook
- If event listeners are registered multiple times
- Timing patterns of duplicate events
Major changes to fix duplicate input and extended key issues:

1. Remove keyup listener (only listen to keydown)
   - Synthesize complete key press (keydown + keyup) in handler
   - 10ms delay between down and up ensures proper sequencing
   - Prevents duplicate character input

2. Fix Windows extended key handling in native addon
   - Properly construct lParam for PostMessage
   - Set bit 24 for extended keys (arrows, Insert, Delete, Home, End, etc.)
   - Set bits 30-31 for keyup events
   - Extended keys now detected by VK code

3. Improved logging
   - Changed from ⬇️/⬆️ to ⌨️ (single key press event)
   - Shows complete key press action

Fixes:
- Duplicate character input (1 input → 2 outputs)
- Insert, Delete, Home, End, Page Up, Page Down not working
- Arrow keys not working correctly
- Numpad Enter not working

Note: Native addon changes require recompilation:
  npm run build:native-addon (Windows/Linux)
  npm run build:native-addon:mac-arm64 or mac-x64 (macOS)
Problem: uiohook-napi returns base scan codes (e.g., 82) instead of full
extended key codes (e.g., 57426), causing wrong key mapping:
- Insert (82) -> r (VK_R = 0x52)
- Home (71) -> g (VK_G = 0x47)
- PgUp (73) -> i (VK_I = 0x49)
- Delete (83) -> s (VK_S = 0x53)
- End (79) -> o (VK_O = 0x4F)
- PgDn (81) -> q (VK_Q = 0x51)
- Numlock (69) -> e (VK_E = 0x45)

Solution: Add base scan code mappings for extended keys

Windows mappings added:
- 82 -> VK_INSERT (0x2D)
- 71 -> VK_HOME (0x24)
- 73 -> VK_PRIOR/PgUp (0x21)
- 83 -> VK_DELETE (0x2E)
- 79 -> VK_END (0x23)
- 81 -> VK_NEXT/PgDn (0x22)
- 72 -> VK_UP (0x26)
- 80 -> VK_DOWN (0x28)
- 75 -> VK_LEFT (0x25)
- 77 -> VK_RIGHT (0x27)
- 69 -> VK_NUMLOCK (0x90)

macOS mappings added with corresponding CGKeyCode values

Numpad conflict note:
- Most numpad number keys (0-9 except 5) conflict with extended keys
- Extended keys prioritized as they are more commonly used
- Only Numpad 5, operators (+, -, *, /), and Enter remain available
Add comprehensive logging to see what uiohook-napi actually returns:
- Log all event object keys
- Log full event object
- Log raw keycode value

This will help identify:
1. Correct keycode values for Insert, Delete, Home, End, Page Up, Page Down
2. How numpad keys are differentiated from extended keys
3. Any additional fields (like location) that might help distinguish keys

User reported:
- Insert, Delete, Home, End, PgUp, PgDn still not mapping correctly
- Numpad 1-9 stopped working (were working before our changes)

Need actual keycode values from logs to fix mappings accurately.
…port

This commit replaces uiohook-napi with @tkomde/iohook and removes the
complex keycode mapping tables that were causing issues with extended
keys and numpad keys.

Key changes:
- Replace uiohook-napi dependency with @tkomde/iohook ^1.1.7
- Enable rawcode mode to get native OS keycodes directly
- Remove UIOHOOK_TO_WINDOWS_VK mapping table (140 lines)
- Remove UIOHOOK_TO_MACOS_CGKEY mapping table (120 lines)
- Remove convertKeyCode() function that used these tables
- Update KeyboardEventData interface to include rawcode field
- Update handleKeyDown/handleKeyUp to use rawcode directly
- Simplify keycode handling by using native OS codes without conversion
- Fix ESLint error in wheel event handler (self-assignment)

Benefits:
- Eliminates keycode conflicts between numpad and extended keys
- More accurate keyboard event synchronization
- Simpler, more maintainable code (284 fewer lines)
- Direct use of native keycodes avoids mapping errors
- Supports Node.js 20-24 (including Node 22)

The @tkomde/iohook library provides rawcode field which contains the
native OS keycode (Windows VK_* or macOS CGKeyCode) directly, removing
the need for virtual-to-native keycode conversion.
…pport

- Upgrade Electron from 27.0.0 to 31.0.0
- Update @tkomde/iohook target from electron-118 to electron-125
- Electron 31 (ABI 125) has official prebuilt binaries for @tkomde/iohook
- This eliminates the need for local compilation and DLL dependency issues
claude and others added 29 commits November 12, 2025 17:42
Implemented smart menu detection using WindowFromPoint:

**How it works:**
1. Use WindowFromPoint to find window at click position
2. Check if window class is "#32768" (standard Windows menu class)
3. Route message to menu window if detected, otherwise to main window

**Benefits:**
- No cursor movement needed
- No window activation/focus changes
- Context menu clicks now work correctly
- Automatic detection, no user intervention required
- Clean PostMessage-only approach

This solves the menu click problem without the side effects of
cursor jumping or window flickering.
Implemented comprehensive popup window matching inspired by Chrome-Manager:

**New Functions:**
1. `FindPopupWindows(pid)` - Finds all popup windows (menus, tooltips, etc.) for a process
2. `FindMatchingPopup(...)` - Matches popup windows between master and slave using relative positioning
3. `SendMouseEventWithPopupMatching(...)` - Sends mouse events with intelligent popup matching

**Matching Algorithm:**
- Calculates popup position relative to parent window
- Uses Manhattan distance to find best matching popup in slave process
- Handles both popup clicks and normal window clicks

**How It Works:**
1. Check if click is on a popup in master window
2. Calculate popup's relative position to master main window
3. Find popup with closest relative position in slave process
4. Convert click coordinates to popup-relative coordinates
5. Send event to matched slave popup window

This enables proper synchronization of:
- Context menu clicks
- Extension popup interactions
- Tooltip clicks
- Any other popup UI elements

Resolves issue where context menu clicks in master didn't trigger
in slave windows due to popup windows being separate HWNDs.
Updated handleMouseDown and handleMouseUp to use the new
sendMouseEventWithPopupMatching API instead of simple coordinate mapping.

**Changes:**
- Removed manual relative position calculation for clicks
- Now passing master PID to enable popup window matching
- Directly uses master window coordinates (x, y)
- C++ layer handles all popup detection and matching logic

**Benefits:**
- Context menu clicks now properly sync to slave windows
- Extension popup interactions work correctly
- Automatic matching of corresponding popups across windows
- Cleaner TypeScript code, complexity moved to C++ layer

This completes the popup window synchronization feature,
enabling proper cross-window interaction with menus and popups.
Added comprehensive logging to troubleshoot right-click menu issues:

**TypeScript layer:**
- Log every mouse down/up event with position and button info
- Log when sending events to each slave window
- Shows master PID, slave PID, and event type

**C++ layer:**
- Log number of popup windows found for master and slave
- Log when click is detected on a popup window
- Use OutputDebugStringA for Windows DebugView integration

This will help diagnose:
1. Why slave windows don't show context menus
2. Why master window shows "extra layer" in menus
3. Whether popup windows are being detected correctly
4. Event timing and coordination issues
For right-click events, temporarily move cursor to target position:
- Chrome uses GetCursorPos() to determine context menu position
- We need actual cursor at target location when menu is triggered
- rightdown: Move cursor to slave window position, send event
- rightup: Move cursor to position, send event, restore cursor

This ensures context menus appear at correct positions in slave windows
instead of all appearing at master window position.

Note: With multiple slave windows, cursor will jump between positions
briefly, but this is necessary for proper menu positioning.
Right-click context menu sync is too complex and causes issues:
- Menus appear at wrong positions
- Cursor jumping between windows is disruptive
- Multiple layers of menus appear

Changes:
- Skip all right-click events (button 2) in handleMouseDown/Up
- Only sync left-click events (button 1)
- Extension popup clicks still work via popup matching API
- Users can right-click manually in windows that need context menus

This simplification improves reliability and user experience.
The popup matching API remains for extension popup window synchronization.
Added IsProcessWindowActive API to check if master window is foreground:
- Windows: Use GetForegroundWindow + GetWindowThreadProcessId
- macOS: Use NSWorkspace frontmostApplication

Benefits:
- More accurate than cursor position check alone
- Prevents syncing when user has switched to another application
- Properly handles window focus states

Also re-enabled right-click synchronization now that we have:
1. Popup window matching API for extension popups
2. Cursor movement for menu positioning
3. Active window check to prevent unwanted syncing

The combination of these features should make right-click sync more reliable.
Remove cursor movement logic for right-click events.
Right-click menus require cursor positioning but moving the cursor
disrupts user experience too much.

Users should right-click manually in windows that need context menus.
Left-click synchronization still works perfectly with popup matching API.
…ization

Changes:
- Move cursor to target window position for right-click events
- Ensure Chrome's GetCursorPos() returns correct position for menu display
- Restore cursor to original position after event is sent
- Strategy: Move -> Send message -> Restore (for each slave window)
- Left-click events remain unchanged (no cursor movement)

Technical details:
- Added cursor position save/restore logic in SendMouseEventWithPopupMatching
- For rightdown: Move cursor -> Send WM_RBUTTONDOWN -> Sleep 2ms -> Restore
- For rightup: Move cursor -> Send WM_RBUTTONUP -> Sleep 5ms -> Restore
- Longer delay for rightup allows context menu to be triggered

Build fixes:
- Fixed GetMonitors() scope issues with forward declarations
- Removed throw statements (exceptions disabled in build config)
- Added Linux stub implementation for cross-platform compilation
Issues with previous implementation:
- PostMessage is async - message queued but not processed immediately
- Delays too short (2-5ms) - Chrome hasn't processed event before cursor restored
- Result: Context menus appeared at master window position instead of slave

Improvements:
1. Use SendMessage (synchronous) instead of PostMessage for right-click
   - Ensures message is processed before function returns

2. Increased delays:
   - rightdown: Move -> Sleep(15ms) -> SendMessage -> Sleep(10ms) -> Restore
   - rightup: Move -> Sleep(15ms) -> SendMessage -> Sleep(50ms) -> Restore
   - Total: ~25ms for rightdown, ~65ms for rightup per window

3. Each window processes independently:
   - Saves original cursor position
   - Moves to target, waits, sends, waits, restores
   - Prevents cursor position confusion between windows

Trade-offs:
- Longer visible cursor movement (~90ms per window)
- More noticeable with multiple slave windows
- But ensures Chrome's GetCursorPos() returns correct position for menus
…eScript layer

Key changes:
1. Calculate slave window coordinates in TypeScript using relative positioning
2. Use simple sendMouseEvent instead of sendMouseEventWithPopupMatching
3. No cursor movement required

Benefits:
- Each slave window receives events in its own coordinate system
- Chrome displays context menu in each window (not overlapping on master)
- No cursor flickering or movement
- Simpler and more efficient

Comparison with old uiohook-napi version:
- Old: TypeScript calculates slave coords -> C++ converts to client coords -> PostMessage
- Current: Same approach, just using @tkomde/iohook instead of uiohook-napi

Technical details:
- calculateRelativePosition(): Get click position as ratio (0-1) in master window
- applyToSlaveWindow(): Apply same ratio to slave window -> screen coordinates
- SendMouseEvent (C++): Convert screen to client coords -> PostMessage
- No SetCursorPos calls = no flickering

Note: Context menus will appear in each slave window, though position may not
be exact due to Chrome's GetCursorPos() call, but they won't overlap on master.
Problem:
- Wheel events were sent to slave windows but didn't scroll
- SendWheelEvent used GetCursorPos() which returned master window cursor position
- WM_MOUSEWHEEL requires screen coordinates in lParam, but cursor was not in slave window

Solution:
1. TypeScript layer calculates slave window coordinates using relative positioning
2. Pass calculated coordinates (x, y) to sendWheelEvent
3. C++ layer uses provided coordinates instead of GetCursorPos()

Changes:
- multi-window-sync-service.ts: Calculate slave position and pass to sendWheelEvent
- window-addon.cpp: Accept optional x, y parameters (backward compatible)

Technical details:
- sendWheelEvent(pid, deltaX, deltaY, x, y)
- If x, y not provided, falls back to GetCursorPos() (backward compatible)
- WM_MOUSEWHEEL lParam now contains correct slave window coordinates
- Each slave window receives scroll event with its own coordinate system

Result:
- Wheel scrolling now works in all slave windows
- No cursor movement required
- Consistent with mouse click event handling
Problem:
- Mousemove events are throttled (10ms + 2px distance)
- When user slowly moves cursor to hover-sensitive UI elements (e.g., Chrome's "+" tab button)
- Many small movements are skipped due to throttling
- Slave windows don't receive complete movement trajectory
- Slave window's mouse position is inaccurate when click happens
- Click on wrong position → UI element not activated (e.g., new tab not created)

Solution:
Send a mousemove event immediately before each mousedown event:
1. Send mousemove to update slave window's cursor position
2. Wait 5ms for hover states to update (UI elements need time to respond)
3. Send the actual mousedown event

Benefits:
- Ensures slave windows have accurate mouse position before clicks
- Hover-dependent UI elements work correctly (buttons appear, etc.)
- No need to reduce mousemove throttling (maintains performance)
- Fixes specific issue with Chrome's "+" tab button and similar elements

Technical details:
- Uses setTimeout with 5ms delay between mousemove and mousedown
- Each slave window processes events independently
- Minimal performance impact (only adds one extra event per click)

Example scenario (now fixed):
- User hovers near Chrome's "+" button → Main window shows button
- Throttling skips some moves → Slave window doesn't show button
- User clicks → Mousemove sent first → Button appears in slave
- After 5ms → Click sent → New tab created in slave window
…onization

Problem:
- Mousemove throttling (10ms + 2px) improves performance but skips many small movements
- When user slowly moves to hover-sensitive UI (e.g., Chrome's "+" tab button),
  slave windows don't receive complete movement trajectory
- Slave window mouse position is inaccurate, hover effects don't trigger
- Previous fix only synced on click, but user wants hover to work without clicking

Solution: Mouse Settle Debounce
- Every mousemove event resets a 50ms debounce timer
- When timer fires (mouse has stopped moving for 50ms), sync final position
- This ensures accurate hover states when user pauses cursor

Implementation:
1. Add mouseMoveDebounceTimer and MOUSE_SETTLE_DELAY_MS (50ms)
2. On each mousemove:
   - Clear previous timer
   - Process throttled movement (if passes 10ms + 2px check)
   - Set new timer to sync position after 50ms
3. Timer callback: Send final position to all slave windows
4. Cleanup: Clear timer in stopSync()

Benefits:
✓ Hover states sync automatically when mouse settles
✓ Maintains performance with throttling during rapid movement
✓ No need to reduce throttling thresholds
✓ Works for all hover-sensitive elements (buttons, menus, tooltips, etc.)
✓ No extra sync on click needed (removed setTimeout in mousedown)

Example scenario:
1. User moves mouse quickly → Many moves throttled → Good performance
2. User slows down near "+" button → Moves still throttled → Position slightly off
3. User stops moving → 50ms timer fires → Final position synced
4. Slave window "+" button appears with hover state ✓
5. User clicks → Click works correctly ✓

Technical details:
- Debounce pattern prevents flooding during continuous movement
- Only syncs once after movement stops
- Timer cleared on new movement, preventing redundant syncs
- Memory leak prevented by clearing timer in stopSync()
…e windows

- Remove continuous mouse position synchronization from handleMouseMove
- Keep mousemove listener only for focus tracking (needed for keyboard sync)
- Position now synced only before clicks/scrolls for accuracy
- Removes ~100 events/sec × N windows overhead
- Eliminates lag reported with 6 windows
- Replace sidebar layout with horizontal card-based design
- Add three control cards: Master Window, Sync Control, Window Arrangement
- Move sync features to main panel with toggle switches
- Add statistics display for selected windows and slave count
- Improve visual hierarchy with icons and colors
- Add role tags with icons (Master/Slave/Ready)
- Move advanced settings to separate bottom card
- Implement responsive layout for better mobile support
- Remove nested collapse components for cleaner UX
Reference: assets/ads-example.png

Major changes:
- Reorganize layout to left-right split (16:8 ratio)
- Move sync controls to top toolbar (Start/Stop + Selected count)
- Use Tabs in right panel (Sync Control / Window Arrange)
- Add arrange mode selection (Grid Tile / Cascade)
- Improve status display with animated Syncing tag
- Add keyboard shortcuts in button labels
- Simplify control panel hierarchy with tab navigation
- Remove redundant cards, use cleaner tab-based organization
Major improvements:
- Add complete Chinese and English translations for sync page
- Replace all hardcoded text with translation keys
- Optimize layout spacing and margins using inline styles
- Improve consistency with other pages (windows, detail)
- Use translation interpolation for dynamic content
- Adjust padding and spacing for better visual hierarchy
- Fix TypeScript type error in map function
- Ensure all message notifications use translations

Translations added:
- sync_start, sync_stop, sync_selected
- sync_active_title, sync_active_desc
- sync_opened_windows, sync_master_info
- sync_control_panel, sync_control, sync_features
- sync_mouse, sync_keyboard, sync_wheel
- sync_advanced, sync_wheel_throttle, sync_cdp_sync
- sync_window_arrange, sync_display, sync_arrange_mode
- sync_grid_tile, sync_cascade, sync_arrange_button
- sync_status_* (syncing, master, ready)
- sync_msg_* (all message notifications)
Major improvements:
- Remove arrange mode selection (only Grid Tile now)
- Remove advanced settings (CDP sync, wheel throttle)
- Add custom scrollbar styles globally
- Fix i18n interpolation using built-in feature
- Change "重启同步" to "停止同步" for clarity
- Add icon-text spacing with CSS (8px margin)
- Simplify sync control tab (only essential features)
- Improve visual consistency and cleaner layout

UI changes:
- Removed: arrange mode radio buttons
- Removed: CDP scroll sync toggle
- Removed: Wheel throttle input
- Removed: CDP interval input
- Added: Global scrollbar styling
- Added: Icon spacing rules
- Fixed: Use i18n interpolation parameters correctly
Changed all interpolation placeholders from {variable} to {{variable}}
to match i18next standard syntax. This fixes display issues where
variables like {count} and {name} were shown as literal strings
instead of their actual values.

Fixed placeholders:
- {{count}} in sync_active_desc, sync_master_info, sync_msg_started
- {{name}} in sync_master_info
- {{error}} in sync_msg_start_failed

Both English and Chinese translations updated.
Problem:
- Popup windows could receive mouse click sync
- But keyboard input was not syncing to popup windows
- Root cause: isMouseInMaster was false when mouse was in popup

Solution:
- Added isMouseInMasterOrExtensionWindow() method
- Checks if mouse is in master window OR its extension/popup windows
- Updated handleMouseMove to use new method for focus tracking
- Now keyboard events sync correctly when typing in popups

How it works:
1. Extension windows are tracked in extensionWindows Map
2. When mouse moves, check master window + all its extension windows
3. If mouse in any of them, set isMouseInMaster = true
4. Keyboard events now sync when focus is in popup windows

Technical details:
- Added null check for masterWindowPid before accessing Map
- Extension window detection uses same bounds checking as master
- Debug logging added for extension window detection
Problem:
- Ctrl+C and other shortcuts were syncing to all slave windows
- This caused unwanted duplicate actions across windows
- User wants shortcuts like Ctrl+C to only execute on master

Solution:
- Added shouldIgnoreKeyboardEvent() method to filter shortcuts
- Filters Ctrl+C (Copy) and other common shortcuts
- Shortcuts execute only on master window, not slaves

Filtered shortcuts:
- Ctrl+C (Copy) - keycode 67
- Ctrl+V (Paste) - 86
- Ctrl+X (Cut) - 88
- Ctrl+A (Select All) - 65
- Ctrl+Z (Undo) - 90
- Ctrl+Y (Redo) - 89
- Ctrl+S (Save) - 83
- Ctrl+F (Find) - 70
- Ctrl+H (History) - 72
- Ctrl+N (New) - 78
- Ctrl+T (New Tab) - 84
- Ctrl+W (Close Tab) - 87
- Ctrl+R (Refresh) - 82

How it works:
- Check ctrlKey flag and keycode before syncing
- Log ignored shortcuts for debugging
- Only filters Ctrl combinations (no Alt/Shift modifiers)
- Added proper type annotation for ignoredKeycodes object
Added getAllWindows() native method and implemented detectExtensionWindows()
to populate extensionWindows Map, enabling keyboard synchronization in popup
and extension windows. The extension window monitoring runs every 1 second
to track newly opened popups.

Changes:
- Added getAllWindows() method in native addon to return all windows including extensions
- Implemented detectExtensionWindows() to populate extensionWindows Map
- Fixed ExtensionWindow interface to use inline bounds type instead of WindowBounds
- Extension windows are now properly tracked for master and slave processes
- Keyboard events in popup windows are now synchronized to slave windows
@zmzimpl zmzimpl merged commit a1b24b5 into main Nov 17, 2025
6 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants