@@ -36,6 +36,8 @@ pub struct Settings {
3636 pub audio_buffer_size : usize ,
3737 #[ serde( default ) ]
3838 pub custom_colors : Vec < ( String , HSBK32 ) > ,
39+ #[ serde( default ) ]
40+ pub run_on_startup : bool ,
3941}
4042
4143impl Default for Settings {
@@ -50,6 +52,7 @@ impl Default for Settings {
5052 scheduled_scenes : Vec :: new ( ) ,
5153 audio_buffer_size : AUDIO_BUFFER_DEFAULT ,
5254 custom_colors : Vec :: new ( ) ,
55+ run_on_startup : false ,
5356 }
5457 }
5558}
@@ -82,6 +85,8 @@ impl MantleApp {
8285
8386 self . render_locale_selector ( ui) ;
8487
88+ self . render_run_on_startup ( ui) ;
89+
8590 self . render_refresh_rate ( ui) ;
8691
8792 self . render_update_rate ( ui) ;
@@ -364,6 +369,60 @@ impl MantleApp {
364369 } ) ;
365370 }
366371
372+ fn render_run_on_startup ( & mut self , ui : & mut egui:: Ui ) {
373+ let mut run_on_startup = self . settings . run_on_startup ;
374+ if ui
375+ . checkbox (
376+ & mut run_on_startup,
377+ t ! ( "settings.run_on_startup" ) . to_string ( ) ,
378+ )
379+ . on_hover_text ( t ! ( "settings.run_on_startup_hover" ) . to_string ( ) )
380+ . changed ( )
381+ {
382+ self . settings . run_on_startup = run_on_startup;
383+ match Self :: set_auto_launch ( run_on_startup) {
384+ Ok ( _) => {
385+ if run_on_startup {
386+ self . success_toast ( & t ! ( "settings.startup_enabled" ) ) ;
387+ } else {
388+ self . info_toast ( & t ! ( "settings.startup_disabled" ) ) ;
389+ }
390+ }
391+ Err ( e) => {
392+ self . settings . run_on_startup = !run_on_startup;
393+ self . error_toast ( & t ! ( "error.startup" , error = e) ) ;
394+ }
395+ }
396+ }
397+ }
398+
399+ fn build_auto_launch ( ) -> Result < auto_launch:: AutoLaunch , String > {
400+ let exe = std:: env:: current_exe ( ) . map_err ( |e| e. to_string ( ) ) ?;
401+ let exe_str = exe. to_str ( ) . ok_or ( "Invalid executable path" ) ?;
402+ auto_launch:: AutoLaunchBuilder :: new ( )
403+ . set_app_name ( "Mantle" )
404+ . set_app_path ( exe_str)
405+ . set_macos_launch_mode ( auto_launch:: MacOSLaunchMode :: LaunchAgent )
406+ . build ( )
407+ . map_err ( |e| e. to_string ( ) )
408+ }
409+
410+ fn set_auto_launch ( enable : bool ) -> Result < ( ) , String > {
411+ let auto = Self :: build_auto_launch ( ) ?;
412+ if enable {
413+ auto. enable ( ) . map_err ( |e| e. to_string ( ) )
414+ } else {
415+ auto. disable ( ) . map_err ( |e| e. to_string ( ) )
416+ }
417+ }
418+
419+ pub fn sync_auto_launch_state ( & mut self ) {
420+ match Self :: build_auto_launch ( ) . and_then ( |a| a. is_enabled ( ) . map_err ( |e| e. to_string ( ) ) ) {
421+ Ok ( enabled) => self . settings . run_on_startup = enabled,
422+ Err ( e) => log:: warn!( "Could not query auto-launch state: {}" , e) ,
423+ }
424+ }
425+
367426 fn render_refresh_rate ( & mut self , ui : & mut egui:: Ui ) {
368427 ui. horizontal ( |ui| {
369428 ui. label ( t ! ( "settings.refresh_rate" ) . to_string ( ) ) ;
@@ -760,6 +819,7 @@ mod tests {
760819 scheduled_scenes : Vec :: new ( ) ,
761820 audio_buffer_size : 4096 ,
762821 custom_colors : Vec :: new ( ) ,
822+ run_on_startup : false ,
763823 } ;
764824 let json = serde_json:: to_string ( & settings) . unwrap ( ) ;
765825 let deserialized: Settings = serde_json:: from_str ( & json) . unwrap ( ) ;
@@ -769,6 +829,7 @@ mod tests {
769829 assert ! ( deserialized. custom_shortcuts. is_empty( ) ) ;
770830 assert ! ( deserialized. scenes. is_empty( ) ) ;
771831 assert ! ( deserialized. custom_colors. is_empty( ) ) ;
832+ assert ! ( !deserialized. run_on_startup) ;
772833 }
773834
774835 #[ test]
0 commit comments