mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-03-11 13:23:06 +01:00
Set background color for window controls on Linux (#247)
The Window Controls Overlay API applies a transparent overlay on Windows, but not on Linux. This PR addresses this by capturing the area underneath the overlay, averaging the color of the area, and setting this as the overlay background color. It will also detect whether to make the control symbols white or black, depending on how dark the background color is. On Linux, this will set both the background color and the symbol color, on Windows it will just set the symbol color. <img width="721" alt="image" src="https://github.com/user-attachments/assets/e6f9f8f8-a49f-41b6-984e-09e7d52c631d">
This commit is contained in:
parent
3f37837394
commit
e6003c310e
@ -17,6 +17,7 @@ export default defineConfig({
|
|||||||
input: {
|
input: {
|
||||||
index: "emain/emain.ts",
|
index: "emain/emain.ts",
|
||||||
},
|
},
|
||||||
|
external: ["sharp"],
|
||||||
},
|
},
|
||||||
outDir: "dist/main",
|
outDir: "dist/main",
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
import * as electron from "electron";
|
import * as electron from "electron";
|
||||||
|
import { getAverageColor } from "fast-average-color-node";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import * as child_process from "node:child_process";
|
import * as child_process from "node:child_process";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
@ -584,6 +585,27 @@ electron.ipcMain.on("getEnv", (event, varName) => {
|
|||||||
event.returnValue = process.env[varName] ?? null;
|
event.returnValue = process.env[varName] ?? null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("update-window-controls-overlay", async (event, rect: Dimensions) => {
|
||||||
|
if (unamePlatform !== "darwin") {
|
||||||
|
const zoomFactor = event.sender.getZoomFactor();
|
||||||
|
const electronRect: Electron.Rectangle = {
|
||||||
|
x: rect.left * zoomFactor,
|
||||||
|
y: rect.top * zoomFactor,
|
||||||
|
height: rect.height * zoomFactor,
|
||||||
|
width: rect.width * zoomFactor,
|
||||||
|
};
|
||||||
|
const overlay = await event.sender.capturePage(electronRect);
|
||||||
|
const overlayBuffer = overlay.toPNG();
|
||||||
|
|
||||||
|
const color = await getAverageColor(overlayBuffer);
|
||||||
|
const window = electron.BrowserWindow.fromWebContents(event.sender);
|
||||||
|
window.setTitleBarOverlay({
|
||||||
|
color: unamePlatform === "linux" ? color.rgba : "#00000000", // Windows supports a true transparent overlay, so we don't need to set a background color.
|
||||||
|
symbolColor: color.isDark ? "white" : "black",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function createNewWaveWindow() {
|
async function createNewWaveWindow() {
|
||||||
const clientData = await services.ClientService.GetClientData();
|
const clientData = await services.ClientService.GetClientData();
|
||||||
const newWindow = await services.ClientService.MakeWindow();
|
const newWindow = await services.ClientService.MakeWindow();
|
||||||
|
@ -24,6 +24,7 @@ contextBridge.exposeInMainWorld("api", {
|
|||||||
onUpdaterStatusChange: (callback) => ipcRenderer.on("app-update-status", (_event, status) => callback(status)),
|
onUpdaterStatusChange: (callback) => ipcRenderer.on("app-update-status", (_event, status) => callback(status)),
|
||||||
getUpdaterStatus: () => ipcRenderer.sendSync("get-app-update-status"),
|
getUpdaterStatus: () => ipcRenderer.sendSync("get-app-update-status"),
|
||||||
installAppUpdate: () => ipcRenderer.send("install-app-update"),
|
installAppUpdate: () => ipcRenderer.send("install-app-update"),
|
||||||
|
updateWindowControlsOverlay: (rect) => ipcRenderer.send("update-window-controls-overlay", rect),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Custom event for "new-window"
|
// Custom event for "new-window"
|
||||||
|
@ -5,11 +5,12 @@ import { useWaveObjectValue } from "@/app/store/wos";
|
|||||||
import { Workspace } from "@/app/workspace/workspace";
|
import { Workspace } from "@/app/workspace/workspace";
|
||||||
import { deleteLayoutModelForTab, getLayoutModelForTab } from "@/layout/index";
|
import { deleteLayoutModelForTab, getLayoutModelForTab } from "@/layout/index";
|
||||||
import { ContextMenuModel } from "@/store/contextmenu";
|
import { ContextMenuModel } from "@/store/contextmenu";
|
||||||
import { PLATFORM, WOS, atoms, globalStore, setBlockFocus } from "@/store/global";
|
import { PLATFORM, WOS, atoms, getApi, globalStore, setBlockFocus } from "@/store/global";
|
||||||
import * as services from "@/store/services";
|
import * as services from "@/store/services";
|
||||||
import { getWebServerEndpoint } from "@/util/endpoints";
|
import { getWebServerEndpoint } from "@/util/endpoints";
|
||||||
import * as keyutil from "@/util/keyutil";
|
import * as keyutil from "@/util/keyutil";
|
||||||
import * as util from "@/util/util";
|
import * as util from "@/util/util";
|
||||||
|
import useResizeObserver from "@react-hook/resize-observer";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import Color from "color";
|
import Color from "color";
|
||||||
import * as csstree from "css-tree";
|
import * as csstree from "css-tree";
|
||||||
@ -18,6 +19,7 @@ import "overlayscrollbars/overlayscrollbars.css";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { DndProvider } from "react-dnd";
|
import { DndProvider } from "react-dnd";
|
||||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||||
|
import { debounce } from "throttle-debounce";
|
||||||
import "./app.less";
|
import "./app.less";
|
||||||
import { CenteredDiv } from "./element/quickelems";
|
import { CenteredDiv } from "./element/quickelems";
|
||||||
|
|
||||||
@ -291,7 +293,9 @@ function processBackgroundUrls(cssText: string): string {
|
|||||||
const backgroundAttr = "url(/Users/mike/Downloads/wave-logo_appicon.png) repeat-x fixed";
|
const backgroundAttr = "url(/Users/mike/Downloads/wave-logo_appicon.png) repeat-x fixed";
|
||||||
|
|
||||||
function AppBackground() {
|
function AppBackground() {
|
||||||
|
const bgRef = React.useRef<HTMLDivElement>(null);
|
||||||
const tabId = jotai.useAtomValue(atoms.activeTabId);
|
const tabId = jotai.useAtomValue(atoms.activeTabId);
|
||||||
|
const windowOpacity = jotai.useAtomValue(atoms.settingsConfigAtom).window.opacity;
|
||||||
const [tabData] = useWaveObjectValue<Tab>(WOS.makeORef("tab", tabId));
|
const [tabData] = useWaveObjectValue<Tab>(WOS.makeORef("tab", tabId));
|
||||||
const bgAttr = tabData?.meta?.bg;
|
const bgAttr = tabData?.meta?.bg;
|
||||||
const style: React.CSSProperties = {};
|
const style: React.CSSProperties = {};
|
||||||
@ -311,7 +315,34 @@ function AppBackground() {
|
|||||||
console.error("error processing background", e);
|
console.error("error processing background", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return <div className="app-background" style={style} />;
|
const getAvgColor = React.useCallback(
|
||||||
|
debounce(10, () => {
|
||||||
|
if (
|
||||||
|
bgRef.current &&
|
||||||
|
PLATFORM !== "darwin" &&
|
||||||
|
bgRef.current &&
|
||||||
|
"windowControlsOverlay" in window.navigator
|
||||||
|
) {
|
||||||
|
const titlebarRect: Dimensions = (window.navigator.windowControlsOverlay as any).getTitlebarAreaRect();
|
||||||
|
const bgRect = bgRef.current.getBoundingClientRect();
|
||||||
|
if (titlebarRect && bgRect) {
|
||||||
|
const windowControlsLeft = titlebarRect.width - titlebarRect.height;
|
||||||
|
const windowControlsRect: Dimensions = {
|
||||||
|
top: titlebarRect.top,
|
||||||
|
left: windowControlsLeft,
|
||||||
|
height: titlebarRect.height,
|
||||||
|
width: bgRect.width - bgRect.left - windowControlsLeft,
|
||||||
|
};
|
||||||
|
getApi().updateWindowControlsOverlay(windowControlsRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[bgRef, style]
|
||||||
|
);
|
||||||
|
React.useEffect(getAvgColor, [getAvgColor]);
|
||||||
|
useResizeObserver(bgRef, getAvgColor);
|
||||||
|
|
||||||
|
return <div ref={bgRef} className="app-background" style={style} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function genericClose(tabId: string) {
|
function genericClose(tabId: string) {
|
||||||
|
8
frontend/types/custom.d.ts
vendored
8
frontend/types/custom.d.ts
vendored
@ -68,6 +68,7 @@ declare global {
|
|||||||
onUpdaterStatusChange: (callback: (status: UpdaterStatus) => void) => void;
|
onUpdaterStatusChange: (callback: (status: UpdaterStatus) => void) => void;
|
||||||
getUpdaterStatus: () => UpdaterStatus;
|
getUpdaterStatus: () => UpdaterStatus;
|
||||||
installAppUpdate: () => void;
|
installAppUpdate: () => void;
|
||||||
|
updateWindowControlsOverlay: (rect: Dimensions) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ElectronContextMenuItem = {
|
type ElectronContextMenuItem = {
|
||||||
@ -206,6 +207,13 @@ declare global {
|
|||||||
|
|
||||||
// jotai doesn't export this type :/
|
// jotai doesn't export this type :/
|
||||||
type Loadable<T> = { state: "loading" } | { state: "hasData"; data: T } | { state: "hasError"; error: unknown };
|
type Loadable<T> = { state: "loading" } | { state: "hasData"; data: T } | { state: "hasError"; error: unknown };
|
||||||
|
|
||||||
|
interface Dimensions {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
@ -92,6 +92,7 @@
|
|||||||
"css-tree": "^2.3.1",
|
"css-tree": "^2.3.1",
|
||||||
"dayjs": "^1.11.12",
|
"dayjs": "^1.11.12",
|
||||||
"electron-updater": "6.3.3",
|
"electron-updater": "6.3.3",
|
||||||
|
"fast-average-color-node": "^3.0.0",
|
||||||
"htl": "^0.3.1",
|
"htl": "^0.3.1",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
|
59
yarn.lock
59
yarn.lock
@ -7559,6 +7559,24 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"fast-average-color-node@npm:^3.0.0":
|
||||||
|
version: 3.0.0
|
||||||
|
resolution: "fast-average-color-node@npm:3.0.0"
|
||||||
|
dependencies:
|
||||||
|
fast-average-color: "npm:^9.4.0"
|
||||||
|
node-fetch: "npm:^2.6.7"
|
||||||
|
sharp: "npm:^0.33.2"
|
||||||
|
checksum: 10c0/92b9dd3f19a7ef64542e47d512ca25b93d38da5b4a3c47ee1e9e2e8df3f3a24cac1f2a21fd028b443c92dae3b608702195d596aaa0d6877d9fb9c990361ae69c
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"fast-average-color@npm:^9.4.0":
|
||||||
|
version: 9.4.0
|
||||||
|
resolution: "fast-average-color@npm:9.4.0"
|
||||||
|
checksum: 10c0/9031181113356abe240c52f78e908607e3b47dc0121cec3077b3735823951e40f8d6e14eca50d9941e30bcea60e0ed52e36410a8ded0972a89253c3dbefc966d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
|
"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
|
||||||
version: 3.1.3
|
version: 3.1.3
|
||||||
resolution: "fast-deep-equal@npm:3.1.3"
|
resolution: "fast-deep-equal@npm:3.1.3"
|
||||||
@ -10450,6 +10468,20 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"node-fetch@npm:^2.6.7":
|
||||||
|
version: 2.7.0
|
||||||
|
resolution: "node-fetch@npm:2.7.0"
|
||||||
|
dependencies:
|
||||||
|
whatwg-url: "npm:^5.0.0"
|
||||||
|
peerDependencies:
|
||||||
|
encoding: ^0.1.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
encoding:
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"node-gyp@npm:latest":
|
"node-gyp@npm:latest":
|
||||||
version: 10.1.0
|
version: 10.1.0
|
||||||
resolution: "node-gyp@npm:10.1.0"
|
resolution: "node-gyp@npm:10.1.0"
|
||||||
@ -12008,7 +12040,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"sharp@npm:^0.33.5":
|
"sharp@npm:^0.33.2, sharp@npm:^0.33.5":
|
||||||
version: 0.33.5
|
version: 0.33.5
|
||||||
resolution: "sharp@npm:0.33.5"
|
resolution: "sharp@npm:0.33.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12640,6 +12672,7 @@ __metadata:
|
|||||||
electron-vite: "npm:^2.3.0"
|
electron-vite: "npm:^2.3.0"
|
||||||
eslint: "npm:^9.9.0"
|
eslint: "npm:^9.9.0"
|
||||||
eslint-config-prettier: "npm:^9.1.0"
|
eslint-config-prettier: "npm:^9.1.0"
|
||||||
|
fast-average-color-node: "npm:^3.0.0"
|
||||||
htl: "npm:^0.3.1"
|
htl: "npm:^0.3.1"
|
||||||
html-to-image: "npm:^1.11.11"
|
html-to-image: "npm:^1.11.11"
|
||||||
immer: "npm:^10.1.1"
|
immer: "npm:^10.1.1"
|
||||||
@ -12787,6 +12820,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"tr46@npm:~0.0.3":
|
||||||
|
version: 0.0.3
|
||||||
|
resolution: "tr46@npm:0.0.3"
|
||||||
|
checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"trim-lines@npm:^3.0.0":
|
"trim-lines@npm:^3.0.0":
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
resolution: "trim-lines@npm:3.0.1"
|
resolution: "trim-lines@npm:3.0.1"
|
||||||
@ -13610,6 +13650,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"webidl-conversions@npm:^3.0.0":
|
||||||
|
version: 3.0.1
|
||||||
|
resolution: "webidl-conversions@npm:3.0.1"
|
||||||
|
checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"webpack-sources@npm:^3.2.3":
|
"webpack-sources@npm:^3.2.3":
|
||||||
version: 3.2.3
|
version: 3.2.3
|
||||||
resolution: "webpack-sources@npm:3.2.3"
|
resolution: "webpack-sources@npm:3.2.3"
|
||||||
@ -13624,6 +13671,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"whatwg-url@npm:^5.0.0":
|
||||||
|
version: 5.0.0
|
||||||
|
resolution: "whatwg-url@npm:5.0.0"
|
||||||
|
dependencies:
|
||||||
|
tr46: "npm:~0.0.3"
|
||||||
|
webidl-conversions: "npm:^3.0.0"
|
||||||
|
checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.2":
|
"which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.2":
|
||||||
version: 1.1.15
|
version: 1.1.15
|
||||||
resolution: "which-typed-array@npm:1.1.15"
|
resolution: "which-typed-array@npm:1.1.15"
|
||||||
|
Loading…
Reference in New Issue
Block a user