fix : opening link 1
This commit is contained in:
21
README.md
21
README.md
@@ -351,6 +351,27 @@ L'agent évite donc les deux :
|
||||
Les agents configurés avant ce correctif sont migrés au démarrage du serveur, sauf si leurs
|
||||
arguments ou leur comportement d'arrêt ont été personnalisés.
|
||||
|
||||
### « Firefox est déjà ouvert »
|
||||
|
||||
Ce dialogue signifie que le processus lancé par l'agent n'a pas trouvé l'instance déjà en
|
||||
cours : il bute alors sur le verrou de profil au lieu de lui confier l'URL.
|
||||
|
||||
Le passage de relais se fait par le **bus de session D-Bus** — le seul mécanisme disponible
|
||||
sous Wayland, le protocole X de remoting n'y existant pas. Or un service systemd « system »
|
||||
n'hérite pas de ce bus, pas plus qu'il n'hérite du cookie X. L'agent résout donc lui-même,
|
||||
à chaque lancement :
|
||||
|
||||
| Variable | Origine |
|
||||
| --- | --- |
|
||||
| `DISPLAY` | valeur héritée, sinon la socket X présente dans `/tmp/.X11-unix` |
|
||||
| `XAUTHORITY` | `$XAUTHORITY` s'il existe, puis GDM, Xwayland, `~/.Xauthority` |
|
||||
| `XDG_RUNTIME_DIR` | valeur héritée, sinon `/run/user/<uid>` |
|
||||
| `DBUS_SESSION_BUS_ADDRESS` | valeur héritée, sinon la socket `$XDG_RUNTIME_DIR/bus` |
|
||||
|
||||
`agent.cjs --check` affiche les quatre. Si le bus ressort en avertissement, vérifie que
|
||||
l'agent tourne bien sous **le même compte** que la session graphique : l'utilisateur est
|
||||
choisi par `--user` à l'installation.
|
||||
|
||||
### Éviter l'accumulation d'onglets
|
||||
|
||||
Avec les réglages d'usine de Firefox, un lien venu de l'extérieur ouvre un **nouvel
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { execFile, spawn } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
import type { AgentEvent, BrowserSettings } from '@stream-control/shared';
|
||||
import { x11Env } from './x11.ts';
|
||||
import { sessionEnv } from './x11.ts';
|
||||
|
||||
const run = promisify(execFile);
|
||||
|
||||
@@ -9,6 +9,22 @@ export interface BrowserLog {
|
||||
(level: 'info' | 'warn' | 'error', message: string, event?: AgentEvent): void;
|
||||
}
|
||||
|
||||
export interface LaunchOptions {
|
||||
/**
|
||||
* Forcer XWayland. Une fenêtre Wayland native est invisible à xdotool, donc
|
||||
* impossible à basculer en plein écran par une touche. On ne l'impose que
|
||||
* lorsque ce rappel est réellement demandé : sous Wayland, XWayland ajoute une
|
||||
* copie d'image dont une capture par rognage OBS n'a que faire.
|
||||
*/
|
||||
forceXWayland?: boolean;
|
||||
}
|
||||
|
||||
function launchEnv(options: LaunchOptions): NodeJS.ProcessEnv {
|
||||
const env = sessionEnv();
|
||||
if (options.forceXWayland) env.MOZ_ENABLE_WAYLAND = '0';
|
||||
return env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seules les URL http(s) sont acceptées. Elles finissent en argument de
|
||||
* processus — jamais dans un shell, donc pas d'injection possible — mais un
|
||||
@@ -39,6 +55,7 @@ export async function openUrl(
|
||||
settings: BrowserSettings,
|
||||
url: string,
|
||||
log: BrowserLog,
|
||||
options: LaunchOptions = {},
|
||||
): Promise<{ command: string; url: string }> {
|
||||
const target = assertWebUrl(url);
|
||||
const args = [...settings.args, target];
|
||||
@@ -48,12 +65,7 @@ export async function openUrl(
|
||||
const child = spawn(settings.command, args, {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
// Sous une session Wayland, une fenêtre Firefox native est invisible à
|
||||
// xdotool : ni activation, ni touche, donc pas de plein écran. XWayland la
|
||||
// rend pilotable, sans rien changer à la lecture vidéo. Sans effet sur une
|
||||
// session X11, et sans effet non plus si une instance Firefox tourne déjà —
|
||||
// elle récupérerait l'URL avec ses propres variables d'environnement.
|
||||
env: { ...x11Env(), MOZ_ENABLE_WAYLAND: '0' },
|
||||
env: launchEnv(options),
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -85,6 +97,7 @@ export async function openUrl(
|
||||
export async function blankPage(
|
||||
settings: BrowserSettings,
|
||||
log: BrowserLog,
|
||||
options: LaunchOptions = {},
|
||||
): Promise<{ command: string; url: string }> {
|
||||
const args = [...settings.args, 'about:blank'];
|
||||
log('info', 'Lecteur déchargé (page vide) — fenêtre conservée pour OBS', 'browser.closed');
|
||||
@@ -92,7 +105,7 @@ export async function blankPage(
|
||||
const child = spawn(settings.command, args, {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
env: { ...x11Env(), MOZ_ENABLE_WAYLAND: '0' },
|
||||
env: launchEnv(options),
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -118,7 +131,7 @@ export async function closeWindow(
|
||||
throw new Error(`Fermeture de fenêtre non gérée sur ${process.platform}`);
|
||||
}
|
||||
|
||||
const env = x11Env();
|
||||
const env = sessionEnv();
|
||||
|
||||
let ids: string[] = [];
|
||||
try {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { promisify } from 'node:util';
|
||||
import { WebSocket } from 'ws';
|
||||
import { CONFIG_PATH, type AgentConfig } from './config.ts';
|
||||
import { looksLikeWayland } from './hotkey.ts';
|
||||
import { resolveDisplay, resolveXauthority, x11Env } from './x11.ts';
|
||||
import { resolveDisplay, resolveSessionBus, resolveXauthority, sessionEnv } from './x11.ts';
|
||||
|
||||
const run = promisify(execFile);
|
||||
|
||||
@@ -103,7 +103,7 @@ function probeHandshake(
|
||||
* renseigné. Sans lui, les trois produisent le même message.
|
||||
*/
|
||||
async function reportVisibleWindows(display: string): Promise<void> {
|
||||
const env = { ...x11Env(), DISPLAY: display };
|
||||
const env = { ...sessionEnv(), DISPLAY: display };
|
||||
let ids: string[] = [];
|
||||
|
||||
try {
|
||||
@@ -253,6 +253,19 @@ export async function runDiagnostics(config: AgentConfig): Promise<number> {
|
||||
),
|
||||
);
|
||||
|
||||
// Sans le bus de session, Firefox ne trouve pas l'instance déjà lancée et
|
||||
// affiche « Firefox est déjà ouvert » au lieu d'ouvrir la page.
|
||||
const bus = resolveSessionBus();
|
||||
results.push(
|
||||
bus
|
||||
? line('ok', 'bus session', bus)
|
||||
: line(
|
||||
'warn',
|
||||
'bus session',
|
||||
"introuvable — Firefox affichera « déjà ouvert » au lieu d'ouvrir la page",
|
||||
),
|
||||
);
|
||||
|
||||
if (process.env.WAYLAND_DISPLAY) {
|
||||
line('warn', 'session', 'Wayland détecté — xdotool exige X11');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { execFile } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
import { resolveXauthority, x11Env } from './x11.ts';
|
||||
import { resolveXauthority, sessionEnv } from './x11.ts';
|
||||
|
||||
const run = promisify(execFile);
|
||||
|
||||
@@ -182,7 +182,7 @@ async function findWindow(env: NodeJS.ProcessEnv, match: string): Promise<Visibl
|
||||
}
|
||||
|
||||
function describeNoMatch(match: string, windows: VisibleWindow[]): string {
|
||||
const display = x11Env().DISPLAY;
|
||||
const display = sessionEnv().DISPLAY;
|
||||
|
||||
if (windows.length === 0) {
|
||||
return (
|
||||
@@ -213,7 +213,7 @@ function describeNoMatch(match: string, windows: VisibleWindow[]): string {
|
||||
}
|
||||
|
||||
async function sendLinux(key: string, match: string): Promise<HotkeyResult> {
|
||||
const env = x11Env();
|
||||
const env = sessionEnv();
|
||||
const target = await findWindow(env, match);
|
||||
|
||||
await run('xdotool', ['windowactivate', '--sync', target.id], { env, timeout: 5000 });
|
||||
|
||||
@@ -238,7 +238,9 @@ async function startCapture(params: Record<string, unknown>): Promise<unknown> {
|
||||
}
|
||||
|
||||
const url = requireUrl(params);
|
||||
const opened = await openUrl(browserSettings, url, report);
|
||||
const opened = await openUrl(browserSettings, url, report, {
|
||||
forceXWayland: watcher.snapshotSettings.fullscreen.enabled,
|
||||
});
|
||||
|
||||
const wait = Number(params.readyDelayMs ?? browserSettings.readyDelayMs);
|
||||
report('info', `Attente de ${Math.round(wait / 1000)} s avant le plein écran`);
|
||||
@@ -275,7 +277,9 @@ async function stopCapture(): Promise<unknown> {
|
||||
const action =
|
||||
browserSettings.onStop === 'close'
|
||||
? closeWindow(watcher.snapshotSettings.fullscreen.windowMatch, report)
|
||||
: blankPage(browserSettings, report);
|
||||
: blankPage(browserSettings, report, {
|
||||
forceXWayland: watcher.snapshotSettings.fullscreen.enabled,
|
||||
});
|
||||
closed = await action.catch((err: Error) => {
|
||||
report('warn', `Libération de la fenêtre impossible : ${err.message}`, 'command.failed');
|
||||
return `échec : ${err.message}`;
|
||||
@@ -297,7 +301,9 @@ async function runAction(action: AgentAction, params: Record<string, unknown>):
|
||||
case 'hotkey.fullscreen':
|
||||
return watcher.restoreFullscreen();
|
||||
case 'browser.open':
|
||||
return openUrl(browserSettings, requireUrl(params), report);
|
||||
return openUrl(browserSettings, requireUrl(params), report, {
|
||||
forceXWayland: watcher.snapshotSettings.fullscreen.enabled,
|
||||
});
|
||||
case 'browser.close':
|
||||
return closeWindow(watcher.snapshotSettings.fullscreen.windowMatch, report);
|
||||
case 'capture.start':
|
||||
|
||||
@@ -80,10 +80,47 @@ export function resolveDisplay(): string {
|
||||
return ':0';
|
||||
}
|
||||
|
||||
/** Environnement d'exécution des outils graphiques (xdotool, navigateur). */
|
||||
export function x11Env(): NodeJS.ProcessEnv {
|
||||
/**
|
||||
* Bus de session de l'utilisateur.
|
||||
*
|
||||
* Firefox s'en sert pour confier une URL à l'instance déjà lancée. Sans lui, le
|
||||
* nouveau processus ne la trouve pas, bute sur le verrou de profil et affiche
|
||||
* « Firefox est déjà ouvert » au lieu d'ouvrir la page — c'est le mécanisme
|
||||
* retenu sous Wayland, où le protocole X de remoting n'existe pas.
|
||||
*/
|
||||
export function resolveSessionBus(): string | null {
|
||||
if (process.env.DBUS_SESSION_BUS_ADDRESS) return process.env.DBUS_SESSION_BUS_ADDRESS;
|
||||
|
||||
const runtime = runtimeDir();
|
||||
if (!runtime) return null;
|
||||
|
||||
const socket = path.join(runtime, 'bus');
|
||||
try {
|
||||
if (fs.statSync(socket).isSocket()) return `unix:path=${socket}`;
|
||||
} catch {
|
||||
/* pas de bus de session accessible */
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Environnement d'exécution des outils de session (xdotool, navigateur).
|
||||
*
|
||||
* Un service systemd « system » n'hérite de rien de la session graphique : ni
|
||||
* écran, ni cookie d'autorisation, ni bus. Les trois se résolvent ici, à chaque
|
||||
* appel, pour survivre à un changement de session sans réinstallation.
|
||||
*/
|
||||
export function sessionEnv(): NodeJS.ProcessEnv {
|
||||
const env: NodeJS.ProcessEnv = { ...process.env, DISPLAY: resolveDisplay() };
|
||||
|
||||
const auth = resolveXauthority();
|
||||
if (auth) env.XAUTHORITY = auth;
|
||||
|
||||
const runtime = runtimeDir();
|
||||
if (runtime) env.XDG_RUNTIME_DIR = runtime;
|
||||
|
||||
const bus = resolveSessionBus();
|
||||
if (bus) env.DBUS_SESSION_BUS_ADDRESS = bus;
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user