fix: fallback to title when process name unavailable, dont skip windows
This commit is contained in:
parent
948f7de3a9
commit
cd1388a02b
1 changed files with 58 additions and 22 deletions
|
|
@ -16,7 +16,8 @@ mod win_impl {
|
||||||
keybd_event, KEYEVENTF_KEYUP, VK_MENU,
|
keybd_event, KEYEVENTF_KEYUP, VK_MENU,
|
||||||
};
|
};
|
||||||
use windows::Win32::System::Threading::{
|
use windows::Win32::System::Threading::{
|
||||||
OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
|
OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_FORMAT,
|
||||||
|
PROCESS_QUERY_LIMITED_INFORMATION,
|
||||||
};
|
};
|
||||||
use windows::Win32::System::ProcessStatus::GetModuleBaseNameW;
|
use windows::Win32::System::ProcessStatus::GetModuleBaseNameW;
|
||||||
|
|
||||||
|
|
@ -46,6 +47,8 @@ mod win_impl {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the process name (exe without extension) for a window handle.
|
/// Get the process name (exe without extension) for a window handle.
|
||||||
|
/// Tries `GetModuleBaseNameW` first, then `QueryFullProcessImageNameW`
|
||||||
|
/// (which works for elevated processes with `PROCESS_QUERY_LIMITED_INFORMATION`).
|
||||||
fn hwnd_process_name(hwnd: HWND) -> Option<String> {
|
fn hwnd_process_name(hwnd: HWND) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut pid: u32 = 0;
|
let mut pid: u32 = 0;
|
||||||
|
|
@ -54,24 +57,50 @@ mod win_impl {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid).ok()?;
|
let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid).ok()?;
|
||||||
|
|
||||||
|
// Try GetModuleBaseNameW first
|
||||||
let mut buf = [0u16; 260];
|
let mut buf = [0u16; 260];
|
||||||
let len = GetModuleBaseNameW(handle, None, &mut buf);
|
let len = GetModuleBaseNameW(handle, None, &mut buf);
|
||||||
let _ = windows::Win32::Foundation::CloseHandle(handle);
|
if len > 0 {
|
||||||
if len == 0 {
|
let _ = windows::Win32::Foundation::CloseHandle(handle);
|
||||||
return None;
|
let name = String::from_utf16_lossy(&buf[..len as usize]);
|
||||||
|
return Some(strip_exe_ext(&name));
|
||||||
}
|
}
|
||||||
let name = String::from_utf16_lossy(&buf[..len as usize]);
|
|
||||||
// Strip .exe extension
|
// Fallback: QueryFullProcessImageNameW (works for elevated processes)
|
||||||
Some(name.strip_suffix(".exe").or(name.strip_suffix(".EXE")).unwrap_or(&name).to_string())
|
let mut buf2 = [0u16; 1024];
|
||||||
|
let mut size = buf2.len() as u32;
|
||||||
|
let ok = QueryFullProcessImageNameW(handle, PROCESS_NAME_FORMAT(0), &mut buf2, &mut size);
|
||||||
|
let _ = windows::Win32::Foundation::CloseHandle(handle);
|
||||||
|
if ok.is_ok() && size > 0 {
|
||||||
|
let full_path = String::from_utf16_lossy(&buf2[..size as usize]);
|
||||||
|
// Extract filename from full path
|
||||||
|
let filename = full_path.rsplit('\\').next().unwrap_or(&full_path);
|
||||||
|
return Some(strip_exe_ext(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Minimal filter: only block explorer's "Program Manager" desktop window.
|
fn strip_exe_ext(name: &str) -> String {
|
||||||
fn is_ghost_window(_hwnd: HWND, title: &str, process_name: &str) -> bool {
|
name.strip_suffix(".exe")
|
||||||
let proc_lower = process_name.to_lowercase();
|
.or(name.strip_suffix(".EXE"))
|
||||||
// Only block explorer when title is "Program Manager" or empty
|
.unwrap_or(name)
|
||||||
if proc_lower == "explorer" {
|
.to_string()
|
||||||
let t = title.trim();
|
}
|
||||||
|
|
||||||
|
/// Filter out ghost/invisible windows. Works even without a process name.
|
||||||
|
fn is_ghost_window(title: &str, process_name: &str) -> bool {
|
||||||
|
let t = title.trim();
|
||||||
|
if t.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if t.eq_ignore_ascii_case("program manager") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Block explorer when title is empty or "Program Manager"
|
||||||
|
if !process_name.is_empty() && process_name.to_lowercase() == "explorer" {
|
||||||
if t.is_empty() || t.eq_ignore_ascii_case("program manager") {
|
if t.is_empty() || t.eq_ignore_ascii_case("program manager") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -109,10 +138,7 @@ mod win_impl {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let process_name = hwnd_process_name(*hwnd).unwrap_or_default();
|
let process_name = hwnd_process_name(*hwnd).unwrap_or_default();
|
||||||
if process_name.is_empty() {
|
if is_ghost_window(&title, &process_name) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if is_ghost_window(*hwnd, &title, &process_name) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
raw_windows.push((*hwnd, title, process_name));
|
raw_windows.push((*hwnd, title, process_name));
|
||||||
|
|
@ -120,16 +146,26 @@ mod win_impl {
|
||||||
|
|
||||||
// Second pass: generate labels from process names with dedup numbering
|
// Second pass: generate labels from process names with dedup numbering
|
||||||
let mut label_counts: HashMap<String, usize> = HashMap::new();
|
let mut label_counts: HashMap<String, usize> = HashMap::new();
|
||||||
// First count how many of each process name we have
|
// First count how many of each label base we have
|
||||||
for (_, _, proc_name) in &raw_windows {
|
for (_, title, proc_name) in &raw_windows {
|
||||||
let base = sanitize_label(proc_name);
|
let base = if proc_name.is_empty() {
|
||||||
*label_counts.entry(base).or_insert(0) += 1;
|
sanitize_label(title)
|
||||||
|
} else {
|
||||||
|
sanitize_label(proc_name)
|
||||||
|
};
|
||||||
|
if !base.is_empty() {
|
||||||
|
*label_counts.entry(base).or_insert(0) += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut label_index: HashMap<String, usize> = HashMap::new();
|
let mut label_index: HashMap<String, usize> = HashMap::new();
|
||||||
let mut windows = Vec::new();
|
let mut windows = Vec::new();
|
||||||
for (hwnd, title, proc_name) in raw_windows {
|
for (hwnd, title, proc_name) in raw_windows {
|
||||||
let base = sanitize_label(&proc_name);
|
let base = if proc_name.is_empty() {
|
||||||
|
sanitize_label(&title)
|
||||||
|
} else {
|
||||||
|
sanitize_label(&proc_name)
|
||||||
|
};
|
||||||
if base.is_empty() {
|
if base.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue