@tobq/boardclip 2.1.113 → 2.1.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,12 +22,13 @@
22
22
 
23
23
  const { parentPort, workerData } = require('worker_threads');
24
24
  const koffi = require('koffi');
25
+ // Which presses the hook claims, and the shared-state layout main agrees on.
26
+ const { STATE, VK, createHookClaimer, modsFrom } = require('./windows-hook-policy');
25
27
 
26
- // --- Shared state (worker reads, main writes) ---
27
- // Layout: [popupVisible, slot1, slot2, ..., slot9, reserved, ...]
28
+ // --- Shared state (worker reads, main writes; layout = STATE) ---
28
29
  const sharedState = workerData && workerData.sharedStateBuffer
29
30
  ? new Uint8Array(workerData.sharedStateBuffer)
30
- : new Uint8Array(16);
31
+ : new Uint8Array(STATE.SIZE);
31
32
 
32
33
  // --- Win32 constants ---
33
34
  const WH_KEYBOARD_LL = 13;
@@ -35,12 +36,6 @@ const WM_KEYDOWN = 0x0100;
35
36
  const WM_KEYUP = 0x0101;
36
37
  const WM_SYSKEYDOWN = 0x0104;
37
38
  const WM_SYSKEYUP = 0x0105;
38
- const VK_LWIN = 0x5B;
39
- const VK_RWIN = 0x5C;
40
- const VK_CONTROL = 0x11;
41
- const VK_V = 0x56;
42
- const VK_NUMPAD1 = 0x61;
43
- const VK_NUMPAD9 = 0x69;
44
39
  const KEYEVENTF_KEYUP = 0x0002;
45
40
 
46
41
  // --- koffi types ---
@@ -107,29 +102,21 @@ const keybd_event = user32.func(
107
102
  'void __stdcall keybd_event(uint8 bVk, uint8 bScan, uint32 dwFlags, uintptr_t dwExtraInfo)'
108
103
  );
109
104
 
110
- let winVChordActive = false;
111
- let winVChordConsumed = false;
112
-
113
- // Auto-repeat suppression for numpad quick-paste. When a key is physically
114
- // held, Windows emits repeated WM_KEYDOWN with no intervening WM_KEYUP. Without
115
- // tracking this, one held (or lag-stretched) press fires the paste many times.
116
- // numpadHeld = keys currently down; numpadIntercepted = keys whose keydown we
117
- // swallowed, so we also swallow their paired keyup.
118
- const numpadHeld = new Set();
119
- const numpadIntercepted = new Set();
120
-
121
- function isWinHeld() {
122
- return (GetAsyncKeyState(VK_LWIN) & 0x8000) !== 0 ||
123
- (GetAsyncKeyState(VK_RWIN) & 0x8000) !== 0;
124
- }
105
+ const isKeyDown = (vk) => (GetAsyncKeyState(vk) & 0x8000) !== 0;
106
+ // Win+V, Win+N and Numpad1-9: each claimed only with exactly its modifiers,
107
+ // one verdict per press (lib/windows-hook-policy.js).
108
+ const claimer = createHookClaimer(sharedState, {
109
+ isKeyDown,
110
+ readMods: (flags) => modsFrom(isKeyDown, flags),
111
+ });
125
112
 
126
113
  function markWinKeyUsed() {
127
- // If V is swallowed, Windows can still react to the original Win release.
114
+ // If a key is swallowed while Win is held, Windows can still react to the original Win release.
128
115
  // A tiny Ctrl tap marks the Win key as used while allowing the real Win-up
129
116
  // through, so we avoid both Clipboard History and stuck modifier state.
130
117
  try {
131
- keybd_event(VK_CONTROL, 0, 0, 0);
132
- keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0);
118
+ keybd_event(VK.CONTROL, 0, 0, 0);
119
+ keybd_event(VK.CONTROL, 0, KEYEVENTF_KEYUP, 0);
133
120
  } catch {}
134
121
  }
135
122
 
@@ -149,56 +136,10 @@ const hookCallback = koffi.register((nCode, wParam, lParamExt) => {
149
136
  return CallNextHookEx(null, nCode, wParam, lParamExt);
150
137
  }
151
138
 
152
- const isWinKey = vk === VK_LWIN || vk === VK_RWIN;
153
-
154
- // Win+V → show popup. Checked before Windows Clipboard History can.
155
- if (isWinKey && keyUp && winVChordConsumed) {
156
- markWinKeyUsed();
157
- winVChordConsumed = false;
158
- }
159
-
160
- if (vk === VK_V) {
161
- if (keyDown) {
162
- if (isWinHeld() || winVChordActive) {
163
- if (!winVChordActive) {
164
- winVChordActive = true;
165
- winVChordConsumed = true;
166
- parentPort.postMessage({ type: 'show' });
167
- }
168
- return 1;
169
- }
170
- } else if (winVChordActive) {
171
- winVChordActive = false;
172
- return 1;
173
- }
174
- }
175
-
176
- // Plain Numpad1-9 -> quick-paste when a slot is assigned and the popup
177
- // is closed. When the popup is visible, pass through so the search box
178
- // can accept normal numpad typing. Auto-repeat (held key) fires the paste
179
- // exactly once — repeats are swallowed but not re-dispatched.
180
- if (vk >= VK_NUMPAD1 && vk <= VK_NUMPAD9) {
181
- const slot = vk - VK_NUMPAD1 + 1;
182
- if (keyDown) {
183
- const popupVisible = sharedState[0] === 1;
184
- const slotAssigned = sharedState[slot] === 1;
185
- if (!popupVisible && slotAssigned) {
186
- if (numpadHeld.has(vk)) return 1; // auto-repeat: swallow, don't re-fire
187
- numpadHeld.add(vk);
188
- numpadIntercepted.add(vk);
189
- parentPort.postMessage({ type: 'numpad', slot });
190
- return 1;
191
- }
192
- // Not intercepting this press (popup open or slot unassigned): let it
193
- // type normally and don't track it.
194
- } else if (keyUp) {
195
- numpadHeld.delete(vk);
196
- if (numpadIntercepted.has(vk)) {
197
- numpadIntercepted.delete(vk);
198
- return 1; // swallow the keyup paired with a swallowed keydown
199
- }
200
- }
201
- }
139
+ const verdict = claimer.event(vk, keyDown, kb.flags);
140
+ if (verdict.markWinUsed) markWinKeyUsed();
141
+ if (verdict.post) parentPort.postMessage(verdict.post);
142
+ if (verdict.swallow) return 1;
202
143
  }
203
144
  } catch (err) {
204
145
  // Never let JS exceptions escape the hook — Windows will stall the
@@ -4,34 +4,37 @@
4
4
  // On Windows we can't use Electron's `globalShortcut.register('Super+V')`
5
5
  // because Windows Clipboard History owns Win+V at the RegisterHotKey layer
6
6
  // and silently rejects our registration. This module installs a
7
- // WH_KEYBOARD_LL hook on a dedicated worker thread which intercepts Win+V
8
- // and numpad keys *before* Windows Clipboard History sees them.
7
+ // WH_KEYBOARD_LL hook on a dedicated worker thread which intercepts Win+V,
8
+ // Win+N (new note, while that is the shortcut) and numpad keys *before*
9
+ // Windows Clipboard History / the notification panel see them.
9
10
  //
10
11
  // See lib/windows-hook-worker.js for the hook implementation and the
11
12
  // rationale for running it in a worker.
12
13
  //
13
- // Shared state (popup visible + which numpad slots are assigned) is passed
14
- // via a SharedArrayBuffer because the worker is synchronously blocked in
14
+ // Shared state (popup visible, which numpad slots are assigned, Win+N on;
15
+ // layout = STATE in lib/windows-hook-policy.js) is passed via a
16
+ // SharedArrayBuffer because the worker is synchronously blocked in
15
17
  // GetMessageW and can't process postMessage events from its JS event loop.
16
18
 
17
19
  const { Worker } = require('worker_threads');
18
20
  const path = require('path');
21
+ const { STATE } = require('./windows-hook-policy');
19
22
 
20
23
  let currentHook = null;
21
24
 
22
- function install({ onShowPopup, onNumpadPaste }) {
25
+ function install({ onShowPopup, onNumpadPaste, onNewNote }) {
23
26
  if (process.platform !== 'win32') {
24
27
  // Non-Windows platforms use Electron's globalShortcut; no-op API here.
25
28
  return {
26
29
  setPopupVisible() {},
27
30
  setSlotAssignments() {},
31
+ setNewNoteHotkey() {},
28
32
  uninstall() {},
29
33
  };
30
34
  }
31
35
  if (currentHook) return currentHook;
32
36
 
33
- // Layout: [popupVisible, slot1, slot2, ..., slot9, reserved...]
34
- const sharedStateBuffer = new SharedArrayBuffer(16);
37
+ const sharedStateBuffer = new SharedArrayBuffer(STATE.SIZE);
35
38
  const sharedState = new Uint8Array(sharedStateBuffer);
36
39
 
37
40
  const worker = new Worker(path.join(__dirname, 'windows-hook-worker.js'), {
@@ -46,6 +49,9 @@ function install({ onShowPopup, onNumpadPaste }) {
46
49
  case 'numpad':
47
50
  try { onNumpadPaste(msg.slot); } catch (e) { console.error('[windows-hook] onNumpadPaste:', e); }
48
51
  break;
52
+ case 'new-note':
53
+ try { if (onNewNote) onNewNote(); } catch (e) { console.error('[windows-hook] onNewNote:', e); }
54
+ break;
49
55
  case 'ready':
50
56
  // Hook installed successfully — no action needed.
51
57
  break;
@@ -67,14 +73,19 @@ function install({ onShowPopup, onNumpadPaste }) {
67
73
 
68
74
  currentHook = {
69
75
  setPopupVisible(visible) {
70
- sharedState[0] = visible ? 1 : 0;
76
+ sharedState[STATE.POPUP_VISIBLE] = visible ? 1 : 0;
71
77
  },
72
78
  setSlotAssignments(assignedSet) {
73
79
  // assignedSet: Set<number> of assigned numpad slot numbers (1-9)
74
80
  for (let n = 1; n <= 9; n++) {
75
- sharedState[n] = assignedSet && assignedSet.has(n) ? 1 : 0;
81
+ sharedState[STATE.SLOT_FIRST + n - 1] = assignedSet && assignedSet.has(n) ? 1 : 0;
76
82
  }
77
83
  },
84
+ // Win+N opens a new note only while it is the new-note shortcut (not
85
+ // while it is off or another chord is recorded, nor during a recording).
86
+ setNewNoteHotkey(enabled) {
87
+ sharedState[STATE.NEW_NOTE_HOTKEY] = enabled ? 1 : 0;
88
+ },
78
89
  uninstall() {
79
90
  if (!currentHook) return;
80
91
  currentHook = null;