@@ -6,8 +6,10 @@ import {
66 app ,
77 clipboard ,
88 session ,
9+ nativeImage ,
910} from "electron"
1011import { join } from "path"
12+ import { readFileSync , existsSync , writeFileSync , mkdirSync } from "fs"
1113import { createIPCHandler } from "trpc-electron/main"
1214import { createAppRouter } from "../lib/trpc/routers"
1315import { getAuthManager , handleAuthCode , getBaseUrl } from "../index"
@@ -23,17 +25,92 @@ function registerIpcHandlers(getWindow: () => BrowserWindow | null): void {
2325 // App info
2426 ipcMain . handle ( "app:version" , ( ) => app . getVersion ( ) )
2527 ipcMain . handle ( "app:isPackaged" , ( ) => app . isPackaged )
28+
29+ // Windows: Frame preference persistence
30+ ipcMain . handle ( "window:set-frame-preference" , ( _event , useNativeFrame : boolean ) => {
31+ try {
32+ const settingsPath = join ( app . getPath ( "userData" ) , "window-settings.json" )
33+ const settingsDir = app . getPath ( "userData" )
34+ mkdirSync ( settingsDir , { recursive : true } )
35+ writeFileSync ( settingsPath , JSON . stringify ( { useNativeFrame } , null , 2 ) )
36+ return true
37+ } catch ( error ) {
38+ console . error ( "[Main] Failed to save frame preference:" , error )
39+ return false
40+ }
41+ } )
42+
43+ // Windows: Get current window frame state
44+ ipcMain . handle ( "window:get-frame-state" , ( ) => {
45+ if ( process . platform !== "win32" ) return false
46+ try {
47+ const settingsPath = join ( app . getPath ( "userData" ) , "window-settings.json" )
48+ if ( existsSync ( settingsPath ) ) {
49+ const settings = JSON . parse ( readFileSync ( settingsPath , "utf-8" ) )
50+ return settings . useNativeFrame === true
51+ }
52+ return false // Default: frameless
53+ } catch {
54+ return false
55+ }
56+ } )
57+
2658 // Note: Update checking is now handled by auto-updater module (lib/auto-updater.ts)
2759 ipcMain . handle ( "app:set-badge" , ( _event , count : number | null ) => {
60+ const win = getWindow ( )
2861 if ( process . platform === "darwin" ) {
2962 app . dock . setBadge ( count ? String ( count ) : "" )
63+ } else if ( process . platform === "win32" && win ) {
64+ // Windows: Update title with count as fallback
65+ if ( count !== null && count > 0 ) {
66+ win . setTitle ( `1Code (${ count } )` )
67+ } else {
68+ win . setTitle ( "1Code" )
69+ win . setOverlayIcon ( null , "" )
70+ }
71+ }
72+ } )
73+
74+ // Windows: Badge overlay icon
75+ ipcMain . handle ( "app:set-badge-icon" , ( _event , imageData : string | null ) => {
76+ const win = getWindow ( )
77+ if ( process . platform === "win32" && win ) {
78+ if ( imageData ) {
79+ const image = nativeImage . createFromDataURL ( imageData )
80+ win . setOverlayIcon ( image , "New messages" )
81+ } else {
82+ win . setOverlayIcon ( null , "" )
83+ }
3084 }
3185 } )
86+
3287 ipcMain . handle (
3388 "app:show-notification" ,
3489 ( _event , options : { title : string ; body : string } ) => {
35- const { Notification } = require ( "electron" )
36- new Notification ( options ) . show ( )
90+ try {
91+ const { Notification } = require ( "electron" )
92+ const iconPath = join ( __dirname , "../../../build/icon.ico" )
93+ const icon = existsSync ( iconPath ) ? nativeImage . createFromPath ( iconPath ) : undefined
94+
95+ const notification = new Notification ( {
96+ title : options . title ,
97+ body : options . body ,
98+ icon,
99+ ...( process . platform === "win32" && { silent : false } ) ,
100+ } )
101+
102+ notification . show ( )
103+
104+ notification . on ( "click" , ( ) => {
105+ const win = getWindow ( )
106+ if ( win ) {
107+ if ( win . isMinimized ( ) ) win . restore ( )
108+ win . focus ( )
109+ }
110+ } )
111+ } catch ( error ) {
112+ console . error ( "[Main] Failed to show notification:" , error )
113+ }
37114 } ,
38115 )
39116
@@ -229,13 +306,35 @@ export function getWindow(): BrowserWindow | null {
229306 return currentWindow
230307}
231308
309+ /**
310+ * Read window frame preference from settings file (Windows only)
311+ * Returns true if native frame should be used, false for frameless
312+ */
313+ function getUseNativeFramePreference ( ) : boolean {
314+ if ( process . platform !== "win32" ) return false
315+
316+ try {
317+ const settingsPath = join ( app . getPath ( "userData" ) , "window-settings.json" )
318+ if ( existsSync ( settingsPath ) ) {
319+ const settings = JSON . parse ( readFileSync ( settingsPath , "utf-8" ) )
320+ return settings . useNativeFrame === true
321+ }
322+ return false // Default: frameless (dark title bar)
323+ } catch {
324+ return false
325+ }
326+ }
327+
232328/**
233329 * Create the main application window
234330 */
235331export function createMainWindow ( ) : BrowserWindow {
236332 // Register IPC handlers before creating window
237333 registerIpcHandlers ( getWindow )
238334
335+ // Read Windows frame preference
336+ const useNativeFrame = getUseNativeFramePreference ( )
337+
239338 const window = new BrowserWindow ( {
240339 width : 1400 ,
241340 height : 900 ,
@@ -250,6 +349,11 @@ export function createMainWindow(): BrowserWindow {
250349 titleBarStyle : process . platform === "darwin" ? "hiddenInset" : "default" ,
251350 trafficLightPosition :
252351 process . platform === "darwin" ? { x : 15 , y : 12 } : undefined ,
352+ // Windows: Use native frame or frameless based on user preference
353+ ...( process . platform === "win32" && {
354+ frame : useNativeFrame ,
355+ autoHideMenuBar : true ,
356+ } ) ,
253357 webPreferences : {
254358 preload : join ( __dirname , "../preload/index.js" ) ,
255359 nodeIntegration : false ,
0 commit comments