Fix : try FS 1
This commit is contained in:
@@ -92,6 +92,57 @@ function probeHandshake(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Énumère ce que l'agent voit réellement de la session graphique.
|
||||||
|
*
|
||||||
|
* C'est le seul contrôle qui distingue les trois causes d'un rappel plein écran
|
||||||
|
* en échec : xdotool sans accès à l'affichage, affichage accessible mais vide
|
||||||
|
* (fenêtres Wayland natives, invisibles à xdotool), ou titre de fenêtre mal
|
||||||
|
* renseigné. Sans lui, les trois produisent le même message.
|
||||||
|
*/
|
||||||
|
async function reportVisibleWindows(display: string): Promise<void> {
|
||||||
|
const env = { ...process.env, DISPLAY: display };
|
||||||
|
let ids: string[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout } = await run('xdotool', ['search', '--onlyvisible', '--name', '.'], {
|
||||||
|
env,
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
ids = stdout.split('\n').map((entry) => entry.trim()).filter(Boolean);
|
||||||
|
} catch (err) {
|
||||||
|
const stderr = String((err as { stderr?: string }).stderr ?? '').trim();
|
||||||
|
if (stderr) {
|
||||||
|
line('warn', 'fenêtres', `xdotool en échec : ${stderr}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Code 1 sans message : l'affichage répond, il n'y a simplement rien.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ids.length === 0) {
|
||||||
|
line(
|
||||||
|
'warn',
|
||||||
|
'fenêtres',
|
||||||
|
'aucune fenêtre visible — session Wayland (fenêtres invisibles à xdotool) ' +
|
||||||
|
"ou navigateur lancé hors de cette session ?",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const titles: string[] = [];
|
||||||
|
for (const id of ids.slice(0, 12)) {
|
||||||
|
try {
|
||||||
|
const { stdout } = await run('xdotool', ['getwindowname', id], { env, timeout: 5000 });
|
||||||
|
if (stdout.trim()) titles.push(stdout.trim());
|
||||||
|
} catch {
|
||||||
|
/* titre indisponible, sans importance ici */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line('ok', 'fenêtres', `${ids.length} visible(s)`);
|
||||||
|
for (const title of titles) console.log(` · ${title}`);
|
||||||
|
}
|
||||||
|
|
||||||
async function commandExists(command: string, args: string[]): Promise<boolean> {
|
async function commandExists(command: string, args: string[]): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await run(command, args, { timeout: 8000, windowsHide: true });
|
await run(command, args, { timeout: 8000, windowsHide: true });
|
||||||
@@ -177,6 +228,7 @@ export async function runDiagnostics(config: AgentConfig): Promise<number> {
|
|||||||
if (process.env.WAYLAND_DISPLAY) {
|
if (process.env.WAYLAND_DISPLAY) {
|
||||||
line('warn', 'session', 'Wayland détecté — xdotool exige X11');
|
line('warn', 'session', 'Wayland détecté — xdotool exige X11');
|
||||||
}
|
}
|
||||||
|
if (hasXdotool && display) await reportVisibleWindows(display);
|
||||||
} else if (process.platform === 'win32') {
|
} else if (process.platform === 'win32') {
|
||||||
const hasPowershell = await commandExists('powershell.exe', ['-NoProfile', '-Command', 'exit']);
|
const hasPowershell = await commandExists('powershell.exe', ['-NoProfile', '-Command', 'exit']);
|
||||||
results.push(
|
results.push(
|
||||||
|
|||||||
@@ -60,42 +60,133 @@ export async function sendHotkey(request: HotkeyRequest): Promise<HotkeyResult>
|
|||||||
|
|
||||||
// --- X11 --------------------------------------------------------------------
|
// --- X11 --------------------------------------------------------------------
|
||||||
|
|
||||||
async function sendLinux(key: string, match: string): Promise<HotkeyResult> {
|
interface VisibleWindow {
|
||||||
const env = { ...process.env, DISPLAY: process.env.DISPLAY ?? ':0' };
|
id: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traduit un échec de xdotool en cause identifiable.
|
||||||
|
*
|
||||||
|
* xdotool sort en code 1 aussi bien quand rien ne correspond que quand il ne
|
||||||
|
* peut pas ouvrir l'affichage. Confondre les deux envoyait l'opérateur corriger
|
||||||
|
* un titre de fenêtre parfaitement valide.
|
||||||
|
*/
|
||||||
|
function displayFault(err: unknown): string | null {
|
||||||
|
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||||
|
return 'xdotool est absent — installe-le : apt install xdotool';
|
||||||
|
}
|
||||||
|
const stderr = String((err as { stderr?: string }).stderr ?? '').trim();
|
||||||
|
// « Invalid MIT-MAGIC-COOKIE-1 key » est le symptôme d'un XAUTHORITY qui pointe
|
||||||
|
// sur le mauvais fichier — le cas le plus courant sous GDM.
|
||||||
|
if (
|
||||||
|
/can't open display|no protocol specified|authorization required|bad display|magic-cookie/i.test(
|
||||||
|
stderr,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
`xdotool ne peut pas ouvrir l'affichage DISPLAY=${process.env.DISPLAY ?? ':0'} : ${stderr}. ` +
|
||||||
|
"Vérifie XAUTHORITY dans l'unité systemd — sous GDM le fichier n'est pas " +
|
||||||
|
'~/.Xauthority mais /run/user/<uid>/gdm/Xauthority.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fenêtres visibles portant un titre, avec leur identifiant. */
|
||||||
|
async function listVisibleWindows(env: NodeJS.ProcessEnv): Promise<VisibleWindow[]> {
|
||||||
let ids: string[];
|
let ids: string[];
|
||||||
try {
|
try {
|
||||||
const { stdout } = await run(
|
// « . » : toute fenêtre dont le titre contient au moins un caractère.
|
||||||
'xdotool',
|
const { stdout } = await run('xdotool', ['search', '--onlyvisible', '--name', '.'], {
|
||||||
['search', '--onlyvisible', '--name', match],
|
env,
|
||||||
{ env, timeout: 5000 },
|
timeout: 5000,
|
||||||
);
|
});
|
||||||
ids = stdout.split('\n').map((line) => line.trim()).filter(Boolean);
|
ids = stdout.split('\n').map((line) => line.trim()).filter(Boolean).slice(0, 60);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const code = (err as NodeJS.ErrnoException).code;
|
const fault = displayFault(err);
|
||||||
if (code === 'ENOENT') {
|
if (fault) throw new Error(fault);
|
||||||
throw new Error('xdotool est absent — installe-le : apt install xdotool');
|
return [];
|
||||||
}
|
}
|
||||||
// xdotool sort en code 1 quand rien ne correspond.
|
|
||||||
throw new Error(`Aucune fenêtre visible ne correspond à « ${match} »`);
|
|
||||||
}
|
|
||||||
if (ids.length === 0) throw new Error(`Aucune fenêtre visible ne correspond à « ${match} »`);
|
|
||||||
|
|
||||||
// La dernière fenêtre listée est en général la plus récemment mappée.
|
const windows = await Promise.all(
|
||||||
const windowId = ids[ids.length - 1]!;
|
ids.map(async (id) => ({ id, title: await windowName(env, id) })),
|
||||||
|
);
|
||||||
|
return windows.filter((window) => window.title !== '');
|
||||||
|
}
|
||||||
|
|
||||||
let title = windowId;
|
async function windowName(env: NodeJS.ProcessEnv, id: string): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await run('xdotool', ['getwindowname', windowId], { env, timeout: 5000 });
|
const { stdout } = await run('xdotool', ['getwindowname', id], { env, timeout: 5000 });
|
||||||
title = stdout.trim() || windowId;
|
return stdout.trim();
|
||||||
} catch {
|
} catch {
|
||||||
/* le titre n'est qu'informatif */
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Localise la fenêtre cible.
|
||||||
|
*
|
||||||
|
* Deux passes : on laisse d'abord xdotool filtrer lui-même, puis, s'il ne trouve
|
||||||
|
* rien, on énumère et on compare en JavaScript. La seconde passe ne dépend ni de
|
||||||
|
* la syntaxe d'expression régulière de xdotool ni de sa sensibilité à la casse,
|
||||||
|
* et elle fournit la liste des fenêtres réellement présentes — de loin
|
||||||
|
* l'information la plus utile quand ça échoue.
|
||||||
|
*/
|
||||||
|
async function findWindow(env: NodeJS.ProcessEnv, match: string): Promise<VisibleWindow> {
|
||||||
|
try {
|
||||||
|
const { stdout } = await run('xdotool', ['search', '--onlyvisible', '--name', match], {
|
||||||
|
env,
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
const ids = stdout.split('\n').map((line) => line.trim()).filter(Boolean);
|
||||||
|
if (ids.length > 0) {
|
||||||
|
// La dernière listée est en général la plus récemment mappée.
|
||||||
|
const id = ids[ids.length - 1]!;
|
||||||
|
return { id, title: (await windowName(env, id)) || id };
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const fault = displayFault(err);
|
||||||
|
if (fault) throw new Error(fault);
|
||||||
|
// Sinon : simple absence de correspondance, la seconde passe prend le relais.
|
||||||
}
|
}
|
||||||
|
|
||||||
await run('xdotool', ['windowactivate', '--sync', windowId], { env, timeout: 5000 });
|
const windows = await listVisibleWindows(env);
|
||||||
|
const needle = match.toLowerCase();
|
||||||
|
const hit = [...windows].reverse().find((window) => window.title.toLowerCase().includes(needle));
|
||||||
|
if (hit) return hit;
|
||||||
|
|
||||||
|
throw new Error(describeNoMatch(match, windows));
|
||||||
|
}
|
||||||
|
|
||||||
|
function describeNoMatch(match: string, windows: VisibleWindow[]): string {
|
||||||
|
const display = process.env.DISPLAY ?? ':0';
|
||||||
|
|
||||||
|
if (windows.length === 0) {
|
||||||
|
return (
|
||||||
|
`Aucune fenêtre visible sur DISPLAY=${display}. L'agent ne voit pas ta session ` +
|
||||||
|
'graphique : soit le navigateur tourne dans une autre session, soit la session est ' +
|
||||||
|
'en Wayland — une fenêtre Wayland native est invisible à xdotool. ' +
|
||||||
|
'Lance Firefox avec MOZ_ENABLE_WAYLAND=0, ou ouvre une session Xorg.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const shown = windows.slice(-12).map((window) => `« ${window.title} »`).join(', ');
|
||||||
|
const more = windows.length > 12 ? ` (+${windows.length - 12} autres)` : '';
|
||||||
|
return (
|
||||||
|
`Aucune fenêtre visible ne correspond à « ${match} ». Fenêtres ouvertes : ${shown}${more}. ` +
|
||||||
|
"Reporte un fragment de l'une d'elles dans « Titre de la fenêtre du lecteur »."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendLinux(key: string, match: string): Promise<HotkeyResult> {
|
||||||
|
const env = { ...process.env, DISPLAY: process.env.DISPLAY ?? ':0' };
|
||||||
|
const target = await findWindow(env, match);
|
||||||
|
|
||||||
|
await run('xdotool', ['windowactivate', '--sync', target.id], { env, timeout: 5000 });
|
||||||
await run('xdotool', ['key', '--clearmodifiers', key], { env, timeout: 5000 });
|
await run('xdotool', ['key', '--clearmodifiers', key], { env, timeout: 5000 });
|
||||||
|
|
||||||
return { method: 'xdotool', window: title };
|
return { method: 'xdotool', window: target.title };
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Windows ----------------------------------------------------------------
|
// --- Windows ----------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user