diff --git a/src/app/TitleBar.tsx b/src/app/TitleBar.tsx
index 68ae85a..b583686 100644
--- a/src/app/TitleBar.tsx
+++ b/src/app/TitleBar.tsx
@@ -11,6 +11,8 @@ import { cn } from "@/lib/utils";
import { useDeepLink } from "@/contexts/DeepLinkContext";
import { useEffect, useState } from "react";
import { DyadProSuccessDialog } from "@/components/DyadProSuccessDialog";
+import { useTheme } from "@/contexts/ThemeContext";
+import { IpcClient } from "@/ipc/ipc_client";
export const TitleBar = () => {
const [selectedAppId] = useAtom(selectedAppIdAtom);
@@ -18,6 +20,21 @@ export const TitleBar = () => {
const { navigate } = useRouter();
const { settings, refreshSettings } = useSettings();
const [isSuccessDialogOpen, setIsSuccessDialogOpen] = useState(false);
+ const [showWindowControls, setShowWindowControls] = useState(false);
+
+ useEffect(() => {
+ // Check if we're running on Windows
+ const checkPlatform = async () => {
+ try {
+ const platform = await IpcClient.getInstance().getSystemPlatform();
+ setShowWindowControls(platform !== "darwin");
+ } catch (error) {
+ console.error("Failed to get platform info:", error);
+ }
+ };
+
+ checkPlatform();
+ }, []);
const showDyadProSuccessDialog = () => {
setIsSuccessDialogOpen(true);
@@ -82,6 +99,7 @@ export const TitleBar = () => {
{isDyadProEnabled ? "Dyad Pro" : "Dyad Pro (disabled)"}
)}
+ {showWindowControls && }
{
>
);
};
+
+function WindowsControls() {
+ const { isDarkMode } = useTheme();
+ const ipcClient = IpcClient.getInstance();
+
+ const minimizeWindow = () => {
+ ipcClient.minimizeWindow();
+ };
+
+ const maximizeWindow = () => {
+ ipcClient.maximizeWindow();
+ };
+
+ const closeWindow = () => {
+ ipcClient.closeWindow();
+ };
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/ipc/handlers/window_handlers.ts b/src/ipc/handlers/window_handlers.ts
new file mode 100644
index 0000000..eecf44c
--- /dev/null
+++ b/src/ipc/handlers/window_handlers.ts
@@ -0,0 +1,53 @@
+import { BrowserWindow, ipcMain } from "electron";
+import log from "electron-log";
+import { platform } from "os";
+
+const logger = log.scope("window-handlers");
+
+// Handler for minimizing the window
+const handleMinimize = (event: Electron.IpcMainInvokeEvent) => {
+ const window = BrowserWindow.fromWebContents(event.sender);
+ if (!window) {
+ logger.error("Failed to get BrowserWindow instance for minimize command");
+ return;
+ }
+ window.minimize();
+};
+
+// Handler for maximizing/restoring the window
+const handleMaximize = (event: Electron.IpcMainInvokeEvent) => {
+ const window = BrowserWindow.fromWebContents(event.sender);
+ if (!window) {
+ logger.error("Failed to get BrowserWindow instance for maximize command");
+ return;
+ }
+
+ if (window.isMaximized()) {
+ window.restore();
+ } else {
+ window.maximize();
+ }
+};
+
+// Handler for closing the window
+const handleClose = (event: Electron.IpcMainInvokeEvent) => {
+ const window = BrowserWindow.fromWebContents(event.sender);
+ if (!window) {
+ logger.error("Failed to get BrowserWindow instance for close command");
+ return;
+ }
+ window.close();
+};
+
+// Handler to get the current system platform
+const handleGetSystemPlatform = () => {
+ return platform();
+};
+
+export function registerWindowHandlers() {
+ logger.debug("Registering window control handlers");
+ ipcMain.handle("window:minimize", handleMinimize);
+ ipcMain.handle("window:maximize", handleMaximize);
+ ipcMain.handle("window:close", handleClose);
+ ipcMain.handle("window:get-platform", handleGetSystemPlatform);
+}
diff --git a/src/ipc/ipc_client.ts b/src/ipc/ipc_client.ts
index 37739e4..a8cff00 100644
--- a/src/ipc/ipc_client.ts
+++ b/src/ipc/ipc_client.ts
@@ -776,4 +776,45 @@ export class IpcClient {
throw error;
}
}
+
+ // Window control methods
+ public async minimizeWindow(): Promise {
+ try {
+ await this.ipcRenderer.invoke("window:minimize");
+ } catch (error) {
+ showError(error);
+ throw error;
+ }
+ }
+
+ public async maximizeWindow(): Promise {
+ try {
+ await this.ipcRenderer.invoke("window:maximize");
+ } catch (error) {
+ showError(error);
+ throw error;
+ }
+ }
+
+ public async closeWindow(): Promise {
+ try {
+ await this.ipcRenderer.invoke("window:close");
+ } catch (error) {
+ showError(error);
+ throw error;
+ }
+ }
+
+ // Get system platform (win32, darwin, linux)
+ public async getSystemPlatform(): Promise {
+ try {
+ const platform = await this.ipcRenderer.invoke("window:get-platform");
+ return platform;
+ } catch (error) {
+ showError(error);
+ throw error;
+ }
+ }
+
+ // --- End window control methods ---
}
diff --git a/src/ipc/ipc_host.ts b/src/ipc/ipc_host.ts
index 2f443c4..6328f06 100644
--- a/src/ipc/ipc_host.ts
+++ b/src/ipc/ipc_host.ts
@@ -11,6 +11,7 @@ import { registerDebugHandlers } from "./handlers/debug_handlers";
import { registerSupabaseHandlers } from "./handlers/supabase_handlers";
import { registerLocalModelHandlers } from "./handlers/local_model_handlers";
import { registerTokenCountHandlers } from "./handlers/token_count_handlers";
+import { registerWindowHandlers } from "./handlers/window_handlers";
export function registerIpcHandlers() {
// Register all IPC handlers by category
@@ -27,4 +28,5 @@ export function registerIpcHandlers() {
registerSupabaseHandlers();
registerLocalModelHandlers();
registerTokenCountHandlers();
+ registerWindowHandlers();
}
diff --git a/src/main.ts b/src/main.ts
index d396b4e..a32aa14 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -96,7 +96,7 @@ const createWindow = () => {
width: process.env.NODE_ENV === "development" ? 1280 : 960,
height: 700,
titleBarStyle: "hidden",
- titleBarOverlay: true,
+ titleBarOverlay: false,
trafficLightPosition: {
x: 10,
y: 8,
diff --git a/src/preload.ts b/src/preload.ts
index 9b22973..ad11fa3 100644
--- a/src/preload.ts
+++ b/src/preload.ts
@@ -48,6 +48,10 @@ const validInvokeChannels = [
"supabase:set-app-project",
"supabase:unset-app-project",
"local-models:list",
+ "window:minimize",
+ "window:maximize",
+ "window:close",
+ "window:get-platform",
] as const;
// Add valid receive channels