This commit is contained in:
2026-02-17 22:50:57 -08:00
parent ffeef9c136
commit f20d019c11
315 changed files with 9876 additions and 12537 deletions

View File

@@ -4,26 +4,26 @@ export function asFiniteNumber(
min?: number,
max?: number,
): number {
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
if (typeof value !== 'number' || !Number.isFinite(value)) return fallback;
if (min !== undefined && value < min) return min;
if (max !== undefined && value > max) return max;
return value;
}
export function asString(value: unknown, fallback: string): string {
if (typeof value !== "string") return fallback;
if (typeof value !== 'string') return fallback;
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : fallback;
}
export function asBoolean(value: unknown, fallback: boolean): boolean {
if (typeof value === "boolean") return value;
if (typeof value === "string") {
if (typeof value === 'boolean') return value;
if (typeof value === 'string') {
const normalized = value.trim().toLowerCase();
if (normalized === "yes" || normalized === "true" || normalized === "1") {
if (normalized === 'yes' || normalized === 'true' || normalized === '1') {
return true;
}
if (normalized === "no" || normalized === "false" || normalized === "0") {
if (normalized === 'no' || normalized === 'false' || normalized === '0') {
return false;
}
}

View File

@@ -1,13 +1,13 @@
import * as fs from "fs";
import * as path from "path";
import * as readline from "readline";
import { CliArgs } from "../../cli/args";
import { createLogger } from "../../logger";
import * as fs from 'fs';
import * as path from 'path';
import * as readline from 'readline';
import { CliArgs } from '../../cli/args';
import { createLogger } from '../../logger';
const logger = createLogger("core:config-gen");
const logger = createLogger('core:config-gen');
function formatBackupTimestamp(date = new Date()): string {
const pad = (v: number): string => String(v).padStart(2, "0");
const pad = (v: number): string => String(v).padStart(2, '0');
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
}
@@ -20,7 +20,7 @@ function promptYesNo(question: string): Promise<boolean> {
rl.question(question, (answer) => {
rl.close();
const normalized = answer.trim().toLowerCase();
resolve(normalized === "y" || normalized === "yes");
resolve(normalized === 'y' || normalized === 'yes');
});
});
}
@@ -35,14 +35,14 @@ export async function generateDefaultConfigFile(
): Promise<number> {
const targetPath = args.configPath
? path.resolve(args.configPath)
: path.join(options.configDir, "config.jsonc");
: path.join(options.configDir, 'config.jsonc');
const template = options.generateTemplate(options.defaultConfig);
if (fs.existsSync(targetPath)) {
if (args.backupOverwrite) {
const backupPath = `${targetPath}.bak.${formatBackupTimestamp()}`;
fs.copyFileSync(targetPath, backupPath);
fs.writeFileSync(targetPath, template, "utf-8");
fs.writeFileSync(targetPath, template, 'utf-8');
logger.info(`Backed up existing config to ${backupPath}`);
logger.info(`Generated config at ${targetPath}`);
return 0;
@@ -59,13 +59,13 @@ export async function generateDefaultConfigFile(
`Config exists at ${targetPath}. Back up and overwrite? [y/N] `,
);
if (!confirmed) {
logger.info("Config generation cancelled.");
logger.info('Config generation cancelled.');
return 0;
}
const backupPath = `${targetPath}.bak.${formatBackupTimestamp()}`;
fs.copyFileSync(targetPath, backupPath);
fs.writeFileSync(targetPath, template, "utf-8");
fs.writeFileSync(targetPath, template, 'utf-8');
logger.info(`Backed up existing config to ${backupPath}`);
logger.info(`Generated config at ${targetPath}`);
return 0;
@@ -75,7 +75,7 @@ export async function generateDefaultConfigFile(
if (!fs.existsSync(parentDir)) {
fs.mkdirSync(parentDir, { recursive: true });
}
fs.writeFileSync(targetPath, template, "utf-8");
fs.writeFileSync(targetPath, template, 'utf-8');
logger.info(`Generated config at ${targetPath}`);
return 0;
}

View File

@@ -1,7 +1,7 @@
import { CliArgs, shouldStartApp } from "../../cli/args";
import { createLogger } from "../../logger";
import { CliArgs, shouldStartApp } from '../../cli/args';
import { createLogger } from '../../logger';
const logger = createLogger("core:electron-backend");
const logger = createLogger('core:electron-backend');
function getElectronOzonePlatformHint(): string | null {
const hint = process.env.ELECTRON_OZONE_PLATFORM_HINT?.trim().toLowerCase();
@@ -12,31 +12,29 @@ function getElectronOzonePlatformHint(): string | null {
}
function shouldPreferWaylandBackend(): boolean {
return Boolean(
process.env.HYPRLAND_INSTANCE_SIGNATURE || process.env.SWAYSOCK,
);
return Boolean(process.env.HYPRLAND_INSTANCE_SIGNATURE || process.env.SWAYSOCK);
}
export function forceX11Backend(args: CliArgs): void {
if (process.platform !== "linux") return;
if (process.platform !== 'linux') return;
if (!shouldStartApp(args)) return;
if (shouldPreferWaylandBackend()) return;
const hint = getElectronOzonePlatformHint();
if (hint === "x11") return;
if (hint === 'x11') return;
process.env.ELECTRON_OZONE_PLATFORM_HINT = "x11";
process.env.OZONE_PLATFORM = "x11";
process.env.ELECTRON_OZONE_PLATFORM_HINT = 'x11';
process.env.OZONE_PLATFORM = 'x11';
}
export function enforceUnsupportedWaylandMode(args: CliArgs): void {
if (process.platform !== "linux") return;
if (process.platform !== 'linux') return;
if (!shouldStartApp(args)) return;
const hint = getElectronOzonePlatformHint();
if (hint !== "wayland") return;
if (hint !== 'wayland') return;
const message =
"Unsupported Electron backend: Wayland. Set ELECTRON_OZONE_PLATFORM_HINT=x11 and restart SubMiner.";
'Unsupported Electron backend: Wayland. Set ELECTRON_OZONE_PLATFORM_HINT=x11 and restart SubMiner.';
logger.error(message);
throw new Error(message);
}

View File

@@ -1,9 +1,6 @@
export { generateDefaultConfigFile } from "./config-gen";
export {
enforceUnsupportedWaylandMode,
forceX11Backend,
} from "./electron-backend";
export { asBoolean, asFiniteNumber, asString } from "./coerce";
export { resolveKeybindings } from "./keybindings";
export { resolveConfiguredShortcuts } from "./shortcut-config";
export { showDesktopNotification } from "./notification";
export { generateDefaultConfigFile } from './config-gen';
export { enforceUnsupportedWaylandMode, forceX11Backend } from './electron-backend';
export { asBoolean, asFiniteNumber, asString } from './coerce';
export { resolveKeybindings } from './keybindings';
export { resolveConfiguredShortcuts } from './shortcut-config';
export { showDesktopNotification } from './notification';

View File

@@ -1,9 +1,6 @@
import { Config, Keybinding } from "../../types";
import { Config, Keybinding } from '../../types';
export function resolveKeybindings(
config: Config,
defaultKeybindings: Keybinding[],
): Keybinding[] {
export function resolveKeybindings(config: Config, defaultKeybindings: Keybinding[]): Keybinding[] {
const userBindings = config.keybindings || [];
const bindingMap = new Map<string, (string | number)[] | null>();

View File

@@ -1,8 +1,8 @@
import { Notification, nativeImage } from "electron";
import * as fs from "fs";
import { createLogger } from "../../logger";
import { Notification, nativeImage } from 'electron';
import * as fs from 'fs';
import { createLogger } from '../../logger';
const logger = createLogger("core:notification");
const logger = createLogger('core:notification');
export function showDesktopNotification(
title: string,
@@ -20,33 +20,28 @@ export function showDesktopNotification(
if (options.icon) {
const isFilePath =
typeof options.icon === "string" &&
(options.icon.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(options.icon));
typeof options.icon === 'string' &&
(options.icon.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(options.icon));
if (isFilePath) {
if (fs.existsSync(options.icon)) {
notificationOptions.icon = options.icon;
} else {
logger.warn("Notification icon file not found", options.icon);
logger.warn('Notification icon file not found', options.icon);
}
} else if (
typeof options.icon === "string" &&
options.icon.startsWith("data:image/")
) {
const base64Data = options.icon.replace(/^data:image\/\w+;base64,/, "");
} else if (typeof options.icon === 'string' && options.icon.startsWith('data:image/')) {
const base64Data = options.icon.replace(/^data:image\/\w+;base64,/, '');
try {
const image = nativeImage.createFromBuffer(
Buffer.from(base64Data, "base64"),
);
const image = nativeImage.createFromBuffer(Buffer.from(base64Data, 'base64'));
if (image.isEmpty()) {
logger.warn(
"Notification icon created from base64 is empty - image format may not be supported by Electron",
'Notification icon created from base64 is empty - image format may not be supported by Electron',
);
} else {
notificationOptions.icon = image;
}
} catch (err) {
logger.error("Failed to create notification icon from base64", err);
logger.error('Failed to create notification icon from base64', err);
}
} else {
notificationOptions.icon = options.icon;

View File

@@ -1,4 +1,4 @@
import { Config } from "../../types";
import { Config } from '../../types';
export interface ConfiguredShortcuts {
toggleVisibleOverlayGlobal: string | null | undefined;
@@ -21,13 +21,9 @@ export function resolveConfiguredShortcuts(
config: Config,
defaultConfig: Config,
): ConfiguredShortcuts {
const normalizeShortcut = (
value: string | null | undefined,
): string | null | undefined => {
if (typeof value !== "string") return value;
return value
.replace(/\bKey([A-Z])\b/g, "$1")
.replace(/\bDigit([0-9])\b/g, "$1");
const normalizeShortcut = (value: string | null | undefined): string | null | undefined => {
if (typeof value !== 'string') return value;
return value.replace(/\bKey([A-Z])\b/g, '$1').replace(/\bDigit([0-9])\b/g, '$1');
};
return {
@@ -43,42 +39,34 @@ export function resolveConfiguredShortcuts(
config.shortcuts?.copySubtitle ?? defaultConfig.shortcuts?.copySubtitle,
),
copySubtitleMultiple: normalizeShortcut(
config.shortcuts?.copySubtitleMultiple ??
defaultConfig.shortcuts?.copySubtitleMultiple,
config.shortcuts?.copySubtitleMultiple ?? defaultConfig.shortcuts?.copySubtitleMultiple,
),
updateLastCardFromClipboard: normalizeShortcut(
config.shortcuts?.updateLastCardFromClipboard ??
defaultConfig.shortcuts?.updateLastCardFromClipboard,
),
triggerFieldGrouping: normalizeShortcut(
config.shortcuts?.triggerFieldGrouping ??
defaultConfig.shortcuts?.triggerFieldGrouping,
config.shortcuts?.triggerFieldGrouping ?? defaultConfig.shortcuts?.triggerFieldGrouping,
),
triggerSubsync: normalizeShortcut(
config.shortcuts?.triggerSubsync ??
defaultConfig.shortcuts?.triggerSubsync,
config.shortcuts?.triggerSubsync ?? defaultConfig.shortcuts?.triggerSubsync,
),
mineSentence: normalizeShortcut(
config.shortcuts?.mineSentence ?? defaultConfig.shortcuts?.mineSentence,
),
mineSentenceMultiple: normalizeShortcut(
config.shortcuts?.mineSentenceMultiple ??
defaultConfig.shortcuts?.mineSentenceMultiple,
config.shortcuts?.mineSentenceMultiple ?? defaultConfig.shortcuts?.mineSentenceMultiple,
),
multiCopyTimeoutMs:
config.shortcuts?.multiCopyTimeoutMs ??
defaultConfig.shortcuts?.multiCopyTimeoutMs ??
5000,
config.shortcuts?.multiCopyTimeoutMs ?? defaultConfig.shortcuts?.multiCopyTimeoutMs ?? 5000,
toggleSecondarySub: normalizeShortcut(
config.shortcuts?.toggleSecondarySub ??
defaultConfig.shortcuts?.toggleSecondarySub,
config.shortcuts?.toggleSecondarySub ?? defaultConfig.shortcuts?.toggleSecondarySub,
),
markAudioCard: normalizeShortcut(
config.shortcuts?.markAudioCard ?? defaultConfig.shortcuts?.markAudioCard,
),
openRuntimeOptions: normalizeShortcut(
config.shortcuts?.openRuntimeOptions ??
defaultConfig.shortcuts?.openRuntimeOptions,
config.shortcuts?.openRuntimeOptions ?? defaultConfig.shortcuts?.openRuntimeOptions,
),
openJimaku: normalizeShortcut(
config.shortcuts?.openJimaku ?? defaultConfig.shortcuts?.openJimaku,