use windows::{
Win32::{
Foundation::{
CloseHandle, ERROR_ALREADY_EXISTS, GetLastError, HANDLE, HWND, LPARAM, WPARAM,
},
System::Threading::CreateMutexW,
UI::{
Controls::{BST_CHECKED, BST_UNCHECKED},
Shell::SetCurrentProcessExplicitAppUserModelID,
WindowsAndMessaging::{
BM_GETCHECK, BM_SETCHECK, SendMessageW, SetWindowTextW, WINDOW_STYLE,
},
},
},
core::{Error, HRESULT, PCWSTR, Result},
};
use crate::app_identity;
pub const fn ws_i32(base: WINDOW_STYLE, extra: i32) -> WINDOW_STYLE {
WINDOW_STYLE(base.0 | extra as u32)
}
pub fn last_error() -> Error {
Error::from_hresult(HRESULT::from_win32(unsafe { GetLastError() }.0))
}
pub struct SingleInstanceGuard(pub HANDLE);
impl Drop for SingleInstanceGuard {
fn drop(&mut self) {
unsafe {
let _ = CloseHandle(self.0);
}
}
}
pub fn single_instance_guard() -> Result<Option<SingleInstanceGuard>> {
unsafe {
let name: Vec<u16> = app_identity::SINGLE_INSTANCE_MUTEX
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let h = CreateMutexW(None, false, PCWSTR(name.as_ptr()))?;
if GetLastError() == ERROR_ALREADY_EXISTS {
return Ok(None);
}
Ok(Some(SingleInstanceGuard(h)))
}
}
pub const fn loword(v: usize) -> u16 {
(v & 0xffff) as u16
}
pub const fn hiword(v: usize) -> u16 {
((v >> 16) & 0xffff) as u16
}
pub fn default_window_pos(window_w: i32, window_h: i32) -> (i32, i32) {
use windows::Win32::UI::WindowsAndMessaging::{GetSystemMetrics, SM_CXSCREEN, SM_CYSCREEN};
let sw = unsafe { GetSystemMetrics(SM_CXSCREEN) };
let sh = unsafe { GetSystemMetrics(SM_CYSCREEN) };
#[cfg(debug_assertions)]
{
let cell_w = sw / 3;
let cell_h = sh / 3;
let cx = cell_w / 2;
let cy = cell_h * 2 + cell_h / 2;
let mut x = cx - window_w / 2;
let mut y = cy - window_h / 2;
x -= 100;
y -= 95;
if x < 0 {
x = 0;
}
if y < 0 {
y = 0;
}
if x + window_w > sw {
x = sw - window_w;
}
if y + window_h > sh {
y = sh - window_h;
}
(x, y)
}
#[cfg(not(debug_assertions))]
{
let x = (sw - window_w) / 2;
let y = (sh - window_h) / 2;
(x, y)
}
}
pub fn set_checkbox(hwnd: HWND, value: bool) {
let v = if value { BST_CHECKED } else { BST_UNCHECKED };
unsafe {
SendMessageW(
hwnd,
BM_SETCHECK,
Some(WPARAM(v.0 as usize)),
Some(LPARAM(0)),
);
}
}
pub fn get_checkbox(hwnd: HWND) -> bool {
let r = unsafe { SendMessageW(hwnd, BM_GETCHECK, Some(WPARAM(0)), Some(LPARAM(0))) };
r.0 as u32 == BST_CHECKED.0
}
pub fn set_edit_text(hwnd: HWND, s: &str) -> windows::core::Result<()> {
let wide: Vec<u16> = s.encode_utf16().chain(std::iter::once(0)).collect();
unsafe { SetWindowTextW(hwnd, PCWSTR(wide.as_ptr())) }
}
#[cfg(debug_assertions)]
pub const DEBUG_TIMER_ID_STARTUP_ERROR: usize = 0xC001;
#[cfg(debug_assertions)]
pub const DEBUG_TIMER_ID_STARTUP_INFO: usize = 0xC002;
pub fn init_app_user_model_id() -> windows::core::Result<()> {
let id: Vec<u16> = app_identity::APP_USER_MODEL_ID
.encode_utf16()
.chain(std::iter::once(0))
.collect();
unsafe { SetCurrentProcessExplicitAppUserModelID(PCWSTR(id.as_ptr())) }
}
#[cfg(debug_assertions)]
pub fn debug_log(msg: &str) {
tracing::debug!(message = %msg, "debug");
}
#[cfg(debug_assertions)]
pub fn debug_startup_notification(hwnd: HWND, _state: &mut crate::app::AppState) {
use std::sync::atomic::{AtomicBool, Ordering};
use windows::Win32::UI::WindowsAndMessaging::SetTimer;
static STARTUP_NOTIFS_ARMED: AtomicBool = AtomicBool::new(false);
if STARTUP_NOTIFS_ARMED.swap(true, Ordering::SeqCst) {
return;
}
unsafe {
let _ = SetTimer(Some(hwnd), DEBUG_TIMER_ID_STARTUP_ERROR, 200, None);
let _ = SetTimer(Some(hwnd), DEBUG_TIMER_ID_STARTUP_INFO, 800, None);
}
}