@@ -20,6 +20,8 @@ import { resolveDefaultDesktopUpdateChannel } from "../updates/updateChannels.ts
2020import { isValidDistroName } from "../wsl/wslPathParsing.ts" ;
2121
2222export interface DesktopSettings {
23+ readonly mainWindowBounds : DesktopWindowBounds | null ;
24+ readonly mainWindowMaximized : boolean ;
2325 readonly serverExposureMode : DesktopServerExposureMode ;
2426 readonly tailscaleServeEnabled : boolean ;
2527 readonly tailscaleServePort : number ;
@@ -48,8 +50,25 @@ export interface DesktopSettingsChange {
4850}
4951
5052export const DEFAULT_TAILSCALE_SERVE_PORT = 443 ;
53+ const MIN_MAIN_WINDOW_SIZE = {
54+ width : 840 ,
55+ height : 620 ,
56+ } as const ;
57+ export const DesktopWindowBoundsSchema = Schema . Struct ( {
58+ x : Schema . Int ,
59+ y : Schema . Int ,
60+ width : Schema . Int . check ( Schema . isGreaterThanOrEqualTo ( MIN_MAIN_WINDOW_SIZE . width ) ) ,
61+ height : Schema . Int . check ( Schema . isGreaterThanOrEqualTo ( MIN_MAIN_WINDOW_SIZE . height ) ) ,
62+ } ) ;
63+ export type DesktopWindowBounds = typeof DesktopWindowBoundsSchema . Type ;
64+ export const DEFAULT_MAIN_WINDOW_SIZE = {
65+ width : 1100 ,
66+ height : 780 ,
67+ } as const ;
5168
5269export const DEFAULT_DESKTOP_SETTINGS : DesktopSettings = {
70+ mainWindowBounds : null ,
71+ mainWindowMaximized : false ,
5372 serverExposureMode : "local-only" ,
5473 tailscaleServeEnabled : false ,
5574 tailscaleServePort : DEFAULT_TAILSCALE_SERVE_PORT ,
@@ -60,7 +79,16 @@ export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
6079 wslOnly : false ,
6180} ;
6281
82+ const DesktopWindowBoundsDocument = Schema . Struct ( {
83+ x : Schema . Number ,
84+ y : Schema . Number ,
85+ width : Schema . Number ,
86+ height : Schema . Number ,
87+ } ) ;
88+
6389const DesktopSettingsDocument = Schema . Struct ( {
90+ mainWindowBounds : Schema . optionalKey ( Schema . NullOr ( DesktopWindowBoundsDocument ) ) ,
91+ mainWindowMaximized : Schema . optionalKey ( Schema . Boolean ) ,
6492 serverExposureMode : Schema . optionalKey ( DesktopServerExposureModeSchema ) ,
6593 tailscaleServeEnabled : Schema . optionalKey ( Schema . Boolean ) ,
6694 tailscaleServePort : Schema . optionalKey ( Schema . Number ) ,
@@ -81,6 +109,8 @@ type Mutable<T> = { -readonly [K in keyof T]: T[K] };
81109const DesktopSettingsJson = fromLenientJson ( DesktopSettingsDocument ) ;
82110const decodeDesktopSettingsJson = Schema . decodeEffect ( DesktopSettingsJson ) ;
83111const encodeDesktopSettingsJson = Schema . encodeEffect ( DesktopSettingsJson ) ;
112+ const decodeDesktopWindowBounds = Schema . decodeUnknownOption ( DesktopWindowBoundsSchema ) ;
113+ const desktopWindowBoundsEquivalence = Schema . toEquivalence ( DesktopWindowBoundsSchema ) ;
84114
85115const settingsChange = ( settings : DesktopSettings , changed : boolean ) : DesktopSettingsChange => ( {
86116 settings,
@@ -114,6 +144,10 @@ export class DesktopAppSettings extends Context.Service<
114144 {
115145 readonly load : Effect . Effect < DesktopSettings > ;
116146 readonly get : Effect . Effect < DesktopSettings > ;
147+ readonly setMainWindowBounds : (
148+ bounds : DesktopWindowBounds ,
149+ isMaximized : boolean ,
150+ ) => Effect . Effect < DesktopSettingsChange , DesktopSettingsWriteError > ;
117151 readonly setServerExposureMode : (
118152 mode : DesktopServerExposureMode ,
119153 ) => Effect . Effect < DesktopSettingsChange , DesktopSettingsWriteError > ;
@@ -158,11 +192,16 @@ function normalizeWslDistro(value: unknown): string | null {
158192 return typeof value === "string" && isValidDistroName ( value ) ? value : null ;
159193}
160194
195+ export function normalizeMainWindowBounds ( value : unknown ) : DesktopWindowBounds | null {
196+ return Option . getOrNull ( decodeDesktopWindowBounds ( value ) ) ;
197+ }
198+
161199function normalizeDesktopSettingsDocument (
162200 parsed : DesktopSettingsDocument ,
163201 appVersion : string ,
164202) : DesktopSettings {
165203 const defaultSettings = resolveDefaultDesktopSettings ( appVersion ) ;
204+ const mainWindowBounds = normalizeMainWindowBounds ( parsed . mainWindowBounds ) ;
166205 const parsedUpdateChannel = Option . fromNullishOr ( parsed . updateChannel ) ;
167206 const isLegacySettings = parsed . updateChannelConfiguredByUser === undefined ;
168207 const updateChannelConfiguredByUser =
@@ -177,6 +216,8 @@ function normalizeDesktopSettingsDocument(
177216 ( parsed . wslBackendEnabled === undefined && parsed . wslMode === "wsl" ) ;
178217
179218 return {
219+ mainWindowBounds,
220+ mainWindowMaximized : mainWindowBounds !== null && parsed . mainWindowMaximized === true ,
180221 serverExposureMode :
181222 parsed . serverExposureMode === "network-accessible" ? "network-accessible" : "local-only" ,
182223 tailscaleServeEnabled : parsed . tailscaleServeEnabled === true ,
@@ -197,6 +238,12 @@ function toDesktopSettingsDocument(
197238) : DesktopSettingsDocument {
198239 const document : Mutable < DesktopSettingsDocument > = { } ;
199240
241+ if ( settings . mainWindowBounds !== null ) {
242+ document . mainWindowBounds = settings . mainWindowBounds ;
243+ }
244+ if ( settings . mainWindowMaximized ) {
245+ document . mainWindowMaximized = true ;
246+ }
200247 if ( settings . serverExposureMode !== defaults . serverExposureMode ) {
201248 document . serverExposureMode = settings . serverExposureMode ;
202249 }
@@ -237,6 +284,22 @@ function setServerExposureMode(
237284 } ;
238285}
239286
287+ function setMainWindowBounds (
288+ settings : DesktopSettings ,
289+ bounds : DesktopWindowBounds ,
290+ isMaximized : boolean ,
291+ ) : DesktopSettings {
292+ return settings . mainWindowBounds !== null &&
293+ desktopWindowBoundsEquivalence ( settings . mainWindowBounds , bounds ) &&
294+ settings . mainWindowMaximized === isMaximized
295+ ? settings
296+ : {
297+ ...settings ,
298+ mainWindowBounds : bounds ,
299+ mainWindowMaximized : isMaximized ,
300+ } ;
301+ }
302+
240303function setTailscaleServe (
241304 settings : DesktopSettings ,
242305 input : { readonly enabled : boolean ; readonly port : Option . Option < number > } ,
@@ -431,6 +494,18 @@ export const make = Effect.gen(function* () {
431494 ) ;
432495 return yield * SynchronizedRef . setAndGet ( settingsRef , settings ) ;
433496 } ) . pipe ( Effect . withSpan ( "desktop.settings.load" ) ) ,
497+ setMainWindowBounds : ( bounds , isMaximized ) =>
498+ persist ( ( settings ) => setMainWindowBounds ( settings , bounds , isMaximized ) ) . pipe (
499+ Effect . withSpan ( "desktop.settings.setMainWindowBounds" , {
500+ attributes : {
501+ x : bounds . x ,
502+ y : bounds . y ,
503+ width : bounds . width ,
504+ height : bounds . height ,
505+ isMaximized,
506+ } ,
507+ } ) ,
508+ ) ,
434509 setServerExposureMode : ( mode ) =>
435510 persist ( ( settings ) => setServerExposureMode ( settings , mode ) ) . pipe (
436511 Effect . withSpan ( "desktop.settings.setServerExposureMode" , { attributes : { mode } } ) ,
@@ -488,6 +563,8 @@ export const layerTest = (initialSettings: DesktopSettings = DEFAULT_DESKTOP_SET
488563 return DesktopAppSettings . of ( {
489564 get : SynchronizedRef . get ( settingsRef ) ,
490565 load : SynchronizedRef . get ( settingsRef ) ,
566+ setMainWindowBounds : ( bounds , isMaximized ) =>
567+ update ( ( settings ) => setMainWindowBounds ( settings , bounds , isMaximized ) ) ,
491568 setServerExposureMode : ( mode ) =>
492569 update ( ( settings ) => setServerExposureMode ( settings , mode ) ) ,
493570 setTailscaleServe : ( input ) => update ( ( settings ) => setTailscaleServe ( settings , input ) ) ,
0 commit comments