#[cfg(windows)]
use windows_sys::Win32::Foundation::ERROR_SUCCESS;
#[cfg(windows)]
use windows_sys::Win32::System::Registry::{HKEY_CURRENT_USER, RRF_RT_REG_BINARY, RegGetValueW};
#[cfg(windows)]
const ACCENT_KEY: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Accent";
#[cfg(windows)]
const ACCENT_PALETTE_VALUE: &str = "AccentPalette";
const ACCENT_PALETTE_BYTES: usize = 32;
const FLUENT_ACCENT_INDEXES: (usize, usize) = (4, 1);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Accent {
pub light: String,
pub dark: String,
}
#[cfg(windows)]
pub(super) fn read_accent() -> Option<Accent> {
let subkey = utf16_z(ACCENT_KEY);
let value = utf16_z(ACCENT_PALETTE_VALUE);
let mut palette = [0u8; ACCENT_PALETTE_BYTES];
let mut size = u32::try_from(palette.len()).ok()?;
let status = unsafe {
RegGetValueW(
HKEY_CURRENT_USER,
subkey.as_ptr(),
value.as_ptr(),
RRF_RT_REG_BINARY,
std::ptr::null_mut(),
palette.as_mut_ptr().cast(),
&mut size,
)
};
if status != ERROR_SUCCESS || usize::try_from(size).ok()? != ACCENT_PALETTE_BYTES {
return None;
}
fluent_accent(&palette)
}
#[cfg(windows)]
fn utf16_z(text: &str) -> Vec<u16> {
text.encode_utf16().chain(std::iter::once(0)).collect()
}
#[cfg_attr(not(any(windows, test)), allow(dead_code))]
pub(super) fn fluent_accent(palette: &[u8]) -> Option<Accent> {
if palette.len() != ACCENT_PALETTE_BYTES {
return None;
}
let color = |index: usize| {
let offset = index * 4;
format!(
"#{:02x}{:02x}{:02x}",
palette[offset],
palette[offset + 1],
palette[offset + 2]
)
};
Some(Accent {
light: color(FLUENT_ACCENT_INDEXES.0),
dark: color(FLUENT_ACCENT_INDEXES.1),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fluent_accent_uses_light_and_dark_palette_entries() {
let mut palette = [0u8; ACCENT_PALETTE_BYTES];
palette[16..20].copy_from_slice(&[0x12, 0x34, 0x56, 0xff]);
palette[4..8].copy_from_slice(&[0xab, 0xcd, 0xef, 0xff]);
let accent = fluent_accent(&palette).expect("32-byte palette is accepted");
assert_eq!(accent.light, "#123456");
assert_eq!(accent.dark, "#abcdef");
}
#[test]
fn fluent_accent_reads_the_default_blue_palette_windows_writes() {
let palette = [
0x99, 0xeb, 0xff, 0x00, 0x4c, 0xc2, 0xff, 0x00, 0x00, 0x91, 0xf8, 0x00, 0x00, 0x78,
0xd4, 0x00, 0x00, 0x67, 0xc0, 0x00, 0x00, 0x3e, 0x92, 0x00, 0x00, 0x1a, 0x68, 0x00,
0xf7, 0x63, 0x0c, 0x00,
];
let accent = fluent_accent(&palette).expect("32-byte palette is accepted");
assert_eq!(accent.light, "#0067c0");
assert_eq!(accent.dark, "#4cc2ff");
}
#[test]
fn fluent_accent_rejects_non_32_byte_palettes() {
let short = [0u8; ACCENT_PALETTE_BYTES - 1];
let long = [0u8; ACCENT_PALETTE_BYTES + 1];
assert!(fluent_accent(&short).is_none());
assert!(fluent_accent(&long).is_none());
}
}