wintheme/
lib.rs

1// SPDX-License-Identifier: BSL-1.0 OR Apache-2.0
2//               Copyright John Nunley, 2023.
3// Distributed under the Boost Software License, Version 1.0 or the Apache
4//                 License, Version 2.0.
5//       (See accompanying file LICENSE or copy at
6//         https://www.boost.org/LICENSE_1_0.txt)
7
8//! A brief wrapper around Windows themes in Rust.
9//!
10//! To start, create a [`Theme`].
11
12#![cfg(windows)]
13
14extern crate alloc;
15
16#[rustfmt::skip]
17mod auto;
18
19pub use auto::*;
20
21use core::fmt;
22use core::mem::MaybeUninit;
23use core::num::NonZeroIsize;
24use core::ptr::NonNull;
25use core::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
26
27use alloc::vec::Vec;
28
29use std::ffi::OsString;
30use std::os::windows::ffi::OsStringExt;
31use std::path::PathBuf;
32
33use windows_sys::w;
34
35use windows_sys::Win32::Foundation::{COLORREF, HINSTANCE, HWND, POINT, RECT, SIZE, S_OK};
36
37use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW;
38
39use windows_sys::Win32::Graphics::Gdi::{HBITMAP, LOGFONTW};
40
41use windows_sys::Win32::UI::WindowsAndMessaging::WNDCLASSEXW;
42use windows_sys::Win32::UI::WindowsAndMessaging::{
43    CloseWindow, CreateWindowExW, DefWindowProcW, RegisterClassExW,
44};
45
46use windows_sys::Win32::UI::Controls::{
47    CloseThemeData, GetThemeBitmap, GetThemeBool, GetThemeColor, GetThemeEnumValue,
48    GetThemeFilename, GetThemeFont, GetThemeInt, GetThemeIntList, GetThemeMargins,
49    GetThemePosition, GetThemeRect, GetThemeStream, GetThemeString, GetThemeSysBool,
50    GetThemeSysString, OpenThemeData,
51};
52use windows_sys::Win32::UI::Controls::{HTHEME, MARGINS};
53
54/// A color.
55#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
56pub struct Color(COLORREF);
57
58impl Color {
59    /// Create a new color.
60    pub const fn new(red: u8, green: u8, blue: u8) -> Self {
61        Self((red as u32) | ((green as u32) << 8) | ((blue as u32) << 16))
62    }
63
64    /// Get the red component.
65    pub fn red(&self) -> u8 {
66        (self.0 & 0xFF) as u8
67    }
68
69    /// Get the green component.
70    pub fn green(&self) -> u8 {
71        ((self.0 >> 8) & 0xFF) as u8
72    }
73
74    /// Get the blue component.
75    pub fn blue(&self) -> u8 {
76        ((self.0 >> 16) & 0xFF) as u8
77    }
78
79    /// Get the raw color.
80    pub fn raw(&self) -> COLORREF {
81        self.0
82    }
83
84    /// Create from a raw color.
85    pub const fn from_raw(raw: COLORREF) -> Self {
86        Self(raw)
87    }
88}
89
90impl fmt::Debug for Color {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        f.debug_struct("Color")
93            .field("red", &self.red())
94            .field("green", &self.green())
95            .field("blue", &self.blue())
96            .finish()
97    }
98}
99
100/// A font object.
101#[derive(Clone, Copy)]
102pub struct LogicalFont(LOGFONTW);
103
104impl LogicalFont {
105    /// Get the height of the font.
106    pub fn height(&self) -> i32 {
107        self.0.lfHeight
108    }
109
110    /// Get the width of the font.
111    pub fn width(&self) -> i32 {
112        self.0.lfWidth
113    }
114
115    /// Get the escapement of the font.
116    pub fn escapement(&self) -> i32 {
117        self.0.lfEscapement
118    }
119
120    /// Get the orientation of the font.
121    pub fn orientation(&self) -> i32 {
122        self.0.lfOrientation
123    }
124
125    /// Get the weight of the font.
126    pub fn weight(&self) -> i32 {
127        self.0.lfWeight
128    }
129
130    /// Get if the font is italic.
131    pub fn italic(&self) -> bool {
132        self.0.lfItalic != 0
133    }
134
135    /// Get if the font is underlined.
136    pub fn underline(&self) -> bool {
137        self.0.lfUnderline != 0
138    }
139
140    /// Get if the font is struck out.
141    pub fn strike_out(&self) -> bool {
142        self.0.lfStrikeOut != 0
143    }
144
145    /// Get the character set of the font.
146    pub fn char_set(&self) -> u8 {
147        self.0.lfCharSet
148    }
149
150    /// Get the output precision of the font.
151    pub fn output_precision(&self) -> OutputPrecision {
152        use windows_sys::Win32::Graphics::Gdi::OUT_DEFAULT_PRECIS;
153
154        match self.0.lfOutPrecision {
155            OUT_DEFAULT_PRECIS => OutputPrecision::Default,
156            _ => OutputPrecision::Unknown(self.0.lfOutPrecision),
157        }
158    }
159
160    /// Get the clip precision of the font.
161    pub fn clip_precision(&self) -> ClipPrecision {
162        use windows_sys::Win32::Graphics::Gdi::CLIP_DEFAULT_PRECIS;
163
164        match self.0.lfClipPrecision {
165            CLIP_DEFAULT_PRECIS => ClipPrecision::Default,
166            _ => ClipPrecision::Unknown(self.0.lfClipPrecision),
167        }
168    }
169
170    /// Get the output quality of the font.
171    pub fn output_quality(&self) -> OutputQuality {
172        use windows_sys::Win32::Graphics::Gdi::{
173            ANTIALIASED_QUALITY, CLEARTYPE_QUALITY, DEFAULT_QUALITY, DRAFT_QUALITY,
174        };
175
176        match self.0.lfQuality {
177            ANTIALIASED_QUALITY => OutputQuality::AntiAliased,
178            CLEARTYPE_QUALITY => OutputQuality::ClearType,
179            DEFAULT_QUALITY => OutputQuality::Default,
180            DRAFT_QUALITY => OutputQuality::Draft,
181            _ => panic!("Unknown output quality"),
182        }
183    }
184
185    /// The typeface name of the font.
186    pub fn typeface_name(&self) -> OsString {
187        let len = self.0.lfFaceName.iter().position(|&c| c == 0).unwrap();
188        OsString::from_wide(&self.0.lfFaceName[..len])
189    }
190}
191
192impl fmt::Debug for LogicalFont {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        f.debug_struct("LogicalFont")
195            .field("height", &self.height())
196            .field("width", &self.width())
197            .field("escapement", &self.escapement())
198            .field("orientation", &self.orientation())
199            .field("weight", &self.weight())
200            .field("italic", &self.italic())
201            .field("underline", &self.underline())
202            .field("strike_out", &self.strike_out())
203            .field("char_set", &self.char_set())
204            .field("output_precision", &self.output_precision())
205            .field("clip_precision", &self.clip_precision())
206            .field("output_quality", &self.output_quality())
207            .field("typeface_name", &self.typeface_name())
208            .finish()
209    }
210}
211
212/// The output precision of the font.
213#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
214#[non_exhaustive]
215pub enum OutputPrecision {
216    /// The default output precision.
217    Default,
218
219    /// Unknown.
220    Unknown(u8),
221}
222
223/// The clip precision of the font.
224#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
225#[non_exhaustive]
226pub enum ClipPrecision {
227    /// The default clip precision.
228    Default,
229
230    /// Unknown.
231    Unknown(u8),
232}
233
234/// The output quality.
235#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
236#[non_exhaustive]
237pub enum OutputQuality {
238    /// Quality is anti-aliased.
239    AntiAliased,
240
241    /// Quality is clear type.
242    ClearType,
243
244    /// Quality is default.
245    Default,
246
247    /// Quality is draft.
248    Draft,
249
250    /// Quality is proof.
251    Proof,
252
253    /// Quality is non-anti-aliased.
254    NonAntiAliased,
255}
256
257/// A handle to a bitmap.
258pub struct Bitmap {
259    /// The bitmap handle.
260    ///
261    /// This cannot be zero, so we use `NonZeroIsize` to ensure that.
262    /// Semantically, this is an `HBITMAP`.
263    handle: NonZeroIsize,
264}
265
266impl fmt::Debug for Bitmap {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        struct PtrWrapper(HBITMAP);
269
270        impl fmt::Debug for PtrWrapper {
271            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272                fmt::Pointer::fmt(&(self.0 as *const ()), f)
273            }
274        }
275
276        f.debug_tuple("Bitmap")
277            .field(&PtrWrapper(self.handle() as _))
278            .finish()
279    }
280}
281
282impl Bitmap {
283    /// Get the handle to the bitmap.
284    pub fn handle(&self) -> HBITMAP {
285        self.handle.get() as _
286    }
287}
288
289/// The margins of a window.
290#[derive(Clone, Copy)]
291pub struct Margins(MARGINS);
292
293impl Margins {
294    /// Get the left margin.
295    pub fn left(&self) -> i32 {
296        self.0.cxLeftWidth
297    }
298
299    /// Get the right margin.
300    pub fn right(&self) -> i32 {
301        self.0.cxRightWidth
302    }
303
304    /// Get the top margin.
305    pub fn top(&self) -> i32 {
306        self.0.cyTopHeight
307    }
308
309    /// Get the bottom margin.
310    pub fn bottom(&self) -> i32 {
311        self.0.cyBottomHeight
312    }
313}
314
315impl fmt::Debug for Margins {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        f.debug_struct("Margins")
318            .field("left", &self.left())
319            .field("right", &self.right())
320            .field("top", &self.top())
321            .field("bottom", &self.bottom())
322            .finish()
323    }
324}
325
326/// A point.
327#[derive(Clone, Copy)]
328pub struct Point(POINT);
329
330impl Point {
331    /// Get the x-coordinate.
332    pub fn x(&self) -> i32 {
333        self.0.x
334    }
335
336    /// Get the y-coordinate.
337    pub fn y(&self) -> i32 {
338        self.0.y
339    }
340}
341
342impl fmt::Debug for Point {
343    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344        f.debug_struct("Point")
345            .field("x", &self.x())
346            .field("y", &self.y())
347            .finish()
348    }
349}
350
351/// A rectangle.
352#[derive(Clone, Copy)]
353pub struct Rect(RECT);
354
355impl Rect {
356    /// Get the left coordinate.
357    pub fn left(&self) -> i32 {
358        self.0.left
359    }
360
361    /// Get the top coordinate.
362    pub fn top(&self) -> i32 {
363        self.0.top
364    }
365
366    /// Get the right coordinate.
367    pub fn right(&self) -> i32 {
368        self.0.right
369    }
370
371    /// Get the bottom coordinate.
372    pub fn bottom(&self) -> i32 {
373        self.0.bottom
374    }
375}
376
377impl fmt::Debug for Rect {
378    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379        f.debug_struct("Rect")
380            .field("left", &self.left())
381            .field("top", &self.top())
382            .field("right", &self.right())
383            .field("bottom", &self.bottom())
384            .finish()
385    }
386}
387
388/// The size of a window.
389#[derive(Clone, Copy)]
390pub struct Size(SIZE);
391
392impl Size {
393    /// Get the width.
394    pub fn width(&self) -> i32 {
395        self.0.cx
396    }
397
398    /// Get the height.
399    pub fn height(&self) -> i32 {
400        self.0.cy
401    }
402}
403
404impl fmt::Debug for Size {
405    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406        f.debug_struct("Size")
407            .field("width", &self.width())
408            .field("height", &self.height())
409            .finish()
410    }
411}
412
413/// A theme that can be applied to a window.
414pub struct Theme {
415    /// The theme handle.
416    ///
417    /// This cannot be zero, so we use `NonZeroIsize` to ensure that.
418    /// Semantically, this is an `HTHEME`.
419    handle: NonZeroIsize,
420}
421
422impl fmt::Debug for Theme {
423    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424        struct PtrWrapper(HTHEME);
425
426        impl fmt::Debug for PtrWrapper {
427            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428                fmt::Pointer::fmt(&(self.0 as *const ()), f)
429            }
430        }
431
432        f.debug_tuple("Theme")
433            .field(&PtrWrapper(self.handle() as _))
434            .finish()
435    }
436}
437
438impl Drop for Theme {
439    fn drop(&mut self) {
440        // SAFETY: `handle` is a valid `HTHEME`, and we need to drop it at
441        // the end of its lifetime.
442        unsafe {
443            CloseThemeData(self.handle());
444        }
445    }
446}
447
448type GetThemeBoolRet = Option<bool>;
449type GetThemeColorRet = Option<Color>;
450type GetThemeStreamRet = Option<NonNull<[u8]>>;
451type GetThemeEnumValueRet = Option<i32>;
452type GetThemeFilenameRet = Option<PathBuf>;
453type GetThemeFontRet = Option<LogicalFont>;
454type GetThemeBitmapRet = Option<Bitmap>;
455type GetThemeIntRet = Option<i32>;
456type GetThemeIntListRet = Option<Vec<i32>>;
457type GetThemeMarginsRet = Option<Margins>;
458type GetThemePositionRet = Option<Point>;
459type GetThemeRectRet = Option<Rect>;
460type GetThemeStringRet = Option<OsString>;
461
462impl Theme {
463    /// Get the `HTHEME`.
464    fn handle(&self) -> HTHEME {
465        self.handle.get() as HTHEME
466    }
467
468    /// Create a new theme from a window handle and a class name.
469    ///
470    /// # Safety
471    ///
472    /// `window` must be a valid `HWND`.
473    pub unsafe fn with_window(hwnd: HWND, class_name: &str) -> Option<Self> {
474        let mut name = class_name.encode_utf16().collect::<alloc::vec::Vec<_>>();
475        name.push(0u16);
476
477        let handle = OpenThemeData(hwnd, name.as_ptr());
478
479        NonZeroIsize::new(handle).map(|handle| Self { handle })
480    }
481
482    /// Create a new theme from a class name.
483    pub fn new(class_name: &str) -> Option<Self> {
484        let window = DummyWindow::new();
485
486        unsafe { Self::with_window(window.0, class_name) }
487    }
488
489    /// Get a theme boolean.
490    fn get_theme_bool(&self, part: Part, prop_id: u32) -> Option<bool> {
491        use windows_sys::Win32::Foundation::BOOL;
492        let mut return_value = MaybeUninit::<BOOL>::uninit();
493
494        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
495        let (part, state) = part.part_and_state();
496        let result = unsafe {
497            GetThemeBool(
498                self.handle(),
499                part,
500                state,
501                prop_id,
502                return_value.as_mut_ptr(),
503            )
504        };
505
506        if result == S_OK {
507            // SAFETY: `GetThemeBool` succeeded, so `return_value` is
508            // initialized.
509            let return_value = unsafe { return_value.assume_init() };
510            Some(return_value != 0)
511        } else {
512            None
513        }
514    }
515
516    /// Get a theme's system boolean.
517    fn get_theme_sys_bool(&self, prop_id: u32) -> Option<bool> {
518        // SAFETY: `handle` is a valid `HTHEME`.
519        let result = unsafe { GetThemeSysBool(self.handle(), prop_id) };
520
521        if result == S_OK {
522            Some(result != 0)
523        } else {
524            None
525        }
526    }
527
528    /// Get a theme color.
529    fn get_theme_color(&self, part: Part, prop_id: u32) -> Option<Color> {
530        let mut return_value = MaybeUninit::<COLORREF>::uninit();
531
532        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
533        let (part, state) = part.part_and_state();
534        let result = unsafe {
535            GetThemeColor(
536                self.handle(),
537                part,
538                state,
539                prop_id,
540                return_value.as_mut_ptr(),
541            )
542        };
543
544        if result == S_OK {
545            // SAFETY: `GetThemeColor` succeeded, so `return_value` is
546            // initialized.
547            let return_value = unsafe { return_value.assume_init() };
548            Some(Color::from_raw(return_value))
549        } else {
550            None
551        }
552    }
553
554    /// Get a theme stream.
555    fn get_theme_stream(&self, part: Part, prop_id: u32) -> Option<NonNull<[u8]>> {
556        let mut return_ptr = MaybeUninit::<*mut u8>::uninit();
557        let mut return_len = MaybeUninit::<u32>::uninit();
558
559        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
560        let (part, state) = part.part_and_state();
561        let result = unsafe {
562            GetThemeStream(
563                self.handle(),
564                part,
565                state,
566                prop_id as _,
567                return_ptr.as_mut_ptr() as _,
568                return_len.as_mut_ptr(),
569                0,
570            )
571        };
572
573        if result == S_OK {
574            let (return_ptr, return_len) = unsafe {
575                // SAFETY: `GetThemeStream` succeeded, so `return_ptr` and
576                // `return_len` are initialized.
577                (return_ptr.assume_init(), return_len.assume_init())
578            };
579
580            // SAFETY: `return_ptr` is a valid pointer to `return_len` bytes.
581            NonNull::new(return_ptr).map(|ptr| unsafe {
582                NonNull::new_unchecked(core::ptr::slice_from_raw_parts(
583                    ptr.as_ptr(),
584                    return_len as usize,
585                ) as _)
586            })
587        } else {
588            None
589        }
590    }
591
592    /// Gets a theme enum value.
593    fn get_theme_enum_value(&self, part: Part, prop_id: u32) -> Option<i32> {
594        let mut return_value = MaybeUninit::<i32>::uninit();
595
596        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
597        let (part, state) = part.part_and_state();
598        let result = unsafe {
599            GetThemeEnumValue(
600                self.handle(),
601                part,
602                state,
603                prop_id,
604                return_value.as_mut_ptr(),
605            )
606        };
607
608        if result == S_OK {
609            // SAFETY: `GetThemeEnumValue` succeeded, so `return_value` is
610            // initialized.
611            let return_value = unsafe { return_value.assume_init() };
612            Some(return_value)
613        } else {
614            None
615        }
616    }
617
618    /// Get a theme filename.
619    fn get_theme_filename(&self, part: Part, prop_id: u32) -> Option<PathBuf> {
620        // Should be long enough in most cases.
621        const MAX_FILENAME_LENGTH: usize = 256;
622        let mut return_value = [0u16; MAX_FILENAME_LENGTH];
623
624        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
625        let (part, state) = part.part_and_state();
626        let result = unsafe {
627            GetThemeFilename(
628                self.handle(),
629                part,
630                state,
631                prop_id,
632                return_value.as_mut_ptr() as _,
633                MAX_FILENAME_LENGTH as _,
634            )
635        };
636
637        if result == S_OK {
638            let filename = {
639                let len = return_value
640                    .iter()
641                    .position(|&c| c == 0)
642                    .unwrap_or(MAX_FILENAME_LENGTH);
643
644                OsString::from_wide(&return_value[..len])
645            };
646
647            Some(filename.into())
648        } else {
649            None
650        }
651    }
652
653    /// Get a theme font.
654    fn get_theme_font(&self, part: Part, prop_id: u32) -> Option<LogicalFont> {
655        let mut return_value = MaybeUninit::<LOGFONTW>::uninit();
656
657        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
658        let (part, state) = part.part_and_state();
659        let result = unsafe {
660            GetThemeFont(
661                self.handle(),
662                0,
663                part,
664                state,
665                prop_id as _,
666                return_value.as_mut_ptr(),
667            )
668        };
669
670        if result == S_OK {
671            // SAFETY: `GetThemeFont` succeeded, so `return_value` is
672            // initialized.
673            let return_value = unsafe { return_value.assume_init() };
674            Some(LogicalFont(return_value))
675        } else {
676            None
677        }
678    }
679
680    /// Get a theme bitmap.
681    fn get_theme_bitmap(&self, part: Part, prop_id: u32) -> Option<Bitmap> {
682        let mut return_value = MaybeUninit::<HBITMAP>::uninit();
683
684        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
685        let (part, state) = part.part_and_state();
686        let result = unsafe {
687            GetThemeBitmap(
688                self.handle(),
689                part,
690                state,
691                prop_id,
692                0,
693                return_value.as_mut_ptr(),
694            )
695        };
696
697        if result == S_OK {
698            // SAFETY: `GetThemeBitmap` succeeded, so `return_value` is
699            // initialized.
700            let return_value = unsafe { return_value.assume_init() };
701            NonZeroIsize::new(return_value).map(|handle| Bitmap { handle })
702        } else {
703            None
704        }
705    }
706
707    /// Get a theme int.
708    fn get_theme_int(&self, part: Part, prop_id: u32) -> Option<i32> {
709        let mut return_value = MaybeUninit::<i32>::uninit();
710
711        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
712        let (part, state) = part.part_and_state();
713        let result = unsafe {
714            GetThemeInt(
715                self.handle(),
716                part,
717                state,
718                prop_id,
719                return_value.as_mut_ptr(),
720            )
721        };
722
723        if result == S_OK {
724            // SAFETY: `GetThemeInt` succeeded, so `return_value` is
725            // initialized.
726            let return_value = unsafe { return_value.assume_init() };
727            Some(return_value)
728        } else {
729            None
730        }
731    }
732
733    /// Get ta theme int list.
734    fn get_theme_int_list(&self, part: Part, prop_id: u32) -> Option<Vec<i32>> {
735        use windows_sys::Win32::UI::Controls::INTLIST;
736
737        let mut return_list = MaybeUninit::<INTLIST>::uninit();
738
739        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
740        let (part, state) = part.part_and_state();
741        let result = unsafe {
742            GetThemeIntList(
743                self.handle(),
744                part,
745                state,
746                prop_id,
747                return_list.as_mut_ptr(),
748            )
749        };
750
751        if result == S_OK {
752            // SAFETY: `GetThemeIntList` succeeded
753            let return_list = unsafe { return_list.assume_init() };
754            let ints = &return_list.iValues[..return_list.iValueCount as _];
755            Some(ints.to_vec())
756        } else {
757            None
758        }
759    }
760
761    /// Get the margins for a theme.
762    fn get_theme_margins(&self, part: Part, prop_id: u32) -> Option<Margins> {
763        let mut return_value = MaybeUninit::<MARGINS>::uninit();
764
765        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
766        let (part, state) = part.part_and_state();
767        let result = unsafe {
768            GetThemeMargins(
769                self.handle(),
770                0,
771                part,
772                state,
773                prop_id,
774                std::ptr::null(),
775                return_value.as_mut_ptr(),
776            )
777        };
778
779        if result == S_OK {
780            // SAFETY: `GetThemeMargins` succeeded, so `return_value` is
781            // initialized.
782            let return_value = unsafe { return_value.assume_init() };
783            Some(Margins(return_value))
784        } else {
785            None
786        }
787    }
788
789    /// Get the position of a theme part.
790    fn get_theme_position(&self, part: Part, prop_id: u32) -> Option<Point> {
791        let mut return_value = MaybeUninit::<POINT>::uninit();
792
793        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
794        let (part, state) = part.part_and_state();
795        let result = unsafe {
796            GetThemePosition(
797                self.handle(),
798                part,
799                state,
800                prop_id,
801                return_value.as_mut_ptr(),
802            )
803        };
804
805        if result == S_OK {
806            // SAFETY: `GetThemePosition` succeeded, so `return_value` is
807            // initialized.
808            let return_value = unsafe { return_value.assume_init() };
809            Some(Point(return_value))
810        } else {
811            None
812        }
813    }
814
815    /// Get the rectangle of a theme part.
816    fn get_theme_rect(&self, part: Part, prop_id: u32) -> Option<Rect> {
817        let mut return_value = MaybeUninit::<RECT>::uninit();
818
819        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
820        let (part, state) = part.part_and_state();
821        let result = unsafe {
822            GetThemeRect(
823                self.handle(),
824                part,
825                state,
826                prop_id as _,
827                return_value.as_mut_ptr(),
828            )
829        };
830
831        if result == S_OK {
832            // SAFETY: `GetThemeRect` succeeded, so `return_value` is
833            // initialized.
834            let return_value = unsafe { return_value.assume_init() };
835            Some(Rect(return_value))
836        } else {
837            None
838        }
839    }
840
841    /// Get a theme string.
842    fn get_theme_string(&self, part: Part, prop_id: u32) -> Option<OsString> {
843        const MAX_STRING_LENGTH: usize = 1024;
844        let mut return_value = MaybeUninit::<[u16; MAX_STRING_LENGTH]>::uninit();
845
846        // SAFETY: `handle` is a valid `HTHEME`, `part` is a valid `Part`.
847        let (part, state) = part.part_and_state();
848        let result = unsafe {
849            GetThemeString(
850                self.handle(),
851                part,
852                state,
853                prop_id as _,
854                return_value.as_mut_ptr() as _,
855                MAX_STRING_LENGTH as _,
856            )
857        };
858
859        if result == S_OK {
860            // SAFETY: `GetThemeString` succeeded, so `return_value` is
861            // initialized.
862            let return_value = unsafe { return_value.assume_init() };
863            let slice = {
864                let len = return_value
865                    .iter()
866                    .position(|&c| c == 0)
867                    .unwrap_or(MAX_STRING_LENGTH);
868
869                &return_value[..len]
870            };
871
872            Some(OsString::from_wide(slice))
873        } else {
874            None
875        }
876    }
877
878    /// Get a theme system string.
879    fn get_theme_sys_string(&self, prop_id: u32) -> Option<OsString> {
880        const MAX_STRING_LENGTH: usize = 1024;
881        let mut return_value = MaybeUninit::<[u16; MAX_STRING_LENGTH]>::uninit();
882
883        // SAFETY: `handle` is a valid `HTHEME`.
884        let result = unsafe {
885            GetThemeSysString(
886                self.handle(),
887                prop_id as _,
888                return_value.as_mut_ptr() as _,
889                MAX_STRING_LENGTH as _,
890            )
891        };
892
893        if result == S_OK {
894            // SAFETY: `GetThemeSysString` succeeded, so `return_value` is
895            // initialized.
896            let return_value = unsafe { return_value.assume_init() };
897            let slice = {
898                let len = return_value
899                    .iter()
900                    .position(|&c| c == 0)
901                    .unwrap_or(MAX_STRING_LENGTH);
902
903                &return_value[..len]
904            };
905
906            Some(OsString::from_wide(slice))
907        } else {
908            None
909        }
910    }
911}
912
913/// A dummy window for getting a theme.
914struct DummyWindow(HWND);
915
916impl Drop for DummyWindow {
917    fn drop(&mut self) {
918        // SAFETY: `self.0` is a valid `HWND`.
919        unsafe {
920            CloseWindow(self.0);
921        }
922    }
923}
924
925impl DummyWindow {
926    /// Create a new dummy window.
927    fn new() -> Self {
928        static CLASS_CREATED: AtomicBool = AtomicBool::new(false);
929        const CLASS_NAME: *const u16 = w!("notgull::wintheme::DummyWindow");
930
931        // If the class hasn't been registered yet, register it.
932        if !CLASS_CREATED.load(Ordering::Relaxed) {
933            let class = WNDCLASSEXW {
934                cbSize: core::mem::size_of::<WNDCLASSEXW>() as u32,
935                style: 0,
936                lpfnWndProc: Some(DefWindowProcW),
937                lpszClassName: CLASS_NAME,
938                ..unsafe { core::mem::zeroed() }
939            };
940
941            // SAFETY: `CLASS_NAME` is a valid pointer to a null-terminated
942            // string, and `class` is a valid `WNDCLASSEXW`.
943            unsafe {
944                RegisterClassExW(&class);
945            }
946
947            CLASS_CREATED.store(true, Ordering::Release);
948        }
949
950        // Create a window from the class.
951        let hwnd = unsafe {
952            CreateWindowExW(
953                0,
954                CLASS_NAME,
955                CLASS_NAME,
956                0,
957                0,
958                0,
959                1,
960                1,
961                0,
962                0,
963                instance(),
964                core::ptr::null(),
965            )
966        };
967
968        if hwnd == 0 {
969            panic!("CreateWindowExW failed");
970        }
971
972        Self(hwnd)
973    }
974}
975
976fn instance() -> HINSTANCE {
977    static INSTANCE: AtomicIsize = AtomicIsize::new(0);
978
979    let instance = INSTANCE.load(Ordering::Relaxed);
980    if instance != 0 {
981        return instance;
982    }
983
984    // Load the current instance handle.
985    let handle = unsafe { GetModuleHandleW(core::ptr::null()) };
986
987    if handle == 0 {
988        panic!("GetModuleHandleW failed");
989    }
990
991    // Install it in our cached variable.
992    INSTANCE
993        .compare_exchange(instance, handle, Ordering::SeqCst, Ordering::SeqCst)
994        .unwrap_or_else(|x| x)
995}