mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 00:55:16 -07:00
Launch macOS app background-detached when no args passed
- Add `launchAppBackgroundDetached` that spawns with `--start --background` and `SUBMINER_BACKGROUND_CHILD=1` - On darwin with empty appArgs, use detached background launch instead of inherited process - Add `extraEnv` param to `launchAppCommandDetached` for env injection - Inject deps into `runAppPassthroughCommand` for testability - Bump vendor/subminer-yomitan submodule
This commit is contained in:
@@ -1,19 +1,45 @@
|
|||||||
import { launchTexthookerOnly, runAppCommandWithInherit } from '../mpv.js';
|
import {
|
||||||
|
launchAppBackgroundDetached,
|
||||||
|
launchTexthookerOnly,
|
||||||
|
runAppCommandWithInherit,
|
||||||
|
} from '../mpv.js';
|
||||||
import type { LauncherCommandContext } from './context.js';
|
import type { LauncherCommandContext } from './context.js';
|
||||||
|
|
||||||
export function runAppPassthroughCommand(context: LauncherCommandContext): boolean {
|
type AppCommandDeps = {
|
||||||
|
platform: () => NodeJS.Platform;
|
||||||
|
runAppCommandWithInherit: (appPath: string, appArgs: string[]) => void;
|
||||||
|
launchAppBackgroundDetached: (
|
||||||
|
appPath: string,
|
||||||
|
logLevel: LauncherCommandContext['args']['logLevel'],
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultAppCommandDeps: AppCommandDeps = {
|
||||||
|
platform: () => process.platform,
|
||||||
|
runAppCommandWithInherit,
|
||||||
|
launchAppBackgroundDetached,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function runAppPassthroughCommand(
|
||||||
|
context: LauncherCommandContext,
|
||||||
|
deps: AppCommandDeps = defaultAppCommandDeps,
|
||||||
|
): boolean {
|
||||||
const { args, appPath } = context;
|
const { args, appPath } = context;
|
||||||
if (!appPath) {
|
if (!appPath) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (args.settings) {
|
if (args.settings) {
|
||||||
runAppCommandWithInherit(appPath, ['--settings']);
|
deps.runAppCommandWithInherit(appPath, ['--settings']);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!args.appPassthrough) {
|
if (!args.appPassthrough) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
runAppCommandWithInherit(appPath, args.appArgs);
|
if (deps.platform() === 'darwin' && args.appArgs.length === 0) {
|
||||||
|
deps.launchAppBackgroundDetached(appPath, args.logLevel);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
deps.runAppCommandWithInherit(appPath, args.appArgs);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { runConfigCommand } from './config-command.js';
|
|||||||
import { runDictionaryCommand } from './dictionary-command.js';
|
import { runDictionaryCommand } from './dictionary-command.js';
|
||||||
import { runDoctorCommand } from './doctor-command.js';
|
import { runDoctorCommand } from './doctor-command.js';
|
||||||
import { runMpvPreAppCommand } from './mpv-command.js';
|
import { runMpvPreAppCommand } from './mpv-command.js';
|
||||||
|
import { runAppPassthroughCommand } from './app-command.js';
|
||||||
import { runStatsCommand } from './stats-command.js';
|
import { runStatsCommand } from './stats-command.js';
|
||||||
import { runUpdateCommand } from './update-command.js';
|
import { runUpdateCommand } from './update-command.js';
|
||||||
|
|
||||||
@@ -168,6 +169,48 @@ test('doctor command forwards refresh-known-words to app binary', () => {
|
|||||||
assert.deepEqual(forwarded, [['--refresh-known-words']]);
|
assert.deepEqual(forwarded, [['--refresh-known-words']]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('app command starts default macOS background app detached from launcher', () => {
|
||||||
|
const context = createContext();
|
||||||
|
context.args.appPassthrough = true;
|
||||||
|
context.args.appArgs = [];
|
||||||
|
const calls: string[] = [];
|
||||||
|
|
||||||
|
const handled = runAppPassthroughCommand(context, {
|
||||||
|
platform: () => 'darwin',
|
||||||
|
runAppCommandWithInherit: () => {
|
||||||
|
calls.push('attached');
|
||||||
|
},
|
||||||
|
launchAppBackgroundDetached: (appPath, logLevel) => {
|
||||||
|
calls.push(`detached:${appPath}:${logLevel}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(handled, true);
|
||||||
|
assert.deepEqual(calls, ['detached:/tmp/subminer.app:info']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('app command keeps explicit passthrough args attached', () => {
|
||||||
|
const context = createContext();
|
||||||
|
context.args.appPassthrough = true;
|
||||||
|
context.args.appArgs = ['--settings'];
|
||||||
|
const forwarded: string[][] = [];
|
||||||
|
const detached: string[] = [];
|
||||||
|
|
||||||
|
const handled = runAppPassthroughCommand(context, {
|
||||||
|
platform: () => 'darwin',
|
||||||
|
runAppCommandWithInherit: (_appPath, appArgs) => {
|
||||||
|
forwarded.push(appArgs);
|
||||||
|
},
|
||||||
|
launchAppBackgroundDetached: () => {
|
||||||
|
detached.push('detached');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(handled, true);
|
||||||
|
assert.deepEqual(forwarded, [['--settings']]);
|
||||||
|
assert.deepEqual(detached, []);
|
||||||
|
});
|
||||||
|
|
||||||
test('mpv pre-app command exits non-zero when socket is not ready', async () => {
|
test('mpv pre-app command exits non-zero when socket is not ready', async () => {
|
||||||
const context = createContext();
|
const context = createContext();
|
||||||
context.args.mpvStatus = true;
|
context.args.mpvStatus = true;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
buildMpvEnv,
|
buildMpvEnv,
|
||||||
cleanupPlaybackSession,
|
cleanupPlaybackSession,
|
||||||
detectBackend,
|
detectBackend,
|
||||||
|
launchAppBackgroundDetached,
|
||||||
findAppBinary,
|
findAppBinary,
|
||||||
launchAppCommandDetached,
|
launchAppCommandDetached,
|
||||||
launchTexthookerOnly,
|
launchTexthookerOnly,
|
||||||
@@ -425,6 +426,34 @@ test('launchAppCommandDetached handles child process spawn errors', async () =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('launchAppBackgroundDetached starts background child directly', async () => {
|
||||||
|
const { dir } = createTempSocketPath();
|
||||||
|
const appPath = path.join(dir, 'fake-subminer.sh');
|
||||||
|
const argsPath = path.join(dir, 'args.txt');
|
||||||
|
const envPath = path.join(dir, 'env.txt');
|
||||||
|
fs.writeFileSync(
|
||||||
|
appPath,
|
||||||
|
[
|
||||||
|
'#!/bin/sh',
|
||||||
|
`printf '%s\\n' "$@" > ${JSON.stringify(argsPath)}`,
|
||||||
|
`printf '%s\\n' "$SUBMINER_BACKGROUND_CHILD" > ${JSON.stringify(envPath)}`,
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
|
);
|
||||||
|
fs.chmodSync(appPath, 0o755);
|
||||||
|
|
||||||
|
launchAppBackgroundDetached(appPath, 'info');
|
||||||
|
|
||||||
|
const deadline = Date.now() + 1000;
|
||||||
|
while ((!fs.existsSync(argsPath) || !fs.existsSync(envPath)) && Date.now() < deadline) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(fs.readFileSync(argsPath, 'utf8').trim(), '--start\n--background');
|
||||||
|
assert.equal(fs.readFileSync(envPath, 'utf8').trim(), '1');
|
||||||
|
fs.rmSync(dir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
test('stopOverlay logs a warning when stop command cannot be spawned', () => {
|
test('stopOverlay logs a warning when stop command cannot be spawned', () => {
|
||||||
const originalWrite = process.stdout.write;
|
const originalWrite = process.stdout.write;
|
||||||
const writes: string[] = [];
|
const writes: string[] = [];
|
||||||
|
|||||||
+11
-1
@@ -57,6 +57,7 @@ const OVERLAY_START_SOCKET_READY_TIMEOUT_MS = 900;
|
|||||||
const OVERLAY_START_COMMAND_SETTLE_TIMEOUT_MS = 700;
|
const OVERLAY_START_COMMAND_SETTLE_TIMEOUT_MS = 700;
|
||||||
const TRANSPORTED_APP_ARGC_ENV = 'SUBMINER_APP_ARGC';
|
const TRANSPORTED_APP_ARGC_ENV = 'SUBMINER_APP_ARGC';
|
||||||
const TRANSPORTED_APP_ARG_PREFIX = 'SUBMINER_APP_ARG_';
|
const TRANSPORTED_APP_ARG_PREFIX = 'SUBMINER_APP_ARG_';
|
||||||
|
const BACKGROUND_CHILD_ENV = 'SUBMINER_BACKGROUND_CHILD';
|
||||||
|
|
||||||
export interface LauncherRuntimePluginPlan {
|
export interface LauncherRuntimePluginPlan {
|
||||||
scriptPath: string | null;
|
scriptPath: string | null;
|
||||||
@@ -1589,11 +1590,20 @@ export function launchAppStartDetached(appPath: string, logLevel: LogLevel): voi
|
|||||||
launchAppCommandDetached(appPath, startArgs, logLevel, 'start');
|
launchAppCommandDetached(appPath, startArgs, logLevel, 'start');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function launchAppBackgroundDetached(appPath: string, logLevel: LogLevel): void {
|
||||||
|
const startArgs = ['--start', '--background'];
|
||||||
|
if (logLevel !== 'info') startArgs.push('--log-level', logLevel);
|
||||||
|
launchAppCommandDetached(appPath, startArgs, logLevel, 'app', {
|
||||||
|
[BACKGROUND_CHILD_ENV]: '1',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function launchAppCommandDetached(
|
export function launchAppCommandDetached(
|
||||||
appPath: string,
|
appPath: string,
|
||||||
appArgs: string[],
|
appArgs: string[],
|
||||||
logLevel: LogLevel,
|
logLevel: LogLevel,
|
||||||
label: string,
|
label: string,
|
||||||
|
extraEnv: NodeJS.ProcessEnv = {},
|
||||||
): void {
|
): void {
|
||||||
if (maybeCaptureAppArgs(appArgs)) {
|
if (maybeCaptureAppArgs(appArgs)) {
|
||||||
return;
|
return;
|
||||||
@@ -1612,7 +1622,7 @@ export function launchAppCommandDetached(
|
|||||||
const proc = spawn(target.command, target.args, {
|
const proc = spawn(target.command, target.args, {
|
||||||
stdio: ['ignore', stdoutFd, stderrFd],
|
stdio: ['ignore', stdoutFd, stderrFd],
|
||||||
detached: true,
|
detached: true,
|
||||||
env: buildAppEnv(process.env, target.env),
|
env: buildAppEnv(process.env, { ...target.env, ...extraEnv }),
|
||||||
});
|
});
|
||||||
proc.once('error', (error) => {
|
proc.once('error', (error) => {
|
||||||
log('warn', logLevel, `${label}: failed to launch detached app: ${error.message}`);
|
log('warn', logLevel, `${label}: failed to launch detached app: ${error.message}`);
|
||||||
|
|||||||
Vendored
+1
-1
Submodule vendor/subminer-yomitan updated: ed31b7a3ee...58d970d302
Reference in New Issue
Block a user