Skip to content
Open
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
20 changes: 20 additions & 0 deletions apps/desktop/src/desktopSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { afterEach, describe, expect, it } from "vitest";
import {
DEFAULT_DESKTOP_SETTINGS,
readDesktopSettings,
setDesktopLinuxTitleBarMode,
setDesktopServerExposurePreference,
writeDesktopSettings,
} from "./desktopSettings";
Expand Down Expand Up @@ -35,10 +36,12 @@ describe("desktopSettings", () => {

writeDesktopSettings(settingsPath, {
serverExposureMode: "network-accessible",
linuxTitleBarMode: "custom",
});

expect(readDesktopSettings(settingsPath)).toEqual({
serverExposureMode: "network-accessible",
linuxTitleBarMode: "custom",
});
});

Expand All @@ -47,11 +50,13 @@ describe("desktopSettings", () => {
setDesktopServerExposurePreference(
{
serverExposureMode: "local-only",
linuxTitleBarMode: DEFAULT_DESKTOP_SETTINGS.linuxTitleBarMode,
},
"network-accessible",
),
).toEqual({
serverExposureMode: "network-accessible",
linuxTitleBarMode: DEFAULT_DESKTOP_SETTINGS.linuxTitleBarMode,
});
});

Expand All @@ -61,4 +66,19 @@ describe("desktopSettings", () => {

expect(readDesktopSettings(settingsPath)).toEqual(DEFAULT_DESKTOP_SETTINGS);
});

it("updates the requested linux title bar mode", () => {
expect(
setDesktopLinuxTitleBarMode(
{
...DEFAULT_DESKTOP_SETTINGS,
linuxTitleBarMode: "native",
},
"overlay",
),
).toEqual({
...DEFAULT_DESKTOP_SETTINGS,
linuxTitleBarMode: "overlay",
});
});
});
20 changes: 20 additions & 0 deletions apps/desktop/src/desktopSettings.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import * as FS from "node:fs";
import * as Path from "node:path";
import type { DesktopServerExposureMode } from "@t3tools/contracts";
import { DEFAULT_LINUX_TITLE_BAR_MODE, type LinuxTitleBarMode } from "@t3tools/contracts/settings";

export interface DesktopSettings {
readonly serverExposureMode: DesktopServerExposureMode;
readonly linuxTitleBarMode: LinuxTitleBarMode;
}

export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
serverExposureMode: "local-only",
linuxTitleBarMode: DEFAULT_LINUX_TITLE_BAR_MODE,
};

export function setDesktopServerExposurePreference(
Expand All @@ -22,6 +25,18 @@ export function setDesktopServerExposurePreference(
};
}

export function setDesktopLinuxTitleBarMode(
settings: DesktopSettings,
requestedMode: LinuxTitleBarMode,
): DesktopSettings {
return settings.linuxTitleBarMode === requestedMode
? settings
: {
...settings,
linuxTitleBarMode: requestedMode,
};
}

export function readDesktopSettings(settingsPath: string): DesktopSettings {
try {
if (!FS.existsSync(settingsPath)) {
Expand All @@ -31,11 +46,16 @@ export function readDesktopSettings(settingsPath: string): DesktopSettings {
const raw = FS.readFileSync(settingsPath, "utf8");
const parsed = JSON.parse(raw) as {
readonly serverExposureMode?: unknown;
readonly linuxTitleBarMode?: unknown;
};

return {
serverExposureMode:
parsed.serverExposureMode === "network-accessible" ? "network-accessible" : "local-only",
linuxTitleBarMode:
parsed.linuxTitleBarMode === "overlay" || parsed.linuxTitleBarMode === "custom"
? parsed.linuxTitleBarMode
: DEFAULT_LINUX_TITLE_BAR_MODE,
};
} catch {
return DEFAULT_DESKTOP_SETTINGS;
Expand Down
110 changes: 110 additions & 0 deletions apps/desktop/src/env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { describe, expect, it, vi } from "vitest";

import { getWindowChromeOptions, getWindowControlsLayout } from "./env";

vi.mock("./linuxWindowControls", () => ({
getLinuxWindowControlsLayout: vi.fn().mockReturnValue({
left: [],
right: ["minimize", "maximize", "close"],
}),
}));

describe("getWindowControlsLayout", () => {
it("uses the standard macOS traffic-light placement in ltr locales", () => {
expect(getWindowControlsLayout({ locale: "en-US", platform: "macos" })).toEqual({
left: ["close", "minimize", "maximize"],
right: [],
});
});

it("keeps macOS traffic lights left-aligned in rtl locales", () => {
expect(getWindowControlsLayout({ locale: "ar", platform: "macos" })).toEqual({
left: ["close", "minimize", "maximize"],
right: [],
});
});

it("uses the standard Windows control layout in ltr locales", () => {
expect(getWindowControlsLayout({ locale: "en-US", platform: "windows" })).toEqual({
left: [],
right: ["minimize", "maximize", "close"],
});
});

it("mirrors Windows controls in rtl locales", () => {
expect(getWindowControlsLayout({ locale: "he", platform: "windows" })).toEqual({
left: ["close", "maximize", "minimize"],
right: [],
});
});

it("keeps Linux layout unchanged even in rtl locales", () => {
expect(getWindowControlsLayout({ locale: "ar", platform: "linux" })).toEqual({
left: [],
right: ["minimize", "maximize", "close"],
});
});
});

describe("getWindowChromeOptions", () => {
it("uses transparent overlay and light symbols for windows in dark mode", () => {
expect(
getWindowChromeOptions({
darkMode: true,
linuxTitleBarMode: "native",
platform: "windows",
}),
).toEqual({
titleBarStyle: "hidden",
titleBarOverlay: {
height: 52,
color: "#00000000",
symbolColor: "#ffffff",
},
});
});

it("uses transparent overlay and dark symbols for windows in light mode", () => {
expect(
getWindowChromeOptions({
darkMode: false,
linuxTitleBarMode: "native",
platform: "windows",
}),
).toEqual({
titleBarStyle: "hidden",
titleBarOverlay: {
height: 52,
color: "#00000000",
symbolColor: "#000000",
},
});
});

it("keeps linux native titlebars unchanged", () => {
expect(
getWindowChromeOptions({
darkMode: true,
linuxTitleBarMode: "native",
platform: "linux",
}),
).toEqual({});
});

it("keeps linux overlay transparent workaround and applies symbol contrast", () => {
expect(
getWindowChromeOptions({
darkMode: false,
linuxTitleBarMode: "overlay",
platform: "linux",
}),
).toEqual({
titleBarStyle: "hidden",
titleBarOverlay: {
height: 52,
color: "#01000000",
symbolColor: "#000000",
},
});
});
});
120 changes: 120 additions & 0 deletions apps/desktop/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import type { DesktopPlatform, DesktopWindowControlsLayout } from "@t3tools/contracts";
import type { LinuxTitleBarMode } from "@t3tools/contracts/settings";
import { DESKTOP_TITLEBAR_HEIGHT_PX } from "@t3tools/shared/desktop";
import type { BrowserWindowConstructorOptions } from "electron";
import { getLinuxWindowControlsLayout } from "./linuxWindowControls";

const RTL_LANGUAGES = new Set(["ar", "dv", "fa", "he", "ku", "ps", "sd", "ug", "ur", "yi"]);
const MACOS_WINDOW_CONTROLS_LAYOUT: DesktopWindowControlsLayout = {
left: ["close", "minimize", "maximize"],
right: [],
};
const WINDOWS_WINDOW_CONTROLS_LAYOUT: DesktopWindowControlsLayout = {
left: [],
right: ["minimize", "maximize", "close"],
};
const WINDOW_CONTROL_SYMBOL_COLOR_DARK = "#ffffff";
const WINDOW_CONTROL_SYMBOL_COLOR_LIGHT = "#000000";
type WindowChromeOptions = Pick<
BrowserWindowConstructorOptions,
"titleBarStyle" | "titleBarOverlay" | "trafficLightPosition"
>;

export const platform: DesktopPlatform = (() => {
switch (process.platform) {
case "darwin":
return "macos";
case "win32":
return "windows";
case "linux":
return "linux";
default:
throw new Error(`Unsupported desktop platform: ${process.platform}`);
}
})();

function isRightToLeftLocale(locale: string | undefined): boolean {
if (!locale) {
return false;
}

const language = locale.split(/[-_]/, 1)[0]?.toLowerCase();
return language !== undefined && RTL_LANGUAGES.has(language);
}

function mirrorWindowControlsLayout(
layout: DesktopWindowControlsLayout,
): DesktopWindowControlsLayout {
return {
left: layout.right.toReversed(),
right: layout.left.toReversed(),
};
}

export function getWindowControlsLayout(options?: {
locale?: string;
platform?: DesktopPlatform;
}): DesktopWindowControlsLayout {
const resolvedPlatform = options?.platform ?? platform;
if (resolvedPlatform === "linux") {
return getLinuxWindowControlsLayout();
}

const rtl = isRightToLeftLocale(options?.locale);
const layout =
resolvedPlatform === "macos" ? MACOS_WINDOW_CONTROLS_LAYOUT : WINDOWS_WINDOW_CONTROLS_LAYOUT;

if (!rtl || resolvedPlatform === "macos") {
return layout;
}

return mirrorWindowControlsLayout(layout);
}

export function getWindowChromeOptions(input: {
darkMode: boolean;
linuxTitleBarMode: LinuxTitleBarMode;
platform: DesktopPlatform;
}): WindowChromeOptions {
const symbolColor = input.darkMode
? WINDOW_CONTROL_SYMBOL_COLOR_DARK
: WINDOW_CONTROL_SYMBOL_COLOR_LIGHT;

switch (input.platform) {
case "macos":
return {
titleBarStyle: "hiddenInset",
trafficLightPosition: { x: 16, y: 18 },
};

case "linux":
if (input.linuxTitleBarMode === "native") {
return {};
}

if (input.linuxTitleBarMode === "overlay") {
return {
titleBarStyle: "hidden",
titleBarOverlay: {
height: DESKTOP_TITLEBAR_HEIGHT_PX,
color: "#01000000", // #00000000 doesn't work falling back to default value, not sure why, probably some bug in Electron
symbolColor,
},
};
}

return {
titleBarStyle: "hidden",
};

case "windows":
return {
titleBarStyle: "hidden",
titleBarOverlay: {
height: DESKTOP_TITLEBAR_HEIGHT_PX,
color: "#00000000",
symbolColor,
},
};
}
}
Loading
Loading