mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
Improve startup dictionary progress and fix overlay/plugin input handlin
- show a dedicated startup OSD "building" phase for character dictionary sync - forward bare `Tab` from visible overlay to mpv so AniSkip works while focused - fix Windows plugin env override resolution for `SUBMINER_BINARY_PATH`
This commit is contained in:
@@ -72,6 +72,10 @@ export {
|
||||
syncOverlayWindowLayer,
|
||||
updateOverlayWindowBounds,
|
||||
} from './overlay-window';
|
||||
export {
|
||||
handleOverlayWindowBeforeInputEvent,
|
||||
isTabInputForMpvForwarding,
|
||||
} from './overlay-window-input';
|
||||
export { initializeOverlayRuntime } from './overlay-runtime-init';
|
||||
export { setVisibleOverlayVisible, updateVisibleOverlayVisibility } from './overlay-visibility';
|
||||
export {
|
||||
|
||||
61
src/core/services/overlay-window-input.ts
Normal file
61
src/core/services/overlay-window-input.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
export type OverlayWindowKind = 'visible' | 'modal';
|
||||
|
||||
export function isTabInputForMpvForwarding(input: Electron.Input): boolean {
|
||||
if (input.type !== 'keyDown' || input.isAutoRepeat) return false;
|
||||
if (input.alt || input.control || input.meta || input.shift) return false;
|
||||
return input.code === 'Tab' || input.key === 'Tab';
|
||||
}
|
||||
|
||||
function isLookupWindowToggleInput(input: Electron.Input): boolean {
|
||||
if (input.type !== 'keyDown') return false;
|
||||
if (input.alt) return false;
|
||||
if (!input.control && !input.meta) return false;
|
||||
if (input.shift) return false;
|
||||
const normalizedKey = typeof input.key === 'string' ? input.key.toLowerCase() : '';
|
||||
return input.code === 'KeyY' || normalizedKey === 'y';
|
||||
}
|
||||
|
||||
function isKeyboardModeToggleInput(input: Electron.Input): boolean {
|
||||
if (input.type !== 'keyDown') return false;
|
||||
if (input.alt) return false;
|
||||
if (!input.control && !input.meta) return false;
|
||||
if (!input.shift) return false;
|
||||
const normalizedKey = typeof input.key === 'string' ? input.key.toLowerCase() : '';
|
||||
return input.code === 'KeyY' || normalizedKey === 'y';
|
||||
}
|
||||
|
||||
export function handleOverlayWindowBeforeInputEvent(options: {
|
||||
kind: OverlayWindowKind;
|
||||
windowVisible: boolean;
|
||||
input: Electron.Input;
|
||||
preventDefault: () => void;
|
||||
sendKeyboardModeToggleRequested: () => void;
|
||||
sendLookupWindowToggleRequested: () => void;
|
||||
tryHandleOverlayShortcutLocalFallback: (input: Electron.Input) => boolean;
|
||||
forwardTabToMpv: () => void;
|
||||
}): boolean {
|
||||
if (options.kind === 'modal') return false;
|
||||
if (!options.windowVisible) return false;
|
||||
|
||||
if (isKeyboardModeToggleInput(options.input)) {
|
||||
options.preventDefault();
|
||||
options.sendKeyboardModeToggleRequested();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isLookupWindowToggleInput(options.input)) {
|
||||
options.preventDefault();
|
||||
options.sendLookupWindowToggleRequested();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isTabInputForMpvForwarding(options.input)) {
|
||||
options.preventDefault();
|
||||
options.forwardTabToMpv();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!options.tryHandleOverlayShortcutLocalFallback(options.input)) return false;
|
||||
options.preventDefault();
|
||||
return true;
|
||||
}
|
||||
84
src/core/services/overlay-window.test.ts
Normal file
84
src/core/services/overlay-window.test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
handleOverlayWindowBeforeInputEvent,
|
||||
isTabInputForMpvForwarding,
|
||||
} from './overlay-window-input';
|
||||
|
||||
test('isTabInputForMpvForwarding matches bare Tab keydown only', () => {
|
||||
assert.equal(
|
||||
isTabInputForMpvForwarding({
|
||||
type: 'keyDown',
|
||||
key: 'Tab',
|
||||
code: 'Tab',
|
||||
} as Electron.Input),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
isTabInputForMpvForwarding({
|
||||
type: 'keyDown',
|
||||
key: 'Tab',
|
||||
code: 'Tab',
|
||||
shift: true,
|
||||
} as Electron.Input),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
isTabInputForMpvForwarding({
|
||||
type: 'keyUp',
|
||||
key: 'Tab',
|
||||
code: 'Tab',
|
||||
} as Electron.Input),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test('handleOverlayWindowBeforeInputEvent forwards Tab to mpv for visible overlays', () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const handled = handleOverlayWindowBeforeInputEvent({
|
||||
kind: 'visible',
|
||||
windowVisible: true,
|
||||
input: {
|
||||
type: 'keyDown',
|
||||
key: 'Tab',
|
||||
code: 'Tab',
|
||||
} as Electron.Input,
|
||||
preventDefault: () => calls.push('prevent-default'),
|
||||
sendKeyboardModeToggleRequested: () => calls.push('keyboard-mode'),
|
||||
sendLookupWindowToggleRequested: () => calls.push('lookup-toggle'),
|
||||
tryHandleOverlayShortcutLocalFallback: () => {
|
||||
calls.push('fallback');
|
||||
return false;
|
||||
},
|
||||
forwardTabToMpv: () => calls.push('forward-tab'),
|
||||
});
|
||||
|
||||
assert.equal(handled, true);
|
||||
assert.deepEqual(calls, ['prevent-default', 'forward-tab']);
|
||||
});
|
||||
|
||||
test('handleOverlayWindowBeforeInputEvent leaves modal Tab handling alone', () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const handled = handleOverlayWindowBeforeInputEvent({
|
||||
kind: 'modal',
|
||||
windowVisible: true,
|
||||
input: {
|
||||
type: 'keyDown',
|
||||
key: 'Tab',
|
||||
code: 'Tab',
|
||||
} as Electron.Input,
|
||||
preventDefault: () => calls.push('prevent-default'),
|
||||
sendKeyboardModeToggleRequested: () => calls.push('keyboard-mode'),
|
||||
sendLookupWindowToggleRequested: () => calls.push('lookup-toggle'),
|
||||
tryHandleOverlayShortcutLocalFallback: () => {
|
||||
calls.push('fallback');
|
||||
return false;
|
||||
},
|
||||
forwardTabToMpv: () => calls.push('forward-tab'),
|
||||
});
|
||||
|
||||
assert.equal(handled, false);
|
||||
assert.deepEqual(calls, []);
|
||||
});
|
||||
@@ -3,6 +3,10 @@ import * as path from 'path';
|
||||
import { WindowGeometry } from '../../types';
|
||||
import { createLogger } from '../../logger';
|
||||
import { IPC_CHANNELS } from '../../shared/ipc/contracts';
|
||||
import {
|
||||
handleOverlayWindowBeforeInputEvent,
|
||||
type OverlayWindowKind,
|
||||
} from './overlay-window-input';
|
||||
|
||||
const logger = createLogger('main:overlay-window');
|
||||
const overlayWindowLayerByInstance = new WeakMap<BrowserWindow, OverlayWindowKind>();
|
||||
@@ -23,26 +27,6 @@ function loadOverlayWindowLayer(window: BrowserWindow, layer: OverlayWindowKind)
|
||||
});
|
||||
}
|
||||
|
||||
export type OverlayWindowKind = 'visible' | 'modal';
|
||||
|
||||
function isLookupWindowToggleInput(input: Electron.Input): boolean {
|
||||
if (input.type !== 'keyDown') return false;
|
||||
if (input.alt) return false;
|
||||
if (!input.control && !input.meta) return false;
|
||||
if (input.shift) return false;
|
||||
const normalizedKey = typeof input.key === 'string' ? input.key.toLowerCase() : '';
|
||||
return input.code === 'KeyY' || normalizedKey === 'y';
|
||||
}
|
||||
|
||||
function isKeyboardModeToggleInput(input: Electron.Input): boolean {
|
||||
if (input.type !== 'keyDown') return false;
|
||||
if (input.alt) return false;
|
||||
if (!input.control && !input.meta) return false;
|
||||
if (!input.shift) return false;
|
||||
const normalizedKey = typeof input.key === 'string' ? input.key.toLowerCase() : '';
|
||||
return input.code === 'KeyY' || normalizedKey === 'y';
|
||||
}
|
||||
|
||||
export function updateOverlayWindowBounds(
|
||||
geometry: WindowGeometry,
|
||||
window: BrowserWindow | null,
|
||||
@@ -92,6 +76,7 @@ export function createOverlayWindow(
|
||||
setOverlayDebugVisualizationEnabled: (enabled: boolean) => void;
|
||||
isOverlayVisible: (kind: OverlayWindowKind) => boolean;
|
||||
tryHandleOverlayShortcutLocalFallback: (input: Electron.Input) => boolean;
|
||||
forwardTabToMpv: () => void;
|
||||
onWindowClosed: (kind: OverlayWindowKind) => void;
|
||||
},
|
||||
): BrowserWindow {
|
||||
@@ -142,20 +127,19 @@ export function createOverlayWindow(
|
||||
}
|
||||
|
||||
window.webContents.on('before-input-event', (event, input) => {
|
||||
if (kind === 'modal') return;
|
||||
if (!window.isVisible()) return;
|
||||
if (isKeyboardModeToggleInput(input)) {
|
||||
event.preventDefault();
|
||||
window.webContents.send(IPC_CHANNELS.event.keyboardModeToggleRequested);
|
||||
return;
|
||||
}
|
||||
if (isLookupWindowToggleInput(input)) {
|
||||
event.preventDefault();
|
||||
window.webContents.send(IPC_CHANNELS.event.lookupWindowToggleRequested);
|
||||
return;
|
||||
}
|
||||
if (!options.tryHandleOverlayShortcutLocalFallback(input)) return;
|
||||
event.preventDefault();
|
||||
handleOverlayWindowBeforeInputEvent({
|
||||
kind,
|
||||
windowVisible: window.isVisible(),
|
||||
input,
|
||||
preventDefault: () => event.preventDefault(),
|
||||
sendKeyboardModeToggleRequested: () =>
|
||||
window.webContents.send(IPC_CHANNELS.event.keyboardModeToggleRequested),
|
||||
sendLookupWindowToggleRequested: () =>
|
||||
window.webContents.send(IPC_CHANNELS.event.lookupWindowToggleRequested),
|
||||
tryHandleOverlayShortcutLocalFallback: (nextInput) =>
|
||||
options.tryHandleOverlayShortcutLocalFallback(nextInput),
|
||||
forwardTabToMpv: () => options.forwardTabToMpv(),
|
||||
});
|
||||
});
|
||||
|
||||
window.hide();
|
||||
@@ -185,3 +169,5 @@ export function syncOverlayWindowLayer(window: BrowserWindow, layer: 'visible'):
|
||||
if (overlayWindowLayerByInstance.get(window) === layer) return;
|
||||
loadOverlayWindowLayer(window, layer);
|
||||
}
|
||||
|
||||
export type { OverlayWindowKind } from './overlay-window-input';
|
||||
|
||||
Reference in New Issue
Block a user