use windows::core::{w, PCWSTR};
use windows::Win32::Foundation::{HWND, LPARAM, RECT, WPARAM};
use windows::Win32::Graphics::Gdi::{
RedrawWindow, RDW_ALLCHILDREN, RDW_INVALIDATE, RDW_UPDATENOW, HRGN,
};
use windows::Win32::UI::WindowsAndMessaging::{
FindWindowExW, FindWindowW, GetSystemMetrics, IsWindow, ShowWindow, HWND_BROADCAST,
SystemParametersInfoW, SendMessageTimeoutW, SMTO_ABORTIFHUNG, SMTO_NORMAL,
SM_CXSCREEN, SM_CYSCREEN, SPIF_SENDCHANGE, SPI_GETWORKAREA, SPI_SETWORKAREA,
SW_HIDE, SW_SHOW, WM_SETTINGCHANGE,
};
const BROADCAST_TIMEOUT_MS: u32 = 100;
pub struct TaskbarHider {
original_work_area: RECT,
work_area_saved: bool,
is_hidden: bool,
}
impl TaskbarHider {
pub fn new() -> Self {
Self {
original_work_area: RECT::default(),
work_area_saved: false,
is_hidden: false,
}
}
pub fn is_hidden(&self) -> bool {
self.is_hidden
}
pub fn set_hidden(&mut self, hidden: bool) -> Result<(), String> {
if hidden {
self.hide()
} else {
self.show()
}
}
pub fn hide(&mut self) -> Result<(), String> {
if self.is_hidden {
return Ok(());
}
if !self.work_area_saved {
self.original_work_area = query_work_area()?;
self.work_area_saved = true;
}
for taskbar in find_taskbar_windows() {
hide_window(taskbar);
}
let full = full_screen_rect()?;
apply_work_area(full)?;
broadcast_work_area_changed();
self.is_hidden = true;
Ok(())
}
pub fn show(&mut self) -> Result<(), String> {
if !self.is_hidden {
return Ok(());
}
if self.work_area_saved {
apply_work_area(self.original_work_area)?;
}
for taskbar in find_taskbar_windows() {
show_window(taskbar);
}
broadcast_work_area_changed();
self.is_hidden = false;
Ok(())
}
}
impl Drop for TaskbarHider {
fn drop(&mut self) {
let _ = self.show();
}
}
fn query_work_area() -> Result<RECT, String> {
unsafe {
let mut rect = RECT::default();
SystemParametersInfoW(
SPI_GETWORKAREA,
0,
Some(&mut rect as *mut RECT as *mut _),
Default::default(),
)
.map_err(|e| format!("SPI_GETWORKAREA failed: {e}"))?;
Ok(rect)
}
}
fn full_screen_rect() -> Result<RECT, String> {
unsafe {
let cx = GetSystemMetrics(SM_CXSCREEN);
let cy = GetSystemMetrics(SM_CYSCREEN);
if cx <= 0 || cy <= 0 {
return Err("GetSystemMetrics returned invalid screen size.".to_string());
}
Ok(RECT {
left: 0,
top: 0,
right: cx,
bottom: cy,
})
}
}
fn apply_work_area(mut rect: RECT) -> Result<(), String> {
unsafe {
SystemParametersInfoW(
SPI_SETWORKAREA,
0,
Some(&mut rect as *mut RECT as *mut _),
SPIF_SENDCHANGE,
)
.map_err(|e| format!("SPI_SETWORKAREA failed: {e}"))
}
}
fn broadcast_work_area_changed() {
unsafe {
let mut result: usize = 0;
let _ = SendMessageTimeoutW(
HWND_BROADCAST,
WM_SETTINGCHANGE,
WPARAM(SPI_SETWORKAREA.0 as usize),
LPARAM(0),
SMTO_ABORTIFHUNG | SMTO_NORMAL,
BROADCAST_TIMEOUT_MS,
Some(&mut result as *mut usize),
);
}
}
fn find_taskbar_windows() -> Vec<HWND> {
let mut taskbars = Vec::new();
unsafe {
if let Ok(main) = FindWindowW(w!("Shell_TrayWnd"), PCWSTR::null()) {
if !main.is_invalid() && IsWindow(main).as_bool() {
taskbars.push(main);
}
}
let mut prev = HWND::default();
loop {
let secondary =
FindWindowExW(HWND::default(), prev, w!("Shell_SecondaryTrayWnd"), PCWSTR::null())
.unwrap_or_default();
if secondary.is_invalid() {
break;
}
if IsWindow(secondary).as_bool() {
taskbars.push(secondary);
}
prev = secondary;
}
}
taskbars
}
fn hide_window(hwnd: HWND) {
unsafe {
if IsWindow(hwnd).as_bool() {
let _ = ShowWindow(hwnd, SW_HIDE);
}
}
}
fn show_window(hwnd: HWND) {
unsafe {
if IsWindow(hwnd).as_bool() {
let _ = ShowWindow(hwnd, SW_SHOW);
let _ = RedrawWindow(
hwnd,
None,
HRGN::default(),
RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN,
);
}
}
}