mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-12 04:19:25 -07:00
fix: address PR #49 CodeRabbit follow-ups
This commit is contained in:
4
changes/fix-pr-49-coderabbit-review-follow-ups.md
Normal file
4
changes/fix-pr-49-coderabbit-review-follow-ups.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
type: fixed
|
||||||
|
area: overlay
|
||||||
|
|
||||||
|
- Addressed the latest CodeRabbit follow-ups on PR #49, including generation-scoped Lua session binding names, stricter session command validation, session-help shortcut visibility, the numeric-selection key guard, stats-overlay startup classification, and safer session-binding persistence.
|
||||||
@@ -313,12 +313,14 @@ function M.create(ctx)
|
|||||||
|
|
||||||
local previous_binding_names = state.session_binding_names
|
local previous_binding_names = state.session_binding_names
|
||||||
local next_binding_names = {}
|
local next_binding_names = {}
|
||||||
|
state.session_binding_generation = (state.session_binding_generation or 0) + 1
|
||||||
|
local generation = state.session_binding_generation
|
||||||
|
|
||||||
local timeout_ms = tonumber(artifact.numericSelectionTimeoutMs) or 3000
|
local timeout_ms = tonumber(artifact.numericSelectionTimeoutMs) or 3000
|
||||||
for index, binding in ipairs(artifact.bindings) do
|
for index, binding in ipairs(artifact.bindings) do
|
||||||
local key_name = key_spec_to_mpv_binding(binding.key)
|
local key_name = key_spec_to_mpv_binding(binding.key)
|
||||||
if key_name then
|
if key_name then
|
||||||
local name = "subminer-session-binding-" .. tostring(index)
|
local name = "subminer-session-binding-" .. tostring(generation) .. "-" .. tostring(index)
|
||||||
next_binding_names[#next_binding_names + 1] = name
|
next_binding_names[#next_binding_names + 1] = name
|
||||||
mp.add_forced_key_binding(key_name, name, function()
|
mp.add_forced_key_binding(key_name, name, function()
|
||||||
handle_binding(binding, timeout_ms)
|
handle_binding(binding, timeout_ms)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ function M.new()
|
|||||||
auto_play_ready_timeout = nil,
|
auto_play_ready_timeout = nil,
|
||||||
auto_play_ready_osd_timer = nil,
|
auto_play_ready_osd_timer = nil,
|
||||||
suppress_ready_overlay_restore = false,
|
suppress_ready_overlay_restore = false,
|
||||||
|
session_binding_generation = 0,
|
||||||
session_binding_names = {},
|
session_binding_names = {},
|
||||||
session_numeric_binding_names = {},
|
session_numeric_binding_names = {},
|
||||||
session_numeric_selection = nil,
|
session_numeric_selection = nil,
|
||||||
|
|||||||
@@ -231,6 +231,9 @@ test('hasExplicitCommand and shouldStartApp preserve command intent', () => {
|
|||||||
assert.equal(shouldStartApp(cycleRuntimeOption), true);
|
assert.equal(shouldStartApp(cycleRuntimeOption), true);
|
||||||
assert.equal(commandNeedsOverlayRuntime(cycleRuntimeOption), true);
|
assert.equal(commandNeedsOverlayRuntime(cycleRuntimeOption), true);
|
||||||
|
|
||||||
|
const toggleStatsOverlayRuntime = parseArgs(['--toggle-stats-overlay']);
|
||||||
|
assert.equal(commandNeedsOverlayRuntime(toggleStatsOverlayRuntime), true);
|
||||||
|
|
||||||
const dictionary = parseArgs(['--dictionary']);
|
const dictionary = parseArgs(['--dictionary']);
|
||||||
assert.equal(dictionary.dictionary, true);
|
assert.equal(dictionary.dictionary, true);
|
||||||
assert.equal(hasExplicitCommand(dictionary), true);
|
assert.equal(hasExplicitCommand(dictionary), true);
|
||||||
|
|||||||
@@ -686,6 +686,7 @@ export function commandNeedsOverlayRuntime(args: CliArgs): boolean {
|
|||||||
args.mineSentenceMultiple ||
|
args.mineSentenceMultiple ||
|
||||||
args.updateLastCardFromClipboard ||
|
args.updateLastCardFromClipboard ||
|
||||||
args.toggleSecondarySub ||
|
args.toggleSecondarySub ||
|
||||||
|
args.toggleStatsOverlay ||
|
||||||
args.toggleSubtitleSidebar ||
|
args.toggleSubtitleSidebar ||
|
||||||
args.triggerFieldGrouping ||
|
args.triggerFieldGrouping ||
|
||||||
args.triggerSubsync ||
|
args.triggerSubsync ||
|
||||||
|
|||||||
@@ -230,6 +230,23 @@ test('compileSessionBindings rejects malformed command arrays', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('compileSessionBindings rejects non-string command heads and extra args on special commands', () => {
|
||||||
|
const result = compileSessionBindings({
|
||||||
|
shortcuts: createShortcuts(),
|
||||||
|
keybindings: [
|
||||||
|
createKeybinding('Ctrl+J', [42] as never),
|
||||||
|
createKeybinding('Ctrl+K', [SPECIAL_COMMANDS.JIMAKU_OPEN, 'extra'] as never),
|
||||||
|
],
|
||||||
|
platform: 'linux',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(result.bindings, []);
|
||||||
|
assert.deepEqual(result.warnings.map((warning) => `${warning.kind}:${warning.path}`), [
|
||||||
|
'unsupported:keybindings[0].key',
|
||||||
|
'unsupported:keybindings[1].key',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test('compileSessionBindings warns on deprecated toggleVisibleOverlayGlobal config', () => {
|
test('compileSessionBindings warns on deprecated toggleVisibleOverlayGlobal config', () => {
|
||||||
const result = compileSessionBindings({
|
const result = compileSessionBindings({
|
||||||
shortcuts: createShortcuts(),
|
shortcuts: createShortcuts(),
|
||||||
|
|||||||
@@ -268,40 +268,49 @@ function resolveCommandBinding(
|
|||||||
|
|
||||||
const first = command[0];
|
const first = command[0];
|
||||||
if (typeof first !== 'string') {
|
if (typeof first !== 'string') {
|
||||||
return {
|
return null;
|
||||||
actionType: 'mpv-command',
|
|
||||||
command,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (first === SPECIAL_COMMANDS.SUBSYNC_TRIGGER) {
|
if (first === SPECIAL_COMMANDS.SUBSYNC_TRIGGER) {
|
||||||
|
if (command.length !== 1) return null;
|
||||||
return { actionType: 'session-action', actionId: 'triggerSubsync' };
|
return { actionType: 'session-action', actionId: 'triggerSubsync' };
|
||||||
}
|
}
|
||||||
if (first === SPECIAL_COMMANDS.RUNTIME_OPTIONS_OPEN) {
|
if (first === SPECIAL_COMMANDS.RUNTIME_OPTIONS_OPEN) {
|
||||||
|
if (command.length !== 1) return null;
|
||||||
return { actionType: 'session-action', actionId: 'openRuntimeOptions' };
|
return { actionType: 'session-action', actionId: 'openRuntimeOptions' };
|
||||||
}
|
}
|
||||||
if (first === SPECIAL_COMMANDS.JIMAKU_OPEN) {
|
if (first === SPECIAL_COMMANDS.JIMAKU_OPEN) {
|
||||||
|
if (command.length !== 1) return null;
|
||||||
return { actionType: 'session-action', actionId: 'openJimaku' };
|
return { actionType: 'session-action', actionId: 'openJimaku' };
|
||||||
}
|
}
|
||||||
if (first === SPECIAL_COMMANDS.YOUTUBE_PICKER_OPEN) {
|
if (first === SPECIAL_COMMANDS.YOUTUBE_PICKER_OPEN) {
|
||||||
|
if (command.length !== 1) return null;
|
||||||
return { actionType: 'session-action', actionId: 'openYoutubePicker' };
|
return { actionType: 'session-action', actionId: 'openYoutubePicker' };
|
||||||
}
|
}
|
||||||
if (first === SPECIAL_COMMANDS.PLAYLIST_BROWSER_OPEN) {
|
if (first === SPECIAL_COMMANDS.PLAYLIST_BROWSER_OPEN) {
|
||||||
|
if (command.length !== 1) return null;
|
||||||
return { actionType: 'session-action', actionId: 'openPlaylistBrowser' };
|
return { actionType: 'session-action', actionId: 'openPlaylistBrowser' };
|
||||||
}
|
}
|
||||||
if (first === SPECIAL_COMMANDS.REPLAY_SUBTITLE) {
|
if (first === SPECIAL_COMMANDS.REPLAY_SUBTITLE) {
|
||||||
|
if (command.length !== 1) return null;
|
||||||
return { actionType: 'session-action', actionId: 'replayCurrentSubtitle' };
|
return { actionType: 'session-action', actionId: 'replayCurrentSubtitle' };
|
||||||
}
|
}
|
||||||
if (first === SPECIAL_COMMANDS.PLAY_NEXT_SUBTITLE) {
|
if (first === SPECIAL_COMMANDS.PLAY_NEXT_SUBTITLE) {
|
||||||
|
if (command.length !== 1) return null;
|
||||||
return { actionType: 'session-action', actionId: 'playNextSubtitle' };
|
return { actionType: 'session-action', actionId: 'playNextSubtitle' };
|
||||||
}
|
}
|
||||||
if (first === SPECIAL_COMMANDS.SHIFT_SUB_DELAY_TO_PREVIOUS_SUBTITLE_START) {
|
if (first === SPECIAL_COMMANDS.SHIFT_SUB_DELAY_TO_PREVIOUS_SUBTITLE_START) {
|
||||||
|
if (command.length !== 1) return null;
|
||||||
return { actionType: 'session-action', actionId: 'shiftSubDelayPrevLine' };
|
return { actionType: 'session-action', actionId: 'shiftSubDelayPrevLine' };
|
||||||
}
|
}
|
||||||
if (first === SPECIAL_COMMANDS.SHIFT_SUB_DELAY_TO_NEXT_SUBTITLE_START) {
|
if (first === SPECIAL_COMMANDS.SHIFT_SUB_DELAY_TO_NEXT_SUBTITLE_START) {
|
||||||
|
if (command.length !== 1) return null;
|
||||||
return { actionType: 'session-action', actionId: 'shiftSubDelayNextLine' };
|
return { actionType: 'session-action', actionId: 'shiftSubDelayNextLine' };
|
||||||
}
|
}
|
||||||
if (first.startsWith(SPECIAL_COMMANDS.RUNTIME_OPTION_CYCLE_PREFIX)) {
|
if (first.startsWith(SPECIAL_COMMANDS.RUNTIME_OPTION_CYCLE_PREFIX)) {
|
||||||
|
if (command.length !== 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const parts = first.split(':');
|
const parts = first.split(':');
|
||||||
if (parts.length !== 3) {
|
if (parts.length !== 3) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
26
src/main.ts
26
src/main.ts
@@ -1944,7 +1944,17 @@ function resolveWindowsOverlayBindTargetHandle(targetMpvSocketPath?: string | nu
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
void targetMpvSocketPath;
|
if (targetMpvSocketPath) {
|
||||||
|
const windowTracker = appState.windowTracker as
|
||||||
|
| {
|
||||||
|
getTargetWindowHandle?: () => number | null;
|
||||||
|
}
|
||||||
|
| null;
|
||||||
|
const trackedHandle = windowTracker?.getTargetWindowHandle?.();
|
||||||
|
if (typeof trackedHandle === 'number' && Number.isFinite(trackedHandle)) {
|
||||||
|
return trackedHandle;
|
||||||
|
}
|
||||||
|
}
|
||||||
return findWindowsMpvTargetWindowHandle();
|
return findWindowsMpvTargetWindowHandle();
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
@@ -4244,15 +4254,13 @@ function persistSessionBindings(
|
|||||||
bindings: CompiledSessionBinding[],
|
bindings: CompiledSessionBinding[],
|
||||||
warnings: ReturnType<typeof compileSessionBindings>['warnings'] = [],
|
warnings: ReturnType<typeof compileSessionBindings>['warnings'] = [],
|
||||||
): void {
|
): void {
|
||||||
|
const artifact = buildPluginSessionBindingsArtifact({
|
||||||
|
bindings,
|
||||||
|
warnings,
|
||||||
|
numericSelectionTimeoutMs: getConfiguredShortcuts().multiCopyTimeoutMs,
|
||||||
|
});
|
||||||
|
writeSessionBindingsArtifact(CONFIG_DIR, artifact);
|
||||||
appState.sessionBindings = bindings;
|
appState.sessionBindings = bindings;
|
||||||
writeSessionBindingsArtifact(
|
|
||||||
CONFIG_DIR,
|
|
||||||
buildPluginSessionBindingsArtifact({
|
|
||||||
bindings,
|
|
||||||
warnings,
|
|
||||||
numericSelectionTimeoutMs: getConfiguredShortcuts().multiCopyTimeoutMs,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (appState.mpvClient?.connected) {
|
if (appState.mpvClient?.connected) {
|
||||||
sendMpvCommandRuntime(appState.mpvClient, [
|
sendMpvCommandRuntime(appState.mpvClient, [
|
||||||
'script-message',
|
'script-message',
|
||||||
|
|||||||
@@ -389,6 +389,88 @@ function createKeyboardHandlerHarness() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('session help chord resolver follows remapped session bindings', async () => {
|
||||||
|
const { handlers, testGlobals } = createKeyboardHandlerHarness();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await handlers.setupMpvInputForwarding();
|
||||||
|
|
||||||
|
assert.deepEqual(handlers.getSessionHelpOpeningInfo(), {
|
||||||
|
bindingKey: 'KeyH',
|
||||||
|
fallbackUsed: false,
|
||||||
|
fallbackUnavailable: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
handlers.updateSessionBindings([
|
||||||
|
{
|
||||||
|
sourcePath: 'keybindings[0].key',
|
||||||
|
originalKey: 'KeyH',
|
||||||
|
key: { code: 'KeyH', modifiers: [] },
|
||||||
|
actionType: 'session-action',
|
||||||
|
actionId: 'openJimaku',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: 'keybindings[1].key',
|
||||||
|
originalKey: 'KeyJ',
|
||||||
|
key: { code: 'KeyJ', modifiers: [] },
|
||||||
|
actionType: 'mpv-command',
|
||||||
|
command: ['cycle', 'pause'],
|
||||||
|
},
|
||||||
|
] as never);
|
||||||
|
|
||||||
|
assert.deepEqual(handlers.getSessionHelpOpeningInfo(), {
|
||||||
|
bindingKey: 'KeyK',
|
||||||
|
fallbackUsed: true,
|
||||||
|
fallbackUnavailable: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
handlers.updateSessionBindings([
|
||||||
|
{
|
||||||
|
sourcePath: 'keybindings[0].key',
|
||||||
|
originalKey: 'KeyH',
|
||||||
|
key: { code: 'KeyH', modifiers: [] },
|
||||||
|
actionType: 'session-action',
|
||||||
|
actionId: 'openSessionHelp',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: 'keybindings[1].key',
|
||||||
|
originalKey: 'KeyK',
|
||||||
|
key: { code: 'KeyK', modifiers: [] },
|
||||||
|
actionType: 'session-action',
|
||||||
|
actionId: 'openControllerSelect',
|
||||||
|
},
|
||||||
|
] as never);
|
||||||
|
|
||||||
|
assert.deepEqual(handlers.getSessionHelpOpeningInfo(), {
|
||||||
|
bindingKey: 'KeyK',
|
||||||
|
fallbackUsed: true,
|
||||||
|
fallbackUnavailable: true,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
testGlobals.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('numeric selection ignores non-digit keys instead of falling through to other shortcuts', async () => {
|
||||||
|
const { handlers, testGlobals, ctx } = createKeyboardHandlerHarness();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await handlers.setupMpvInputForwarding();
|
||||||
|
handlers.beginSessionNumericSelection('copySubtitleMultiple');
|
||||||
|
|
||||||
|
testGlobals.dispatchKeydown({ key: 'y', code: 'KeyY' });
|
||||||
|
|
||||||
|
assert.equal(ctx.state.chordPending, false);
|
||||||
|
assert.deepEqual(testGlobals.sessionActions, []);
|
||||||
|
assert.equal(
|
||||||
|
testGlobals.commandEvents.some((event) => event.type === 'forwardKeyDown'),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
testGlobals.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('keyboard mode: left and right move token selection while popup remains open', async () => {
|
test('keyboard mode: left and right move token selection while popup remains open', async () => {
|
||||||
const { ctx, handlers, testGlobals } = createKeyboardHandlerHarness();
|
const { ctx, handlers, testGlobals } = createKeyboardHandlerHarness();
|
||||||
|
|
||||||
|
|||||||
@@ -160,8 +160,9 @@ export function createKeyboardHandlers(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!/^[1-9]$/.test(e.key) || e.ctrlKey || e.metaKey || e.altKey) {
|
if (!/^[1-9]$/.test(e.key) || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) {
|
||||||
return false;
|
e.preventDefault();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -1115,6 +1116,7 @@ export function createKeyboardHandlers(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
beginSessionNumericSelection,
|
beginSessionNumericSelection,
|
||||||
|
getSessionHelpOpeningInfo: resolveSessionHelpChordBinding,
|
||||||
setupMpvInputForwarding,
|
setupMpvInputForwarding,
|
||||||
refreshConfiguredShortcuts,
|
refreshConfiguredShortcuts,
|
||||||
updateSessionBindings,
|
updateSessionBindings,
|
||||||
|
|||||||
@@ -96,6 +96,10 @@ const OVERLAY_SHORTCUTS: Array<{
|
|||||||
{ key: 'markAudioCard', label: 'Mark audio card' },
|
{ key: 'markAudioCard', label: 'Mark audio card' },
|
||||||
{ key: 'openRuntimeOptions', label: 'Open runtime options' },
|
{ key: 'openRuntimeOptions', label: 'Open runtime options' },
|
||||||
{ key: 'openJimaku', label: 'Open jimaku' },
|
{ key: 'openJimaku', label: 'Open jimaku' },
|
||||||
|
{ key: 'openSessionHelp', label: 'Open session help' },
|
||||||
|
{ key: 'openControllerSelect', label: 'Open controller select' },
|
||||||
|
{ key: 'openControllerDebug', label: 'Open controller debug' },
|
||||||
|
{ key: 'toggleSubtitleSidebar', label: 'Toggle subtitle sidebar' },
|
||||||
{ key: 'toggleVisibleOverlayGlobal', label: 'Show/hide visible overlay' },
|
{ key: 'toggleVisibleOverlayGlobal', label: 'Show/hide visible overlay' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -104,11 +108,12 @@ function buildOverlayShortcutSections(shortcuts: RuntimeShortcutConfig): Session
|
|||||||
|
|
||||||
for (const shortcut of OVERLAY_SHORTCUTS) {
|
for (const shortcut of OVERLAY_SHORTCUTS) {
|
||||||
const keybind = shortcuts[shortcut.key];
|
const keybind = shortcuts[shortcut.key];
|
||||||
if (typeof keybind !== 'string') continue;
|
|
||||||
if (keybind.trim().length === 0) continue;
|
|
||||||
|
|
||||||
rows.push({
|
rows.push({
|
||||||
shortcut: formatKeybinding(keybind),
|
shortcut:
|
||||||
|
typeof keybind === 'string' && keybind.trim().length > 0
|
||||||
|
? formatKeybinding(keybind)
|
||||||
|
: 'Unbound',
|
||||||
action: shortcut.label,
|
action: shortcut.label,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -591,13 +596,17 @@ export function createSessionHelpModal(
|
|||||||
priorFocus = document.activeElement;
|
priorFocus = document.activeElement;
|
||||||
|
|
||||||
ctx.state.sessionHelpModalOpen = true;
|
ctx.state.sessionHelpModalOpen = true;
|
||||||
|
helpSections = [];
|
||||||
|
helpFilterValue = '';
|
||||||
options.syncSettingsModalSubtitleSuppression();
|
options.syncSettingsModalSubtitleSuppression();
|
||||||
ctx.dom.overlay.classList.add('interactive');
|
ctx.dom.overlay.classList.add('interactive');
|
||||||
ctx.dom.sessionHelpModal.classList.remove('hidden');
|
ctx.dom.sessionHelpModal.classList.remove('hidden');
|
||||||
ctx.dom.sessionHelpModal.setAttribute('aria-hidden', 'false');
|
ctx.dom.sessionHelpModal.setAttribute('aria-hidden', 'false');
|
||||||
ctx.dom.sessionHelpModal.setAttribute('tabindex', '-1');
|
ctx.dom.sessionHelpModal.setAttribute('tabindex', '-1');
|
||||||
ctx.dom.sessionHelpFilter.value = '';
|
ctx.dom.sessionHelpFilter.value = '';
|
||||||
helpFilterValue = '';
|
ctx.state.sessionHelpSelectedIndex = 0;
|
||||||
|
ctx.dom.sessionHelpContent.innerHTML = '';
|
||||||
|
ctx.dom.sessionHelpContent.classList.remove('session-help-content-no-results');
|
||||||
if (ctx.platform.shouldToggleMouseIgnore) {
|
if (ctx.platform.shouldToggleMouseIgnore) {
|
||||||
window.electronAPI.setIgnoreMouseEvents(false);
|
window.electronAPI.setIgnoreMouseEvents(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -431,11 +431,7 @@ function registerModalOpenHandlers(): void {
|
|||||||
});
|
});
|
||||||
window.electronAPI.onOpenSessionHelp(() => {
|
window.electronAPI.onOpenSessionHelp(() => {
|
||||||
runGuarded('session-help:open', () => {
|
runGuarded('session-help:open', () => {
|
||||||
sessionHelpModal.openSessionHelpModal({
|
sessionHelpModal.openSessionHelpModal(keyboardHandlers.getSessionHelpOpeningInfo());
|
||||||
bindingKey: 'KeyH',
|
|
||||||
fallbackUsed: false,
|
|
||||||
fallbackUnavailable: false,
|
|
||||||
});
|
|
||||||
window.electronAPI.notifyOverlayModalOpened('session-help');
|
window.electronAPI.notifyOverlayModalOpened('session-help');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export class WindowsWindowTracker extends BaseWindowTracker {
|
|||||||
private trackingLossStartedAtMs: number | null = null;
|
private trackingLossStartedAtMs: number | null = null;
|
||||||
private targetWindowMinimized = false;
|
private targetWindowMinimized = false;
|
||||||
private readonly targetMpvSocketPath: string | null;
|
private readonly targetMpvSocketPath: string | null;
|
||||||
|
private currentTargetWindowHwnd: number | null = null;
|
||||||
|
|
||||||
constructor(_targetMpvSocketPath?: string, deps: WindowsTrackerDeps = {}) {
|
constructor(_targetMpvSocketPath?: string, deps: WindowsTrackerDeps = {}) {
|
||||||
super();
|
super();
|
||||||
@@ -81,6 +82,10 @@ export class WindowsWindowTracker extends BaseWindowTracker {
|
|||||||
return this.targetWindowMinimized;
|
return this.targetWindowMinimized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTargetWindowHandle(): number | null {
|
||||||
|
return this.currentTargetWindowHwnd;
|
||||||
|
}
|
||||||
|
|
||||||
private maybeLogPollError(error: Error): void {
|
private maybeLogPollError(error: Error): void {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const fingerprint = error.message;
|
const fingerprint = error.message;
|
||||||
@@ -122,7 +127,7 @@ export class WindowsWindowTracker extends BaseWindowTracker {
|
|||||||
|
|
||||||
private selectBestMatch(
|
private selectBestMatch(
|
||||||
result: MpvPollResult,
|
result: MpvPollResult,
|
||||||
): { geometry: WindowGeometry; focused: boolean } | null {
|
): { geometry: WindowGeometry; focused: boolean; hwnd: number } | null {
|
||||||
if (result.matches.length === 0) return null;
|
if (result.matches.length === 0) return null;
|
||||||
|
|
||||||
const focusedMatch = result.matches.find((m) => m.isForeground);
|
const focusedMatch = result.matches.find((m) => m.isForeground);
|
||||||
@@ -133,6 +138,7 @@ export class WindowsWindowTracker extends BaseWindowTracker {
|
|||||||
return {
|
return {
|
||||||
geometry: best.bounds,
|
geometry: best.bounds,
|
||||||
focused: best.isForeground,
|
focused: best.isForeground,
|
||||||
|
hwnd: best.hwnd,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +153,7 @@ export class WindowsWindowTracker extends BaseWindowTracker {
|
|||||||
if (best) {
|
if (best) {
|
||||||
this.resetTrackingLossState();
|
this.resetTrackingLossState();
|
||||||
this.targetWindowMinimized = false;
|
this.targetWindowMinimized = false;
|
||||||
|
this.currentTargetWindowHwnd = best.hwnd;
|
||||||
this.updateTargetWindowFocused(best.focused);
|
this.updateTargetWindowFocused(best.focused);
|
||||||
this.updateGeometry(best.geometry);
|
this.updateGeometry(best.geometry);
|
||||||
return;
|
return;
|
||||||
@@ -154,18 +161,21 @@ export class WindowsWindowTracker extends BaseWindowTracker {
|
|||||||
|
|
||||||
if (result.windowState === 'minimized') {
|
if (result.windowState === 'minimized') {
|
||||||
this.targetWindowMinimized = true;
|
this.targetWindowMinimized = true;
|
||||||
|
this.currentTargetWindowHwnd = null;
|
||||||
this.updateTargetWindowFocused(false);
|
this.updateTargetWindowFocused(false);
|
||||||
this.registerTrackingMiss(this.minimizedTrackingLossGraceMs);
|
this.registerTrackingMiss(this.minimizedTrackingLossGraceMs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.targetWindowMinimized = false;
|
this.targetWindowMinimized = false;
|
||||||
|
this.currentTargetWindowHwnd = null;
|
||||||
this.updateTargetWindowFocused(false);
|
this.updateTargetWindowFocused(false);
|
||||||
this.registerTrackingMiss();
|
this.registerTrackingMiss();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const err = error instanceof Error ? error : new Error(String(error));
|
const err = error instanceof Error ? error : new Error(String(error));
|
||||||
this.maybeLogPollError(err);
|
this.maybeLogPollError(err);
|
||||||
this.targetWindowMinimized = false;
|
this.targetWindowMinimized = false;
|
||||||
|
this.currentTargetWindowHwnd = null;
|
||||||
this.updateTargetWindowFocused(false);
|
this.updateTargetWindowFocused(false);
|
||||||
this.registerTrackingMiss();
|
this.registerTrackingMiss();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user