Skip to content

Commit b2d21bf

Browse files
committed
fix: runtime issues — left-click, startup task, toggle buttons
- tray: left-click now toggles icons (right-click shows menu) via .with_menu_on_left_click(false) - startup: add -ExecutionPolicy Bypass to all PowerShell calls so the scheduled task registers reliably regardless of policy - installer: desktop shortcut is checked by default and writes to userdesktop instead of commondesktop (fixes silent failure when installing without elevation) - icons: send WM_COMMAND(0x7402) to SHELLDLL_DefView instead of Progman — more reliable on Windows 10/11 - logging: add debug.log written to %APPDATA%\HideDesktopApps\ so errors from Win32 calls are visible (eprintln goes nowhere in a windows_subsystem=windows app)
1 parent 5fb1e4a commit b2d21bf

8 files changed

Lines changed: 116 additions & 110 deletions

File tree

assets/installer.iss

-6 Bytes
Binary file not shown.

src/icons.rs

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,43 @@
11
use anyhow::Result;
22
use windows::core::w;
33
use windows::Win32::Foundation::HWND;
4-
use windows::Win32::UI::WindowsAndMessaging::{FindWindowW, SendMessageTimeoutW, SMTO_ABORTIFHUNG};
4+
use windows::Win32::UI::WindowsAndMessaging::{
5+
FindWindowW, SendMessageTimeoutW, SMTO_ABORTIFHUNG,
6+
};
57

68
const WM_COMMAND: u32 = 0x0111;
7-
// Shell command to toggle desktop icon visibility
89
const SHCMD_TOGGLE_DESKTOP_ICONS: u32 = 0x7402;
910

1011
fn get_progman() -> HWND {
11-
// SAFETY: FFI call to find the Progman window which hosts desktop icons
1212
unsafe {
1313
FindWindowW(w!("Progman"), windows::core::PCWSTR::null())
1414
.unwrap_or(HWND(std::ptr::null_mut()))
1515
}
1616
}
1717

1818
fn get_shell_defview() -> Option<HWND> {
19-
// SAFETY: FFI calls to traverse the window hierarchy to find ShellDefView
2019
unsafe {
2120
let progman = get_progman();
22-
if progman.0.is_null() {
23-
return None;
24-
}
25-
26-
let defview = windows::Win32::UI::WindowsAndMessaging::FindWindowExW(
27-
progman,
28-
HWND(std::ptr::null_mut()),
29-
w!("SHELLDLL_DefView"),
30-
windows::core::PCWSTR::null(),
31-
)
32-
.unwrap_or(HWND(std::ptr::null_mut()));
33-
34-
if !defview.0.is_null() {
35-
return Some(defview);
21+
if !progman.0.is_null() {
22+
let dv = windows::Win32::UI::WindowsAndMessaging::FindWindowExW(
23+
progman,
24+
HWND(std::ptr::null_mut()),
25+
w!("SHELLDLL_DefView"),
26+
windows::core::PCWSTR::null(),
27+
)
28+
.unwrap_or(HWND(std::ptr::null_mut()));
29+
if !dv.0.is_null() {
30+
return Some(dv);
31+
}
3632
}
3733

38-
// Desktop icons may be hosted inside a WorkerW instead of Progman
34+
// On Windows 11 DefView may be hosted inside a WorkerW, not Progman.
3935
let mut found = HWND(std::ptr::null_mut());
36+
4037
unsafe extern "system" fn find_defview_cb(
4138
hwnd: HWND,
4239
lparam: windows::Win32::Foundation::LPARAM,
4340
) -> windows::Win32::Foundation::BOOL {
44-
// SAFETY: lparam is a valid pointer to HWND that we passed in
4541
let target = &mut *(lparam.0 as *mut HWND);
4642
let dv = windows::Win32::UI::WindowsAndMessaging::FindWindowExW(
4743
hwnd,
@@ -72,11 +68,13 @@ fn get_shell_defview() -> Option<HWND> {
7268

7369
/// Returns true if desktop icons are currently visible.
7470
pub fn are_icons_visible() -> bool {
75-
// SAFETY: FFI call to find the SysListView32 which is the icon container
7671
unsafe {
7772
let defview = match get_shell_defview() {
7873
Some(dv) => dv,
79-
None => return true,
74+
None => {
75+
crate::dlog!("are_icons_visible: SHELLDLL_DefView not found, assuming visible");
76+
return true;
77+
}
8078
};
8179

8280
let listview = windows::Win32::UI::WindowsAndMessaging::FindWindowExW(
@@ -88,46 +86,67 @@ pub fn are_icons_visible() -> bool {
8886
.unwrap_or(HWND(std::ptr::null_mut()));
8987

9088
if listview.0.is_null() {
89+
crate::dlog!("are_icons_visible: SysListView32 not found, assuming visible");
9190
return true;
9291
}
9392

94-
windows::Win32::UI::WindowsAndMessaging::IsWindowVisible(listview).as_bool()
93+
let visible =
94+
windows::Win32::UI::WindowsAndMessaging::IsWindowVisible(listview).as_bool();
95+
crate::dlog!("are_icons_visible: SysListView32 visible = {visible}");
96+
visible
9597
}
9698
}
9799

98-
/// Toggle desktop icon visibility by sending a WM_COMMAND to the ShellDefView.
100+
/// Toggle desktop icon visibility by sending WM_COMMAND(0x7402) to
101+
/// SHELLDLL_DefView. Falls back to Progman if DefView is not found.
99102
pub fn toggle_icons() -> Result<()> {
100-
// SAFETY: FFI calls to send a shell command that toggles icon visibility
101103
unsafe {
102-
let progman = get_progman();
103-
anyhow::ensure!(!progman.0.is_null(), "Could not find Progman window");
104+
let target = match get_shell_defview() {
105+
Some(dv) => {
106+
crate::dlog!("toggle_icons: target = SHELLDLL_DefView");
107+
dv
108+
}
109+
None => {
110+
let progman = get_progman();
111+
anyhow::ensure!(!progman.0.is_null(), "Could not find Progman window");
112+
crate::dlog!("toggle_icons: target = Progman (fallback)");
113+
progman
114+
}
115+
};
104116

105117
let mut result = 0usize;
106118
SendMessageTimeoutW(
107-
progman,
119+
target,
108120
WM_COMMAND,
109121
windows::Win32::Foundation::WPARAM(SHCMD_TOGGLE_DESKTOP_ICONS as usize),
110122
windows::Win32::Foundation::LPARAM(0),
111123
SMTO_ABORTIFHUNG,
112124
1000,
113125
Some(&mut result),
114126
);
127+
crate::dlog!("toggle_icons: WM_COMMAND sent, result = {result}");
115128
}
116129
Ok(())
117130
}
118131

119-
/// Hide desktop icons. If already hidden, does nothing.
132+
/// Hide desktop icons. Does nothing if already hidden.
120133
pub fn hide_icons() -> Result<()> {
134+
crate::dlog!("hide_icons called");
121135
if are_icons_visible() {
122136
toggle_icons()?;
137+
} else {
138+
crate::dlog!("hide_icons: already hidden, skipping");
123139
}
124140
Ok(())
125141
}
126142

127-
/// Show desktop icons. If already visible, does nothing.
143+
/// Show desktop icons. Does nothing if already visible.
128144
pub fn show_icons() -> Result<()> {
145+
crate::dlog!("show_icons called");
129146
if !are_icons_visible() {
130147
toggle_icons()?;
148+
} else {
149+
crate::dlog!("show_icons: already visible, skipping");
131150
}
132151
Ok(())
133152
}

src/log_util.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use std::fs::OpenOptions;
2+
use std::io::Write;
3+
4+
/// Write a line to %APPDATA%\HideDesktopApps\debug.log.
5+
/// Does nothing if the path can't be opened — never panics.
6+
pub fn write(msg: &str) {
7+
let log_path = match std::env::var("APPDATA") {
8+
Ok(appdata) => std::path::PathBuf::from(appdata)
9+
.join("HideDesktopApps")
10+
.join("debug.log"),
11+
Err(_) => return,
12+
};
13+
14+
// Make sure the directory exists before trying to open the file.
15+
if let Some(parent) = log_path.parent() {
16+
let _ = std::fs::create_dir_all(parent);
17+
}
18+
19+
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&log_path) {
20+
let now = chrono::Local::now();
21+
let _ = writeln!(file, "[{}] {}", now.format("%H:%M:%S%.3f"), msg);
22+
}
23+
}
24+
25+
/// Convenience macro so call sites look like eprintln!.
26+
#[macro_export]
27+
macro_rules! dlog {
28+
($($arg:tt)*) => {
29+
$crate::log_util::write(&format!($($arg)*))
30+
};
31+
}

src/main.rs

Lines changed: 17 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ mod config;
44
mod discord;
55
mod hotkeys;
66
mod icons;
7+
#[macro_use]
8+
mod log_util;
79
mod notifications;
810
mod profiles;
911
mod startup;
@@ -58,6 +60,7 @@ fn main() {
5860
}
5961

6062
fn run() -> Result<()> {
63+
dlog!("--- HideDesktopApps starting ---");
6164
let config = config::load_config()?;
6265
let config_shared = Arc::new(Mutex::new(config.clone()));
6366
let state_shared = Arc::new(Mutex::new(AppState::default()));
@@ -236,15 +239,18 @@ fn main_loop(
236239
while let Ok(cmd) = cmd_rx.try_recv() {
237240
match cmd {
238241
Cmd::ToggleIcons => {
242+
dlog!("Cmd::ToggleIcons received");
239243
let mut state = state_shared.lock().unwrap();
240244
if state.icons_hidden {
241245
if let Err(e) = icons::show_icons() {
246+
dlog!("show_icons error: {e}");
242247
eprintln!("show_icons error: {e}");
243248
} else {
244249
state.icons_hidden = false;
245250
}
246251
} else {
247252
if let Err(e) = icons::hide_icons() {
253+
dlog!("hide_icons error: {e}");
248254
eprintln!("hide_icons error: {e}");
249255
} else {
250256
state.icons_hidden = true;
@@ -255,15 +261,18 @@ fn main_loop(
255261
}
256262

257263
Cmd::ToggleTaskbar => {
264+
dlog!("Cmd::ToggleTaskbar received");
258265
let mut state = state_shared.lock().unwrap();
259266
if state.taskbar_hidden {
260267
if let Err(e) = taskbar::show_taskbar() {
268+
dlog!("show_taskbar error: {e}");
261269
eprintln!("show_taskbar error: {e}");
262270
} else {
263271
state.taskbar_hidden = false;
264272
}
265273
} else {
266274
if let Err(e) = taskbar::hide_taskbar() {
275+
dlog!("hide_taskbar error: {e}");
267276
eprintln!("hide_taskbar error: {e}");
268277
} else {
269278
state.taskbar_hidden = true;
@@ -274,10 +283,12 @@ fn main_loop(
274283
}
275284

276285
Cmd::ToggleWindows => {
286+
dlog!("Cmd::ToggleWindows received");
277287
let mut state = state_shared.lock().unwrap();
278288
let cfg = config_shared.lock().unwrap().clone();
279289
if state.windows_hidden {
280290
if let Err(e) = win_ops::restore_windows(&state.hidden_windows) {
291+
dlog!("restore_windows error: {e}");
281292
eprintln!("restore_windows error: {e}");
282293
} else {
283294
state.hidden_windows.clear();
@@ -289,7 +300,10 @@ fn main_loop(
289300
state.hidden_windows = hidden;
290301
state.windows_hidden = true;
291302
}
292-
Err(e) => eprintln!("hide_windows error: {e}"),
303+
Err(e) => {
304+
dlog!("hide_windows error: {e}");
305+
eprintln!("hide_windows error: {e}");
306+
}
293307
}
294308
}
295309
tray::update_tray(&tray_handle, &state);
@@ -329,56 +343,10 @@ fn main_loop(
329343
}
330344

331345
Cmd::OpenSettings => {
346+
dlog!("Cmd::OpenSettings received");
332347
ui::open_settings(Arc::clone(&config_shared), cmd_tx.clone());
333348
}
334349

335350
Cmd::CheckForUpdates => {
336351
let cfg = config_shared.lock().unwrap().clone();
337-
updater::background_check(cfg.updater, cmd_tx.clone());
338-
}
339-
340-
Cmd::Restart => {
341-
// Restore all before restarting
342-
{
343-
let mut state = state_shared.lock().unwrap();
344-
profiles::restore_all(&mut state);
345-
}
346-
let exe = std::env::current_exe()
347-
.unwrap_or_else(|_| std::path::PathBuf::from("HideDesktopApps.exe"));
348-
let _ = std::process::Command::new(exe).spawn();
349-
return Ok(());
350-
}
351-
352-
Cmd::Exit => {
353-
// Restore everything cleanly before exiting
354-
let mut state = state_shared.lock().unwrap();
355-
profiles::restore_all(&mut state);
356-
return Ok(());
357-
}
358-
359-
Cmd::UpdateAvailable(version) => {
360-
let cfg = config_shared.lock().unwrap().clone();
361-
notifications::notify_update_available(&version, &cfg.notifications);
362-
eprintln!("Update available: {version}");
363-
}
364-
365-
Cmd::HotkeyFailed(hotkey) => {
366-
let cfg = config_shared.lock().unwrap().clone();
367-
notifications::notify_hotkey_failed(&hotkey, &cfg.notifications);
368-
}
369-
}
370-
}
371-
}
372-
}
373-
374-
/// Update Discord Rich Presence if enabled.
375-
fn update_discord(state: &AppState, config: &AppConfig) {
376-
if config.discord.enabled {
377-
discord::set_rich_presence(
378-
state.icons_hidden,
379-
state.taskbar_hidden,
380-
state.windows_hidden,
381-
state.active_profile.clone(),
382-
);
383-
}
384-
}
352+
updater::background_check(cfg.updater, cmd_t

src/startup.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Register-ScheduledTask -TaskName 'HideDesktopApps' -Action $action -Trigger $tri
1818

1919
let output = std::process::Command::new("powershell")
2020
.args([
21+
"-ExecutionPolicy",
22+
"Bypass",
2123
"-WindowStyle",
2224
"Hidden",
2325
"-NonInteractive",
@@ -38,6 +40,8 @@ Register-ScheduledTask -TaskName 'HideDesktopApps' -Action $action -Trigger $tri
3840
pub fn unregister() -> Result<()> {
3941
let output = std::process::Command::new("powershell")
4042
.args([
43+
"-ExecutionPolicy",
44+
"Bypass",
4145
"-WindowStyle",
4246
"Hidden",
4347
"-NonInteractive",
@@ -58,6 +62,8 @@ pub fn unregister() -> Result<()> {
5862
pub fn is_registered() -> bool {
5963
let out = std::process::Command::new("powershell")
6064
.args([
65+
"-ExecutionPolicy",
66+
"Bypass",
6167
"-WindowStyle",
6268
"Hidden",
6369
"-NonInteractive",
@@ -74,10 +80,4 @@ pub fn sync_startup(config: &crate::config::StartupConfig, exe_path: &str) {
7480
if config.enabled {
7581
if let Err(e) = register(exe_path, config.delay_s) {
7682
eprintln!("Failed to register startup task: {e}");
77-
}
78-
} else if is_registered() {
79-
if let Err(e) = unregister() {
80-
eprintln!("Failed to unregister startup task: {e}");
81-
}
82-
}
83-
}
83+

0 commit comments

Comments
 (0)