1use 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 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 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"); 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, obj.as_mut_slice(),
283 &mut nb_returned,
284 );
285
286 if nb_returned == 0 {
287 return None; }
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 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 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 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 self.enumerator.take();
338 self.server_connection.take();
339 self.instance.take();
340 }
341}