copyhub-cli 1.0.0 → 1.0.1

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/ui/main.mjs CHANGED
@@ -1,331 +1,331 @@
1
- import 'dotenv/config';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import {
5
- app,
6
- BrowserWindow,
7
- globalShortcut,
8
- clipboard,
9
- ipcMain,
10
- screen,
11
- Tray,
12
- Menu,
13
- nativeImage,
14
- } from 'electron';
15
- import { readRecentHistorySync } from '../src/storage.js';
16
- import { loadOverlayAcceleratorFromConfigSync } from '../src/config.js';
17
-
18
- const gotLock = app.requestSingleInstanceLock();
19
- if (!gotLock) {
20
- app.quit();
21
- }
22
-
23
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
-
25
- /** Set to 1 so the window does not hide on blur (only Esc / pick row to copy). */
26
- const STICKY_NO_BLUR = process.env.COPYHUB_OVERLAY_STICKY === '1';
27
-
28
- /** Electron Accelerator: use `Control`, not `Ctrl`; `CommandOrControl` = Ctrl (Win) / Cmd (Mac). */
29
- function normalizeAccelerator(raw) {
30
- if (!raw || typeof raw !== 'string') return '';
31
- let s = raw.trim();
32
- s = s.replace(/\bCtrl\b/gi, 'Control');
33
- s = s.replace(/\bCmd\b/gi, 'Command');
34
- s = s.replace(/\s*\+\s*/g, '+');
35
- return s;
36
- }
37
-
38
- const DEFAULT_ACCEL = 'CommandOrControl+Shift+H';
39
- const HIDE_ON_START = process.env.COPYHUB_OVERLAY_HIDE_ON_START === '1';
40
-
41
- /** Overlay width (~70% of 460px baseline). */
42
- const OVERLAY_WIDTH = Math.round(460 * 0.7);
43
-
44
- /** For UI / IPC: registered shortcut and raw value from .env */
45
- let overlayHotkeyMeta = {
46
- accelerator: '',
47
- usedFallback: false,
48
- requestedRaw: '',
49
- };
50
-
51
- let win = null;
52
- let tray = null;
53
- /** Avoid hiding immediately after show (WM quirks). */
54
- let blurHideEnabled = false;
55
-
56
- /**
57
- * Stay above other apps: screen-saver level (highest in Electron), moveTop, all workspaces.
58
- * @param {BrowserWindow} w
59
- */
60
- function applyAlwaysOnTopStack(w) {
61
- if (!w || w.isDestroyed()) return;
62
- try {
63
- w.setAlwaysOnTop(true, 'screen-saver');
64
- } catch {
65
- try {
66
- w.setAlwaysOnTop(true, 'floating');
67
- } catch {
68
- w.setAlwaysOnTop(true);
69
- }
70
- }
71
- try {
72
- w.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
73
- } catch {
74
- /* unsupported on some Linux builds */
75
- }
76
- try {
77
- if (typeof w.moveTop === 'function') {
78
- w.moveTop();
79
- }
80
- } catch {
81
- /* ignore */
82
- }
83
- }
84
-
85
- function createWindow() {
86
- win = new BrowserWindow({
87
- width: OVERLAY_WIDTH,
88
- height: 540,
89
- alwaysOnTop: true,
90
- show: false,
91
- /** Frameless: no title bar + menu (Windows/macOS). */
92
- frame: false,
93
- roundedCorners: true,
94
- /** Show on taskbar for visibility (COPYHUB_OVERLAY_SKIP_TASKBAR=1 hides from taskbar). */
95
- skipTaskbar: process.env.COPYHUB_OVERLAY_SKIP_TASKBAR === '1',
96
- title: 'CopyHub',
97
- backgroundColor: '#ffffff',
98
- webPreferences: {
99
- preload: path.join(__dirname, 'preload.cjs'),
100
- contextIsolation: true,
101
- nodeIntegration: false,
102
- },
103
- });
104
-
105
- win.loadFile(path.join(__dirname, 'renderer', 'index.html'));
106
-
107
- win.on('show', () => {
108
- applyAlwaysOnTopStack(win);
109
- });
110
-
111
- if (!STICKY_NO_BLUR) {
112
- win.on('blur', () => {
113
- if (!blurHideEnabled) return;
114
- if (win && !win.webContents.isDevToolsOpened()) {
115
- win.hide();
116
- }
117
- });
118
- }
119
-
120
- win.on('close', (e) => {
121
- e.preventDefault();
122
- win?.hide();
123
- });
124
-
125
- win.webContents.on('before-input-event', (_event, input) => {
126
- if (input.type === 'keyDown' && input.key === 'Escape') {
127
- win?.hide();
128
- }
129
- });
130
-
131
- win.once('ready-to-show', () => {
132
- if (HIDE_ON_START) {
133
- blurHideEnabled = true;
134
- return;
135
- }
136
- placeWindowAtCursor(win);
137
- win.show();
138
- applyAlwaysOnTopStack(win);
139
- win.focus();
140
- win.webContents.send('overlay:open');
141
- setTimeout(() => {
142
- applyAlwaysOnTopStack(win);
143
- blurHideEnabled = true;
144
- }, 800);
145
- });
146
- }
147
-
148
- /**
149
- * Position window near cursor (display containing pointer).
150
- * @param {BrowserWindow} w
151
- */
152
- function placeWindowAtCursor(w) {
153
- if (!w || w.isDestroyed()) return;
154
- const point = screen.getCursorScreenPoint();
155
- const display = screen.getDisplayNearestPoint(point);
156
- const { workArea } = display;
157
- const { width, height } = w.getBounds();
158
- const margin = 10;
159
- let x = Math.round(point.x - width / 2);
160
- let y = Math.round(point.y - 40);
161
- x = Math.max(
162
- workArea.x + margin,
163
- Math.min(x, workArea.x + workArea.width - width - margin),
164
- );
165
- y = Math.max(
166
- workArea.y + margin,
167
- Math.min(y, workArea.y + workArea.height - height - margin),
168
- );
169
- w.setPosition(x, y);
170
- }
171
-
172
- function toggleOverlay() {
173
- if (!win) return;
174
- if (win.isVisible()) {
175
- win.hide();
176
- } else {
177
- blurHideEnabled = false;
178
- placeWindowAtCursor(win);
179
- win.show();
180
- applyAlwaysOnTopStack(win);
181
- win.focus();
182
- win.webContents.send('overlay:open');
183
- setTimeout(() => {
184
- applyAlwaysOnTopStack(win);
185
- blurHideEnabled = true;
186
- }, 800);
187
- }
188
- }
189
-
190
- /**
191
- * Register global shortcut: try .env (normalized) then default CommandOrControl+Shift+H.
192
- * @returns {{ accelerator: string, usedFallback: boolean }}
193
- */
194
- function registerHotkeys() {
195
- const raw =
196
- process.env.COPYHUB_OVERLAY_ACCELERATOR?.trim() ||
197
- loadOverlayAcceleratorFromConfigSync();
198
- const candidates = [];
199
- if (raw) {
200
- const n = normalizeAccelerator(raw);
201
- if (n) candidates.push(n);
202
- }
203
- candidates.push(DEFAULT_ACCEL);
204
-
205
- let usedFallback = false;
206
- for (let i = 0; i < candidates.length; i++) {
207
- const acc = candidates[i];
208
- try {
209
- if (globalShortcut.register(acc, () => toggleOverlay())) {
210
- if (i > 0) usedFallback = true;
211
- return { accelerator: acc, usedFallback };
212
- }
213
- } catch (e) {
214
- console.warn('Invalid accelerator:', acc, /** @type {Error} */ (e).message);
215
- }
216
- try {
217
- globalShortcut.unregister(acc);
218
- } catch {
219
- /* ignore */
220
- }
221
- }
222
- return { accelerator: '', usedFallback: false };
223
- }
224
-
225
- function registerIpc() {
226
- ipcMain.handle('overlay:meta', () => ({
227
- ...overlayHotkeyMeta,
228
- platform: process.platform,
229
- defaultAccelerator: DEFAULT_ACCEL,
230
- sticky: STICKY_NO_BLUR,
231
- }));
232
-
233
- ipcMain.handle('history:get', () => {
234
- try {
235
- return {
236
- items: readRecentHistorySync(200).map((row) => ({
237
- ts: row.ts || '',
238
- text: typeof row.text === 'string' ? row.text : '',
239
- synced: Boolean(row.syncedToSheet || row.syncedToGmail),
240
- })),
241
- };
242
- } catch (e) {
243
- return { error: /** @type {Error} */ (e).message, items: [] };
244
- }
245
- });
246
-
247
- ipcMain.handle('history:copy', (_e, text) => {
248
- if (typeof text === 'string') {
249
- clipboard.writeText(text);
250
- }
251
- win?.hide();
252
- return true;
253
- });
254
- }
255
-
256
- function registerTray() {
257
- const icon = nativeImage.createFromDataURL(
258
- 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhn1IGOMJoAmBGOSMDEwMmABWDWHJjBCSpBKGBSDjBAAAeoRBIEs/x0AAAAASUVORK5CYII=',
259
- );
260
- tray = new Tray(icon);
261
- tray.setToolTip('CopyHub overlay');
262
- const accLabel = overlayHotkeyMeta.accelerator
263
- ? `Shortcut: ${overlayHotkeyMeta.accelerator}`
264
- : 'Shortcut: (see terminal)';
265
- tray.setContextMenu(
266
- Menu.buildFromTemplate([
267
- { label: accLabel, enabled: false },
268
- { label: 'Open history (always on top)', click: () => toggleOverlay() },
269
- { type: 'separator' },
270
- { label: 'Quit', click: () => app.quit() },
271
- ]),
272
- );
273
- tray.on('click', () => toggleOverlay());
274
- }
275
-
276
- if (gotLock) {
277
- app.on('second-instance', () => {
278
- toggleOverlay();
279
- });
280
-
281
- app.whenReady().then(() => {
282
- Menu.setApplicationMenu(null);
283
- createWindow();
284
- registerIpc();
285
-
286
- const { accelerator, usedFallback } = registerHotkeys();
287
- overlayHotkeyMeta = {
288
- accelerator,
289
- usedFallback,
290
- requestedRaw:
291
- process.env.COPYHUB_OVERLAY_ACCELERATOR?.trim() ||
292
- loadOverlayAcceleratorFromConfigSync() ||
293
- '',
294
- };
295
- if (accelerator) {
296
- console.log('CopyHub overlay — shortcut in use:', accelerator);
297
- console.log('Windows tip: Ctrl+Shift+H (CommandOrControl+Shift+H).');
298
- if (usedFallback) {
299
- console.warn(
300
- 'COPYHUB_OVERLAY_ACCELERATOR could not be registered. Using default CommandOrControl+Shift+H.',
301
- );
302
- console.warn('Leave COPYHUB_OVERLAY_ACCELERATOR unset in .env to always use Ctrl+Shift+H.');
303
- }
304
- } else {
305
- console.error(
306
- 'Could not register a global shortcut. Open history from the tray or taskbar icon.',
307
- );
308
- }
309
-
310
- console.log(
311
- STICKY_NO_BLUR
312
- ? 'COPYHUB_OVERLAY_STICKY=1 — window does not close on outside click (Esc / row pick only).'
313
- : 'Overlay: opens near cursor; click outside the window to close. Esc closes too. COPYHUB_OVERLAY_STICKY=1 keeps it open on blur.',
314
- );
315
- console.log(
316
- HIDE_ON_START
317
- ? 'COPYHUB_OVERLAY_HIDE_ON_START=1 — window opens only via shortcut / tray.'
318
- : 'Window shows on startup; check taskbar or tray if you do not see it.',
319
- );
320
-
321
- try {
322
- registerTray();
323
- } catch (e) {
324
- console.warn('Could not create system tray icon:', /** @type {Error} */ (e).message);
325
- }
326
- });
327
-
328
- app.on('will-quit', () => {
329
- globalShortcut.unregisterAll();
330
- });
331
- }
1
+ import 'dotenv/config';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import {
5
+ app,
6
+ BrowserWindow,
7
+ globalShortcut,
8
+ clipboard,
9
+ ipcMain,
10
+ screen,
11
+ Tray,
12
+ Menu,
13
+ nativeImage,
14
+ } from 'electron';
15
+ import { readRecentHistorySync } from '../src/storage.js';
16
+ import { loadOverlayAcceleratorFromConfigSync } from '../src/config.js';
17
+
18
+ const gotLock = app.requestSingleInstanceLock();
19
+ if (!gotLock) {
20
+ app.quit();
21
+ }
22
+
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+
25
+ /** Set to 1 so the window does not hide on blur (only Esc / pick row to copy). */
26
+ const STICKY_NO_BLUR = process.env.COPYHUB_OVERLAY_STICKY === '1';
27
+
28
+ /** Electron Accelerator: use `Control`, not `Ctrl`; `CommandOrControl` = Ctrl (Win) / Cmd (Mac). */
29
+ function normalizeAccelerator(raw) {
30
+ if (!raw || typeof raw !== 'string') return '';
31
+ let s = raw.trim();
32
+ s = s.replace(/\bCtrl\b/gi, 'Control');
33
+ s = s.replace(/\bCmd\b/gi, 'Command');
34
+ s = s.replace(/\s*\+\s*/g, '+');
35
+ return s;
36
+ }
37
+
38
+ const DEFAULT_ACCEL = 'CommandOrControl+Shift+H';
39
+ const HIDE_ON_START = process.env.COPYHUB_OVERLAY_HIDE_ON_START === '1';
40
+
41
+ /** Overlay width (~70% of 460px baseline). */
42
+ const OVERLAY_WIDTH = Math.round(460 * 0.7);
43
+
44
+ /** For UI / IPC: registered shortcut and raw value from .env */
45
+ let overlayHotkeyMeta = {
46
+ accelerator: '',
47
+ usedFallback: false,
48
+ requestedRaw: '',
49
+ };
50
+
51
+ let win = null;
52
+ let tray = null;
53
+ /** Avoid hiding immediately after show (WM quirks). */
54
+ let blurHideEnabled = false;
55
+
56
+ /**
57
+ * Stay above other apps: screen-saver level (highest in Electron), moveTop, all workspaces.
58
+ * @param {BrowserWindow} w
59
+ */
60
+ function applyAlwaysOnTopStack(w) {
61
+ if (!w || w.isDestroyed()) return;
62
+ try {
63
+ w.setAlwaysOnTop(true, 'screen-saver');
64
+ } catch {
65
+ try {
66
+ w.setAlwaysOnTop(true, 'floating');
67
+ } catch {
68
+ w.setAlwaysOnTop(true);
69
+ }
70
+ }
71
+ try {
72
+ w.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
73
+ } catch {
74
+ /* unsupported on some Linux builds */
75
+ }
76
+ try {
77
+ if (typeof w.moveTop === 'function') {
78
+ w.moveTop();
79
+ }
80
+ } catch {
81
+ /* ignore */
82
+ }
83
+ }
84
+
85
+ function createWindow() {
86
+ win = new BrowserWindow({
87
+ width: OVERLAY_WIDTH,
88
+ height: 540,
89
+ alwaysOnTop: true,
90
+ show: false,
91
+ /** Frameless: no title bar + menu (Windows/macOS). */
92
+ frame: false,
93
+ roundedCorners: true,
94
+ /** Show on taskbar for visibility (COPYHUB_OVERLAY_SKIP_TASKBAR=1 hides from taskbar). */
95
+ skipTaskbar: process.env.COPYHUB_OVERLAY_SKIP_TASKBAR === '1',
96
+ title: 'CopyHub',
97
+ backgroundColor: '#ffffff',
98
+ webPreferences: {
99
+ preload: path.join(__dirname, 'preload.cjs'),
100
+ contextIsolation: true,
101
+ nodeIntegration: false,
102
+ },
103
+ });
104
+
105
+ win.loadFile(path.join(__dirname, 'renderer', 'index.html'));
106
+
107
+ win.on('show', () => {
108
+ applyAlwaysOnTopStack(win);
109
+ });
110
+
111
+ if (!STICKY_NO_BLUR) {
112
+ win.on('blur', () => {
113
+ if (!blurHideEnabled) return;
114
+ if (win && !win.webContents.isDevToolsOpened()) {
115
+ win.hide();
116
+ }
117
+ });
118
+ }
119
+
120
+ win.on('close', (e) => {
121
+ e.preventDefault();
122
+ win?.hide();
123
+ });
124
+
125
+ win.webContents.on('before-input-event', (_event, input) => {
126
+ if (input.type === 'keyDown' && input.key === 'Escape') {
127
+ win?.hide();
128
+ }
129
+ });
130
+
131
+ win.once('ready-to-show', () => {
132
+ if (HIDE_ON_START) {
133
+ blurHideEnabled = true;
134
+ return;
135
+ }
136
+ placeWindowAtCursor(win);
137
+ win.show();
138
+ applyAlwaysOnTopStack(win);
139
+ win.focus();
140
+ win.webContents.send('overlay:open');
141
+ setTimeout(() => {
142
+ applyAlwaysOnTopStack(win);
143
+ blurHideEnabled = true;
144
+ }, 800);
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Position window near cursor (display containing pointer).
150
+ * @param {BrowserWindow} w
151
+ */
152
+ function placeWindowAtCursor(w) {
153
+ if (!w || w.isDestroyed()) return;
154
+ const point = screen.getCursorScreenPoint();
155
+ const display = screen.getDisplayNearestPoint(point);
156
+ const { workArea } = display;
157
+ const { width, height } = w.getBounds();
158
+ const margin = 10;
159
+ let x = Math.round(point.x - width / 2);
160
+ let y = Math.round(point.y - 40);
161
+ x = Math.max(
162
+ workArea.x + margin,
163
+ Math.min(x, workArea.x + workArea.width - width - margin),
164
+ );
165
+ y = Math.max(
166
+ workArea.y + margin,
167
+ Math.min(y, workArea.y + workArea.height - height - margin),
168
+ );
169
+ w.setPosition(x, y);
170
+ }
171
+
172
+ function toggleOverlay() {
173
+ if (!win) return;
174
+ if (win.isVisible()) {
175
+ win.hide();
176
+ } else {
177
+ blurHideEnabled = false;
178
+ placeWindowAtCursor(win);
179
+ win.show();
180
+ applyAlwaysOnTopStack(win);
181
+ win.focus();
182
+ win.webContents.send('overlay:open');
183
+ setTimeout(() => {
184
+ applyAlwaysOnTopStack(win);
185
+ blurHideEnabled = true;
186
+ }, 800);
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Register global shortcut: try .env (normalized) then default CommandOrControl+Shift+H.
192
+ * @returns {{ accelerator: string, usedFallback: boolean }}
193
+ */
194
+ function registerHotkeys() {
195
+ const raw =
196
+ process.env.COPYHUB_OVERLAY_ACCELERATOR?.trim() ||
197
+ loadOverlayAcceleratorFromConfigSync();
198
+ const candidates = [];
199
+ if (raw) {
200
+ const n = normalizeAccelerator(raw);
201
+ if (n) candidates.push(n);
202
+ }
203
+ candidates.push(DEFAULT_ACCEL);
204
+
205
+ let usedFallback = false;
206
+ for (let i = 0; i < candidates.length; i++) {
207
+ const acc = candidates[i];
208
+ try {
209
+ if (globalShortcut.register(acc, () => toggleOverlay())) {
210
+ if (i > 0) usedFallback = true;
211
+ return { accelerator: acc, usedFallback };
212
+ }
213
+ } catch (e) {
214
+ console.warn('Invalid accelerator:', acc, /** @type {Error} */ (e).message);
215
+ }
216
+ try {
217
+ globalShortcut.unregister(acc);
218
+ } catch {
219
+ /* ignore */
220
+ }
221
+ }
222
+ return { accelerator: '', usedFallback: false };
223
+ }
224
+
225
+ function registerIpc() {
226
+ ipcMain.handle('overlay:meta', () => ({
227
+ ...overlayHotkeyMeta,
228
+ platform: process.platform,
229
+ defaultAccelerator: DEFAULT_ACCEL,
230
+ sticky: STICKY_NO_BLUR,
231
+ }));
232
+
233
+ ipcMain.handle('history:get', () => {
234
+ try {
235
+ return {
236
+ items: readRecentHistorySync(200).map((row) => ({
237
+ ts: row.ts || '',
238
+ text: typeof row.text === 'string' ? row.text : '',
239
+ synced: Boolean(row.syncedToSheet || row.syncedToGmail),
240
+ })),
241
+ };
242
+ } catch (e) {
243
+ return { error: /** @type {Error} */ (e).message, items: [] };
244
+ }
245
+ });
246
+
247
+ ipcMain.handle('history:copy', (_e, text) => {
248
+ if (typeof text === 'string') {
249
+ clipboard.writeText(text);
250
+ }
251
+ win?.hide();
252
+ return true;
253
+ });
254
+ }
255
+
256
+ function registerTray() {
257
+ const icon = nativeImage.createFromDataURL(
258
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhn1IGOMJoAmBGOSMDEwMmABWDWHJjBCSpBKGBSDjBAAAeoRBIEs/x0AAAAASUVORK5CYII=',
259
+ );
260
+ tray = new Tray(icon);
261
+ tray.setToolTip('CopyHub overlay');
262
+ const accLabel = overlayHotkeyMeta.accelerator
263
+ ? `Shortcut: ${overlayHotkeyMeta.accelerator}`
264
+ : 'Shortcut: (see terminal)';
265
+ tray.setContextMenu(
266
+ Menu.buildFromTemplate([
267
+ { label: accLabel, enabled: false },
268
+ { label: 'Open history (always on top)', click: () => toggleOverlay() },
269
+ { type: 'separator' },
270
+ { label: 'Quit', click: () => app.quit() },
271
+ ]),
272
+ );
273
+ tray.on('click', () => toggleOverlay());
274
+ }
275
+
276
+ if (gotLock) {
277
+ app.on('second-instance', () => {
278
+ toggleOverlay();
279
+ });
280
+
281
+ app.whenReady().then(() => {
282
+ Menu.setApplicationMenu(null);
283
+ createWindow();
284
+ registerIpc();
285
+
286
+ const { accelerator, usedFallback } = registerHotkeys();
287
+ overlayHotkeyMeta = {
288
+ accelerator,
289
+ usedFallback,
290
+ requestedRaw:
291
+ process.env.COPYHUB_OVERLAY_ACCELERATOR?.trim() ||
292
+ loadOverlayAcceleratorFromConfigSync() ||
293
+ '',
294
+ };
295
+ if (accelerator) {
296
+ console.log('CopyHub overlay — shortcut in use:', accelerator);
297
+ console.log('Windows tip: Ctrl+Shift+H (CommandOrControl+Shift+H).');
298
+ if (usedFallback) {
299
+ console.warn(
300
+ 'COPYHUB_OVERLAY_ACCELERATOR could not be registered. Using default CommandOrControl+Shift+H.',
301
+ );
302
+ console.warn('Leave COPYHUB_OVERLAY_ACCELERATOR unset in .env to always use Ctrl+Shift+H.');
303
+ }
304
+ } else {
305
+ console.error(
306
+ 'Could not register a global shortcut. Open history from the tray or taskbar icon.',
307
+ );
308
+ }
309
+
310
+ console.log(
311
+ STICKY_NO_BLUR
312
+ ? 'COPYHUB_OVERLAY_STICKY=1 — window does not close on outside click (Esc / row pick only).'
313
+ : 'Overlay: opens near cursor; click outside the window to close. Esc closes too. COPYHUB_OVERLAY_STICKY=1 keeps it open on blur.',
314
+ );
315
+ console.log(
316
+ HIDE_ON_START
317
+ ? 'COPYHUB_OVERLAY_HIDE_ON_START=1 — window opens only via shortcut / tray.'
318
+ : 'Window shows on startup; check taskbar or tray if you do not see it.',
319
+ );
320
+
321
+ try {
322
+ registerTray();
323
+ } catch (e) {
324
+ console.warn('Could not create system tray icon:', /** @type {Error} */ (e).message);
325
+ }
326
+ });
327
+
328
+ app.on('will-quit', () => {
329
+ globalShortcut.unregisterAll();
330
+ });
331
+ }