mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-26 04:19:27 -07:00
fix: restore linux multi-copy digit capture
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
id: TASK-292
|
||||||
|
title: Restore Linux multi-subtitle copy digit capture
|
||||||
|
status: Done
|
||||||
|
assignee:
|
||||||
|
- '@codex'
|
||||||
|
created_date: '2026-04-25 21:31'
|
||||||
|
updated_date: '2026-04-25 21:36'
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
- linux
|
||||||
|
- shortcuts
|
||||||
|
- clipboard
|
||||||
|
dependencies: []
|
||||||
|
priority: high
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
On Linux, the copy-subtitle-multiple shortcut opens the numeric prompt but the follow-up digit is not captured, so the flow times out. User confirmed `wl-copy` itself is installed and working, so investigate the shortcut/digit capture path and restore multi-line subtitle copy without regressing existing session action behavior.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [x] #1 Linux copy-subtitle-multiple shortcut accepts a follow-up digit and copies that number of recent subtitle lines instead of timing out.
|
||||||
|
- [x] #2 The fix avoids depending on Linux Electron global shortcut digit registration for the follow-up numeric selection when a renderer-visible session can handle it.
|
||||||
|
- [x] #3 Regression tests cover the Linux multi-copy shortcut/digit flow and existing non-Linux/global shortcut behavior remains intact.
|
||||||
|
<!-- AC:END -->
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
<!-- SECTION:PLAN:BEGIN -->
|
||||||
|
1. Add failing regression coverage for Linux copy-subtitle-multiple local shortcut fallback starting renderer/session numeric selection instead of main-process digit globalShortcut capture.
|
||||||
|
2. Patch the overlay shortcut fallback/runtime path so Linux visible-overlay multi-copy and mine-sentence-multiple can dispatch session-action numeric selection when renderer handling is available, while preserving main-process numeric sessions for CLI/non-renderer paths.
|
||||||
|
3. Run targeted tests for shortcut fallback, overlay runtime, and renderer keyboard numeric selection; then run typecheck or a wider focused gate if needed.
|
||||||
|
4. Update task acceptance criteria/final notes after verification.
|
||||||
|
<!-- SECTION:PLAN:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
Implemented the approved path by keeping multi-step numeric overlay shortcuts out of the main-process local fallback. The visible overlay now receives the original keydown and uses the existing renderer/session-action numeric selection flow for follow-up digits, avoiding Linux Electron globalShortcut digit capture for multi-copy and mine-sentence-multiple. Verification: targeted shortcut/renderer tests and changelog lint pass. `bun run typecheck` is currently blocked by unrelated existing errors in CLI/AniList dictionary-candidate work and `src/main/dependencies.ts` manual-selection API shape.
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
|
|
||||||
|
## Final Summary
|
||||||
|
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||||
|
Restored Linux multi-line subtitle copy by preventing main-process overlay shortcut fallback from consuming multi-step numeric shortcuts (`copySubtitleMultiple` and `mineSentenceMultiple`). Those shortcuts now fall through to the visible overlay renderer, where the existing session binding flow prompts for a digit and dispatches the counted session action locally instead of relying on Electron globalShortcut digit registration. Added regression coverage for the fallback behavior and renderer follow-up digit dispatch, plus a changelog fragment.
|
||||||
|
|
||||||
|
Verification: `bun test src/core/services/overlay-shortcut-handler.test.ts src/renderer/handlers/keyboard.test.ts`; `bun run changelog:lint`. Full `bun run typecheck` was attempted but is blocked by unrelated current worktree errors in CLI/AniList dictionary-candidate tests/types and `src/main/dependencies.ts`.
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||||
4
changes/292-linux-multi-copy.md
Normal file
4
changes/292-linux-multi-copy.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
type: fixed
|
||||||
|
area: overlay
|
||||||
|
|
||||||
|
- Fixed Linux multi-line subtitle copy timing out after the prompt by letting the overlay handle the follow-up digit locally.
|
||||||
@@ -135,12 +135,11 @@ test('createOverlayShortcutRuntimeHandlers reports async failures via OSD', asyn
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('runOverlayShortcutLocalFallback dispatches matching actions with timeout', () => {
|
test('runOverlayShortcutLocalFallback dispatches matching single-step actions', () => {
|
||||||
const handled: string[] = [];
|
const handled: string[] = [];
|
||||||
const matched: Array<{ accelerator: string; allowWhenRegistered: boolean }> = [];
|
const matched: Array<{ accelerator: string; allowWhenRegistered: boolean }> = [];
|
||||||
const shortcuts = makeShortcuts({
|
const shortcuts = makeShortcuts({
|
||||||
copySubtitleMultiple: 'Ctrl+M',
|
copySubtitle: 'Ctrl+M',
|
||||||
multiCopyTimeoutMs: 4321,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = runOverlayShortcutLocalFallback(
|
const result = runOverlayShortcutLocalFallback(
|
||||||
@@ -169,10 +168,61 @@ test('runOverlayShortcutLocalFallback dispatches matching actions with timeout',
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(result, true);
|
assert.equal(result, true);
|
||||||
assert.deepEqual(handled, ['copySubtitleMultiple:4321']);
|
assert.deepEqual(handled, ['copySubtitle']);
|
||||||
assert.deepEqual(matched, [{ accelerator: 'Ctrl+M', allowWhenRegistered: false }]);
|
assert.deepEqual(matched, [{ accelerator: 'Ctrl+M', allowWhenRegistered: false }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('runOverlayShortcutLocalFallback leaves multi-step numeric shortcuts for renderer handling', () => {
|
||||||
|
const handled: string[] = [];
|
||||||
|
const shortcuts = makeShortcuts({
|
||||||
|
copySubtitleMultiple: 'Ctrl+M',
|
||||||
|
mineSentenceMultiple: 'Ctrl+N',
|
||||||
|
multiCopyTimeoutMs: 4321,
|
||||||
|
});
|
||||||
|
|
||||||
|
const copyResult = runOverlayShortcutLocalFallback(
|
||||||
|
{} as Electron.Input,
|
||||||
|
shortcuts,
|
||||||
|
(_input, accelerator) => accelerator === 'Ctrl+M',
|
||||||
|
{
|
||||||
|
openRuntimeOptions: () => handled.push('openRuntimeOptions'),
|
||||||
|
openJimaku: () => handled.push('openJimaku'),
|
||||||
|
markAudioCard: () => handled.push('markAudioCard'),
|
||||||
|
copySubtitleMultiple: (timeoutMs) => handled.push(`copySubtitleMultiple:${timeoutMs}`),
|
||||||
|
copySubtitle: () => handled.push('copySubtitle'),
|
||||||
|
toggleSecondarySub: () => handled.push('toggleSecondarySub'),
|
||||||
|
updateLastCardFromClipboard: () => handled.push('updateLastCardFromClipboard'),
|
||||||
|
triggerFieldGrouping: () => handled.push('triggerFieldGrouping'),
|
||||||
|
triggerSubsync: () => handled.push('triggerSubsync'),
|
||||||
|
mineSentence: () => handled.push('mineSentence'),
|
||||||
|
mineSentenceMultiple: (timeoutMs) => handled.push(`mineSentenceMultiple:${timeoutMs}`),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const mineResult = runOverlayShortcutLocalFallback(
|
||||||
|
{} as Electron.Input,
|
||||||
|
shortcuts,
|
||||||
|
(_input, accelerator) => accelerator === 'Ctrl+N',
|
||||||
|
{
|
||||||
|
openRuntimeOptions: () => handled.push('openRuntimeOptions'),
|
||||||
|
openJimaku: () => handled.push('openJimaku'),
|
||||||
|
markAudioCard: () => handled.push('markAudioCard'),
|
||||||
|
copySubtitleMultiple: (timeoutMs) => handled.push(`copySubtitleMultiple:${timeoutMs}`),
|
||||||
|
copySubtitle: () => handled.push('copySubtitle'),
|
||||||
|
toggleSecondarySub: () => handled.push('toggleSecondarySub'),
|
||||||
|
updateLastCardFromClipboard: () => handled.push('updateLastCardFromClipboard'),
|
||||||
|
triggerFieldGrouping: () => handled.push('triggerFieldGrouping'),
|
||||||
|
triggerSubsync: () => handled.push('triggerSubsync'),
|
||||||
|
mineSentence: () => handled.push('mineSentence'),
|
||||||
|
mineSentenceMultiple: (timeoutMs) => handled.push(`mineSentenceMultiple:${timeoutMs}`),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(copyResult, false);
|
||||||
|
assert.equal(mineResult, false);
|
||||||
|
assert.deepEqual(handled, []);
|
||||||
|
});
|
||||||
|
|
||||||
test('runOverlayShortcutLocalFallback passes allowWhenRegistered for secondary-sub toggle', () => {
|
test('runOverlayShortcutLocalFallback passes allowWhenRegistered for secondary-sub toggle', () => {
|
||||||
const matched: Array<{ accelerator: string; allowWhenRegistered: boolean }> = [];
|
const matched: Array<{ accelerator: string; allowWhenRegistered: boolean }> = [];
|
||||||
const shortcuts = makeShortcuts({
|
const shortcuts = makeShortcuts({
|
||||||
|
|||||||
@@ -147,12 +147,6 @@ export function runOverlayShortcutLocalFallback(
|
|||||||
handlers.markAudioCard();
|
handlers.markAudioCard();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
accelerator: shortcuts.copySubtitleMultiple,
|
|
||||||
run: () => {
|
|
||||||
handlers.copySubtitleMultiple(shortcuts.multiCopyTimeoutMs);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
accelerator: shortcuts.copySubtitle,
|
accelerator: shortcuts.copySubtitle,
|
||||||
run: () => {
|
run: () => {
|
||||||
@@ -188,12 +182,6 @@ export function runOverlayShortcutLocalFallback(
|
|||||||
handlers.mineSentence();
|
handlers.mineSentence();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
accelerator: shortcuts.mineSentenceMultiple,
|
|
||||||
run: () => {
|
|
||||||
handlers.mineSentenceMultiple(shortcuts.multiCopyTimeoutMs);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
|
|||||||
@@ -1119,6 +1119,32 @@ test('session binding: Ctrl+Shift+O dispatches runtime options locally', async (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('session binding: copy subtitle multiple captures follow-up digit locally', async () => {
|
||||||
|
const { handlers, testGlobals } = createKeyboardHandlerHarness();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await handlers.setupMpvInputForwarding();
|
||||||
|
handlers.updateSessionBindings([
|
||||||
|
{
|
||||||
|
sourcePath: 'shortcuts.copySubtitleMultiple',
|
||||||
|
originalKey: 'Ctrl+M',
|
||||||
|
key: { code: 'KeyM', modifiers: ['ctrl'] },
|
||||||
|
actionType: 'session-action',
|
||||||
|
actionId: 'copySubtitleMultiple',
|
||||||
|
},
|
||||||
|
] as never);
|
||||||
|
|
||||||
|
testGlobals.dispatchKeydown({ key: 'm', code: 'KeyM', ctrlKey: true });
|
||||||
|
testGlobals.dispatchKeydown({ key: '3', code: 'Digit3' });
|
||||||
|
|
||||||
|
assert.deepEqual(testGlobals.sessionActions, [
|
||||||
|
{ actionId: 'copySubtitleMultiple', payload: { count: 3 } },
|
||||||
|
]);
|
||||||
|
} finally {
|
||||||
|
testGlobals.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('keyboard mode: h moves left when popup is closed', async () => {
|
test('keyboard mode: h moves left when popup is closed', async () => {
|
||||||
const { ctx, handlers, testGlobals } = createKeyboardHandlerHarness();
|
const { ctx, handlers, testGlobals } = createKeyboardHandlerHarness();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user