1 // WhoAmI
2 // Copyright © 2017-2021 Jeron Aldaron Lau.
3 //
4 // Licensed under any of:
5 // - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
6 // - MIT License (https://mit-license.org/)
7 // - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt)
8 // At your choosing (See accompanying files LICENSE_APACHE_2_0.txt,
9 // LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt).
10
11 use crate::{DesktopEnv, Platform};
12
13 use std::convert::TryInto;
14 use std::ffi::OsString;
15 use std::os::raw::{c_char, c_int, c_uchar, c_ulong, c_ushort};
16 use std::os::windows::ffi::OsStringExt;
17 use std::ptr;
18
19 #[repr(C)]
20 struct OsVersionInfoEx {
21 os_version_info_size: c_ulong,
22 major_version: c_ulong,
23 minor_version: c_ulong,
24 build_number: c_ulong,
25 platform_id: c_ulong,
26 sz_csd_version: [u16; 128],
27 service_pack_major: c_ushort,
28 service_pack_minor: c_ushort,
29 suite_mask: c_ushort,
30 product_type: c_uchar,
31 reserved: c_uchar,
32 }
33
34 #[allow(unused)]
35 #[repr(C)]
36 enum ExtendedNameFormat {
37 Unknown, // Nothing
38 FullyQualifiedDN, // Nothing
39 SamCompatible, // Hostname Followed By Username
40 Display, // Full Name
41 UniqueId, // Nothing
42 Canonical, // Nothing
43 UserPrincipal, // Nothing
44 CanonicalEx, // Nothing
45 ServicePrincipal, // Nothing
46 DnsDomain, // Nothing
47 GivenName, // Nothing
48 Surname, // Nothing
49 }
50
51 #[allow(unused)]
52 #[repr(C)]
53 enum ComputerNameFormat {
54 NetBIOS, // Same as GetComputerNameW
55 DnsHostname, // Fancy Name
56 DnsDomain, // Nothing
57 DnsFullyQualified, // Fancy Name with, for example, .com
58 PhysicalNetBIOS, // Same as GetComputerNameW
59 PhysicalDnsHostname, // Same as GetComputerNameW
60 PhysicalDnsDomain, // Nothing
61 PhysicalDnsFullyQualified, // Fancy Name with, for example, .com
62 Max,
63 }
64
65 #[link(name = "secur32")]
66 extern "system" {
GetLastError() -> c_ulong67 fn GetLastError() -> c_ulong;
GetUserNameExW( a: ExtendedNameFormat, b: *mut c_char, c: *mut c_ulong, ) -> c_uchar68 fn GetUserNameExW(
69 a: ExtendedNameFormat,
70 b: *mut c_char,
71 c: *mut c_ulong,
72 ) -> c_uchar;
GetUserNameW(a: *mut c_char, b: *mut c_ulong) -> c_int73 fn GetUserNameW(a: *mut c_char, b: *mut c_ulong) -> c_int;
GetComputerNameExW( a: ComputerNameFormat, b: *mut c_char, c: *mut c_ulong, ) -> c_int74 fn GetComputerNameExW(
75 a: ComputerNameFormat,
76 b: *mut c_char,
77 c: *mut c_ulong,
78 ) -> c_int;
79 }
80
81 #[link(name = "ntdll")]
82 extern "system" {
RtlGetVersion(a: *mut OsVersionInfoEx) -> u3283 fn RtlGetVersion(a: *mut OsVersionInfoEx) -> u32;
84 }
85
86 #[link(name = "kernel32")]
87 extern "system" {
GetUserPreferredUILanguages( dw_flags: c_ulong, pul_num_languages: *mut c_ulong, pwsz_languages_buffer: *mut u16, pcch_languages_buffer: *mut c_ulong, ) -> c_int88 fn GetUserPreferredUILanguages(
89 dw_flags: c_ulong,
90 pul_num_languages: *mut c_ulong,
91 pwsz_languages_buffer: *mut u16,
92 pcch_languages_buffer: *mut c_ulong,
93 ) -> c_int;
94 }
95
96 // Convert an OsString into a String
string_from_os(string: OsString) -> String97 fn string_from_os(string: OsString) -> String {
98 match string.into_string() {
99 Ok(string) => string,
100 Err(string) => string.to_string_lossy().to_string(),
101 }
102 }
103
username() -> String104 pub fn username() -> String {
105 string_from_os(username_os())
106 }
107
username_os() -> OsString108 pub fn username_os() -> OsString {
109 // Step 1. Retreive the entire length of the username
110 let mut size = 0;
111 let fail = unsafe {
112 // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER
113 GetUserNameW(ptr::null_mut(), &mut size) == 0
114 };
115 debug_assert_eq!(fail, true);
116
117 // Step 2. Allocate memory to put the Windows (UTF-16) string.
118 let mut name: Vec<u16> = Vec::with_capacity(size.try_into().unwrap());
119 let orig_size = size;
120 let fail =
121 unsafe { GetUserNameW(name.as_mut_ptr().cast(), &mut size) == 0 };
122 if fail {
123 panic!(
124 "Failed to get username: {}, report at https://github.com/libcala/whoami/issues",
125 unsafe { GetLastError() }
126 );
127 }
128 debug_assert_eq!(orig_size, size);
129 unsafe {
130 name.set_len(size.try_into().unwrap());
131 }
132 let terminator = name.pop(); // Remove Trailing Null
133 debug_assert_eq!(terminator, Some(0u16));
134
135 // Step 3. Convert to Rust String
136 OsString::from_wide(&name)
137 }
138
139 #[inline(always)]
realname() -> String140 pub fn realname() -> String {
141 string_from_os(realname_os())
142 }
143
144 #[inline(always)]
realname_os() -> OsString145 pub fn realname_os() -> OsString {
146 // Step 1. Retrieve the entire length of the username
147 let mut buf_size = 0;
148 let fail = unsafe {
149 GetUserNameExW(
150 ExtendedNameFormat::Display,
151 ptr::null_mut(),
152 &mut buf_size,
153 ) == 0
154 };
155 debug_assert_eq!(fail, true);
156 match unsafe { GetLastError() } {
157 0x00EA /* more data */ => { /* Success, continue */ }
158 0x054B /* no such domain */ => {
159 // If domain controller over the network can't be contacted, return
160 // "Unknown".
161 return "Unknown".into()
162 }
163 0x0534 /* none mapped */ => {
164 // Fallback to username
165 return username_os();
166 }
167 u => {
168 eprintln!("Unknown error code: {}, report at https://github.com/libcala/whoami/issues", u);
169 unreachable!();
170 }
171 }
172
173 // Step 2. Allocate memory to put the Windows (UTF-16) string.
174 let mut name: Vec<u16> = Vec::with_capacity(buf_size.try_into().unwrap());
175 let mut name_len = buf_size;
176 let fail = unsafe {
177 GetUserNameExW(
178 ExtendedNameFormat::Display,
179 name.as_mut_ptr().cast(),
180 &mut name_len,
181 ) == 0
182 };
183 if fail {
184 panic!(
185 "Failed to get username: {}, report at https://github.com/libcala/whoami/issues",
186 unsafe { GetLastError() }
187 );
188 }
189 debug_assert_eq!(buf_size, name_len + 1);
190 unsafe {
191 name.set_len(name_len.try_into().unwrap());
192 }
193
194 // Step 3. Convert to Rust String
195 OsString::from_wide(&name)
196 }
197
198 #[inline(always)]
devicename() -> String199 pub fn devicename() -> String {
200 string_from_os(devicename_os())
201 }
202
203 #[inline(always)]
devicename_os() -> OsString204 pub fn devicename_os() -> OsString {
205 // Step 1. Retreive the entire length of the username
206 let mut size = 0;
207 let fail = unsafe {
208 // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER
209 GetComputerNameExW(
210 ComputerNameFormat::DnsHostname,
211 ptr::null_mut(),
212 &mut size,
213 ) == 0
214 };
215 debug_assert_eq!(fail, true);
216
217 // Step 2. Allocate memory to put the Windows (UTF-16) string.
218 let mut name: Vec<u16> = Vec::with_capacity(size.try_into().unwrap());
219 let fail = unsafe {
220 GetComputerNameExW(
221 ComputerNameFormat::DnsHostname,
222 name.as_mut_ptr().cast(),
223 &mut size,
224 ) == 0
225 };
226 if fail {
227 panic!(
228 "Failed to get computer name: {}, report at https://github.com/libcala/whoami/issues",
229 unsafe { GetLastError() }
230 );
231 }
232 unsafe {
233 name.set_len(size.try_into().unwrap());
234 }
235
236 // Step 3. Convert to Rust String
237 OsString::from_wide(&name)
238 }
239
hostname() -> String240 pub fn hostname() -> String {
241 string_from_os(hostname_os())
242 }
243
hostname_os() -> OsString244 pub fn hostname_os() -> OsString {
245 // Step 1. Retreive the entire length of the username
246 let mut size = 0;
247 let fail = unsafe {
248 // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER
249 GetComputerNameExW(
250 ComputerNameFormat::NetBIOS,
251 ptr::null_mut(),
252 &mut size,
253 ) == 0
254 };
255 debug_assert_eq!(fail, true);
256
257 // Step 2. Allocate memory to put the Windows (UTF-16) string.
258 let mut name: Vec<u16> = Vec::with_capacity(size.try_into().unwrap());
259 let fail = unsafe {
260 GetComputerNameExW(
261 ComputerNameFormat::NetBIOS,
262 name.as_mut_ptr().cast(),
263 &mut size,
264 ) == 0
265 };
266 if fail {
267 panic!(
268 "Failed to get computer name: {}, report at https://github.com/libcala/whoami/issues",
269 unsafe { GetLastError() }
270 );
271 }
272 unsafe {
273 name.set_len(size.try_into().unwrap());
274 }
275
276 // Step 3. Convert to Rust String
277 OsString::from_wide(&name)
278 }
279
distro_os() -> Option<OsString>280 pub fn distro_os() -> Option<OsString> {
281 distro().map(|a| a.into())
282 }
283
distro() -> Option<String>284 pub fn distro() -> Option<String> {
285 let mut version = std::mem::MaybeUninit::<OsVersionInfoEx>::zeroed();
286
287 let version = unsafe {
288 (*version.as_mut_ptr()).os_version_info_size =
289 std::mem::size_of::<OsVersionInfoEx>() as u32;
290 RtlGetVersion(version.as_mut_ptr());
291 version.assume_init()
292 };
293
294 let product = match version.product_type {
295 1 => "Workstation",
296 2 => "Domain Controller",
297 3 => "Server",
298 _ => "Unknown",
299 };
300
301 let out = format!(
302 "Windows {}.{}.{} ({})",
303 version.major_version,
304 version.minor_version,
305 version.build_number,
306 product
307 );
308
309 Some(out)
310 }
311
312 #[inline(always)]
desktop_env() -> DesktopEnv313 pub const fn desktop_env() -> DesktopEnv {
314 DesktopEnv::Windows
315 }
316
317 #[inline(always)]
platform() -> Platform318 pub const fn platform() -> Platform {
319 Platform::Windows
320 }
321
322 struct LangIter {
323 array: Vec<String>,
324 index: usize,
325 }
326
327 impl Iterator for LangIter {
328 type Item = String;
329
next(&mut self) -> Option<Self::Item>330 fn next(&mut self) -> Option<Self::Item> {
331 if let Some(value) = self.array.get(self.index) {
332 self.index += 1;
333 Some(value.to_string())
334 } else {
335 None
336 }
337 }
338 }
339
340 #[inline(always)]
lang() -> impl Iterator<Item = String>341 pub fn lang() -> impl Iterator<Item = String> {
342 let mut num_languages = 0;
343 let mut buffer_size = 0;
344 let mut buffer;
345
346 unsafe {
347 assert_ne!(
348 GetUserPreferredUILanguages(
349 0x08, /* MUI_LANGUAGE_NAME */
350 &mut num_languages,
351 std::ptr::null_mut(), // List of languages.
352 &mut buffer_size,
353 ),
354 0
355 );
356
357 buffer = Vec::with_capacity(buffer_size as usize);
358
359 assert_ne!(
360 GetUserPreferredUILanguages(
361 0x08, /* MUI_LANGUAGE_NAME */
362 &mut num_languages,
363 buffer.as_mut_ptr(), // List of languages.
364 &mut buffer_size,
365 ),
366 0
367 );
368
369 buffer.set_len(buffer_size as usize);
370 }
371
372 // We know it ends in two null characters.
373 buffer.pop();
374 buffer.pop();
375
376 //
377 let array = String::from_utf16_lossy(&buffer)
378 .split('\0')
379 .map(|x| x.to_string())
380 .collect();
381 let index = 0;
382
383 LangIter { array, index }
384 }
385