Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ Running an AppImage normally requires FUSE and, on some distributions, the
`libfuse2` compatibility package. If FUSE is unavailable, launch it with
`--appimage-extract-and-run` instead.

Linux users whose window manager supplies all window controls can remove the
native Electron title bar with `--no-window-frame`:

```bash
./bb-x86_64.AppImage --no-window-frame
```

The native frame remains the default. Changing this startup option requires a
full desktop app restart.

CI builds Linux artifacts on the pinned `ubuntu-22.04` runner. The AppImage
links against the build machine's glibc, so that pin sets the oldest
distribution that can run a published build. Raise it deliberately.
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/desktop-window-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ interface CreateDesktopWindowFactoryArgs {
displayWorkAreas: DisplayWorkArea[] | null;
icon: DesktopWindowIcon;
isMac: boolean;
isLinuxFrameless: boolean;
isQuitting(): boolean;
openExternalUrl(args: OpenExternalUrlArgs): void;
preloadPath: string;
Expand Down Expand Up @@ -138,6 +139,7 @@ interface CreateWindowOptionsArgs {
bounds: WindowBounds;
icon: DesktopWindowIcon;
isMac: boolean;
isLinuxFrameless: boolean;
preloadPath: string;
}

Expand Down Expand Up @@ -168,6 +170,7 @@ function createWindowOptions(
args: CreateWindowOptionsArgs,
): BrowserWindowConstructorOptions {
return {
...(args.isLinuxFrameless ? { frame: false } : {}),
...(args.isMac
? {
frame: false,
Expand Down Expand Up @@ -238,6 +241,7 @@ export function createDesktopWindowFactory(
bounds: restoredState.bounds,
icon: args.icon,
isMac: args.isMac,
isLinuxFrameless: args.isLinuxFrameless,
preloadPath: args.preloadPath,
}),
);
Expand Down
15 changes: 15 additions & 0 deletions apps/desktop/src/desktop-window-frame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const LINUX_FRAMELESS_WINDOW_ARGUMENT = "--no-window-frame";

interface ShouldUseLinuxFramelessWindowArgs {
argv: readonly string[];
platform: NodeJS.Platform;
}

export function shouldUseLinuxFramelessWindow(
args: ShouldUseLinuxFramelessWindowArgs,
): boolean {
return (
args.platform === "linux" &&
args.argv.includes(LINUX_FRAMELESS_WINDOW_ARGUMENT)
);
}
5 changes: 5 additions & 0 deletions apps/desktop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ import {
type DesktopBrowserWindowCreator,
type DesktopWindowFactory,
} from "./desktop-window-factory.js";
import { shouldUseLinuxFramelessWindow } from "./desktop-window-frame.js";
import {
createDesktopAboutDialogOptions,
createDesktopAboutPanelOptions,
Expand Down Expand Up @@ -2371,6 +2372,10 @@ async function runDesktopApp(): Promise<void> {
displayWorkAreas: null,
icon: nativeImage.createFromPath(iconPath),
isMac: process.platform === "darwin",
isLinuxFrameless: shouldUseLinuxFramelessWindow({
argv: process.argv,
platform: process.platform,
}),
isQuitting() {
return quitting;
},
Expand Down
50 changes: 50 additions & 0 deletions apps/desktop/test/desktop-window-factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ describe("desktop window factory", () => {
],
icon: undefined,
isMac: true,
isLinuxFrameless: false,
isQuitting() {
return false;
},
Expand Down Expand Up @@ -335,6 +336,7 @@ describe("desktop window factory", () => {
],
icon: undefined,
isMac: true,
isLinuxFrameless: false,
isQuitting() {
return false;
},
Expand Down Expand Up @@ -393,6 +395,7 @@ describe("desktop window factory", () => {
],
icon: undefined,
isMac: true,
isLinuxFrameless: false,
isQuitting() {
return false;
},
Expand Down Expand Up @@ -448,6 +451,7 @@ describe("desktop window factory", () => {
],
icon: undefined,
isMac: true,
isLinuxFrameless: false,
isQuitting() {
return false;
},
Expand Down Expand Up @@ -505,6 +509,7 @@ describe("desktop window factory", () => {
],
icon: undefined,
isMac: true,
isLinuxFrameless: false,
isQuitting() {
return false;
},
Expand Down Expand Up @@ -565,6 +570,7 @@ describe("desktop window factory", () => {
],
icon: undefined,
isMac: true,
isLinuxFrameless: false,
isQuitting() {
return false;
},
Expand Down Expand Up @@ -623,6 +629,7 @@ describe("desktop window factory", () => {
],
icon: undefined,
isMac: false,
isLinuxFrameless: false,
isQuitting() {
return false;
},
Expand All @@ -639,4 +646,47 @@ describe("desktop window factory", () => {
"trafficLightPosition",
);
});

it("removes the native window frame when requested on Linux", async () => {
const tempDir = await createTempDir();
const createdWindows: FakeDesktopWindow[] = [];
const browserWindowCreator: DesktopBrowserWindowCreator = {
create(options) {
const browserWindow = new FakeDesktopWindow({ options });
createdWindows.push(browserWindow);
return browserWindow;
},
};
const factory = createDesktopWindowFactory({
browserWindowCreator,
createWindowStateKey() {
return "frameless-linux-window";
},
displayWorkAreas: [
{
height: 900,
width: 1440,
x: 0,
y: 0,
},
],
icon: undefined,
isMac: false,
isLinuxFrameless: true,
isQuitting() {
return false;
},
openExternalUrl() {},
preloadPath: "/tmp/preload.cjs",
userDataPath: tempDir.path,
});

await factory.createWindow({ initialUrl: null, stateKey: null });

expect(createdWindows[0]?.options.frame).toBe(false);
expect(createdWindows[0]?.options).not.toHaveProperty("titleBarStyle");
expect(createdWindows[0]?.options).not.toHaveProperty(
"trafficLightPosition",
);
});
});
36 changes: 36 additions & 0 deletions apps/desktop/test/desktop-window-frame.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, expect, it } from "vitest";
import {
LINUX_FRAMELESS_WINDOW_ARGUMENT,
shouldUseLinuxFramelessWindow,
} from "../src/desktop-window-frame.js";

describe("desktop window frame", () => {
it("enables frameless windows on Linux when requested", () => {
expect(
shouldUseLinuxFramelessWindow({
argv: ["bb-nightly", LINUX_FRAMELESS_WINDOW_ARGUMENT],
platform: "linux",
}),
).toBe(true);
});

it("keeps the native Linux frame by default", () => {
expect(
shouldUseLinuxFramelessWindow({
argv: ["bb-nightly"],
platform: "linux",
}),
).toBe(false);
});

it("ignores the Linux-only option on macOS and Windows", () => {
for (const platform of ["darwin", "win32"] as const) {
expect(
shouldUseLinuxFramelessWindow({
argv: ["bb", LINUX_FRAMELESS_WINDOW_ARGUMENT],
platform,
}),
).toBe(false);
}
});
});