Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom window / native window controls #457

Merged
merged 12 commits into from
Dec 29, 2024
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ export const IPC_CHANNELS = {
VALIDATE_COMFYUI_SOURCE: 'validate-comfyui-source',
SHOW_DIRECTORY_PICKER: 'show-directory-picker',
INSTALL_COMFYUI: 'install-comfyui',
CHANGE_THEME: 'change-theme',
SHOW_CONTEXT_MENU: 'show-context-menu',
RESTART_CORE: 'restart-core',
GET_GPU: 'get-gpu',
SET_WINDOW_STYLE: 'set-window-style',
} as const;

export enum ProgressStatus {
Expand Down
7 changes: 7 additions & 0 deletions src/handlers/appInfoHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { app, ipcMain } from 'electron';
import { IPC_CHANNELS } from '../constants';
import { useDesktopConfig } from '../store/desktopConfig';
import type { TorchDeviceType } from '../preload';
import type { DesktopSettings } from '../store/desktopSettings';

/**
* Handles information about the app and current state in IPC channels.
Expand All @@ -20,5 +21,11 @@ export class AppInfoHandlers {
ipcMain.handle(IPC_CHANNELS.GET_GPU, async (): Promise<TorchDeviceType | undefined> => {
return await useDesktopConfig().getAsync('detectedGpu');
});
ipcMain.handle(
IPC_CHANNELS.SET_WINDOW_STYLE,
async (_event: Electron.IpcMainInvokeEvent, style: DesktopSettings['windowStyle']): Promise<void> => {
await useDesktopConfig().setAsync('windowStyle', style);
}
);
}
}
36 changes: 35 additions & 1 deletion src/main-process/appWindow.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
import { BrowserWindow, screen, app, shell, ipcMain, Tray, Menu, dialog, MenuItem } from 'electron';
import {
BrowserWindow,
screen,
app,
shell,
ipcMain,
Tray,
Menu,
dialog,
MenuItem,
nativeTheme,
type TitleBarOverlayOptions,
} from 'electron';
import path from 'node:path';
import Store from 'electron-store';
import { AppWindowSettings } from '../store/AppWindowSettings';
Expand All @@ -18,6 +30,10 @@ export class AppWindow {
private store: Store<AppWindowSettings>;
private messageQueue: Array<{ channel: string; data: unknown }> = [];
private rendererReady: boolean = false;
/** Default dark mode config for system window overlay (min/max/close window). */
private darkOverlay = { color: '#00000000', symbolColor: '#ddd' };
/** Default light mode config for system window overlay (min/max/close window). */
private lightOverlay = { ...this.darkOverlay, symbolColor: '#333' };
/** The application menu. */
private menu: Electron.Menu | null;
/** The "edit" menu - cut/copy/paste etc. */
Expand All @@ -36,6 +52,15 @@ export class AppWindow {
const storedX = store.get('windowX');
const storedY = store.get('windowY');

// macOS requires different handling to linux / win32
const customChrome: Pick<Electron.BrowserWindowConstructorOptions, 'titleBarStyle' | 'titleBarOverlay'> =
process.platform !== 'darwin' && useDesktopConfig().get('windowStyle') === 'custom'
? {
titleBarStyle: 'hidden',
titleBarOverlay: nativeTheme.shouldUseDarkColors ? this.darkOverlay : this.lightOverlay,
}
: {};

this.window = new BrowserWindow({
title: 'ComfyUI',
width: storedWidth,
Expand All @@ -53,6 +78,7 @@ export class AppWindow {
devTools: true,
},
autoHideMenuBar: true,
...customChrome,
});

if (!installed && storedX === undefined) this.window.center();
Expand Down Expand Up @@ -245,6 +271,14 @@ export class AppWindow {
});
}

changeTheme(options: TitleBarOverlayOptions): void {
if (process.platform === 'darwin' || useDesktopConfig().get('windowStyle') !== 'custom') return;

if (options.height) options.height = Math.round(options.height);
if (!options.height) delete options.height;
this.window.setTitleBarOverlay(options);
}

showSystemContextMenu(options?: ElectronContextMenuOptions): void {
if (options?.type === 'text') {
this.editMenu?.popup(options.pos);
Expand Down
5 changes: 4 additions & 1 deletion src/main-process/comfyDesktopApp.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { app, dialog, ipcMain, Notification } from 'electron';
import { app, dialog, ipcMain, Notification, type TitleBarOverlayOptions } from 'electron';
import log from 'electron-log/main';
import * as Sentry from '@sentry/electron/main';
import { graphics } from 'systeminformation';
Expand Down Expand Up @@ -91,6 +91,9 @@ export class ComfyDesktopApp {
}

registerIPCHandlers(): void {
ipcMain.on(IPC_CHANNELS.CHANGE_THEME, (_event, options: TitleBarOverlayOptions) => {
this.appWindow.changeTheme(options);
});
ipcMain.on(IPC_CHANNELS.SHOW_CONTEXT_MENU, (_event, options?: ElectronContextMenuOptions) => {
this.appWindow.showSystemContextMenu(options);
});
Expand Down
1 change: 1 addition & 0 deletions src/main_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export type {
PathValidationResult,
SystemPaths,
DownloadProgressUpdate,
ElectronOverlayOptions,
} from './preload';
44 changes: 35 additions & 9 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { contextBridge, ipcRenderer } from 'electron';
import { IPC_CHANNELS, ELECTRON_BRIDGE_API, ProgressStatus, DownloadStatus } from './constants';
import type { DownloadState } from './models/DownloadManager';
import path from 'node:path';
import type { DesktopSettings } from './store/desktopSettings';

/**
* Open a folder in the system's default file explorer.
Expand Down Expand Up @@ -41,6 +42,22 @@ export interface DownloadProgressUpdate {
message?: string;
}

/** @todo Type inference chain broken by comfyui-electron-types. This is duplication. */
export interface ElectronOverlayOptions {
/**
* The CSS color of the Window Controls Overlay when enabled.
*/
color?: string;
/**
* The CSS color of the symbols on the Window Controls Overlay when enabled.
*/
symbolColor?: string;
/**
* The height of the title bar and Window Controls Overlay in pixels.
*/
height?: number;
}

export interface ElectronContextMenuOptions {
type: 'system' | 'text' | 'image';
pos?: Electron.Point;
Expand Down Expand Up @@ -86,14 +103,15 @@ const electronAPI = {
console.log('Sending ready event to main process');
ipcRenderer.send(IPC_CHANNELS.RENDERER_READY);
},
isPackaged: () => {
/** Emulates app.ispackaged in renderer */
isPackaged: (): Promise<boolean> => {
return ipcRenderer.invoke(IPC_CHANNELS.IS_PACKAGED);
}, //Emulates app.ispackaged in renderer
},
restartApp: (customMessage?: string, delay?: number): void => {
console.log('Sending restarting app message to main process with custom message:', customMessage);
ipcRenderer.send(IPC_CHANNELS.RESTART_APP, { customMessage, delay });
},
reinstall: () => {
reinstall: (): Promise<void> => {
return ipcRenderer.invoke(IPC_CHANNELS.REINSTALL);
},
openDialog: (options: Electron.OpenDialogOptions) => {
Expand Down Expand Up @@ -161,9 +179,8 @@ const electronAPI = {
getElectronVersion: () => {
return ipcRenderer.invoke(IPC_CHANNELS.GET_ELECTRON_VERSION);
},
getComfyUIVersion: () => {
return __COMFYUI_VERSION__;
},
/** The ComfyUI core version (as defined in package.json) */
getComfyUIVersion: () => __COMFYUI_VERSION__,
/**
* Send an error message to Sentry
* @param error The error object or message to send
Expand Down Expand Up @@ -243,6 +260,12 @@ const electronAPI = {
installComfyUI: (installOptions: InstallOptions) => {
ipcRenderer.send(IPC_CHANNELS.INSTALL_COMFYUI, installOptions);
},
/**
* Update the Window Controls Overlay theme overrides
* @param theme The theme settings to apply
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window_Controls_Overlay_API}
*/
changeTheme: (theme: ElectronOverlayOptions): void => ipcRenderer.send(IPC_CHANNELS.CHANGE_THEME, theme),
/**
* Opens native context menus.
*
Expand All @@ -261,15 +284,18 @@ const electronAPI = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return await ipcRenderer.invoke(IPC_CHANNELS.GET_GPU);
},
/** Sets the window style */
setWindowStyle: (style: DesktopSettings['windowStyle']): Promise<void> => {
return ipcRenderer.invoke(IPC_CHANNELS.SET_WINDOW_STYLE, style);
},
},
/** Restart the python server without restarting desktop. */
restartCore: async (): Promise<void> => {
console.log('Restarting core process');
await ipcRenderer.invoke(IPC_CHANNELS.RESTART_APP);
},
getPlatform: (): NodeJS.Platform => {
return process.platform;
},
/** Gets the platform reported by node.js */
getPlatform: () => process.platform,
} as const;

export type ElectronAPI = typeof electronAPI;
Expand Down
6 changes: 6 additions & 0 deletions src/store/desktopSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ export type DesktopSettings = {
/** The pytorch device that the user selected during installation. */
selectedDevice?: TorchDeviceType;
'Comfy-Desktop.RestoredCustomNodes': boolean;
/**
* Controls whether to use a custom window on linux/win32
* - `custom`: Modern, theme-reactive, feels like an integral part of the UI
* - `default`: Impersonal, static, plain - default window title bar
*/
windowStyle?: 'custom' | 'default';
};
Loading