is_dark_theme/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
//! Check whether the system is set to a "dark" theme.
//!
//! This checks a *global* system default. It is intended for headless tools or programs that can't access their windows.
//! It may not be accurate when specific screens or applications have a different theme set.
//! If you control your GUI, please check window-specific properties instead (on macOS that is `NSAppearance` protocol).
//!
//! On macOS this crate uses Core Foundation to read a `AppleInterfaceStyle` global setting, which is equivalent of:
//!
//! ```bash
//! defaults read -g AppleInterfaceStyle
//! ```
//!
//! On other platforms only `None` is returned. Please submit pull requests for more OSes!
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Theme {
/// Standard appearance (possibly light), or themes are not supported
Default,
/// The theme has a "Dark" appearance (light text on dark background)
Dark,
}
/// Reads the current system-wide default setting for application theme.
///
/// Returns `None` if the platform is not supported.
pub fn global_default_theme() -> Option<Theme> {
#[cfg(target_vendor = "apple")]
return crate::apple::get();
#[cfg(not(target_vendor = "apple"))]
return None;
}
#[cfg(target_vendor = "apple")]
mod apple {
use crate::Theme;
use core_foundation_sys::preferences::kCFPreferencesAnyApplication;
use core::ptr;
use core_foundation_sys::base::CFRelease;
use core_foundation_sys::base::kCFAllocatorNull;
use core_foundation_sys::preferences::CFPreferencesCopyAppValue;
use core_foundation_sys::string::CFStringCreateWithBytesNoCopy;
use core_foundation_sys::string::CFStringHasPrefix;
use core_foundation_sys::string::CFStringRef;
use core_foundation_sys::string::kCFStringEncodingUTF8;
fn static_cf_string(string: &'static str) -> CFStringRef {
unsafe {
CFStringCreateWithBytesNoCopy(
ptr::null_mut(),
string.as_ptr(),
string.len() as _,
kCFStringEncodingUTF8,
false as _,
kCFAllocatorNull,
)
}
}
pub(crate) fn get() -> Option<Theme> {
#[cfg(target_vendor = "apple")]
unsafe {
let interface_style = static_cf_string("AppleInterfaceStyle");
if interface_style.is_null() {
return None;
}
let dark = static_cf_string("Dark");
let value = CFPreferencesCopyAppValue(interface_style, kCFPreferencesAnyApplication);
let is_dark = !value.is_null() && !dark.is_null() && 0 != CFStringHasPrefix(value.cast(), dark);
CFRelease(dark.cast());
CFRelease(interface_style.cast());
Some(if is_dark { Theme::Dark } else { Theme::Default })
}
}
}
#[test]
fn test() {
#[cfg(target_vendor = "apple")]
assert!(global_default_theme().is_some());
global_default_theme();
}