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
@@ -50,7 +50,7 @@ test('linux mpv fullscreen overlay refresh burst schedules overlay refresh work
}
});
test('linux mpv fullscreen overlay refresh update cancels burst when fullscreen exits', async () => {
test('linux mpv fullscreen overlay refresh update schedules a fresh burst when fullscreen exits', async () => {
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
Object.defineProperty(process, 'platform', {
configurable: true,
@@ -82,8 +82,11 @@ test('linux mpv fullscreen overlay refresh update cancels burst when fullscreen
await new Promise((resolve) => setTimeout(resolve, 80));
assert.equal(nextCancel, null);
assert.deepEqual(calls, []);
assert.equal(typeof nextCancel, 'function');
assert.ok(calls.includes('updateVisibleOverlayVisibility'));
assert.ok(calls.includes('hide'));
assert.ok(calls.includes('showInactive'));
assert.ok(calls.includes('ensureOverlayWindowLevel'));
} finally {
clearLinuxMpvFullscreenOverlayRefreshTimeouts();
if (originalPlatformDescriptor) {
@@ -68,14 +68,11 @@ export function scheduleLinuxVisibleOverlayFullscreenRefreshBurst(
}
export function updateLinuxMpvFullscreenOverlayRefreshBurst(
isFullscreen: boolean,
_isFullscreen: boolean,
deps: LinuxMpvFullscreenOverlayRefreshDeps,
cancelCurrentBurst: CancelLinuxMpvFullscreenOverlayRefreshBurst | null,
): CancelLinuxMpvFullscreenOverlayRefreshBurst | null {
cancelCurrentBurst?.();
if (!isFullscreen) {
return null;
}
return scheduleLinuxVisibleOverlayFullscreenRefreshBurst(deps);
}
@@ -15,6 +15,7 @@ test('overlay visibility runtime main deps builder maps state and geometry callb
getModalActive: () => true,
getVisibleOverlayVisible: () => true,
getForceMousePassthrough: () => true,
getSuspendVisibleOverlay: () => true,
getOverlayInteractionActive: () => true,
getWindowTracker: () => tracker,
getLastKnownWindowsForegroundProcessName: () => 'mpv',
@@ -41,6 +42,7 @@ test('overlay visibility runtime main deps builder maps state and geometry callb
assert.equal(deps.getModalActive(), true);
assert.equal(deps.getVisibleOverlayVisible(), true);
assert.equal(deps.getForceMousePassthrough(), true);
assert.equal(deps.getSuspendVisibleOverlay?.(), true);
assert.equal(deps.getOverlayInteractionActive?.(), true);
assert.equal(deps.getLastKnownWindowsForegroundProcessName?.(), 'mpv');
assert.equal(deps.getWindowsOverlayProcessName?.(), 'subminer');
@@ -10,6 +10,7 @@ export function createBuildOverlayVisibilityRuntimeMainDepsHandler(
getModalActive: () => deps.getModalActive(),
getVisibleOverlayVisible: () => deps.getVisibleOverlayVisible(),
getForceMousePassthrough: () => deps.getForceMousePassthrough(),
getSuspendVisibleOverlay: () => deps.getSuspendVisibleOverlay?.() ?? false,
getOverlayInteractionActive: () => deps.getOverlayInteractionActive?.() ?? false,
getWindowTracker: () => deps.getWindowTracker(),
getLastKnownWindowsForegroundProcessName: () =>
@@ -15,9 +15,16 @@ test('overlay window layout main deps builders map callbacks', () => {
visible.setOverlayWindowBounds({ x: 0, y: 0, width: 1, height: 1 });
const level = createBuildEnsureOverlayWindowLevelMainDepsHandler({
shouldSuppressOverlayWindowLevel: () => {
calls.push('ensure-suppressed-check');
return false;
},
ensureOverlayWindowLevelCore: () => calls.push('ensure'),
afterEnsureOverlayWindowLevel: () => calls.push('ensure-after'),
})();
assert.equal(level.shouldSuppressOverlayWindowLevel?.({}), false);
level.ensureOverlayWindowLevelCore({});
level.afterEnsureOverlayWindowLevel?.({});
const order = createBuildEnforceOverlayLayerOrderMainDepsHandler({
enforceOverlayLayerOrderCore: () => calls.push('order'),
@@ -34,5 +41,12 @@ test('overlay window layout main deps builders map callbacks', () => {
assert.deepEqual(order.getMainWindow(), { kind: 'main' });
order.ensureOverlayWindowLevel({});
assert.deepEqual(calls, ['visible', 'ensure', 'order', 'order-level']);
assert.deepEqual(calls, [
'visible',
'ensure-suppressed-check',
'ensure',
'ensure-after',
'order',
'order-level',
]);
});
@@ -23,7 +23,11 @@ export function createBuildEnsureOverlayWindowLevelMainDepsHandler(
deps: EnsureOverlayWindowLevelMainDeps,
) {
return (): EnsureOverlayWindowLevelMainDeps => ({
shouldSuppressOverlayWindowLevel: (window: unknown) =>
deps.shouldSuppressOverlayWindowLevel?.(window) ?? false,
ensureOverlayWindowLevelCore: (window: unknown) => deps.ensureOverlayWindowLevelCore(window),
afterEnsureOverlayWindowLevel: (window: unknown) =>
deps.afterEnsureOverlayWindowLevel?.(window),
});
}
+20 -1
View File
@@ -36,9 +36,28 @@ test('ensure overlay window level handler delegates to core', () => {
const calls: string[] = [];
const ensureLevel = createEnsureOverlayWindowLevelHandler({
ensureOverlayWindowLevelCore: () => calls.push('core'),
afterEnsureOverlayWindowLevel: () => calls.push('after'),
});
ensureLevel({});
assert.deepEqual(calls, ['core']);
assert.deepEqual(calls, ['core', 'after']);
});
test('ensure overlay window level handler skips while top reassertion is suppressed', () => {
const calls: string[] = [];
const window = {};
const ensureLevel = createEnsureOverlayWindowLevelHandler({
shouldSuppressOverlayWindowLevel: (nextWindow) => {
assert.equal(nextWindow, window);
calls.push('suppress-check');
return true;
},
ensureOverlayWindowLevelCore: () => calls.push('core'),
afterEnsureOverlayWindowLevel: () => calls.push('after'),
});
ensureLevel(window);
assert.deepEqual(calls, ['suppress-check']);
});
test('enforce overlay layer order handler forwards resolved state', () => {
@@ -11,10 +11,16 @@ export function createUpdateVisibleOverlayBoundsHandler(deps: {
}
export function createEnsureOverlayWindowLevelHandler(deps: {
shouldSuppressOverlayWindowLevel?: (window: unknown) => boolean;
ensureOverlayWindowLevelCore: (window: unknown) => void;
afterEnsureOverlayWindowLevel?: (window: unknown) => void;
}) {
return (window: unknown): void => {
if (deps.shouldSuppressOverlayWindowLevel?.(window) === true) {
return;
}
deps.ensureOverlayWindowLevelCore(window);
deps.afterEnsureOverlayWindowLevel?.(window);
};
}
@@ -0,0 +1,56 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createStatsOverlayVisibilityChangeHandler } from './stats-overlay-visibility';
test('stats overlay visibility handler makes overlay mouse-passive before opening stats', () => {
const calls: string[] = [];
const handler = createStatsOverlayVisibilityChangeHandler({
setStatsOverlayVisibleState: (visible) => calls.push(`state:${visible}`),
resetVisibleOverlayInteraction: () => calls.push('reset-interaction'),
getMainWindow: () =>
({
isDestroyed: () => false,
isVisible: () => true,
setIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => {
calls.push(`mouse-ignore:${ignore}:${options?.forward === true ? 'forward' : 'plain'}`);
},
}) as never,
updateVisibleOverlayVisibility: () => calls.push('update-visible'),
});
handler(true);
assert.deepEqual(calls, [
'state:true',
'reset-interaction',
'mouse-ignore:true:forward',
'update-visible',
]);
});
test('stats overlay visibility handler restores overlay then leaves mpv mouse-responsive after close', () => {
const calls: string[] = [];
const handler = createStatsOverlayVisibilityChangeHandler({
setStatsOverlayVisibleState: (visible) => calls.push(`state:${visible}`),
resetVisibleOverlayInteraction: () => calls.push('reset-interaction'),
getMainWindow: () =>
({
isDestroyed: () => false,
isVisible: () => true,
setIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => {
calls.push(`mouse-ignore:${ignore}:${options?.forward === true ? 'forward' : 'plain'}`);
},
}) as never,
updateVisibleOverlayVisibility: () => calls.push('update-visible'),
});
handler(false);
assert.deepEqual(calls, [
'state:false',
'reset-interaction',
'update-visible',
'mouse-ignore:true:forward',
]);
});
@@ -0,0 +1,33 @@
type StatsOverlayVisibilityWindow = {
isDestroyed: () => boolean;
isVisible: () => boolean;
setIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => void;
};
function makeOverlayMousePassive(window: StatsOverlayVisibilityWindow | null): void {
if (!window || window.isDestroyed() || !window.isVisible()) {
return;
}
window.setIgnoreMouseEvents(true, { forward: true });
}
export function createStatsOverlayVisibilityChangeHandler(deps: {
setStatsOverlayVisibleState: (visible: boolean) => void;
resetVisibleOverlayInteraction: () => void;
getMainWindow: () => StatsOverlayVisibilityWindow | null;
updateVisibleOverlayVisibility: () => void;
}) {
return (visible: boolean): void => {
deps.setStatsOverlayVisibleState(visible);
deps.resetVisibleOverlayInteraction();
if (visible) {
makeOverlayMousePassive(deps.getMainWindow());
deps.updateVisibleOverlayVisibility();
return;
}
deps.updateVisibleOverlayVisibility();
makeOverlayMousePassive(deps.getMainWindow());
};
}