use kui_core::{Color, Locale, MotionPref};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub(crate) struct Queried {
pub accent: Option<Color>,
pub motion: MotionPref,
pub locale: Option<Locale>,
}
pub(crate) fn query() -> Queried {
Queried {
accent: accent(),
motion: motion(),
locale: locale(),
}
}
#[cfg(target_os = "macos")]
fn accent() -> Option<Color> {
use objc2_app_kit::{NSColor, NSColorSpace};
let srgb =
NSColor::controlAccentColor().colorUsingColorSpace(&NSColorSpace::sRGBColorSpace())?;
let ch = |v: f64| (v.clamp(0.0, 1.0) * 255.0).round() as u8;
Some(Color::rgba8(
ch(srgb.redComponent()),
ch(srgb.greenComponent()),
ch(srgb.blueComponent()),
ch(srgb.alphaComponent()),
))
}
#[cfg(target_os = "macos")]
fn motion() -> MotionPref {
if objc2_app_kit::NSWorkspace::sharedWorkspace().accessibilityDisplayShouldReduceMotion() {
MotionPref::Reduced
} else {
MotionPref::Full
}
}
#[cfg(target_os = "macos")]
fn locale() -> Option<Locale> {
let preferred = objc2_foundation::NSLocale::preferredLanguages()
.firstObject()
.and_then(|tag| Locale::new(&tag.to_string()));
preferred.or_else(posix_locale_from_env)
}
#[cfg(target_os = "windows")]
fn accent() -> Option<Color> {
use windows_sys::Win32::Graphics::Dwm::DwmGetColorizationColor;
use windows_sys::Win32::System::Registry::{HKEY_CURRENT_USER, RRF_RT_REG_DWORD, RegGetValueW};
let wide = |s: &str| s.encode_utf16().chain([0]).collect::<Vec<u16>>();
let (key, value) = (wide(r"Software\Microsoft\Windows\DWM"), wide("AccentColor"));
let (mut abgr, mut size) = (0u32, 4u32);
let read = unsafe {
RegGetValueW(
HKEY_CURRENT_USER,
key.as_ptr(),
value.as_ptr(),
RRF_RT_REG_DWORD,
std::ptr::null_mut(),
(&raw mut abgr).cast(),
&raw mut size,
)
};
if read == 0 {
return Some(Color::rgb8(
abgr as u8,
(abgr >> 8) as u8,
(abgr >> 16) as u8,
));
}
let (mut argb, mut opaque) = (0u32, 0);
if unsafe { DwmGetColorizationColor(&raw mut argb, &raw mut opaque) } < 0 {
return None;
}
Some(Color::rgb8(
(argb >> 16) as u8,
(argb >> 8) as u8,
argb as u8,
))
}
#[cfg(target_os = "windows")]
fn motion() -> MotionPref {
use windows_sys::Win32::UI::WindowsAndMessaging::{
SPI_GETCLIENTAREAANIMATION, SystemParametersInfoW,
};
let mut animations = 0i32;
let ok = unsafe {
SystemParametersInfoW(
SPI_GETCLIENTAREAANIMATION,
0,
(&raw mut animations).cast(),
0,
)
};
if ok == 0 {
MotionPref::Unknown
} else if animations == 0 {
MotionPref::Reduced
} else {
MotionPref::Full
}
}
#[cfg(target_os = "windows")]
fn locale() -> Option<Locale> {
use windows_sys::Win32::Globalization::GetUserDefaultLocaleName;
const LOCALE_NAME_MAX_LENGTH: usize = 85;
let mut buf = [0u16; LOCALE_NAME_MAX_LENGTH];
let len = unsafe { GetUserDefaultLocaleName(buf.as_mut_ptr(), buf.len() as i32) };
if len <= 1 {
return None;
}
let tag = String::from_utf16_lossy(&buf[..len as usize - 1]);
Locale::new(&tag)
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn accent() -> Option<Color> {
None
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn motion() -> MotionPref {
MotionPref::Unknown
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn locale() -> Option<Locale> {
posix_locale_from_env()
}
#[cfg_attr(target_os = "windows", allow(dead_code))]
fn posix_locale_from_env() -> Option<Locale> {
let raw = ["LC_ALL", "LC_MESSAGES", "LANG"]
.into_iter()
.find_map(|k| std::env::var(k).ok().filter(|v| !v.is_empty()))?;
posix_locale(&raw)
}
fn posix_locale(raw: &str) -> Option<Locale> {
let tag = raw.split(['.', '@']).next()?.replace('_', "-");
if tag.is_empty() || tag == "C" || tag == "POSIX" {
return None;
}
Locale::new(&tag)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_posix_locale_becomes_a_language_tag() {
let tag = |s: &str| posix_locale(s).map(|l| l.as_str().to_string());
assert_eq!(tag("en_US.UTF-8").as_deref(), Some("en-US"));
assert_eq!(tag("pt_BR@euro").as_deref(), Some("pt-BR"));
assert_eq!(tag("en").as_deref(), Some("en"));
assert_eq!(tag("zh_Hant_HK.UTF-8").as_deref(), Some("zh-Hant-HK"));
assert_eq!(tag("C"), None);
assert_eq!(tag("POSIX"), None);
assert_eq!(tag(""), None);
assert_eq!(tag(".UTF-8"), None, "an encoding is not a language");
}
#[test]
fn the_query_answers_without_raising() {
let q = query();
if cfg!(any(target_os = "macos", target_os = "windows")) {
assert_ne!(q.motion, MotionPref::Unknown, "the OS knows this one");
assert!(q.accent.is_some(), "and this one");
} else {
assert_eq!(
q,
Queried {
accent: None,
motion: MotionPref::Unknown,
locale: q.locale,
}
);
}
}
}