mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-15 20:12:59 -07:00
fix(macos): preserve overlay on transient tracker loss and fix subsync m
- macOS tracker now reports minimized vs not-found so transient helper misses no longer hide the overlay; minimizing mpv still triggers hide - overlay-runtime-init skips hide on non-minimized window-lost and calls updateVisibleOverlayVisibility instead - overlay-visibility preserves window level and passthrough state during transient tracker loss - subsync modal open uses dedicated modal window with retry logic to fix first-attempt flash and stale modal state on macOS
This commit is contained in:
@@ -547,7 +547,7 @@ test('initializeOverlayRuntime hides overlay windows when tracker loses the targ
|
||||
assert.deepEqual(calls, ['hide-visible', 'hide-modal', 'sync-shortcuts']);
|
||||
});
|
||||
|
||||
test('initializeOverlayRuntime hides visible overlay on Windows tracker loss when target is not minimized', () => {
|
||||
test('initializeOverlayRuntime refreshes visible overlay on tracker loss when target is not minimized', () => {
|
||||
const calls: string[] = [];
|
||||
const tracker = {
|
||||
onGeometryChange: null as ((...args: unknown[]) => void) | null,
|
||||
@@ -600,7 +600,7 @@ test('initializeOverlayRuntime hides visible overlay on Windows tracker loss whe
|
||||
calls.length = 0;
|
||||
tracker.onWindowLost?.();
|
||||
|
||||
assert.deepEqual(calls, ['hide-visible', 'sync-shortcuts']);
|
||||
assert.deepEqual(calls, ['update-visible']);
|
||||
});
|
||||
|
||||
test('initializeOverlayRuntime restores overlay bounds and visibility when tracker finds the target window again', () => {
|
||||
|
||||
@@ -105,10 +105,14 @@ export function initializeOverlayRuntime(options: {
|
||||
};
|
||||
windowTracker.onWindowLost = () => {
|
||||
options.releaseOverlayOwner?.();
|
||||
for (const window of options.getOverlayWindows()) {
|
||||
window.hide();
|
||||
if (windowTracker.isTargetWindowMinimized()) {
|
||||
for (const window of options.getOverlayWindows()) {
|
||||
window.hide();
|
||||
}
|
||||
options.syncOverlayShortcuts();
|
||||
return;
|
||||
}
|
||||
options.syncOverlayShortcuts();
|
||||
options.updateVisibleOverlayVisibility();
|
||||
};
|
||||
windowTracker.onWindowFocusChange = () => {
|
||||
if (options.isVisibleOverlayVisible()) {
|
||||
|
||||
@@ -1398,6 +1398,55 @@ test('macOS preserves visible overlay during transient tracker loss with retaine
|
||||
assert.ok(!calls.includes('show'));
|
||||
});
|
||||
|
||||
test('macOS preserves visible overlay level during non-minimized tracker loss', () => {
|
||||
const { window, calls } = createMainWindowRecorder();
|
||||
const tracker: WindowTrackerStub = {
|
||||
isTracking: () => false,
|
||||
getGeometry: () => null,
|
||||
isTargetWindowFocused: () => false,
|
||||
isTargetWindowMinimized: () => false,
|
||||
};
|
||||
|
||||
window.show();
|
||||
calls.length = 0;
|
||||
|
||||
updateVisibleOverlayVisibility({
|
||||
visibleOverlayVisible: 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: true,
|
||||
showOverlayLoadingOsd: () => {
|
||||
calls.push('loading-osd');
|
||||
},
|
||||
} as never);
|
||||
|
||||
assert.ok(calls.includes('sync-layer'));
|
||||
assert.ok(calls.includes('mouse-ignore:false:plain'));
|
||||
assert.ok(calls.includes('ensure-level'));
|
||||
assert.ok(calls.includes('enforce-order'));
|
||||
assert.ok(calls.includes('sync-shortcuts'));
|
||||
assert.ok(!calls.includes('hide'));
|
||||
assert.ok(!calls.includes('always-on-top:false'));
|
||||
assert.ok(!calls.includes('loading-osd'));
|
||||
});
|
||||
|
||||
test('macOS suppresses immediate repeat loading OSD after tracker recovery until cooldown expires', () => {
|
||||
const { window } = createMainWindowRecorder();
|
||||
const osdMessages: string[] = [];
|
||||
|
||||
@@ -94,13 +94,27 @@ export function updateVisibleOverlayVisibility(args: {
|
||||
const wasVisible = mainWindow.isVisible();
|
||||
const isVisibleOverlayFocused =
|
||||
typeof mainWindow.isFocused === 'function' && mainWindow.isFocused();
|
||||
const windowTracker = args.windowTracker;
|
||||
const canReportMacOSTargetMinimized =
|
||||
args.isMacOSPlatform && typeof windowTracker?.isTargetWindowMinimized === 'function';
|
||||
const isTrackedMacOSTargetMinimized =
|
||||
canReportMacOSTargetMinimized &&
|
||||
windowTracker?.isTargetWindowMinimized() === true;
|
||||
const hasTransientMacOSTrackerLoss =
|
||||
args.isMacOSPlatform &&
|
||||
canReportMacOSTargetMinimized &&
|
||||
!!windowTracker &&
|
||||
!windowTracker.isTracking() &&
|
||||
!isTrackedMacOSTargetMinimized &&
|
||||
mainWindow.isVisible();
|
||||
const isTrackedMacOSTargetFocused =
|
||||
!args.isMacOSPlatform || !args.windowTracker
|
||||
hasTransientMacOSTrackerLoss || !args.isMacOSPlatform || !args.windowTracker
|
||||
? true
|
||||
: (args.windowTracker.isTargetWindowFocused?.() ?? true);
|
||||
const shouldReleaseMacOSOverlayLevel =
|
||||
args.isMacOSPlatform &&
|
||||
!!args.windowTracker &&
|
||||
!hasTransientMacOSTrackerLoss &&
|
||||
!isVisibleOverlayFocused &&
|
||||
!isTrackedMacOSTargetFocused;
|
||||
const shouldDefaultToPassthrough =
|
||||
@@ -274,9 +288,17 @@ export function updateVisibleOverlayVisibility(args: {
|
||||
const hasRetainedTrackedGeometry = args.windowTracker.getGeometry() !== null;
|
||||
const hasActiveMacOSTargetSignal =
|
||||
args.isMacOSPlatform && (args.windowTracker.isTargetWindowFocused?.() ?? false);
|
||||
const canReportMacOSTargetMinimized =
|
||||
args.isMacOSPlatform && typeof args.windowTracker.isTargetWindowMinimized === 'function';
|
||||
const isTrackedMacOSTargetMinimized =
|
||||
canReportMacOSTargetMinimized &&
|
||||
args.windowTracker.isTargetWindowMinimized();
|
||||
const shouldPreserveTransientTrackedOverlay =
|
||||
(args.isMacOSPlatform &&
|
||||
(hasRetainedTrackedGeometry || (mainWindow.isVisible() && hasActiveMacOSTargetSignal))) ||
|
||||
!isTrackedMacOSTargetMinimized &&
|
||||
(hasRetainedTrackedGeometry ||
|
||||
(mainWindow.isVisible() && hasActiveMacOSTargetSignal) ||
|
||||
(canReportMacOSTargetMinimized && mainWindow.isVisible()))) ||
|
||||
(args.isWindowsPlatform &&
|
||||
typeof args.windowTracker.isTargetWindowMinimized === 'function' &&
|
||||
!args.windowTracker.isTargetWindowMinimized());
|
||||
|
||||
+6
-4
@@ -483,13 +483,13 @@ import { createOverlayModalInputState } from './main/runtime/overlay-modal-input
|
||||
import { openYoutubeTrackPicker } from './main/runtime/youtube-picker-open';
|
||||
import { openRuntimeOptionsModal as openRuntimeOptionsModalRuntime } from './main/runtime/runtime-options-open';
|
||||
import { openJimakuModal as openJimakuModalRuntime } from './main/runtime/jimaku-open';
|
||||
import { openSubsyncManualModal as openSubsyncManualModalRuntime } from './main/runtime/subsync-open';
|
||||
import { openSessionHelpModal as openSessionHelpModalRuntime } from './main/runtime/session-help-open';
|
||||
import { openCharacterDictionaryModal as openCharacterDictionaryModalRuntime } from './main/runtime/character-dictionary-open';
|
||||
import { openControllerSelectModal as openControllerSelectModalRuntime } from './main/runtime/controller-select-open';
|
||||
import { openControllerDebugModal as openControllerDebugModalRuntime } from './main/runtime/controller-debug-open';
|
||||
import { createPlaylistBrowserIpcRuntime } from './main/runtime/playlist-browser-ipc';
|
||||
import { writeSessionBindingsArtifact } from './main/runtime/session-bindings-artifact';
|
||||
import { openOverlayHostedModal } from './main/runtime/overlay-hosted-modal-open';
|
||||
import { createOverlayShortcutsRuntimeService } from './main/overlay-shortcuts-runtime';
|
||||
import {
|
||||
createFrequencyDictionaryRuntimeService,
|
||||
@@ -1479,9 +1479,11 @@ const buildMainSubsyncRuntimeMainDepsHandler = createBuildMainSubsyncRuntimeMain
|
||||
},
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
openManualPicker: (payload) => {
|
||||
sendToActiveOverlayWindow('subsync:open-manual', payload, {
|
||||
restoreOnModalClose: 'subsync',
|
||||
});
|
||||
openOverlayHostedModalWithOsd(
|
||||
(deps) => openSubsyncManualModalRuntime(deps, payload),
|
||||
'Subsync overlay unavailable.',
|
||||
'Failed to open subsync overlay.',
|
||||
);
|
||||
},
|
||||
});
|
||||
const immersionMediaRuntime = createImmersionMediaRuntime(
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { openSubsyncManualModal } from './subsync-open';
|
||||
import type { SubsyncManualPayload } from '../../types';
|
||||
|
||||
const payload: SubsyncManualPayload = {
|
||||
sourceTracks: [{ id: 2, label: 'External #2 - eng' }],
|
||||
};
|
||||
|
||||
test('subsync manual open prefers dedicated modal window on first attempt', async () => {
|
||||
const sends: Array<{
|
||||
channel: string;
|
||||
payload: SubsyncManualPayload;
|
||||
options: {
|
||||
restoreOnModalClose: 'subsync';
|
||||
preferModalWindow: boolean;
|
||||
};
|
||||
}> = [];
|
||||
|
||||
const opened = await openSubsyncManualModal(
|
||||
{
|
||||
ensureOverlayStartupPrereqs: () => {},
|
||||
ensureOverlayWindowsReadyForVisibilityActions: () => {},
|
||||
sendToActiveOverlayWindow: (channel, nextPayload, options) => {
|
||||
sends.push({
|
||||
channel,
|
||||
payload: nextPayload as SubsyncManualPayload,
|
||||
options: options as {
|
||||
restoreOnModalClose: 'subsync';
|
||||
preferModalWindow: boolean;
|
||||
},
|
||||
});
|
||||
return true;
|
||||
},
|
||||
waitForModalOpen: async (modal, timeoutMs) => {
|
||||
assert.equal(modal, 'subsync');
|
||||
assert.equal(timeoutMs, 1500);
|
||||
return true;
|
||||
},
|
||||
logWarn: () => {
|
||||
throw new Error('should not warn on first-attempt success');
|
||||
},
|
||||
},
|
||||
payload,
|
||||
);
|
||||
|
||||
assert.equal(opened, true);
|
||||
assert.deepEqual(sends, [
|
||||
{
|
||||
channel: 'subsync:open-manual',
|
||||
payload,
|
||||
options: {
|
||||
restoreOnModalClose: 'subsync',
|
||||
preferModalWindow: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('subsync manual open retries on the dedicated modal window after open timeout', async () => {
|
||||
const preferModalWindowValues: boolean[] = [];
|
||||
const warnings: string[] = [];
|
||||
let waitCalls = 0;
|
||||
|
||||
const opened = await openSubsyncManualModal(
|
||||
{
|
||||
ensureOverlayStartupPrereqs: () => {},
|
||||
ensureOverlayWindowsReadyForVisibilityActions: () => {},
|
||||
sendToActiveOverlayWindow: (_channel, _payload, options) => {
|
||||
preferModalWindowValues.push(Boolean(options?.preferModalWindow));
|
||||
return true;
|
||||
},
|
||||
waitForModalOpen: async () => {
|
||||
waitCalls += 1;
|
||||
return waitCalls === 2;
|
||||
},
|
||||
logWarn: (message) => {
|
||||
warnings.push(message);
|
||||
},
|
||||
},
|
||||
payload,
|
||||
);
|
||||
|
||||
assert.equal(opened, true);
|
||||
assert.deepEqual(preferModalWindowValues, [true, true]);
|
||||
assert.deepEqual(warnings, [
|
||||
'Subsync modal did not acknowledge modal open on first attempt; retrying dedicated modal window.',
|
||||
]);
|
||||
});
|
||||
|
||||
test('subsync manual open fails when the dedicated modal window cannot be targeted', async () => {
|
||||
let waitCalls = 0;
|
||||
|
||||
const opened = await openSubsyncManualModal(
|
||||
{
|
||||
ensureOverlayStartupPrereqs: () => {},
|
||||
ensureOverlayWindowsReadyForVisibilityActions: () => {},
|
||||
sendToActiveOverlayWindow: () => false,
|
||||
waitForModalOpen: async () => {
|
||||
waitCalls += 1;
|
||||
return true;
|
||||
},
|
||||
logWarn: () => {},
|
||||
},
|
||||
payload,
|
||||
);
|
||||
|
||||
assert.equal(opened, false);
|
||||
assert.equal(waitCalls, 0);
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { OverlayHostedModal } from '../../shared/ipc/contracts';
|
||||
import { IPC_CHANNELS } from '../../shared/ipc/contracts';
|
||||
import type { SubsyncManualPayload } from '../../types';
|
||||
import { openOverlayHostedModal, retryOverlayModalOpen } from './overlay-hosted-modal-open';
|
||||
|
||||
const SUBSYNC_MODAL: OverlayHostedModal = 'subsync';
|
||||
const SUBSYNC_OPEN_TIMEOUT_MS = 1500;
|
||||
|
||||
export async function openSubsyncManualModal(
|
||||
deps: {
|
||||
ensureOverlayStartupPrereqs: () => void;
|
||||
ensureOverlayWindowsReadyForVisibilityActions: () => void;
|
||||
sendToActiveOverlayWindow: (
|
||||
channel: string,
|
||||
payload?: unknown,
|
||||
runtimeOptions?: {
|
||||
restoreOnModalClose?: OverlayHostedModal;
|
||||
preferModalWindow?: boolean;
|
||||
},
|
||||
) => boolean;
|
||||
waitForModalOpen: (modal: OverlayHostedModal, timeoutMs: number) => Promise<boolean>;
|
||||
logWarn: (message: string) => void;
|
||||
},
|
||||
payload: SubsyncManualPayload,
|
||||
): Promise<boolean> {
|
||||
return await retryOverlayModalOpen(
|
||||
{
|
||||
waitForModalOpen: deps.waitForModalOpen,
|
||||
logWarn: deps.logWarn,
|
||||
},
|
||||
{
|
||||
modal: SUBSYNC_MODAL,
|
||||
timeoutMs: SUBSYNC_OPEN_TIMEOUT_MS,
|
||||
retryWarning:
|
||||
'Subsync modal did not acknowledge modal open on first attempt; retrying dedicated modal window.',
|
||||
sendOpen: () =>
|
||||
openOverlayHostedModal(
|
||||
{
|
||||
ensureOverlayStartupPrereqs: deps.ensureOverlayStartupPrereqs,
|
||||
ensureOverlayWindowsReadyForVisibilityActions:
|
||||
deps.ensureOverlayWindowsReadyForVisibilityActions,
|
||||
sendToActiveOverlayWindow: deps.sendToActiveOverlayWindow,
|
||||
},
|
||||
{
|
||||
channel: IPC_CHANNELS.event.subsyncOpenManual,
|
||||
modal: SUBSYNC_MODAL,
|
||||
payload,
|
||||
preferModalWindow: true,
|
||||
},
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { MacOSWindowTracker } from './macos-tracker';
|
||||
import { MacOSWindowTracker, parseMacOSHelperOutput } from './macos-tracker';
|
||||
|
||||
test('parseMacOSHelperOutput parses minimized state', () => {
|
||||
assert.deepEqual(parseMacOSHelperOutput('minimized'), {
|
||||
geometry: null,
|
||||
focused: false,
|
||||
minimized: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('MacOSWindowTracker keeps the last geometry through a single helper miss', async () => {
|
||||
let callIndex = 0;
|
||||
@@ -170,3 +178,40 @@ test('MacOSWindowTracker drops tracking after grace window expires', async () =>
|
||||
assert.equal(tracker.isTracking(), false);
|
||||
assert.equal(tracker.getGeometry(), null);
|
||||
});
|
||||
|
||||
test('MacOSWindowTracker reports minimized target when helper reports minimized', async () => {
|
||||
let callIndex = 0;
|
||||
let now = 1_000;
|
||||
const outputs = [
|
||||
{ stdout: '10,20,1280,720,1', stderr: '' },
|
||||
{ stdout: 'minimized', stderr: '' },
|
||||
{ stdout: 'minimized', stderr: '' },
|
||||
];
|
||||
|
||||
const tracker = new MacOSWindowTracker('/tmp/mpv.sock', {
|
||||
resolveHelper: () => ({
|
||||
helperPath: 'helper.swift',
|
||||
helperType: 'swift',
|
||||
}),
|
||||
runHelper: async () => outputs[callIndex++] ?? outputs.at(-1)!,
|
||||
now: () => now,
|
||||
minimizedTrackingLossGraceMs: 200,
|
||||
});
|
||||
|
||||
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
assert.equal(tracker.isTracking(), true);
|
||||
assert.equal(tracker.isTargetWindowMinimized(), false);
|
||||
|
||||
now += 250;
|
||||
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
assert.equal(tracker.isTargetWindowMinimized(), true);
|
||||
assert.equal(tracker.isTracking(), true);
|
||||
|
||||
now += 250;
|
||||
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
assert.equal(tracker.isTargetWindowMinimized(), true);
|
||||
assert.equal(tracker.isTracking(), false);
|
||||
});
|
||||
|
||||
@@ -40,13 +40,21 @@ type MacOSTrackerDeps = {
|
||||
) => Promise<MacOSTrackerRunnerResult>;
|
||||
maxConsecutiveMisses?: number;
|
||||
trackingLossGraceMs?: number;
|
||||
minimizedTrackingLossGraceMs?: number;
|
||||
now?: () => number;
|
||||
};
|
||||
|
||||
export interface MacOSHelperWindowState {
|
||||
geometry: WindowGeometry;
|
||||
focused: boolean;
|
||||
}
|
||||
export type MacOSHelperWindowState =
|
||||
| {
|
||||
geometry: WindowGeometry;
|
||||
focused: boolean;
|
||||
minimized?: false;
|
||||
}
|
||||
| {
|
||||
geometry: null;
|
||||
focused: false;
|
||||
minimized: true;
|
||||
};
|
||||
|
||||
function runHelperWithExecFile(
|
||||
helperPath: string,
|
||||
@@ -84,6 +92,13 @@ function runHelperWithExecFile(
|
||||
|
||||
export function parseMacOSHelperOutput(result: string): MacOSHelperWindowState | null {
|
||||
const trimmed = result.trim();
|
||||
if (trimmed === 'minimized') {
|
||||
return {
|
||||
geometry: null,
|
||||
focused: false,
|
||||
minimized: true,
|
||||
};
|
||||
}
|
||||
if (!trimmed || trimmed === 'not-found') {
|
||||
return null;
|
||||
}
|
||||
@@ -137,9 +152,11 @@ export class MacOSWindowTracker extends BaseWindowTracker {
|
||||
) => Promise<MacOSTrackerRunnerResult>;
|
||||
private readonly maxConsecutiveMisses: number;
|
||||
private readonly trackingLossGraceMs: number;
|
||||
private readonly minimizedTrackingLossGraceMs: number;
|
||||
private readonly now: () => number;
|
||||
private consecutiveMisses = 0;
|
||||
private trackingLossStartedAtMs: number | null = null;
|
||||
private targetWindowMinimized = false;
|
||||
|
||||
constructor(targetMpvSocketPath?: string, deps: MacOSTrackerDeps = {}) {
|
||||
super();
|
||||
@@ -147,6 +164,10 @@ export class MacOSWindowTracker extends BaseWindowTracker {
|
||||
this.runHelper = deps.runHelper ?? runHelperWithExecFile;
|
||||
this.maxConsecutiveMisses = Math.max(1, Math.floor(deps.maxConsecutiveMisses ?? 2));
|
||||
this.trackingLossGraceMs = Math.max(0, Math.floor(deps.trackingLossGraceMs ?? 1_500));
|
||||
this.minimizedTrackingLossGraceMs = Math.max(
|
||||
0,
|
||||
Math.floor(deps.minimizedTrackingLossGraceMs ?? 500),
|
||||
);
|
||||
this.now = deps.now ?? (() => Date.now());
|
||||
const resolvedHelper = deps.resolveHelper?.() ?? null;
|
||||
if (resolvedHelper) {
|
||||
@@ -259,28 +280,32 @@ export class MacOSWindowTracker extends BaseWindowTracker {
|
||||
}
|
||||
}
|
||||
|
||||
override isTargetWindowMinimized(): boolean {
|
||||
return this.targetWindowMinimized;
|
||||
}
|
||||
|
||||
private resetTrackingLossState(): void {
|
||||
this.consecutiveMisses = 0;
|
||||
this.trackingLossStartedAtMs = null;
|
||||
}
|
||||
|
||||
private shouldDropTracking(): boolean {
|
||||
private shouldDropTracking(graceMs = this.trackingLossGraceMs): boolean {
|
||||
if (!this.isTracking()) {
|
||||
return true;
|
||||
}
|
||||
if (this.trackingLossGraceMs === 0) {
|
||||
if (graceMs === 0) {
|
||||
return this.consecutiveMisses >= this.maxConsecutiveMisses;
|
||||
}
|
||||
if (this.trackingLossStartedAtMs === null) {
|
||||
this.trackingLossStartedAtMs = this.now();
|
||||
return false;
|
||||
}
|
||||
return this.now() - this.trackingLossStartedAtMs > this.trackingLossGraceMs;
|
||||
return this.now() - this.trackingLossStartedAtMs > graceMs;
|
||||
}
|
||||
|
||||
private registerTrackingMiss(): void {
|
||||
private registerTrackingMiss(graceMs = this.trackingLossGraceMs): void {
|
||||
this.consecutiveMisses += 1;
|
||||
if (this.shouldDropTracking()) {
|
||||
if (this.shouldDropTracking(graceMs)) {
|
||||
this.updateGeometry(null);
|
||||
this.resetTrackingLossState();
|
||||
}
|
||||
@@ -296,12 +321,20 @@ export class MacOSWindowTracker extends BaseWindowTracker {
|
||||
.then(({ stdout }) => {
|
||||
const parsed = parseMacOSHelperOutput(stdout || '');
|
||||
if (parsed) {
|
||||
if (parsed.minimized) {
|
||||
this.targetWindowMinimized = true;
|
||||
this.updateTargetWindowFocused(false);
|
||||
this.registerTrackingMiss(this.minimizedTrackingLossGraceMs);
|
||||
return;
|
||||
}
|
||||
this.resetTrackingLossState();
|
||||
this.targetWindowMinimized = false;
|
||||
this.updateFocus(parsed.focused);
|
||||
this.updateGeometry(parsed.geometry);
|
||||
return;
|
||||
}
|
||||
|
||||
this.targetWindowMinimized = false;
|
||||
this.registerTrackingMiss();
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
@@ -314,6 +347,7 @@ export class MacOSWindowTracker extends BaseWindowTracker {
|
||||
? (error as { stderr: string }).stderr
|
||||
: '';
|
||||
this.maybeLogExecError(err, stderr);
|
||||
this.targetWindowMinimized = false;
|
||||
this.registerTrackingMiss();
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
Reference in New Issue
Block a user