sysinfo/windows/
component.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::Component;
4
5use windows::core::{w, VARIANT};
6use windows::Win32::Foundation::{SysAllocString, SysFreeString};
7use windows::Win32::Security::PSECURITY_DESCRIPTOR;
8use windows::Win32::System::Com::{
9    CoCreateInstance, CoInitializeEx, CoInitializeSecurity, CoSetProxyBlanket,
10    CLSCTX_INPROC_SERVER, EOAC_NONE, RPC_C_AUTHN_LEVEL_CALL, RPC_C_AUTHN_LEVEL_DEFAULT,
11    RPC_C_IMP_LEVEL_IMPERSONATE,
12};
13use windows::Win32::System::Rpc::{RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE};
14use windows::Win32::System::Variant::VariantClear;
15use windows::Win32::System::Wmi::{
16    IEnumWbemClassObject, IWbemLocator, IWbemServices, WbemLocator, WBEM_FLAG_FORWARD_ONLY,
17    WBEM_FLAG_NONSYSTEM_ONLY, WBEM_FLAG_RETURN_IMMEDIATELY, WBEM_INFINITE,
18};
19
20use std::cell::OnceCell;
21use std::sync::OnceLock;
22
23pub(crate) struct ComponentInner {
24    temperature: f32,
25    max: f32,
26    critical: Option<f32>,
27    label: String,
28    connection: Option<Connection>,
29    pub(crate) updated: bool,
30}
31
32impl ComponentInner {
33    /// Creates a new `ComponentInner` with the given information.
34    fn new() -> Option<Self> {
35        let mut c = Connection::new()
36            .and_then(|x| x.create_instance())
37            .and_then(|x| x.connect_server())
38            .and_then(|x| x.set_proxy_blanket())
39            .and_then(|x| x.exec_query())?;
40
41        c.temperature(true)
42            .map(|(temperature, critical)| ComponentInner {
43                temperature,
44                label: "Computer".to_owned(),
45                max: temperature,
46                critical,
47                connection: Some(c),
48                updated: true,
49            })
50    }
51
52    pub(crate) fn temperature(&self) -> Option<f32> {
53        Some(self.temperature)
54    }
55
56    pub(crate) fn max(&self) -> Option<f32> {
57        Some(self.max)
58    }
59
60    pub(crate) fn critical(&self) -> Option<f32> {
61        self.critical
62    }
63
64    pub(crate) fn label(&self) -> &str {
65        &self.label
66    }
67
68    pub(crate) fn refresh(&mut self) {
69        if self.connection.is_none() {
70            self.connection = Connection::new()
71                .and_then(|x| x.create_instance())
72                .and_then(|x| x.connect_server())
73                .and_then(|x| x.set_proxy_blanket());
74        }
75        self.connection = if let Some(x) = self.connection.take() {
76            x.exec_query()
77        } else {
78            None
79        };
80        if let Some(ref mut connection) = self.connection {
81            if let Some((temperature, _)) = connection.temperature(false) {
82                self.temperature = temperature;
83                if self.temperature > self.max {
84                    self.max = self.temperature;
85                }
86            }
87        }
88    }
89}
90
91pub(crate) struct ComponentsInner {
92    pub(crate) components: Vec<Component>,
93}
94
95impl ComponentsInner {
96    pub(crate) fn new() -> Self {
97        Self {
98            components: Vec::new(),
99        }
100    }
101
102    pub(crate) fn from_vec(components: Vec<Component>) -> Self {
103        Self { components }
104    }
105
106    pub(crate) fn into_vec(self) -> Vec<Component> {
107        self.components
108    }
109
110    pub(crate) fn list(&self) -> &[Component] {
111        &self.components
112    }
113
114    pub(crate) fn list_mut(&mut self) -> &mut [Component] {
115        &mut self.components
116    }
117
118    pub(crate) fn refresh(&mut self) {
119        if self.components.is_empty() {
120            self.components = match ComponentInner::new() {
121                Some(c) => vec![Component { inner: c }],
122                None => Vec::new(),
123            };
124        } else {
125            // There should always be only one here but just in case...
126            for c in self.components.iter_mut() {
127                c.refresh();
128                c.inner.updated = true;
129            }
130        }
131    }
132}
133
134macro_rules! bstr {
135    ($x:literal) => {{
136        SysAllocString(w!($x))
137    }};
138}
139
140struct Connection {
141    instance: Option<IWbemLocator>,
142    server_connection: Option<IWbemServices>,
143    enumerator: Option<IEnumWbemClassObject>,
144}
145
146#[allow(clippy::non_send_fields_in_send_ty)]
147unsafe impl Send for Connection {}
148unsafe impl Sync for Connection {}
149
150static SECURITY: OnceLock<Result<(), ()>> = OnceLock::new();
151thread_local! {
152    pub static CONNECTION: OnceCell<Result<(), ()>> = const { OnceCell::new() };
153}
154
155unsafe fn initialize_connection() -> Result<(), ()> {
156    if CoInitializeEx(None, Default::default()).is_err() {
157        sysinfo_debug!("Failed to initialize connection");
158        Err(())
159    } else {
160        Ok(())
161    }
162}
163
164unsafe fn initialize_security() -> Result<(), ()> {
165    if CoInitializeSecurity(
166        PSECURITY_DESCRIPTOR::default(),
167        -1,
168        None,
169        None,
170        RPC_C_AUTHN_LEVEL_DEFAULT,
171        RPC_C_IMP_LEVEL_IMPERSONATE,
172        None,
173        EOAC_NONE,
174        None,
175    )
176    .is_err()
177    {
178        sysinfo_debug!("Failed to initialize security");
179        Err(())
180    } else {
181        Ok(())
182    }
183}
184
185impl Connection {
186    #[allow(clippy::unnecessary_wraps)]
187    fn new() -> Option<Connection> {
188        if CONNECTION
189            .with(|x| *x.get_or_init(|| unsafe { initialize_connection() }))
190            .is_err()
191            || SECURITY
192                .get_or_init(|| unsafe { initialize_security() })
193                .is_err()
194        {
195            return None;
196        }
197        Some(Connection {
198            instance: None,
199            server_connection: None,
200            enumerator: None,
201        })
202    }
203
204    fn create_instance(mut self) -> Option<Connection> {
205        let instance =
206            unsafe { CoCreateInstance(&WbemLocator, None, CLSCTX_INPROC_SERVER) }.ok()?;
207        self.instance = Some(instance);
208        Some(self)
209    }
210
211    fn connect_server(mut self) -> Option<Connection> {
212        let instance = self.instance.as_ref()?;
213        let svc = unsafe {
214            let s = bstr!("root\\WMI");
215            let res = instance.ConnectServer(
216                &s,
217                &Default::default(),
218                &Default::default(),
219                &Default::default(),
220                0,
221                &Default::default(),
222                None,
223            );
224            SysFreeString(&s);
225            res
226        }
227        .ok()?;
228
229        self.server_connection = Some(svc);
230        Some(self)
231    }
232
233    fn set_proxy_blanket(self) -> Option<Connection> {
234        unsafe {
235            CoSetProxyBlanket(
236                self.server_connection.as_ref()?,
237                RPC_C_AUTHN_WINNT,
238                RPC_C_AUTHZ_NONE,
239                None,
240                RPC_C_AUTHN_LEVEL_CALL,
241                RPC_C_IMP_LEVEL_IMPERSONATE,
242                None,
243                EOAC_NONE,
244            )
245        }
246        .ok()?;
247
248        Some(self)
249    }
250
251    fn exec_query(mut self) -> Option<Connection> {
252        let server_connection = self.server_connection.as_ref()?;
253
254        let enumerator = unsafe {
255            let s = bstr!("WQL"); // query kind
256            let query = bstr!("SELECT * FROM MSAcpi_ThermalZoneTemperature");
257            let hres = server_connection.ExecQuery(
258                &s,
259                &query,
260                WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
261                None,
262            );
263            SysFreeString(&s);
264            SysFreeString(&query);
265            hres
266        }
267        .ok()?;
268
269        self.enumerator = Some(enumerator);
270        Some(self)
271    }
272
273    fn temperature(&mut self, get_critical: bool) -> Option<(f32, Option<f32>)> {
274        let enumerator = self.enumerator.take()?;
275
276        let mut nb_returned = 0;
277        let mut obj = [None; 1];
278
279        unsafe {
280            let _r = enumerator.Next(
281                WBEM_INFINITE, // Time out
282                obj.as_mut_slice(),
283                &mut nb_returned,
284            );
285
286            if nb_returned == 0 {
287                return None; // not enough rights I suppose...
288            }
289
290            let class_obj = match &mut obj {
291                [Some(co)] => co,
292                _ => return None,
293            };
294
295            let _r = class_obj.BeginEnumeration(WBEM_FLAG_NONSYSTEM_ONLY.0);
296
297            let mut variant = std::mem::MaybeUninit::<VARIANT>::uninit();
298            // `Get` only initializes the variant if it succeeds, early returning is not a problem
299            //
300            // <https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemclassobject-get>
301            class_obj
302                .Get(
303                    w!("CurrentTemperature"),
304                    0,
305                    variant.as_mut_ptr(),
306                    None,
307                    None,
308                )
309                .ok()?;
310
311            let mut variant = variant.assume_init();
312
313            // temperature is given in tenth of degrees Kelvin
314            let temp = (variant.as_raw().Anonymous.decVal.Anonymous2.Lo64 / 10) as f32 - 273.15;
315            let _r = VariantClear(&mut variant);
316
317            let mut critical = None;
318            if get_critical {
319                class_obj
320                    .Get(w!("CriticalTripPoint"), 0, &mut variant, None, None)
321                    .ok()?;
322
323                // temperature is given in tenth of degrees Kelvin
324                critical =
325                    Some((variant.as_raw().Anonymous.decVal.Anonymous2.Lo64 / 10) as f32 - 273.15);
326                let _r = VariantClear(&mut variant);
327            }
328
329            Some((temp, critical))
330        }
331    }
332}
333
334impl Drop for Connection {
335    fn drop(&mut self) {
336        // Those three calls are here to enforce that they get dropped in the good order.
337        self.enumerator.take();
338        self.server_connection.take();
339        self.instance.take();
340    }
341}