copyhub-cli 1.0.9 → 1.1.0
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.
- package/package.json +1 -1
- package/ui/main.mjs +173 -20
package/package.json
CHANGED
package/ui/main.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
Menu,
|
|
12
12
|
nativeImage,
|
|
13
13
|
systemPreferences,
|
|
14
|
+
powerMonitor,
|
|
14
15
|
} from 'electron';
|
|
15
16
|
import { loadCopyhubEnv } from '../src/load-env.js';
|
|
16
17
|
import { readRecentHistorySync } from '../src/storage.js';
|
|
@@ -132,6 +133,28 @@ function applyAlwaysOnTopStack(w) {
|
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
/** Bring app + overlay forward (macOS often needs app focus for always-on-top popups after idle). */
|
|
137
|
+
function bringOverlayToFront(w) {
|
|
138
|
+
if (!w || w.isDestroyed()) return;
|
|
139
|
+
applyAlwaysOnTopStack(w);
|
|
140
|
+
if (process.platform === 'darwin') {
|
|
141
|
+
try {
|
|
142
|
+
app.focus({ steal: true });
|
|
143
|
+
} catch {
|
|
144
|
+
try {
|
|
145
|
+
app.focus();
|
|
146
|
+
} catch {
|
|
147
|
+
/* ignore */
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
w.focus();
|
|
153
|
+
} catch {
|
|
154
|
+
/* ignore */
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
135
158
|
function createWindow() {
|
|
136
159
|
win = new BrowserWindow({
|
|
137
160
|
width: OVERLAY_WIDTH,
|
|
@@ -154,6 +177,19 @@ function createWindow() {
|
|
|
154
177
|
|
|
155
178
|
win.loadFile(path.join(__dirname, 'renderer', 'index.html'));
|
|
156
179
|
|
|
180
|
+
win.webContents.on('render-process-gone', (_event, details) => {
|
|
181
|
+
console.warn('[CopyHub overlay] Renderer process ended:', details.reason);
|
|
182
|
+
if (!win || win.isDestroyed()) return;
|
|
183
|
+
try {
|
|
184
|
+
win.webContents.reload();
|
|
185
|
+
} catch (e) {
|
|
186
|
+
console.warn(
|
|
187
|
+
'[CopyHub overlay] Reload after renderer exit failed:',
|
|
188
|
+
/** @type {Error} */ (e).message,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
157
193
|
win.on('show', () => {
|
|
158
194
|
applyAlwaysOnTopStack(win);
|
|
159
195
|
});
|
|
@@ -185,8 +221,7 @@ function createWindow() {
|
|
|
185
221
|
}
|
|
186
222
|
placeWindowAtCursor(win);
|
|
187
223
|
win.show();
|
|
188
|
-
|
|
189
|
-
win.focus();
|
|
224
|
+
bringOverlayToFront(win);
|
|
190
225
|
win.webContents.send('overlay:open');
|
|
191
226
|
setTimeout(() => applyAlwaysOnTopStack(win), 120);
|
|
192
227
|
armBlurHideEnable(win);
|
|
@@ -218,18 +253,50 @@ function placeWindowAtCursor(w) {
|
|
|
218
253
|
}
|
|
219
254
|
|
|
220
255
|
function toggleOverlay() {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
256
|
+
try {
|
|
257
|
+
if (!win || win.isDestroyed()) {
|
|
258
|
+
blurHideEnabled = false;
|
|
259
|
+
createWindow();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (win.isVisible()) {
|
|
263
|
+
win.hide();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
225
266
|
blurHideEnabled = false;
|
|
226
267
|
placeWindowAtCursor(win);
|
|
227
268
|
win.show();
|
|
228
|
-
|
|
229
|
-
win.
|
|
230
|
-
|
|
269
|
+
bringOverlayToFront(win);
|
|
270
|
+
const wc = win.webContents;
|
|
271
|
+
if (!wc.isDestroyed()) {
|
|
272
|
+
wc.send('overlay:open');
|
|
273
|
+
}
|
|
231
274
|
setTimeout(() => applyAlwaysOnTopStack(win), 120);
|
|
232
275
|
armBlurHideEnable(win);
|
|
276
|
+
} catch (e) {
|
|
277
|
+
const msg = /** @type {Error} */ (e).message || String(e);
|
|
278
|
+
console.warn('[CopyHub overlay] toggle failed:', msg);
|
|
279
|
+
try {
|
|
280
|
+
if (win && !win.isDestroyed()) {
|
|
281
|
+
const wc = win.webContents;
|
|
282
|
+
if (!wc.isDestroyed()) {
|
|
283
|
+
wc.reload();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
} catch {
|
|
288
|
+
/* recreate below */
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
if (win && !win.isDestroyed()) {
|
|
292
|
+
win.destroy();
|
|
293
|
+
}
|
|
294
|
+
} catch {
|
|
295
|
+
/* ignore */
|
|
296
|
+
}
|
|
297
|
+
win = null;
|
|
298
|
+
blurHideEnabled = false;
|
|
299
|
+
createWindow();
|
|
233
300
|
}
|
|
234
301
|
}
|
|
235
302
|
|
|
@@ -289,6 +356,70 @@ function registerHotkeys() {
|
|
|
289
356
|
return { accelerator: '', usedFallback: false };
|
|
290
357
|
}
|
|
291
358
|
|
|
359
|
+
/**
|
|
360
|
+
* macOS often drops Electron globalShortcut listeners (sleep/wake or while running); re-register.
|
|
361
|
+
* @param {{ silentSuccess?: boolean }} [opts] — omit success log for periodic refresh noise
|
|
362
|
+
*/
|
|
363
|
+
function reregisterOverlayHotkeys(opts = {}) {
|
|
364
|
+
const silentSuccess = Boolean(opts.silentSuccess);
|
|
365
|
+
const prev = overlayHotkeyMeta.accelerator;
|
|
366
|
+
if (prev) {
|
|
367
|
+
try {
|
|
368
|
+
globalShortcut.unregister(prev);
|
|
369
|
+
} catch {
|
|
370
|
+
/* ignore */
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const { accelerator, usedFallback } = registerHotkeys();
|
|
374
|
+
overlayHotkeyMeta = {
|
|
375
|
+
accelerator,
|
|
376
|
+
usedFallback,
|
|
377
|
+
requestedRaw:
|
|
378
|
+
process.env.COPYHUB_OVERLAY_ACCELERATOR?.trim() ||
|
|
379
|
+
loadOverlayAcceleratorFromConfigSync() ||
|
|
380
|
+
'',
|
|
381
|
+
};
|
|
382
|
+
if (accelerator) {
|
|
383
|
+
if (!silentSuccess) {
|
|
384
|
+
console.log('[CopyHub overlay] Shortcut active again:', accelerator);
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
console.warn(
|
|
388
|
+
'[CopyHub overlay] Shortcut re-registration failed — open from menu bar or restart CopyHub.',
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
refreshTrayContextMenu();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Detect shortcut unregistered while process still runs (common on macOS without sleep). */
|
|
395
|
+
function startGlobalShortcutHealthMonitor() {
|
|
396
|
+
const intervalMs = process.platform === 'darwin' ? 45_000 : 120_000;
|
|
397
|
+
setInterval(() => {
|
|
398
|
+
const acc = overlayHotkeyMeta.accelerator;
|
|
399
|
+
if (!acc || !gotLock) return;
|
|
400
|
+
try {
|
|
401
|
+
if (!globalShortcut.isRegistered(acc)) {
|
|
402
|
+
console.warn('[CopyHub overlay] Global shortcut registration lost — repairing.');
|
|
403
|
+
reregisterOverlayHotkeys({ silentSuccess: false });
|
|
404
|
+
}
|
|
405
|
+
} catch (e) {
|
|
406
|
+
console.warn(
|
|
407
|
+
'[CopyHub overlay] Shortcut health check failed:',
|
|
408
|
+
/** @type {Error} */ (e).message,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
}, intervalMs);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Proactive refresh: Electron/macOS can leave shortcuts broken while isRegistered stays true. */
|
|
415
|
+
function startDarwinShortcutKeepalive() {
|
|
416
|
+
if (process.platform !== 'darwin') return;
|
|
417
|
+
const periodMs = 8 * 60 * 1000;
|
|
418
|
+
setInterval(() => {
|
|
419
|
+
reregisterOverlayHotkeys({ silentSuccess: true });
|
|
420
|
+
}, periodMs);
|
|
421
|
+
}
|
|
422
|
+
|
|
292
423
|
function mergeHistoryForOverlay(localItems, sheetItems, cap) {
|
|
293
424
|
const seen = new Set();
|
|
294
425
|
/** @type {typeof localItems} */
|
|
@@ -547,23 +678,37 @@ function registerIpc() {
|
|
|
547
678
|
});
|
|
548
679
|
}
|
|
549
680
|
|
|
681
|
+
function buildTrayMenuTemplate() {
|
|
682
|
+
const accLabel = overlayHotkeyMeta.accelerator
|
|
683
|
+
? `Shortcut: ${overlayHotkeyMeta.accelerator}`
|
|
684
|
+
: 'Shortcut: (see terminal)';
|
|
685
|
+
return [
|
|
686
|
+
{ label: accLabel, enabled: false },
|
|
687
|
+
{ label: 'Open history (always on top)', click: () => toggleOverlay() },
|
|
688
|
+
{ type: 'separator' },
|
|
689
|
+
{ label: 'Quit', click: () => app.quit() },
|
|
690
|
+
];
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function refreshTrayContextMenu() {
|
|
694
|
+
if (!tray) return;
|
|
695
|
+
try {
|
|
696
|
+
tray.setContextMenu(Menu.buildFromTemplate(buildTrayMenuTemplate()));
|
|
697
|
+
} catch (e) {
|
|
698
|
+
console.warn(
|
|
699
|
+
'[CopyHub overlay] Tray menu refresh failed:',
|
|
700
|
+
/** @type {Error} */ (e).message,
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
550
705
|
function registerTray() {
|
|
551
706
|
const icon = nativeImage.createFromDataURL(
|
|
552
707
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhn1IGOMJoAmBGOSMDEwMmABWDWHJjBCSpBKGBSDjBAAAeoRBIEs/x0AAAAASUVORK5CYII=',
|
|
553
708
|
);
|
|
554
709
|
tray = new Tray(icon);
|
|
555
710
|
tray.setToolTip('CopyHub overlay');
|
|
556
|
-
|
|
557
|
-
? `Shortcut: ${overlayHotkeyMeta.accelerator}`
|
|
558
|
-
: 'Shortcut: (see terminal)';
|
|
559
|
-
tray.setContextMenu(
|
|
560
|
-
Menu.buildFromTemplate([
|
|
561
|
-
{ label: accLabel, enabled: false },
|
|
562
|
-
{ label: 'Open history (always on top)', click: () => toggleOverlay() },
|
|
563
|
-
{ type: 'separator' },
|
|
564
|
-
{ label: 'Quit', click: () => app.quit() },
|
|
565
|
-
]),
|
|
566
|
-
);
|
|
711
|
+
tray.setContextMenu(Menu.buildFromTemplate(buildTrayMenuTemplate()));
|
|
567
712
|
tray.on('click', () => toggleOverlay());
|
|
568
713
|
}
|
|
569
714
|
|
|
@@ -617,6 +762,14 @@ if (gotLock) {
|
|
|
617
762
|
} catch (e) {
|
|
618
763
|
console.warn('Could not create system tray icon:', /** @type {Error} */ (e).message);
|
|
619
764
|
}
|
|
765
|
+
|
|
766
|
+
/** Delay slightly so macOS finishes restoring input / accessibility after wake. */
|
|
767
|
+
powerMonitor.on('resume', () => {
|
|
768
|
+
setTimeout(() => reregisterOverlayHotkeys({ silentSuccess: false }), 400);
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
startGlobalShortcutHealthMonitor();
|
|
772
|
+
startDarwinShortcutKeepalive();
|
|
620
773
|
});
|
|
621
774
|
|
|
622
775
|
app.on('will-quit', () => {
|