util.rs

  1use std::sync::OnceLock;
  2
  3use ::util::ResultExt;
  4use anyhow::Context;
  5use windows::{
  6    UI::{
  7        Color,
  8        ViewManagement::{UIColorType, UISettings},
  9    },
 10    Wdk::System::SystemServices::RtlGetVersion,
 11    Win32::{
 12        Foundation::*, Graphics::Dwm::*, System::LibraryLoader::LoadLibraryA,
 13        UI::WindowsAndMessaging::*,
 14    },
 15    core::{BOOL, HSTRING, PCSTR},
 16};
 17
 18use crate::*;
 19
 20#[derive(Debug, Clone, Copy)]
 21pub(crate) enum WindowsVersion {
 22    Win10,
 23    Win11,
 24}
 25
 26impl WindowsVersion {
 27    pub(crate) fn new() -> anyhow::Result<Self> {
 28        let mut version = unsafe { std::mem::zeroed() };
 29        let status = unsafe { RtlGetVersion(&mut version) };
 30
 31        status.ok()?;
 32        if version.dwBuildNumber >= 22000 {
 33            Ok(WindowsVersion::Win11)
 34        } else {
 35            Ok(WindowsVersion::Win10)
 36        }
 37    }
 38}
 39
 40pub(crate) trait HiLoWord {
 41    fn hiword(&self) -> u16;
 42    fn loword(&self) -> u16;
 43    fn signed_hiword(&self) -> i16;
 44    fn signed_loword(&self) -> i16;
 45}
 46
 47impl HiLoWord for WPARAM {
 48    fn hiword(&self) -> u16 {
 49        ((self.0 >> 16) & 0xFFFF) as u16
 50    }
 51
 52    fn loword(&self) -> u16 {
 53        (self.0 & 0xFFFF) as u16
 54    }
 55
 56    fn signed_hiword(&self) -> i16 {
 57        ((self.0 >> 16) & 0xFFFF) as i16
 58    }
 59
 60    fn signed_loword(&self) -> i16 {
 61        (self.0 & 0xFFFF) as i16
 62    }
 63}
 64
 65impl HiLoWord for LPARAM {
 66    fn hiword(&self) -> u16 {
 67        ((self.0 >> 16) & 0xFFFF) as u16
 68    }
 69
 70    fn loword(&self) -> u16 {
 71        (self.0 & 0xFFFF) as u16
 72    }
 73
 74    fn signed_hiword(&self) -> i16 {
 75        ((self.0 >> 16) & 0xFFFF) as i16
 76    }
 77
 78    fn signed_loword(&self) -> i16 {
 79        (self.0 & 0xFFFF) as i16
 80    }
 81}
 82
 83pub(crate) unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize {
 84    #[cfg(target_pointer_width = "64")]
 85    unsafe {
 86        GetWindowLongPtrW(hwnd, nindex)
 87    }
 88    #[cfg(target_pointer_width = "32")]
 89    unsafe {
 90        GetWindowLongW(hwnd, nindex) as isize
 91    }
 92}
 93
 94pub(crate) unsafe fn set_window_long(
 95    hwnd: HWND,
 96    nindex: WINDOW_LONG_PTR_INDEX,
 97    dwnewlong: isize,
 98) -> isize {
 99    #[cfg(target_pointer_width = "64")]
100    unsafe {
101        SetWindowLongPtrW(hwnd, nindex, dwnewlong)
102    }
103    #[cfg(target_pointer_width = "32")]
104    unsafe {
105        SetWindowLongW(hwnd, nindex, dwnewlong as i32) as isize
106    }
107}
108
109pub(crate) fn windows_credentials_target_name(url: &str) -> String {
110    format!("zed:url={}", url)
111}
112
113pub(crate) fn load_cursor(style: CursorStyle) -> Option<HCURSOR> {
114    static ARROW: OnceLock<SafeCursor> = OnceLock::new();
115    static IBEAM: OnceLock<SafeCursor> = OnceLock::new();
116    static CROSS: OnceLock<SafeCursor> = OnceLock::new();
117    static HAND: OnceLock<SafeCursor> = OnceLock::new();
118    static SIZEWE: OnceLock<SafeCursor> = OnceLock::new();
119    static SIZENS: OnceLock<SafeCursor> = OnceLock::new();
120    static SIZENWSE: OnceLock<SafeCursor> = OnceLock::new();
121    static SIZENESW: OnceLock<SafeCursor> = OnceLock::new();
122    static NO: OnceLock<SafeCursor> = OnceLock::new();
123    let (lock, name) = match style {
124        CursorStyle::IBeam | CursorStyle::IBeamCursorForVerticalLayout => (&IBEAM, IDC_IBEAM),
125        CursorStyle::Crosshair => (&CROSS, IDC_CROSS),
126        CursorStyle::PointingHand | CursorStyle::DragLink => (&HAND, IDC_HAND),
127        CursorStyle::ResizeLeft
128        | CursorStyle::ResizeRight
129        | CursorStyle::ResizeLeftRight
130        | CursorStyle::ResizeColumn => (&SIZEWE, IDC_SIZEWE),
131        CursorStyle::ResizeUp
132        | CursorStyle::ResizeDown
133        | CursorStyle::ResizeUpDown
134        | CursorStyle::ResizeRow => (&SIZENS, IDC_SIZENS),
135        CursorStyle::ResizeUpLeftDownRight => (&SIZENWSE, IDC_SIZENWSE),
136        CursorStyle::ResizeUpRightDownLeft => (&SIZENESW, IDC_SIZENESW),
137        CursorStyle::OperationNotAllowed => (&NO, IDC_NO),
138        CursorStyle::None => return None,
139        _ => (&ARROW, IDC_ARROW),
140    };
141    Some(
142        *(*lock.get_or_init(|| {
143            HCURSOR(
144                unsafe { LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED) }
145                    .log_err()
146                    .unwrap_or_default()
147                    .0,
148            )
149            .into()
150        })),
151    )
152}
153
154/// This function is used to configure the dark mode for the window built-in title bar.
155pub(crate) fn configure_dwm_dark_mode(hwnd: HWND, appearance: WindowAppearance) {
156    let dark_mode_enabled: BOOL = match appearance {
157        WindowAppearance::Dark | WindowAppearance::VibrantDark => true.into(),
158        WindowAppearance::Light | WindowAppearance::VibrantLight => false.into(),
159    };
160    unsafe {
161        DwmSetWindowAttribute(
162            hwnd,
163            DWMWA_USE_IMMERSIVE_DARK_MODE,
164            &dark_mode_enabled as *const _ as _,
165            std::mem::size_of::<BOOL>() as u32,
166        )
167        .log_err();
168    }
169}
170
171#[inline]
172pub(crate) fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point<Pixels> {
173    Point {
174        x: px(x / scale_factor),
175        y: px(y / scale_factor),
176    }
177}
178
179// https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/apply-windows-themes
180#[inline]
181pub(crate) fn system_appearance() -> Result<WindowAppearance> {
182    let ui_settings = UISettings::new()?;
183    let foreground_color = ui_settings.GetColorValue(UIColorType::Foreground)?;
184    // If the foreground is light, then is_color_light will evaluate to true,
185    // meaning Dark mode is enabled.
186    if is_color_light(&foreground_color) {
187        Ok(WindowAppearance::Dark)
188    } else {
189        Ok(WindowAppearance::Light)
190    }
191}
192
193#[inline(always)]
194fn is_color_light(color: &Color) -> bool {
195    ((5 * color.G as u32) + (2 * color.R as u32) + color.B as u32) > (8 * 128)
196}
197
198pub(crate) fn show_error(title: &str, content: String) {
199    let _ = unsafe {
200        MessageBoxW(
201            None,
202            &HSTRING::from(content),
203            &HSTRING::from(title),
204            MB_ICONERROR | MB_SYSTEMMODAL,
205        )
206    };
207}
208
209pub(crate) fn with_dll_library<R, F>(dll_name: PCSTR, f: F) -> Result<R>
210where
211    F: FnOnce(HMODULE) -> Result<R>,
212{
213    let library = unsafe {
214        LoadLibraryA(dll_name).with_context(|| format!("Loading dll: {}", dll_name.display()))?
215    };
216    let result = f(library);
217    unsafe {
218        FreeLibrary(library)
219            .with_context(|| format!("Freeing dll: {}", dll_name.display()))
220            .log_err();
221    }
222    result
223}