fix(linux): paint a hairline edge on the frameless settings window

Linux compositors draw no shadow for undecorated windows (Wayland has
no server-side decorations at all), so the frameless settings window is
indistinguishable from the desktop there — #595's follow-up after the
Windows DWM-shadow fix. Paint a 1px --border-window inset ring around
the window root: the webview draws the same hairline on every
windowing system.
This commit is contained in:
arookieofc
2026-11-02 20:50:29 +08:00
parent 795f8e85f1
commit 58ff3e93d0
3 changed files with 29 additions and 2 deletions
@@ -22,6 +22,7 @@ vi.mock("@/platform/ipc", async (importOriginal) => {
});
import { registerAction, executeAction } from "@/platform/actions";
import { usePlatform } from "@/platform/platformInfo";
import { useProject } from "@/platform/project";
import { useKeymap } from "@/platform/keymap";
import { ContextMenuHost } from "@/ui";
@@ -29,6 +30,8 @@ import { PageContext, useSetting } from "../components/context";
import { DEFAULT_SETTINGS, setAt } from "../model/schema";
import { openSettings, stage, useSettingsDialog } from "../model/staging";
import { SettingsDialog } from "../dialog/SettingsDialog";
import { SettingsWindow } from "../window/SettingsWindow";
import dialogStyles from "../dialog/SettingsDialog.module.css";
import { activate } from "..";
import { MessagesHost } from "@/workbench/messages";
@@ -471,6 +474,23 @@ describe("Settings dialog", () => {
});
});
describe("native Settings window root", () => {
it("marks the window edge on Linux, whose frameless windows get no compositor shadow", () => {
const saved = usePlatform.getState();
usePlatform.setState({ os: "linux" });
let view = render(<SettingsWindow />);
let root = document.querySelector(`.${dialogStyles.window}`);
expect(root).toHaveClass(dialogStyles.windowEdge!);
view.unmount();
usePlatform.setState({ os: "windows" });
view = render(<SettingsWindow />);
root = document.querySelector(`.${dialogStyles.window}`);
expect(root).not.toHaveClass(dialogStyles.windowEdge!);
view.unmount();
usePlatform.setState(saved);
});
});
describe("native Settings window background", () => {
it.each([
["a hex frame", "#26282c", "", "#26282c"],
@@ -203,6 +203,11 @@
background: var(--bg-dialog, var(--bg-elev));
color: var(--fg);
}
/* Linux draws no shadow for a frameless window (Wayland/KDE give undecorated
windows no CSD), so mark its edge in CSS instead. */
.windowEdge {
box-shadow: inset 0 0 0 1px var(--border-window);
}
.windowBody {
flex: 1;
min-height: 0;
@@ -1,4 +1,5 @@
/** Content of the native Settings window: title bar, page tree + page, buttons. */
import clsx from "clsx";
import { useEffect, useRef } from "react";
import { OverlayHost } from "@/workbench/OverlayHost";
import { usePlatform } from "@/platform/platformInfo";
@@ -10,6 +11,7 @@ import styles from "../dialog/SettingsDialog.module.css";
export function SettingsWindow() {
useApplyTheme();
const os = usePlatform((s) => s.os);
const ownTitleBar = usePlatform((s) => s.overlayTitleBar || s.framelessTitleBar);
const open = useSettingsDialog((s) => s.open);
const title = useSettingsTitle();
@@ -20,8 +22,8 @@ export function SettingsWindow() {
}, [open]);
return (
<div ref={ref} className={styles.window}>
{/* macOS overlays traffic lights on it; Windows is frameless. Linux keeps the OS title bar. */}
<div ref={ref} className={clsx(styles.window, os === "linux" && styles.windowEdge)}>
{/* macOS overlays traffic lights on it; Windows and Linux are frameless (tauri.<os>.conf.json). */}
{ownTitleBar && <TitleBar minimal dialog title={title} />}
{open && (
<>