From 97dc9f97e9f1668bd0c65c59531633e53f8088ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20Sepp=C3=A4nen?= Date: Mon, 9 Nov 2026 00:54:19 +0200 Subject: [PATCH 1/2] fix(server): boot-started linux servers offer and open the file manager --- .../src/process/externalLauncher.test.ts | 63 +++++++++++++++++++ apps/server/src/process/externalLauncher.ts | 13 +++- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/apps/server/src/process/externalLauncher.test.ts b/apps/server/src/process/externalLauncher.test.ts index 9f975ee209e3d..e41817ca1f01e 100644 --- a/apps/server/src/process/externalLauncher.test.ts +++ b/apps/server/src/process/externalLauncher.test.ts @@ -214,6 +214,69 @@ it.effect.skipIf(windowsHost)( }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), ); +// Windows drive letters cannot resolve the POSIX xdg stubs. +it.effect.skipIf(windowsHost)( + "a Linux server without a display offers and opens the file manager on the desktop session's display", + () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" }); + for (const name of ["xdg-open", "xdg-mime"]) { + const filePath = path.join(binDir, name); + yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n"); + yield* fileSystem.chmod(filePath, 0o755); + } + + const fileManager = (sessionEnv: ReadonlyArray) => + Effect.gen(function* () { + const spawned: ChildProcess.StandardCommand[] = []; + const result = yield* Effect.gen(function* () { + const launcher = yield* ExternalLauncher.ExternalLauncher; + const editors = yield* launcher.resolveAvailableEditors(); + const kind = yield* launcher.resolveFileManagerRevealKind(); + const opened = yield* launcher + .launchEditor({ editor: "file-manager", cwd: "/workspace" }) + .pipe(Effect.exit); + return { offered: editors.includes("file-manager"), kind, opened }; + }).pipe( + Effect.provide( + layerTest({ + platform: "linux", + env: { PATH: binDir }, + onSpawn: (command) => spawned.push(command), + spawnResult: (command) => + command.command === "busctl" + ? { stdout: JSON.stringify({ type: "as", data: sessionEnv }) } + : command.command === "xdg-mime" + ? { stdout: "org.kde.dolphin.desktop\n" } + : undefined, + }), + ), + Effect.provideService(HostProcess.Environment, { PATH: binDir }), + ); + const open = spawned.find((command) => command.command === "xdg-open"); + return { ...result, open: open && { args: open.args, env: open.options.env } }; + }); + + const session = yield* fileManager(["DISPLAY=:0", "HOME=/home/user"]); + assert.equal(session.offered, true); + assert.equal(session.kind, "files"); + assert.equal(Exit.isSuccess(session.opened), true); + assert.deepEqual(session.open, { + args: ["/workspace"], + env: { PATH: binDir, DISPLAY: ":0" }, + }); + + // No one is logged in to a desktop, so there is no screen to open it on. + const headless = yield* fileManager(["HOME=/home/user"]); + assert.equal(headless.offered, false); + assert.equal(headless.kind, undefined); + assert.equal(Exit.isFailure(headless.opened), true); + assert.equal(headless.open, undefined); + }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), +); + it.effect("launches an installed editor with platform-safe arguments", () => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; diff --git a/apps/server/src/process/externalLauncher.ts b/apps/server/src/process/externalLauncher.ts index 7f4417d1fb67a..beb935f987539 100644 --- a/apps/server/src/process/externalLauncher.ts +++ b/apps/server/src/process/externalLauncher.ts @@ -429,16 +429,23 @@ const resolveBrowserLaunch = Effect.fn("externalLauncher.resolveBrowserLaunch")( return buildBrowserLaunch(target, platform, env); }); +// Editors and the file manager open on the desktop session, so a boot-started +// server discovers and launches them with that session's display. +const readEditorEnv = Effect.fn("externalLauncher.readEditorEnv")(function* () { + const env = { ...(yield* readBrowserLaunchEnv), ...(yield* readCommandLookupEnv) }; + return hasGraphicalLinuxSession(env) ? env : { ...env, ...(yield* readGraphicalSessionEnv()) }; +}); + const resolveAvailableEditors = Effect.fn("externalLauncher.resolveAvailableEditors")(function* () { const platform = yield* HostProcess.Platform; - const env = { ...(yield* readBrowserLaunchEnv), ...(yield* readCommandLookupEnv) }; + const env = yield* readEditorEnv(); return yield* buildAvailableEditors(platform, env).pipe(withPathDirectoryListings); }); const resolveFileManagerRevealKind = Effect.fn("externalLauncher.resolveFileManagerRevealKind")( function* () { const platform = yield* HostProcess.Platform; - const env = { ...(yield* readBrowserLaunchEnv), ...(yield* readCommandLookupEnv) }; + const env = yield* readEditorEnv(); return yield* fileManagerRevealKindForPlatform(platform, env); }, ); @@ -464,7 +471,7 @@ const resolveEditorLaunch = Effect.fn("resolveEditorLaunch")(function* ( FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner > { const platform = yield* HostProcess.Platform; - const env = { ...(yield* readBrowserLaunchEnv), ...(yield* readCommandLookupEnv) }; + const env = yield* readEditorEnv(); yield* Effect.annotateCurrentSpan({ "externalLauncher.editor": input.editor, "externalLauncher.cwd": input.cwd, From 55928dc01d24b5dfedd7b8f4de914090a4971ea9 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Wed, 11 Nov 2026 22:58:01 +0000 Subject: [PATCH 2/2] test(server): give zed daemon regression a graphical session --- apps/server/src/process/externalLauncher.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/process/externalLauncher.test.ts b/apps/server/src/process/externalLauncher.test.ts index 1b0365e6cc806..7f026b35fb2bb 100644 --- a/apps/server/src/process/externalLauncher.test.ts +++ b/apps/server/src/process/externalLauncher.test.ts @@ -664,7 +664,7 @@ it.effect.skipIf(windowsHost)("does not open Zed with the OpenZFS zed daemon on Effect.provide( layerTest({ platform: "linux", - env: { HOME: home, PATH: binDir }, + env: { HOME: home, PATH: binDir, DISPLAY: ":0" }, onSpawn: (command) => spawned.push(command), spawnResult: (command) => (command.args[0] === "--version" ? { exitCode: 1 } : undefined), }),