electron-menubar 11.1.2 → 11.1.3
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/lib/index.cjs +97 -20
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +36 -11
- package/lib/index.d.mts +36 -11
- package/lib/index.mjs +96 -18
- package/lib/index.mjs.map +1 -1
- package/package.json +36 -19
package/lib/index.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { BrowserWindow, BrowserWindowConstructorOptions, LoadURLOptions, Menu, Rectangle, Tray } from "electron";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
|
-
//#region src/Positioner.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Named anchor points for placing the menubar window. The `tray*` values are
|
|
6
5
|
* relative to the tray icon's bounds; the rest are relative to the work area
|
|
@@ -15,13 +14,18 @@ type WindowPosition$1 = 'trayLeft' | 'trayBottomLeft' | 'trayRight' | 'trayBotto
|
|
|
15
14
|
declare class Positioner {
|
|
16
15
|
private readonly browserWindow;
|
|
17
16
|
constructor(browserWindow: BrowserWindow);
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* @param windowSize - Size to place, when it differs from the window's
|
|
19
|
+
* current size (e.g. the size about to be applied with `setBounds`).
|
|
20
|
+
*/
|
|
21
|
+
calculate(position?: WindowPosition$1, trayBounds?: Rectangle, windowSize?: {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
}): {
|
|
19
25
|
x: number;
|
|
20
26
|
y: number;
|
|
21
27
|
};
|
|
22
28
|
}
|
|
23
|
-
//#endregion
|
|
24
|
-
//#region src/types.d.ts
|
|
25
29
|
/**
|
|
26
30
|
* Options for creating a menubar application
|
|
27
31
|
*/
|
|
@@ -190,8 +194,6 @@ interface Options {
|
|
|
190
194
|
*/
|
|
191
195
|
windowPosition?: WindowPosition$1;
|
|
192
196
|
}
|
|
193
|
-
//#endregion
|
|
194
|
-
//#region src/Menubar.d.ts
|
|
195
197
|
/**
|
|
196
198
|
* The main Menubar class.
|
|
197
199
|
*/
|
|
@@ -210,8 +212,10 @@ export declare class Menubar extends EventEmitter {
|
|
|
210
212
|
private _rightClickContextMenuBound;
|
|
211
213
|
private _warnedNoPositioning;
|
|
212
214
|
private _lastShowTime;
|
|
215
|
+
private _lastBlurHideTime;
|
|
213
216
|
private _dockRehideTimeout?;
|
|
214
217
|
private _repositioning;
|
|
218
|
+
private _heldSize?;
|
|
215
219
|
private _showOnAllWorkspaces;
|
|
216
220
|
private _tray?;
|
|
217
221
|
constructor(app: Electron.App, options?: Partial<Options>);
|
|
@@ -320,6 +324,32 @@ export declare class Menubar extends EventEmitter {
|
|
|
320
324
|
* `setSize` calls reposition the window correctly.
|
|
321
325
|
*/
|
|
322
326
|
private readonly positionWindow;
|
|
327
|
+
/**
|
|
328
|
+
* Re-anchor the window after the app resizes it (e.g. via `setSize`).
|
|
329
|
+
* Ignores the resize that moving the window itself causes; see
|
|
330
|
+
* {@link holdSize}.
|
|
331
|
+
*/
|
|
332
|
+
private readonly onWindowResize;
|
|
333
|
+
/**
|
|
334
|
+
* Pick the size to apply with the next move and remember it.
|
|
335
|
+
*
|
|
336
|
+
* On X11 with fractional display scaling, `setPosition` grows the window by
|
|
337
|
+
* a pixel or two per call, and so does `setBounds` with the size `getSize`
|
|
338
|
+
* reports, because each conversion between DIPs and device pixels rounds
|
|
339
|
+
* up. That drift often lands without a `resize` event, so only the size can
|
|
340
|
+
* tell it apart: a dimension within {@link sizeDrift} of the held size keeps
|
|
341
|
+
* the held value, and any other value came from the app and becomes the new
|
|
342
|
+
* held one.
|
|
343
|
+
*/
|
|
344
|
+
private holdSize;
|
|
345
|
+
/**
|
|
346
|
+
* Size change per dimension, in DIPs, that moving the window can cause.
|
|
347
|
+
* Rounding drift only happens at a fractional scale factor, so on any other
|
|
348
|
+
* display every size change counts as the app's. At a fractional scale, an
|
|
349
|
+
* app resize of up to this many DIPs is indistinguishable from drift and is
|
|
350
|
+
* not kept.
|
|
351
|
+
*/
|
|
352
|
+
private sizeDrift;
|
|
323
353
|
/**
|
|
324
354
|
* Compute and apply the tray-anchored position. Always call through
|
|
325
355
|
* {@link positionWindow}, which guards against re-entrancy.
|
|
@@ -363,8 +393,6 @@ export declare class Menubar extends EventEmitter {
|
|
|
363
393
|
private applyWorkspaceVisibility;
|
|
364
394
|
private windowClear;
|
|
365
395
|
}
|
|
366
|
-
//#endregion
|
|
367
|
-
//#region src/util/getWindowPosition.d.ts
|
|
368
396
|
type TaskbarLocation = 'top' | 'bottom' | 'left' | 'right';
|
|
369
397
|
/**
|
|
370
398
|
* Determine taskbard location: "top", "bottom", "left" or "right".
|
|
@@ -382,8 +410,6 @@ type WindowPosition = 'trayCenter' | 'topRight' | 'trayBottomCenter' | 'leftCent
|
|
|
382
410
|
* @param tray - The Electron Tray instance.
|
|
383
411
|
*/
|
|
384
412
|
export declare function getWindowPosition(tray: Tray): WindowPosition;
|
|
385
|
-
//#endregion
|
|
386
|
-
//#region src/index.d.ts
|
|
387
413
|
/**
|
|
388
414
|
* Factory function to create a menubar application
|
|
389
415
|
*
|
|
@@ -391,6 +417,5 @@ export declare function getWindowPosition(tray: Tray): WindowPosition;
|
|
|
391
417
|
* {@link Options}
|
|
392
418
|
*/
|
|
393
419
|
export declare function menubar(options?: Partial<Options>): Menubar;
|
|
394
|
-
//#endregion
|
|
395
420
|
export {};
|
|
396
421
|
//# sourceMappingURL=index.d.mts.map
|
package/lib/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { EventEmitter } from "node:events";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import url from "node:url";
|
|
6
|
-
|
|
6
|
+
// src/Positioner.ts
|
|
7
7
|
/**
|
|
8
8
|
* Computes `{x, y}` coordinates for placing a {@link BrowserWindow} at a named
|
|
9
9
|
* position, optionally relative to a tray icon's bounds. Ported from
|
|
@@ -14,10 +14,14 @@ var Positioner = class {
|
|
|
14
14
|
constructor(browserWindow) {
|
|
15
15
|
this.browserWindow = browserWindow;
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* @param windowSize - Size to place, when it differs from the window's
|
|
19
|
+
* current size (e.g. the size about to be applied with `setBounds`).
|
|
20
|
+
*/
|
|
21
|
+
calculate(position, trayBounds, windowSize) {
|
|
18
22
|
if (!position) throw new TypeError("Positioner.calculate: a `position` argument is required.");
|
|
19
23
|
const screenSize = trayBounds ? screen.getDisplayMatching(trayBounds).workArea : screen.getDisplayNearestPoint(screen.getCursorScreenPoint()).workArea;
|
|
20
|
-
const [windowWidth, windowHeight] = this.browserWindow.getSize();
|
|
24
|
+
const [windowWidth, windowHeight] = windowSize ? [windowSize.width, windowSize.height] : this.browserWindow.getSize();
|
|
21
25
|
const trayX = trayBounds?.x ?? NaN;
|
|
22
26
|
const trayWidth = trayBounds?.width ?? NaN;
|
|
23
27
|
const positions = {
|
|
@@ -92,8 +96,7 @@ var Positioner = class {
|
|
|
92
96
|
return coords;
|
|
93
97
|
}
|
|
94
98
|
};
|
|
95
|
-
|
|
96
|
-
//#region src/util/cleanOptions.ts
|
|
99
|
+
// src/util/cleanOptions.ts
|
|
97
100
|
/**
|
|
98
101
|
* @ignore
|
|
99
102
|
*/
|
|
@@ -124,8 +127,7 @@ function cleanOptions(opts) {
|
|
|
124
127
|
options.browserWindow.height = options.browserWindow.height !== void 0 ? options.browserWindow.height : DEFAULT_WINDOW_HEIGHT;
|
|
125
128
|
return options;
|
|
126
129
|
}
|
|
127
|
-
|
|
128
|
-
//#region src/util/getWindowPosition.ts
|
|
130
|
+
// src/util/getWindowPosition.ts
|
|
129
131
|
/**
|
|
130
132
|
* Utilities to get taskbar position and consequently menubar's position
|
|
131
133
|
*/
|
|
@@ -195,8 +197,7 @@ function getWindowPosition(tray) {
|
|
|
195
197
|
}
|
|
196
198
|
return "topRight";
|
|
197
199
|
}
|
|
198
|
-
|
|
199
|
-
//#region src/Menubar.ts
|
|
200
|
+
// src/Menubar.ts
|
|
200
201
|
/**
|
|
201
202
|
* Grace period after `showWindow()` during which a `blur` on the popup is
|
|
202
203
|
* treated as the spurious Windows post-show blur and ignored instead of
|
|
@@ -204,6 +205,12 @@ function getWindowPosition(tray) {
|
|
|
204
205
|
*/
|
|
205
206
|
const BLUR_HIDE_GRACE_MS = 400;
|
|
206
207
|
/**
|
|
208
|
+
* Window after a blur-triggered hide during which a tray click on Linux is
|
|
209
|
+
* taken as the click that caused the blur, so it leaves the popup closed. See
|
|
210
|
+
* {@link Menubar.clicked}.
|
|
211
|
+
*/
|
|
212
|
+
const TRAY_CLICK_AFTER_BLUR_HIDE_MS = 500;
|
|
213
|
+
/**
|
|
207
214
|
* Delay before re-asserting the hidden dock after startup. macOS can silently
|
|
208
215
|
* drop an `app.dock.hide()` that races the app's launch activation
|
|
209
216
|
* transition, leaving the dock icon stuck for the whole session. See the
|
|
@@ -211,6 +218,12 @@ const BLUR_HIDE_GRACE_MS = 400;
|
|
|
211
218
|
*/
|
|
212
219
|
const DOCK_REHIDE_DELAY_MS = 2e3;
|
|
213
220
|
/**
|
|
221
|
+
* Largest size change per dimension, in DIPs, taken as rounding drift from
|
|
222
|
+
* moving the window on a display with a fractional scale factor. A move there
|
|
223
|
+
* changes each dimension by up to 2 DIPs. See {@link Menubar.holdSize}.
|
|
224
|
+
*/
|
|
225
|
+
const FRACTIONAL_SCALE_DRIFT = 4;
|
|
226
|
+
/**
|
|
214
227
|
* The main Menubar class.
|
|
215
228
|
*/
|
|
216
229
|
var Menubar = class extends EventEmitter {
|
|
@@ -228,8 +241,10 @@ var Menubar = class extends EventEmitter {
|
|
|
228
241
|
_rightClickContextMenuBound = false;
|
|
229
242
|
_warnedNoPositioning = false;
|
|
230
243
|
_lastShowTime = 0;
|
|
244
|
+
_lastBlurHideTime = 0;
|
|
231
245
|
_dockRehideTimeout;
|
|
232
246
|
_repositioning = false;
|
|
247
|
+
_heldSize;
|
|
233
248
|
_showOnAllWorkspaces = false;
|
|
234
249
|
_tray;
|
|
235
250
|
constructor(app, options) {
|
|
@@ -378,8 +393,13 @@ var Menubar = class extends EventEmitter {
|
|
|
378
393
|
recenterOnTray() {
|
|
379
394
|
if (!this._browserWindow || !this._tray) return;
|
|
380
395
|
const bounds = this._tray.getBounds();
|
|
381
|
-
const
|
|
382
|
-
this.
|
|
396
|
+
const size = this.holdSize(this._browserWindow);
|
|
397
|
+
const { x, y } = this.positioner.calculate("trayCenter", bounds, size);
|
|
398
|
+
this._browserWindow.setBounds({
|
|
399
|
+
x: Math.round(x),
|
|
400
|
+
y: Math.round(y),
|
|
401
|
+
...size
|
|
402
|
+
});
|
|
383
403
|
}
|
|
384
404
|
/**
|
|
385
405
|
* Replace the tray context menu after construction. On Linux this also
|
|
@@ -454,6 +474,7 @@ var Menubar = class extends EventEmitter {
|
|
|
454
474
|
this.positionWindow();
|
|
455
475
|
this.applyWindowFocusBehavior();
|
|
456
476
|
this._lastShowTime = Date.now();
|
|
477
|
+
this._lastBlurHideTime = 0;
|
|
457
478
|
this._browserWindow.show();
|
|
458
479
|
this._isVisible = true;
|
|
459
480
|
this.emit("after-show");
|
|
@@ -476,6 +497,52 @@ var Menubar = class extends EventEmitter {
|
|
|
476
497
|
}
|
|
477
498
|
};
|
|
478
499
|
/**
|
|
500
|
+
* Re-anchor the window after the app resizes it (e.g. via `setSize`).
|
|
501
|
+
* Ignores the resize that moving the window itself causes; see
|
|
502
|
+
* {@link holdSize}.
|
|
503
|
+
*/
|
|
504
|
+
onWindowResize = () => {
|
|
505
|
+
if (!this._browserWindow) return;
|
|
506
|
+
const [width, height] = this._browserWindow.getSize();
|
|
507
|
+
const held = this._heldSize;
|
|
508
|
+
const drift = this.sizeDrift(this._browserWindow);
|
|
509
|
+
if (held && Math.abs(width - held.width) <= drift && Math.abs(height - held.height) <= drift) return;
|
|
510
|
+
this.positionWindow();
|
|
511
|
+
};
|
|
512
|
+
/**
|
|
513
|
+
* Pick the size to apply with the next move and remember it.
|
|
514
|
+
*
|
|
515
|
+
* On X11 with fractional display scaling, `setPosition` grows the window by
|
|
516
|
+
* a pixel or two per call, and so does `setBounds` with the size `getSize`
|
|
517
|
+
* reports, because each conversion between DIPs and device pixels rounds
|
|
518
|
+
* up. That drift often lands without a `resize` event, so only the size can
|
|
519
|
+
* tell it apart: a dimension within {@link sizeDrift} of the held size keeps
|
|
520
|
+
* the held value, and any other value came from the app and becomes the new
|
|
521
|
+
* held one.
|
|
522
|
+
*/
|
|
523
|
+
holdSize(win) {
|
|
524
|
+
const [width, height] = win.getSize();
|
|
525
|
+
const held = this._heldSize;
|
|
526
|
+
const drift = this.sizeDrift(win);
|
|
527
|
+
const size = {
|
|
528
|
+
width: held && Math.abs(width - held.width) <= drift ? held.width : width,
|
|
529
|
+
height: held && Math.abs(height - held.height) <= drift ? held.height : height
|
|
530
|
+
};
|
|
531
|
+
this._heldSize = size;
|
|
532
|
+
return size;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Size change per dimension, in DIPs, that moving the window can cause.
|
|
536
|
+
* Rounding drift only happens at a fractional scale factor, so on any other
|
|
537
|
+
* display every size change counts as the app's. At a fractional scale, an
|
|
538
|
+
* app resize of up to this many DIPs is indistinguishable from drift and is
|
|
539
|
+
* not kept.
|
|
540
|
+
*/
|
|
541
|
+
sizeDrift(win) {
|
|
542
|
+
const { scaleFactor } = screen.getDisplayMatching(win.getBounds());
|
|
543
|
+
return Number.isInteger(scaleFactor) ? 0 : FRACTIONAL_SCALE_DRIFT;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
479
546
|
* Compute and apply the tray-anchored position. Always call through
|
|
480
547
|
* {@link positionWindow}, which guards against re-entrancy.
|
|
481
548
|
*/
|
|
@@ -485,12 +552,17 @@ var Menubar = class extends EventEmitter {
|
|
|
485
552
|
const trayPos = this._cachedBounds ?? this._tray.getBounds?.();
|
|
486
553
|
let noBoundsPosition;
|
|
487
554
|
if ((trayPos === void 0 || trayPos.x === 0) && this._options.windowPosition?.startsWith("tray")) noBoundsPosition = process.platform === "win32" ? "bottomRight" : "topRight";
|
|
488
|
-
const
|
|
555
|
+
const size = this.holdSize(this._browserWindow);
|
|
556
|
+
const position = this.positioner.calculate(this._options.windowPosition || noBoundsPosition, trayPos, size);
|
|
489
557
|
const x = this._options.browserWindow.x !== void 0 ? this._options.browserWindow.x : position.x;
|
|
490
558
|
const y = this._options.browserWindow.y !== void 0 ? this._options.browserWindow.y : position.y;
|
|
491
559
|
const targetX = Math.round(x);
|
|
492
560
|
const targetY = Math.round(y);
|
|
493
|
-
this._browserWindow.
|
|
561
|
+
this._browserWindow.setBounds({
|
|
562
|
+
x: targetX,
|
|
563
|
+
y: targetY,
|
|
564
|
+
...size
|
|
565
|
+
});
|
|
494
566
|
if (process.platform === "linux" && !!process.env.WAYLAND_DISPLAY && !this._warnedNoPositioning) {
|
|
495
567
|
const [actualX, actualY] = this._browserWindow.getPosition();
|
|
496
568
|
if (Math.abs(actualX - targetX) > 24 || Math.abs(actualY - targetY) > 24) {
|
|
@@ -534,6 +606,10 @@ var Menubar = class extends EventEmitter {
|
|
|
534
606
|
if (event && (event.shiftKey || event.ctrlKey || event.metaKey)) return this.hideWindow();
|
|
535
607
|
if (this._blurTimeout) clearInterval(this._blurTimeout);
|
|
536
608
|
if (this._browserWindow && this._isVisible) return this.hideWindow();
|
|
609
|
+
if (process.platform === "linux" && Date.now() - this._lastBlurHideTime < TRAY_CLICK_AFTER_BLUR_HIDE_MS) {
|
|
610
|
+
this._lastBlurHideTime = 0;
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
537
613
|
this._cachedBounds = bounds || this._cachedBounds;
|
|
538
614
|
await this.showWindow(this._cachedBounds);
|
|
539
615
|
};
|
|
@@ -603,7 +679,10 @@ var Menubar = class extends EventEmitter {
|
|
|
603
679
|
if (this._blurTimeout) clearTimeout(this._blurTimeout);
|
|
604
680
|
this._blurTimeout = setTimeout(() => {
|
|
605
681
|
this._blurTimeout = null;
|
|
606
|
-
if (this.shouldHideOnBlur())
|
|
682
|
+
if (this.shouldHideOnBlur()) {
|
|
683
|
+
this.hideWindow();
|
|
684
|
+
this._lastBlurHideTime = Date.now();
|
|
685
|
+
}
|
|
607
686
|
}, 100);
|
|
608
687
|
});
|
|
609
688
|
this._showOnAllWorkspaces = this._options.showOnAllWorkspaces !== false;
|
|
@@ -617,7 +696,7 @@ var Menubar = class extends EventEmitter {
|
|
|
617
696
|
if (input.type === "keyDown" && input.key === "Escape") this.hideWindow();
|
|
618
697
|
});
|
|
619
698
|
this._browserWindow.on("closed", this.windowClear.bind(this));
|
|
620
|
-
this._browserWindow.on("resize", this.
|
|
699
|
+
this._browserWindow.on("resize", this.onWindowResize);
|
|
621
700
|
this.emit("before-load");
|
|
622
701
|
if (this._options.index !== false) await this._browserWindow.loadURL(this._options.index, this._options.loadUrlOptions);
|
|
623
702
|
this.emit("after-create-window");
|
|
@@ -643,11 +722,11 @@ var Menubar = class extends EventEmitter {
|
|
|
643
722
|
}
|
|
644
723
|
windowClear() {
|
|
645
724
|
this._browserWindow = void 0;
|
|
725
|
+
this._heldSize = void 0;
|
|
646
726
|
this.emit("after-close");
|
|
647
727
|
}
|
|
648
728
|
};
|
|
649
|
-
|
|
650
|
-
//#region src/index.ts
|
|
729
|
+
// src/index.ts
|
|
651
730
|
/**
|
|
652
731
|
* Entry point of menubar
|
|
653
732
|
* @example
|
|
@@ -665,7 +744,6 @@ var Menubar = class extends EventEmitter {
|
|
|
665
744
|
function menubar(options) {
|
|
666
745
|
return new Menubar(app, options);
|
|
667
746
|
}
|
|
668
|
-
//#endregion
|
|
669
747
|
export { Menubar, getWindowPosition, menubar, taskbarLocation };
|
|
670
748
|
|
|
671
749
|
//# sourceMappingURL=index.mjs.map
|
package/lib/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["electronScreen"],"sources":["../src/Positioner.ts","../src/util/cleanOptions.ts","../src/util/getWindowPosition.ts","../src/Menubar.ts","../src/index.ts"],"sourcesContent":["import { type BrowserWindow, type Rectangle, screen } from 'electron';\n\n/**\n * Named anchor points for placing the menubar window. The `tray*` values are\n * relative to the tray icon's bounds; the rest are relative to the work area\n * of the display containing the cursor (or the tray, when bounds are given).\n */\nexport type WindowPosition =\n | 'trayLeft'\n | 'trayBottomLeft'\n | 'trayRight'\n | 'trayBottomRight'\n | 'trayCenter'\n | 'trayBottomCenter'\n | 'topLeft'\n | 'topRight'\n | 'bottomLeft'\n | 'bottomRight'\n | 'topCenter'\n | 'bottomCenter'\n | 'leftCenter'\n | 'rightCenter'\n | 'center';\n\n/**\n * Computes `{x, y}` coordinates for placing a {@link BrowserWindow} at a named\n * position, optionally relative to a tray icon's bounds. Ported from\n * `electron-positioner@4.1.0` to drop the unmaintained runtime dependency.\n */\nexport class Positioner {\n private readonly browserWindow: BrowserWindow;\n\n constructor(browserWindow: BrowserWindow) {\n this.browserWindow = browserWindow;\n }\n\n calculate(\n position?: WindowPosition,\n trayBounds?: Rectangle,\n ): { x: number; y: number } {\n if (!position) {\n throw new TypeError(\n 'Positioner.calculate: a `position` argument is required.',\n );\n }\n\n const screenSize = trayBounds\n ? screen.getDisplayMatching(trayBounds).workArea\n : screen.getDisplayNearestPoint(screen.getCursorScreenPoint()).workArea;\n const [windowWidth, windowHeight] = this.browserWindow.getSize();\n const trayX = trayBounds?.x ?? Number.NaN;\n const trayWidth = trayBounds?.width ?? Number.NaN;\n\n const positions: Record<WindowPosition, { x: number; y: number }> = {\n trayLeft: {\n x: Math.floor(trayX),\n y: screenSize.y,\n },\n trayBottomLeft: {\n x: Math.floor(trayX),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n trayRight: {\n x: Math.floor(trayX - windowWidth + trayWidth),\n y: screenSize.y,\n },\n trayBottomRight: {\n x: Math.floor(trayX - windowWidth + trayWidth),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n trayCenter: {\n x: Math.floor(trayX - windowWidth / 2 + trayWidth / 2),\n y: screenSize.y,\n },\n trayBottomCenter: {\n x: Math.floor(trayX - windowWidth / 2 + trayWidth / 2),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n topLeft: {\n x: screenSize.x,\n y: screenSize.y,\n },\n topRight: {\n x: Math.floor(screenSize.x + (screenSize.width - windowWidth)),\n y: screenSize.y,\n },\n bottomLeft: {\n x: screenSize.x,\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n bottomRight: {\n x: Math.floor(screenSize.x + (screenSize.width - windowWidth)),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n topCenter: {\n x: Math.floor(screenSize.x + (screenSize.width / 2 - windowWidth / 2)),\n y: screenSize.y,\n },\n bottomCenter: {\n x: Math.floor(screenSize.x + (screenSize.width / 2 - windowWidth / 2)),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n leftCenter: {\n x: screenSize.x,\n y:\n screenSize.y +\n Math.floor(screenSize.height / 2) -\n Math.floor(windowHeight / 2),\n },\n rightCenter: {\n x: Math.floor(screenSize.x + (screenSize.width - windowWidth)),\n y:\n screenSize.y +\n Math.floor(screenSize.height / 2) -\n Math.floor(windowHeight / 2),\n },\n center: {\n x: Math.floor(screenSize.x + (screenSize.width / 2 - windowWidth / 2)),\n y: Math.floor(\n (screenSize.height + screenSize.y) / 2 - windowHeight / 2,\n ),\n },\n };\n\n const coords = positions[position];\n\n // On Windows, a tray-relative position can push the window past the right\n // edge of the work area. Snap back to `topRight` x in that case so it stays\n // visible. See https://github.com/jenslind/electron-positioner.\n if (position.startsWith('tray')) {\n if (coords.x + windowWidth > screenSize.width + screenSize.x) {\n return { x: positions.topRight.x, y: coords.y };\n }\n }\n\n return coords;\n }\n}\n","/**\n * @ignore\n */\n\n/** */\n\nimport path from 'node:path';\nimport url from 'node:url';\n\nimport { app } from 'electron';\n\nimport type { Options } from '../types';\n\nconst DEFAULT_WINDOW_HEIGHT = 400;\nconst DEFAULT_WINDOW_WIDTH = 400;\n\n/**\n * Take as input some options, and return a sanitized version of it.\n *\n * @param opts - The options to clean.\n * @ignore\n */\nexport function cleanOptions(opts?: Partial<Options>): Options {\n const options: Partial<Options> = { ...opts };\n\n options.activateWithApp ??= true;\n options.ignoreDoubleClickEvents ??= true;\n if (!options.dir) {\n options.dir = app.getAppPath();\n }\n if (!path.isAbsolute(options.dir)) {\n options.dir = path.resolve(options.dir);\n }\n // Note: options.index can be `false`\n options.index ??= url.format({\n pathname: path.join(options.dir, 'index.html'),\n protocol: 'file:',\n slashes: true,\n });\n options.loadUrlOptions = options.loadUrlOptions || {};\n\n options.tooltip = options.tooltip || '';\n\n // `icon`, `preloadWindow`, `showDockIcon`, `showOnAllWorkspaces`,\n // `showOnRightClick` don't need any special treatment\n\n // Now we take care of `browserWindow`\n if (!options.browserWindow) {\n options.browserWindow = {};\n }\n\n // Set width/height on options to be usable before the window is created\n options.browserWindow.width =\n // Note: not using `options.browserWindow.width || DEFAULT_WINDOW_WIDTH` so\n // that users can put a 0 width\n options.browserWindow.width !== undefined\n ? options.browserWindow.width\n : DEFAULT_WINDOW_WIDTH;\n options.browserWindow.height =\n options.browserWindow.height !== undefined\n ? options.browserWindow.height\n : DEFAULT_WINDOW_HEIGHT;\n\n return options as Options;\n}\n","/**\n * Utilities to get taskbar position and consequently menubar's position\n */\n\n/** */\n\nimport { screen as electronScreen, type Rectangle, type Tray } from 'electron';\n\nconst isLinux = process.platform === 'linux';\n\nconst trayToScreenRects = (trayBounds: Rectangle): [Rectangle, Rectangle] => {\n // There may be more than one screen, so we need to figure out on which screen our tray icon lives.\n const { workArea, bounds: screenBounds } =\n electronScreen.getDisplayMatching(trayBounds);\n\n return [\n screenBounds,\n {\n ...workArea,\n x: workArea.x - screenBounds.x,\n y: workArea.y - screenBounds.y,\n },\n ];\n};\n\ntype TaskbarLocation = 'top' | 'bottom' | 'left' | 'right';\n\n/**\n * Determine taskbard location: \"top\", \"bottom\", \"left\" or \"right\".\n *\n * Only tested on Windows for now, and only used in Windows.\n *\n * @param tray - The Electron Tray instance.\n */\nexport function taskbarLocation(tray: Tray): TaskbarLocation {\n const trayBounds = tray.getBounds();\n const [screenBounds, workArea] = trayToScreenRects(trayBounds);\n\n if (\n process.platform === 'win32' &&\n trayBounds.width > 0 &&\n trayBounds.height > 0\n ) {\n // Other appbars also reserve work-area space. The tray's nearest display\n // edge identifies the taskbar without mistaking a PowerToys dock for it.\n const x = trayBounds.x + trayBounds.width / 2 - screenBounds.x;\n const y = trayBounds.y + trayBounds.height / 2 - screenBounds.y;\n const insets: Record<TaskbarLocation, number> = {\n top: workArea.y,\n bottom: screenBounds.height - workArea.y - workArea.height,\n left: workArea.x,\n right: screenBounds.width - workArea.x - workArea.width,\n };\n let nearest: [TaskbarLocation, number] = [\n 'bottom',\n Math.abs(screenBounds.height - y),\n ];\n const edges: [TaskbarLocation, number][] = [\n ['top', Math.abs(y)],\n ['left', Math.abs(x)],\n ['right', Math.abs(screenBounds.width - x)],\n ];\n for (const edge of edges) {\n // At a corner, prefer the edge that actually reserves work-area space.\n if (\n edge[1] < nearest[1] ||\n (edge[1] === nearest[1] && insets[edge[0]] > insets[nearest[0]])\n )\n nearest = edge;\n }\n return nearest[0];\n }\n\n // TASKBAR LEFT\n if (workArea.x > 0) {\n // Most likely Ubuntu hence assuming the window should be on top\n if (isLinux && workArea.y > 0) return 'top';\n // The workspace starts more on the right\n return 'left';\n }\n\n // TASKBAR TOP\n if (workArea.y > 0) {\n return 'top';\n }\n\n // TASKBAR RIGHT\n // Here both workArea.y and workArea.x are 0 so we can no longer leverage them.\n // We can use the workarea and display width though.\n // Determine taskbar location\n if (workArea.width < screenBounds.width) {\n // The taskbar is either on the left or right, but since the LEFT case was handled above,\n // we can be sure we're dealing with a right taskbar\n return 'right';\n }\n\n // TASKBAR BOTTOM\n // Since all the other cases were handled, we can be sure we're dealing with a bottom taskbar\n return 'bottom';\n}\n\ntype WindowPosition =\n | 'trayCenter'\n | 'topRight'\n | 'trayBottomCenter'\n | 'leftCenter'\n | 'bottomRight';\n\n/**\n * Depending on where the taskbar is, determine where the window should be\n * positioned.\n *\n * @param tray - The Electron Tray instance.\n */\nexport function getWindowPosition(tray: Tray): WindowPosition {\n switch (process.platform) {\n // macOS\n // Supports top taskbars\n case 'darwin':\n return 'trayCenter';\n // Linux\n // Windows\n // Supports top/bottom/left/right taskbar\n case 'linux':\n case 'win32': {\n const traySide = taskbarLocation(tray);\n\n // Assign position for menubar\n if (traySide === 'top') {\n return isLinux ? 'topRight' : 'trayCenter';\n }\n if (traySide === 'bottom') {\n return 'bottomRight';\n }\n if (traySide === 'left') {\n // Vertically centered against the left edge of the work area.\n // `bottomLeft` would put the window in the screen's bottom-left\n // corner — visually disconnected from the tray icon, which sits\n // somewhere on the left strip. Tray-anchored y isn't available\n // through `Positioner` for side taskbars, so center is the best\n // compromise that stays close to the tray.\n return 'leftCenter';\n }\n if (traySide === 'right') {\n return 'bottomRight';\n }\n }\n }\n\n // When we really don't know, we just show the menubar on the top-right\n return 'topRight';\n}\n","import { EventEmitter } from 'node:events';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n autoUpdater,\n BrowserWindow,\n globalShortcut,\n type Menu,\n Tray,\n} from 'electron';\n\nimport { Positioner } from './Positioner';\nimport type { Options } from './types';\nimport { cleanOptions } from './util/cleanOptions';\nimport { getWindowPosition } from './util/getWindowPosition';\n\n/**\n * Grace period after `showWindow()` during which a `blur` on the popup is\n * treated as the spurious Windows post-show blur and ignored instead of\n * triggering the auto-hide. See the `blur` handler in {@link Menubar.createWindow}.\n */\nconst BLUR_HIDE_GRACE_MS = 400;\n\n/**\n * Delay before re-asserting the hidden dock after startup. macOS can silently\n * drop an `app.dock.hide()` that races the app's launch activation\n * transition, leaving the dock icon stuck for the whole session. See the\n * re-check in {@link Menubar.appReady}.\n */\nconst DOCK_REHIDE_DELAY_MS = 2_000;\n\n/**\n * The main Menubar class.\n */\nexport class Menubar extends EventEmitter {\n private readonly _app: Electron.App;\n private _browserWindow?: BrowserWindow;\n private _contextMenu?: Menu;\n private _blurTimeout: NodeJS.Timeout | null = null; // track blur events with timeout\n private _isDestroyed: boolean;\n private _isQuitting: boolean; // set when the app is shutting down, used by hideOnClose\n private _isVisible: boolean; // track visibility\n private _cachedBounds?: Electron.Rectangle; // _cachedBounds are needed for double-clicked event\n private readonly _options: Options;\n private _positioner: Positioner | undefined;\n private _shortcut?: Electron.Accelerator;\n private _rightClickContextMenuBound = false;\n private _warnedNoPositioning = false; // guards the one-time Wayland warning\n private _lastShowTime = 0; // timestamp of last show(), debounces the post-show blur on Windows\n private _dockRehideTimeout?: NodeJS.Timeout; // pending post-startup dock re-hide check\n private _repositioning = false; // guards against re-entrant positionWindow calls\n private _showOnAllWorkspaces = false; // `showOnAllWorkspaces` as read when the window was created\n private _tray?: Tray;\n\n constructor(app: Electron.App, options?: Partial<Options>) {\n super();\n this._app = app;\n this._options = cleanOptions(options);\n this._isDestroyed = false;\n this._isQuitting = false;\n this._isVisible = false;\n\n app.on('before-quit', this.onBeforeQuit);\n autoUpdater.on('before-quit-for-update', this.onBeforeQuit);\n\n if (app.isReady()) {\n // See https://github.com/maxogden/menubar/pull/151\n process.nextTick(this.onAppReady);\n } else {\n app.on('ready', this.onAppReady);\n }\n }\n\n /**\n * The Electron [App](https://electronjs.org/docs/api/app)\n * instance.\n */\n get app(): Electron.App {\n return this._app;\n }\n\n /**\n * The {@link Positioner} instance used to compute where the menubar window\n * should appear on screen. Available after the `after-create-window` event.\n */\n get positioner(): Positioner {\n if (!this._positioner) {\n throw new Error(\n 'Please access `this.positioner` after the `after-create-window` event has fired.',\n );\n }\n\n return this._positioner;\n }\n\n /**\n * The Electron [Tray](https://electronjs.org/docs/api/tray) instance.\n */\n get tray(): Tray {\n if (!this._tray) {\n throw new Error(\n 'Please access `this.tray` after the `ready` event has fired.',\n );\n }\n\n return this._tray;\n }\n\n /**\n * The Electron [BrowserWindow](https://electronjs.org/docs/api/browser-window)\n * instance, if it's present.\n */\n get window(): BrowserWindow | undefined {\n return this._browserWindow;\n }\n\n /**\n * Tear down the menubar instance: destroy the window, remove the tray, and\n * detach all listeners. Subsequent clicks on the tray will be no-ops until a\n * new {@link Menubar} instance is created.\n */\n destroy(): void {\n if (this.isDestroyed()) {\n return;\n }\n // Set first so `hideOnClose` lets the close go through instead of\n // intercepting it.\n this._isDestroyed = true;\n\n if (this._shortcut) {\n globalShortcut.unregister(this._shortcut);\n this._shortcut = undefined;\n }\n\n if (this._dockRehideTimeout) {\n clearTimeout(this._dockRehideTimeout);\n this._dockRehideTimeout = undefined;\n }\n\n if (this._browserWindow) {\n this._browserWindow.destroy();\n this._browserWindow = undefined;\n }\n\n if (this._tray) {\n // Ensure all potential listeners are removed.\n for (const event of ['click', 'right-click', 'double-click']) {\n this._tray.removeListener(\n event as Parameters<Tray['on']>[0],\n this.clicked,\n );\n }\n this._tray.setToolTip('');\n this._tray = undefined;\n }\n\n this._app.removeListener('ready', this.onAppReady);\n this._app.removeListener('activate', this.onAppActivate);\n this._app.removeListener('before-quit', this.onBeforeQuit);\n autoUpdater.removeListener('before-quit-for-update', this.onBeforeQuit);\n }\n\n /**\n * Whether {@link destroy} has been called on this menubar instance.\n */\n isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Retrieve a menubar option.\n *\n * @param key - The option key to retrieve, see {@link Options}.\n */\n getOption<K extends keyof Options>(key: K): Options[K] {\n return this._options[key];\n }\n\n /**\n * Hide the menubar window.\n */\n hideWindow(): void {\n if (!this._browserWindow || !this._isVisible) {\n return;\n }\n this.emit('hide');\n this._browserWindow.hide();\n if (process.platform === 'darwin') {\n this.applyWorkspaceVisibility(false);\n }\n this.emit('after-hide');\n this._isVisible = false;\n if (this._blurTimeout) {\n clearTimeout(this._blurTimeout);\n this._blurTimeout = null;\n }\n this.refreshContextMenu();\n }\n\n /**\n * Register a global keyboard accelerator that toggles the menubar window.\n * Replaces any previously registered shortcut owned by this Menubar.\n * Pass `undefined` to clear the current shortcut without registering a new\n * one. Returns whether the registration succeeded.\n *\n * @param accelerator - An Electron\n * [Accelerator](https://electronjs.org/docs/api/accelerator) string, or\n * `undefined` to clear.\n */\n setGlobalShortcut(accelerator: Electron.Accelerator | undefined): boolean {\n if (this._shortcut) {\n globalShortcut.unregister(this._shortcut);\n this._shortcut = undefined;\n }\n this._options.globalShortcut = accelerator;\n if (!accelerator) {\n return true;\n }\n const ok = globalShortcut.register(accelerator, () => this.toggleWindow());\n if (ok) {\n this._shortcut = accelerator;\n }\n return ok;\n }\n\n /**\n * Toggle the menubar window: hide it if visible, show it otherwise.\n * Resolves once the window finishes showing or hiding.\n */\n async toggleWindow(): Promise<void> {\n if (this._browserWindow && this._isVisible) {\n this.hideWindow();\n return;\n }\n await this.showWindow();\n }\n\n /**\n * Re-center the menubar window over the tray icon. Convenience wrapper for\n * `positioner.move('trayCenter', tray.getBounds())` that's safe to call\n * after the `after-create-window` event. No-op if the window doesn't\n * exist yet.\n */\n recenterOnTray(): void {\n if (!this._browserWindow || !this._tray) {\n return;\n }\n const bounds = this._tray.getBounds();\n const { x, y } = this.positioner.calculate('trayCenter', bounds);\n this._browserWindow.setPosition(Math.round(x), Math.round(y));\n }\n\n /**\n * Replace the tray context menu after construction. On Linux this also\n * re-publishes the menu to the SNI host, which is required after mutating\n * items in-place since libappindicator caches the previous serialization.\n * On macOS/Windows the right-click popup handler reads the current menu\n * reference, so swapping or clearing here takes effect immediately.\n *\n * @param menu - The new menu, or `null` to clear it.\n */\n setContextMenu(menu: Menu | null): void {\n this._contextMenu = menu ?? undefined;\n this._options.contextMenu = menu ?? undefined;\n if (!this._tray) {\n return;\n }\n if (process.platform === 'linux') {\n // `setContextMenu(null)` clears the menu on Linux.\n this._tray.setContextMenu(menu);\n return;\n }\n // macOS / Windows: bind the right-click popup once on first non-empty\n // assignment. The handler reads `this._contextMenu` at invoke time, so\n // later swaps and clears take effect without rebinding (and never leak\n // a stale closure reference).\n if (menu && !this._rightClickContextMenuBound) {\n this.bindRightClickContextMenu();\n }\n }\n\n /**\n * Re-publish the current context menu so in-place mutations of its items\n * (`visible`, `enabled`, `checked`, `label`) become visible to the user.\n *\n * Only Linux needs this: libappindicator / StatusNotifierItem serializes the\n * menu once and serves that copy until it is set again, so a mutated item\n * would otherwise keep rendering its old state until the next show or hide.\n * A no-op on macOS and Windows, where the menu is read at popup time — call\n * it unconditionally after mutating items.\n */\n refreshContextMenu(): void {\n if (\n process.platform === 'linux' &&\n this._contextMenu &&\n this._tray &&\n !this._tray.isDestroyed?.()\n ) {\n this._tray.setContextMenu(this._contextMenu);\n }\n }\n\n /**\n * Change an option after menubar is created.\n *\n * @param key - The option key to modify, see {@link Options}.\n * @param value - The value to set.\n */\n setOption<K extends keyof Options>(key: K, value: Options[K]): void {\n this._options[key] = value;\n if (key === 'hideOnBlur' || key === 'browserWindow') {\n this.applyWindowFocusBehavior();\n }\n }\n\n private shouldHideOnBlur(): boolean {\n const win = this._browserWindow;\n return (\n !!win &&\n !win.isDestroyed() &&\n this._options.hideOnBlur !== false &&\n !win.webContents.isDevToolsOpened()\n );\n }\n\n private applyWindowFocusBehavior(): void {\n const win = this._browserWindow;\n if (!win || win.isDestroyed()) {\n return;\n }\n\n const keepOpen = !this.shouldHideOnBlur();\n if (this._blurTimeout) {\n clearTimeout(this._blurTimeout);\n this._blurTimeout = null;\n }\n\n if (process.platform === 'win32') {\n win.setAlwaysOnTop(true, 'pop-up-menu');\n } else {\n win.setAlwaysOnTop(\n keepOpen || this._options.browserWindow.alwaysOnTop === true,\n );\n }\n }\n\n /**\n * Show the menubar window.\n *\n * @param trayPos - The bounds to show the window in.\n */\n async showWindow(trayPos?: Electron.Rectangle): Promise<void> {\n if (!this.tray) {\n throw new Error('Tray should have been instantiated by now');\n }\n\n if (!this._browserWindow) {\n await this.createWindow();\n }\n\n // Use guard for TypeScript, to avoid ! everywhere\n if (!this._browserWindow) {\n throw new Error('Window has been initialized just above. qed.');\n }\n\n this.emit('show');\n\n // Cache fresh tray bounds (or fall back to existing cache / tray bounds)\n // so `positionWindow` can reposition without an event payload — for\n // example when the window resizes via `setSize`.\n if (trayPos && trayPos.x !== 0) {\n this._cachedBounds = trayPos;\n } else if (!this._cachedBounds && this.tray.getBounds) {\n this._cachedBounds = this.tray.getBounds();\n }\n\n if (process.platform === 'darwin') {\n this.applyWorkspaceVisibility(true);\n }\n this.positionWindow();\n // Record the show time before `show()` so the blur handler can recognise\n // and ignore the transient blur Windows fires right after (see below).\n this.applyWindowFocusBehavior();\n this._lastShowTime = Date.now();\n this._browserWindow.show();\n this._isVisible = true;\n this.emit('after-show');\n this.refreshContextMenu();\n }\n\n /**\n * Compute and apply the tray-anchored position of the browser window. Safe\n * to call any time after `createWindow` has run — invoked from\n * {@link showWindow} on every show, and from the window's `resize` event so\n * `setSize` calls reposition the window correctly.\n */\n private readonly positionWindow = (): void => {\n if (!this._browserWindow || !this._tray) {\n return;\n }\n\n // `setPosition` below can make Windows emit `resize` synchronously, which\n // re-enters this method through the window's `resize` listener. Each pass\n // nudges the window again, so the recursion never unwinds: the main\n // process wedges with the event loop blocked and the window grows past\n // screen size (gitify-app/gitify#3064). Ignore re-entrant calls; the\n // outermost one already applies the final position.\n if (this._repositioning) {\n return;\n }\n this._repositioning = true;\n try {\n this.applyWindowPosition();\n } finally {\n this._repositioning = false;\n }\n };\n\n /**\n * Compute and apply the tray-anchored position. Always call through\n * {@link positionWindow}, which guards against re-entrancy.\n */\n private applyWindowPosition(): void {\n if (!this._browserWindow || !this._tray) {\n return;\n }\n\n // 'Windows' taskbar: sync window position each time before positioning.\n // https://github.com/maxogden/menubar/issues/232\n if (['win32', 'linux'].includes(process.platform)) {\n this._options.windowPosition = getWindowPosition(this._tray);\n }\n\n const trayPos = this._cachedBounds ?? this._tray.getBounds?.();\n\n // Default the window to the right if `trayPos` bounds are undefined or null.\n let noBoundsPosition: Options['windowPosition'];\n if (\n (trayPos === undefined || trayPos.x === 0) &&\n this._options.windowPosition?.startsWith('tray')\n ) {\n noBoundsPosition =\n process.platform === 'win32' ? 'bottomRight' : 'topRight';\n }\n\n const position = this.positioner.calculate(\n this._options.windowPosition || noBoundsPosition,\n trayPos,\n ) as { x: number; y: number };\n\n // Not using `||` because x and y can be zero.\n const x =\n this._options.browserWindow.x !== undefined\n ? this._options.browserWindow.x\n : position.x;\n const y =\n this._options.browserWindow.y !== undefined\n ? this._options.browserWindow.y\n : position.y;\n\n // `.setPosition` crashed on non-integers\n // https://github.com/maxogden/menubar/issues/233\n const targetX = Math.round(x);\n const targetY = Math.round(y);\n this._browserWindow.setPosition(targetX, targetY);\n\n // Native Wayland gives an application no way to position its own window:\n // `setPosition` is a no-op and `getPosition` reads back [0, 0]\n // (https://github.com/electron/electron/issues/40886). The compositor\n // decides where the window lands, usually centered, so the popover can't\n // be anchored to the tray. Detect that the move didn't take and warn once,\n // pointing at the X11 fallback. Gated on a Wayland session (`WAYLAND_DISPLAY`)\n // so pure X11 never trips it; the tolerance absorbs the few-pixel offsets\n // X11 window managers add for decorations, so XWayland (where positioning\n // works) doesn't trip it either.\n if (\n process.platform === 'linux' &&\n !!process.env.WAYLAND_DISPLAY &&\n !this._warnedNoPositioning\n ) {\n const [actualX, actualY] = this._browserWindow.getPosition();\n const ignored =\n Math.abs(actualX - targetX) > 24 || Math.abs(actualY - targetY) > 24;\n if (ignored) {\n this._warnedNoPositioning = true;\n console.warn(\n '[menubar] The window could not be positioned programmatically, ' +\n 'which is expected on native Wayland where the compositor controls ' +\n 'placement. The popover will not be anchored to the tray icon. Run ' +\n 'with --ozone-platform=x11 to restore tray-relative positioning.',\n );\n }\n }\n }\n\n private async appReady(): Promise<void> {\n if (this.app.dock && !this._options.showDockIcon) {\n this.app.dock.hide();\n\n // The hide above can be silently dropped when it races the launch\n // activation transition. Re-check once startup has settled; guarded on\n // visibility because `dock.hide()` also deactivates the app, so it must\n // not run when the dock is already hidden.\n this._dockRehideTimeout = setTimeout(() => {\n if (!this._isDestroyed && this.app.dock?.isVisible()) {\n this.app.dock.hide();\n }\n }, DOCK_REHIDE_DELAY_MS);\n }\n\n if (this._options.activateWithApp) {\n this.app.on('activate', this.onAppActivate);\n }\n\n let trayImage =\n this._options.icon || path.join(this._options.dir, 'IconTemplate.png');\n if (typeof trayImage === 'string' && !fs.existsSync(trayImage)) {\n trayImage = path.join(__dirname, '..', 'assets', 'IconTemplate.png'); // Default cat icon\n }\n\n // The deprecated `showOnRightClick` option stays honoured until the next\n // major release removes it (see {@link Options.showOnRightClick}).\n const trigger =\n this._options.trigger ??\n (this._options.showOnRightClick ? 'right-click' : 'click'); // NOSONAR:typescript:S1874\n\n this._tray = this._options.tray || new Tray(trayImage);\n // Type guards for TS not to complain\n if (!this.tray) {\n throw new Error('Tray has been initialized above');\n }\n if (trigger !== 'none') {\n this.tray.on(trigger as Parameters<Tray['on']>[0], this.clicked);\n this.tray.on('double-click', this.clicked);\n }\n // macOS-only: ignore double-click so an accidental second click doesn't\n // race the blur handler and cause a tray-icon flicker.\n if (\n process.platform === 'darwin' &&\n this._options.ignoreDoubleClickEvents\n ) {\n this.tray.setIgnoreDoubleClickEvents(true);\n }\n this.tray.setToolTip(this._options.tooltip);\n\n if (this._options.contextMenu) {\n this.bindContextMenu(this._options.contextMenu);\n }\n\n if (this._options.globalShortcut) {\n this.setGlobalShortcut(this._options.globalShortcut);\n }\n\n if (!this._options.windowPosition) {\n this._options.windowPosition = getWindowPosition(this.tray);\n }\n\n if (this._options.preloadWindow) {\n await this.createWindow();\n }\n\n this.emit('ready');\n }\n\n /**\n * Callback on tray icon click or double-click.\n *\n * @param e\n * @param bounds\n */\n private readonly clicked = async (\n event?: Electron.KeyboardEvent,\n bounds?: Electron.Rectangle,\n ): Promise<void> => {\n if (event && (event.shiftKey || event.ctrlKey || event.metaKey)) {\n return this.hideWindow();\n }\n\n // if blur was invoked clear timeout\n if (this._blurTimeout) {\n clearInterval(this._blurTimeout);\n }\n\n if (this._browserWindow && this._isVisible) {\n return this.hideWindow();\n }\n\n this._cachedBounds = bounds || this._cachedBounds;\n await this.showWindow(this._cachedBounds);\n };\n\n private readonly onAppActivate = (\n _event: Electron.Event,\n hasVisibleWindows: boolean,\n ): void => {\n if (!hasVisibleWindows) {\n this.showWindow().catch(console.error);\n }\n };\n\n /**\n * Marks the app as shutting down so `hideOnClose` stops intercepting closes.\n *\n * Bound to both `app`'s `before-quit` and the auto updater's\n * `before-quit-for-update`. Installing an update never emits `before-quit`:\n * Electron closes every window first and only quits once the window list is\n * empty, so a `hideOnClose` veto there would leave the window open and the\n * install waiting forever.\n */\n private readonly onBeforeQuit = (): void => {\n this._isQuitting = true;\n };\n\n private bindContextMenu(menu: Menu): void {\n this._contextMenu = menu;\n if (process.platform === 'linux') {\n // libappindicator / StatusNotifierItem requires the menu to live on the\n // tray itself; right-click is handled by the desktop environment.\n this.tray.setContextMenu(menu);\n return;\n }\n this.bindRightClickContextMenu();\n }\n\n private bindRightClickContextMenu(): void {\n // macOS / Windows: pop up the current menu on right-click so left-click\n // stays bound to toggling the menubar window. Read `this._contextMenu`\n // at invoke time so `setContextMenu()` swaps and clears take effect\n // without rebinding (and without leaking a stale closure reference).\n this.tray.on('right-click', (_event, bounds) => {\n const current = this._contextMenu;\n if (!current) {\n return;\n }\n this.tray.popUpContextMenu(current, { x: bounds.x, y: bounds.y });\n });\n this._rightClickContextMenuBound = true;\n }\n\n private readonly onAppReady = (): void => {\n // Guard against `destroy()` being called between construction and the\n // scheduled `process.nextTick`/`'ready'` firing.\n if (this._isDestroyed) {\n return;\n }\n this.appReady().catch((err) => console.error('menubar: ', err));\n };\n\n private async createWindow(): Promise<void> {\n this.emit('create-window');\n\n // We add some default behavior for menubar's browserWindow, to make it\n // look like a menubar\n const defaults = {\n show: false, // Don't show it at first\n frame: false, // Remove window frame\n };\n\n this._browserWindow = new BrowserWindow({\n ...defaults,\n ...this._options.browserWindow,\n });\n\n this._positioner = new Positioner(this._browserWindow);\n\n this.applyWindowFocusBehavior();\n this._browserWindow.webContents.on('devtools-opened', () => {\n this.applyWindowFocusBehavior();\n });\n this._browserWindow.webContents.on('devtools-closed', () => {\n this.applyWindowFocusBehavior();\n });\n\n this._browserWindow.on('blur', () => {\n if (!this._browserWindow) {\n return;\n }\n\n if (!this.shouldHideOnBlur()) {\n this.emit('focus-lost');\n return;\n }\n\n // Windows foreground-activation race: clicking the tray icon keeps the\n // shell (explorer.exe) as the foreground process, so `show()` cannot pull\n // focus to the popup and Windows immediately fires `blur`. Left unguarded,\n // the hide timer below fires before the first paint and the window never\n // visibly appears (gitify-app/gitify#3064). Ignore blur events that land\n // within the grace window right after a show; a genuine click-away\n // arrives well after it.\n if (\n process.platform === 'win32' &&\n Date.now() - this._lastShowTime < BLUR_HIDE_GRACE_MS\n ) {\n return;\n }\n\n if (this._blurTimeout) {\n clearTimeout(this._blurTimeout);\n }\n this._blurTimeout = setTimeout(() => {\n this._blurTimeout = null;\n if (this.shouldHideOnBlur()) {\n this.hideWindow();\n }\n }, 100);\n });\n\n this._showOnAllWorkspaces = this._options.showOnAllWorkspaces !== false;\n this.applyWorkspaceVisibility(true);\n\n if (this._options.hideOnClose) {\n this._browserWindow.on('close', (event) => {\n if (this._isDestroyed || this._isQuitting) {\n return;\n }\n event.preventDefault();\n // Defer the hide for Wayland: hiding synchronously from the `close`\n // handler can leave frameless surfaces in a half-closed state.\n setImmediate(() => this.hideWindow());\n });\n }\n\n if (this._options.escapeToHide) {\n this._browserWindow.webContents.on(\n 'before-input-event',\n (_event, input) => {\n if (input.type === 'keyDown' && input.key === 'Escape') {\n this.hideWindow();\n }\n },\n );\n }\n\n // Use `closed` (not `close`) so consumer `close` listeners can still read\n // `mb.window` and call `event.preventDefault()` without racing our cleanup.\n this._browserWindow.on('closed', this.windowClear.bind(this));\n\n // Re-anchor the window to the tray when its size changes (e.g. via\n // `mb.window.setSize(...)`), so the window doesn't end up clipped under\n // the taskbar. https://github.com/maxogden/menubar/issues/349\n this._browserWindow.on('resize', this.positionWindow);\n\n this.emit('before-load');\n\n // If the user explicity set options.index to false, we don't loadURL\n // https://github.com/maxogden/menubar/issues/255\n if (this._options.index !== false) {\n await this._browserWindow.loadURL(\n this._options.index,\n this._options.loadUrlOptions,\n );\n }\n this.emit('after-create-window');\n }\n\n /**\n * Join or leave every workspace when `showOnAllWorkspaces` is enabled.\n *\n * On macOS the hidden popup leaves every Space in {@link hideWindow} and\n * rejoins them right before `show()` in {@link showWindow}. macOS can drop\n * a hidden window from all but one Space while `isVisibleOnAllWorkspaces()`\n * still reports `true`, e.g. when the fullscreen Space the popup was last\n * shown on closes. Showing it then switches to that one Space, and setting\n * the flag again to the value it already has does not re-add the window.\n * Clearing the flag while hidden makes the next show a real change, which\n * re-adds the window to every Space before it appears.\n */\n private applyWorkspaceVisibility(visible: boolean): void {\n if (\n !this._browserWindow ||\n this._browserWindow.isDestroyed() ||\n !this._showOnAllWorkspaces\n ) {\n return;\n }\n // https://github.com/electron/electron/issues/37832#issuecomment-1497882944\n this._browserWindow.setVisibleOnAllWorkspaces(visible, {\n // Maps to NSWindowCollectionBehaviorFullScreenAuxiliary, which\n // Electron clears when the flag is omitted. Without it the popup\n // cannot appear inside a fullscreen space, so macOS switches to\n // another space to show it.\n visibleOnFullScreen: visible,\n skipTransformProcessType: true, // Avoid damaging the original visible state of app.dock\n });\n }\n\n private windowClear(): void {\n this._browserWindow = undefined;\n this.emit('after-close');\n }\n}\n","/**\n * Entry point of menubar\n * @example\n * ```typescript\n * import { menubar } from 'menubar';\n * ```\n */\n\n/** */\n\nimport { app } from 'electron';\n\nimport { Menubar } from './Menubar';\nimport type { Options } from './types';\n\nexport * from './util/getWindowPosition';\nexport { Menubar };\n\n/**\n * Factory function to create a menubar application\n *\n * @param options - Options for creating a menubar application, see\n * {@link Options}\n */\nexport function menubar(options?: Partial<Options>): Menubar {\n return new Menubar(app, options);\n}\n"],"mappings":";;;;;;;;;;;AA6BA,IAAa,aAAb,MAAwB;CACtB;CAEA,YAAY,eAA8B;EACxC,KAAK,gBAAgB;CACvB;CAEA,UACE,UACA,YAC0B;EAC1B,IAAI,CAAC,UACH,MAAM,IAAI,UACR,0DACF;EAGF,MAAM,aAAa,aACf,OAAO,mBAAmB,UAAU,CAAC,CAAC,WACtC,OAAO,uBAAuB,OAAO,qBAAqB,CAAC,CAAC,CAAC;EACjE,MAAM,CAAC,aAAa,gBAAgB,KAAK,cAAc,QAAQ;EAC/D,MAAM,QAAQ,YAAY,KAAK;EAC/B,MAAM,YAAY,YAAY,SAAS;EAEvC,MAAM,YAA8D;GAClE,UAAU;IACR,GAAG,KAAK,MAAM,KAAK;IACnB,GAAG,WAAW;GAChB;GACA,gBAAgB;IACd,GAAG,KAAK,MAAM,KAAK;IACnB,GAAG,KAAK,MAAM,WAAW,UAAU,eAAe,WAAW,EAAE;GACjE;GACA,WAAW;IACT,GAAG,KAAK,MAAM,QAAQ,cAAc,SAAS;IAC7C,GAAG,WAAW;GAChB;GACA,iBAAiB;IACf,GAAG,KAAK,MAAM,QAAQ,cAAc,SAAS;IAC7C,GAAG,KAAK,MAAM,WAAW,UAAU,eAAe,WAAW,EAAE;GACjE;GACA,YAAY;IACV,GAAG,KAAK,MAAM,QAAQ,cAAc,IAAI,YAAY,CAAC;IACrD,GAAG,WAAW;GAChB;GACA,kBAAkB;IAChB,GAAG,KAAK,MAAM,QAAQ,cAAc,IAAI,YAAY,CAAC;IACrD,GAAG,KAAK,MAAM,WAAW,UAAU,eAAe,WAAW,EAAE;GACjE;GACA,SAAS;IACP,GAAG,WAAW;IACd,GAAG,WAAW;GAChB;GACA,UAAU;IACR,GAAG,KAAK,MAAM,WAAW,KAAK,WAAW,QAAQ,YAAY;IAC7D,GAAG,WAAW;GAChB;GACA,YAAY;IACV,GAAG,WAAW;IACd,GAAG,KAAK,MAAM,WAAW,UAAU,eAAe,WAAW,EAAE;GACjE;GACA,aAAa;IACX,GAAG,KAAK,MAAM,WAAW,KAAK,WAAW,QAAQ,YAAY;IAC7D,GAAG,KAAK,MAAM,WAAW,UAAU,eAAe,WAAW,EAAE;GACjE;GACA,WAAW;IACT,GAAG,KAAK,MAAM,WAAW,KAAK,WAAW,QAAQ,IAAI,cAAc,EAAE;IACrE,GAAG,WAAW;GAChB;GACA,cAAc;IACZ,GAAG,KAAK,MAAM,WAAW,KAAK,WAAW,QAAQ,IAAI,cAAc,EAAE;IACrE,GAAG,KAAK,MAAM,WAAW,UAAU,eAAe,WAAW,EAAE;GACjE;GACA,YAAY;IACV,GAAG,WAAW;IACd,GACE,WAAW,IACX,KAAK,MAAM,WAAW,SAAS,CAAC,IAChC,KAAK,MAAM,eAAe,CAAC;GAC/B;GACA,aAAa;IACX,GAAG,KAAK,MAAM,WAAW,KAAK,WAAW,QAAQ,YAAY;IAC7D,GACE,WAAW,IACX,KAAK,MAAM,WAAW,SAAS,CAAC,IAChC,KAAK,MAAM,eAAe,CAAC;GAC/B;GACA,QAAQ;IACN,GAAG,KAAK,MAAM,WAAW,KAAK,WAAW,QAAQ,IAAI,cAAc,EAAE;IACrE,GAAG,KAAK,OACL,WAAW,SAAS,WAAW,KAAK,IAAI,eAAe,CAC1D;GACF;EACF;EAEA,MAAM,SAAS,UAAU;EAKzB,IAAI,SAAS,WAAW,MAAM,GACxB;OAAA,OAAO,IAAI,cAAc,WAAW,QAAQ,WAAW,GACzD,OAAO;IAAE,GAAG,UAAU,SAAS;IAAG,GAAG,OAAO;GAAE;EAAA;EAIlD,OAAO;CACT;AACF;;;;;;;AC5HA,MAAM,wBAAwB;AAC9B,MAAM,uBAAuB;;;;;;;AAQ7B,SAAgB,aAAa,MAAkC;CAC7D,MAAM,UAA4B,EAAE,GAAG,KAAK;CAE5C,QAAQ,oBAAoB;CAC5B,QAAQ,4BAA4B;CACpC,IAAI,CAAC,QAAQ,KACX,QAAQ,MAAM,IAAI,WAAW;CAE/B,IAAI,CAAC,KAAK,WAAW,QAAQ,GAAG,GAC9B,QAAQ,MAAM,KAAK,QAAQ,QAAQ,GAAG;CAGxC,QAAQ,UAAU,IAAI,OAAO;EAC3B,UAAU,KAAK,KAAK,QAAQ,KAAK,YAAY;EAC7C,UAAU;EACV,SAAS;CACX,CAAC;CACD,QAAQ,iBAAiB,QAAQ,kBAAkB,CAAC;CAEpD,QAAQ,UAAU,QAAQ,WAAW;CAMrC,IAAI,CAAC,QAAQ,eACX,QAAQ,gBAAgB,CAAC;CAI3B,QAAQ,cAAc,QAGpB,QAAQ,cAAc,UAAU,KAAA,IAC5B,QAAQ,cAAc,QACtB;CACN,QAAQ,cAAc,SACpB,QAAQ,cAAc,WAAW,KAAA,IAC7B,QAAQ,cAAc,SACtB;CAEN,OAAO;AACT;;;;;;;ACxDA,MAAM,UAAU,QAAQ,aAAa;AAErC,MAAM,qBAAqB,eAAkD;CAE3E,MAAM,EAAE,UAAU,QAAQ,iBACxBA,OAAe,mBAAmB,UAAU;CAE9C,OAAO,CACL,cACA;EACE,GAAG;EACH,GAAG,SAAS,IAAI,aAAa;EAC7B,GAAG,SAAS,IAAI,aAAa;CAC/B,CACF;AACF;;;;;;;;AAWA,SAAgB,gBAAgB,MAA6B;CAC3D,MAAM,aAAa,KAAK,UAAU;CAClC,MAAM,CAAC,cAAc,YAAY,kBAAkB,UAAU;CAE7D,IACE,QAAQ,aAAa,WACrB,WAAW,QAAQ,KACnB,WAAW,SAAS,GACpB;EAGA,MAAM,IAAI,WAAW,IAAI,WAAW,QAAQ,IAAI,aAAa;EAC7D,MAAM,IAAI,WAAW,IAAI,WAAW,SAAS,IAAI,aAAa;EAC9D,MAAM,SAA0C;GAC9C,KAAK,SAAS;GACd,QAAQ,aAAa,SAAS,SAAS,IAAI,SAAS;GACpD,MAAM,SAAS;GACf,OAAO,aAAa,QAAQ,SAAS,IAAI,SAAS;EACpD;EACA,IAAI,UAAqC,CACvC,UACA,KAAK,IAAI,aAAa,SAAS,CAAC,CAClC;EACA,MAAM,QAAqC;GACzC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC;GACnB,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC;GACpB,CAAC,SAAS,KAAK,IAAI,aAAa,QAAQ,CAAC,CAAC;EAC5C;EACA,KAAK,MAAM,QAAQ,OAEjB,IACE,KAAK,KAAK,QAAQ,MACjB,KAAK,OAAO,QAAQ,MAAM,OAAO,KAAK,MAAM,OAAO,QAAQ,KAE5D,UAAU;EAEd,OAAO,QAAQ;CACjB;CAGA,IAAI,SAAS,IAAI,GAAG;EAElB,IAAI,WAAW,SAAS,IAAI,GAAG,OAAO;EAEtC,OAAO;CACT;CAGA,IAAI,SAAS,IAAI,GACf,OAAO;CAOT,IAAI,SAAS,QAAQ,aAAa,OAGhC,OAAO;CAKT,OAAO;AACT;;;;;;;AAeA,SAAgB,kBAAkB,MAA4B;CAC5D,QAAQ,QAAQ,UAAhB;EAGE,KAAK,UACH,OAAO;EAIT,KAAK;EACL,KAAK,SAAS;GACZ,MAAM,WAAW,gBAAgB,IAAI;GAGrC,IAAI,aAAa,OACf,OAAO,UAAU,aAAa;GAEhC,IAAI,aAAa,UACf,OAAO;GAET,IAAI,aAAa,QAOf,OAAO;GAET,IAAI,aAAa,SACf,OAAO;EAEX;CACF;CAGA,OAAO;AACT;;;;;;;;ACjIA,MAAM,qBAAqB;;;;;;;AAQ3B,MAAM,uBAAuB;;;;AAK7B,IAAa,UAAb,cAA6B,aAAa;CACxC;CACA;CACA;CACA,eAA8C;CAC9C;CACA;CACA;CACA;CACA;CACA;CACA;CACA,8BAAsC;CACtC,uBAA+B;CAC/B,gBAAwB;CACxB;CACA,iBAAyB;CACzB,uBAA+B;CAC/B;CAEA,YAAY,KAAmB,SAA4B;EACzD,MAAM;EACN,KAAK,OAAO;EACZ,KAAK,WAAW,aAAa,OAAO;EACpC,KAAK,eAAe;EACpB,KAAK,cAAc;EACnB,KAAK,aAAa;EAElB,IAAI,GAAG,eAAe,KAAK,YAAY;EACvC,YAAY,GAAG,0BAA0B,KAAK,YAAY;EAE1D,IAAI,IAAI,QAAQ,GAEd,QAAQ,SAAS,KAAK,UAAU;OAEhC,IAAI,GAAG,SAAS,KAAK,UAAU;CAEnC;;;;;CAMA,IAAI,MAAoB;EACtB,OAAO,KAAK;CACd;;;;;CAMA,IAAI,aAAyB;EAC3B,IAAI,CAAC,KAAK,aACR,MAAM,IAAI,MACR,kFACF;EAGF,OAAO,KAAK;CACd;;;;CAKA,IAAI,OAAa;EACf,IAAI,CAAC,KAAK,OACR,MAAM,IAAI,MACR,8DACF;EAGF,OAAO,KAAK;CACd;;;;;CAMA,IAAI,SAAoC;EACtC,OAAO,KAAK;CACd;;;;;;CAOA,UAAgB;EACd,IAAI,KAAK,YAAY,GACnB;EAIF,KAAK,eAAe;EAEpB,IAAI,KAAK,WAAW;GAClB,eAAe,WAAW,KAAK,SAAS;GACxC,KAAK,YAAY,KAAA;EACnB;EAEA,IAAI,KAAK,oBAAoB;GAC3B,aAAa,KAAK,kBAAkB;GACpC,KAAK,qBAAqB,KAAA;EAC5B;EAEA,IAAI,KAAK,gBAAgB;GACvB,KAAK,eAAe,QAAQ;GAC5B,KAAK,iBAAiB,KAAA;EACxB;EAEA,IAAI,KAAK,OAAO;GAEd,KAAK,MAAM,SAAS;IAAC;IAAS;IAAe;GAAc,GACzD,KAAK,MAAM,eACT,OACA,KAAK,OACP;GAEF,KAAK,MAAM,WAAW,EAAE;GACxB,KAAK,QAAQ,KAAA;EACf;EAEA,KAAK,KAAK,eAAe,SAAS,KAAK,UAAU;EACjD,KAAK,KAAK,eAAe,YAAY,KAAK,aAAa;EACvD,KAAK,KAAK,eAAe,eAAe,KAAK,YAAY;EACzD,YAAY,eAAe,0BAA0B,KAAK,YAAY;CACxE;;;;CAKA,cAAuB;EACrB,OAAO,KAAK;CACd;;;;;;CAOA,UAAmC,KAAoB;EACrD,OAAO,KAAK,SAAS;CACvB;;;;CAKA,aAAmB;EACjB,IAAI,CAAC,KAAK,kBAAkB,CAAC,KAAK,YAChC;EAEF,KAAK,KAAK,MAAM;EAChB,KAAK,eAAe,KAAK;EACzB,IAAI,QAAQ,aAAa,UACvB,KAAK,yBAAyB,KAAK;EAErC,KAAK,KAAK,YAAY;EACtB,KAAK,aAAa;EAClB,IAAI,KAAK,cAAc;GACrB,aAAa,KAAK,YAAY;GAC9B,KAAK,eAAe;EACtB;EACA,KAAK,mBAAmB;CAC1B;;;;;;;;;;;CAYA,kBAAkB,aAAwD;EACxE,IAAI,KAAK,WAAW;GAClB,eAAe,WAAW,KAAK,SAAS;GACxC,KAAK,YAAY,KAAA;EACnB;EACA,KAAK,SAAS,iBAAiB;EAC/B,IAAI,CAAC,aACH,OAAO;EAET,MAAM,KAAK,eAAe,SAAS,mBAAmB,KAAK,aAAa,CAAC;EACzE,IAAI,IACF,KAAK,YAAY;EAEnB,OAAO;CACT;;;;;CAMA,MAAM,eAA8B;EAClC,IAAI,KAAK,kBAAkB,KAAK,YAAY;GAC1C,KAAK,WAAW;GAChB;EACF;EACA,MAAM,KAAK,WAAW;CACxB;;;;;;;CAQA,iBAAuB;EACrB,IAAI,CAAC,KAAK,kBAAkB,CAAC,KAAK,OAChC;EAEF,MAAM,SAAS,KAAK,MAAM,UAAU;EACpC,MAAM,EAAE,GAAG,MAAM,KAAK,WAAW,UAAU,cAAc,MAAM;EAC/D,KAAK,eAAe,YAAY,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;CAC9D;;;;;;;;;;CAWA,eAAe,MAAyB;EACtC,KAAK,eAAe,QAAQ,KAAA;EAC5B,KAAK,SAAS,cAAc,QAAQ,KAAA;EACpC,IAAI,CAAC,KAAK,OACR;EAEF,IAAI,QAAQ,aAAa,SAAS;GAEhC,KAAK,MAAM,eAAe,IAAI;GAC9B;EACF;EAKA,IAAI,QAAQ,CAAC,KAAK,6BAChB,KAAK,0BAA0B;CAEnC;;;;;;;;;;;CAYA,qBAA2B;EACzB,IACE,QAAQ,aAAa,WACrB,KAAK,gBACL,KAAK,SACL,CAAC,KAAK,MAAM,cAAc,GAE1B,KAAK,MAAM,eAAe,KAAK,YAAY;CAE/C;;;;;;;CAQA,UAAmC,KAAQ,OAAyB;EAClE,KAAK,SAAS,OAAO;EACrB,IAAI,QAAQ,gBAAgB,QAAQ,iBAClC,KAAK,yBAAyB;CAElC;CAEA,mBAAoC;EAClC,MAAM,MAAM,KAAK;EACjB,OACE,CAAC,CAAC,OACF,CAAC,IAAI,YAAY,KACjB,KAAK,SAAS,eAAe,SAC7B,CAAC,IAAI,YAAY,iBAAiB;CAEtC;CAEA,2BAAyC;EACvC,MAAM,MAAM,KAAK;EACjB,IAAI,CAAC,OAAO,IAAI,YAAY,GAC1B;EAGF,MAAM,WAAW,CAAC,KAAK,iBAAiB;EACxC,IAAI,KAAK,cAAc;GACrB,aAAa,KAAK,YAAY;GAC9B,KAAK,eAAe;EACtB;EAEA,IAAI,QAAQ,aAAa,SACvB,IAAI,eAAe,MAAM,aAAa;OAEtC,IAAI,eACF,YAAY,KAAK,SAAS,cAAc,gBAAgB,IAC1D;CAEJ;;;;;;CAOA,MAAM,WAAW,SAA6C;EAC5D,IAAI,CAAC,KAAK,MACR,MAAM,IAAI,MAAM,2CAA2C;EAG7D,IAAI,CAAC,KAAK,gBACR,MAAM,KAAK,aAAa;EAI1B,IAAI,CAAC,KAAK,gBACR,MAAM,IAAI,MAAM,8CAA8C;EAGhE,KAAK,KAAK,MAAM;EAKhB,IAAI,WAAW,QAAQ,MAAM,GAC3B,KAAK,gBAAgB;OAChB,IAAI,CAAC,KAAK,iBAAiB,KAAK,KAAK,WAC1C,KAAK,gBAAgB,KAAK,KAAK,UAAU;EAG3C,IAAI,QAAQ,aAAa,UACvB,KAAK,yBAAyB,IAAI;EAEpC,KAAK,eAAe;EAGpB,KAAK,yBAAyB;EAC9B,KAAK,gBAAgB,KAAK,IAAI;EAC9B,KAAK,eAAe,KAAK;EACzB,KAAK,aAAa;EAClB,KAAK,KAAK,YAAY;EACtB,KAAK,mBAAmB;CAC1B;;;;;;;CAQA,uBAA8C;EAC5C,IAAI,CAAC,KAAK,kBAAkB,CAAC,KAAK,OAChC;EASF,IAAI,KAAK,gBACP;EAEF,KAAK,iBAAiB;EACtB,IAAI;GACF,KAAK,oBAAoB;EAC3B,UAAU;GACR,KAAK,iBAAiB;EACxB;CACF;;;;;CAMA,sBAAoC;EAClC,IAAI,CAAC,KAAK,kBAAkB,CAAC,KAAK,OAChC;EAKF,IAAI,CAAC,SAAS,OAAO,CAAC,CAAC,SAAS,QAAQ,QAAQ,GAC9C,KAAK,SAAS,iBAAiB,kBAAkB,KAAK,KAAK;EAG7D,MAAM,UAAU,KAAK,iBAAiB,KAAK,MAAM,YAAY;EAG7D,IAAI;EACJ,KACG,YAAY,KAAA,KAAa,QAAQ,MAAM,MACxC,KAAK,SAAS,gBAAgB,WAAW,MAAM,GAE/C,mBACE,QAAQ,aAAa,UAAU,gBAAgB;EAGnD,MAAM,WAAW,KAAK,WAAW,UAC/B,KAAK,SAAS,kBAAkB,kBAChC,OACF;EAGA,MAAM,IACJ,KAAK,SAAS,cAAc,MAAM,KAAA,IAC9B,KAAK,SAAS,cAAc,IAC5B,SAAS;EACf,MAAM,IACJ,KAAK,SAAS,cAAc,MAAM,KAAA,IAC9B,KAAK,SAAS,cAAc,IAC5B,SAAS;EAIf,MAAM,UAAU,KAAK,MAAM,CAAC;EAC5B,MAAM,UAAU,KAAK,MAAM,CAAC;EAC5B,KAAK,eAAe,YAAY,SAAS,OAAO;EAWhD,IACE,QAAQ,aAAa,WACrB,CAAC,CAAC,QAAQ,IAAI,mBACd,CAAC,KAAK,sBACN;GACA,MAAM,CAAC,SAAS,WAAW,KAAK,eAAe,YAAY;GAG3D,IADE,KAAK,IAAI,UAAU,OAAO,IAAI,MAAM,KAAK,IAAI,UAAU,OAAO,IAAI,IACvD;IACX,KAAK,uBAAuB;IAC5B,QAAQ,KACN,oQAIF;GACF;EACF;CACF;CAEA,MAAc,WAA0B;EACtC,IAAI,KAAK,IAAI,QAAQ,CAAC,KAAK,SAAS,cAAc;GAChD,KAAK,IAAI,KAAK,KAAK;GAMnB,KAAK,qBAAqB,iBAAiB;IACzC,IAAI,CAAC,KAAK,gBAAgB,KAAK,IAAI,MAAM,UAAU,GACjD,KAAK,IAAI,KAAK,KAAK;GAEvB,GAAG,oBAAoB;EACzB;EAEA,IAAI,KAAK,SAAS,iBAChB,KAAK,IAAI,GAAG,YAAY,KAAK,aAAa;EAG5C,IAAI,YACF,KAAK,SAAS,QAAQ,KAAK,KAAK,KAAK,SAAS,KAAK,kBAAkB;EACvE,IAAI,OAAO,cAAc,YAAY,CAAC,GAAG,WAAW,SAAS,GAC3D,YAAY,KAAK,KAAK,WAAW,MAAM,UAAU,kBAAkB;EAKrE,MAAM,UACJ,KAAK,SAAS,YACb,KAAK,SAAS,mBAAmB,gBAAgB;EAEpD,KAAK,QAAQ,KAAK,SAAS,QAAQ,IAAI,KAAK,SAAS;EAErD,IAAI,CAAC,KAAK,MACR,MAAM,IAAI,MAAM,iCAAiC;EAEnD,IAAI,YAAY,QAAQ;GACtB,KAAK,KAAK,GAAG,SAAsC,KAAK,OAAO;GAC/D,KAAK,KAAK,GAAG,gBAAgB,KAAK,OAAO;EAC3C;EAGA,IACE,QAAQ,aAAa,YACrB,KAAK,SAAS,yBAEd,KAAK,KAAK,2BAA2B,IAAI;EAE3C,KAAK,KAAK,WAAW,KAAK,SAAS,OAAO;EAE1C,IAAI,KAAK,SAAS,aAChB,KAAK,gBAAgB,KAAK,SAAS,WAAW;EAGhD,IAAI,KAAK,SAAS,gBAChB,KAAK,kBAAkB,KAAK,SAAS,cAAc;EAGrD,IAAI,CAAC,KAAK,SAAS,gBACjB,KAAK,SAAS,iBAAiB,kBAAkB,KAAK,IAAI;EAG5D,IAAI,KAAK,SAAS,eAChB,MAAM,KAAK,aAAa;EAG1B,KAAK,KAAK,OAAO;CACnB;;;;;;;CAQA,UAA2B,OACzB,OACA,WACkB;EAClB,IAAI,UAAU,MAAM,YAAY,MAAM,WAAW,MAAM,UACrD,OAAO,KAAK,WAAW;EAIzB,IAAI,KAAK,cACP,cAAc,KAAK,YAAY;EAGjC,IAAI,KAAK,kBAAkB,KAAK,YAC9B,OAAO,KAAK,WAAW;EAGzB,KAAK,gBAAgB,UAAU,KAAK;EACpC,MAAM,KAAK,WAAW,KAAK,aAAa;CAC1C;CAEA,iBACE,QACA,sBACS;EACT,IAAI,CAAC,mBACH,KAAK,WAAW,CAAC,CAAC,MAAM,QAAQ,KAAK;CAEzC;;;;;;;;;;CAWA,qBAA4C;EAC1C,KAAK,cAAc;CACrB;CAEA,gBAAwB,MAAkB;EACxC,KAAK,eAAe;EACpB,IAAI,QAAQ,aAAa,SAAS;GAGhC,KAAK,KAAK,eAAe,IAAI;GAC7B;EACF;EACA,KAAK,0BAA0B;CACjC;CAEA,4BAA0C;EAKxC,KAAK,KAAK,GAAG,gBAAgB,QAAQ,WAAW;GAC9C,MAAM,UAAU,KAAK;GACrB,IAAI,CAAC,SACH;GAEF,KAAK,KAAK,iBAAiB,SAAS;IAAE,GAAG,OAAO;IAAG,GAAG,OAAO;GAAE,CAAC;EAClE,CAAC;EACD,KAAK,8BAA8B;CACrC;CAEA,mBAA0C;EAGxC,IAAI,KAAK,cACP;EAEF,KAAK,SAAS,CAAC,CAAC,OAAO,QAAQ,QAAQ,MAAM,aAAa,GAAG,CAAC;CAChE;CAEA,MAAc,eAA8B;EAC1C,KAAK,KAAK,eAAe;EAIzB,MAAM,WAAW;GACf,MAAM;GACN,OAAO;EACT;EAEA,KAAK,iBAAiB,IAAI,cAAc;GACtC,GAAG;GACH,GAAG,KAAK,SAAS;EACnB,CAAC;EAED,KAAK,cAAc,IAAI,WAAW,KAAK,cAAc;EAErD,KAAK,yBAAyB;EAC9B,KAAK,eAAe,YAAY,GAAG,yBAAyB;GAC1D,KAAK,yBAAyB;EAChC,CAAC;EACD,KAAK,eAAe,YAAY,GAAG,yBAAyB;GAC1D,KAAK,yBAAyB;EAChC,CAAC;EAED,KAAK,eAAe,GAAG,cAAc;GACnC,IAAI,CAAC,KAAK,gBACR;GAGF,IAAI,CAAC,KAAK,iBAAiB,GAAG;IAC5B,KAAK,KAAK,YAAY;IACtB;GACF;GASA,IACE,QAAQ,aAAa,WACrB,KAAK,IAAI,IAAI,KAAK,gBAAgB,oBAElC;GAGF,IAAI,KAAK,cACP,aAAa,KAAK,YAAY;GAEhC,KAAK,eAAe,iBAAiB;IACnC,KAAK,eAAe;IACpB,IAAI,KAAK,iBAAiB,GACxB,KAAK,WAAW;GAEpB,GAAG,GAAG;EACR,CAAC;EAED,KAAK,uBAAuB,KAAK,SAAS,wBAAwB;EAClE,KAAK,yBAAyB,IAAI;EAElC,IAAI,KAAK,SAAS,aAChB,KAAK,eAAe,GAAG,UAAU,UAAU;GACzC,IAAI,KAAK,gBAAgB,KAAK,aAC5B;GAEF,MAAM,eAAe;GAGrB,mBAAmB,KAAK,WAAW,CAAC;EACtC,CAAC;EAGH,IAAI,KAAK,SAAS,cAChB,KAAK,eAAe,YAAY,GAC9B,uBACC,QAAQ,UAAU;GACjB,IAAI,MAAM,SAAS,aAAa,MAAM,QAAQ,UAC5C,KAAK,WAAW;EAEpB,CACF;EAKF,KAAK,eAAe,GAAG,UAAU,KAAK,YAAY,KAAK,IAAI,CAAC;EAK5D,KAAK,eAAe,GAAG,UAAU,KAAK,cAAc;EAEpD,KAAK,KAAK,aAAa;EAIvB,IAAI,KAAK,SAAS,UAAU,OAC1B,MAAM,KAAK,eAAe,QACxB,KAAK,SAAS,OACd,KAAK,SAAS,cAChB;EAEF,KAAK,KAAK,qBAAqB;CACjC;;;;;;;;;;;;;CAcA,yBAAiC,SAAwB;EACvD,IACE,CAAC,KAAK,kBACN,KAAK,eAAe,YAAY,KAChC,CAAC,KAAK,sBAEN;EAGF,KAAK,eAAe,0BAA0B,SAAS;GAKrD,qBAAqB;GACrB,0BAA0B;EAC5B,CAAC;CACH;CAEA,cAA4B;EAC1B,KAAK,iBAAiB,KAAA;EACtB,KAAK,KAAK,aAAa;CACzB;AACF;;;;;;;;;;;;;;;;;AChwBA,SAAgB,QAAQ,SAAqC;CAC3D,OAAO,IAAI,QAAQ,KAAK,OAAO;AACjC"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["electronScreen"],"sources":["../src/Positioner.ts","../src/util/cleanOptions.ts","../src/util/getWindowPosition.ts","../src/Menubar.ts","../src/index.ts"],"sourcesContent":["import { type BrowserWindow, type Rectangle, screen } from 'electron';\n\n/**\n * Named anchor points for placing the menubar window. The `tray*` values are\n * relative to the tray icon's bounds; the rest are relative to the work area\n * of the display containing the cursor (or the tray, when bounds are given).\n */\nexport type WindowPosition =\n | 'trayLeft'\n | 'trayBottomLeft'\n | 'trayRight'\n | 'trayBottomRight'\n | 'trayCenter'\n | 'trayBottomCenter'\n | 'topLeft'\n | 'topRight'\n | 'bottomLeft'\n | 'bottomRight'\n | 'topCenter'\n | 'bottomCenter'\n | 'leftCenter'\n | 'rightCenter'\n | 'center';\n\n/**\n * Computes `{x, y}` coordinates for placing a {@link BrowserWindow} at a named\n * position, optionally relative to a tray icon's bounds. Ported from\n * `electron-positioner@4.1.0` to drop the unmaintained runtime dependency.\n */\nexport class Positioner {\n private readonly browserWindow: BrowserWindow;\n\n constructor(browserWindow: BrowserWindow) {\n this.browserWindow = browserWindow;\n }\n\n /**\n * @param windowSize - Size to place, when it differs from the window's\n * current size (e.g. the size about to be applied with `setBounds`).\n */\n calculate(\n position?: WindowPosition,\n trayBounds?: Rectangle,\n windowSize?: { width: number; height: number },\n ): { x: number; y: number } {\n if (!position) {\n throw new TypeError(\n 'Positioner.calculate: a `position` argument is required.',\n );\n }\n\n const screenSize = trayBounds\n ? screen.getDisplayMatching(trayBounds).workArea\n : screen.getDisplayNearestPoint(screen.getCursorScreenPoint()).workArea;\n const [windowWidth, windowHeight] = windowSize\n ? [windowSize.width, windowSize.height]\n : this.browserWindow.getSize();\n const trayX = trayBounds?.x ?? Number.NaN;\n const trayWidth = trayBounds?.width ?? Number.NaN;\n\n const positions: Record<WindowPosition, { x: number; y: number }> = {\n trayLeft: {\n x: Math.floor(trayX),\n y: screenSize.y,\n },\n trayBottomLeft: {\n x: Math.floor(trayX),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n trayRight: {\n x: Math.floor(trayX - windowWidth + trayWidth),\n y: screenSize.y,\n },\n trayBottomRight: {\n x: Math.floor(trayX - windowWidth + trayWidth),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n trayCenter: {\n x: Math.floor(trayX - windowWidth / 2 + trayWidth / 2),\n y: screenSize.y,\n },\n trayBottomCenter: {\n x: Math.floor(trayX - windowWidth / 2 + trayWidth / 2),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n topLeft: {\n x: screenSize.x,\n y: screenSize.y,\n },\n topRight: {\n x: Math.floor(screenSize.x + (screenSize.width - windowWidth)),\n y: screenSize.y,\n },\n bottomLeft: {\n x: screenSize.x,\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n bottomRight: {\n x: Math.floor(screenSize.x + (screenSize.width - windowWidth)),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n topCenter: {\n x: Math.floor(screenSize.x + (screenSize.width / 2 - windowWidth / 2)),\n y: screenSize.y,\n },\n bottomCenter: {\n x: Math.floor(screenSize.x + (screenSize.width / 2 - windowWidth / 2)),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n leftCenter: {\n x: screenSize.x,\n y:\n screenSize.y +\n Math.floor(screenSize.height / 2) -\n Math.floor(windowHeight / 2),\n },\n rightCenter: {\n x: Math.floor(screenSize.x + (screenSize.width - windowWidth)),\n y:\n screenSize.y +\n Math.floor(screenSize.height / 2) -\n Math.floor(windowHeight / 2),\n },\n center: {\n x: Math.floor(screenSize.x + (screenSize.width / 2 - windowWidth / 2)),\n y: Math.floor(\n (screenSize.height + screenSize.y) / 2 - windowHeight / 2,\n ),\n },\n };\n\n const coords = positions[position];\n\n // On Windows, a tray-relative position can push the window past the right\n // edge of the work area. Snap back to `topRight` x in that case so it stays\n // visible. See https://github.com/jenslind/electron-positioner.\n if (position.startsWith('tray')) {\n if (coords.x + windowWidth > screenSize.width + screenSize.x) {\n return { x: positions.topRight.x, y: coords.y };\n }\n }\n\n return coords;\n }\n}\n","/**\n * @ignore\n */\n\n/** */\n\nimport path from 'node:path';\nimport url from 'node:url';\n\nimport { app } from 'electron';\n\nimport type { Options } from '../types';\n\nconst DEFAULT_WINDOW_HEIGHT = 400;\nconst DEFAULT_WINDOW_WIDTH = 400;\n\n/**\n * Take as input some options, and return a sanitized version of it.\n *\n * @param opts - The options to clean.\n * @ignore\n */\nexport function cleanOptions(opts?: Partial<Options>): Options {\n const options: Partial<Options> = { ...opts };\n\n options.activateWithApp ??= true;\n options.ignoreDoubleClickEvents ??= true;\n if (!options.dir) {\n options.dir = app.getAppPath();\n }\n if (!path.isAbsolute(options.dir)) {\n options.dir = path.resolve(options.dir);\n }\n // Note: options.index can be `false`\n options.index ??= url.format({\n pathname: path.join(options.dir, 'index.html'),\n protocol: 'file:',\n slashes: true,\n });\n options.loadUrlOptions = options.loadUrlOptions || {};\n\n options.tooltip = options.tooltip || '';\n\n // `icon`, `preloadWindow`, `showDockIcon`, `showOnAllWorkspaces`,\n // `showOnRightClick` don't need any special treatment\n\n // Now we take care of `browserWindow`\n if (!options.browserWindow) {\n options.browserWindow = {};\n }\n\n // Set width/height on options to be usable before the window is created\n options.browserWindow.width =\n // Note: not using `options.browserWindow.width || DEFAULT_WINDOW_WIDTH` so\n // that users can put a 0 width\n options.browserWindow.width !== undefined\n ? options.browserWindow.width\n : DEFAULT_WINDOW_WIDTH;\n options.browserWindow.height =\n options.browserWindow.height !== undefined\n ? options.browserWindow.height\n : DEFAULT_WINDOW_HEIGHT;\n\n return options as Options;\n}\n","/**\n * Utilities to get taskbar position and consequently menubar's position\n */\n\n/** */\n\nimport { screen as electronScreen, type Rectangle, type Tray } from 'electron';\n\nconst isLinux = process.platform === 'linux';\n\nconst trayToScreenRects = (trayBounds: Rectangle): [Rectangle, Rectangle] => {\n // There may be more than one screen, so we need to figure out on which screen our tray icon lives.\n const { workArea, bounds: screenBounds } =\n electronScreen.getDisplayMatching(trayBounds);\n\n return [\n screenBounds,\n {\n ...workArea,\n x: workArea.x - screenBounds.x,\n y: workArea.y - screenBounds.y,\n },\n ];\n};\n\ntype TaskbarLocation = 'top' | 'bottom' | 'left' | 'right';\n\n/**\n * Determine taskbard location: \"top\", \"bottom\", \"left\" or \"right\".\n *\n * Only tested on Windows for now, and only used in Windows.\n *\n * @param tray - The Electron Tray instance.\n */\nexport function taskbarLocation(tray: Tray): TaskbarLocation {\n const trayBounds = tray.getBounds();\n const [screenBounds, workArea] = trayToScreenRects(trayBounds);\n\n if (\n process.platform === 'win32' &&\n trayBounds.width > 0 &&\n trayBounds.height > 0\n ) {\n // Other appbars also reserve work-area space. The tray's nearest display\n // edge identifies the taskbar without mistaking a PowerToys dock for it.\n const x = trayBounds.x + trayBounds.width / 2 - screenBounds.x;\n const y = trayBounds.y + trayBounds.height / 2 - screenBounds.y;\n const insets: Record<TaskbarLocation, number> = {\n top: workArea.y,\n bottom: screenBounds.height - workArea.y - workArea.height,\n left: workArea.x,\n right: screenBounds.width - workArea.x - workArea.width,\n };\n let nearest: [TaskbarLocation, number] = [\n 'bottom',\n Math.abs(screenBounds.height - y),\n ];\n const edges: [TaskbarLocation, number][] = [\n ['top', Math.abs(y)],\n ['left', Math.abs(x)],\n ['right', Math.abs(screenBounds.width - x)],\n ];\n for (const edge of edges) {\n // At a corner, prefer the edge that actually reserves work-area space.\n if (\n edge[1] < nearest[1] ||\n (edge[1] === nearest[1] && insets[edge[0]] > insets[nearest[0]])\n )\n nearest = edge;\n }\n return nearest[0];\n }\n\n // TASKBAR LEFT\n if (workArea.x > 0) {\n // Most likely Ubuntu hence assuming the window should be on top\n if (isLinux && workArea.y > 0) return 'top';\n // The workspace starts more on the right\n return 'left';\n }\n\n // TASKBAR TOP\n if (workArea.y > 0) {\n return 'top';\n }\n\n // TASKBAR RIGHT\n // Here both workArea.y and workArea.x are 0 so we can no longer leverage them.\n // We can use the workarea and display width though.\n // Determine taskbar location\n if (workArea.width < screenBounds.width) {\n // The taskbar is either on the left or right, but since the LEFT case was handled above,\n // we can be sure we're dealing with a right taskbar\n return 'right';\n }\n\n // TASKBAR BOTTOM\n // Since all the other cases were handled, we can be sure we're dealing with a bottom taskbar\n return 'bottom';\n}\n\ntype WindowPosition =\n | 'trayCenter'\n | 'topRight'\n | 'trayBottomCenter'\n | 'leftCenter'\n | 'bottomRight';\n\n/**\n * Depending on where the taskbar is, determine where the window should be\n * positioned.\n *\n * @param tray - The Electron Tray instance.\n */\nexport function getWindowPosition(tray: Tray): WindowPosition {\n switch (process.platform) {\n // macOS\n // Supports top taskbars\n case 'darwin':\n return 'trayCenter';\n // Linux\n // Windows\n // Supports top/bottom/left/right taskbar\n case 'linux':\n case 'win32': {\n const traySide = taskbarLocation(tray);\n\n // Assign position for menubar\n if (traySide === 'top') {\n return isLinux ? 'topRight' : 'trayCenter';\n }\n if (traySide === 'bottom') {\n return 'bottomRight';\n }\n if (traySide === 'left') {\n // Vertically centered against the left edge of the work area.\n // `bottomLeft` would put the window in the screen's bottom-left\n // corner — visually disconnected from the tray icon, which sits\n // somewhere on the left strip. Tray-anchored y isn't available\n // through `Positioner` for side taskbars, so center is the best\n // compromise that stays close to the tray.\n return 'leftCenter';\n }\n if (traySide === 'right') {\n return 'bottomRight';\n }\n }\n }\n\n // When we really don't know, we just show the menubar on the top-right\n return 'topRight';\n}\n","import { EventEmitter } from 'node:events';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n autoUpdater,\n BrowserWindow,\n globalShortcut,\n type Menu,\n screen,\n Tray,\n} from 'electron';\n\nimport { Positioner } from './Positioner';\nimport type { Options } from './types';\nimport { cleanOptions } from './util/cleanOptions';\nimport { getWindowPosition } from './util/getWindowPosition';\n\n/**\n * Grace period after `showWindow()` during which a `blur` on the popup is\n * treated as the spurious Windows post-show blur and ignored instead of\n * triggering the auto-hide. See the `blur` handler in {@link Menubar.createWindow}.\n */\nconst BLUR_HIDE_GRACE_MS = 400;\n\n/**\n * Window after a blur-triggered hide during which a tray click on Linux is\n * taken as the click that caused the blur, so it leaves the popup closed. See\n * {@link Menubar.clicked}.\n */\nconst TRAY_CLICK_AFTER_BLUR_HIDE_MS = 500;\n\n/**\n * Delay before re-asserting the hidden dock after startup. macOS can silently\n * drop an `app.dock.hide()` that races the app's launch activation\n * transition, leaving the dock icon stuck for the whole session. See the\n * re-check in {@link Menubar.appReady}.\n */\nconst DOCK_REHIDE_DELAY_MS = 2_000;\n\n/**\n * Largest size change per dimension, in DIPs, taken as rounding drift from\n * moving the window on a display with a fractional scale factor. A move there\n * changes each dimension by up to 2 DIPs. See {@link Menubar.holdSize}.\n */\nconst FRACTIONAL_SCALE_DRIFT = 4;\n\ntype Size = { width: number; height: number };\n\n/**\n * The main Menubar class.\n */\nexport class Menubar extends EventEmitter {\n private readonly _app: Electron.App;\n private _browserWindow?: BrowserWindow;\n private _contextMenu?: Menu;\n private _blurTimeout: NodeJS.Timeout | null = null; // track blur events with timeout\n private _isDestroyed: boolean;\n private _isQuitting: boolean; // set when the app is shutting down, used by hideOnClose\n private _isVisible: boolean; // track visibility\n private _cachedBounds?: Electron.Rectangle; // _cachedBounds are needed for double-clicked event\n private readonly _options: Options;\n private _positioner: Positioner | undefined;\n private _shortcut?: Electron.Accelerator;\n private _rightClickContextMenuBound = false;\n private _warnedNoPositioning = false; // guards the one-time Wayland warning\n private _lastShowTime = 0; // timestamp of last show(), debounces the post-show blur on Windows\n private _lastBlurHideTime = 0; // timestamp of the last blur-triggered hide, debounces tray clicks on Linux\n private _dockRehideTimeout?: NodeJS.Timeout; // pending post-startup dock re-hide check\n private _repositioning = false; // guards against re-entrant positionWindow calls\n private _heldSize?: Size; // size requested on each move, see holdSize\n private _showOnAllWorkspaces = false; // `showOnAllWorkspaces` as read when the window was created\n private _tray?: Tray;\n\n constructor(app: Electron.App, options?: Partial<Options>) {\n super();\n this._app = app;\n this._options = cleanOptions(options);\n this._isDestroyed = false;\n this._isQuitting = false;\n this._isVisible = false;\n\n app.on('before-quit', this.onBeforeQuit);\n autoUpdater.on('before-quit-for-update', this.onBeforeQuit);\n\n if (app.isReady()) {\n // See https://github.com/maxogden/menubar/pull/151\n process.nextTick(this.onAppReady);\n } else {\n app.on('ready', this.onAppReady);\n }\n }\n\n /**\n * The Electron [App](https://electronjs.org/docs/api/app)\n * instance.\n */\n get app(): Electron.App {\n return this._app;\n }\n\n /**\n * The {@link Positioner} instance used to compute where the menubar window\n * should appear on screen. Available after the `after-create-window` event.\n */\n get positioner(): Positioner {\n if (!this._positioner) {\n throw new Error(\n 'Please access `this.positioner` after the `after-create-window` event has fired.',\n );\n }\n\n return this._positioner;\n }\n\n /**\n * The Electron [Tray](https://electronjs.org/docs/api/tray) instance.\n */\n get tray(): Tray {\n if (!this._tray) {\n throw new Error(\n 'Please access `this.tray` after the `ready` event has fired.',\n );\n }\n\n return this._tray;\n }\n\n /**\n * The Electron [BrowserWindow](https://electronjs.org/docs/api/browser-window)\n * instance, if it's present.\n */\n get window(): BrowserWindow | undefined {\n return this._browserWindow;\n }\n\n /**\n * Tear down the menubar instance: destroy the window, remove the tray, and\n * detach all listeners. Subsequent clicks on the tray will be no-ops until a\n * new {@link Menubar} instance is created.\n */\n destroy(): void {\n if (this.isDestroyed()) {\n return;\n }\n // Set first so `hideOnClose` lets the close go through instead of\n // intercepting it.\n this._isDestroyed = true;\n\n if (this._shortcut) {\n globalShortcut.unregister(this._shortcut);\n this._shortcut = undefined;\n }\n\n if (this._dockRehideTimeout) {\n clearTimeout(this._dockRehideTimeout);\n this._dockRehideTimeout = undefined;\n }\n\n if (this._browserWindow) {\n this._browserWindow.destroy();\n this._browserWindow = undefined;\n }\n\n if (this._tray) {\n // Ensure all potential listeners are removed.\n for (const event of ['click', 'right-click', 'double-click']) {\n this._tray.removeListener(\n event as Parameters<Tray['on']>[0],\n this.clicked,\n );\n }\n this._tray.setToolTip('');\n this._tray = undefined;\n }\n\n this._app.removeListener('ready', this.onAppReady);\n this._app.removeListener('activate', this.onAppActivate);\n this._app.removeListener('before-quit', this.onBeforeQuit);\n autoUpdater.removeListener('before-quit-for-update', this.onBeforeQuit);\n }\n\n /**\n * Whether {@link destroy} has been called on this menubar instance.\n */\n isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Retrieve a menubar option.\n *\n * @param key - The option key to retrieve, see {@link Options}.\n */\n getOption<K extends keyof Options>(key: K): Options[K] {\n return this._options[key];\n }\n\n /**\n * Hide the menubar window.\n */\n hideWindow(): void {\n if (!this._browserWindow || !this._isVisible) {\n return;\n }\n this.emit('hide');\n this._browserWindow.hide();\n if (process.platform === 'darwin') {\n this.applyWorkspaceVisibility(false);\n }\n this.emit('after-hide');\n this._isVisible = false;\n if (this._blurTimeout) {\n clearTimeout(this._blurTimeout);\n this._blurTimeout = null;\n }\n this.refreshContextMenu();\n }\n\n /**\n * Register a global keyboard accelerator that toggles the menubar window.\n * Replaces any previously registered shortcut owned by this Menubar.\n * Pass `undefined` to clear the current shortcut without registering a new\n * one. Returns whether the registration succeeded.\n *\n * @param accelerator - An Electron\n * [Accelerator](https://electronjs.org/docs/api/accelerator) string, or\n * `undefined` to clear.\n */\n setGlobalShortcut(accelerator: Electron.Accelerator | undefined): boolean {\n if (this._shortcut) {\n globalShortcut.unregister(this._shortcut);\n this._shortcut = undefined;\n }\n this._options.globalShortcut = accelerator;\n if (!accelerator) {\n return true;\n }\n const ok = globalShortcut.register(accelerator, () => this.toggleWindow());\n if (ok) {\n this._shortcut = accelerator;\n }\n return ok;\n }\n\n /**\n * Toggle the menubar window: hide it if visible, show it otherwise.\n * Resolves once the window finishes showing or hiding.\n */\n async toggleWindow(): Promise<void> {\n if (this._browserWindow && this._isVisible) {\n this.hideWindow();\n return;\n }\n await this.showWindow();\n }\n\n /**\n * Re-center the menubar window over the tray icon. Convenience wrapper for\n * `positioner.move('trayCenter', tray.getBounds())` that's safe to call\n * after the `after-create-window` event. No-op if the window doesn't\n * exist yet.\n */\n recenterOnTray(): void {\n if (!this._browserWindow || !this._tray) {\n return;\n }\n const bounds = this._tray.getBounds();\n const size = this.holdSize(this._browserWindow);\n const { x, y } = this.positioner.calculate('trayCenter', bounds, size);\n this._browserWindow.setBounds({\n x: Math.round(x),\n y: Math.round(y),\n ...size,\n });\n }\n\n /**\n * Replace the tray context menu after construction. On Linux this also\n * re-publishes the menu to the SNI host, which is required after mutating\n * items in-place since libappindicator caches the previous serialization.\n * On macOS/Windows the right-click popup handler reads the current menu\n * reference, so swapping or clearing here takes effect immediately.\n *\n * @param menu - The new menu, or `null` to clear it.\n */\n setContextMenu(menu: Menu | null): void {\n this._contextMenu = menu ?? undefined;\n this._options.contextMenu = menu ?? undefined;\n if (!this._tray) {\n return;\n }\n if (process.platform === 'linux') {\n // `setContextMenu(null)` clears the menu on Linux.\n this._tray.setContextMenu(menu);\n return;\n }\n // macOS / Windows: bind the right-click popup once on first non-empty\n // assignment. The handler reads `this._contextMenu` at invoke time, so\n // later swaps and clears take effect without rebinding (and never leak\n // a stale closure reference).\n if (menu && !this._rightClickContextMenuBound) {\n this.bindRightClickContextMenu();\n }\n }\n\n /**\n * Re-publish the current context menu so in-place mutations of its items\n * (`visible`, `enabled`, `checked`, `label`) become visible to the user.\n *\n * Only Linux needs this: libappindicator / StatusNotifierItem serializes the\n * menu once and serves that copy until it is set again, so a mutated item\n * would otherwise keep rendering its old state until the next show or hide.\n * A no-op on macOS and Windows, where the menu is read at popup time — call\n * it unconditionally after mutating items.\n */\n refreshContextMenu(): void {\n if (\n process.platform === 'linux' &&\n this._contextMenu &&\n this._tray &&\n !this._tray.isDestroyed?.()\n ) {\n this._tray.setContextMenu(this._contextMenu);\n }\n }\n\n /**\n * Change an option after menubar is created.\n *\n * @param key - The option key to modify, see {@link Options}.\n * @param value - The value to set.\n */\n setOption<K extends keyof Options>(key: K, value: Options[K]): void {\n this._options[key] = value;\n if (key === 'hideOnBlur' || key === 'browserWindow') {\n this.applyWindowFocusBehavior();\n }\n }\n\n private shouldHideOnBlur(): boolean {\n const win = this._browserWindow;\n return (\n !!win &&\n !win.isDestroyed() &&\n this._options.hideOnBlur !== false &&\n !win.webContents.isDevToolsOpened()\n );\n }\n\n private applyWindowFocusBehavior(): void {\n const win = this._browserWindow;\n if (!win || win.isDestroyed()) {\n return;\n }\n\n const keepOpen = !this.shouldHideOnBlur();\n if (this._blurTimeout) {\n clearTimeout(this._blurTimeout);\n this._blurTimeout = null;\n }\n\n if (process.platform === 'win32') {\n win.setAlwaysOnTop(true, 'pop-up-menu');\n } else {\n win.setAlwaysOnTop(\n keepOpen || this._options.browserWindow.alwaysOnTop === true,\n );\n }\n }\n\n /**\n * Show the menubar window.\n *\n * @param trayPos - The bounds to show the window in.\n */\n async showWindow(trayPos?: Electron.Rectangle): Promise<void> {\n if (!this.tray) {\n throw new Error('Tray should have been instantiated by now');\n }\n\n if (!this._browserWindow) {\n await this.createWindow();\n }\n\n // Use guard for TypeScript, to avoid ! everywhere\n if (!this._browserWindow) {\n throw new Error('Window has been initialized just above. qed.');\n }\n\n this.emit('show');\n\n // Cache fresh tray bounds (or fall back to existing cache / tray bounds)\n // so `positionWindow` can reposition without an event payload — for\n // example when the window resizes via `setSize`.\n if (trayPos && trayPos.x !== 0) {\n this._cachedBounds = trayPos;\n } else if (!this._cachedBounds && this.tray.getBounds) {\n this._cachedBounds = this.tray.getBounds();\n }\n\n if (process.platform === 'darwin') {\n this.applyWorkspaceVisibility(true);\n }\n this.positionWindow();\n // Record the show time before `show()` so the blur handler can recognise\n // and ignore the transient blur Windows fires right after (see below).\n this.applyWindowFocusBehavior();\n this._lastShowTime = Date.now();\n this._lastBlurHideTime = 0;\n this._browserWindow.show();\n this._isVisible = true;\n this.emit('after-show');\n this.refreshContextMenu();\n }\n\n /**\n * Compute and apply the tray-anchored position of the browser window. Safe\n * to call any time after `createWindow` has run — invoked from\n * {@link showWindow} on every show, and from the window's `resize` event so\n * `setSize` calls reposition the window correctly.\n */\n private readonly positionWindow = (): void => {\n if (!this._browserWindow || !this._tray) {\n return;\n }\n\n // Moving the window below can make Windows emit `resize` synchronously, which\n // re-enters this method through the window's `resize` listener. Each pass\n // nudges the window again, so the recursion never unwinds: the main\n // process wedges with the event loop blocked and the window grows past\n // screen size (gitify-app/gitify#3064). Ignore re-entrant calls; the\n // outermost one already applies the final position.\n if (this._repositioning) {\n return;\n }\n this._repositioning = true;\n try {\n this.applyWindowPosition();\n } finally {\n this._repositioning = false;\n }\n };\n\n /**\n * Re-anchor the window after the app resizes it (e.g. via `setSize`).\n * Ignores the resize that moving the window itself causes; see\n * {@link holdSize}.\n */\n private readonly onWindowResize = (): void => {\n if (!this._browserWindow) {\n return;\n }\n // On X11 with fractional display scaling, a move can emit `resize` a tick\n // later because the window's size changed by a pixel or two.\n // Repositioning on that echo moves the window again, which echoes again:\n // the window grows thousands of pixels a second until the GPU process\n // crashes and the main process stops responding. The re-entrancy guard in\n // positionWindow can't catch it because the echo arrives asynchronously.\n const [width, height] = this._browserWindow.getSize();\n const held = this._heldSize;\n const drift = this.sizeDrift(this._browserWindow);\n if (\n held &&\n Math.abs(width - held.width) <= drift &&\n Math.abs(height - held.height) <= drift\n ) {\n return;\n }\n this.positionWindow();\n };\n\n /**\n * Pick the size to apply with the next move and remember it.\n *\n * On X11 with fractional display scaling, `setPosition` grows the window by\n * a pixel or two per call, and so does `setBounds` with the size `getSize`\n * reports, because each conversion between DIPs and device pixels rounds\n * up. That drift often lands without a `resize` event, so only the size can\n * tell it apart: a dimension within {@link sizeDrift} of the held size keeps\n * the held value, and any other value came from the app and becomes the new\n * held one.\n */\n private holdSize(win: BrowserWindow): Size {\n const [width, height] = win.getSize();\n const held = this._heldSize;\n const drift = this.sizeDrift(win);\n const size: Size = {\n width: held && Math.abs(width - held.width) <= drift ? held.width : width,\n height:\n held && Math.abs(height - held.height) <= drift ? held.height : height,\n };\n this._heldSize = size;\n return size;\n }\n\n /**\n * Size change per dimension, in DIPs, that moving the window can cause.\n * Rounding drift only happens at a fractional scale factor, so on any other\n * display every size change counts as the app's. At a fractional scale, an\n * app resize of up to this many DIPs is indistinguishable from drift and is\n * not kept.\n */\n private sizeDrift(win: BrowserWindow): number {\n const { scaleFactor } = screen.getDisplayMatching(win.getBounds());\n return Number.isInteger(scaleFactor) ? 0 : FRACTIONAL_SCALE_DRIFT;\n }\n\n /**\n * Compute and apply the tray-anchored position. Always call through\n * {@link positionWindow}, which guards against re-entrancy.\n */\n private applyWindowPosition(): void {\n if (!this._browserWindow || !this._tray) {\n return;\n }\n\n // 'Windows' taskbar: sync window position each time before positioning.\n // https://github.com/maxogden/menubar/issues/232\n if (['win32', 'linux'].includes(process.platform)) {\n this._options.windowPosition = getWindowPosition(this._tray);\n }\n\n const trayPos = this._cachedBounds ?? this._tray.getBounds?.();\n\n // Default the window to the right if `trayPos` bounds are undefined or null.\n let noBoundsPosition: Options['windowPosition'];\n if (\n (trayPos === undefined || trayPos.x === 0) &&\n this._options.windowPosition?.startsWith('tray')\n ) {\n noBoundsPosition =\n process.platform === 'win32' ? 'bottomRight' : 'topRight';\n }\n\n // Place the size that is about to be applied, which can differ from the\n // current one by rounding drift.\n const size = this.holdSize(this._browserWindow);\n const position = this.positioner.calculate(\n this._options.windowPosition || noBoundsPosition,\n trayPos,\n size,\n ) as { x: number; y: number };\n\n // Not using `||` because x and y can be zero.\n const x =\n this._options.browserWindow.x !== undefined\n ? this._options.browserWindow.x\n : position.x;\n const y =\n this._options.browserWindow.y !== undefined\n ? this._options.browserWindow.y\n : position.y;\n\n // Electron's window move APIs crashed on non-integers\n // https://github.com/maxogden/menubar/issues/233\n const targetX = Math.round(x);\n const targetY = Math.round(y);\n this._browserWindow.setBounds({ x: targetX, y: targetY, ...size });\n\n // Native Wayland gives an application no way to position its own window:\n // moving the window is a no-op and `getPosition` reads back [0, 0]\n // (https://github.com/electron/electron/issues/40886). The compositor\n // decides where the window lands, usually centered, so the popover can't\n // be anchored to the tray. Detect that the move didn't take and warn once,\n // pointing at the X11 fallback. Gated on a Wayland session (`WAYLAND_DISPLAY`)\n // so pure X11 never trips it; the tolerance absorbs the few-pixel offsets\n // X11 window managers add for decorations, so XWayland (where positioning\n // works) doesn't trip it either.\n if (\n process.platform === 'linux' &&\n !!process.env.WAYLAND_DISPLAY &&\n !this._warnedNoPositioning\n ) {\n const [actualX, actualY] = this._browserWindow.getPosition();\n const ignored =\n Math.abs(actualX - targetX) > 24 || Math.abs(actualY - targetY) > 24;\n if (ignored) {\n this._warnedNoPositioning = true;\n console.warn(\n '[menubar] The window could not be positioned programmatically, ' +\n 'which is expected on native Wayland where the compositor controls ' +\n 'placement. The popover will not be anchored to the tray icon. Run ' +\n 'with --ozone-platform=x11 to restore tray-relative positioning.',\n );\n }\n }\n }\n\n private async appReady(): Promise<void> {\n if (this.app.dock && !this._options.showDockIcon) {\n this.app.dock.hide();\n\n // The hide above can be silently dropped when it races the launch\n // activation transition. Re-check once startup has settled; guarded on\n // visibility because `dock.hide()` also deactivates the app, so it must\n // not run when the dock is already hidden.\n this._dockRehideTimeout = setTimeout(() => {\n if (!this._isDestroyed && this.app.dock?.isVisible()) {\n this.app.dock.hide();\n }\n }, DOCK_REHIDE_DELAY_MS);\n }\n\n if (this._options.activateWithApp) {\n this.app.on('activate', this.onAppActivate);\n }\n\n let trayImage =\n this._options.icon || path.join(this._options.dir, 'IconTemplate.png');\n if (typeof trayImage === 'string' && !fs.existsSync(trayImage)) {\n trayImage = path.join(__dirname, '..', 'assets', 'IconTemplate.png'); // Default cat icon\n }\n\n // The deprecated `showOnRightClick` option stays honoured until the next\n // major release removes it (see {@link Options.showOnRightClick}).\n const trigger =\n this._options.trigger ??\n (this._options.showOnRightClick ? 'right-click' : 'click'); // NOSONAR:typescript:S1874\n\n this._tray = this._options.tray || new Tray(trayImage);\n // Type guards for TS not to complain\n if (!this.tray) {\n throw new Error('Tray has been initialized above');\n }\n if (trigger !== 'none') {\n this.tray.on(trigger as Parameters<Tray['on']>[0], this.clicked);\n this.tray.on('double-click', this.clicked);\n }\n // macOS-only: ignore double-click so an accidental second click doesn't\n // race the blur handler and cause a tray-icon flicker.\n if (\n process.platform === 'darwin' &&\n this._options.ignoreDoubleClickEvents\n ) {\n this.tray.setIgnoreDoubleClickEvents(true);\n }\n this.tray.setToolTip(this._options.tooltip);\n\n if (this._options.contextMenu) {\n this.bindContextMenu(this._options.contextMenu);\n }\n\n if (this._options.globalShortcut) {\n this.setGlobalShortcut(this._options.globalShortcut);\n }\n\n if (!this._options.windowPosition) {\n this._options.windowPosition = getWindowPosition(this.tray);\n }\n\n if (this._options.preloadWindow) {\n await this.createWindow();\n }\n\n this.emit('ready');\n }\n\n /**\n * Callback on tray icon click or double-click.\n *\n * @param e\n * @param bounds\n */\n private readonly clicked = async (\n event?: Electron.KeyboardEvent,\n bounds?: Electron.Rectangle,\n ): Promise<void> => {\n if (event && (event.shiftKey || event.ctrlKey || event.metaKey)) {\n return this.hideWindow();\n }\n\n // if blur was invoked clear timeout\n if (this._blurTimeout) {\n clearInterval(this._blurTimeout);\n }\n\n if (this._browserWindow && this._isVisible) {\n return this.hideWindow();\n }\n\n // Linux tray hosts deliver the click on button release over D-Bus, while\n // pressing the tray icon already moved focus away from the popup. A press\n // held past the blur timer hides the popup first, and the click would then\n // reopen it. Treat a click that soon after a blur hide as the toggle.\n if (\n process.platform === 'linux' &&\n Date.now() - this._lastBlurHideTime < TRAY_CLICK_AFTER_BLUR_HIDE_MS\n ) {\n this._lastBlurHideTime = 0;\n return;\n }\n\n this._cachedBounds = bounds || this._cachedBounds;\n await this.showWindow(this._cachedBounds);\n };\n\n private readonly onAppActivate = (\n _event: Electron.Event,\n hasVisibleWindows: boolean,\n ): void => {\n if (!hasVisibleWindows) {\n this.showWindow().catch(console.error);\n }\n };\n\n /**\n * Marks the app as shutting down so `hideOnClose` stops intercepting closes.\n *\n * Bound to both `app`'s `before-quit` and the auto updater's\n * `before-quit-for-update`. Installing an update never emits `before-quit`:\n * Electron closes every window first and only quits once the window list is\n * empty, so a `hideOnClose` veto there would leave the window open and the\n * install waiting forever.\n */\n private readonly onBeforeQuit = (): void => {\n this._isQuitting = true;\n };\n\n private bindContextMenu(menu: Menu): void {\n this._contextMenu = menu;\n if (process.platform === 'linux') {\n // libappindicator / StatusNotifierItem requires the menu to live on the\n // tray itself; right-click is handled by the desktop environment.\n this.tray.setContextMenu(menu);\n return;\n }\n this.bindRightClickContextMenu();\n }\n\n private bindRightClickContextMenu(): void {\n // macOS / Windows: pop up the current menu on right-click so left-click\n // stays bound to toggling the menubar window. Read `this._contextMenu`\n // at invoke time so `setContextMenu()` swaps and clears take effect\n // without rebinding (and without leaking a stale closure reference).\n this.tray.on('right-click', (_event, bounds) => {\n const current = this._contextMenu;\n if (!current) {\n return;\n }\n this.tray.popUpContextMenu(current, { x: bounds.x, y: bounds.y });\n });\n this._rightClickContextMenuBound = true;\n }\n\n private readonly onAppReady = (): void => {\n // Guard against `destroy()` being called between construction and the\n // scheduled `process.nextTick`/`'ready'` firing.\n if (this._isDestroyed) {\n return;\n }\n this.appReady().catch((err) => console.error('menubar: ', err));\n };\n\n private async createWindow(): Promise<void> {\n this.emit('create-window');\n\n // We add some default behavior for menubar's browserWindow, to make it\n // look like a menubar\n const defaults = {\n show: false, // Don't show it at first\n frame: false, // Remove window frame\n };\n\n this._browserWindow = new BrowserWindow({\n ...defaults,\n ...this._options.browserWindow,\n });\n\n this._positioner = new Positioner(this._browserWindow);\n\n this.applyWindowFocusBehavior();\n this._browserWindow.webContents.on('devtools-opened', () => {\n this.applyWindowFocusBehavior();\n });\n this._browserWindow.webContents.on('devtools-closed', () => {\n this.applyWindowFocusBehavior();\n });\n\n this._browserWindow.on('blur', () => {\n if (!this._browserWindow) {\n return;\n }\n\n if (!this.shouldHideOnBlur()) {\n this.emit('focus-lost');\n return;\n }\n\n // Windows foreground-activation race: clicking the tray icon keeps the\n // shell (explorer.exe) as the foreground process, so `show()` cannot pull\n // focus to the popup and Windows immediately fires `blur`. Left unguarded,\n // the hide timer below fires before the first paint and the window never\n // visibly appears (gitify-app/gitify#3064). Ignore blur events that land\n // within the grace window right after a show; a genuine click-away\n // arrives well after it.\n if (\n process.platform === 'win32' &&\n Date.now() - this._lastShowTime < BLUR_HIDE_GRACE_MS\n ) {\n return;\n }\n\n if (this._blurTimeout) {\n clearTimeout(this._blurTimeout);\n }\n this._blurTimeout = setTimeout(() => {\n this._blurTimeout = null;\n if (this.shouldHideOnBlur()) {\n this.hideWindow();\n this._lastBlurHideTime = Date.now();\n }\n }, 100);\n });\n\n this._showOnAllWorkspaces = this._options.showOnAllWorkspaces !== false;\n this.applyWorkspaceVisibility(true);\n\n if (this._options.hideOnClose) {\n this._browserWindow.on('close', (event) => {\n if (this._isDestroyed || this._isQuitting) {\n return;\n }\n event.preventDefault();\n // Defer the hide for Wayland: hiding synchronously from the `close`\n // handler can leave frameless surfaces in a half-closed state.\n setImmediate(() => this.hideWindow());\n });\n }\n\n if (this._options.escapeToHide) {\n this._browserWindow.webContents.on(\n 'before-input-event',\n (_event, input) => {\n if (input.type === 'keyDown' && input.key === 'Escape') {\n this.hideWindow();\n }\n },\n );\n }\n\n // Use `closed` (not `close`) so consumer `close` listeners can still read\n // `mb.window` and call `event.preventDefault()` without racing our cleanup.\n this._browserWindow.on('closed', this.windowClear.bind(this));\n\n // Re-anchor the window to the tray when its size changes (e.g. via\n // `mb.window.setSize(...)`), so the window doesn't end up clipped under\n // the taskbar. https://github.com/maxogden/menubar/issues/349\n this._browserWindow.on('resize', this.onWindowResize);\n\n this.emit('before-load');\n\n // If the user explicity set options.index to false, we don't loadURL\n // https://github.com/maxogden/menubar/issues/255\n if (this._options.index !== false) {\n await this._browserWindow.loadURL(\n this._options.index,\n this._options.loadUrlOptions,\n );\n }\n this.emit('after-create-window');\n }\n\n /**\n * Join or leave every workspace when `showOnAllWorkspaces` is enabled.\n *\n * On macOS the hidden popup leaves every Space in {@link hideWindow} and\n * rejoins them right before `show()` in {@link showWindow}. macOS can drop\n * a hidden window from all but one Space while `isVisibleOnAllWorkspaces()`\n * still reports `true`, e.g. when the fullscreen Space the popup was last\n * shown on closes. Showing it then switches to that one Space, and setting\n * the flag again to the value it already has does not re-add the window.\n * Clearing the flag while hidden makes the next show a real change, which\n * re-adds the window to every Space before it appears.\n */\n private applyWorkspaceVisibility(visible: boolean): void {\n if (\n !this._browserWindow ||\n this._browserWindow.isDestroyed() ||\n !this._showOnAllWorkspaces\n ) {\n return;\n }\n // https://github.com/electron/electron/issues/37832#issuecomment-1497882944\n this._browserWindow.setVisibleOnAllWorkspaces(visible, {\n // Maps to NSWindowCollectionBehaviorFullScreenAuxiliary, which\n // Electron clears when the flag is omitted. Without it the popup\n // cannot appear inside a fullscreen space, so macOS switches to\n // another space to show it.\n visibleOnFullScreen: visible,\n skipTransformProcessType: true, // Avoid damaging the original visible state of app.dock\n });\n }\n\n private windowClear(): void {\n this._browserWindow = undefined;\n this._heldSize = undefined;\n this.emit('after-close');\n }\n}\n","/**\n * Entry point of menubar\n * @example\n * ```typescript\n * import { menubar } from 'menubar';\n * ```\n */\n\n/** */\n\nimport { app } from 'electron';\n\nimport { Menubar } from './Menubar';\nimport type { Options } from './types';\n\nexport * from './util/getWindowPosition';\nexport { Menubar };\n\n/**\n * Factory function to create a menubar application\n *\n * @param options - Options for creating a menubar application, see\n * {@link Options}\n */\nexport function menubar(options?: Partial<Options>): Menubar {\n return new Menubar(app, options);\n}\n"],"mappings":"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"}
|