mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-09 15:13:32 -07:00
feat(notifications): add overlay notifications with position config
- Add Catppuccin Macchiato overlay notification stack with 3s transient timeout - Add `notifications.overlayPosition` config (top-left | top | top-right) - Route startup tokenization and subtitle annotation status through configured surfaces - Deduplicate rapid subtitle mode toggle notifications - Change `both` to mean overlay + system; add `osd-system` as legacy alias for old behavior - Keep `osd`/`osd-system` as config-file-only legacy values; Settings UI offers overlay/system/both/none
This commit is contained in:
@@ -45,6 +45,7 @@ function createContext(overrides: Partial<LauncherCommandContext> = {}): Launche
|
||||
autoStart: true,
|
||||
autoStartVisibleOverlay: true,
|
||||
autoStartPauseUntilReady: true,
|
||||
osdMessages: false,
|
||||
texthookerEnabled: false,
|
||||
aniskipEnabled: true,
|
||||
aniskipButtonKey: 'TAB',
|
||||
|
||||
@@ -82,6 +82,7 @@ function createContext(): LauncherCommandContext {
|
||||
autoStart: true,
|
||||
autoStartVisibleOverlay: true,
|
||||
autoStartPauseUntilReady: true,
|
||||
osdMessages: false,
|
||||
texthookerEnabled: false,
|
||||
aniskipEnabled: true,
|
||||
aniskipButtonKey: 'TAB',
|
||||
@@ -209,6 +210,7 @@ test('plugin auto-start playback leaves app lifetime to managed-playback owner',
|
||||
autoStart: true,
|
||||
autoStartVisibleOverlay: false,
|
||||
autoStartPauseUntilReady: false,
|
||||
osdMessages: false,
|
||||
texthookerEnabled: false,
|
||||
aniskipEnabled: true,
|
||||
aniskipButtonKey: 'TAB',
|
||||
@@ -272,6 +274,7 @@ test('plugin auto-start playback attaches a warm background app through the laun
|
||||
autoStart: true,
|
||||
autoStartVisibleOverlay: true,
|
||||
autoStartPauseUntilReady: true,
|
||||
osdMessages: false,
|
||||
texthookerEnabled: true,
|
||||
aniskipEnabled: true,
|
||||
aniskipButtonKey: 'TAB',
|
||||
@@ -341,6 +344,7 @@ test('plugin auto-start attach mode reuses launcher-resolved config dir for app
|
||||
autoStart: true,
|
||||
autoStartVisibleOverlay: true,
|
||||
autoStartPauseUntilReady: true,
|
||||
osdMessages: false,
|
||||
texthookerEnabled: true,
|
||||
aniskipEnabled: true,
|
||||
aniskipButtonKey: 'TAB',
|
||||
@@ -403,6 +407,7 @@ test('plugin auto-start attach mode omits texthooker flag when CLI texthooker is
|
||||
autoStart: true,
|
||||
autoStartVisibleOverlay: true,
|
||||
autoStartPauseUntilReady: true,
|
||||
osdMessages: false,
|
||||
texthookerEnabled: true,
|
||||
aniskipEnabled: true,
|
||||
aniskipButtonKey: 'TAB',
|
||||
|
||||
@@ -129,6 +129,11 @@ test('parseLauncherMpvConfig ignores invalid launch mode values', () => {
|
||||
test('parsePluginRuntimeConfigFromMainConfig maps config.jsonc values over plugin defaults', () => {
|
||||
const parsed = parsePluginRuntimeConfigFromMainConfig({
|
||||
auto_start_overlay: false,
|
||||
ankiConnect: {
|
||||
behavior: {
|
||||
notificationType: 'osd-system',
|
||||
},
|
||||
},
|
||||
texthooker: {
|
||||
launchAtStartup: false,
|
||||
},
|
||||
@@ -148,18 +153,32 @@ test('parsePluginRuntimeConfigFromMainConfig maps config.jsonc values over plugi
|
||||
assert.equal(parsed.autoStart, true);
|
||||
assert.equal(parsed.autoStartVisibleOverlay, false);
|
||||
assert.equal(parsed.autoStartPauseUntilReady, true);
|
||||
assert.equal(parsed.osdMessages, true);
|
||||
assert.equal(parsed.binaryPath, '/opt/SubMiner/SubMiner.AppImage');
|
||||
assert.equal(parsed.texthookerEnabled, false);
|
||||
assert.equal(parsed.aniskipEnabled, false);
|
||||
assert.equal(parsed.aniskipButtonKey, 'F8');
|
||||
});
|
||||
|
||||
test('parsePluginRuntimeConfigFromMainConfig disables plugin osd messages for overlay notification routing', () => {
|
||||
const parsed = parsePluginRuntimeConfigFromMainConfig({
|
||||
ankiConnect: {
|
||||
behavior: {
|
||||
notificationType: 'both',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(parsed.osdMessages, false);
|
||||
});
|
||||
|
||||
test('parsePluginRuntimeConfigFromMainConfig defaults to background-only managed startup', () => {
|
||||
const parsed = parsePluginRuntimeConfigFromMainConfig(null);
|
||||
|
||||
assert.equal(parsed.autoStart, true);
|
||||
assert.equal(parsed.autoStartVisibleOverlay, false);
|
||||
assert.equal(parsed.autoStartPauseUntilReady, true);
|
||||
assert.equal(parsed.osdMessages, false);
|
||||
assert.equal(parsed.texthookerEnabled, false);
|
||||
assert.equal(parsed.aniskipEnabled, true);
|
||||
assert.equal(parsed.aniskipButtonKey, 'TAB');
|
||||
@@ -175,6 +194,7 @@ test('buildPluginRuntimeScriptOptParts emits config values that override plugin
|
||||
autoStart: true,
|
||||
autoStartVisibleOverlay: false,
|
||||
autoStartPauseUntilReady: true,
|
||||
osdMessages: true,
|
||||
texthookerEnabled: false,
|
||||
aniskipEnabled: false,
|
||||
aniskipButtonKey: 'F8',
|
||||
@@ -188,6 +208,7 @@ test('buildPluginRuntimeScriptOptParts emits config values that override plugin
|
||||
'subminer-auto_start=yes',
|
||||
'subminer-auto_start_visible_overlay=no',
|
||||
'subminer-auto_start_pause_until_ready=yes',
|
||||
'subminer-osd_messages=yes',
|
||||
'subminer-texthooker_enabled=no',
|
||||
'subminer-aniskip_enabled=no',
|
||||
'subminer-aniskip_button_key=F8',
|
||||
@@ -205,6 +226,7 @@ test('buildPluginRuntimeScriptOptParts strips script-option delimiters from stri
|
||||
autoStart: true,
|
||||
autoStartVisibleOverlay: false,
|
||||
autoStartPauseUntilReady: true,
|
||||
osdMessages: false,
|
||||
texthookerEnabled: false,
|
||||
aniskipEnabled: false,
|
||||
aniskipButtonKey: 'F8,\nF9',
|
||||
@@ -218,6 +240,7 @@ test('buildPluginRuntimeScriptOptParts strips script-option delimiters from stri
|
||||
'subminer-auto_start=yes',
|
||||
'subminer-auto_start_visible_overlay=no',
|
||||
'subminer-auto_start_pause_until_ready=yes',
|
||||
'subminer-osd_messages=no',
|
||||
'subminer-texthooker_enabled=no',
|
||||
'subminer-aniskip_enabled=no',
|
||||
'subminer-aniskip_button_key=F8 F9',
|
||||
|
||||
@@ -22,6 +22,11 @@ function nonEmptyStringOrDefault(value: unknown, fallback: string): string {
|
||||
return trimmed.length > 0 ? trimmed : fallback;
|
||||
}
|
||||
|
||||
function pluginOsdMessagesFromNotificationType(root: Record<string, unknown> | null): boolean {
|
||||
const notificationType = rootObject(rootObject(root, 'ankiConnect'), 'behavior').notificationType;
|
||||
return notificationType === 'osd' || notificationType === 'osd-system';
|
||||
}
|
||||
|
||||
function validBackendOrDefault(value: unknown, fallback: Backend): Backend {
|
||||
if (typeof value !== 'string') return fallback;
|
||||
const normalized = value.trim().toLowerCase();
|
||||
@@ -53,6 +58,7 @@ export function parsePluginRuntimeConfigFromMainConfig(
|
||||
autoStart: booleanOrDefault(mpvConfig.autoStartSubMiner, true),
|
||||
autoStartVisibleOverlay: booleanOrDefault(root?.auto_start_overlay, false),
|
||||
autoStartPauseUntilReady: booleanOrDefault(mpvConfig.pauseUntilOverlayReady, true),
|
||||
osdMessages: pluginOsdMessagesFromNotificationType(root),
|
||||
texthookerEnabled: booleanOrDefault(texthooker.launchAtStartup, false),
|
||||
aniskipEnabled: booleanOrDefault(mpvConfig.aniskipEnabled, true),
|
||||
aniskipButtonKey: nonEmptyStringOrDefault(mpvConfig.aniskipButtonKey, 'TAB'),
|
||||
@@ -72,7 +78,7 @@ export function readPluginRuntimeConfig(logLevel: LogLevel): PluginRuntimeConfig
|
||||
log(
|
||||
'debug',
|
||||
logLevel,
|
||||
`Using mpv plugin settings from SubMiner config: socket_path=${parsed.socketPath}, backend=${parsed.backend}, auto_start=${parsed.autoStart}, auto_start_visible_overlay=${parsed.autoStartVisibleOverlay}, auto_start_pause_until_ready=${parsed.autoStartPauseUntilReady}, texthooker_enabled=${parsed.texthookerEnabled}, aniskip_enabled=${parsed.aniskipEnabled}, aniskip_button_key=${parsed.aniskipButtonKey}`,
|
||||
`Using mpv plugin settings from SubMiner config: socket_path=${parsed.socketPath}, backend=${parsed.backend}, auto_start=${parsed.autoStart}, auto_start_visible_overlay=${parsed.autoStartVisibleOverlay}, auto_start_pause_until_ready=${parsed.autoStartPauseUntilReady}, osd_messages=${parsed.osdMessages}, texthooker_enabled=${parsed.texthookerEnabled}, aniskip_enabled=${parsed.aniskipEnabled}, aniskip_button_key=${parsed.aniskipButtonKey}`,
|
||||
);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
@@ -387,6 +387,7 @@ test('buildRuntimeExtraScriptOptParts marks launcher-owned startup pause gate',
|
||||
autoStart: true,
|
||||
autoStartVisibleOverlay: true,
|
||||
autoStartPauseUntilReady: true,
|
||||
osdMessages: false,
|
||||
texthookerEnabled: false,
|
||||
aniskipEnabled: true,
|
||||
aniskipButtonKey: 'TAB',
|
||||
@@ -405,6 +406,7 @@ test('shouldResolveAniSkipMetadataForLaunch respects disabled runtime plugin Ani
|
||||
autoStart: true,
|
||||
autoStartVisibleOverlay: true,
|
||||
autoStartPauseUntilReady: true,
|
||||
osdMessages: false,
|
||||
texthookerEnabled: false,
|
||||
aniskipEnabled: false,
|
||||
aniskipButtonKey: 'TAB',
|
||||
|
||||
@@ -209,6 +209,7 @@ export interface PluginRuntimeConfig {
|
||||
autoStart: boolean;
|
||||
autoStartVisibleOverlay: boolean;
|
||||
autoStartPauseUntilReady: boolean;
|
||||
osdMessages: boolean;
|
||||
texthookerEnabled: boolean;
|
||||
aniskipEnabled: boolean;
|
||||
aniskipButtonKey: string;
|
||||
|
||||
Reference in New Issue
Block a user