upgrade Electron 39→42 and fix Hyprland overlay z-order/placement (#79)

This commit is contained in:
2026-05-22 23:22:51 -07:00
committed by GitHub
parent c6328eef09
commit c4f99fec2f
30 changed files with 557 additions and 126 deletions
@@ -60,7 +60,10 @@ test('buildHyprlandPlacementDispatches floats tiled overlay windows without pinn
floating: false,
pinned: false,
}),
[['dispatch', 'setfloating', 'address:0xabc']],
[
['dispatch', 'setfloating', 'address:0xabc'],
['dispatch', 'alterzorder', 'top,address:0xabc'],
],
);
});
@@ -87,6 +90,7 @@ test('buildHyprlandPlacementDispatches force-aligns floating overlay windows to
['dispatch', 'setprop', 'address:0xabc no_shadow 1'],
['dispatch', 'setprop', 'address:0xabc no_blur 1'],
['dispatch', 'setprop', 'address:0xabc decorate 0'],
['dispatch', 'alterzorder', 'top,address:0xabc'],
],
);
});
@@ -98,7 +102,7 @@ test('buildHyprlandPlacementDispatches does not pin already floating overlay win
floating: true,
pinned: false,
}),
[],
[['dispatch', 'alterzorder', 'top,address:0xabc']],
);
});
@@ -109,7 +113,10 @@ test('buildHyprlandPlacementDispatches unpins previously pinned overlay windows'
floating: true,
pinned: true,
}),
[['dispatch', 'pin', 'address:0xabc']],
[
['dispatch', 'pin', 'address:0xabc'],
['dispatch', 'alterzorder', 'top,address:0xabc'],
],
);
});
@@ -146,6 +153,7 @@ test('ensureHyprlandWindowFloatingByTitle dispatches float-only placement for ma
[
['-j', 'clients'],
['dispatch', 'setfloating', 'address:0xmatch'],
['dispatch', 'alterzorder', 'top,address:0xmatch'],
],
);
});
@@ -195,6 +203,7 @@ test('ensureHyprlandWindowFloatingByTitle dispatches exact Hyprland geometry whe
['dispatch', 'setprop', 'address:0xmatch no_shadow 1'],
['dispatch', 'setprop', 'address:0xmatch no_blur 1'],
['dispatch', 'setprop', 'address:0xmatch decorate 0'],
['dispatch', 'alterzorder', 'top,address:0xmatch'],
],
);
});
@@ -95,6 +95,7 @@ export function buildHyprlandPlacementDispatches(
dispatches.push(['dispatch', 'setprop', `${windowAddress} no_blur 1`]);
dispatches.push(['dispatch', 'setprop', `${windowAddress} decorate 0`]);
}
dispatches.push(['dispatch', 'alterzorder', `top,${windowAddress}`]);
return dispatches;
}
@@ -197,6 +197,53 @@ test('tracked non-macOS overlay stays hidden while tracker is not ready', () =>
assert.ok(!calls.includes('osd'));
});
test('suspended visible overlay hides without refreshing bounds or z-order', () => {
const { window, calls } = createMainWindowRecorder();
const tracker: WindowTrackerStub = {
isTracking: () => true,
getGeometry: () => ({ x: 0, y: 0, width: 1280, height: 720 }),
};
window.show();
calls.length = 0;
updateVisibleOverlayVisibility({
visibleOverlayVisible: true,
suspendVisibleOverlay: true,
mainWindow: window as never,
windowTracker: tracker as never,
trackerNotReadyWarningShown: false,
setTrackerNotReadyWarningShown: () => {},
updateVisibleOverlayBounds: () => {
calls.push('update-bounds');
},
ensureOverlayWindowLevel: () => {
calls.push('ensure-level');
},
syncPrimaryOverlayWindowLayer: () => {
calls.push('sync-layer');
},
enforceOverlayLayerOrder: () => {
calls.push('enforce-order');
},
syncOverlayShortcuts: () => {
calls.push('sync-shortcuts');
},
isMacOSPlatform: false,
isWindowsPlatform: false,
} as never);
assert.ok(calls.includes('mouse-ignore:true:forward'));
assert.ok(calls.includes('always-on-top:false'));
assert.ok(calls.includes('hide'));
assert.ok(calls.includes('sync-shortcuts'));
assert.ok(!calls.includes('update-bounds'));
assert.ok(!calls.includes('ensure-level'));
assert.ok(!calls.includes('enforce-order'));
assert.ok(!calls.includes('show'));
assert.ok(!calls.includes('focus'));
});
test('untracked non-macOS overlay keeps fallback visible behavior when no tracker exists', () => {
const { window, calls } = createMainWindowRecorder();
let trackerWarning = false;
+13
View File
@@ -64,6 +64,7 @@ export function updateVisibleOverlayVisibility(args: {
visibleOverlayVisible: boolean;
modalActive?: boolean;
forceMousePassthrough?: boolean;
suspendVisibleOverlay?: boolean;
overlayInteractionActive?: boolean;
mainWindow: BrowserWindow | null;
windowTracker: BaseWindowTracker | null;
@@ -103,6 +104,18 @@ export function updateVisibleOverlayVisibility(args: {
return;
}
if (args.suspendVisibleOverlay) {
if (args.isWindowsPlatform) {
clearPendingWindowsOverlayReveal(mainWindow);
setOverlayWindowOpacity(mainWindow, 0);
}
mainWindow.setIgnoreMouseEvents(true, { forward: true });
releaseOverlayWindowLevel(mainWindow);
mainWindow.hide();
args.syncOverlayShortcuts();
return;
}
const showPassiveVisibleOverlay = (): boolean => {
const forceMousePassthrough = args.forceMousePassthrough === true;
const wasVisible = mainWindow.isVisible();
+18
View File
@@ -7,6 +7,8 @@ export const STATS_WINDOW_TITLE = 'SubMiner Stats';
type StatsWindowLevelController = Pick<BrowserWindow, 'setAlwaysOnTop' | 'moveTop'> &
Partial<Pick<BrowserWindow, 'setVisibleOnAllWorkspaces' | 'setFullScreenable'>>;
type VisibleStatsWindowLevelController = StatsWindowLevelController &
Pick<BrowserWindow, 'isDestroyed' | 'isVisible'>;
type StatsWindowBoundsController = Pick<BrowserWindow, 'getBounds' | 'getContentBounds'>;
type StatsWindowPresentationController = Pick<BrowserWindow, 'show' | 'focus'> &
@@ -106,6 +108,22 @@ export function promoteStatsWindowLevel(
window.moveTop();
}
export function promoteVisibleStatsWindowAboveOverlay(
window: VisibleStatsWindowLevelController,
options: {
platform?: NodeJS.Platform;
promoteHyprlandWindow?: () => void;
} = {},
): boolean {
if (window.isDestroyed() || !window.isVisible()) {
return false;
}
promoteStatsWindowLevel(window, options.platform);
options.promoteHyprlandWindow?.();
return true;
}
export function presentStatsWindow(
window: StatsWindowPresentationController,
platform: NodeJS.Platform = process.platform,
+42
View File
@@ -4,6 +4,7 @@ import {
buildStatsWindowLoadFileOptions,
buildStatsWindowOptions,
presentStatsWindow,
promoteVisibleStatsWindowAboveOverlay,
promoteStatsWindowLevel,
resolveStatsWindowOuterBoundsForContent,
shouldHideStatsWindowForInput,
@@ -232,6 +233,47 @@ test('promoteStatsWindowLevel raises stats above overlay level on Windows', () =
assert.deepEqual(calls, ['always-on-top:true:screen-saver:2', 'move-top']);
});
test('promoteVisibleStatsWindowAboveOverlay reasserts stats above overlay on Hyprland', () => {
const calls: string[] = [];
const promoted = promoteVisibleStatsWindowAboveOverlay(
{
isDestroyed: () => false,
isVisible: () => true,
setAlwaysOnTop: (flag: boolean, level?: string, relativeLevel?: number) => {
calls.push(`always-on-top:${flag}:${level ?? 'none'}:${relativeLevel ?? 0}`);
},
moveTop: () => {
calls.push('move-top');
},
} as never,
{
platform: 'linux',
promoteHyprlandWindow: () => calls.push('hyprland-top'),
},
);
assert.equal(promoted, true);
assert.deepEqual(calls, ['always-on-top:true:none:0', 'move-top', 'hyprland-top']);
});
test('promoteVisibleStatsWindowAboveOverlay skips hidden stats windows', () => {
const calls: string[] = [];
const promoted = promoteVisibleStatsWindowAboveOverlay(
{
isDestroyed: () => false,
isVisible: () => false,
setAlwaysOnTop: () => calls.push('always-on-top'),
moveTop: () => calls.push('move-top'),
} as never,
{
promoteHyprlandWindow: () => calls.push('hyprland-top'),
},
);
assert.equal(promoted, false);
assert.deepEqual(calls, []);
});
test('presentStatsWindow shows inactive on macOS to stay on the fullscreen mpv Space', () => {
const calls: string[] = [];
+15 -2
View File
@@ -7,6 +7,7 @@ import {
buildStatsWindowOptions,
presentStatsWindow,
promoteStatsWindowLevel,
promoteVisibleStatsWindowAboveOverlay,
resolveStatsWindowOuterBoundsForContent,
shouldHideStatsWindowForInput,
STATS_WINDOW_TITLE,
@@ -58,7 +59,19 @@ function showStatsWindow(window: BrowserWindow, options: StatsWindowOptions): vo
placementBounds = syncStatsWindowBounds(window, bounds) ?? placementBounds;
}
options.onVisibilityChanged?.(true);
promoteStatsWindowLevel(window);
promoteStatsOverlayAbovePlayback();
}
export function promoteStatsOverlayAbovePlayback(): boolean {
if (!statsWindow) {
return false;
}
return promoteVisibleStatsWindowAboveOverlay(statsWindow, {
promoteHyprlandWindow: () => {
ensureHyprlandWindowFloatingByTitle({ title: STATS_WINDOW_TITLE });
},
});
}
/**
@@ -104,7 +117,7 @@ export function toggleStatsOverlay(options: StatsWindowOptions): void {
if (!statsWindow || statsWindow.isDestroyed() || !statsWindow.isVisible()) {
return;
}
promoteStatsWindowLevel(statsWindow);
promoteStatsOverlayAbovePlayback();
});
} else if (statsWindow.isVisible()) {
statsWindow.hide();