mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 00:55:16 -07:00
upgrade Electron 39→42 and fix Hyprland overlay z-order/placement (#79)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user