Grayza
Grayza

Reputation: 393

RegGetValueW(), how to do it right

I'm creating a windows app that can change system color.

I saw this code, where we are accessing and reading from registry with RegGetValueW(). Then I copied and changed it a little with the help of compiler:

use windows::{Win32::System::Registry::RegGetValueW, core::PWSTR};
use winreg::enums::*;

pub fn is_light_theme() -> bool {
    // based on https://stackoverflow.com/a/51336913/709884
    let mut buffer: [u8; 4] = [0; 4];
    let mut cb_data: u32 = (buffer.len()).try_into().unwrap();
    let res = unsafe {
        RegGetValueW(
            HKEY_CURRENT_USER,
            w!(r#"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"#),
            w!("AppsUseLightTheme"),
            RRF_RT_REG_DWORD,
            Some(std::ptr::null_mut()),
            Some(buffer.as_mut_ptr() as _),
            Some(&mut cb_data as *mut u32),
        )
    };
    assert_eq!(
        res,
        ERROR_SUCCESS,
        format!("failed to read key from registry: err_code= {}", res).as_str(),
    );

    // REG_DWORD is signed 32-bit, using little endian
    let light_mode = i32::from_le_bytes(buffer) == 1;
    light_mode
}

pub fn is_dark_theme() -> bool {
    !is_light_theme()
}

// convert &str to Win32 PWSTR
#[derive(Default)]
pub struct WideString(pub Vec<u16>);

pub trait ToWide {
    fn to_wide(&self) -> WideString;
}

impl ToWide for &str {
    fn to_wide(&self) -> WideString {
        let mut result: Vec<u16> = self.encode_utf16().collect();
        result.push(0);
        WideString(result)
    }
}

impl ToWide for String {
    fn to_wide(&self) -> WideString {
        let mut result: Vec<u16> = self.encode_utf16().collect();
        result.push(0);
        WideString(result)
    }
}

impl WideString {
    pub fn as_pwstr(&self) -> PWSTR {
        PWSTR(self.0.as_ptr() as *mut _)
    }
}

From where is it getting HKEY_CURRENT_USER, RRF_RT_REG_DWORD, ERROR_SUCCESS?

I'm getting HKEY_CURRENT_USER with help of winreg crate, but I assume you can get HKEY_CURRENT_USER, RRF_RT_REG_DWORD from windows-rs directly, couldn't find this info.

I know that this code will not change system colors, reading theme value from registry is the closest target for me right now.

Upvotes: 0

Views: 274

Answers (1)

IInspectable
IInspectable

Reputation: 51394

The first question revolves around discoverability. The current situation isn't great and you will have to look up information in two places:

  1. The crate documentation, and
  2. The feature index.

The first resource answers the question of where to find any given symbol. Both HKEY_CURRENT_USER and RRF_RT_REG_DWORD are exported from the windows::Win32::System::Registry module. (I'd link to the documentation, but those links may no longer be correct when the next version is published1.)

ERROR_SUCCESS is exported from the windows::Win32::Foundation module, a foundational module used across the entire API.

Once you know which modules to use you have to make sure to enable the respective features. That's what the second resource is for. The only feature you need here is "Win32_System_Registry"; the windows::Win32::Foundation module is no longer feature-gated.

The second question is concerned with string representations. Rust's internal string representation is unfortunately alien to Windows' and work is required to bridge the impedance mismatch. The windows crate provides basic translation primitives. For string literals, you can use the w! macro that re-encodes the source string as UTF-16, appends a NUL character and returns a PCWSTR ready for use.

The following compiles and runs as expected:

main.rs

use windows::{
    core::w,
    Win32::{Foundation::*, System::Registry::*},
};

pub fn is_light_theme() -> bool {
    // based on https://stackoverflow.com/a/51336913/709884
    let mut buffer: [u8; 4] = [0; 4];
    let mut cb_data: u32 = (buffer.len()).try_into().unwrap();
    let res = unsafe {
        RegGetValueW(
            HKEY_CURRENT_USER,
            w!(r#"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"#),
            w!("AppsUseLightTheme"),
            RRF_RT_REG_DWORD,
            Some(std::ptr::null_mut()),
            Some(buffer.as_mut_ptr() as _),
            Some(&mut cb_data as *mut u32),
        )
    };
    assert_eq!(res, ERROR_SUCCESS);

    // REG_DWORD is signed 32-bit, using little endian
    let light_mode = i32::from_le_bytes(buffer) == 1;
    light_mode
}

fn main() {
    let light_theme = is_light_theme();
    println!("Light theme: {light_theme:?}");
}

Cargo.toml

[package]
name = "is_light_theme"
version = "0.0.0"
edition = "2021"

[dependencies.windows]
version = "0.54.0"
features = ["Win32_System_Registry"]

There's no need to use the winreg crate.

As an alternative, you could use the windows-registry instead. It is part of the same repository as the windows crate. This vastly cuts down on the amount of code you need to write.

main.rs

use windows_registry::*;

pub fn is_light_theme() -> bool {
    let val = CURRENT_USER
        .open(r#"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"#)
        .expect("Failed to get registry key")
        .get_u32("AppsUseLightTheme")
        .expect("Failed to get registry value");
    val != 0
}

fn main() {
    let light_theme = is_light_theme();
    println!("Light theme: {light_theme:?}");
}

Cargo.toml

[package]
name = "is_light_theme"
version = "0.0.0"
edition = "2021"

[dependencies]
windows-registry = { version = "0.1.0" }

1 Unfortunately, docs.rs is unable to handle the windows crate, and the crate maintainers are forced to entertain a custom hosting solution with a fire-and-forget data retention policy.

Upvotes: 0

Related Questions