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}