druid_shell/backend/windows/
util.rs1use std::ffi::{CString, OsStr, OsString};
21use std::mem;
22use std::os::windows::ffi::{OsStrExt, OsStringExt};
23use std::ptr;
24use std::slice;
25
26use once_cell::sync::Lazy;
27use winapi::ctypes::c_void;
28use winapi::shared::dxgi::IDXGIDevice;
29use winapi::shared::guiddef::REFIID;
30use winapi::shared::minwindef::{BOOL, HMODULE, UINT};
31use winapi::shared::ntdef::{HRESULT, LPWSTR};
32use winapi::shared::windef::{HMONITOR, HWND, RECT};
33use winapi::shared::winerror::SUCCEEDED;
34use winapi::um::fileapi::{CreateFileA, GetFileType, OPEN_EXISTING};
35use winapi::um::handleapi::INVALID_HANDLE_VALUE;
36use winapi::um::libloaderapi::{GetModuleHandleW, GetProcAddress, LoadLibraryW};
37use winapi::um::processenv::{GetStdHandle, SetStdHandle};
38use winapi::um::shellscalingapi::{MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS};
39use winapi::um::winbase::{FILE_TYPE_UNKNOWN, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE};
40use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS};
41use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE};
42
43use crate::kurbo::Rect;
44use crate::region::Region;
45use crate::scale::{Scalable, Scale};
46
47use super::error::Error;
48
49pub fn as_result(hr: HRESULT) -> Result<(), Error> {
50 if SUCCEEDED(hr) {
51 Ok(())
52 } else {
53 Err(Error::Hr(hr))
54 }
55}
56
57impl From<HRESULT> for Error {
58 fn from(hr: HRESULT) -> Error {
59 Error::Hr(hr)
60 }
61}
62
63pub trait ToWide {
64 fn to_wide_sized(&self) -> Vec<u16>;
65 fn to_wide(&self) -> Vec<u16>;
66}
67
68impl<T> ToWide for T
69where
70 T: AsRef<OsStr>,
71{
72 fn to_wide_sized(&self) -> Vec<u16> {
73 self.as_ref().encode_wide().collect()
74 }
75 fn to_wide(&self) -> Vec<u16> {
76 self.as_ref().encode_wide().chain(Some(0)).collect()
77 }
78}
79
80pub trait FromWide {
81 fn to_u16_slice(&self) -> &[u16];
82
83 fn to_os_string(&self) -> OsString {
84 OsStringExt::from_wide(self.to_u16_slice())
85 }
86
87 fn to_string(&self) -> Option<String> {
88 String::from_utf16(self.to_u16_slice()).ok()
89 }
90}
91
92impl FromWide for LPWSTR {
93 fn to_u16_slice(&self) -> &[u16] {
94 unsafe {
95 let mut len = 0;
96 while *self.offset(len) != 0 {
97 len += 1;
98 }
99 slice::from_raw_parts(*self, len as usize)
100 }
101 }
102}
103
104impl FromWide for [u16] {
105 fn to_u16_slice(&self) -> &[u16] {
106 self
107 }
108}
109
110#[inline]
112pub(crate) fn rect_to_recti(rect: Rect) -> RECT {
113 RECT {
114 left: rect.x0 as i32,
115 top: rect.y0 as i32,
116 right: rect.x1 as i32,
117 bottom: rect.y1 as i32,
118 }
119}
120
121#[inline]
123pub(crate) fn recti_to_rect(rect: RECT) -> Rect {
124 Rect::new(
125 rect.left as f64,
126 rect.top as f64,
127 rect.right as f64,
128 rect.bottom as f64,
129 )
130}
131
132pub(crate) fn region_to_rectis(region: &Region, scale: Scale) -> Vec<RECT> {
135 region
136 .rects()
137 .iter()
138 .map(|r| rect_to_recti(r.to_px(scale).round()))
139 .collect()
140}
141
142type GetDpiForSystem = unsafe extern "system" fn() -> UINT;
145type GetDpiForWindow = unsafe extern "system" fn(HWND) -> UINT;
146type SetProcessDpiAwarenessContext =
147 unsafe extern "system" fn(winapi::shared::windef::DPI_AWARENESS_CONTEXT) -> BOOL;
148type GetSystemMetricsForDpi =
149 unsafe extern "system" fn(winapi::ctypes::c_int, UINT) -> winapi::ctypes::c_int;
150type GetDpiForMonitor = unsafe extern "system" fn(HMONITOR, MONITOR_DPI_TYPE, *mut UINT, *mut UINT);
152type SetProcessDpiAwareness = unsafe extern "system" fn(PROCESS_DPI_AWARENESS) -> HRESULT;
153type DCompositionCreateDevice = unsafe extern "system" fn(
154 dxgiDevice: *const IDXGIDevice,
155 iid: REFIID,
156 dcompositionDevice: *mut *mut c_void,
157) -> HRESULT;
158
159#[allow(non_snake_case)] pub struct OptionalFunctions {
161 pub GetDpiForSystem: Option<GetDpiForSystem>,
162 pub GetDpiForWindow: Option<GetDpiForWindow>,
163 pub SetProcessDpiAwarenessContext: Option<SetProcessDpiAwarenessContext>,
164 pub GetDpiForMonitor: Option<GetDpiForMonitor>,
165 pub SetProcessDpiAwareness: Option<SetProcessDpiAwareness>,
166 pub GetSystemMetricsForDpi: Option<GetSystemMetricsForDpi>,
167 pub DCompositionCreateDevice: Option<DCompositionCreateDevice>,
168}
169
170#[allow(non_snake_case)] fn load_optional_functions() -> OptionalFunctions {
172 macro_rules! load_function {
176 ($lib: expr, $function: ident, $min_windows_version: expr) => {{
177 let name = stringify!($function);
178
179 let cstr = CString::new(name).unwrap();
182
183 let function_ptr = unsafe { GetProcAddress($lib, cstr.as_ptr()) };
184
185 if function_ptr.is_null() {
186 tracing::info!(
187 "Could not load `{}`. Windows {} or later is needed",
188 name,
189 $min_windows_version
190 );
191 } else {
192 let function = unsafe { mem::transmute::<_, $function>(function_ptr) };
193 $function = Some(function);
194 }
195 }};
196 }
197
198 fn load_library(name: &str) -> HMODULE {
199 let encoded_name = name.to_wide();
200
201 let library = unsafe { GetModuleHandleW(encoded_name.as_ptr()) };
204 if !library.is_null() {
205 return library;
206 }
207
208 unsafe { LoadLibraryW(encoded_name.as_ptr()) }
209 }
210
211 let shcore = load_library("shcore.dll");
212 let user32 = load_library("user32.dll");
213 let dcomp = load_library("dcomp.dll");
214
215 let mut GetDpiForSystem = None;
216 let mut GetDpiForMonitor = None;
217 let mut GetDpiForWindow = None;
218 let mut SetProcessDpiAwarenessContext = None;
219 let mut SetProcessDpiAwareness = None;
220 let mut GetSystemMetricsForDpi = None;
221 let mut DCompositionCreateDevice = None;
222
223 if shcore.is_null() {
224 tracing::info!("No shcore.dll");
225 } else {
226 load_function!(shcore, SetProcessDpiAwareness, "8.1");
227 load_function!(shcore, GetDpiForMonitor, "8.1");
228 }
229
230 if user32.is_null() {
231 tracing::info!("No user32.dll");
232 } else {
233 load_function!(user32, GetDpiForSystem, "10");
234 load_function!(user32, GetDpiForWindow, "10");
235 load_function!(user32, SetProcessDpiAwarenessContext, "10");
236 load_function!(user32, GetSystemMetricsForDpi, "10");
237 }
238
239 if dcomp.is_null() {
240 tracing::info!("No dcomp.dll");
241 } else {
242 load_function!(dcomp, DCompositionCreateDevice, "8.1");
243 }
244
245 OptionalFunctions {
246 GetDpiForSystem,
247 GetDpiForWindow,
248 SetProcessDpiAwarenessContext,
249 GetDpiForMonitor,
250 SetProcessDpiAwareness,
251 GetSystemMetricsForDpi,
252 DCompositionCreateDevice,
253 }
254}
255
256pub static OPTIONAL_FUNCTIONS: Lazy<OptionalFunctions> = Lazy::new(load_optional_functions);
257
258pub(crate) const CLASS_NAME: &str = "druid";
259
260#[macro_export]
262macro_rules! accel {
263 ( $( $fVirt:expr, $key:expr, $cmd:expr, )* ) => {
264 [
265 $(
266 ACCEL { fVirt: $fVirt | FVIRTKEY, key: $key as WORD, cmd: $cmd as WORD },
267 )*
268 ]
269 }
270}
271
272pub(crate) fn attach_console() {
276 unsafe {
277 let stdout = GetStdHandle(STD_OUTPUT_HANDLE);
278 if stdout != INVALID_HANDLE_VALUE && GetFileType(stdout) != FILE_TYPE_UNKNOWN {
279 return;
282 }
283
284 if AttachConsole(ATTACH_PARENT_PROCESS) > 0 {
285 let name = CString::new("CONOUT$").unwrap();
286 let chnd = CreateFileA(
287 name.as_ptr(),
288 GENERIC_READ | GENERIC_WRITE,
289 FILE_SHARE_WRITE,
290 ptr::null_mut(),
291 OPEN_EXISTING,
292 0,
293 ptr::null_mut(),
294 );
295
296 if chnd == INVALID_HANDLE_VALUE {
297 return;
299 }
300
301 SetStdHandle(STD_OUTPUT_HANDLE, chnd);
302 SetStdHandle(STD_ERROR_HANDLE, chnd);
303 }
304 }
305}