├── .cargo └── config.toml ├── .gitignore ├── xtask ├── src │ └── main.rs └── Cargo.toml ├── .github ├── PULL_REQUEST_TEMPLATE │ ├── backport_port.md │ ├── report_and_fix.md │ ├── fix_issue.md │ └── release.md ├── dependabot.yml ├── pull_request_template.md ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── ci.yml ├── Cargo.toml ├── whoami ├── src │ ├── result.rs │ ├── conversions.rs │ ├── platform.rs │ ├── os │ │ ├── daku.rs │ │ ├── wasi.rs │ │ ├── redox.rs │ │ ├── target.rs │ │ ├── web.rs │ │ ├── windows.rs │ │ └── unix.rs │ ├── desktop_env.rs │ ├── lib.rs │ ├── arch.rs │ ├── os.rs │ ├── api.rs │ └── language.rs ├── Cargo.toml └── examples │ ├── os-strings.rs │ └── whoami-demo.rs ├── recipe.toml ├── .rustfmt.toml ├── example-web ├── index.html ├── README.md ├── Cargo.toml └── src │ └── lib.rs ├── LICENSE_MIT ├── CONTRIBUTING.md ├── LICENSE_BOOST ├── clippy.toml ├── WASM.md ├── CODE_OF_CONDUCT.md ├── README.md ├── res └── icon.svg ├── Cargo.lock └── LICENSE_APACHE /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /**/build/ 2 | /**/target/ 3 | /**/*.swp 4 | /**/*.rs.bk 5 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | edition = "2024" 4 | 5 | [dependencies] 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/backport_port.md: -------------------------------------------------------------------------------- 1 | Port changes from a different version 2 | 3 | 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "3" 3 | members = ["example-web", "whoami", "xtask"] 4 | 5 | [profile.release] 6 | opt-level = "s" # Tell `rustc` to optimize for small code size. 7 | -------------------------------------------------------------------------------- /whoami/src/result.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error; 2 | 3 | /// This crate's convenience type alias for [`Result`](std::result::Result)s 4 | pub type Result = std::result::Result; 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /recipe.toml: -------------------------------------------------------------------------------- 1 | # Redox recipe for testing whoami 2 | 3 | [source] 4 | git = "https://github.com/AldaronLau/whome.git" 5 | rev = "d29f4e29e8ed71f7284c84b093c58dbf1a151c01" 6 | 7 | [build] 8 | template = "cargo" 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/report_and_fix.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Testing / Verification 5 | 6 | 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/fix_issue.md: -------------------------------------------------------------------------------- 1 | Fixes #0 2 | 3 | 4 | 5 | 6 | ## Testing / Verification 7 | 8 | 9 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Don't deviate too much, just reduce columns and stricter enforcement 2 | edition = "2024" 3 | unstable_features = true 4 | max_width = 80 5 | reorder_impl_items = true 6 | group_imports = "StdExternalCrate" 7 | imports_granularity = "Crate" 8 | normalize_doc_attributes = true 9 | wrap_comments = true 10 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Please go to the `Preview` tab and select the appropriate template: 2 | 3 | - [Report and Fix](?expand=1&template=report_and_fix.md) 4 | - [Fix an Issue](?expand=1&template=fix_issue.md) 5 | - [Backport / Port](?expand=1&template=backport_port.md) 6 | - [Release PR](?expand=1&template=release.md) 7 | -------------------------------------------------------------------------------- /example-web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example-web/README.md: -------------------------------------------------------------------------------- 1 | # A Web Example 2 | 3 | ## Install `wasm-pack` and `http` 4 | ```bash 5 | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 6 | cargo install https 7 | ``` 8 | 9 | ## Build & Run Web Server 10 | ```bash 11 | wasm-pack build --target web && http . 12 | ``` 13 | 14 | Now, open http://localhost:8000/ in your web browser and check the javascript 15 | console. 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /example-web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "whoami_web" 3 | edition = "2024" 4 | 5 | [lib] 6 | crate-type = ["cdylib", "rlib"] 7 | 8 | [features] 9 | default = ["console_error_panic_hook"] 10 | 11 | # The `console_error_panic_hook` crate provides better debugging of panics by 12 | # logging them with `console.error`. This is great for development, but requires 13 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 14 | # code size when deploying. 15 | [dependencies.console_error_panic_hook] 16 | version = "0.1.7" 17 | optional = true 18 | 19 | # wasm-bindgen lets us export our main function to the javascript that runs the 20 | # web assembly module. 21 | [dependencies.wasm-bindgen] 22 | version = "0.2.100" 23 | 24 | # The `web-sys` crate lets us log in the web console. 25 | [dependencies.web-sys] 26 | version = "0.3.77" 27 | features = ["console"] 28 | 29 | # `whoami` - the crate we're testing! 30 | [dependencies.whoami] 31 | path = "../whoami" 32 | -------------------------------------------------------------------------------- /whoami/src/conversions.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::OsString, 3 | io::{Error, ErrorKind}, 4 | }; 5 | 6 | use crate::Result; 7 | 8 | pub(crate) fn string_from_os(string: OsString) -> Result { 9 | #[cfg(any( 10 | all(not(target_os = "windows"), not(target_arch = "wasm32")), 11 | all(target_arch = "wasm32", target_os = "wasi"), 12 | ))] 13 | { 14 | #[cfg(not(target_os = "wasi"))] 15 | use std::os::unix::ffi::OsStringExt; 16 | #[cfg(target_os = "wasi")] 17 | use std::os::wasi::ffi::OsStringExt; 18 | 19 | String::from_utf8(string.into_vec()) 20 | .map_err(|e| Error::new(ErrorKind::InvalidData, e)) 21 | } 22 | 23 | #[cfg(any( 24 | target_os = "windows", 25 | all(target_arch = "wasm32", not(target_os = "wasi")), 26 | ))] 27 | { 28 | string.into_string().map_err(|_| { 29 | Error::new(ErrorKind::InvalidData, "Not valid unicode") 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '...' 17 | 3. Scroll down to '...' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information, if applicable):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information, if applicable):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context (Optional)** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Anyone is welcome to contribute! You can [open an issue], 3 | [Post to GitHub Discussions] or [send me an email] about anything related to 4 | this project. You may also [open a PR]. I don't require PRs to be formatted in 5 | a specific manner, since I'll run it through rustfmt after the merge anyway. If 6 | you're going to work on a PR, it would be preferred to let me know ahead of time 7 | (unless it's a quick fix), and open a draft PR if it's a large one. Then I'll 8 | assign the issue to you. Otherwise, I can't guarantee I won't duplicate your 9 | work. If I can't contact you within a week, I may unassign you and finish your 10 | work (opening a Draft PR on this repository puts your code under this crate's 11 | license). 12 | 13 | If you open a bug report, you can usually expect it to be fixed within a week. 14 | If you open a feature request it may stay open indefinitely, until I need it 15 | too. I mark feature requests as "enhancements" on GitHub issues. 16 | 17 | Happy coding! 18 | 19 | [open an issue]: https://github.com/ardaku/whoami/issues 20 | [send me an email]: mailto:aldaronlau@gmail.com 21 | [open a PR]: https://github.com/ardaku/whoami/pulls 22 | [Post to GitHub Discussions]: https://github.com/ardaku/whoami/discussions 23 | -------------------------------------------------------------------------------- /whoami/src/platform.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | 3 | /// The underlying platform for a system 4 | #[allow(missing_docs)] 5 | #[derive(Debug, PartialEq, Eq, Clone)] 6 | #[non_exhaustive] 7 | pub enum Platform { 8 | Unknown(String), 9 | Linux, 10 | Bsd, 11 | Windows, 12 | Mac, 13 | Illumos, 14 | Ios, 15 | Android, 16 | Nintendo3ds, 17 | PlayStation, 18 | Fuchsia, 19 | Redox, 20 | Hurd, 21 | } 22 | 23 | impl Display for Platform { 24 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 25 | if let Self::Unknown(_) = self { 26 | f.write_str("Unknown: ")?; 27 | } 28 | 29 | f.write_str(match self { 30 | Self::Unknown(a) => a, 31 | Self::Linux => "Linux", 32 | Self::Bsd => "BSD", 33 | Self::Windows => "Windows", 34 | Self::Mac => "macOS", 35 | Self::Illumos => "illumos", 36 | Self::Ios => "iOS", 37 | Self::Android => "Android", 38 | Self::Nintendo3ds => "Nintendo 3DS", 39 | Self::PlayStation => "PlayStation", 40 | Self::Fuchsia => "Fuchsia", 41 | Self::Redox => "Redox", 42 | Self::Hurd => "GNU Hurd", 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE_BOOST: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | [[disallowed-types]] 2 | path = "std::os::raw::c_char" 3 | reason = "Use ffi::c_char" 4 | 5 | [[disallowed-types]] 6 | path = "std::os::raw::c_double" 7 | reason = "Use ffi::c_double" 8 | 9 | [[disallowed-types]] 10 | path = "std::os::raw::c_float" 11 | reason = "Use ffi::c_float" 12 | 13 | [[disallowed-types]] 14 | path = "std::os::raw::c_int" 15 | reason = "Use ffi::c_int" 16 | 17 | [[disallowed-types]] 18 | path = "std::os::raw::c_long" 19 | reason = "Use ffi::c_long" 20 | 21 | [[disallowed-types]] 22 | path = "std::os::raw::c_longlong" 23 | reason = "Use ffi::c_longlong" 24 | 25 | [[disallowed-types]] 26 | path = "std::os::raw::c_schar" 27 | reason = "Use ffi::c_schar" 28 | 29 | [[disallowed-types]] 30 | path = "std::os::raw::c_short" 31 | reason = "Use ffi::c_short" 32 | 33 | [[disallowed-types]] 34 | path = "std::os::raw::c_uchar" 35 | reason = "Use ffi::c_uchar" 36 | 37 | [[disallowed-types]] 38 | path = "std::os::raw::c_uint" 39 | reason = "Use ffi::c_uint" 40 | 41 | [[disallowed-types]] 42 | path = "std::os::raw::c_ulong" 43 | reason = "Use ffi::c_ulong" 44 | 45 | [[disallowed-types]] 46 | path = "std::os::raw::c_ulonglong" 47 | reason = "Use ffi::c_ulonglong" 48 | 49 | [[disallowed-types]] 50 | path = "std::os::raw::c_ushort" 51 | reason = "Use ffi::c_ushort" 52 | 53 | [[disallowed-types]] 54 | path = "std::os::raw::c_void" 55 | reason = "Use ffi::c_void" 56 | 57 | [[disallowed-macros]] 58 | path = "std::vec" 59 | reason = "Use .to_vec() on arrays or array.into() to convert to vec" 60 | -------------------------------------------------------------------------------- /whoami/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "whoami" 3 | version = "2.0.0-pre.3" 4 | edition = "2021" 5 | license = "Apache-2.0 OR BSL-1.0 OR MIT" 6 | documentation = "https://docs.rs/whoami" 7 | homepage = "https://github.com/ardaku/whoami/releases" 8 | repository = "https://github.com/ardaku/whoami" 9 | readme = "../README.md" 10 | description = "Rust library for getting information about the current user and environment" 11 | keywords = ["user", "username", "whoami", "platform", "detect"] 12 | categories = [ 13 | "os", 14 | "wasm", 15 | "internationalization", 16 | "localization", 17 | "value-formatting", 18 | ] 19 | include = [ 20 | "../LICENSE_APACHE", 21 | "../LICENSE_BOOST", 22 | "../LICENSE_MIT", 23 | "../README.md", 24 | "/src/*", 25 | ] 26 | rust-version = "1.65" 27 | 28 | # Target specific dependency for redox 29 | [target.'cfg(all(target_os = "redox", not(target_arch = "wasm32")))'.dependencies.libredox] 30 | version = "0.1.11" 31 | 32 | # Target specific dependency for wasite 33 | [target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'.dependencies.wasite] 34 | version = "0.1.0" 35 | 36 | # Target-specific dependency for web browser 37 | [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(daku)))'.dependencies.web-sys] 38 | version = "0.3.77" 39 | features = ["Navigator", "Document", "Window", "Location"] 40 | optional = true 41 | 42 | [features] 43 | default = ["web"] 44 | # Enabling this feature indicates that the wasm32-unknown-unknown target should 45 | # be assumed to be in a web environment where it can call DOM APIs. 46 | web = ["web-sys"] 47 | 48 | [lints.rust] 49 | unexpected_cfgs = { level = "allow", check-cfg = ['cfg(daku)'] } 50 | -------------------------------------------------------------------------------- /whoami/examples/os-strings.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("WhoAmI {}", env!("CARGO_PKG_VERSION")); 3 | println!(); 4 | println!( 5 | "User's Language whoami::lang_prefs(): {:?}", 6 | whoami::lang_prefs().unwrap_or_default() 7 | ); 8 | println!( 9 | "User's Name whoami::realname_os(): {:?}", 10 | whoami::realname_os() 11 | .unwrap_or_else(|_| "".to_string().into()), 12 | ); 13 | println!( 14 | "User's Username whoami::username_os(): {:?}", 15 | whoami::username_os() 16 | .unwrap_or_else(|_| "".to_string().into()), 17 | ); 18 | println!( 19 | "User's Account whoami::account_os(): {:?}", 20 | whoami::account_os().unwrap_or_else(|_| "".to_string().into()), 21 | ); 22 | println!( 23 | "Device's Pretty Name whoami::devicename_os(): {:?}", 24 | whoami::devicename_os() 25 | .unwrap_or_else(|_| "".to_string().into()), 26 | ); 27 | println!( 28 | "Device's Hostname whoami::hostname(): {:?}", 29 | whoami::hostname().unwrap_or_else(|_| "".to_string()), 30 | ); 31 | println!( 32 | "Device's Platform whoami::platform(): {:?}", 33 | whoami::platform(), 34 | ); 35 | println!( 36 | "Device's OS Distro whoami::distro(): {:?}", 37 | whoami::distro(), 38 | ); 39 | println!( 40 | "Device's Desktop Env. whoami::desktop_env(): {:?}", 41 | whoami::desktop_env(), 42 | ); 43 | println!( 44 | "Device's CPU Arch whoami::arch(): {:?}", 45 | whoami::arch(), 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /whoami/examples/whoami-demo.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("WhoAmI {}", env!("CARGO_PKG_VERSION")); 3 | println!(); 4 | println!( 5 | "User's Language whoami::lang_prefs(): {}", 6 | whoami::lang_prefs().unwrap_or_default() 7 | ); 8 | println!( 9 | "User's Name whoami::realname(): {}", 10 | whoami::realname().unwrap_or_else(|_| "".to_string()), 11 | ); 12 | println!( 13 | "User's Username whoami::username(): {}", 14 | whoami::username().unwrap_or_else(|_| "".to_string()), 15 | ); 16 | println!( 17 | "User's Username whoami::account(): {}", 18 | whoami::account().unwrap_or_else(|_| "".to_string()), 19 | ); 20 | println!( 21 | "Device's Pretty Name whoami::devicename(): {}", 22 | whoami::devicename().unwrap_or_else(|_| "".to_string()), 23 | ); 24 | println!( 25 | "Device's Hostname whoami::hostname(): {}", 26 | whoami::hostname().unwrap_or_else(|_| "".to_string()), 27 | ); 28 | println!( 29 | "Device's Platform whoami::platform(): {}", 30 | whoami::platform(), 31 | ); 32 | println!( 33 | "Device's OS Distro whoami::distro(): {}", 34 | whoami::distro().unwrap_or_else(|_| "".to_string()), 35 | ); 36 | println!( 37 | "Device's Desktop Env. whoami::desktop_env(): {}", 38 | whoami::desktop_env() 39 | .map(|e| e.to_string()) 40 | .unwrap_or_else(|| "".to_string()), 41 | ); 42 | println!( 43 | "Device's CPU Arch whoami::arch(): {}", 44 | whoami::arch(), 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /WASM.md: -------------------------------------------------------------------------------- 1 | # WhoAmI WebAssembly Documentation 2 | 3 | ## Web (Browser) 4 | By default, when WhoAmI is compiled for Wasm32 unknown (neither -wasi or -daku), 5 | WhoAmI links to web-sys and defaults values to browser information: 6 | 7 | - `realname()`: "Anonymous" 8 | - `username()`: "anonymous" 9 | - `lang()`: Browser preferred language list 10 | - `devicename()`: Browser name (Example: "Firefox 110.0") 11 | - `hostname()`: "localhost" 12 | - `platform()`: Host operating system by view of browser (Example: "Linux") 13 | - `distro()`: Host distro by view of browser (Example "Unknown Linux") 14 | - `desktop_env()`: "Web Browser" 15 | 16 | ## Fake 17 | If you compile WhoAmI with `default-features = false`, WhoAmI will not bind to 18 | web-sys, and will instead return these fake values: 19 | 20 | - `realname()`: "Anonymous" 21 | - `username()`: "anonymous" 22 | - `lang()`: "en-US" 23 | - `devicename()`: "Unknown" 24 | - `hostname()`: "localhost" 25 | - `platform()`: "Unknown" 26 | - `distro()`: "Emulated" 27 | - `desktop_env()`: "Unknown" 28 | 29 | ## Wasi (Wasite) 30 | Building WhoAmI targeting Wasi will assume the 31 | [wasite](https://ardaku.org/wasite/env_vars.html) environment variables are set, 32 | as Wasi alone does not currently support the functionality WhoAmI requires. 33 | 34 | - `realname()`: `$USER` - Fallback "Anonymous" 35 | - `username()`: `$USER` - Fallback "anonymous" 36 | - `lang()`: `$LANGS` - Fallback "en-US" 37 | - `devicename()`: `$NAME` - Fallback "Unknown" 38 | - `hostname()`: `$HOSTNAME` - Fallback "localhost" 39 | - `platform()`: "WASI" 40 | - `distro()`: "Unknown WASI" 41 | - `desktop_env()`: `Unknown($DESKTOP_SESSION)` - Fallback "Unknown WASI" 42 | 43 | ## Daku (Quantii, other Ardaku environments) 44 | WhoAmi will depend on currently unstable portals in the 45 | [Daku](https://ardaku.org/daku/) specification. 46 | -------------------------------------------------------------------------------- /whoami/src/os/daku.rs: -------------------------------------------------------------------------------- 1 | //! This is mostly the same as fake.rs for now 2 | 3 | use std::{ 4 | ffi::OsString, 5 | io::{Error, ErrorKind}, 6 | }; 7 | 8 | use crate::{ 9 | os::{Os, Target}, 10 | Arch, DesktopEnv, Language, LanguagePrefs, Platform, Result, 11 | }; 12 | 13 | impl Target for Os { 14 | #[inline(always)] 15 | fn lang_prefs(self) -> Result { 16 | Ok(LanguagePrefs { 17 | fallbacks: [Language::from("en/US")].to_vec(), 18 | ..Default::default() 19 | }) 20 | } 21 | 22 | #[inline(always)] 23 | fn realname(self) -> Result { 24 | Ok("Anonymous".to_string().into()) 25 | } 26 | 27 | #[inline(always)] 28 | fn username(self) -> Result { 29 | Ok("anonymous".to_string().into()) 30 | } 31 | 32 | #[inline(always)] 33 | fn devicename(self) -> Result { 34 | Ok("Unknown".to_string().into()) 35 | } 36 | 37 | #[inline(always)] 38 | fn hostname(self) -> Result { 39 | Ok("localhost".to_string()) 40 | } 41 | 42 | #[inline(always)] 43 | fn distro(self) -> Result { 44 | Ok("Emulated".to_string()) 45 | } 46 | 47 | #[inline(always)] 48 | fn desktop_env(self) -> Option { 49 | None 50 | } 51 | 52 | #[inline(always)] 53 | fn platform(self) -> Platform { 54 | Platform::Unknown("Daku".to_string()) 55 | } 56 | 57 | #[inline(always)] 58 | fn arch(self) -> Result { 59 | Ok(if cfg!(target_pointer_width = "64") { 60 | Arch::Wasm64 61 | } else if cfg!(target_pointer_width = "32") { 62 | Arch::Wasm32 63 | } else { 64 | return Err(Error::new( 65 | ErrorKind::Unsupported, 66 | "Unexpected pointer width for target platform", 67 | )); 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /whoami/src/os/wasi.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] 2 | compile_error!("Unexpected pointer width for target platform"); 3 | 4 | use std::{env, ffi::OsString}; 5 | 6 | use crate::{ 7 | os::{Os, Target}, 8 | Arch, DesktopEnv, LanguagePrefs, Platform, Result, 9 | }; 10 | 11 | impl Target for Os { 12 | fn lang_prefs(self) -> Result { 13 | super::unix_lang() 14 | } 15 | 16 | #[inline(always)] 17 | fn realname(self) -> Result { 18 | Ok(wasite::user() 19 | .unwrap_or_else(|_e| "Anonymous".to_string()) 20 | .into()) 21 | } 22 | 23 | #[inline(always)] 24 | fn username(self) -> Result { 25 | Ok(wasite::user() 26 | .unwrap_or_else(|_e| "anonymous".to_string()) 27 | .into()) 28 | } 29 | 30 | #[inline(always)] 31 | fn devicename(self) -> Result { 32 | Ok(wasite::name() 33 | .unwrap_or_else(|_e| "Unknown".to_string()) 34 | .into()) 35 | } 36 | 37 | #[inline(always)] 38 | fn hostname(self) -> Result { 39 | Ok(wasite::hostname().unwrap_or_else(|_e| "localhost".to_string())) 40 | } 41 | 42 | #[inline(always)] 43 | fn distro(self) -> Result { 44 | Ok("Unknown WASI".to_string()) 45 | } 46 | 47 | #[inline(always)] 48 | fn desktop_env(self) -> Option { 49 | env::var_os("DESKTOP_SESSION") 50 | .map(|env| DesktopEnv::Unknown(env.to_string_lossy().to_string())) 51 | } 52 | 53 | #[inline(always)] 54 | fn platform(self) -> Platform { 55 | Platform::Unknown("WASI".to_string()) 56 | } 57 | 58 | #[inline(always)] 59 | fn arch(self) -> Result { 60 | Ok(if cfg!(target_pointer_width = "64") { 61 | Arch::Wasm64 62 | } else { 63 | Arch::Wasm32 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example-web/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | fn log(text: String) { 4 | web_sys::console::log_1(&text.into()) 5 | } 6 | 7 | #[wasm_bindgen] 8 | pub fn main() { 9 | // When the `console_error_panic_hook` feature is enabled, we can call the 10 | // `set_panic_hook` function at least once during initialization, and then 11 | // we will get better error messages if our code ever panics. 12 | // 13 | // For more details see 14 | // https://github.com/rustwasm/console_error_panic_hook#readme 15 | #[cfg(feature = "console_error_panic_hook")] 16 | console_error_panic_hook::set_once(); 17 | 18 | // Print out code from regular example. 19 | log(format!( 20 | "User's Name whoami::realname(): {}", 21 | whoami::realname().unwrap_or_default(), 22 | )); 23 | log(format!( 24 | "User's Username whoami::username(): {}", 25 | whoami::username().unwrap_or_default(), 26 | )); 27 | log(format!( 28 | "User's Language whoami::lang_prefs(): {}", 29 | whoami::lang_prefs().unwrap_or_default(), 30 | )); 31 | log(format!( 32 | "Device's Pretty Name whoami::devicename(): {}", 33 | whoami::devicename().unwrap_or_default(), 34 | )); 35 | log(format!( 36 | "Device's Hostname whoami::fallible::hostname(): {}", 37 | whoami::hostname().unwrap_or_else(|_| "localhost".to_string()), 38 | )); 39 | log(format!( 40 | "Device's Platform whoami::platform(): {:?}", 41 | whoami::platform(), 42 | )); 43 | log(format!( 44 | "Device's OS Distro whoami::distro(): {}", 45 | whoami::distro().unwrap_or_default(), 46 | )); 47 | log(format!( 48 | "Device's Desktop Env. whoami::desktop_env(): {}", 49 | whoami::desktop_env().unwrap(), 50 | )); 51 | log(format!( 52 | "Device's CPU Arch whoami::arch(): {}", 53 | whoami::arch(), 54 | )); 55 | } 56 | -------------------------------------------------------------------------------- /whoami/src/desktop_env.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | 3 | /// The desktop environment of a system 4 | #[derive(Debug, PartialEq, Eq, Clone)] 5 | #[non_exhaustive] 6 | pub enum DesktopEnv { 7 | /// Unknown desktop environment 8 | Unknown(String), 9 | /// Popular GTK-based desktop environment on Linux 10 | Gnome, 11 | /// One of the desktop environments for a specific version of Windows 12 | Windows, 13 | /// Linux desktop environment optimized for low resource requirements 14 | Lxde, 15 | /// Stacking window manager for X Windows on Linux 16 | Openbox, 17 | /// Desktop environment for Linux, BSD and illumos 18 | Mate, 19 | /// Lightweight desktop enivornment for unix-like operating systems 20 | Xfce, 21 | /// KDE Plasma desktop enviroment 22 | Plasma, 23 | /// Default desktop environment on Linux Mint 24 | Cinnamon, 25 | /// Tiling window manager for Linux 26 | I3, 27 | /// Desktop environment for MacOS 28 | Aqua, 29 | /// Desktop environment for iOS 30 | Ios, 31 | /// Desktop environment for Android 32 | Android, 33 | /// Running as Web Assembly on a web page 34 | WebBrowser(String), 35 | /// A desktop environment for a video game console 36 | Console, 37 | /// Ubuntu-branded GNOME 38 | Ubuntu, 39 | /// Default shell for Fuchsia 40 | Ermine, 41 | /// Default desktop environment for Redox 42 | Orbital, 43 | } 44 | 45 | impl Display for DesktopEnv { 46 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 47 | if let Self::Unknown(_) = self { 48 | f.write_str("Unknown: ")?; 49 | } 50 | 51 | f.write_str(match self { 52 | Self::Unknown(a) => a, 53 | Self::Gnome => "Gnome", 54 | Self::Windows => "Windows", 55 | Self::Lxde => "LXDE", 56 | Self::Openbox => "Openbox", 57 | Self::Mate => "Mate", 58 | Self::Xfce => "XFCE", 59 | Self::Plasma => "KDE Plasma", 60 | Self::Cinnamon => "Cinnamon", 61 | Self::I3 => "I3", 62 | Self::Aqua => "Aqua", 63 | Self::Ios => "IOS", 64 | Self::Android => "Android", 65 | Self::WebBrowser(a) => return write!(f, "WebBrowser ({a})"), 66 | Self::Console => "Console", 67 | Self::Ubuntu => "Ubuntu", 68 | Self::Ermine => "Ermine", 69 | Self::Orbital => "Orbital", 70 | }) 71 | } 72 | } 73 | 74 | impl DesktopEnv { 75 | /// Returns true if the desktop environment is based on GTK. 76 | pub fn is_gtk(&self) -> bool { 77 | *self == Self::Gnome 78 | || *self == Self::Ubuntu 79 | || *self == Self::Cinnamon 80 | || *self == Self::Lxde 81 | || *self == Self::Mate 82 | || *self == Self::Xfce 83 | } 84 | 85 | /// Returns true if the desktop environment is based on KDE. 86 | pub fn is_kde(&self) -> bool { 87 | *self == Self::Plasma 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at aldaronlau@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /whoami/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rust library for getting information about the current user and environment. 2 | //! 3 | //! ## Getting Started 4 | //! 5 | //! Using the whoami crate is super easy! All of the public items are simple 6 | //! functions with no parameters that return [`String`]s or [`OsString`]s (with 7 | //! the exception of [`desktop_env()`], [`platform()`], and [`arch()`], which 8 | //! return enums, and [`lang_prefs()`] that returns [`LanguagePrefs`]). 9 | //! The following example shows how to use all of the functions (except those 10 | //! that return [`OsString`]): 11 | //! 12 | //! ```rust 13 | //! println!( 14 | //! "User's Language whoami::lang_prefs(): {}", 15 | //! whoami::lang_prefs().unwrap_or_default(), 16 | //! ); 17 | //! println!( 18 | //! "User's Name whoami::realname(): {}", 19 | //! whoami::realname().unwrap_or_else(|_| "".to_string()), 20 | //! ); 21 | //! println!( 22 | //! "User's Username whoami::username(): {}", 23 | //! whoami::username().unwrap_or_else(|_| "".to_string()), 24 | //! ); 25 | //! println!( 26 | //! "User's Username whoami::account(): {}", 27 | //! whoami::account().unwrap_or_else(|_| "".to_string()), 28 | //! ); 29 | //! println!( 30 | //! "Device's Pretty Name whoami::devicename(): {}", 31 | //! whoami::devicename().unwrap_or_else(|_| "".to_string()), 32 | //! ); 33 | //! println!( 34 | //! "Device's Hostname whoami::hostname(): {}", 35 | //! whoami::hostname().unwrap_or_else(|_| "".to_string()), 36 | //! ); 37 | //! println!( 38 | //! "Device's Platform whoami::platform(): {}", 39 | //! whoami::platform(), 40 | //! ); 41 | //! println!( 42 | //! "Device's OS Distro whoami::distro(): {}", 43 | //! whoami::distro().unwrap_or_else(|_| "".to_string()), 44 | //! ); 45 | //! println!( 46 | //! "Device's Desktop Env. whoami::desktop_env(): {}", 47 | //! whoami::desktop_env() 48 | //! .map(|e| e.to_string()) 49 | //! .unwrap_or_else(|| "".to_string()), 50 | //! ); 51 | //! println!( 52 | //! "Device's CPU Arch whoami::arch(): {}", 53 | //! whoami::arch(), 54 | //! ); 55 | //! ``` 56 | //! 57 | //! [`OsString`]: std::ffi::OsString 58 | 59 | #![warn( 60 | anonymous_parameters, 61 | missing_copy_implementations, 62 | missing_debug_implementations, 63 | missing_docs, 64 | nonstandard_style, 65 | rust_2018_idioms, 66 | single_use_lifetimes, 67 | trivial_casts, 68 | trivial_numeric_casts, 69 | unreachable_pub, 70 | unused_extern_crates, 71 | unused_qualifications, 72 | variant_size_differences, 73 | unsafe_code 74 | )] 75 | #![deny( 76 | rustdoc::broken_intra_doc_links, 77 | rustdoc::private_intra_doc_links, 78 | rustdoc::missing_crate_level_docs, 79 | rustdoc::private_doc_tests, 80 | rustdoc::invalid_codeblock_attributes, 81 | rustdoc::invalid_html_tags, 82 | rustdoc::invalid_rust_codeblocks, 83 | rustdoc::bare_urls, 84 | rustdoc::unescaped_backticks, 85 | rustdoc::redundant_explicit_links 86 | )] 87 | #![doc( 88 | html_logo_url = "https://raw.githubusercontent.com/ardaku/whoami/v2/res/icon.svg", 89 | html_favicon_url = "https://raw.githubusercontent.com/ardaku/whoami/v2/res/icon.svg" 90 | )] 91 | 92 | mod api; 93 | mod arch; 94 | mod conversions; 95 | mod desktop_env; 96 | mod language; 97 | mod os; 98 | mod platform; 99 | mod result; 100 | 101 | pub use self::{ 102 | api::{ 103 | account, account_os, arch, desktop_env, devicename, devicename_os, 104 | distro, hostname, lang_prefs, platform, realname, realname_os, 105 | username, username_os, 106 | }, 107 | arch::{Arch, Width}, 108 | desktop_env::DesktopEnv, 109 | language::{Country, Language, LanguagePrefs}, 110 | platform::Platform, 111 | result::Result, 112 | }; 113 | -------------------------------------------------------------------------------- /whoami/src/os/redox.rs: -------------------------------------------------------------------------------- 1 | // We don't need unsafe, yay! 2 | #![forbid(unsafe_code)] 3 | 4 | use std::{borrow::Cow, ffi::OsString, fs, io::Error}; 5 | 6 | use libredox::{call, error}; 7 | 8 | use crate::{ 9 | os::{Os, Target}, 10 | Arch, DesktopEnv, LanguagePrefs, Platform, Result, 11 | }; 12 | 13 | /// Row in the Redox /etc/passwd file 14 | struct Passwd<'a>(Cow<'a, str>); 15 | 16 | impl Passwd<'_> { 17 | fn column(&self, number: usize) -> Option<&str> { 18 | self.0.split(';').nth(number) 19 | } 20 | 21 | fn username(&self) -> Option { 22 | self.column(0).map(ToString::to_string) 23 | } 24 | 25 | fn uid(&self) -> Option { 26 | self.column(1)?.parse().ok() 27 | } 28 | 29 | fn gid(&self) -> Option { 30 | self.column(2)?.parse().ok() 31 | } 32 | 33 | fn fullname(&self) -> Option { 34 | self.column(3).map(ToString::to_string) 35 | } 36 | } 37 | 38 | struct Uname<'a>(Cow<'a, str>); 39 | 40 | impl Uname<'_> { 41 | fn row(&self, number: usize) -> Option<&str> { 42 | self.0.lines().nth(number) 43 | } 44 | 45 | fn machine_arch(&self) -> Option { 46 | // FIXME: Don't hardcode unknown arch 47 | Some(Arch::Unknown(self.row(4)?.to_string())) 48 | } 49 | } 50 | 51 | fn to_io_error(error: error::Error) -> Error { 52 | Error::from_raw_os_error(error.errno()) 53 | } 54 | 55 | fn euid() -> Result { 56 | call::geteuid().map_err(to_io_error) 57 | } 58 | 59 | fn egid() -> Result { 60 | call::getegid().map_err(to_io_error) 61 | } 62 | 63 | fn passwd() -> Result> { 64 | let (euid, egid) = (euid()?, egid()?); 65 | let passwd_file = fs::read_to_string("/etc/passwd")?; 66 | 67 | for user in passwd_file.lines() { 68 | let passwd = Passwd(user.into()); 69 | 70 | if passwd.uid() == Some(euid) && passwd.gid() == Some(egid) { 71 | return Ok(Passwd(passwd.0.into_owned().into())); 72 | } 73 | } 74 | 75 | Err(super::err_missing_record()) 76 | } 77 | 78 | fn uname() -> Result> { 79 | let uname_file = fs::read_to_string("sys:uname")?; 80 | 81 | Ok(Uname(uname_file.into())) 82 | } 83 | 84 | fn hostname() -> Result { 85 | let hostname_file = fs::read_to_string("/etc/hostname")?; 86 | 87 | Ok(hostname_file.lines().next().unwrap_or_default().to_string()) 88 | } 89 | 90 | impl Target for Os { 91 | fn lang_prefs(self) -> Result { 92 | super::unix_lang() 93 | } 94 | 95 | #[inline(always)] 96 | fn realname(self) -> Result { 97 | Ok(passwd()?.fullname().unwrap_or_default().into()) 98 | } 99 | 100 | #[inline(always)] 101 | fn username(self) -> Result { 102 | Ok(passwd()?.username().unwrap_or_default().into()) 103 | } 104 | 105 | #[inline(always)] 106 | fn devicename(self) -> Result { 107 | hostname().map(OsString::from) 108 | } 109 | 110 | #[inline(always)] 111 | fn hostname(self) -> Result { 112 | hostname() 113 | } 114 | 115 | #[inline(always)] 116 | fn distro(self) -> Result { 117 | let release_file = fs::read_to_string("/etc/os-release")?; 118 | 119 | for kv in release_file.lines() { 120 | if let Some(kv) = kv.strip_prefix("PRETTY_NAME=\"") { 121 | if let Some(kv) = kv.strip_suffix('\"') { 122 | return Ok(kv.to_string()); 123 | } 124 | } 125 | } 126 | 127 | Err(super::err_missing_record()) 128 | } 129 | 130 | #[inline(always)] 131 | fn desktop_env(self) -> Option { 132 | Some(DesktopEnv::Orbital) 133 | } 134 | 135 | #[inline(always)] 136 | fn platform(self) -> Platform { 137 | Platform::Redox 138 | } 139 | 140 | #[inline(always)] 141 | fn arch(self) -> Result { 142 | uname()? 143 | .machine_arch() 144 | .ok_or_else(super::err_missing_record) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /whoami/src/arch.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{self, Display, Formatter}, 3 | io::{Error, ErrorKind}, 4 | }; 5 | 6 | use crate::Result; 7 | 8 | /// The address width of a CPU architecture 9 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 10 | #[non_exhaustive] 11 | pub enum Width { 12 | /// 32 bits 13 | Bits32, 14 | /// 64 bits 15 | Bits64, 16 | } 17 | 18 | impl Display for Width { 19 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 20 | f.write_str(match self { 21 | Width::Bits32 => "32 bits", 22 | Width::Bits64 => "64 bits", 23 | }) 24 | } 25 | } 26 | 27 | /// The architecture of a CPU 28 | #[non_exhaustive] 29 | #[derive(Debug, PartialEq, Eq, Clone)] 30 | pub enum Arch { 31 | /// Unknown Architecture 32 | Unknown(String), 33 | /// ARMv5 34 | ArmV5, 35 | /// ARMv6 (Sometimes just referred to as ARM) 36 | ArmV6, 37 | /// ARMv7 (May or may not support Neon/Thumb) 38 | ArmV7, 39 | /// ARM64 (aarch64) 40 | Arm64, 41 | /// i386 (x86) 42 | I386, 43 | /// i586 (x86) 44 | I586, 45 | /// i686 (x86) 46 | I686, 47 | /// X86_64 / Amd64 48 | X64, 49 | /// MIPS 50 | Mips, 51 | /// MIPS (LE) 52 | MipsEl, 53 | /// MIPS64 54 | Mips64, 55 | /// MIPS64 (LE) 56 | Mips64El, 57 | /// PowerPC 58 | PowerPc, 59 | /// PowerPC64 60 | PowerPc64, 61 | /// PowerPC64LE 62 | PowerPc64Le, 63 | /// 32-bit RISC-V 64 | Riscv32, 65 | /// 64-bit RISC-V 66 | Riscv64, 67 | /// S390x 68 | S390x, 69 | /// SPARC 70 | Sparc, 71 | /// SPARC64 72 | Sparc64, 73 | /// 32-bit Web Assembly 74 | Wasm32, 75 | /// 64-bit Web Assembly 76 | Wasm64, 77 | } 78 | 79 | impl Display for Arch { 80 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 81 | if let Self::Unknown(_) = self { 82 | f.write_str("Unknown: ")?; 83 | } 84 | 85 | f.write_str(match self { 86 | Self::Unknown(arch) => arch, 87 | Self::ArmV5 => "armv5", 88 | Self::ArmV6 => "armv6", 89 | Self::ArmV7 => "armv7", 90 | Self::Arm64 => "arm64", 91 | Self::I386 => "i386", 92 | Self::I586 => "i586", 93 | Self::I686 => "i686", 94 | Self::Mips => "mips", 95 | Self::MipsEl => "mipsel", 96 | Self::Mips64 => "mips64", 97 | Self::Mips64El => "mips64el", 98 | Self::PowerPc => "powerpc", 99 | Self::PowerPc64 => "powerpc64", 100 | Self::PowerPc64Le => "powerpc64le", 101 | Self::Riscv32 => "riscv32", 102 | Self::Riscv64 => "riscv64", 103 | Self::S390x => "s390x", 104 | Self::Sparc => "sparc", 105 | Self::Sparc64 => "sparc64", 106 | Self::Wasm32 => "wasm32", 107 | Self::Wasm64 => "wasm64", 108 | Self::X64 => "x86_64", 109 | }) 110 | } 111 | } 112 | 113 | impl Arch { 114 | /// Get the width of this architecture. 115 | pub fn width(&self) -> Result { 116 | match self { 117 | Arch::ArmV5 118 | | Arch::ArmV6 119 | | Arch::ArmV7 120 | | Arch::I386 121 | | Arch::I586 122 | | Arch::I686 123 | | Arch::Mips 124 | | Arch::MipsEl 125 | | Arch::PowerPc 126 | | Arch::Riscv32 127 | | Arch::Sparc 128 | | Arch::Wasm32 => Ok(Width::Bits32), 129 | Arch::Arm64 130 | | Arch::Mips64 131 | | Arch::Mips64El 132 | | Arch::PowerPc64 133 | | Arch::PowerPc64Le 134 | | Arch::Riscv64 135 | | Arch::S390x 136 | | Arch::Sparc64 137 | | Arch::Wasm64 138 | | Arch::X64 => Ok(Width::Bits64), 139 | Arch::Unknown(unknown_arch) => Err(Error::new( 140 | ErrorKind::InvalidData, 141 | format!("Tried getting width of unknown arch ({unknown_arch})"), 142 | )), 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /whoami/src/os/target.rs: -------------------------------------------------------------------------------- 1 | //! Unknown target, fake implementation. 2 | //! 3 | //! This can be used as a template when adding new target support. 4 | 5 | use std::{ 6 | ffi::OsString, 7 | io::{Error, ErrorKind}, 8 | }; 9 | 10 | use crate::{ 11 | os::{Os, Target}, 12 | Arch, DesktopEnv, Language, LanguagePrefs, Platform, Result, 13 | }; 14 | 15 | impl Target for Os { 16 | #[inline(always)] 17 | fn lang_prefs(self) -> Result { 18 | Ok(LanguagePrefs { 19 | fallbacks: [Language::from("en/US")].to_vec(), 20 | ..Default::default() 21 | }) 22 | } 23 | 24 | #[inline(always)] 25 | fn realname(self) -> Result { 26 | Ok("Anonymous".to_string().into()) 27 | } 28 | 29 | #[inline(always)] 30 | fn username(self) -> Result { 31 | Ok("anonymous".to_string().into()) 32 | } 33 | 34 | #[inline(always)] 35 | fn devicename(self) -> Result { 36 | Ok("Unknown".to_string().into()) 37 | } 38 | 39 | #[inline(always)] 40 | fn hostname(self) -> Result { 41 | Ok("localhost".to_string()) 42 | } 43 | 44 | #[inline(always)] 45 | fn distro(self) -> Result { 46 | Ok(format!("Unknown {}", self.platform())) 47 | } 48 | 49 | #[inline(always)] 50 | fn desktop_env(self) -> Option { 51 | None 52 | } 53 | 54 | #[inline(always)] 55 | fn platform(self) -> Platform { 56 | if cfg!(daku) { 57 | Platform::Unknown("Daku".to_string()) 58 | } else if cfg!(target_os = "wasi") { 59 | Platform::Unknown("WASI".to_string()) 60 | } else if cfg!(target_os = "windows") { 61 | Platform::Windows 62 | } else if cfg!(target_os = "macos") { 63 | Platform::Mac 64 | } else if cfg!(target_os = "redox") { 65 | Platform::Redox 66 | } else if cfg!(target_os = "linux") { 67 | Platform::Linux 68 | } else if cfg!(target_os = "android") { 69 | Platform::Android 70 | } else if cfg!(target_os = "tvos") { 71 | Platform::Unknown("tvOS".to_string()) 72 | } else if cfg!(target_os = "watchos") { 73 | Platform::Unknown("watchOS".to_string()) 74 | } else if cfg!(target_os = "ios") { 75 | Platform::Unknown("iOS".to_string()) 76 | } else if cfg!(target_os = "fuchsia") { 77 | Platform::Fuchsia 78 | } else if cfg!(target_os = "illumos") { 79 | Platform::Illumos 80 | } else if cfg!(any( 81 | target_os = "dragonfly", 82 | target_os = "freebsd", 83 | target_os = "netbsd", 84 | target_os = "openbsd", 85 | )) { 86 | Platform::Bsd 87 | } else if cfg!(target_os = "haiku") { 88 | Platform::Unknown("Haiku".to_string()) 89 | } else if cfg!(target_os = "vxworks") { 90 | Platform::Unknown("VxWorks".to_string()) 91 | } else if cfg!(target_os = "nto") { 92 | Platform::Unknown("QNX Neutrino".to_string()) 93 | } else if cfg!(target_os = "horizon") { 94 | Platform::Nintendo3ds 95 | } else if cfg!(target_os = "vita") { 96 | Platform::PlayStation 97 | } else if cfg!(target_os = "hurd") { 98 | Platform::Hurd 99 | } else if cfg!(target_os = "aix") { 100 | Platform::Unknown("AIX OS".to_string()) 101 | } else if cfg!(target_os = "espidf") { 102 | Platform::Unknown("ESP-IDF".to_string()) 103 | } else if cfg!(target_os = "emscripten") { 104 | Platform::Unknown("Emscripten".to_string()) 105 | } else if cfg!(target_os = "solaris") { 106 | Platform::Unknown("Solaris".to_string()) 107 | } else if cfg!(target_os = "l4re") { 108 | Platform::Unknown("L4 Runtime Environment".to_string()) 109 | } else { 110 | Platform::Unknown("Unknown".to_string()) 111 | } 112 | } 113 | 114 | #[inline(always)] 115 | fn arch(self) -> Result { 116 | Ok(if cfg!(target_pointer_width = "64") { 117 | Arch::Wasm64 118 | } else if cfg!(target_pointer_width = "32") { 119 | Arch::Wasm32 120 | } else { 121 | return Err(Error::new( 122 | ErrorKind::Unsupported, 123 | "Unexpected pointer width for target platform", 124 | )); 125 | }) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![WhoAmI Logo] 2 | 3 | #### [Changelog] | [Source] | [Getting Started] 4 | 5 | [![tests](https://github.com/ardaku/whoami/actions/workflows/ci.yml/badge.svg)](https://github.com/ardaku/whoami/actions/workflows/ci.yml) 6 | [![GitHub commit activity](https://img.shields.io/github/commit-activity/y/ardaku/whoami)](https://github.com/ardaku/whoami/) 7 | [![GitHub contributors](https://img.shields.io/github/contributors/ardaku/whoami)](https://github.com/ardaku/whoami/graphs/contributors) 8 | [![Crates.io](https://img.shields.io/crates/v/whoami)](https://crates.io/crates/whoami) 9 | [![Crates.io](https://img.shields.io/crates/d/whoami)](https://crates.io/crates/whoami) 10 | [![Crates.io (recent)](https://img.shields.io/crates/dr/whoami)](https://crates.io/crates/whoami) 11 | [![Crates.io](https://img.shields.io/crates/l/whoami)](https://github.com/search?q=repo%3Aardaku%2Fwhoami+path%3A**%2FLICENSE*&type=code) 12 | [![Docs.rs](https://docs.rs/whoami/badge.svg)](https://docs.rs/whoami/) 13 | 14 | Rust library for getting information about the current user and environment. 15 | 16 | Check out the [documentation] for examples. 17 | 18 | ### Features 19 | 20 | - Get the user's full name 21 | - Get the user's username 22 | - Get the user's preferred language(s) 23 | - Get the devices's hostname 24 | - Get the devices's "pretty hostname" or "fancy name" 25 | - Get the devices's desktop environment 26 | - Get the devices's OS name and version 27 | - Get the devices's platform name 28 | - Get the devices's CPU architecture and its width 29 | 30 | ### Supported Platforms 31 | 32 | WhoAmI targets all platforms that can run Rust, including: 33 | 34 | - Linux 35 | - Windows 36 | - Mac OS 37 | - BSD variants (FreeBSD, others) 38 | - illumos variants (SmartOS, OmniOS, others) 39 | - Redox 40 | - [Web Assembly] 41 | - Fake implementation 42 | - Web Browser - DOM 43 | - WASI (Wasite, others) **untested, testing planned later** 44 | - Daku (Ardaku/Quantii, others) **planned later** 45 | - Android **planned later** 46 | - iOS / watchOS / tvOS **planned later** 47 | - Fuchsia **planned later** 48 | - GNU/Hurd **untested** 49 | - Others? (make a PR or open an issue) 50 | 51 | ## MSRV 52 | 53 | MSRV is updated according to the [Ardaku MSRV guidelines], so it will only get 54 | updated on minor and major version bumps. All 2.x releases will be maintained 55 | with bugfixes until the end of 2027 at minimum. 56 | 57 | - WhoAmI 2.2: Rust 1.85 MSRV (bump to get the 2024 edition) 58 | - WhoAmI 2.1: Rust 1.75 MSRV (bump to pull in nix, etc.) 59 | - WhoAmI 2.0: Rust 1.65 MSRV (support based on original 2.0 plan) 60 | - WhoAmI 1.x: Rust 1.40 MSRV (old MSRV policy, maintained until end of 2025 at 61 | minimum) 62 | 63 | ## Binary 64 | 65 | [whome] - `whoami` command RiR (Re-written in Rust) that depends on this crate. 66 | 67 | ## Testing 68 | 69 | The interactive testing procedure is documented in the release PR template. The 70 | full manual test suite is run for each change that affects multiple platforms. 71 | 72 | ## License 73 | 74 | Copyright © 2017-2025 The WhoAmI Contributors. 75 | 76 | Licensed under any of 77 | - Apache License, Version 2.0, ([LICENSE_APACHE] or 78 | ) 79 | - Boost Software License, Version 1.0, ([LICENSE_BOOST] or 80 | ) 81 | - MIT License, ([LICENSE_MIT] or ) 82 | 83 | at your option. 84 | 85 | ### Contribution 86 | 87 | Unless you explicitly state otherwise, any contribution intentionally submitted 88 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 89 | licensed as described above, without any additional terms or conditions. 90 | 91 | ## Help 92 | 93 | If you want help using or contributing to this library, feel free to send me an 94 | email at . 95 | 96 | [Changelog]: https://github.com/ardaku/whoami/releases 97 | [Source]: https://github.com/ardaku/whoami 98 | [Getting Started]: https://docs.rs/whoami#getting-started 99 | [documentation]: https://docs.rs/whoami 100 | [LICENSE_APACHE]: https://github.com/ardaku/whoami/blob/v2/LICENSE_APACHE 101 | [LICENSE_MIT]: https://github.com/ardaku/whoami/blob/v2/LICENSE_MIT 102 | [LICENSE_BOOST]: https://github.com/ardaku/whoami/blob/v2/LICENSE_BOOST 103 | [Ardaku MSRV guidelines]: https://github.com/ardaku/.github/blob/v1/profile/MSRV.md 104 | [WhoAmI Logo]: https://raw.githubusercontent.com/ardaku/whoami/v2/res/icon.svg 105 | [Web Assembly]: https://github.com/ardaku/whoami/blob/v2/WASM.md 106 | [whome]: https://crates.io/crates/whome 107 | -------------------------------------------------------------------------------- /res/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 42 | 44 | 45 | 47 | image/svg+xml 48 | 50 | 51 | 52 | 53 | 54 | 59 | 66 | 73 | 78 | 83 | 88 | 93 | 100 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /whoami/src/os.rs: -------------------------------------------------------------------------------- 1 | #![allow(unsafe_code)] 2 | 3 | // Daku 4 | #[cfg_attr(all(target_arch = "wasm32", daku), path = "os/daku.rs")] 5 | // Redox 6 | #[cfg_attr( 7 | all(target_os = "redox", not(target_arch = "wasm32")), 8 | path = "os/redox.rs" 9 | )] 10 | // Unix 11 | #[cfg_attr( 12 | all( 13 | any( 14 | target_os = "linux", 15 | target_os = "macos", 16 | target_os = "dragonfly", 17 | target_os = "freebsd", 18 | target_os = "netbsd", 19 | target_os = "openbsd", 20 | target_os = "illumos", 21 | target_os = "hurd", 22 | ), 23 | not(target_arch = "wasm32") 24 | ), 25 | path = "os/unix.rs" 26 | )] 27 | // Wasi 28 | #[cfg_attr( 29 | all(target_arch = "wasm32", target_os = "wasi"), 30 | path = "os/wasi.rs" 31 | )] 32 | // Web 33 | #[cfg_attr( 34 | all( 35 | target_arch = "wasm32", 36 | not(target_os = "wasi"), 37 | not(daku), 38 | feature = "web", 39 | ), 40 | path = "os/web.rs" 41 | )] 42 | // Windows 43 | #[cfg_attr( 44 | all(target_os = "windows", not(target_arch = "wasm32")), 45 | path = "os/windows.rs" 46 | )] 47 | mod target; 48 | 49 | use std::{ 50 | env::{self, VarError}, 51 | ffi::OsString, 52 | io::{Error, ErrorKind}, 53 | }; 54 | 55 | use crate::{Arch, DesktopEnv, Language, LanguagePrefs, Platform, Result}; 56 | 57 | /// Implement `Target for Os` to add platform support for a target. 58 | pub(crate) struct Os; 59 | 60 | /// Target platform support 61 | pub(crate) trait Target: Sized { 62 | /// Return a semicolon-delimited string of language/COUNTRY codes. 63 | fn lang_prefs(self) -> Result; 64 | /// Return the user's "real" / "full" name. 65 | fn realname(self) -> Result; 66 | /// Return the user's username. 67 | fn username(self) -> Result; 68 | /// Return the computer's "fancy" / "pretty" name. 69 | fn devicename(self) -> Result; 70 | /// Return the computer's hostname. 71 | fn hostname(self) -> Result; 72 | /// Return the OS distribution's name. 73 | fn distro(self) -> Result; 74 | /// Return the desktop environment. 75 | fn desktop_env(self) -> Option; 76 | /// Return the target platform. 77 | fn platform(self) -> Platform; 78 | /// Return the computer's CPU architecture. 79 | fn arch(self) -> Result; 80 | 81 | /// Return the user's account name (usually just the username, but may 82 | /// include an account server hostname). 83 | fn account(self) -> Result { 84 | self.username() 85 | } 86 | } 87 | 88 | // This is only used on some platforms 89 | #[allow(dead_code)] 90 | fn err_missing_record() -> Error { 91 | Error::new(ErrorKind::NotFound, "Missing record") 92 | } 93 | 94 | // This is only used on some platforms 95 | #[allow(dead_code)] 96 | fn err_null_record() -> Error { 97 | Error::new(ErrorKind::NotFound, "Null record") 98 | } 99 | 100 | // This is only used on some platforms 101 | #[allow(dead_code)] 102 | fn err_empty_record() -> Error { 103 | Error::new(ErrorKind::NotFound, "Empty record") 104 | } 105 | 106 | // This is only used on some platforms 107 | #[allow(dead_code)] 108 | fn unix_lang() -> Result { 109 | let env_var = |var: &str| match env::var(var) { 110 | Ok(value) => Ok(if value.is_empty() { None } else { Some(value) }), 111 | Err(VarError::NotPresent) => Ok(None), 112 | Err(VarError::NotUnicode(_)) => Err(ErrorKind::InvalidData), 113 | }; 114 | 115 | // Uses priority defined in 116 | // 117 | let lc_all = env_var("LC_ALL")?; 118 | let lang = env_var("LANG")?; 119 | 120 | if lang.is_none() && lc_all.is_none() { 121 | return Err(err_empty_record()); 122 | } 123 | 124 | // Standard locales that have a higher global precedence than their specific 125 | // counterparts, indicating that one should not perform any localization. 126 | // https://www.gnu.org/software/libc/manual/html_node/Standard-Locales.html 127 | if let Some(l) = &lang { 128 | if l == "C" || l == "POSIX" { 129 | return Ok(LanguagePrefs { 130 | fallbacks: Vec::new(), 131 | ..Default::default() 132 | }); 133 | } 134 | } 135 | 136 | // The LANGUAGE environment variable takes precedence if and only if 137 | // localization is enabled, i.e., LC_ALL / LANG is not "C" or "POSIX". 138 | // 139 | if let Some(language) = env_var("LANGUAGE")? { 140 | return Ok(LanguagePrefs { 141 | fallbacks: language.split(":").map(Language::from).collect(), 142 | ..Default::default() 143 | }); 144 | } 145 | 146 | // All fields other than LANGUAGE can only contain a single value, so we 147 | // don't need to perform any splitting at this point. 148 | let lang_from_var = |var| -> Result, Error> { 149 | Ok(env_var(var)?.map(Language::from)) 150 | }; 151 | 152 | Ok(LanguagePrefs { 153 | fallbacks: lang.map_or_else(Vec::new, |l| [Language::from(l)].to_vec()), 154 | collation: lang_from_var("LC_COLLATE")?, 155 | char_classes: lang_from_var("LC_CTYPE")?, 156 | monetary: lang_from_var("LC_MONTEARY")?, 157 | messages: lang_from_var("LC_MESSAGES")?, 158 | numeric: lang_from_var("LC_NUMERIC")?, 159 | time: lang_from_var("LC_TIME")?, 160 | }) 161 | } 162 | -------------------------------------------------------------------------------- /whoami/src/api.rs: -------------------------------------------------------------------------------- 1 | use std::{env, ffi::OsString}; 2 | 3 | use crate::{ 4 | conversions, 5 | os::{Os, Target}, 6 | Arch, DesktopEnv, LanguagePrefs, Platform, Result, 7 | }; 8 | 9 | macro_rules! report_message { 10 | () => { 11 | "Please report this issue at https://github.com/ardaku/whoami/issues" 12 | }; 13 | } 14 | 15 | /// Get the CPU Architecture. 16 | #[inline(always)] 17 | pub fn arch() -> Arch { 18 | Target::arch(Os).expect(concat!("arch() failed. ", report_message!())) 19 | } 20 | 21 | /// Get the user's account name; usually just the username, but may include an 22 | /// account server hostname. 23 | /// 24 | /// If you don't want the account server hostname, use [`username()`]. 25 | /// 26 | /// Example: `username@example.com` 27 | #[inline(always)] 28 | pub fn account() -> Result { 29 | account_os().and_then(conversions::string_from_os) 30 | } 31 | 32 | /// Get the user's account name; usually just the username, but may include an 33 | /// account server hostname. 34 | /// 35 | /// If you don't want the account server hostname, use [`username()`]. 36 | /// 37 | /// Example: `username@example.com` 38 | #[inline(always)] 39 | pub fn account_os() -> Result { 40 | Target::account(Os) 41 | } 42 | 43 | /// Get the user's username. 44 | /// 45 | /// On unix-systems this differs from [`realname()`] most notably in that spaces 46 | /// are not allowed in the username. 47 | #[inline(always)] 48 | pub fn username() -> Result { 49 | username_os().and_then(conversions::string_from_os) 50 | } 51 | 52 | /// Get the user's username. 53 | /// 54 | /// On unix-systems this differs from [`realname_os()`] most notably in that 55 | /// spaces are not allowed in the username. 56 | #[inline(always)] 57 | pub fn username_os() -> Result { 58 | Target::username(Os) 59 | } 60 | 61 | /// Get the user's real (full) name. 62 | #[inline(always)] 63 | pub fn realname() -> Result { 64 | realname_os().and_then(conversions::string_from_os) 65 | } 66 | 67 | /// Get the user's real (full) name. 68 | #[inline(always)] 69 | pub fn realname_os() -> Result { 70 | Target::realname(Os) 71 | } 72 | 73 | /// Get the host device's hostname. 74 | /// 75 | /// Usually hostnames are case-insensitive, but it's not a hard requirement. 76 | /// 77 | /// # Platform-Specific Character Limitations 78 | /// 79 | /// ## Unix/Linux/BSD 80 | /// - **Maximum length**: 255 bytes (excluding null terminator) 81 | /// - **Encoding**: Must be valid UTF-8 82 | /// - **Characters**: Typically follows RFC 952/1123 DNS hostname rules: 83 | /// - Alphanumeric characters (a-z, A-Z, 0-9) 84 | /// - Hyphens (-), but not at start or end 85 | /// - Note: POSIX allows any character except null and newline, but network 86 | /// hostnames should follow DNS rules for interoperability 87 | /// 88 | /// ## Windows 89 | /// - **Maximum length**: 63 characters for DNS hostname (per label) 90 | /// - **Encoding**: UTF-16 (converted to UTF-8 String) 91 | /// - **Characters**: Follows DNS hostname rules (RFC 1123): 92 | /// - Alphanumeric characters (a-z, A-Z, 0-9) 93 | /// - Hyphens (-), but not at start or end 94 | /// 95 | /// ## Redox 96 | /// - Reads from `/etc/hostname` file 97 | /// - First line of file is used as hostname 98 | /// - No inherent character limitations beyond file system 99 | /// 100 | /// ## Web (WASM) 101 | /// - Returns the document's domain name 102 | /// - Follows DNS hostname rules as enforced by browsers 103 | /// - Must be valid UTF-8 104 | /// 105 | /// ## Other Platforms 106 | /// - WASI: Returns system hostname or defaults to "localhost" 107 | /// - Default: Returns "localhost" for unsupported platforms 108 | /// 109 | /// # Notes 110 | /// For maximum compatibility across all platforms and network protocols, 111 | /// hostnames should: 112 | /// - Be 63 characters or less 113 | /// - Contain only ASCII alphanumeric characters and hyphens 114 | /// - Not start or end with a hyphen 115 | /// - Be case-insensitive (though case may be preserved) 116 | #[inline(always)] 117 | pub fn hostname() -> Result { 118 | Target::hostname(Os) 119 | } 120 | 121 | /// Get the device name (also known as "Pretty Name"). 122 | /// 123 | /// Often used to identify device for bluetooth pairing. 124 | #[inline(always)] 125 | pub fn devicename() -> Result { 126 | devicename_os().and_then(conversions::string_from_os) 127 | } 128 | 129 | /// Get the device name (also known as "Pretty Name"). 130 | /// 131 | /// Often used to identify device for bluetooth pairing. 132 | #[inline(always)] 133 | pub fn devicename_os() -> Result { 134 | Target::devicename(Os) 135 | } 136 | 137 | /// Get the name of the operating system distribution and (possibly) version. 138 | /// 139 | /// Example: "Windows 10" or "Fedora 26 (Workstation Edition)" 140 | #[inline(always)] 141 | pub fn distro() -> Result { 142 | Target::distro(Os) 143 | } 144 | 145 | /// Get the desktop environment (if any). 146 | /// 147 | /// Example: "gnome" or "windows" 148 | /// 149 | /// Returns `None` if a desktop environment is not available (for example in a 150 | /// TTY or over SSH) 151 | #[inline(always)] 152 | pub fn desktop_env() -> Option { 153 | if env::var_os("SSH_CLIENT").is_some() 154 | || env::var_os("SSH_TTY").is_some() 155 | || env::var_os("SSH_CONNECTION").is_some() 156 | { 157 | return None; 158 | } 159 | 160 | Target::desktop_env(Os) 161 | } 162 | 163 | /// Get the platform. 164 | #[inline(always)] 165 | pub fn platform() -> Platform { 166 | Target::platform(Os) 167 | } 168 | 169 | /// Get the user's preferred language(s). 170 | /// 171 | /// Returned as a [`LanguagePrefs`]. Unrecognized languages may 172 | /// either return an error or be skipped. 173 | #[inline(always)] 174 | pub fn lang_prefs() -> Result { 175 | Target::lang_prefs(Os) 176 | } 177 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "2.9.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 10 | 11 | [[package]] 12 | name = "bumpalo" 13 | version = "3.14.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 22 | 23 | [[package]] 24 | name = "console_error_panic_hook" 25 | version = "0.1.7" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" 28 | dependencies = [ 29 | "cfg-if", 30 | "wasm-bindgen", 31 | ] 32 | 33 | [[package]] 34 | name = "js-sys" 35 | version = "0.3.77" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 38 | dependencies = [ 39 | "once_cell", 40 | "wasm-bindgen", 41 | ] 42 | 43 | [[package]] 44 | name = "libc" 45 | version = "0.2.174" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" 48 | 49 | [[package]] 50 | name = "libredox" 51 | version = "0.1.11" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" 54 | dependencies = [ 55 | "bitflags", 56 | "libc", 57 | "redox_syscall", 58 | ] 59 | 60 | [[package]] 61 | name = "log" 62 | version = "0.4.27" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 65 | 66 | [[package]] 67 | name = "once_cell" 68 | version = "1.21.3" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 71 | 72 | [[package]] 73 | name = "proc-macro2" 74 | version = "1.0.96" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0" 77 | dependencies = [ 78 | "unicode-ident", 79 | ] 80 | 81 | [[package]] 82 | name = "quote" 83 | version = "1.0.40" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 86 | dependencies = [ 87 | "proc-macro2", 88 | ] 89 | 90 | [[package]] 91 | name = "redox_syscall" 92 | version = "0.6.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" 95 | dependencies = [ 96 | "bitflags", 97 | ] 98 | 99 | [[package]] 100 | name = "rustversion" 101 | version = "1.0.22" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 104 | 105 | [[package]] 106 | name = "syn" 107 | version = "2.0.104" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" 110 | dependencies = [ 111 | "proc-macro2", 112 | "quote", 113 | "unicode-ident", 114 | ] 115 | 116 | [[package]] 117 | name = "unicode-ident" 118 | version = "1.0.18" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 121 | 122 | [[package]] 123 | name = "wasite" 124 | version = "0.1.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 127 | 128 | [[package]] 129 | name = "wasm-bindgen" 130 | version = "0.2.100" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 133 | dependencies = [ 134 | "cfg-if", 135 | "once_cell", 136 | "rustversion", 137 | "wasm-bindgen-macro", 138 | ] 139 | 140 | [[package]] 141 | name = "wasm-bindgen-backend" 142 | version = "0.2.100" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 145 | dependencies = [ 146 | "bumpalo", 147 | "log", 148 | "proc-macro2", 149 | "quote", 150 | "syn", 151 | "wasm-bindgen-shared", 152 | ] 153 | 154 | [[package]] 155 | name = "wasm-bindgen-macro" 156 | version = "0.2.100" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 159 | dependencies = [ 160 | "quote", 161 | "wasm-bindgen-macro-support", 162 | ] 163 | 164 | [[package]] 165 | name = "wasm-bindgen-macro-support" 166 | version = "0.2.100" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 169 | dependencies = [ 170 | "proc-macro2", 171 | "quote", 172 | "syn", 173 | "wasm-bindgen-backend", 174 | "wasm-bindgen-shared", 175 | ] 176 | 177 | [[package]] 178 | name = "wasm-bindgen-shared" 179 | version = "0.2.100" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 182 | dependencies = [ 183 | "unicode-ident", 184 | ] 185 | 186 | [[package]] 187 | name = "web-sys" 188 | version = "0.3.77" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 191 | dependencies = [ 192 | "js-sys", 193 | "wasm-bindgen", 194 | ] 195 | 196 | [[package]] 197 | name = "whoami" 198 | version = "2.0.0-pre.3" 199 | dependencies = [ 200 | "libredox", 201 | "wasite", 202 | "web-sys", 203 | ] 204 | 205 | [[package]] 206 | name = "whoami_web" 207 | version = "0.0.0" 208 | dependencies = [ 209 | "console_error_panic_hook", 210 | "wasm-bindgen", 211 | "web-sys", 212 | "whoami", 213 | ] 214 | 215 | [[package]] 216 | name = "xtask" 217 | version = "0.0.0" 218 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: tests 4 | 5 | env: 6 | RUSTUP_AUTO_INSTALL: 0 7 | 8 | jobs: 9 | checks: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-latest, windows-latest] 14 | tc: [nightly] 15 | steps: 16 | - uses: taiki-e/checkout-action@v1 17 | - uses: taiki-e/install-action@v2 18 | - run: rustup set profile minimal 19 | - run: rustup toolchain install ${{ matrix.tc }} 20 | - run: cargo update 21 | - run: cargo fmt --check 22 | - run: cargo clippy -- -D warnings 23 | checks-cross-compile: 24 | runs-on: ${{ matrix.os }} 25 | strategy: 26 | matrix: 27 | os: [ubuntu-latest] 28 | tc: [nightly] 29 | cc: 30 | - aarch64-linux-android 31 | - i686-pc-windows-gnu 32 | - i686-unknown-freebsd 33 | - i686-unknown-linux-gnu 34 | - wasm32-wasip1 35 | - x86_64-apple-darwin 36 | steps: 37 | - uses: taiki-e/checkout-action@v1 38 | - uses: taiki-e/install-action@v2 39 | - run: rustup set profile minimal 40 | - run: rustup toolchain install ${{ matrix.tc }} 41 | - run: rustup target add ${{ matrix.cc }} 42 | - run: cargo update 43 | - run: cargo clippy --all-features --target=${{ matrix.cc }} -- -D warnings 44 | checks-cross-compile-ios: 45 | runs-on: ${{ matrix.os }} 46 | strategy: 47 | matrix: 48 | os: [macos-latest] 49 | tc: [nightly] 50 | cc: [aarch64-apple-ios] 51 | steps: 52 | - uses: taiki-e/checkout-action@v1 53 | - uses: taiki-e/install-action@v2 54 | - run: rustup set profile minimal 55 | - run: rustup toolchain install ${{ matrix.tc }} 56 | - run: rustup target add ${{ matrix.cc }} 57 | - run: cargo clippy --all-features --target=${{ matrix.cc }} -- -D warnings 58 | checks-cross-compile-wasm: 59 | runs-on: ${{ matrix.os }} 60 | strategy: 61 | matrix: 62 | os: [ubuntu-latest] 63 | tc: [nightly] 64 | cc: [wasm32-unknown-unknown] 65 | steps: 66 | - uses: taiki-e/checkout-action@v1 67 | - uses: taiki-e/install-action@v2 68 | - run: rustup set profile minimal 69 | - run: rustup toolchain install ${{ matrix.tc }} 70 | - run: rustup target add ${{ matrix.cc }} 71 | - run: cargo clippy --all-features --target=${{ matrix.cc }} -- -D warnings 72 | - run: cargo update 73 | - run: cargo clippy --no-default-features --target=${{ matrix.cc }} -- -D warnings 74 | - run: RUSTFLAGS="--cfg daku" cargo clippy --target=${{ matrix.cc }} -- -D warnings 75 | test: 76 | runs-on: ${{ matrix.os }} 77 | strategy: 78 | matrix: 79 | os: [ubuntu-latest, macos-latest, windows-latest] 80 | tc: [1.65.0, stable, beta, nightly] 81 | steps: 82 | - uses: taiki-e/checkout-action@v1 83 | - uses: taiki-e/install-action@v2 84 | - run: rustup set profile minimal 85 | - run: rustup toolchain install ${{ matrix.tc }} 86 | - run: cargo doc 87 | - run: cargo test --all --all-features 88 | cross-compile: 89 | runs-on: ${{ matrix.os }} 90 | strategy: 91 | matrix: 92 | os: [ubuntu-latest] 93 | tc: [1.65.0, stable, beta, nightly] 94 | cc: 95 | - aarch64-linux-android 96 | - i686-pc-windows-gnu 97 | - i686-unknown-freebsd 98 | - i686-unknown-linux-gnu 99 | - wasm32-wasip1 100 | - x86_64-apple-darwin 101 | steps: 102 | - uses: taiki-e/checkout-action@v1 103 | - uses: taiki-e/install-action@v2 104 | - run: rustup set profile minimal 105 | - run: rustup toolchain install ${{ matrix.tc }} 106 | - run: rustup target add ${{ matrix.cc }} 107 | - run: cargo build --all-features --target=${{ matrix.cc }} -p whoami 108 | cross-compile-ios: 109 | runs-on: ${{ matrix.os }} 110 | strategy: 111 | matrix: 112 | os: [macos-latest] 113 | tc: [1.65.0, stable, beta, nightly] 114 | cc: [aarch64-apple-ios] 115 | steps: 116 | - uses: taiki-e/checkout-action@v1 117 | - uses: taiki-e/install-action@v2 118 | - run: rustup set profile minimal 119 | - run: rustup toolchain install ${{ matrix.tc }} 120 | - run: rustup target add ${{ matrix.cc }} 121 | - run: cargo build --all-features --target=${{ matrix.cc }} 122 | cross-compile-wasm: 123 | runs-on: ${{ matrix.os }} 124 | strategy: 125 | matrix: 126 | os: [ubuntu-latest] 127 | tc: [1.65.0, stable, beta, nightly] 128 | cc: [wasm32-unknown-unknown] 129 | steps: 130 | - uses: taiki-e/checkout-action@v1 131 | - uses: taiki-e/install-action@v2 132 | - run: rustup set profile minimal 133 | - run: rustup toolchain install ${{ matrix.tc }} 134 | - run: rustup target add ${{ matrix.cc }} 135 | - run: cargo build --all-features --target=${{ matrix.cc }} 136 | - run: cargo update 137 | - run: cargo build --no-default-features --target=${{ matrix.cc }} 138 | - run: RUSTFLAGS="--cfg daku" cargo build --target=${{ matrix.cc }} 139 | cross-compile-illumos: 140 | runs-on: ${{ matrix.os }} 141 | strategy: 142 | matrix: 143 | os: [ubuntu-latest] 144 | tc: [1.65.0, stable, beta, nightly] 145 | cc: [x86_64-unknown-illumos] 146 | steps: 147 | - uses: taiki-e/checkout-action@v1 148 | - uses: taiki-e/install-action@v2 149 | - run: rustup set profile minimal 150 | - run: rustup toolchain install ${{ matrix.tc }} 151 | - run: rustup target add ${{ matrix.cc }} 152 | - run: cargo build --all-features --target=${{ matrix.cc }} -p whoami 153 | cross-compile-redox: 154 | runs-on: ${{ matrix.os }} 155 | strategy: 156 | matrix: 157 | os: [ubuntu-latest] 158 | tc: [1.65.0, stable, beta, nightly] 159 | cc: [x86_64-unknown-redox] 160 | steps: 161 | - uses: taiki-e/checkout-action@v1 162 | - uses: taiki-e/install-action@v2 163 | - run: rustup set profile minimal 164 | - run: rustup toolchain install ${{ matrix.tc }} 165 | - run: rustup target add ${{ matrix.cc }} 166 | - run: cargo build --all-features --target=${{ matrix.cc }} -p whoami 167 | clippy-cross-compile: 168 | runs-on: ${{ matrix.os }} 169 | strategy: 170 | matrix: 171 | os: [ubuntu-latest] 172 | tc: [nightly] 173 | cc: 174 | - x86_64-unknown-illumos 175 | - x86_64-unknown-redox 176 | steps: 177 | - uses: taiki-e/checkout-action@v1 178 | - uses: taiki-e/install-action@v2 179 | - run: rustup set profile minimal 180 | - run: rustup toolchain install ${{ matrix.tc }} 181 | - run: rustup target add ${{ matrix.cc }} 182 | - run: cargo clippy --all-features --target=${{ matrix.cc }} -- -D warnings 183 | -------------------------------------------------------------------------------- /whoami/src/os/web.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] 2 | compile_error!("Unexpected pointer width for target platform"); 3 | 4 | use std::{ 5 | ffi::OsString, 6 | io::{Error, ErrorKind}, 7 | }; 8 | 9 | use web_sys::window; 10 | 11 | use crate::{ 12 | os::{Os, Target}, 13 | Arch, DesktopEnv, Language, LanguagePrefs, Platform, Result, 14 | }; 15 | 16 | // Get the user agent 17 | fn user_agent() -> Option { 18 | window()?.navigator().user_agent().ok() 19 | } 20 | 21 | // Get the document domain 22 | fn document_domain() -> Option { 23 | window()?.document()?.location()?.hostname().ok() 24 | } 25 | 26 | // Get the browser name and version for desktop environment 27 | fn browser_info() -> String { 28 | let orig_string = user_agent().unwrap_or_default(); 29 | let start = if let Some(s) = orig_string.rfind(' ') { 30 | s 31 | } else { 32 | return "Unknown Browser".to_string(); 33 | }; 34 | let string = orig_string 35 | .get(start + 1..) 36 | .unwrap_or("Unknown Browser") 37 | .replace('/', " "); 38 | let string = if let Some(s) = string.rfind("Safari") { 39 | if let Some(s) = orig_string.rfind("Chrome") { 40 | if let Some(e) = orig_string.get(s..).unwrap_or_default().find(' ') 41 | { 42 | orig_string 43 | .get(s..) 44 | .unwrap_or("Chrome") 45 | .get(..e) 46 | .unwrap_or("Chrome") 47 | .replace('/', " ") 48 | } else { 49 | "Chrome".to_string() 50 | } 51 | } else if orig_string.contains("Linux") { 52 | "GNOME Web".to_string() 53 | } else { 54 | string.get(s..).unwrap_or("Safari").replace('/', " ") 55 | } 56 | } else if string.contains("Edg ") { 57 | string.replace("Edg ", "Edge ") 58 | } else if string.contains("OPR ") { 59 | string.replace("OPR ", "Opera ") 60 | } else { 61 | string 62 | }; 63 | 64 | string 65 | } 66 | 67 | impl Target for Os { 68 | fn lang_prefs(self) -> Result { 69 | if let Some(window) = window() { 70 | let langs = window 71 | .navigator() 72 | .languages() 73 | .to_vec() 74 | .into_iter() 75 | .filter_map(|l| l.as_string().map(Language::from)) 76 | .collect::>(); 77 | Ok(LanguagePrefs { 78 | fallbacks: langs, 79 | ..Default::default() 80 | }) 81 | } else { 82 | Err(Error::new( 83 | ErrorKind::NotFound, 84 | "Failed to retrieve languages: Window object is missing", 85 | )) 86 | } 87 | } 88 | 89 | fn realname(self) -> Result { 90 | Ok("Anonymous".to_string().into()) 91 | } 92 | 93 | fn username(self) -> Result { 94 | Ok("anonymous".to_string().into()) 95 | } 96 | 97 | fn devicename(self) -> Result { 98 | Ok("Browser".to_string().into()) 99 | } 100 | 101 | fn hostname(self) -> Result { 102 | document_domain() 103 | .filter(|x| !x.is_empty()) 104 | .ok_or_else(|| { 105 | Error::new( 106 | ErrorKind::NotFound, 107 | "Domain missing, failed to retrieve document domain from window" 108 | ) 109 | }) 110 | } 111 | 112 | fn distro(self) -> Result { 113 | let string = user_agent() 114 | .ok_or_else(|| Error::from(ErrorKind::PermissionDenied))?; 115 | let err = || Error::new(ErrorKind::InvalidData, "Parsing failed"); 116 | let begin = string.find('(').ok_or_else(err)?; 117 | let end = string.find(')').ok_or_else(err)?; 118 | let string = &string[begin + 1..end]; 119 | 120 | Ok(if string.contains("Win32") || string.contains("Win64") { 121 | let begin = if let Some(b) = string.find("NT") { 122 | b 123 | } else { 124 | return Ok("Windows".to_string()); 125 | }; 126 | let end = if let Some(e) = string.find('.') { 127 | e 128 | } else { 129 | return Ok("Windows".to_string()); 130 | }; 131 | let string = &string[begin + 3..end]; 132 | 133 | format!("Windows {string}") 134 | } else if string.contains("Linux") { 135 | let string = if string.contains("X11") || string.contains("Wayland") 136 | { 137 | let begin = if let Some(b) = string.find(';') { 138 | b 139 | } else { 140 | return Ok("Unknown Linux".to_string()); 141 | }; 142 | &string[begin + 2..] 143 | } else { 144 | string 145 | }; 146 | 147 | if string.starts_with("Linux") { 148 | "Unknown Linux".to_string() 149 | } else { 150 | let end = if let Some(e) = string.find(';') { 151 | e 152 | } else { 153 | return Ok("Unknown Linux".to_string()); 154 | }; 155 | string[..end].to_string() 156 | } 157 | } else if let Some(begin) = string.find("Mac OS X") { 158 | if let Some(end) = string[begin..].find(';') { 159 | string[begin..begin + end].to_string() 160 | } else { 161 | string[begin..].to_string().replace('_', ".") 162 | } 163 | } else { 164 | string.to_string() 165 | }) 166 | } 167 | 168 | #[inline(always)] 169 | fn desktop_env(self) -> Option { 170 | Some(DesktopEnv::WebBrowser(browser_info())) 171 | } 172 | 173 | fn platform(self) -> Platform { 174 | let string = user_agent().unwrap_or_default(); 175 | let begin = if let Some(b) = string.find('(') { 176 | b 177 | } else { 178 | return Platform::Unknown("Unknown".to_string()); 179 | }; 180 | let end = if let Some(e) = string.find(')') { 181 | e 182 | } else { 183 | return Platform::Unknown("Unknown".to_string()); 184 | }; 185 | let string = &string[begin + 1..end]; 186 | 187 | if string.contains("Win32") || string.contains("Win64") { 188 | Platform::Windows 189 | } else if string.contains("Linux") { 190 | Platform::Linux 191 | } else if string.contains("Mac OS X") { 192 | Platform::Mac 193 | } else { 194 | Platform::Unknown(string.to_string()) 195 | } 196 | } 197 | 198 | #[inline(always)] 199 | fn arch(self) -> Result { 200 | Ok(if cfg!(target_pointer_width = "64") { 201 | Arch::Wasm64 202 | } else { 203 | Arch::Wasm32 204 | }) 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /whoami/src/language.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | 3 | /// Country code for a [`Language`] dialect 4 | /// 5 | /// Uses 6 | #[non_exhaustive] 7 | #[repr(u32)] 8 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 9 | pub enum Country { 10 | // FIXME: V2: u32::from_ne_bytes for country codes, with `\0` for unused 11 | // FIXME: Add aliases up to 3-4 letters, but hidden 12 | /// Any dialect 13 | Any, 14 | /// `US`: United States of America 15 | #[doc(hidden)] 16 | Us, 17 | } 18 | 19 | impl Display for Country { 20 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 21 | f.write_str(match self { 22 | Self::Any => "**", 23 | Self::Us => "US", 24 | }) 25 | } 26 | } 27 | 28 | /// A spoken language 29 | /// 30 | /// Use [`ToString::to_string()`] to convert to string of two letter lowercase 31 | /// language code followed an forward slash and uppercase country code (example: 32 | /// `en/US`). 33 | /// 34 | /// Language codes defined in ISO 639 , 35 | /// Country codes defined in ISO 3166 36 | #[non_exhaustive] 37 | #[derive(Clone, Eq, PartialEq, Debug)] 38 | // #[allow(variant_size_differences)] 39 | pub enum Language { 40 | #[doc(hidden)] 41 | __(Box), 42 | /// `en`: English 43 | #[doc(hidden)] 44 | En(Country), 45 | /// `es`: Spanish 46 | #[doc(hidden)] 47 | Es(Country), 48 | } 49 | 50 | impl Language { 51 | /// Retrieve the country code for this language dialect. 52 | pub fn country(&self) -> Country { 53 | match self { 54 | Self::__(_) => Country::Any, 55 | Self::En(country) | Self::Es(country) => *country, 56 | } 57 | } 58 | } 59 | 60 | // Reads an `language_COUNTRY.Encoding` formatted string into a Language where 61 | // language is a two letter language code and country is a two letter country 62 | // code. 63 | impl> From for Language { 64 | // FIXME: Could do less allocation 65 | fn from(item: T) -> Self { 66 | let lang = item 67 | .as_ref() 68 | .split_terminator('.') 69 | .next() 70 | .unwrap_or_default() 71 | .replace(|x| ['_', '-'].contains(&x), "/"); 72 | Self::__(Box::new(lang)) 73 | } 74 | } 75 | 76 | impl Display for Language { 77 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 78 | match self { 79 | Self::__(code) => f.write_str(code.as_str()), 80 | Self::En(country) => { 81 | if *country != Country::Any { 82 | f.write_str("en/")?; 83 | ::fmt(country, f) 84 | } else { 85 | f.write_str("en") 86 | } 87 | } 88 | Self::Es(country) => { 89 | if *country != Country::Any { 90 | f.write_str("es/")?; 91 | ::fmt(country, f) 92 | } else { 93 | f.write_str("es") 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | /// [`Language`] preferences for a user. 101 | /// 102 | /// Fields are sorted in order of the user's preference. 103 | /// 104 | /// POSIX locale values and GNU nonstandard categories are defined in 105 | /// . Windows locale values 106 | /// are defined in . 107 | #[derive(Debug, Clone, Default)] 108 | #[non_exhaustive] 109 | pub struct LanguagePrefs { 110 | /// Determines general user language preference, should be used in 111 | /// situations which are not encompassed by other [`LanguagePrefs`]. 112 | pub(crate) fallbacks: Vec, 113 | 114 | /// Determines collation rules used for sorting and regular expressions, 115 | /// including character equivalence classes and multicharacter collating 116 | /// elements. 117 | pub(crate) collation: Option, 118 | 119 | /// Determines the interpretation of byte sequences as characters (e.g., 120 | /// single versus multibyte characters), character classifications (e.g., 121 | /// alphabetic or digit), and the behavior of character classes. 122 | pub(crate) char_classes: Option, 123 | 124 | /// Determines the formatting used for monetary-related numeric values, 125 | /// i.e, the way numbers are usually printed with details such as 126 | /// decimal point versus decimal comma. 127 | pub(crate) monetary: Option, 128 | 129 | /// Determines the language in which messages are 130 | /// displayed and what an affirmative or negative answer looks 131 | /// like. 132 | pub(crate) messages: Option, 133 | 134 | /// Determines the formatting rules used for nonmonetary numeric values. 135 | /// For example, the thousands separator and the radix character. 136 | pub(crate) numeric: Option, 137 | 138 | /// Determines format and contents of date and time information. 139 | pub(crate) time: Option, 140 | } 141 | 142 | impl Display for LanguagePrefs { 143 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 144 | let langs: [(&str, Vec); 6] = [ 145 | ("Collation", self.collation_langs().collect()), 146 | ("CharClasses", self.char_class_langs().collect()), 147 | ("Monetary", self.monetary_langs().collect()), 148 | ("Messages", self.message_langs().collect()), 149 | ("Numeric", self.numeric_langs().collect()), 150 | ("Time", self.time_langs().collect()), 151 | ]; 152 | for (i, (name, langs)) in langs.iter().enumerate() { 153 | if i != 0 { 154 | f.write_str(",")?; 155 | } 156 | write!(f, "{name}=")?; 157 | for (j, lang) in langs.iter().enumerate() { 158 | if j != 0 { 159 | f.write_str(":")?; 160 | } 161 | write!(f, "{lang}")?; 162 | } 163 | } 164 | 165 | Ok(()) 166 | } 167 | } 168 | 169 | impl LanguagePrefs { 170 | fn chain_fallbacks<'a>( 171 | &'a self, 172 | l: &Option, 173 | ) -> impl Iterator + 'a { 174 | l.clone().into_iter().chain(self.fallbacks.iter().cloned()) 175 | } 176 | 177 | /// Returns the collation langs of this [`LanguagePrefs`] in order of the 178 | /// user's preference 179 | /// 180 | /// Collation langs are used for sorting and regular expressions, 181 | /// including character equivalence classes and multicharacter collating 182 | /// elements. 183 | pub fn collation_langs(&self) -> impl Iterator + '_ { 184 | self.chain_fallbacks(&self.collation) 185 | } 186 | 187 | /// Returns the char class langs of this [`LanguagePrefs`] in order of the 188 | /// user's preference 189 | /// 190 | /// Char class langs determine the interpretation of byte sequences as 191 | /// characters (e.g., single versus multibyte characters), character 192 | /// classifications (e.g., alphabetic or digit), and the behavior of 193 | /// character classes. 194 | pub fn char_class_langs(&self) -> impl Iterator + '_ { 195 | self.chain_fallbacks(&self.char_classes) 196 | } 197 | 198 | /// Returns the monetary langs of this [`LanguagePrefs`] in order of the 199 | /// user's preference 200 | /// 201 | /// Monetary langs determine the formatting used for monetary-related 202 | /// numeric values, i.e, the way numbers are usually printed with details 203 | /// such as decimal point versus decimal comma. 204 | /// 205 | /// For nonmonetary numeric values, see [`LanguagePrefs::numeric_langs`] 206 | pub fn monetary_langs(&self) -> impl Iterator + '_ { 207 | self.chain_fallbacks(&self.monetary) 208 | } 209 | 210 | /// Returns the messages langs of this [`LanguagePrefs`] in order of the 211 | /// user's preference 212 | /// 213 | /// Message determines the language in which messages are 214 | /// displayed and what an affirmative or negative answer looks 215 | /// like. 216 | pub fn message_langs(&self) -> impl Iterator + '_ { 217 | self.chain_fallbacks(&self.messages) 218 | } 219 | 220 | /// Returns the numeric langs of this [`LanguagePrefs`] in order of the 221 | /// user's preference 222 | /// 223 | /// Numeric langs determine the formatting rules used for nonmonetary 224 | /// numeric values. For example, the thousands separator and the radix 225 | /// character. 226 | /// 227 | /// For monetary formatting, see [`LanguagePrefs::monetary_langs`]. 228 | pub fn numeric_langs(&self) -> impl Iterator + '_ { 229 | self.chain_fallbacks(&self.numeric) 230 | } 231 | 232 | /// Returns the time langs of this [`LanguagePrefs`] in order of the user's 233 | /// preference 234 | /// 235 | /// Time langs determine format and contents of date and time information. 236 | pub fn time_langs(&self) -> impl Iterator + '_ { 237 | self.chain_fallbacks(&self.time) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /LICENSE_APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /whoami/src/os/windows.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_char, c_int, c_uchar, c_ulong, c_ushort, c_void, OsString}, 3 | io::{Error, ErrorKind}, 4 | mem::{self, MaybeUninit}, 5 | os::windows::ffi::OsStringExt, 6 | ptr, 7 | }; 8 | 9 | use crate::{ 10 | conversions, 11 | os::{Os, Target}, 12 | Arch, DesktopEnv, Language, LanguagePrefs, Platform, Result, 13 | }; 14 | 15 | #[repr(C)] 16 | struct OsVersionInfoEx { 17 | os_version_info_size: c_ulong, 18 | major_version: c_ulong, 19 | minor_version: c_ulong, 20 | build_number: c_ulong, 21 | platform_id: c_ulong, 22 | sz_csd_version: [u16; 128], 23 | service_pack_major: c_ushort, 24 | service_pack_minor: c_ushort, 25 | suite_mask: c_ushort, 26 | product_type: c_uchar, 27 | reserved: c_uchar, 28 | } 29 | 30 | // Source: 31 | // https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info#syntax 32 | #[repr(C)] 33 | struct SystemInfo { 34 | processor_architecture: c_ushort, 35 | reserved: c_ushort, 36 | dw_page_size: c_ulong, 37 | minimum_application_address: *mut c_void, 38 | maximum_application_address: *mut c_void, 39 | active_processor_mask: usize, 40 | number_of_processors: c_ulong, 41 | processor_type: c_ulong, 42 | allocation_granularity: c_ulong, 43 | processor_level: c_ushort, 44 | processor_revision: c_ushort, 45 | } 46 | 47 | #[allow(unused)] 48 | #[repr(C)] 49 | #[derive(Copy, Clone)] 50 | enum ExtendedNameFormat { 51 | Unknown = 0, // Nothing 52 | FullyQualifiedDN = 1, // Nothing 53 | SamCompatible = 2, // Hostname Followed By Username 54 | Display = 3, // Full Name 55 | UniqueId = 6, // Nothing 56 | Canonical = 7, // Nothing 57 | UserPrincipal = 8, // Nothing 58 | CanonicalEx = 9, // Nothing 59 | ServicePrincipal = 10, // Nothing 60 | DnsDomain = 12, // Nothing 61 | GivenName = 13, // Nothing 62 | Surname = 14, // Nothing 63 | } 64 | 65 | #[allow(unused)] 66 | #[repr(C)] 67 | enum ComputerNameFormat { 68 | NetBIOS, // All caps hostname 69 | DnsHostname, // Hostname 70 | DnsDomain, // Nothing or domain 71 | DnsFullyQualified, // Hostname with, for example, .com 72 | PhysicalNetBIOS, // All caps name 73 | PhysicalDnsHostname, // Hostname 74 | PhysicalDnsDomain, // Nothing or domain 75 | PhysicalDnsFullyQualified, // Hostname with, for example, .com 76 | Max, 77 | } 78 | 79 | const ERR_MORE_DATA: i32 = 0xEA; 80 | const ERR_INSUFFICIENT_BUFFER: i32 = 0x7A; 81 | const ERR_NONE_MAPPED: i32 = 0x534; 82 | 83 | #[link(name = "secur32")] 84 | extern "system" { 85 | fn GetUserNameExW( 86 | a: ExtendedNameFormat, 87 | b: *mut c_char, 88 | c: *mut c_ulong, 89 | ) -> c_uchar; 90 | } 91 | 92 | #[link(name = "advapi32")] 93 | extern "system" { 94 | fn GetUserNameW(a: *mut c_char, b: *mut c_ulong) -> c_int; 95 | } 96 | 97 | #[link(name = "kernel32")] 98 | extern "system" { 99 | fn GetUserPreferredUILanguages( 100 | dw_flags: c_ulong, 101 | pul_num_languages: *mut c_ulong, 102 | pwsz_languages_buffer: *mut u16, 103 | pcch_languages_buffer: *mut c_ulong, 104 | ) -> c_int; 105 | fn GetNativeSystemInfo(system_info: *mut SystemInfo); 106 | fn GetComputerNameExW( 107 | a: ComputerNameFormat, 108 | b: *mut c_char, 109 | c: *mut c_ulong, 110 | ) -> c_int; 111 | } 112 | 113 | fn username() -> Result { 114 | // Step 1. Retreive the entire length of the username 115 | let mut size = 0; 116 | let fail = unsafe { GetUserNameW(ptr::null_mut(), &mut size) == 0 }; 117 | assert!(fail); 118 | 119 | if Error::last_os_error().raw_os_error() != Some(ERR_INSUFFICIENT_BUFFER) { 120 | return Err(Error::last_os_error()); 121 | } 122 | 123 | // Step 2. Allocate memory to put the Windows (UTF-16) string. 124 | let mut name: Vec = 125 | Vec::with_capacity(size.try_into().unwrap_or(usize::MAX)); 126 | size = name.capacity().try_into().unwrap_or(u32::MAX); 127 | let orig_size = size; 128 | let fail = 129 | unsafe { GetUserNameW(name.as_mut_ptr().cast(), &mut size) == 0 }; 130 | if fail { 131 | return Err(Error::last_os_error()); 132 | } 133 | debug_assert_eq!(orig_size, size); 134 | unsafe { 135 | name.set_len(size.try_into().unwrap_or(usize::MAX)); 136 | } 137 | let terminator = name.pop(); // Remove Trailing Null 138 | debug_assert_eq!(terminator, Some(0u16)); 139 | 140 | // Step 3. Convert to Rust String 141 | Ok(OsString::from_wide(&name)) 142 | } 143 | 144 | fn extended_name(format: ExtendedNameFormat) -> Result { 145 | // Step 1. Retrieve the entire length of the username 146 | let mut buf_size = 0; 147 | let fail = 148 | unsafe { GetUserNameExW(format, ptr::null_mut(), &mut buf_size) == 0 }; 149 | 150 | assert!(fail); 151 | 152 | let last_err = Error::last_os_error().raw_os_error(); 153 | 154 | if last_err == Some(ERR_NONE_MAPPED) { 155 | return Err(super::err_missing_record()); 156 | } 157 | 158 | if last_err != Some(ERR_MORE_DATA) { 159 | return Err(Error::last_os_error()); 160 | } 161 | 162 | // Step 2. Allocate memory to put the Windows (UTF-16) string. 163 | let mut name: Vec = 164 | Vec::with_capacity(buf_size.try_into().unwrap_or(usize::MAX)); 165 | let mut name_len = name.capacity().try_into().unwrap_or(u32::MAX); 166 | let fail = unsafe { 167 | GetUserNameExW(format, name.as_mut_ptr().cast(), &mut name_len) == 0 168 | }; 169 | if fail { 170 | return Err(Error::last_os_error()); 171 | } 172 | 173 | assert_eq!(buf_size, name_len + 1); 174 | 175 | unsafe { name.set_len(name_len.try_into().unwrap_or(usize::MAX)) }; 176 | 177 | // Step 3. Convert to Rust String 178 | Ok(OsString::from_wide(&name)) 179 | } 180 | 181 | impl Target for Os { 182 | #[inline(always)] 183 | fn lang_prefs(self) -> Result { 184 | let mut num_languages = 0; 185 | let mut buffer_size = 0; 186 | let mut buffer; 187 | 188 | unsafe { 189 | assert_ne!( 190 | GetUserPreferredUILanguages( 191 | 0x08, /* MUI_LANGUAGE_NAME */ 192 | &mut num_languages, 193 | ptr::null_mut(), // List of languages. 194 | &mut buffer_size, 195 | ), 196 | 0 197 | ); 198 | 199 | buffer = Vec::with_capacity(buffer_size as usize); 200 | 201 | assert_ne!( 202 | GetUserPreferredUILanguages( 203 | 0x08, /* MUI_LANGUAGE_NAME */ 204 | &mut num_languages, 205 | buffer.as_mut_ptr(), // List of languages. 206 | &mut buffer_size, 207 | ), 208 | 0 209 | ); 210 | 211 | buffer.set_len(buffer_size as usize); 212 | } 213 | 214 | // We know it ends in two null characters. 215 | buffer.pop(); 216 | buffer.pop(); 217 | 218 | // Combine into a single string 219 | Ok(LanguagePrefs { 220 | fallbacks: String::from_utf16_lossy(&buffer) 221 | .split('\0') 222 | .map(Language::from) 223 | .collect::>(), 224 | ..Default::default() 225 | }) 226 | } 227 | 228 | fn realname(self) -> Result { 229 | extended_name(ExtendedNameFormat::Display) 230 | } 231 | 232 | fn username(self) -> Result { 233 | username() 234 | } 235 | 236 | fn devicename(self) -> Result { 237 | // Step 1. Retreive the entire length of the device name 238 | let mut size = 0; 239 | let fail = unsafe { 240 | // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER 241 | GetComputerNameExW( 242 | ComputerNameFormat::DnsHostname, 243 | ptr::null_mut(), 244 | &mut size, 245 | ) == 0 246 | }; 247 | 248 | assert!(fail); 249 | 250 | if Error::last_os_error().raw_os_error() != Some(ERR_MORE_DATA) { 251 | return Err(Error::last_os_error()); 252 | } 253 | 254 | // Step 2. Allocate memory to put the Windows (UTF-16) string. 255 | let mut name: Vec = 256 | Vec::with_capacity(size.try_into().unwrap_or(usize::MAX)); 257 | let mut size = name.capacity().try_into().unwrap_or(u32::MAX); 258 | 259 | if unsafe { 260 | GetComputerNameExW( 261 | ComputerNameFormat::DnsHostname, 262 | name.as_mut_ptr().cast(), 263 | &mut size, 264 | ) == 0 265 | } { 266 | return Err(Error::last_os_error()); 267 | } 268 | 269 | unsafe { 270 | name.set_len(size.try_into().unwrap_or(usize::MAX)); 271 | } 272 | 273 | // Step 3. Convert to Rust String 274 | Ok(OsString::from_wide(&name)) 275 | } 276 | 277 | fn hostname(self) -> Result { 278 | // Step 1. Retreive the entire length of the username 279 | let mut size = 0; 280 | let fail = unsafe { 281 | // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER 282 | GetComputerNameExW( 283 | ComputerNameFormat::PhysicalDnsHostname, 284 | ptr::null_mut(), 285 | &mut size, 286 | ) == 0 287 | }; 288 | 289 | assert!(fail); 290 | 291 | if Error::last_os_error().raw_os_error() != Some(ERR_MORE_DATA) { 292 | return Err(Error::last_os_error()); 293 | } 294 | 295 | // Step 2. Allocate memory to put the Windows (UTF-16) string. 296 | let mut name: Vec = 297 | Vec::with_capacity(size.try_into().unwrap_or(usize::MAX)); 298 | let mut size = name.capacity().try_into().unwrap_or(u32::MAX); 299 | 300 | if unsafe { 301 | GetComputerNameExW( 302 | ComputerNameFormat::PhysicalDnsHostname, 303 | name.as_mut_ptr().cast(), 304 | &mut size, 305 | ) == 0 306 | } { 307 | return Err(Error::last_os_error()); 308 | } 309 | 310 | unsafe { 311 | name.set_len(size.try_into().unwrap_or(usize::MAX)); 312 | } 313 | 314 | // Step 3. Convert to Rust String 315 | conversions::string_from_os(OsString::from_wide(&name)) 316 | } 317 | 318 | fn distro(self) -> Result { 319 | // Due to MingW Limitations, we must dynamically load ntdll.dll 320 | extern "system" { 321 | fn LoadLibraryExW( 322 | filename: *const u16, 323 | hfile: *mut c_void, 324 | dwflags: c_ulong, 325 | ) -> *mut c_void; 326 | fn FreeLibrary(hmodule: *mut c_void) -> i32; 327 | fn GetProcAddress( 328 | hmodule: *mut c_void, 329 | procname: *const c_char, 330 | ) -> *mut c_void; 331 | } 332 | 333 | let mut path = "ntdll.dll\0".encode_utf16().collect::>(); 334 | let path = path.as_mut_ptr(); 335 | 336 | let inst = 337 | unsafe { LoadLibraryExW(path, ptr::null_mut(), 0x0000_0800) }; 338 | 339 | if inst.is_null() { 340 | return Err(Error::last_os_error()); 341 | } 342 | 343 | let mut path = "RtlGetVersion\0".bytes().collect::>(); 344 | let path = path.as_mut_ptr().cast(); 345 | let func = unsafe { GetProcAddress(inst, path) }; 346 | 347 | if func.is_null() { 348 | if unsafe { FreeLibrary(inst) } == 0 { 349 | return Err(Error::last_os_error()); 350 | } 351 | 352 | return Err(Error::last_os_error()); 353 | } 354 | 355 | let get_version: unsafe extern "system" fn( 356 | a: *mut OsVersionInfoEx, 357 | ) -> u32 = unsafe { mem::transmute(func) }; 358 | 359 | let mut version = MaybeUninit::::zeroed(); 360 | 361 | // FIXME `mem::size_of` seemingly false positive for lint 362 | #[allow(unused_qualifications)] 363 | let version = unsafe { 364 | (*version.as_mut_ptr()).os_version_info_size = 365 | mem::size_of::() as u32; 366 | get_version(version.as_mut_ptr()); 367 | 368 | if FreeLibrary(inst) == 0 { 369 | return Err(Error::last_os_error()); 370 | } 371 | 372 | version.assume_init() 373 | }; 374 | 375 | let product = match version.product_type { 376 | 1 => "Workstation", 377 | 2 => "Domain Controller", 378 | 3 => "Server", 379 | _ => "Unknown", 380 | }; 381 | 382 | Ok(format!( 383 | "Windows {}.{}.{} ({})", 384 | version.major_version, 385 | version.minor_version, 386 | version.build_number, 387 | product, 388 | )) 389 | } 390 | 391 | #[inline(always)] 392 | fn desktop_env(self) -> Option { 393 | Some(DesktopEnv::Windows) 394 | } 395 | 396 | #[inline(always)] 397 | fn platform(self) -> Platform { 398 | Platform::Windows 399 | } 400 | 401 | #[inline(always)] 402 | fn arch(self) -> Result { 403 | fn proc(processor_type: c_ulong) -> Result { 404 | Ok(match processor_type { 405 | // PROCESSOR_INTEL_386 406 | 386 => Arch::I386, 407 | // PROCESSOR_INTEL_486 408 | 486 => Arch::Unknown("I486".to_string()), 409 | // PROCESSOR_INTEL_PENTIUM 410 | 586 => Arch::I586, 411 | // PROCESSOR_INTEL_IA64 412 | 2200 => Arch::Unknown("IA64".to_string()), 413 | // PROCESSOR_AMD_X8664 414 | 8664 => Arch::X64, 415 | v => return Err(v), 416 | }) 417 | } 418 | 419 | let buf: SystemInfo = unsafe { 420 | let mut buf = MaybeUninit::uninit(); 421 | GetNativeSystemInfo(buf.as_mut_ptr()); 422 | buf.assume_init() 423 | }; 424 | 425 | // Supported architectures, source: 426 | // https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info#members 427 | Ok(match buf.processor_architecture { 428 | // PROCESSOR_ARCHITECTURE_INTEL 429 | 0 => Arch::I686, 430 | // PROCESSOR_ARCHITECTURE_ARM 431 | 5 => Arch::ArmV6, 432 | // PROCESSOR_ARCHITECTURE_IA64 433 | 6 => Arch::Unknown("IA64".to_string()), 434 | // PROCESSOR_ARCHITECTURE_AMD64 435 | 9 => Arch::X64, 436 | // PROCESSOR_ARCHITECTURE_ARM64 437 | 12 => Arch::Arm64, 438 | // PROCESSOR_ARCHITECTURE_UNKNOWN 439 | 0xFFFF => proc(buf.processor_type).map_err(|e| { 440 | Error::new(ErrorKind::InvalidData, format!("Unknown arch: {e}")) 441 | })?, 442 | invalid => proc(buf.processor_type).map_err(|e| { 443 | Error::new( 444 | ErrorKind::InvalidData, 445 | format!("Invalid arch: {invalid}/{e}"), 446 | ) 447 | })?, 448 | }) 449 | } 450 | 451 | #[inline(always)] 452 | fn account(self) -> Result { 453 | match extended_name(ExtendedNameFormat::UserPrincipal) { 454 | Ok(name) => Ok(name), 455 | Err(e) if e.kind() == ErrorKind::NotFound => username(), 456 | Err(e) => Err(e), 457 | } 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/release.md: -------------------------------------------------------------------------------- 1 | # Release vX.Y.Z 2 | 3 | 4 | 5 | ## Regression testing plan for all platforms 6 | 7 | - [ ] Testing complete on Fedora Silverblue 41. 8 |
Linux / Fedora Silverblue Testing 9 | Open a terminal (outside of toolbx), and: 10 | 11 | ```rust 12 | cargo run --example whoami-demo 13 | cargo run --example whoami-demo --release 14 | cargo run --example os-strings 15 | cargo run --example os-strings --release 16 | ``` 17 | 18 | Expect to see something like: 19 | 20 | ```console 21 | WhoAmI 1.6.0 22 | 23 | User's Language whoami::langs(): en/US 24 | User's Name whoami::realname(): Jeryn Aldaron Lau 25 | User's Username whoami::username(): jeron 26 | User's Username whoami::fallible::account(): jeron 27 | Device's Pretty Name whoami::devicename(): ¡Zeatei~ 28 | Device's Hostname whoami::fallible::hostname(): zeatei 29 | Device's Platform whoami::platform(): Linux 30 | Device's OS Distro whoami::distro(): Fedora Linux 41.20250305.0 (Silverblue) 31 | Device's Desktop Env. whoami::desktop_env(): Gnome 32 | Device's CPU Arch whoami::arch(): x86_64 33 | ``` 34 | 35 | ```console 36 | WhoAmI 1.6.0 37 | 38 | User's Language whoami::langs(): "en/US" 39 | User's Name whoami::realname_os(): "Jeryn Aldaron Lau" 40 | User's Username whoami::username_os(): "jeron" 41 | User's Account whoami::fallible::account_os(): "jeron" 42 | Device's Pretty Name whoami::devicename_os(): "¡Zeatei~" 43 | Device's Hostname whoami::fallible::hostname(): "zeatei" 44 | Device's Platform whoami::platform(): Linux 45 | Device's OS Distro whoami::distro(): "Fedora Linux 41.20250305.0 (Silverblue)" 46 | Device's Desktop Env. whoami::desktop_env(): Gnome 47 | Device's CPU Arch whoami::arch(): X64 48 | ``` 49 | 50 | Now, `toolbx enter`, and do the same. Expecting something like: 51 | 52 | ```console 53 | WhoAmI 1.6.0 54 | 55 | User's Language whoami::langs(): en/US 56 | User's Name whoami::realname(): Jeron Lau 57 | User's Username whoami::username(): jeron 58 | User's Username whoami::fallible::account(): jeron 59 | Device's Pretty Name whoami::devicename(): toolbx 60 | Device's Hostname whoami::fallible::hostname(): toolbx 61 | Device's Platform whoami::platform(): Linux 62 | Device's OS Distro whoami::distro(): Fedora Linux 41 (Toolbx Container Image) 63 | Device's Desktop Env. whoami::desktop_env(): Gnome 64 | Device's CPU Arch whoami::arch(): x86_64 65 | ``` 66 | 67 | ```console 68 | WhoAmI 1.6.0 69 | 70 | User's Language whoami::langs(): "en/US" 71 | User's Name whoami::realname_os(): "Jeron Lau" 72 | User's Username whoami::username_os(): "jeron" 73 | User's Account whoami::fallible::account_os(): "jeron" 74 | Device's Pretty Name whoami::devicename_os(): "toolbx" 75 | Device's Hostname whoami::fallible::hostname(): "toolbx" 76 | Device's Platform whoami::platform(): Linux 77 | Device's OS Distro whoami::distro(): "Fedora Linux 41 (Toolbx Container Image)" 78 | Device's Desktop Env. whoami::desktop_env(): Gnome 79 | Device's CPU Arch whoami::arch(): X64 80 | ``` 81 |
82 | - [ ] Testing complete on Ubuntu Linux 24.04.1 LTS 83 |
Ubuntu Virtualized on Fedora Silverblue Testing 84 | 85 | 86 | Install from file within GNOME boxes (keep all defaults). 87 | 88 | In Ubuntu Installer, do default installation. 89 | 90 | ```shell 91 | sudo apt install git curl gcc tig # y 92 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # 1 93 | source "$HOME/.cargo/env" 94 | ``` 95 | 96 | Clone whoami, open a terminal, and run: 97 | 98 | ```rust 99 | cargo run --example whoami-demo 100 | cargo run --example whoami-demo --release 101 | ``` 102 | 103 | Expect to see something like: 104 | 105 | ```console 106 | WhoAmI 1.6.0 107 | 108 | User's Language whoami::langs(): en/US 109 | User's Name whoami::realname(): Jeron Lau 110 | User's Username whoami::username(): aldaron 111 | User's Username whoami::fallible::account(): aldaron 112 | Device's Pretty Name whoami::devicename(): ubuntu-box 113 | Device's Hostname whoami::fallible::hostname(): ubuntu-box 114 | Device's Platform whoami::platform(): Linux 115 | Device's OS Distro whoami::distro(): Ubuntu 24.04.1 LTS 116 | Device's Desktop Env. whoami::desktop_env(): Ubuntu 117 | Device's CPU Arch whoami::arch(): x86_64 118 | ``` 119 |
120 | - [ ] Testing complete on Windows 10 121 |
Windows Testing 122 | Clone whoami, open Git BASH, and run: 123 | 124 | ```rust 125 | cargo run --example whoami-demo 126 | cargo run --example whoami-demo --release 127 | ``` 128 | 129 | Expect to see something like: 130 | 131 | ```console 132 | WhoAmI 1.5.0 133 | 134 | User's Language whoami::langs(): en/US 135 | User's Name whoami::realname(): Aldaron Lau 136 | User's Username whoami::username(): Aldaron Lau 137 | User's Username whoami::fallible::account(): Aldaron Lau 138 | Device's Pretty Name whoami::devicename(): Helpy-Witch 139 | Device's Hostname whoami::fallible::hostname(): HELPY-WITCH 140 | Device's Platform whoami::platform(): Windows 141 | Device's OS Distro whoami::distro(): Windows 10.0.19044 (Workstation) 142 | Device's Desktop Env. whoami::desktop_env(): Windows 143 | Device's CPU Arch whoami::arch(): x86_64 144 | ``` 145 |
146 | - [ ] Testing complete on macOS Catalina 147 |
MacOS Testing 148 | Clone whoami, and run: 149 | 150 | ```rust 151 | cargo run --example whoami-demo 152 | cargo run --example whoami-demo --release 153 | ``` 154 | 155 | Expect to see something like: 156 | 157 | ```console 158 | WhoAmI 1.5.0 159 | 160 | User's Language whoami::langs(): en/US 161 | User's Name whoami::realname(): Aldaron Lau 162 | User's Username whoami::username(): aldaronlau 163 | User's Username whoami::fallible::account(): aldaronlau 164 | Device's Pretty Name whoami::devicename(): Aldaron’s MacBook Air 165 | Device's Hostname whoami::fallible::hostname(): Aldarons-MacBook-Air.local 166 | Device's Platform whoami::platform(): Mac OS 167 | Device's OS Distro whoami::distro(): Mac OS X 10.15.7 168 | Device's Desktop Env. whoami::desktop_env(): Aqua 169 | Device's CPU Arch whoami::arch(): x86_64 170 | ``` 171 |
172 | - [ ] Testing complete on FreeBSD 173 |
FreeBSD (virtualized on Fedora Silverblue) Testing 174 | Download from within GNOME Boxes. 175 | 176 | Set 4 GiB memory, and 20 GiB Storage limit 177 | 178 | Go through the installation process, keeping all defaults. 179 | 180 | ### Install packages 181 | 182 | Log in as root 183 | 184 | ```shell 185 | pkg install git 186 | ``` 187 | 188 | Log in as you 189 | 190 | ```shell 191 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # 1 192 | . "$HOME/.cargo/env" 193 | git clone https://github.com/ardaku/whoami.git 194 | cd whoami 195 | # run both debug and release 196 | cargo run --example whoami-demo 197 | cargo run --example whoami-demo --release 198 | ``` 199 | 200 | Expect to see something like: 201 | 202 | ```console 203 | WhoAmI 1.5.0 204 | 205 | User's Language whoami::langs(): 206 | User's Name whoami::realname(): Aldaron Lau 207 | User's Username whoami::username(): aldaron 208 | User's Username whoami::fallible::account(): aldaron 209 | Device's Pretty Name whoami::devicename(): bsdtime 210 | Device's Hostname whoami::fallible::hostname(): bsdtime 211 | Device's Platform whoami::platform(): BSD 212 | Device's OS Distro whoami::distro(): FreeBSD 14.0-RELEASE 213 | Device's Desktop Env. whoami::desktop_env(): Unknown: Unknown 214 | Device's CPU Arch whoami::arch(): x86_64 215 | ``` 216 |
217 | - [ ] Testing complete on illumos 218 |
Tribblix (virtualized on Fedora Silverblue) Testing 219 | 220 | 221 | Download the 64-bit x86/x64 standard image. 222 | 223 | Install it in GNOME Boxes (select operating system OpenIndiana Hipster). 224 | 225 | Set 4 GiB memory, and 16 GiB Storage limit 226 | 227 | Login as `jack` (password `jack`) 228 | 229 | ```shell 230 | su - root # password `tribblix` 231 | format # 0, quit 232 | ./live_install -G c1t0d0 develop # replace c1t0d0 with disk 233 | reboot -p 234 | ``` 235 | 236 | Login as `jack` (password `jack`) 237 | 238 | Now, install Rust (use bash instead of sh, sh doesn't work) 239 | 240 | ```shell 241 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash # 1 242 | source "$HOME/.cargo/env" 243 | ``` 244 | 245 | ### Testing 246 | 247 | ```shell 248 | git clone https://github.com/ardaku/whoami.git 249 | cd whoami 250 | # run both debug and release 251 | cargo run --example whoami-demo 252 | cargo run --example whoami-demo --release 253 | ``` 254 | 255 | Expected output is 256 | 257 | ```console 258 | WhoAmI 1.5.0 259 | 260 | User's Language whoami::langs(): ?? 261 | User's Name whoami::realname(): Tribblix Jack 262 | User's Username whoami::username(): jack 263 | Device's Pretty Name whoami::devicename(): tribblix 264 | Device's Hostname whoami::fallible::hostname(): tribblix 265 | Device's Platform whoami::platform(): illumos 266 | Device's OS Distro whoami::distro(): Tribblix 267 | Device's Desktop Env. whoami::desktop_env(): Unknown: Unknown 268 | Device's CPU Arch whoami::arch(): Unknown: i86pc 269 | ``` 270 |
271 | - [ ] Testing complete on Redox 272 |
Redox (virtualized on Fedora Silverblue) Testing 273 | 274 | 275 | ### Update Rust Nightly and Stable 276 | 277 | ```shell 278 | rustup update nightly stable 279 | rustup target add --toolchain stable x86_64-unknown-redox 280 | ``` 281 | 282 | ### Install pre-requisites 283 | 284 | ```shell 285 | sudo dnf install podman git file autoconf vim bison flex genisoimage gperf glibc-devel.i686 expat expat-devel fuse-devel fuse3-devel gmp-devel perl-HTML-Parser libpng-devel libtool libjpeg-turbo-devel libvorbis-devel SDL2_ttf-devel mesa-libOSMesa-devel m4 nasm po4a syslinux texinfo sdl12-compat-devel ninja-build meson python3-mako make gcc gcc-c++ openssl patch automake perl-Pod-Html perl-FindBin gperf curl gettext-devel perl-Pod-Xhtml pkgconf-pkg-config cmake cbindgen just mpfr-devel qemu doxygen 'perl(ExtUtils::MakeMaker)' 286 | 287 | cargo install --locked --force --version 0.1.1 cargo-config 288 | ``` 289 | 290 | ### Get redox source 291 | 292 | ```shell 293 | mkdir -p build/ 294 | cd build/ 295 | git clone https://gitlab.redox-os.org/redox-os/redox.git --origin upstream --recursive 296 | cd redox 297 | git submodule update --recursive --init 298 | ``` 299 | 300 | ### Create our demo recipe 301 | 302 | Back in the root whoami directory, make sure whome is updated to the whoami 303 | testing branch. 304 | 305 | ```shell 306 | mkdir -p build/redox/cookbook/recipes/demos/whome/ 307 | cp recipe.toml build/redox/cookbook/recipes/demos/whome/ 308 | cp build/redox/config/desktop.toml build/redox/config/x86_64/ardaku.toml 309 | ``` 310 | 311 | In `build/redox/config/x86_64/ardaku.toml`, under `[packages]`, add: 312 | 313 | ```toml 314 | whome = {} 315 | ``` 316 | 317 | ### Select the config 318 | IN `build/redox/mk/config.mk`, set: 319 | 320 | ```make 321 | CONFIG_NAME?=ardaku 322 | ``` 323 | 324 | ### Build Redox 325 | 326 | Back in `cd build/redox`, this takes a while (need to run in toolbx and outside) 327 | 328 | ```shell 329 | make all 330 | ``` 331 | 332 | or 333 | 334 | ```shell 335 | make rebuild 336 | ``` 337 | 338 | ### Run Redox 339 | 340 | ```shell 341 | make qemu 342 | ``` 343 | 344 | ### Test it 345 | 346 | Verify you are on the new version 347 | 348 | ```shell 349 | whome --version 350 | ``` 351 | 352 | Default settings should output: 353 | 354 | ```console 355 | realname: user 356 | username: user 357 | devicename: redox 358 | hostname: redox 359 | distro: Redox OS 0.9.0 360 | desktop_env: Orbital 361 | platform: Redox 362 | arch: Unknown: x86_64 363 | ``` 364 |
365 | - [ ] Testing complete on Web 366 |
Build web example and start webserver on Fedora Silverblue 367 | Check the web console in Firefox: 368 | 369 | ```console 370 | User's Name whoami::realname(): Anonymous 371 | User's Username whoami::username(): anonymous 372 | User's Language whoami::langs(): en/US;en 373 | Device's Pretty Name whoami::devicename(): Firefox 136.0 374 | Device's Hostname whoami::fallible::hostname(): localhost 375 | Device's Platform whoami::platform(): Linux 376 | Device's OS Distro whoami::distro(): Unknown Linux 377 | Device's Desktop Env. whoami::desktop_env(): Web Browser 378 | Device's CPU Arch whoami::arch(): wasm32 379 | ``` 380 | 381 | Check the web console in Opera: 382 | 383 | ```console 384 | User's Name whoami::realname(): Anonymous 385 | User's Username whoami::username(): anonymous 386 | User's Language whoami::langs(): en/US;en 387 | Device's Pretty Name whoami::devicename(): Opera 107.0.0.0 388 | Device's Hostname whoami::fallible::hostname(): localhost 389 | Device's Platform whoami::platform(): Linux 390 | Device's OS Distro whoami::distro(): Unknown Linux 391 | Device's Desktop Env. whoami::desktop_env(): Web Browser 392 | Device's CPU Arch whoami::arch(): wasm32 393 | ``` 394 | 395 | Check the web console in Chrome: 396 | 397 | ```console 398 | User's Name whoami::realname(): Anonymous 399 | User's Username whoami::username(): anonymous 400 | User's Language whoami::langs(): en/US;en 401 | Device's Pretty Name whoami::devicename(): Chrome 122.0.0.0 402 | Device's Hostname whoami::fallible::hostname(): localhost 403 | Device's Platform whoami::platform(): Linux 404 | Device's OS Distro whoami::distro(): Unknown Linux 405 | Device's Desktop Env. whoami::desktop_env(): Web Browser 406 | Device's CPU Arch whoami::arch(): wasm32 407 | ``` 408 | 409 | Check the web console in Ungoogled Chromium: 410 | 411 | ```console 412 | User's Name whoami::realname(): Anonymous 413 | User's Username whoami::username(): anonymous 414 | User's Language whoami::langs(): en/US;en 415 | Device's Pretty Name whoami::devicename(): Chrome 122.0.0.0 416 | Device's Hostname whoami::fallible::hostname(): localhost 417 | Device's Platform whoami::platform(): Linux 418 | Device's OS Distro whoami::distro(): Unknown Linux 419 | Device's Desktop Env. whoami::desktop_env(): Web Browser 420 | Device's CPU Arch whoami::arch(): wasm32 421 | ``` 422 | 423 | Check the web console in GNOME Web (Epiphany): 424 | 425 | ```console 426 | User's Name whoami::realname(): Anonymous 427 | User's Username whoami::username(): anonymous 428 | User's Language whoami::langs(): en/US 429 | Device's Pretty Name whoami::devicename(): GNOME Web 430 | Device's Hostname whoami::fallible::hostname(): localhost 431 | Device's Platform whoami::platform(): Linux 432 | Device's OS Distro whoami::distro(): Unknown Linux 433 | Device's Desktop Env. whoami::desktop_env(): Web Browser 434 | Device's CPU Arch whoami::arch(): wasm32 435 | ``` 436 | 437 | # Changelog 438 | 439 | ## Added 440 | 441 | ## Changed 442 | 443 | ## Fixed 444 | 445 | ## Removed 446 | -------------------------------------------------------------------------------- /whoami/src/os/unix.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any( 2 | target_os = "linux", 3 | target_os = "dragonfly", 4 | target_os = "freebsd", 5 | target_os = "netbsd", 6 | target_os = "openbsd", 7 | target_os = "illumos", 8 | target_os = "hurd", 9 | ))] 10 | use std::env; 11 | use std::{ 12 | ffi::{c_char, c_int, c_void, CStr, OsString}, 13 | fs, 14 | io::{Error, ErrorKind}, 15 | mem, 16 | os::unix::ffi::OsStringExt, 17 | slice, 18 | }; 19 | #[cfg(target_os = "macos")] 20 | use std::{ 21 | ffi::{c_long, c_uchar, OsStr}, 22 | os::unix::ffi::OsStrExt, 23 | ptr::null_mut, 24 | }; 25 | 26 | use crate::{ 27 | os::{Os, Target}, 28 | Arch, DesktopEnv, LanguagePrefs, Platform, Result, 29 | }; 30 | 31 | #[cfg(any(target_os = "linux", target_os = "hurd"))] 32 | #[repr(C)] 33 | struct PassWd { 34 | pw_name: *const c_void, 35 | pw_passwd: *const c_void, 36 | pw_uid: u32, 37 | pw_gid: u32, 38 | pw_gecos: *const c_void, 39 | pw_dir: *const c_void, 40 | pw_shell: *const c_void, 41 | } 42 | 43 | #[cfg(any( 44 | target_os = "macos", 45 | target_os = "dragonfly", 46 | target_os = "freebsd", 47 | target_os = "openbsd", 48 | target_os = "netbsd" 49 | ))] 50 | #[repr(C)] 51 | struct PassWd { 52 | pw_name: *const c_void, 53 | pw_passwd: *const c_void, 54 | pw_uid: u32, 55 | pw_gid: u32, 56 | pw_change: isize, 57 | pw_class: *const c_void, 58 | pw_gecos: *const c_void, 59 | pw_dir: *const c_void, 60 | pw_shell: *const c_void, 61 | pw_expire: isize, 62 | pw_fields: i32, 63 | } 64 | 65 | #[cfg(target_os = "illumos")] 66 | #[repr(C)] 67 | struct PassWd { 68 | pw_name: *const c_void, 69 | pw_passwd: *const c_void, 70 | pw_uid: u32, 71 | pw_gid: u32, 72 | pw_age: *const c_void, 73 | pw_comment: *const c_void, 74 | pw_gecos: *const c_void, 75 | pw_dir: *const c_void, 76 | pw_shell: *const c_void, 77 | } 78 | 79 | #[cfg(target_os = "illumos")] 80 | extern "system" { 81 | fn getpwuid_r( 82 | uid: u32, 83 | pwd: *mut PassWd, 84 | buf: *mut c_void, 85 | buflen: c_int, 86 | ) -> *mut PassWd; 87 | } 88 | 89 | #[cfg(any( 90 | target_os = "linux", 91 | target_os = "macos", 92 | target_os = "dragonfly", 93 | target_os = "freebsd", 94 | target_os = "netbsd", 95 | target_os = "openbsd", 96 | target_os = "hurd", 97 | ))] 98 | extern "system" { 99 | fn getpwuid_r( 100 | uid: u32, 101 | pwd: *mut PassWd, 102 | buf: *mut c_void, 103 | buflen: usize, 104 | result: *mut *mut PassWd, 105 | ) -> i32; 106 | } 107 | 108 | extern "system" { 109 | fn geteuid() -> u32; 110 | fn gethostname(name: *mut c_void, len: usize) -> i32; 111 | } 112 | 113 | #[cfg(target_os = "macos")] 114 | #[link(name = "CoreFoundation", kind = "framework")] 115 | extern "system" { 116 | fn CFStringGetCString( 117 | the_string: *mut c_void, 118 | buffer: *mut u8, 119 | buffer_size: c_long, 120 | encoding: u32, 121 | ) -> c_uchar; 122 | fn CFStringGetLength(the_string: *mut c_void) -> c_long; 123 | fn CFStringGetMaximumSizeForEncoding( 124 | length: c_long, 125 | encoding: u32, 126 | ) -> c_long; 127 | fn CFRelease(cf: *const c_void); 128 | } 129 | 130 | #[cfg(target_os = "macos")] 131 | #[link(name = "SystemConfiguration", kind = "framework")] 132 | extern "system" { 133 | fn SCDynamicStoreCopyComputerName( 134 | store: *mut c_void, 135 | encoding: *mut u32, 136 | ) -> *mut c_void; 137 | } 138 | 139 | enum Name { 140 | User, 141 | Real, 142 | } 143 | 144 | unsafe fn strlen(cs: *const c_void) -> usize { 145 | let mut len = 0; 146 | let mut cs: *const u8 = cs.cast(); 147 | while *cs != 0 { 148 | len += 1; 149 | cs = cs.offset(1); 150 | } 151 | len 152 | } 153 | 154 | unsafe fn strlen_gecos(cs: *const c_void) -> usize { 155 | let mut len = 0; 156 | let mut cs: *const u8 = cs.cast(); 157 | while *cs != 0 && *cs != b',' { 158 | len += 1; 159 | cs = cs.offset(1); 160 | } 161 | len 162 | } 163 | 164 | fn os_from_cstring_gecos(string: *const c_void) -> Result { 165 | if string.is_null() { 166 | return Err(super::err_null_record()); 167 | } 168 | 169 | // Get a byte slice of the c string. 170 | let slice = unsafe { 171 | let length = strlen_gecos(string); 172 | 173 | if length == 0 { 174 | return Err(super::err_empty_record()); 175 | } 176 | 177 | slice::from_raw_parts(string.cast(), length) 178 | }; 179 | 180 | // Turn byte slice into Rust String. 181 | Ok(OsString::from_vec(slice.to_vec())) 182 | } 183 | 184 | fn os_from_cstring(string: *const c_void) -> Result { 185 | if string.is_null() { 186 | return Err(super::err_null_record()); 187 | } 188 | 189 | // Get a byte slice of the c string. 190 | let slice = unsafe { 191 | let length = strlen(string); 192 | 193 | if length == 0 { 194 | return Err(super::err_empty_record()); 195 | } 196 | 197 | slice::from_raw_parts(string.cast(), length) 198 | }; 199 | 200 | // Turn byte slice into Rust String. 201 | Ok(OsString::from_vec(slice.to_vec())) 202 | } 203 | 204 | #[cfg(target_os = "macos")] 205 | fn os_from_cfstring(string: *mut c_void) -> OsString { 206 | if string.is_null() { 207 | return "".to_string().into(); 208 | } 209 | 210 | unsafe { 211 | let len = CFStringGetLength(string); 212 | let capacity = 213 | CFStringGetMaximumSizeForEncoding(len, 134_217_984 /* UTF8 */) + 1; 214 | let mut out = Vec::with_capacity(capacity as usize); 215 | if CFStringGetCString( 216 | string, 217 | out.as_mut_ptr(), 218 | capacity, 219 | 134_217_984, /* UTF8 */ 220 | ) != 0 221 | { 222 | out.set_len(strlen(out.as_ptr().cast())); // Remove trailing NUL byte 223 | out.shrink_to_fit(); 224 | CFRelease(string); 225 | OsString::from_vec(out) 226 | } else { 227 | CFRelease(string); 228 | "".to_string().into() 229 | } 230 | } 231 | } 232 | 233 | // This function must allocate, because a slice or `Cow` would still 234 | // reference `passwd` which is dropped when this function returns. 235 | #[inline(always)] 236 | fn getpwuid(name: Name) -> Result { 237 | const BUF_SIZE: usize = 16_384; // size from the man page 238 | let mut buffer = mem::MaybeUninit::<[u8; BUF_SIZE]>::uninit(); 239 | let mut passwd = mem::MaybeUninit::::uninit(); 240 | 241 | // Get PassWd `struct`. 242 | let passwd = unsafe { 243 | #[cfg(any( 244 | target_os = "linux", 245 | target_os = "macos", 246 | target_os = "dragonfly", 247 | target_os = "freebsd", 248 | target_os = "netbsd", 249 | target_os = "openbsd", 250 | target_os = "hurd", 251 | ))] 252 | { 253 | let mut _passwd = mem::MaybeUninit::<*mut PassWd>::uninit(); 254 | let ret = getpwuid_r( 255 | geteuid(), 256 | passwd.as_mut_ptr(), 257 | buffer.as_mut_ptr() as *mut c_void, 258 | BUF_SIZE, 259 | _passwd.as_mut_ptr(), 260 | ); 261 | 262 | if ret != 0 { 263 | return Err(Error::last_os_error()); 264 | } 265 | 266 | let _passwd = _passwd.assume_init(); 267 | 268 | if _passwd.is_null() { 269 | return Err(super::err_null_record()); 270 | } 271 | passwd.assume_init() 272 | } 273 | 274 | #[cfg(target_os = "illumos")] 275 | { 276 | let ret = getpwuid_r( 277 | geteuid(), 278 | passwd.as_mut_ptr(), 279 | buffer.as_mut_ptr() as *mut c_void, 280 | BUF_SIZE.try_into().unwrap_or(c_int::MAX), 281 | ); 282 | 283 | if ret.is_null() { 284 | return Err(Error::last_os_error()); 285 | } 286 | passwd.assume_init() 287 | } 288 | }; 289 | 290 | // Extract names. 291 | if let Name::Real = name { 292 | os_from_cstring_gecos(passwd.pw_gecos) 293 | } else { 294 | os_from_cstring(passwd.pw_name) 295 | } 296 | } 297 | 298 | #[cfg(target_os = "macos")] 299 | fn distro_xml(data: String) -> Result { 300 | let mut product_name = None; 301 | let mut user_visible_version = None; 302 | 303 | if let Some(start) = data.find("") { 304 | if let Some(end) = data.find("") { 305 | let mut set_product_name = false; 306 | let mut set_user_visible_version = false; 307 | 308 | for line in data[start + "".len()..end].lines() { 309 | let line = line.trim(); 310 | 311 | if let Some(key) = line.strip_prefix("") { 312 | match key.trim_end_matches("") { 313 | "ProductName" => set_product_name = true, 314 | "ProductUserVisibleVersion" => { 315 | set_user_visible_version = true 316 | } 317 | "ProductVersion" => { 318 | if user_visible_version.is_none() { 319 | set_user_visible_version = true 320 | } 321 | } 322 | _ => {} 323 | } 324 | } else if let Some(value) = line.strip_prefix("") { 325 | if set_product_name { 326 | product_name = 327 | Some(value.trim_end_matches("")); 328 | set_product_name = false; 329 | } else if set_user_visible_version { 330 | user_visible_version = 331 | Some(value.trim_end_matches("")); 332 | set_user_visible_version = false; 333 | } 334 | } 335 | } 336 | } 337 | } 338 | 339 | Ok(if let Some(product_name) = product_name { 340 | if let Some(user_visible_version) = user_visible_version { 341 | format!("{product_name} {user_visible_version}") 342 | } else { 343 | product_name.to_string() 344 | } 345 | } else { 346 | user_visible_version 347 | .map(|v| format!("Mac OS (Unknown) {v}")) 348 | .ok_or_else(|| { 349 | Error::new(ErrorKind::InvalidData, "Parsing failed") 350 | })? 351 | }) 352 | } 353 | 354 | #[cfg(any( 355 | target_os = "macos", 356 | target_os = "freebsd", 357 | target_os = "netbsd", 358 | target_os = "openbsd", 359 | ))] 360 | #[repr(C)] 361 | struct UtsName { 362 | sysname: [c_char; 256], 363 | nodename: [c_char; 256], 364 | release: [c_char; 256], 365 | version: [c_char; 256], 366 | machine: [c_char; 256], 367 | } 368 | 369 | #[cfg(target_os = "illumos")] 370 | #[repr(C)] 371 | struct UtsName { 372 | sysname: [c_char; 257], 373 | nodename: [c_char; 257], 374 | release: [c_char; 257], 375 | version: [c_char; 257], 376 | machine: [c_char; 257], 377 | } 378 | 379 | #[cfg(target_os = "dragonfly")] 380 | #[repr(C)] 381 | struct UtsName { 382 | sysname: [c_char; 32], 383 | nodename: [c_char; 32], 384 | release: [c_char; 32], 385 | version: [c_char; 32], 386 | machine: [c_char; 32], 387 | } 388 | 389 | #[cfg(any(target_os = "linux", target_os = "android",))] 390 | #[repr(C)] 391 | struct UtsName { 392 | sysname: [c_char; 65], 393 | nodename: [c_char; 65], 394 | release: [c_char; 65], 395 | version: [c_char; 65], 396 | machine: [c_char; 65], 397 | domainname: [c_char; 65], 398 | } 399 | 400 | #[cfg(target_os = "hurd")] 401 | #[repr(C)] 402 | struct UtsName { 403 | sysname: [c_char; 1024], 404 | nodename: [c_char; 1024], 405 | release: [c_char; 1024], 406 | version: [c_char; 1024], 407 | machine: [c_char; 1024], 408 | } 409 | 410 | // Buffer initialization 411 | impl Default for UtsName { 412 | fn default() -> Self { 413 | unsafe { mem::zeroed() } 414 | } 415 | } 416 | 417 | #[inline(always)] 418 | unsafe fn uname(buf: *mut UtsName) -> c_int { 419 | extern "C" { 420 | #[cfg(any( 421 | target_os = "linux", 422 | target_os = "macos", 423 | target_os = "dragonfly", 424 | target_os = "netbsd", 425 | target_os = "openbsd", 426 | target_os = "illumos", 427 | target_os = "hurd", 428 | ))] 429 | fn uname(buf: *mut UtsName) -> c_int; 430 | 431 | #[cfg(target_os = "freebsd")] 432 | fn __xuname(nmln: c_int, buf: *mut c_void) -> c_int; 433 | } 434 | 435 | // Polyfill `uname()` for FreeBSD 436 | #[inline(always)] 437 | #[cfg(target_os = "freebsd")] 438 | unsafe extern "C" fn uname(buf: *mut UtsName) -> c_int { 439 | __xuname(256, buf.cast()) 440 | } 441 | 442 | uname(buf) 443 | } 444 | 445 | impl Target for Os { 446 | fn lang_prefs(self) -> Result { 447 | super::unix_lang() 448 | } 449 | 450 | fn realname(self) -> Result { 451 | getpwuid(Name::Real) 452 | } 453 | 454 | fn username(self) -> Result { 455 | getpwuid(Name::User) 456 | } 457 | 458 | fn devicename(self) -> Result { 459 | #[cfg(target_os = "macos")] 460 | { 461 | let out = os_from_cfstring(unsafe { 462 | SCDynamicStoreCopyComputerName(null_mut(), null_mut()) 463 | }); 464 | 465 | if out.as_bytes().is_empty() { 466 | return Err(super::err_empty_record()); 467 | } 468 | 469 | Ok(out) 470 | } 471 | 472 | #[cfg(target_os = "illumos")] 473 | { 474 | let mut nodename = fs::read("/etc/nodename")?; 475 | 476 | // Remove all at and after the first newline (before end of file) 477 | if let Some(slice) = nodename.split(|x| *x == b'\n').next() { 478 | nodename.drain(slice.len()..); 479 | } 480 | 481 | if nodename.is_empty() { 482 | return Err(super::err_empty_record()); 483 | } 484 | 485 | Ok(OsString::from_vec(nodename)) 486 | } 487 | 488 | #[cfg(any( 489 | target_os = "linux", 490 | target_os = "dragonfly", 491 | target_os = "freebsd", 492 | target_os = "netbsd", 493 | target_os = "openbsd", 494 | target_os = "hurd", 495 | ))] 496 | { 497 | let machine_info = fs::read("/etc/machine-info")?; 498 | 499 | for i in machine_info.split(|b| *b == b'\n') { 500 | let mut j = i.split(|b| *b == b'='); 501 | 502 | if j.next() == Some(b"PRETTY_HOSTNAME") { 503 | let pretty_hostname = j.next().ok_or(Error::new( 504 | ErrorKind::InvalidData, 505 | "parsing failed", 506 | ))?; 507 | let pretty_hostname = pretty_hostname 508 | .strip_prefix(b"\"") 509 | .unwrap_or(pretty_hostname) 510 | .strip_suffix(b"\"") 511 | .unwrap_or(pretty_hostname); 512 | let pretty_hostname = { 513 | let mut vec = Vec::with_capacity(pretty_hostname.len()); 514 | let mut pretty_hostname = pretty_hostname.iter(); 515 | 516 | while let Some(&c) = pretty_hostname.next() { 517 | if c == b'\\' { 518 | vec.push(match pretty_hostname.next() { 519 | Some(b'\\') => b'\\', 520 | Some(b't') => b'\t', 521 | Some(b'r') => b'\r', 522 | Some(b'n') => b'\n', 523 | Some(b'\'') => b'\'', 524 | Some(b'"') => b'"', 525 | _ => { 526 | return Err(Error::new( 527 | ErrorKind::InvalidData, 528 | "parsing failed", 529 | )); 530 | } 531 | }); 532 | } else { 533 | vec.push(c); 534 | } 535 | } 536 | 537 | vec 538 | }; 539 | 540 | return Ok(OsString::from_vec(pretty_hostname)); 541 | } 542 | } 543 | 544 | Err(super::err_missing_record()) 545 | } 546 | } 547 | 548 | fn hostname(self) -> Result { 549 | // Maximum hostname length = 255, plus a NULL byte. 550 | let mut string = Vec::::with_capacity(256); 551 | 552 | unsafe { 553 | if gethostname(string.as_mut_ptr().cast(), 255) == -1 { 554 | return Err(Error::last_os_error()); 555 | } 556 | 557 | string.set_len(strlen(string.as_ptr().cast())); 558 | }; 559 | 560 | String::from_utf8(string).map_err(|_| { 561 | Error::new(ErrorKind::InvalidData, "Hostname not valid UTF-8") 562 | }) 563 | } 564 | 565 | fn distro(self) -> Result { 566 | #[cfg(target_os = "macos")] 567 | { 568 | if let Ok(data) = fs::read_to_string( 569 | "/System/Library/CoreServices/ServerVersion.plist", 570 | ) { 571 | distro_xml(data) 572 | } else if let Ok(data) = fs::read_to_string( 573 | "/System/Library/CoreServices/SystemVersion.plist", 574 | ) { 575 | distro_xml(data) 576 | } else { 577 | Err(super::err_missing_record()) 578 | } 579 | } 580 | 581 | #[cfg(any( 582 | target_os = "linux", 583 | target_os = "dragonfly", 584 | target_os = "freebsd", 585 | target_os = "netbsd", 586 | target_os = "openbsd", 587 | target_os = "illumos", 588 | target_os = "hurd", 589 | ))] 590 | { 591 | let program = fs::read("/etc/os-release")?; 592 | let distro = String::from_utf8_lossy(&program); 593 | let err = || Error::new(ErrorKind::InvalidData, "Parsing failed"); 594 | let mut fallback = None; 595 | 596 | for i in distro.split('\n') { 597 | let mut j = i.split('='); 598 | 599 | match j.next().ok_or_else(err)? { 600 | "PRETTY_NAME" => { 601 | return Ok(j 602 | .next() 603 | .ok_or_else(err)? 604 | .trim_matches('"') 605 | .to_string()); 606 | } 607 | "NAME" => { 608 | fallback = Some( 609 | j.next() 610 | .ok_or_else(err)? 611 | .trim_matches('"') 612 | .to_string(), 613 | ) 614 | } 615 | _ => {} 616 | } 617 | } 618 | 619 | fallback.ok_or_else(err) 620 | } 621 | } 622 | 623 | fn desktop_env(self) -> Option { 624 | #[cfg(target_os = "macos")] 625 | let env = OsStr::new("Aqua"); 626 | 627 | #[cfg(any( 628 | target_os = "linux", 629 | target_os = "dragonfly", 630 | target_os = "freebsd", 631 | target_os = "netbsd", 632 | target_os = "openbsd", 633 | target_os = "illumos", 634 | target_os = "hurd", 635 | ))] 636 | let env = env::var_os("DESKTOP_SESSION")?; 637 | 638 | // convert `OsStr` to `Cow` 639 | let env = env.to_string_lossy(); 640 | 641 | Some(if env.eq_ignore_ascii_case("AQUA") { 642 | DesktopEnv::Aqua 643 | } else if env.eq_ignore_ascii_case("GNOME") { 644 | DesktopEnv::Gnome 645 | } else if env.eq_ignore_ascii_case("LXDE") { 646 | DesktopEnv::Lxde 647 | } else if env.eq_ignore_ascii_case("OPENBOX") { 648 | DesktopEnv::Openbox 649 | } else if env.eq_ignore_ascii_case("I3") { 650 | DesktopEnv::I3 651 | } else if env.eq_ignore_ascii_case("UBUNTU") { 652 | DesktopEnv::Ubuntu 653 | } else if env.eq_ignore_ascii_case("PLASMA5") { 654 | DesktopEnv::Plasma 655 | } else { 656 | DesktopEnv::Unknown(env.to_string()) 657 | }) 658 | } 659 | 660 | #[inline(always)] 661 | fn platform(self) -> Platform { 662 | #[cfg(target_os = "linux")] 663 | { 664 | Platform::Linux 665 | } 666 | 667 | #[cfg(target_os = "macos")] 668 | { 669 | Platform::Mac 670 | } 671 | 672 | #[cfg(any( 673 | target_os = "dragonfly", 674 | target_os = "freebsd", 675 | target_os = "netbsd", 676 | target_os = "openbsd", 677 | ))] 678 | { 679 | Platform::Bsd 680 | } 681 | 682 | #[cfg(target_os = "illumos")] 683 | { 684 | Platform::Illumos 685 | } 686 | 687 | #[cfg(target_os = "hurd")] 688 | { 689 | Platform::Hurd 690 | } 691 | } 692 | 693 | #[inline(always)] 694 | fn arch(self) -> Result { 695 | let mut buf = UtsName::default(); 696 | 697 | if unsafe { uname(&mut buf) } == -1 { 698 | return Err(Error::last_os_error()); 699 | } 700 | 701 | let arch_str = 702 | unsafe { CStr::from_ptr(buf.machine.as_ptr()) }.to_string_lossy(); 703 | 704 | Ok(match arch_str.as_ref() { 705 | "aarch64" | "arm64" | "aarch64_be" | "armv8b" | "armv8l" => { 706 | Arch::Arm64 707 | } 708 | "armv5" => Arch::ArmV5, 709 | "armv6" | "arm" => Arch::ArmV6, 710 | "armv7" => Arch::ArmV7, 711 | "i386" => Arch::I386, 712 | "i586" => Arch::I586, 713 | "i686" | "i686-AT386" => Arch::I686, 714 | "mips" => Arch::Mips, 715 | "mipsel" => Arch::MipsEl, 716 | "mips64" => Arch::Mips64, 717 | "mips64el" => Arch::Mips64El, 718 | "powerpc" | "ppc" | "ppcle" => Arch::PowerPc, 719 | "powerpc64" | "ppc64" | "ppc64le" => Arch::PowerPc64, 720 | "powerpc64le" => Arch::PowerPc64Le, 721 | "riscv32" => Arch::Riscv32, 722 | "riscv64" => Arch::Riscv64, 723 | "s390x" => Arch::S390x, 724 | "sparc" => Arch::Sparc, 725 | "sparc64" => Arch::Sparc64, 726 | "x86_64" | "amd64" => Arch::X64, 727 | _ => Arch::Unknown(arch_str.into_owned()), 728 | }) 729 | } 730 | } 731 | --------------------------------------------------------------------------------