1- use anyhow:: Result ;
1+ use anyhow:: { bail, Result } ;
2+ use std:: os:: windows:: process:: CommandExt ;
23use std:: path:: PathBuf ;
34use winreg:: enums:: * ;
45use winreg:: RegKey ;
56
7+ const TASK_NAME : & str = "HideDesktopApps" ;
68const APP_NAME : & str = "HideDesktopApps" ;
7- const RUN_KEY : & str = r"Software\Microsoft\Windows\CurrentVersion\Run" ;
9+ // Suppress the console window that PowerShell/schtasks would otherwise flash.
10+ const CREATE_NO_WINDOW : u32 = 0x0800_0000 ;
811
9- /// Old VBS launcher path — used only to clean up on migration.
12+ /// Run a hidden PowerShell command and return an error if it fails.
13+ fn powershell ( script : & str ) -> Result < ( ) > {
14+ let status = std:: process:: Command :: new ( "powershell.exe" )
15+ . args ( [
16+ "-WindowStyle" ,
17+ "Hidden" ,
18+ "-NonInteractive" ,
19+ "-Command" ,
20+ script,
21+ ] )
22+ . creation_flags ( CREATE_NO_WINDOW )
23+ . status ( ) ?;
24+ if !status. success ( ) {
25+ bail ! ( "PowerShell exited with code {:?}" , status. code( ) ) ;
26+ }
27+ Ok ( ( ) )
28+ }
29+
30+ /// Path of the old VBS launcher — cleaned up on migration.
1031fn legacy_vbs_path ( ) -> PathBuf {
1132 let appdata = std:: env:: var ( "APPDATA" ) . unwrap_or_default ( ) ;
1233 PathBuf :: from ( appdata)
@@ -18,69 +39,83 @@ fn legacy_vbs_path() -> PathBuf {
1839 . join ( "HideDesktopApps.vbs" )
1940}
2041
21- /// Remove the old VBS launcher if it exists (left over from a previous install).
22- fn remove_legacy_vbs ( ) {
23- let path = legacy_vbs_path ( ) ;
24- if path. exists ( ) {
25- let _ = std:: fs:: remove_file ( & path) ;
42+ /// Remove any leftover startup entries from older installs.
43+ fn cleanup_legacy ( ) {
44+ // Old VBS launcher
45+ let vbs = legacy_vbs_path ( ) ;
46+ if vbs. exists ( ) {
47+ let _ = std:: fs:: remove_file ( & vbs) ;
48+ }
49+ // Old registry Run entry
50+ let hkcu = RegKey :: predef ( HKEY_CURRENT_USER ) ;
51+ if let Ok ( key) = hkcu. open_subkey_with_flags (
52+ r"Software\Microsoft\Windows\CurrentVersion\Run" ,
53+ KEY_SET_VALUE ,
54+ ) {
55+ let _ = key. delete_value ( APP_NAME ) ;
2656 }
2757}
2858
29- /// Write a registry Run entry so the app launches at login.
30- /// The exe path is quoted to handle spaces in the path.
31- pub fn register ( exe_path : & str , _delay_s : u32 ) -> Result < ( ) > {
32- let hkcu = RegKey :: predef ( HKEY_CURRENT_USER ) ;
33- let key = hkcu. open_subkey_with_flags ( RUN_KEY , KEY_SET_VALUE ) ?;
34- // Quote the path so spaces are handled correctly.
35- let value = format ! ( "\" {}\" " , exe_path) ;
36- key. set_value ( APP_NAME , & value) ?;
37- // Clean up the old VBS launcher if it's still around.
38- remove_legacy_vbs ( ) ;
59+ /// Register a scheduled task that runs the app at logon with an optional delay.
60+ /// Uses the same PowerShell approach as the installer so both agree on the task name.
61+ pub fn register ( exe_path : & str , delay_s : u32 ) -> Result < ( ) > {
62+ // ISO 8601 duration, e.g. PT30S for 30 seconds.
63+ let delay = format ! ( "PT{}S" , delay_s) ;
64+ // Escape single quotes inside the exe path for use in a PowerShell string.
65+ let escaped = exe_path. replace ( '\'' , "''" ) ;
66+ let script = format ! (
67+ "$t = New-ScheduledTaskTrigger -AtLogOn; \
68+ $t.Delay = '{delay}'; \
69+ $a = New-ScheduledTaskAction -Execute '\" {escaped}\" '; \
70+ $s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0 -AllowStartIfOnBatteries $true; \
71+ Register-ScheduledTask -TaskName '{TASK_NAME}' -Trigger $t -Action $a -Settings $s -Force | Out-Null"
72+ ) ;
73+ powershell ( & script) ?;
74+ cleanup_legacy ( ) ;
3975 Ok ( ( ) )
4076}
4177
42- /// Remove the registry Run entry .
78+ /// Remove the scheduled task (and any legacy startup entries) .
4379pub fn unregister ( ) -> Result < ( ) > {
44- let hkcu = RegKey :: predef ( HKEY_CURRENT_USER ) ;
45- let key = hkcu . open_subkey_with_flags ( RUN_KEY , KEY_SET_VALUE ) ? ;
46- // delete_value returns an error if the value doesn't exist; that's fine.
47- let _ = key . delete_value ( APP_NAME ) ;
48- remove_legacy_vbs ( ) ;
80+ let script = format ! (
81+ "Unregister-ScheduledTask -TaskName '{TASK_NAME}' -Confirm:$false -ErrorAction SilentlyContinue"
82+ ) ;
83+ powershell ( & script ) ? ;
84+ cleanup_legacy ( ) ;
4985 Ok ( ( ) )
5086}
5187
52- /// Returns true if the registry Run entry exists.
88+ /// Returns true if the scheduled task exists.
5389pub fn is_registered ( ) -> bool {
54- let hkcu = RegKey :: predef ( HKEY_CURRENT_USER ) ;
55- if let Ok ( key) = hkcu. open_subkey ( RUN_KEY ) {
56- let val: Result < String , _ > = key. get_value ( APP_NAME ) ;
57- val. is_ok ( )
58- } else {
59- false
60- }
90+ std:: process:: Command :: new ( "schtasks.exe" )
91+ . args ( [ "/query" , "/tn" , TASK_NAME ] )
92+ . creation_flags ( CREATE_NO_WINDOW )
93+ . output ( )
94+ . map ( |o| o. status . success ( ) )
95+ . unwrap_or ( false )
6196}
6297
63- /// Sync the startup entry to match the config.
98+ /// Sync the startup task to match config — called on every app startup .
6499pub fn sync_startup ( config : & crate :: config:: StartupConfig , exe_path : & str ) {
65100 crate :: dlog!( "sync_startup: enabled={}, exe={}" , config. enabled, exe_path) ;
66101 if config. enabled {
67102 match register ( exe_path, config. delay_s ) {
68- Ok ( ( ) ) => crate :: dlog!( "startup: registered OK" ) ,
103+ Ok ( ( ) ) => crate :: dlog!( "startup: task registered OK" ) ,
69104 Err ( e) => {
70105 crate :: dlog!( "startup: register failed: {}" , e) ;
71- eprintln ! ( "Failed to register startup: {e}" ) ;
106+ eprintln ! ( "Failed to register startup task : {e}" ) ;
72107 }
73108 }
74109 } else if is_registered ( ) {
75110 match unregister ( ) {
76- Ok ( ( ) ) => crate :: dlog!( "startup: unregistered OK" ) ,
111+ Ok ( ( ) ) => crate :: dlog!( "startup: task unregistered OK" ) ,
77112 Err ( e) => {
78113 crate :: dlog!( "startup: unregister failed: {}" , e) ;
79- eprintln ! ( "Failed to unregister startup: {e}" ) ;
114+ eprintln ! ( "Failed to unregister startup task : {e}" ) ;
80115 }
81116 }
82117 } else {
83- crate :: dlog!( "startup: disabled and not registered , nothing to do" ) ;
118+ crate :: dlog!( "startup: disabled and task not present , nothing to do" ) ;
84119 }
85120}
86121
0 commit comments