From 358dc0ef16960beafdf14f89198616b0f378b884 Mon Sep 17 00:00:00 2001 From: Jeron Aldaron Lau Date: Sun, 3 Mar 2024 22:33:19 -0600 Subject: [PATCH] WhoAmI 1.5.0 Release (#94) --- .github/workflows/ci.yml | 20 +- .gitignore | 9 +- CHANGELOG.md | 23 +- Cargo.lock | 166 ++++++++++++ Cargo.toml | 25 +- README.md | 30 ++- TESTING.md | 294 ++++++++++++++++++++- WASM.md | 6 +- examples/os-strings.rs | 31 ++- examples/web/Cargo.lock | 188 ++++++++++++++ examples/web/src/lib.rs | 24 +- examples/whoami-demo.rs | 13 +- recipe.toml | 1 + rust-toolchain | 2 - src/api.rs | 191 ++++++++++++++ src/arch.rs | 148 +++++++++++ src/desktop_env.rs | 92 +++++++ src/fallible.rs | 22 ++ src/language.rs | 81 ++++++ src/lib.rs | 533 ++------------------------------------- src/os.rs | 79 ++++-- src/os/daku.rs | 27 +- src/os/fake.rs | 66 ----- src/os/redox.rs | 12 +- src/os/target.rs | 124 +++++++++ src/os/unix.rs | 162 ++++++------ src/os/wasi.rs | 17 +- src/os/web.rs | 50 +--- src/os/windows.rs | 238 ++++++++--------- src/platform.rs | 50 ++++ src/result.rs | 4 + 31 files changed, 1748 insertions(+), 980 deletions(-) create mode 100644 Cargo.lock create mode 100644 examples/web/Cargo.lock delete mode 100644 rust-toolchain create mode 100644 src/api.rs create mode 100644 src/arch.rs create mode 100644 src/desktop_env.rs create mode 100644 src/language.rs delete mode 100644 src/os/fake.rs create mode 100644 src/os/target.rs create mode 100644 src/platform.rs create mode 100644 src/result.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 626cb89..aa38ad9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: toolchain: ${{ matrix.tc }} components: rustfmt, clippy override: true + - run: cargo update - run: cargo fmt --check - run: cargo clippy -- -D warnings checks-cross-compile: @@ -43,6 +44,7 @@ jobs: target: ${{ matrix.cc }} components: clippy override: true + - run: cargo update - run: cargo clippy --all-features --target=${{ matrix.cc }} -- -D warnings checks-cross-compile-ios: runs-on: ${{ matrix.os }} @@ -77,12 +79,8 @@ jobs: target: ${{ matrix.cc }} components: clippy override: true - - run: cargo update -p bumpalo --precise 3.4.0 - - run: cargo update -p web-sys --precise 0.3.55 - - run: cargo update -p js-sys --precise 0.3.55 - - run: cargo update -p wasm-bindgen --precise 0.2.78 - - run: cargo update -p log --precise 0.4.17 - run: cargo clippy --all-features --target=${{ matrix.cc }} -- -D warnings + - run: cargo update - run: cargo clippy --no-default-features --target=${{ matrix.cc }} -- -D warnings - run: RUSTFLAGS="--cfg target_os=\"daku\"" cargo clippy --target=${{ matrix.cc }} -- -D warnings test: @@ -111,6 +109,7 @@ jobs: profile: minimal toolchain: ${{ matrix.tc }} override: true + - run: cargo update - run: cargo test --all --all-features --target=x86_64-apple-darwin cross-compile: runs-on: ${{ matrix.os }} @@ -165,6 +164,7 @@ jobs: toolchain: ${{ matrix.tc }} target: ${{ matrix.cc }} override: true + - run: cargo update - run: cargo build --all-features --target=${{ matrix.cc }} cross-compile-wasm: runs-on: ${{ matrix.os }} @@ -181,14 +181,8 @@ jobs: toolchain: ${{ matrix.tc }} target: ${{ matrix.cc }} override: true - - run: cargo update -p bumpalo --precise 3.4.0 - - run: cargo update -p web-sys --precise 0.3.55 - - run: cargo update -p js-sys --precise 0.3.55 - - run: cargo update -p wasm-bindgen --precise 0.2.78 - - run: cargo update -p log --precise 0.4.17 - - run: cargo update -p quote --precise 1.0.30 - - run: cargo update -p proc-macro2 --precise 1.0.63 - run: cargo build --all-features --target=${{ matrix.cc }} + - run: cargo update - run: cargo build --no-default-features --target=${{ matrix.cc }} - run: RUSTFLAGS="--cfg target_os=\"daku\"" cargo build --target=${{ matrix.cc }} cross-compile-illumos: @@ -206,6 +200,7 @@ jobs: toolchain: ${{ matrix.tc }} target: ${{ matrix.cc }} override: true + - run: cargo update - run: cargo build --all-features --target=${{ matrix.cc }} cross-compile-redox: runs-on: ${{ matrix.os }} @@ -222,4 +217,5 @@ jobs: toolchain: ${{ matrix.tc }} target: ${{ matrix.cc }} override: true + - run: cargo update - run: cargo build --all-features --target=${{ matrix.cc }} diff --git a/.gitignore b/.gitignore index 6ec0057..aed3299 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ -**/target/ -**/*.rs.bk -Cargo.lock -/ignore/ -/a.out +/**/build/ +/**/target/ /**/*.swp -/build/ +/**/*.rs.bk diff --git a/CHANGELOG.md b/CHANGELOG.md index b30c1c0..0325aa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning]. -## [1.5.0] - UNRELEASED +## [1.5.0] - 2024-03-03 ### Added @@ -19,14 +19,21 @@ The format is based on [Keep a Changelog], and this project adheres to - `whoami::fallible::realname_os()` - `whoami::fallible::username()` - `whoami::fallible::username_os()` + - `whoami::Language` + - `whoami::Country` - `whoami::langs()` - - `Language` - - `Country` + - `whoami::fallible::account()` + - `whoami::fallible::account_os()` + - `whoami::DesktopEnv::is_gtk()` + - `whoami::DesktopEnv::is_kde()` ### Removed - Generated device names that infer casing based on the hostname when the device name is not available - now returns the hostname unchanged + - Partial (potentially unsound) support for Android, iOS, watchOS, tvOS, + Fuchsia, Haiku, Solaris, and a few others. These targets now use the "fake" + implementation. ### Changed @@ -34,12 +41,18 @@ The format is based on [Keep a Changelog], and this project adheres to - Deprecated `whoami::hostname()` - Deprecated `whoami::hostname_os()` - Deprecated `whoami::lang()` + - illumos and Redox are no longer untested targets + - Documented that illumos and Redox have a higher MSRV (Rust 1.65) than other + targets + - Display implementation on `Platform::Illumos` now displays in lowercase: + illumos ### Fixed - Removed some unnecessary allocations - Rare and nearly impossible cases of undefined behavior - Better handling of UTF-8 non-conformant strings + - Multiple instances of undefined behavior on illumos ## [1.4.1] - 2023-06-25 @@ -51,7 +64,7 @@ The format is based on [Keep a Changelog], and this project adheres to ### Added - - Support for Illumos + - Support for illumos ## [1.3.0] - 2022-12-28 @@ -63,7 +76,7 @@ The format is based on [Keep a Changelog], and this project adheres to architecture - `Arch::width()` method which returns the address width of a CPU architecture - *`web`* feature (enabled by default). Disabling this feature allows you to - use wasm32-unknown-unknown with whoami outside of the browser with a mock + use wasm32-unknown-unknown with whoami outside of the browser with a fake implementation. - Officially support compiling to WASI or Daku WebAssembly platforms; functionality not supported yet. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1ad4c6c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,166 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "proc-macro2" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.5.0" +dependencies = [ + "redox_syscall", + "wasite", + "web-sys", +] diff --git a/Cargo.toml b/Cargo.toml index 462cdc0..1497840 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "whoami" -version = "1.5.0-pre.0" +version = "1.5.0" edition = "2018" license = "Apache-2.0 OR BSL-1.0 OR MIT" documentation = "https://docs.rs/whoami" -homepage = "https://github.com/ardaku/whoami/blob/stable/CHANGELOG.md" +homepage = "https://github.com/ardaku/whoami/blob/v1/CHANGELOG.md" repository = "https://github.com/ardaku/whoami" readme = "README.md" description = "Retrieve the current user and environment." @@ -17,33 +17,30 @@ categories = [ "value-formatting", ] include = [ - "LICENSE_APACHE", - "LICENSE_BOOST", - "LICENSE_MIT", - "README.md", - "src/*", + "/LICENSE_APACHE", + "/LICENSE_BOOST", + "/LICENSE_MIT", + "/README.md", + "/src/*", ] rust-version = "1.40" -# Target specific dependencies for redox +# Target specific dependency for redox [target.'cfg(all(target_os = "redox", not(target_arch = "wasm32")))'.dependencies.redox_syscall] version = "0.4" -# Target specific dependencies for wasite +# Target specific dependency for wasite [target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'.dependencies.wasite] version = "0.1" -# Target-specific dependency for wasm-bindgen +# Target-specific dependency for web browser [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(target_os = "daku")))'.dependencies.web-sys] version = "0.3" features = ["Navigator", "Document", "Window", "Location"] optional = true -[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(target_os = "daku")))'.dependencies.wasm-bindgen] -version = "0.2" -optional = true [features] default = ["web"] # Enabling this feature indicates that the wasm32-unknown-unknown target should # be assumed to be in a web environment where it can call DOM APIs. -web = ["web-sys", "wasm-bindgen"] +web = ["web-sys"] diff --git a/README.md b/README.md index 13e0bed..76ee91f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![WhoAmI Logo](https://raw.githubusercontent.com/ardaku/whoami/stable/res/icon.svg) +![WhoAmI Logo](https://raw.githubusercontent.com/ardaku/whoami/v1/res/icon.svg) #### [Changelog][3] | [Source][4] | [Getting Started][5] @@ -32,17 +32,16 @@ WhoAmI targets all platforms that can run Rust, including: - Windows - Mac OS - BSD variants (FreeBSD, others) - - Illumos variants (SmartOS, OmniOS, others) **Target-Specific MSRV 1.65** + - illumos variants (SmartOS, OmniOS, others) **Target-Specific MSRV 1.65** - Redox **Target-Specific MSRV 1.65** - - [Web Assembly](https://github.com/ardaku/whoami/blob/stable/WASM.md) - - Mock implementation + - [Web Assembly](https://github.com/ardaku/whoami/blob/v1/WASM.md) + - Fake implementation - Web Browser - DOM - - WASI (Wasite, others) **may partially or fully work - but untested** - - Daku (Ardaku/Quantii, others) **mock implementation, full implementation planned later** - - Android **may partially or fully work - but untested, planned later** - - iOS **planned later** + - WASI (Wasite, others) **untested, testing planned later** + - Daku (Ardaku/Quantii, others) **planned later** + - Android **planned later** + - iOS / watchOS / tvOS **planned later** - Fuchsia **planned later** - - Various game consoles **planned later** - Others? (make a PR or open an issue) ## MSRV @@ -59,6 +58,11 @@ bumping MSRV only as needed. [whome](https://crates.io/crates/whome): `whoami` command RiR (Re-written in Rust) that depends on this crate. +## Testing +The testing procedure is documented at +. The full manual test +suite is run for each change that affects multiple platforms. + ## License Copyright © 2017-2024 The WhoAmI Contributors. @@ -83,14 +87,14 @@ email at [aldaronlau@gmail.com][13]. [0]: https://docs.rs/whoami [1]: https://crates.io/crates/whoami [2]: https://github.com/ardaku/whoami/actions?query=workflow%3Atests -[3]: https://github.com/ardaku/whoami/blob/stable/CHANGELOG.md +[3]: https://github.com/ardaku/whoami/blob/v1/CHANGELOG.md [4]: https://github.com/ardaku/whoami/ [5]: https://docs.rs/whoami#getting-started [6]: https://aldaronlau.com/ -[7]: https://github.com/ardaku/whoami/blob/stable/LICENSE_APACHE +[7]: https://github.com/ardaku/whoami/blob/v1/LICENSE_APACHE [8]: https://www.apache.org/licenses/LICENSE-2.0 -[9]: https://github.com/ardaku/whoami/blob/stable/LICENSE_MIT +[9]: https://github.com/ardaku/whoami/blob/v1/LICENSE_MIT [10]: https://mit-license.org/ -[11]: https://github.com/ardaku/whoami/blob/stable/LICENSE_BOOST +[11]: https://github.com/ardaku/whoami/blob/v1/LICENSE_BOOST [12]: https://www.boost.org/LICENSE_1_0.txt [13]: mailto:aldaronlau@gmail.com diff --git a/TESTING.md b/TESTING.md index 172359e..c1bb5ee 100644 --- a/TESTING.md +++ b/TESTING.md @@ -4,15 +4,225 @@ This file outlines the regression testing plan for all platforms. ## Linux / Fedora Silverblue +Testing is done on Fedora Silverblue 39. + +Open a terminal (outside of toolbox), and: + +```rust +cargo run --example whoami-demo +cargo run --example whoami-demo --release +cargo run --example os-strings +cargo run --example os-strings --release +``` + +Expect to see something like: + +```console +WhoAmI 1.5.0 + +User's Language whoami::langs(): en/US +User's Name whoami::realname(): Jeron Lau +User's Username whoami::username(): jeron +User's Username whoami::fallible::account(): jeron +Device's Pretty Name whoami::devicename(): Zetêy +Device's Hostname whoami::fallible::hostname(): zetey +Device's Platform whoami::platform(): Linux +Device's OS Distro whoami::distro(): Fedora Linux 39.20240202.0 (Silverblue) +Device's Desktop Env. whoami::desktop_env(): Gnome +Device's CPU Arch whoami::arch(): x86_64 +``` + +```console +WhoAmI 1.5.0 + +User's Language whoami::langs(): "en/US" +User's Name whoami::realname_os(): "Jeron Lau" +User's Username whoami::username_os(): "jeron" +User's Account whoami::fallible::account_os(): "jeron" +Device's Pretty Name whoami::devicename_os(): "Zetêy" +Device's Hostname whoami::fallible::hostname(): "zetey" +Device's Platform whoami::platform(): Linux +Device's OS Distro whoami::distro(): "Fedora Linux 39.20240202.0 (Silverblue)" +Device's Desktop Env. whoami::desktop_env(): Gnome +Device's CPU Arch whoami::arch(): X64 +``` + +Now, `toolbox enter`, and do the same. Expecting something like: + +```console +WhoAmI 1.5.0 + +User's Language whoami::langs(): en/US +User's Name whoami::realname(): Jeron Lau +User's Username whoami::username(): jeron +User's Username whoami::fallible::account(): jeron +Device's Pretty Name whoami::devicename(): toolbox +Device's Hostname whoami::fallible::hostname(): toolbox +Device's Platform whoami::platform(): Linux +Device's OS Distro whoami::distro(): Fedora Linux 39 (Container Image) +Device's Desktop Env. whoami::desktop_env(): Gnome +Device's CPU Arch whoami::arch(): x86_64 +``` + +```console +WhoAmI 1.5.0 + +User's Language whoami::langs(): "en/US" +User's Name whoami::realname_os(): "Jeron Lau" +User's Username whoami::username_os(): "jeron" +User's Account whoami::fallible::account_os(): "jeron" +Device's Pretty Name whoami::devicename_os(): "toolbox" +Device's Hostname whoami::fallible::hostname(): "toolbox" +Device's Platform whoami::platform(): Linux +Device's OS Distro whoami::distro(): "Fedora Linux 39 (Container Image)" +Device's Desktop Env. whoami::desktop_env(): Gnome +Device's CPU Arch whoami::arch(): X64 +``` + ## Linux / Ubuntu +Testing is done on Ubuntu 23.10 (virtualized on Fedora Silverblue): + +https://ubuntu.com/download/desktop + +Install from file within GNOME boxes (keep all defaults). + +In Ubuntu Installer, do default installation. + +```shell +sudo apt install git curl gcc tig # y +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # 1 +source "$HOME/.cargo/env" +``` + +Clone whoami, open a terminal, and run: + +```rust +cargo run --example whoami-demo +cargo run --example whoami-demo --release +``` + +Expect to see something like: + +```console +WhoAmI 1.5.0 + +User's Language whoami::langs(): en/US +User's Name whoami::realname(): Jeron Lau +User's Username whoami::username(): aldaron +User's Username whoami::fallible::account(): aldaron +Device's Pretty Name whoami::devicename(): ubuntu-box +Device's Hostname whoami::fallible::hostname(): ubuntu-box +Device's Platform whoami::platform(): Linux +Device's OS Distro whoami::distro(): Ubuntu 23.10 +Device's Desktop Env. whoami::desktop_env(): Ubuntu +Device's CPU Arch whoami::arch(): x86_64 +``` + ## Windows +Testing is done on Windows 10 + +Clone whoami, open Git BASH, and run: + +```rust +cargo run --example whoami-demo +cargo run --example whoami-demo --release +``` + +Expect to see something like: + +```console +WhoAmI 1.5.0 + +User's Language whoami::langs(): en/US +User's Name whoami::realname(): Aldaron Lau +User's Username whoami::username(): Aldaron Lau +User's Username whoami::fallible::account(): Aldaron Lau +Device's Pretty Name whoami::devicename(): Helpy-Witch +Device's Hostname whoami::fallible::hostname(): HELPY-WITCH +Device's Platform whoami::platform(): Windows +Device's OS Distro whoami::distro(): Windows 10.0.19044 (Workstation) +Device's Desktop Env. whoami::desktop_env(): Windows +Device's CPU Arch whoami::arch(): x86_64 +``` + ## MacOS -## FreeBSD +Testing is done on macOS Catalina. -## Illumos +Clone whoami, and run: + +```rust +cargo run --example whoami-demo +cargo run --example whoami-demo --release +``` + +Expect to see something like: + +```console +WhoAmI 1.5.0 + +User's Language whoami::langs(): en/US +User's Name whoami::realname(): Aldaron Lau +User's Username whoami::username(): aldaronlau +User's Username whoami::fallible::account(): aldaronlau +Device's Pretty Name whoami::devicename(): Aldaron’s MacBook Air +Device's Hostname whoami::fallible::hostname(): Aldarons-MacBook-Air.local +Device's Platform whoami::platform(): Mac OS +Device's OS Distro whoami::distro(): Mac OS X 10.15.7 +Device's Desktop Env. whoami::desktop_env(): Aqua +Device's CPU Arch whoami::arch(): x86_64 +``` + +## BSD + +Testing is done on FreeBSD (virtualized on Fedora Silverblue): + +Download from within GNOME Boxes. + +Set 4 GiB memory, and 20 GiB Storage limit + +Go through the installation process, keeping all defaults. + +### Install packages + +Log in as root + +```shell +pkg install git +``` + +Log in as you + +```shell +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # 1 +. "$HOME/.cargo/env" +git clone https://github.com/ardaku/whoami.git +cd whoami +# run both debug and release +cargo run --example whoami-demo +cargo run --example whoami-demo --release +``` + +Expect to see something like: + +```console +WhoAmI 1.5.0 + +User's Language whoami::langs(): +User's Name whoami::realname(): Aldaron Lau +User's Username whoami::username(): aldaron +User's Username whoami::fallible::account(): aldaron +Device's Pretty Name whoami::devicename(): bsdtime +Device's Hostname whoami::fallible::hostname(): bsdtime +Device's Platform whoami::platform(): BSD +Device's OS Distro whoami::distro(): FreeBSD 14.0-RELEASE +Device's Desktop Env. whoami::desktop_env(): Unknown: Unknown +Device's CPU Arch whoami::arch(): x86_64 +``` + +## illumos Testing is done on Tribblix (virtualized on Fedora Silverblue): @@ -55,14 +265,14 @@ cargo run --example whoami-demo --release Expected output is ```console -WhoAmI 1.5.0-pre.0 +WhoAmI 1.5.0 -User's Language whoami::langs(): +User's Language whoami::langs(): ?? User's Name whoami::realname(): Tribblix Jack User's Username whoami::username(): jack Device's Pretty Name whoami::devicename(): tribblix Device's Hostname whoami::fallible::hostname(): tribblix -Device's Platform whoami::platform(): Illumos +Device's Platform whoami::platform(): illumos Device's OS Distro whoami::distro(): Tribblix Device's Desktop Env. whoami::desktop_env(): Unknown: Unknown Device's CPU Arch whoami::arch(): Unknown: i86pc @@ -153,3 +363,77 @@ desktop_env: Orbital platform: Redox arch: Unknown: x86_64 ``` + +## Web + +Build web example and start webserver on Fedora Silverblue. + +Check the web console in Firefox: + +```console +User's Name whoami::realname(): Anonymous +User's Username whoami::username(): anonymous +User's Language whoami::langs(): en/US, en +Device's Pretty Name whoami::devicename(): Firefox 122.0 +Device's Hostname whoami::fallible::hostname(): localhost +Device's Platform whoami::platform(): Linux +Device's OS Distro whoami::distro(): Unknown Linux +Device's Desktop Env. whoami::desktop_env(): Web Browser +Device's CPU Arch whoami::arch(): wasm32 +``` + +Check the web console in Opera: + +```console +User's Name whoami::realname(): Anonymous +User's Username whoami::username(): anonymous +User's Language whoami::langs(): en/US, en +Device's Pretty Name whoami::devicename(): Opera 107.0.0.0 +Device's Hostname whoami::fallible::hostname(): localhost +Device's Platform whoami::platform(): Linux +Device's OS Distro whoami::distro(): Unknown Linux +Device's Desktop Env. whoami::desktop_env(): Web Browser +Device's CPU Arch whoami::arch(): wasm32 +``` + +Check the web console in Chrome: + +```console +User's Name whoami::realname(): Anonymous +User's Username whoami::username(): anonymous +User's Language whoami::langs(): en/US, en +Device's Pretty Name whoami::devicename(): Chrome 122.0.0.0 +Device's Hostname whoami::fallible::hostname(): localhost +Device's Platform whoami::platform(): Linux +Device's OS Distro whoami::distro(): Unknown Linux +Device's Desktop Env. whoami::desktop_env(): Web Browser +Device's CPU Arch whoami::arch(): wasm32 +``` + +Check the web console in Ungoogled Chromium: + +```console +User's Name whoami::realname(): Anonymous +User's Username whoami::username(): anonymous +User's Language whoami::langs(): en/US, en +Device's Pretty Name whoami::devicename(): Chrome 122.0.0.0 +Device's Hostname whoami::fallible::hostname(): localhost +Device's Platform whoami::platform(): Linux +Device's OS Distro whoami::distro(): Unknown Linux +Device's Desktop Env. whoami::desktop_env(): Web Browser +Device's CPU Arch whoami::arch(): wasm32 +``` + +Check the web console in GNOME Web (Epiphany): + +```console +User's Name whoami::realname(): Anonymous +User's Username whoami::username(): anonymous +User's Language whoami::langs(): en/US +Device's Pretty Name whoami::devicename(): GNOME Web +Device's Hostname whoami::fallible::hostname(): localhost +Device's Platform whoami::platform(): Linux +Device's OS Distro whoami::distro(): Unknown Linux +Device's Desktop Env. whoami::desktop_env(): Web Browser +Device's CPU Arch whoami::arch(): wasm32 +``` diff --git a/WASM.md b/WASM.md index 66f5b02..e30c4f5 100644 --- a/WASM.md +++ b/WASM.md @@ -13,9 +13,9 @@ WhoAmI links to web-sys and defaults values to browser information: - `distro()`: Host distro by view of browser (Example "Unknown Linux") - `desktop_env()`: "Web Browser" -## Mock +## Fake If you compile WhoAmI with `default-features = false`, WhoAmI will not bind to -web-sys, and will instead return these mock values: +web-sys, and will instead return these fake values: - `realname()`: "Anonymous" - `username()`: "anonymous" @@ -24,7 +24,7 @@ web-sys, and will instead return these mock values: - `hostname()`: "localhost" - `platform()`: "Unknown" - `distro()`: "Emulated" - - `desktop_env()`: "Unknown WebAssembly" + - `desktop_env()`: "Unknown" ## Wasi (Wasite) Building WhoAmI targeting Wasi will assume the diff --git a/examples/os-strings.rs b/examples/os-strings.rs index 6cb2adf..bcf276a 100644 --- a/examples/os-strings.rs +++ b/examples/os-strings.rs @@ -2,44 +2,49 @@ fn main() { println!("WhoAmI {}", env!("CARGO_PKG_VERSION")); println!(); println!( - "User's Language whoami::langs(): {:?}", + "User's Language whoami::langs(): {:?}", whoami::langs() - .map(|l| l - .map(|l| l.to_string()) - .unwrap_or_else(|_| "??".to_string())) - .collect::>(), + .map(|l| { + l.map(|l| l.to_string()).collect::>().join(", ") + }) + .unwrap_or_else(|_| "??".to_string()), ); println!( - "User's Name whoami::realname_os(): {:?}", + "User's Name whoami::realname_os(): {:?}", whoami::realname_os(), ); println!( - "User's Username whoami::username_os(): {:?}", + "User's Username whoami::username_os(): {:?}", whoami::username_os(), ); println!( - "Device's Pretty Name whoami::devicename_os(): {:?}", + "User's Account whoami::fallible::account_os(): {:?}", + whoami::fallible::account_os() + .unwrap_or_else(|_| "".to_string().into()), + ); + println!( + "Device's Pretty Name whoami::devicename_os(): {:?}", whoami::devicename_os(), ); println!( - "Device's Hostname whoami::fallible::hostname(): {:?}", + "Device's Hostname whoami::fallible::hostname(): {:?}", whoami::fallible::hostname() .unwrap_or_else(|_| "localhost".to_string()), ); println!( - "Device's Platform whoami::platform(): {:?}", + "Device's Platform whoami::platform(): {:?}", whoami::platform(), ); println!( - "Device's OS Distro whoami::distro(): {:?}", + "Device's OS Distro whoami::distro(): {:?}", whoami::distro(), ); println!( - "Device's Desktop Env. whoami::desktop_env(): {:?}", + "Device's Desktop Env. whoami::desktop_env(): {:?}", whoami::desktop_env(), ); println!( - "Device's CPU Arch whoami::arch(): {:?}", + "Device's CPU Arch whoami::arch(): {:?}", whoami::arch(), ); } diff --git a/examples/web/Cargo.lock b/examples/web/Cargo.lock new file mode 100644 index 0000000..892a7a0 --- /dev/null +++ b/examples/web/Cargo.lock @@ -0,0 +1,188 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.5.0" +dependencies = [ + "redox_syscall", + "wasite", + "web-sys", +] + +[[package]] +name = "whoami_web" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "wasm-bindgen", + "web-sys", + "whoami", +] diff --git a/examples/web/src/lib.rs b/examples/web/src/lib.rs index 89945b5..4b1dc31 100644 --- a/examples/web/src/lib.rs +++ b/examples/web/src/lib.rs @@ -17,42 +17,44 @@ pub fn main() { // Print out code from regular example. log(format!( - "User's Name whoami::realname(): {}", + "User's Name whoami::realname(): {}", whoami::realname(), )); log(format!( - "User's Username whoami::username(): {}", + "User's Username whoami::username(): {}", whoami::username(), )); log(format!( - "User's Languages whoami::langs(): {:?}", + "User's Language whoami::langs(): {}", whoami::langs() - .map(|l| l.map(|l| l.to_string()).unwrap_or("??".to_string())) - .collect::>(), + .map(|l| { + l.map(|l| l.to_string()).collect::>().join(", ") + }) + .unwrap_or_else(|_| "??".to_string()), )); log(format!( - "Device's Pretty Name whoami::devicename(): {}", + "Device's Pretty Name whoami::devicename(): {}", whoami::devicename(), )); log(format!( - "Device's Hostname whoami::fallible::hostname(): {}", + "Device's Hostname whoami::fallible::hostname(): {}", whoami::fallible::hostname() .unwrap_or_else(|_| "localhost".to_string()), )); log(format!( - "Device's Platform whoami::platform(): {}", + "Device's Platform whoami::platform(): {}", whoami::platform(), )); log(format!( - "Device's OS Distro whoami::distro(): {}", + "Device's OS Distro whoami::distro(): {}", whoami::distro(), )); log(format!( - "Device's Desktop Env. whoami::desktop_env(): {}", + "Device's Desktop Env. whoami::desktop_env(): {}", whoami::desktop_env(), )); log(format!( - "Device's CPU Arch whoami::arch(): {}", + "Device's CPU Arch whoami::arch(): {}", whoami::arch(), )); } diff --git a/examples/whoami-demo.rs b/examples/whoami-demo.rs index 4ed6669..d63b653 100644 --- a/examples/whoami-demo.rs +++ b/examples/whoami-demo.rs @@ -4,11 +4,10 @@ fn main() { println!( "User's Language whoami::langs(): {}", whoami::langs() - .map(|l| l - .map(|l| l.to_string()) - .unwrap_or_else(|_| "??".to_string())) - .collect::>() - .join(", "), + .map(|l| { + l.map(|l| l.to_string()).collect::>().join(", ") + }) + .unwrap_or_else(|_| "??".to_string()), ); println!( "User's Name whoami::realname(): {}", @@ -18,6 +17,10 @@ fn main() { "User's Username whoami::username(): {}", whoami::username(), ); + println!( + "User's Username whoami::fallible::account(): {}", + whoami::fallible::account().unwrap_or_else(|_| "".to_string()), + ); println!( "Device's Pretty Name whoami::devicename(): {}", whoami::devicename(), diff --git a/recipe.toml b/recipe.toml index 10ad316..4304376 100644 --- a/recipe.toml +++ b/recipe.toml @@ -2,6 +2,7 @@ [source] git = "https://github.com/AldaronLau/whome.git" +rev = "0545b887c8419edddc79e5044e5d67f8ec9e0c30" [build] template = "cargo" diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index aacef61..0000000 --- a/rust-toolchain +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "1.40.0" diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000..fd4a117 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,191 @@ +use std::ffi::OsString; + +use crate::{ + fallible, + os::{Os, Target}, + Arch, DesktopEnv, Language, Platform, Result, +}; + +macro_rules! report_message { + () => { + "Please report this issue at https://github.com/ardaku/whoami/issues" + }; +} + +const DEFAULT_USERNAME: &str = "Unknown"; +const DEFAULT_HOSTNAME: &str = "LocalHost"; + +/// Get the CPU Architecture. +#[inline(always)] +pub fn arch() -> Arch { + Target::arch(Os).expect(concat!("arch() failed. ", report_message!())) +} + +/// Get the user's username. +/// +/// On unix-systems this differs from [`realname()`] most notably in that spaces +/// are not allowed in the username. +#[inline(always)] +pub fn username() -> String { + fallible::username().unwrap_or_else(|_| DEFAULT_USERNAME.to_lowercase()) +} + +/// Get the user's username. +/// +/// On unix-systems this differs from [`realname_os()`] most notably in that +/// spaces are not allowed in the username. +#[inline(always)] +pub fn username_os() -> OsString { + fallible::username_os() + .unwrap_or_else(|_| DEFAULT_USERNAME.to_lowercase().into()) +} + +/// Get the user's real (full) name. +#[inline(always)] +pub fn realname() -> String { + fallible::realname() + .or_else(|_| fallible::username()) + .unwrap_or_else(|_| DEFAULT_USERNAME.to_owned()) +} + +/// Get the user's real (full) name. +#[inline(always)] +pub fn realname_os() -> OsString { + fallible::realname_os() + .or_else(|_| fallible::username_os()) + .unwrap_or_else(|_| DEFAULT_USERNAME.to_owned().into()) +} + +/// Get the device name (also known as "Pretty Name"). +/// +/// Often used to identify device for bluetooth pairing. +#[inline(always)] +pub fn devicename() -> String { + fallible::devicename() + .or_else(|_| fallible::hostname()) + .unwrap_or_else(|_| DEFAULT_HOSTNAME.to_string()) +} + +/// Get the device name (also known as "Pretty Name"). +/// +/// Often used to identify device for bluetooth pairing. +#[inline(always)] +pub fn devicename_os() -> OsString { + fallible::devicename_os() + .or_else(|_| fallible::hostname().map(OsString::from)) + .unwrap_or_else(|_| DEFAULT_HOSTNAME.to_string().into()) +} + +/// Get the host device's hostname. +/// +/// Limited to a-z (case insensitive), 0-9, and dashes. This limit also applies +/// to `devicename()` with the exeception of case sensitivity when targeting +/// Windows. This method normalizes to lowercase. Usually hostnames will be +/// case-insensitive, but it's not a hard requirement. +/// +/// Use [`fallible::hostname()`] for case-sensitive hostname. +#[inline(always)] +#[deprecated(note = "use `fallible::hostname()` instead", since = "1.5.0")] +pub fn hostname() -> String { + let mut hostname = fallible::hostname() + .unwrap_or_else(|_| DEFAULT_HOSTNAME.to_lowercase()); + + hostname.make_ascii_lowercase(); + hostname +} + +/// Get the host device's hostname. +/// +/// Limited to a-z (case insensitive), 0-9, and dashes. This limit also applies +/// to `devicename()` with the exeception of case sensitivity when targeting +/// Windows. This method normalizes to lowercase. Usually hostnames will be +/// case-insensitive, but it's not a hard requirement. +/// +/// Use [`fallible::hostname()`] for case-sensitive hostname. +#[inline(always)] +#[deprecated(note = "use `fallible::hostname()` instead", since = "1.5.0")] +pub fn hostname_os() -> OsString { + #[allow(deprecated)] + hostname().into() +} + +/// Get the name of the operating system distribution and (possibly) version. +/// +/// Example: "Windows 10" or "Fedora 26 (Workstation Edition)" +#[inline(always)] +pub fn distro() -> String { + fallible::distro().unwrap_or_else(|_| format!("Unknown {}", platform())) +} + +/// Get the name of the operating system distribution and (possibly) version. +/// +/// Example: "Windows 10" or "Fedora 26 (Workstation Edition)" +#[inline(always)] +#[deprecated(note = "use `distro()` instead", since = "1.5.0")] +pub fn distro_os() -> OsString { + fallible::distro() + .map(OsString::from) + .unwrap_or_else(|_| format!("Unknown {}", platform()).into()) +} + +/// Get the desktop environment. +/// +/// Example: "gnome" or "windows" +#[inline(always)] +pub fn desktop_env() -> DesktopEnv { + Target::desktop_env(Os) +} + +/// Get the platform. +#[inline(always)] +pub fn platform() -> Platform { + Target::platform(Os) +} + +/// Get the user's preferred language(s). +/// +/// Returned as iterator of two letter language codes (lowercase), optionally +/// followed by a dash (-) and a two letter country code (uppercase). The most +/// preferred language is returned first, followed by next preferred, and so on. +#[inline(always)] +#[deprecated(note = "use `langs()` instead", since = "1.5.0")] +pub fn lang() -> impl Iterator { + let langs_vec = if let Ok(langs) = langs() { + langs + .map(|lang| lang.to_string().replace('/', "-")) + .collect() + } else { + ["en-US".to_string()].to_vec() + }; + + langs_vec.into_iter() +} + +/// Get the user's preferred language(s). +/// +/// Returned as iterator of [`Language`]s. The most preferred language is +/// returned first, followed by next preferred, and so on. Unrecognized +/// languages may either return an error or be skipped. +#[inline(always)] +pub fn langs() -> Result> { + // FIXME: Could do less allocation + let langs = Target::langs(Os)?; + let langs = langs + .split(';') + .map(ToString::to_string) + .collect::>(); + + Ok(langs.into_iter().filter_map(|lang| { + let lang = lang + .split_terminator('.') + .next() + .unwrap_or_default() + .replace(|x| ['_', '-'].contains(&x), "/"); + + if lang == "C" { + return None; + } + + Some(Language::__(Box::new(lang))) + })) +} diff --git a/src/arch.rs b/src/arch.rs new file mode 100644 index 0000000..54db122 --- /dev/null +++ b/src/arch.rs @@ -0,0 +1,148 @@ +use std::{ + fmt::{self, Display, Formatter}, + io::{Error, ErrorKind}, +}; + +use crate::Result; + +/// The address width of a CPU architecture +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[non_exhaustive] +pub enum Width { + /// 32 bits + Bits32, + /// 64 bits + Bits64, +} + +impl Display for Width { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Width::Bits32 => "32 bits", + Width::Bits64 => "64 bits", + }) + } +} + +/// The architecture of a CPU +#[non_exhaustive] +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Arch { + /// ARMv5 + ArmV5, + /// ARMv6 (Sometimes just referred to as ARM) + ArmV6, + /// ARMv7 (May or may not support Neon/Thumb) + ArmV7, + /// ARM64 (aarch64) + Arm64, + /// i386 (x86) + I386, + /// i586 (x86) + I586, + /// i686 (x86) + I686, + /// X86_64 / Amd64 + X64, + /// MIPS + Mips, + /// MIPS (LE) + MipsEl, + /// MIPS64 + Mips64, + /// MIPS64 (LE) + Mips64El, + /// PowerPC + PowerPc, + /// PowerPC64 + PowerPc64, + /// PowerPC64LE + PowerPc64Le, + /// 32-bit RISC-V + Riscv32, + /// 64-bit RISC-V + Riscv64, + /// S390x + S390x, + /// SPARC + Sparc, + /// SPARC64 + Sparc64, + /// 32-bit Web Assembly + Wasm32, + /// 64-bit Web Assembly + Wasm64, + /// Unknown Architecture + Unknown(String), +} + +impl Display for Arch { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Self::Unknown(_) = self { + f.write_str("Unknown: ")?; + } + + f.write_str(match self { + Self::ArmV5 => "armv5", + Self::ArmV6 => "armv6", + Self::ArmV7 => "armv7", + Self::Arm64 => "arm64", + Self::I386 => "i386", + Self::I586 => "i586", + Self::I686 => "i686", + Self::Mips => "mips", + Self::MipsEl => "mipsel", + Self::Mips64 => "mips64", + Self::Mips64El => "mips64el", + Self::PowerPc => "powerpc", + Self::PowerPc64 => "powerpc64", + Self::PowerPc64Le => "powerpc64le", + Self::Riscv32 => "riscv32", + Self::Riscv64 => "riscv64", + Self::S390x => "s390x", + Self::Sparc => "sparc", + Self::Sparc64 => "sparc64", + Self::Wasm32 => "wasm32", + Self::Wasm64 => "wasm64", + Self::X64 => "x86_64", + Self::Unknown(arch) => arch, + }) + } +} + +impl Arch { + /// Get the width of this architecture. + pub fn width(&self) -> Result { + match self { + Arch::ArmV5 + | Arch::ArmV6 + | Arch::ArmV7 + | Arch::I386 + | Arch::I586 + | Arch::I686 + | Arch::Mips + | Arch::MipsEl + | Arch::PowerPc + | Arch::Riscv32 + | Arch::Sparc + | Arch::Wasm32 => Ok(Width::Bits32), + Arch::Arm64 + | Arch::Mips64 + | Arch::Mips64El + | Arch::PowerPc64 + | Arch::PowerPc64Le + | Arch::Riscv64 + | Arch::S390x + | Arch::Sparc64 + | Arch::Wasm64 + | Arch::X64 => Ok(Width::Bits64), + Arch::Unknown(unknown_arch) => Err(Error::new( + ErrorKind::InvalidData, + format!( + "Tried getting width of unknown arch ({})", + unknown_arch, + ), + )), + } + } +} diff --git a/src/desktop_env.rs b/src/desktop_env.rs new file mode 100644 index 0000000..70ff0ad --- /dev/null +++ b/src/desktop_env.rs @@ -0,0 +1,92 @@ +use std::fmt::{self, Display, Formatter}; + +// FIXME: V2: Move `Unknown` variants to the top of the enum. + +/// The desktop environment of a system +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum DesktopEnv { + /// Popular GTK-based desktop environment on Linux + Gnome, + /// One of the desktop environments for a specific version of Windows + Windows, + /// Linux desktop environment optimized for low resource requirements + Lxde, + /// Stacking window manager for X Windows on Linux + Openbox, + /// Desktop environment for Linux, BSD and illumos + Mate, + /// Lightweight desktop enivornment for unix-like operating systems + Xfce, + /// KDE Plasma desktop enviroment + // FIXME: Rename to 'Plasma' in whoami 2.0.0 + Kde, + /// Default desktop environment on Linux Mint + Cinnamon, + /// Tiling window manager for Linux + I3, + /// Desktop environment for MacOS + Aqua, + /// Desktop environment for iOS + Ios, + /// Desktop environment for Android + Android, + /// Running as Web Assembly on a web page + WebBrowser, + /// A desktop environment for a video game console + Console, + /// Ubuntu-branded GNOME + Ubuntu, + /// Default shell for Fuchsia + Ermine, + /// Default desktop environment for Redox + Orbital, + /// Unknown desktop environment + Unknown(String), +} + +impl Display for DesktopEnv { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Self::Unknown(_) = self { + f.write_str("Unknown: ")?; + } + + f.write_str(match self { + Self::Gnome => "Gnome", + Self::Windows => "Windows", + Self::Lxde => "LXDE", + Self::Openbox => "Openbox", + Self::Mate => "Mate", + Self::Xfce => "XFCE", + Self::Kde => "KDE", + Self::Cinnamon => "Cinnamon", + Self::I3 => "I3", + Self::Aqua => "Aqua", + Self::Ios => "IOS", + Self::Android => "Android", + Self::WebBrowser => "Web Browser", + Self::Console => "Console", + Self::Ubuntu => "Ubuntu", + Self::Ermine => "Ermine", + Self::Orbital => "Orbital", + Self::Unknown(a) => a, + }) + } +} + +impl DesktopEnv { + /// Returns true if the desktop environment is based on GTK. + pub fn is_gtk(&self) -> bool { + *self == Self::Gnome + || *self == Self::Ubuntu + || *self == Self::Cinnamon + || *self == Self::Lxde + || *self == Self::Mate + || *self == Self::Xfce + } + + /// Returns true if the desktop environment is based on KDE. + pub fn is_kde(&self) -> bool { + *self == Self::Kde + } +} diff --git a/src/fallible.rs b/src/fallible.rs index d6ff0d8..64f5bce 100644 --- a/src/fallible.rs +++ b/src/fallible.rs @@ -12,6 +12,28 @@ use crate::{ Result, }; +/// Get the user's account name; usually just the username, but may include an +/// account server hostname. +/// +/// If you don't want the account server hostname, use [`username()`]. +/// +/// Example: `username@example.com` +#[inline(always)] +pub fn account() -> Result { + account_os().and_then(conversions::string_from_os) +} + +/// Get the user's account name; usually just the username, but may include an +/// account server hostname. +/// +/// If you don't want the account server hostname, use [`username()`]. +/// +/// Example: `username@example.com` +#[inline(always)] +pub fn account_os() -> Result { + Target::account(Os) +} + /// Get the user's username. /// /// On unix-systems this differs from [`realname()`] most notably in that spaces diff --git a/src/language.rs b/src/language.rs new file mode 100644 index 0000000..0d58906 --- /dev/null +++ b/src/language.rs @@ -0,0 +1,81 @@ +use std::fmt::{self, Display, Formatter}; + +/// Country code for a [`Language`] dialect +/// +/// Uses +#[non_exhaustive] +#[repr(u32)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Country { + // FIXME: V2: u32::from_ne_bytes for country codes, with `\0` for unused + // FIXME: Add aliases up to 3-4 letters, but hidden + /// Any dialect + Any, + /// `US`: United States of America + #[doc(hidden)] + Us, +} + +impl Display for Country { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::Any => "**", + Self::Us => "US", + }) + } +} + +/// A spoken language +/// +/// Use [`ToString::to_string()`] to convert to string of two letter lowercase +/// language code followed an forward slash and uppercase country code (example: +/// `en/US`). +/// +/// Uses +#[non_exhaustive] +#[derive(Clone, Eq, PartialEq, Debug)] +// #[allow(variant_size_differences)] +pub enum Language { + #[doc(hidden)] + __(Box), + /// `en`: English + #[doc(hidden)] + En(Country), + /// `es`: Spanish + #[doc(hidden)] + Es(Country), +} + +impl Language { + /// Retrieve the country code for this language dialect. + pub fn country(&self) -> Country { + match self { + Self::__(_) => Country::Any, + Self::En(country) | Self::Es(country) => *country, + } + } +} + +impl Display for Language { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::__(code) => f.write_str(code.as_str()), + Self::En(country) => { + if *country != Country::Any { + f.write_str("en/")?; + ::fmt(country, f) + } else { + f.write_str("en") + } + } + Self::Es(country) => { + if *country != Country::Any { + f.write_str("es/")?; + ::fmt(country, f) + } else { + f.write_str("es") + } + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index f2b4cea..c755bcf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,521 +64,30 @@ unsafe_code )] #![doc( - html_logo_url = "https://raw.githubusercontent.com/ardaku/whoami/stable/res/icon.svg", - html_favicon_url = "https://raw.githubusercontent.com/ardaku/whoami/stable/res/icon.svg" + html_logo_url = "https://raw.githubusercontent.com/ardaku/whoami/v1/res/icon.svg", + html_favicon_url = "https://raw.githubusercontent.com/ardaku/whoami/v1/res/icon.svg" )] +mod api; +mod arch; mod conversions; +mod desktop_env; pub mod fallible; +mod language; mod os; - -use std::{ - ffi::OsString, - fmt::{self, Display, Formatter}, - io::{Error, ErrorKind}, +mod platform; +mod result; + +#[allow(deprecated)] +pub use self::{ + api::{ + arch, desktop_env, devicename, devicename_os, distro, distro_os, + hostname, hostname_os, lang, langs, platform, realname, realname_os, + username, username_os, + }, + arch::{Arch, Width}, + desktop_env::DesktopEnv, + language::{Country, Language}, + platform::Platform, + result::Result, }; - -use crate::os::{Os, Target}; - -macro_rules! report_message { - () => { - "Please report this issue at https://github.com/ardaku/whoami/issues" - }; -} - -const DEFAULT_USERNAME: &str = "Unknown"; -const DEFAULT_HOSTNAME: &str = "LocalHost"; - -/// This crate's convenience type alias for [`Result`](std::result::Result)s -pub type Result = std::result::Result; - -/// Country code for a [`Language`] dialect -/// -/// Uses -#[non_exhaustive] -#[repr(u32)] -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum Country { - // FIXME: V2: u32::from_ne_bytes for country codes, with `\0` for unused - // FIXME: Add aliases up to 3-4 letters, but hidden - /// Any dialect - Any, - /// `US`: United States of America - #[doc(hidden)] - Us, -} - -impl Display for Country { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Self::Any => "**", - Self::Us => "US", - }) - } -} - -/// A spoken language -/// -/// Use [`ToString::to_string()`] to convert to string of two letter lowercase -/// language code followed an forward slash and uppercase country code (example: -/// `en/US`). -/// -/// Uses -#[non_exhaustive] -#[derive(Clone, Eq, PartialEq, Debug)] -// #[allow(variant_size_differences)] -pub enum Language { - #[doc(hidden)] - __(Box), - /// `en`: English - #[doc(hidden)] - En(Country), - /// `es`: Spanish - #[doc(hidden)] - Es(Country), -} - -impl Language { - /// Retrieve the country code for this language dialect. - pub fn country(&self) -> Country { - match self { - Self::__(_) => Country::Any, - Self::En(country) | Self::Es(country) => *country, - } - } -} - -impl Display for Language { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::__(code) => f.write_str(code.as_str()), - Self::En(country) => { - if *country != Country::Any { - f.write_str("en/")?; - ::fmt(country, f) - } else { - f.write_str("en") - } - } - Self::Es(country) => { - if *country != Country::Any { - f.write_str("es/")?; - ::fmt(country, f) - } else { - f.write_str("es") - } - } - } - } -} - -// FIXME: V2: Move `Unknown` variants to the top of the enum. - -/// The desktop environment of a system -#[derive(Debug, PartialEq, Eq, Clone)] -#[non_exhaustive] -pub enum DesktopEnv { - /// Popular GTK-based desktop environment on Linux - Gnome, - /// One of the desktop environments for a specific version of Windows - Windows, - /// Linux desktop environment optimized for low resource requirements - Lxde, - /// Stacking window manager for X Windows on Linux - Openbox, - /// Desktop environment for Linux, BSD and Illumos - Mate, - /// Lightweight desktop enivornment for unix-like operating systems - Xfce, - /// KDE Plasma desktop enviroment - // FIXME: Rename to 'Plasma' in whoami 2.0.0 - Kde, - /// Default desktop environment on Linux Mint - Cinnamon, - /// Tiling window manager for Linux - I3, - /// Desktop environment for MacOS - Aqua, - /// Desktop environment for iOS - Ios, - /// Desktop environment for Android - Android, - /// Running as Web Assembly on a web page - WebBrowser, - /// A desktop environment for a video game console - Console, - /// Ubuntu-branded GNOME - Ubuntu, - /// Default shell for Fuchsia - Ermine, - /// Default desktop environment for Redox - Orbital, - /// Unknown desktop environment - Unknown(String), -} - -impl Display for DesktopEnv { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Self::Unknown(_) = self { - f.write_str("Unknown: ")?; - } - - f.write_str(match self { - Self::Gnome => "Gnome", - Self::Windows => "Windows", - Self::Lxde => "LXDE", - Self::Openbox => "Openbox", - Self::Mate => "Mate", - Self::Xfce => "XFCE", - Self::Kde => "KDE", - Self::Cinnamon => "Cinnamon", - Self::I3 => "I3", - Self::Aqua => "Aqua", - Self::Ios => "IOS", - Self::Android => "Android", - Self::WebBrowser => "Web Browser", - Self::Console => "Console", - Self::Ubuntu => "Ubuntu", - Self::Ermine => "Ermine", - Self::Orbital => "Orbital", - Self::Unknown(a) => a, - }) - } -} - -/// The underlying platform for a system -#[allow(missing_docs)] -#[derive(Debug, PartialEq, Eq, Clone)] -#[non_exhaustive] -pub enum Platform { - Linux, - Bsd, - Windows, - // FIXME: Non-standard casing; Rename to 'Mac' rather than 'MacOs' in - // whoami 2.0.0 - MacOS, - Illumos, - Ios, - Android, - Nintendo, - Xbox, - PlayStation, - Fuchsia, - Redox, - Unknown(String), -} - -impl Display for Platform { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Self::Unknown(_) = self { - f.write_str("Unknown: ")?; - } - - f.write_str(match self { - Self::Linux => "Linux", - Self::Bsd => "BSD", - Self::Windows => "Windows", - Self::MacOS => "Mac OS", - Self::Illumos => "Illumos", - Self::Ios => "iOS", - Self::Android => "Android", - Self::Nintendo => "Nintendo", - Self::Xbox => "XBox", - Self::PlayStation => "PlayStation", - Self::Fuchsia => "Fuchsia", - Self::Redox => "Redox", - Self::Unknown(a) => a, - }) - } -} - -/// The architecture of a CPU -#[non_exhaustive] -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum Arch { - /// ARMv5 - ArmV5, - /// ARMv6 (Sometimes just referred to as ARM) - ArmV6, - /// ARMv7 (May or may not support Neon/Thumb) - ArmV7, - /// ARM64 (aarch64) - Arm64, - /// i386 (x86) - I386, - /// i586 (x86) - I586, - /// i686 (x86) - I686, - /// X86_64 / Amd64 - X64, - /// MIPS - Mips, - /// MIPS (LE) - MipsEl, - /// MIPS64 - Mips64, - /// MIPS64 (LE) - Mips64El, - /// PowerPC - PowerPc, - /// PowerPC64 - PowerPc64, - /// PowerPC64LE - PowerPc64Le, - /// 32-bit RISC-V - Riscv32, - /// 64-bit RISC-V - Riscv64, - /// S390x - S390x, - /// SPARC - Sparc, - /// SPARC64 - Sparc64, - /// 32-bit Web Assembly - Wasm32, - /// 64-bit Web Assembly - Wasm64, - /// Unknown Architecture - Unknown(String), -} - -impl Display for Arch { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Self::Unknown(_) = self { - f.write_str("Unknown: ")?; - } - - f.write_str(match self { - Self::ArmV5 => "armv5", - Self::ArmV6 => "armv6", - Self::ArmV7 => "armv7", - Self::Arm64 => "arm64", - Self::I386 => "i386", - Self::I586 => "i586", - Self::I686 => "i686", - Self::Mips => "mips", - Self::MipsEl => "mipsel", - Self::Mips64 => "mips64", - Self::Mips64El => "mips64el", - Self::PowerPc => "powerpc", - Self::PowerPc64 => "powerpc64", - Self::PowerPc64Le => "powerpc64le", - Self::Riscv32 => "riscv32", - Self::Riscv64 => "riscv64", - Self::S390x => "s390x", - Self::Sparc => "sparc", - Self::Sparc64 => "sparc64", - Self::Wasm32 => "wasm32", - Self::Wasm64 => "wasm64", - Self::X64 => "x86_64", - Self::Unknown(arch) => arch, - }) - } -} - -/// The address width of a CPU architecture -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -#[non_exhaustive] -pub enum Width { - /// 32 bits - Bits32, - /// 64 bits - Bits64, -} - -impl Display for Width { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Width::Bits32 => "32 bits", - Width::Bits64 => "64 bits", - }) - } -} - -impl Arch { - /// Get the width of this architecture. - pub fn width(&self) -> Result { - match self { - Arch::ArmV5 - | Arch::ArmV6 - | Arch::ArmV7 - | Arch::I386 - | Arch::I586 - | Arch::I686 - | Arch::Mips - | Arch::MipsEl - | Arch::PowerPc - | Arch::Riscv32 - | Arch::Sparc - | Arch::Wasm32 => Ok(Width::Bits32), - Arch::Arm64 - | Arch::Mips64 - | Arch::Mips64El - | Arch::PowerPc64 - | Arch::PowerPc64Le - | Arch::Riscv64 - | Arch::S390x - | Arch::Sparc64 - | Arch::Wasm64 - | Arch::X64 => Ok(Width::Bits64), - Arch::Unknown(unknown_arch) => Err(Error::new( - ErrorKind::InvalidData, - format!( - "Tried getting width of unknown arch ({})", - unknown_arch, - ), - )), - } - } -} - -/// Get the CPU Architecture. -#[inline(always)] -pub fn arch() -> Arch { - Target::arch(Os).expect(concat!("arch() failed. ", report_message!())) -} - -/// Get the user's username. -/// -/// On unix-systems this differs from [`realname()`] most notably in that spaces -/// are not allowed in the username. -#[inline(always)] -pub fn username() -> String { - fallible::username().unwrap_or_else(|_| DEFAULT_USERNAME.to_lowercase()) -} - -/// Get the user's username. -/// -/// On unix-systems this differs from [`realname_os()`] most notably in that -/// spaces are not allowed in the username. -#[inline(always)] -pub fn username_os() -> OsString { - fallible::username_os() - .unwrap_or_else(|_| DEFAULT_USERNAME.to_lowercase().into()) -} - -/// Get the user's real (full) name. -#[inline(always)] -pub fn realname() -> String { - fallible::realname() - .or_else(|_| fallible::username()) - .unwrap_or_else(|_| DEFAULT_USERNAME.to_owned()) -} - -/// Get the user's real (full) name. -#[inline(always)] -pub fn realname_os() -> OsString { - fallible::realname_os() - .or_else(|_| fallible::username_os()) - .unwrap_or_else(|_| DEFAULT_USERNAME.to_owned().into()) -} - -/// Get the device name (also known as "Pretty Name"). -/// -/// Often used to identify device for bluetooth pairing. -#[inline(always)] -pub fn devicename() -> String { - fallible::devicename() - .or_else(|_| fallible::hostname()) - .unwrap_or_else(|_| DEFAULT_HOSTNAME.to_string()) -} - -/// Get the device name (also known as "Pretty Name"). -/// -/// Often used to identify device for bluetooth pairing. -#[inline(always)] -pub fn devicename_os() -> OsString { - fallible::devicename_os() - .or_else(|_| fallible::hostname().map(OsString::from)) - .unwrap_or_else(|_| DEFAULT_HOSTNAME.to_string().into()) -} - -/// Get the host device's hostname. -/// -/// Limited to a-z (case insensitive), 0-9, and dashes. This limit also applies -/// to `devicename()` with the exeception of case sensitivity when targeting -/// Windows. This method normalizes to lowercase. Usually hostnames will be -/// case-insensitive, but it's not a hard requirement. -/// -/// Use [`fallible::hostname()`] for case-sensitive hostname. -#[inline(always)] -#[deprecated(note = "use `fallible::hostname()` instead", since = "1.5.0")] -pub fn hostname() -> String { - let mut hostname = fallible::hostname() - .unwrap_or_else(|_| DEFAULT_HOSTNAME.to_lowercase()); - - hostname.make_ascii_lowercase(); - hostname -} - -/// Get the host device's hostname. -/// -/// Limited to a-z (case insensitive), 0-9, and dashes. This limit also applies -/// to `devicename()` with the exeception of case sensitivity when targeting -/// Windows. This method normalizes to lowercase. Usually hostnames will be -/// case-insensitive, but it's not a hard requirement. -/// -/// Use [`fallible::hostname()`] for case-sensitive hostname. -#[inline(always)] -#[deprecated(note = "use `fallible::hostname()` instead", since = "1.5.0")] -pub fn hostname_os() -> OsString { - #[allow(deprecated)] - hostname().into() -} - -/// Get the name of the operating system distribution and (possibly) version. -/// -/// Example: "Windows 10" or "Fedora 26 (Workstation Edition)" -#[inline(always)] -pub fn distro() -> String { - fallible::distro().unwrap_or_else(|_| format!("Unknown {}", platform())) -} - -/// Get the name of the operating system distribution and (possibly) version. -/// -/// Example: "Windows 10" or "Fedora 26 (Workstation Edition)" -#[inline(always)] -#[deprecated(note = "use `distro()` instead", since = "1.5.0")] -pub fn distro_os() -> OsString { - fallible::distro() - .map(OsString::from) - .unwrap_or_else(|_| format!("Unknown {}", platform()).into()) -} - -/// Get the desktop environment. -/// -/// Example: "gnome" or "windows" -#[inline(always)] -pub fn desktop_env() -> DesktopEnv { - Target::desktop_env(Os) -} - -/// Get the platform. -#[inline(always)] -pub fn platform() -> Platform { - Target::platform(Os) -} - -/// Get the user's preferred language(s). -/// -/// Returned as iterator of two letter language codes (lowercase), optionally -/// followed by a dash (-) and a two letter country code (uppercase). The most -/// preferred language is returned first, followed by next preferred, and so on. -#[inline(always)] -#[deprecated(note = "use `langs()` instead", since = "1.5.0")] -pub fn lang() -> impl Iterator { - os::lang() -} - -/// Get the user's preferred language(s). -/// -/// Returned as iterator of [`Language`]s wrapped in [`Result`]s. The most -/// preferred language is returned first, followed by next preferred, and so on. -/// Unrecognized languages may return an error. -#[inline(always)] -pub fn langs() -> impl Iterator> { - #[allow(deprecated)] - lang().map(|string| Ok(Language::__(Box::new(string)))) -} diff --git a/src/os.rs b/src/os.rs index 0c6c45e..ac43a54 100644 --- a/src/os.rs +++ b/src/os.rs @@ -1,5 +1,10 @@ #![allow(unsafe_code)] +// Daku +#[cfg_attr( + all(target_arch = "wasm32", target_os = "daku"), + path = "os/daku.rs" +)] // Redox #[cfg_attr( all(target_os = "redox", not(target_arch = "wasm32")), @@ -7,24 +12,26 @@ )] // Unix #[cfg_attr( - not(any( - target_arch = "wasm32", - target_os = "redox", - target_os = "windows", - )), + all( + any( + target_os = "linux", + target_os = "macos", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ), + not(target_arch = "wasm32") + ), path = "os/unix.rs" )] -// Wasm32 (Daku) -#[cfg_attr( - all(target_arch = "wasm32", target_os = "daku"), - path = "os/daku.rs" -)] -// Wasm32 (Wasi) +// Wasi #[cfg_attr( all(target_arch = "wasm32", target_os = "wasi"), path = "os/wasi.rs" )] -// Wasm32 (Web) +// Web #[cfg_attr( all( target_arch = "wasm32", @@ -34,16 +41,6 @@ ), path = "os/web.rs" )] -// Wasm32 (Unknown) -#[cfg_attr( - all( - target_arch = "wasm32", - not(target_os = "wasi"), - not(target_os = "daku"), - not(feature = "web"), - ), - path = "os/fake.rs" -)] // Windows #[cfg_attr( all(target_os = "windows", not(target_arch = "wasm32")), @@ -52,21 +49,20 @@ mod target; use std::{ + env::{self, VarError}, ffi::OsString, io::{Error, ErrorKind}, }; -pub(crate) use self::target::*; -use crate::{Arch, DesktopEnv, Language, Platform, Result}; +use crate::{Arch, DesktopEnv, Platform, Result}; /// Implement `Target for Os` to add platform support for a target. pub(crate) struct Os; /// Target platform support -pub(crate) trait Target { - /// Return a list of languages. - #[allow(dead_code)] // FIXME - fn langs(self) -> Vec; +pub(crate) trait Target: Sized { + /// Return a semicolon-delimited string of language/COUNTRY codes. + fn langs(self) -> Result; /// Return the user's "real" / "full" name. fn realname(self) -> Result; /// Return the user's username. @@ -83,6 +79,12 @@ pub(crate) trait Target { fn platform(self) -> Platform; /// Return the computer's CPU architecture. fn arch(self) -> Result; + + /// Return the user's account name (usually just the username, but may + /// include an account server hostname). + fn account(self) -> Result { + self.username() + } } // This is only used on some platforms @@ -102,3 +104,24 @@ fn err_null_record() -> Error { fn err_empty_record() -> Error { Error::new(ErrorKind::NotFound, "Empty record") } + +// This is only used on some platforms +#[allow(dead_code)] +fn unix_lang() -> Result { + let check_var = |var| { + env::var(var).map_err(|e| { + let kind = match e { + VarError::NotPresent => ErrorKind::NotFound, + VarError::NotUnicode(_) => ErrorKind::InvalidData, + }; + Error::new(kind, e) + }) + }; + let langs = check_var("LANGS").or_else(|_| check_var("LANG"))?; + + if langs.is_empty() { + return Err(err_empty_record()); + } + + Ok(langs) +} diff --git a/src/os/daku.rs b/src/os/daku.rs index a4cf16f..a7f720a 100644 --- a/src/os/daku.rs +++ b/src/os/daku.rs @@ -1,23 +1,19 @@ //! This is mostly the same as fake.rs for now -#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] -compile_error!("Unexpected pointer width for target platform"); - -use std::ffi::OsString; +use std::{ + ffi::OsString, + io::{Error, ErrorKind}, +}; use crate::{ os::{Os, Target}, - Arch, DesktopEnv, Language, Platform, Result, + Arch, DesktopEnv, Platform, Result, }; -#[inline(always)] -pub(crate) fn lang() -> impl Iterator { - std::iter::once("en/US".to_string()) -} - impl Target for Os { - fn langs(self) -> Vec { - todo!() + #[inline(always)] + fn langs(self) -> Result { + Ok("en/US".to_string()) } #[inline(always)] @@ -59,8 +55,13 @@ impl Target for Os { fn arch(self) -> Result { Ok(if cfg!(target_pointer_width = "64") { Arch::Wasm64 - } else { + } else if cfg!(target_pointer_width = "32") { Arch::Wasm32 + } else { + return Err(Error::new( + ErrorKind::Other, // FIXME: WhoAmI 2.0, Unsupported + "Unexpected pointer width for target platform", + )); }) } } diff --git a/src/os/fake.rs b/src/os/fake.rs deleted file mode 100644 index 152bc88..0000000 --- a/src/os/fake.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Currently used for WebAssembly unknown (non-web) only - -#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] -compile_error!("Unexpected pointer width for target platform"); - -use std::ffi::OsString; - -use crate::{ - os::{Os, Target}, - Arch, DesktopEnv, Language, Platform, Result, -}; - -#[inline(always)] -pub(crate) fn lang() -> impl Iterator { - std::iter::once("en/US".to_string()) -} - -impl Target for Os { - fn langs(self) -> Vec { - todo!() - } - - #[inline(always)] - fn realname(self) -> Result { - Ok("Anonymous".to_string().into()) - } - - #[inline(always)] - fn username(self) -> Result { - Ok("anonymous".to_string().into()) - } - - #[inline(always)] - fn devicename(self) -> Result { - Ok("Unknown".to_string().into()) - } - - #[inline(always)] - fn hostname(self) -> Result { - Ok("localhost".to_string()) - } - - #[inline(always)] - fn distro(self) -> Result { - Ok("Emulated".to_string()) - } - - #[inline(always)] - fn desktop_env(self) -> DesktopEnv { - DesktopEnv::Unknown("WebAssembly".to_string()) - } - - #[inline(always)] - fn platform(self) -> Platform { - Platform::Unknown("Unknown".to_string()) - } - - #[inline(always)] - fn arch(self) -> Result { - Ok(if cfg!(target_pointer_width = "64") { - Arch::Wasm64 - } else { - Arch::Wasm32 - }) - } -} diff --git a/src/os/redox.rs b/src/os/redox.rs index ab3c3a7..2e7994a 100644 --- a/src/os/redox.rs +++ b/src/os/redox.rs @@ -9,7 +9,7 @@ use syscall::{call, error}; use crate::{ os::{Os, Target}, - Arch, DesktopEnv, Language, Platform, Result, + Arch, DesktopEnv, Platform, Result, }; /// Row in the Redox /etc/passwd file @@ -89,15 +89,9 @@ fn hostname() -> Result { Ok(hostname_file.lines().next().unwrap_or_default().to_string()) } -#[inline(always)] -pub(crate) fn lang() -> impl Iterator { - // FIXME: Look for unix-like language env vars - std::iter::once("en/US".to_string()) -} - impl Target for Os { - fn langs(self) -> Vec { - todo!() + fn langs(self) -> Result { + super::unix_lang() } #[inline(always)] diff --git a/src/os/target.rs b/src/os/target.rs new file mode 100644 index 0000000..b861946 --- /dev/null +++ b/src/os/target.rs @@ -0,0 +1,124 @@ +//! Unknown target, fake implementation. +//! +//! This can be used as a template when adding new target support. + +use std::{ + ffi::OsString, + io::{Error, ErrorKind}, +}; + +use crate::{ + os::{Os, Target}, + Arch, DesktopEnv, Platform, Result, +}; + +impl Target for Os { + #[inline(always)] + fn langs(self) -> Result { + Ok("en/US".to_string()) + } + + #[inline(always)] + fn realname(self) -> Result { + Ok("Anonymous".to_string().into()) + } + + #[inline(always)] + fn username(self) -> Result { + Ok("anonymous".to_string().into()) + } + + #[inline(always)] + fn devicename(self) -> Result { + Ok("Unknown".to_string().into()) + } + + #[inline(always)] + fn hostname(self) -> Result { + Ok("localhost".to_string()) + } + + #[inline(always)] + fn distro(self) -> Result { + Ok(format!("Unknown {}", self.platform())) + } + + #[inline(always)] + fn desktop_env(self) -> DesktopEnv { + DesktopEnv::Unknown("Unknown".to_string()) + } + + #[inline(always)] + fn platform(self) -> Platform { + if cfg!(target_os = "daku") { + Platform::Unknown("Daku".to_string()) + } else if cfg!(target_os = "wasi") { + Platform::Unknown("WASI".to_string()) + } else if cfg!(target_os = "windows") { + Platform::Windows + } else if cfg!(target_os = "macos") { + Platform::MacOS + } else if cfg!(target_os = "redox") { + Platform::Redox + } else if cfg!(target_os = "linux") { + Platform::Linux + } else if cfg!(target_os = "android") { + Platform::Android + } else if cfg!(target_os = "tvos") { + Platform::Unknown("tvOS".to_string()) + } else if cfg!(target_os = "watchos") { + Platform::Unknown("watchOS".to_string()) + } else if cfg!(target_os = "ios") { + Platform::Unknown("iOS".to_string()) + } else if cfg!(target_os = "fuchsia") { + Platform::Fuchsia + } else if cfg!(target_os = "illumos") { + Platform::Illumos + } else if cfg!(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + )) { + Platform::Bsd + } else if cfg!(target_os = "haiku") { + Platform::Unknown("Haiku".to_string()) + } else if cfg!(target_os = "vxworks") { + Platform::Unknown("VxWorks".to_string()) + } else if cfg!(target_os = "nto") { + Platform::Unknown("QNX Neutrino".to_string()) + } else if cfg!(target_os = "horizon") { + Platform::Nintendo + } else if cfg!(target_os = "vita") { + Platform::PlayStation + } else if cfg!(target_os = "hurd") { + Platform::Unknown("GNU Hurd".to_string()) + } else if cfg!(target_os = "aix") { + Platform::Unknown("AIX OS".to_string()) + } else if cfg!(target_os = "espidf") { + Platform::Unknown("ESP-IDF".to_string()) + } else if cfg!(target_os = "emscripten") { + Platform::Unknown("Emscripten".to_string()) + } else if cfg!(target_os = "solaris") { + Platform::Unknown("Solaris".to_string()) + } else if cfg!(target_os = "l4re") { + Platform::Unknown("L4 Runtime Environment".to_string()) + } else { + Platform::Unknown("Unknown".to_string()) + } + } + + #[inline(always)] + fn arch(self) -> Result { + Ok(if cfg!(target_pointer_width = "64") { + Arch::Wasm64 + } else if cfg!(target_pointer_width = "32") { + Arch::Wasm32 + } else { + return Err(Error::new( + ErrorKind::Other, // FIXME: WhoAmI 2.0, Unsupported + "Unexpected pointer width for target platform", + )); + }) + } +} diff --git a/src/os/unix.rs b/src/os/unix.rs index 2f88676..3fdabf7 100644 --- a/src/os/unix.rs +++ b/src/os/unix.rs @@ -1,5 +1,15 @@ +#[cfg(target_os = "illumos")] +use std::convert::TryInto; +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", +))] +use std::env; use std::{ - env, ffi::{c_void, CStr, OsString}, fs, io::{Error, ErrorKind}, @@ -8,6 +18,7 @@ use std::{ raw::{c_char, c_int}, unix::ffi::OsStringExt, }, + slice, }; #[cfg(target_os = "macos")] use std::{ @@ -20,18 +31,10 @@ use std::{ use crate::{ os::{Os, Target}, - Arch, DesktopEnv, Language, Platform, Result, + Arch, DesktopEnv, Platform, Result, }; -#[cfg(not(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "bitrig", - target_os = "openbsd", - target_os = "netbsd", - target_os = "illumos", -)))] +#[cfg(target_os = "linux")] #[repr(C)] struct PassWd { pw_name: *const c_void, @@ -45,9 +48,8 @@ struct PassWd { #[cfg(any( target_os = "macos", - target_os = "freebsd", target_os = "dragonfly", - target_os = "bitrig", + target_os = "freebsd", target_os = "openbsd", target_os = "netbsd" ))] @@ -90,7 +92,14 @@ extern "system" { ) -> *mut PassWd; } -#[cfg(not(target_os = "illumos"))] +#[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", +))] extern "system" { fn getpwuid_r( uid: u32, @@ -166,7 +175,7 @@ fn os_from_cstring_gecos(string: *const c_void) -> Result { return Err(super::err_empty_record()); } - std::slice::from_raw_parts(string.cast(), length) + slice::from_raw_parts(string.cast(), length) }; // Turn byte slice into Rust String. @@ -186,7 +195,7 @@ fn os_from_cstring(string: *const c_void) -> Result { return Err(super::err_empty_record()); } - std::slice::from_raw_parts(string.cast(), length) + slice::from_raw_parts(string.cast(), length) }; // Turn byte slice into Rust String. @@ -233,7 +242,14 @@ fn getpwuid(name: Name) -> Result { // Get PassWd `struct`. let passwd = unsafe { - #[cfg(not(target_os = "illumos"))] + #[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + ))] { let ret = getpwuid_r( geteuid(), @@ -256,8 +272,6 @@ fn getpwuid(name: Name) -> Result { #[cfg(target_os = "illumos")] { - use std::convert::TryInto; - let ret = getpwuid_r( geteuid(), passwd.as_mut_ptr(), @@ -341,54 +355,8 @@ fn distro_xml(data: String) -> Result { }) } -struct LangIter { - array: String, - index: Option, -} - -impl Iterator for LangIter { - type Item = String; - - fn next(&mut self) -> Option { - if self.index? && self.array.contains('-') { - self.index = Some(false); - let mut temp = self.array.split('-').next()?.to_string(); - mem::swap(&mut temp, &mut self.array); - Some(temp) - } else { - self.index = None; - let mut temp = String::new(); - mem::swap(&mut temp, &mut self.array); - Some(temp) - } - } -} - -#[inline(always)] -pub(crate) fn lang() -> impl Iterator { - const DEFAULT_LANG: &str = "en_US"; - - let array = env::var("LANG") - .unwrap_or_default() - .split('.') - .next() - .unwrap_or(DEFAULT_LANG) - .to_string(); - let array = if array == "C" { - DEFAULT_LANG.to_string() - } else { - array - }; - - LangIter { - array: array.replace('_', "-"), - index: Some(true), - } -} - #[cfg(any( target_os = "macos", - target_os = "ios", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", @@ -443,7 +411,14 @@ impl Default for UtsName { #[inline(always)] unsafe fn uname(buf: *mut UtsName) -> c_int { extern "C" { - #[cfg(not(target_os = "freebsd"))] + #[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] fn uname(buf: *mut UtsName) -> c_int; #[cfg(target_os = "freebsd")] @@ -461,8 +436,8 @@ unsafe fn uname(buf: *mut UtsName) -> c_int { } impl Target for Os { - fn langs(self) -> Vec { - todo!() + fn langs(self) -> Result { + super::unix_lang() } fn realname(self) -> Result { @@ -503,7 +478,13 @@ impl Target for Os { Ok(OsString::from_vec(nodename)) } - #[cfg(not(any(target_os = "macos", target_os = "illumos")))] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + ))] { let machine_info = fs::read("/etc/machine-info")?; @@ -555,7 +536,14 @@ impl Target for Os { } } - #[cfg(not(target_os = "macos"))] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] { let program = fs::read("/etc/os-release")?; let distro = String::from_utf8_lossy(&program); @@ -592,10 +580,25 @@ impl Target for Os { fn desktop_env(self) -> DesktopEnv { #[cfg(target_os = "macos")] let env = "Aqua"; + // FIXME: WhoAmI 2.0: use `let else` - #[cfg(not(target_os = "macos"))] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] let env = env::var_os("DESKTOP_SESSION"); - #[cfg(not(target_os = "macos"))] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] let env = if let Some(ref env) = env { env.to_string_lossy() } else { @@ -624,15 +627,7 @@ impl Target for Os { #[inline(always)] fn platform(self) -> Platform { - #[cfg(not(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "bitrig", - target_os = "openbsd", - target_os = "netbsd", - target_os = "illumos" - )))] + #[cfg(target_os = "linux")] { Platform::Linux } @@ -643,11 +638,10 @@ impl Target for Os { } #[cfg(any( - target_os = "freebsd", target_os = "dragonfly", - target_os = "bitrig", + target_os = "freebsd", + target_os = "netbsd", target_os = "openbsd", - target_os = "netbsd" ))] { Platform::Bsd diff --git a/src/os/wasi.rs b/src/os/wasi.rs index 9af6cfe..a26f108 100644 --- a/src/os/wasi.rs +++ b/src/os/wasi.rs @@ -5,23 +5,12 @@ use std::{env, ffi::OsString}; use crate::{ os::{Os, Target}, - Arch, DesktopEnv, Language, Platform, Result, + Arch, DesktopEnv, Platform, Result, }; -#[inline(always)] -pub(crate) fn lang() -> impl Iterator { - let langs: Vec = wasite::langs() - .unwrap_or_else(|_e| "en_US".to_string()) - .split(';') - .map(|lang| lang.to_string()) - .collect(); - - langs.into_iter() -} - impl Target for Os { - fn langs(self) -> Vec { - todo!() + fn langs(self) -> Result { + super::unix_lang() } #[inline(always)] diff --git a/src/os/web.rs b/src/os/web.rs index aaeeb85..6e0c1c4 100644 --- a/src/os/web.rs +++ b/src/os/web.rs @@ -6,12 +6,11 @@ use std::{ io::{Error, ErrorKind}, }; -use wasm_bindgen::JsValue; use web_sys::window; use crate::{ os::{Os, Target}, - Arch, DesktopEnv, Language, Platform, Result, + Arch, DesktopEnv, Platform, Result, }; // Get the user agent @@ -24,44 +23,21 @@ fn document_domain() -> Option { window()?.document()?.location()?.hostname().ok() } -struct LangIter { - array: Vec, - index: usize, -} - -impl Iterator for LangIter { - type Item = String; - - fn next(&mut self) -> Option { - if let Some(value) = self.array.get(self.index) { - self.index += 1; - if let Some(lang) = value.as_string() { - Some(lang) - } else { - self.next() - } +impl Target for Os { + fn langs(self) -> Result { + if let Some(window) = window() { + Ok(window + .navigator() + .languages() + .to_vec() + .into_iter() + .filter_map(|l| l.as_string()) + .collect::>() + .join(";")) } else { - None + Err(Error::new(ErrorKind::NotFound, "Window missing")) } } -} - -#[inline(always)] -pub(crate) fn lang() -> impl Iterator { - let array = if let Some(window) = window() { - window.navigator().languages().to_vec() - } else { - Vec::new() - }; - let index = 0; - - LangIter { array, index } -} - -impl Target for Os { - fn langs(self) -> Vec { - todo!() - } fn realname(self) -> Result { Ok("Anonymous".to_string().into()) diff --git a/src/os/windows.rs b/src/os/windows.rs index 54332e9..562a376 100644 --- a/src/os/windows.rs +++ b/src/os/windows.rs @@ -13,7 +13,7 @@ use std::{ use crate::{ conversions, os::{Os, Target}, - Arch, DesktopEnv, Language, Platform, Result, + Arch, DesktopEnv, Platform, Result, }; #[repr(C)] @@ -50,6 +50,7 @@ struct SystemInfo { #[allow(unused)] #[repr(C)] +#[derive(Copy, Clone)] enum ExtendedNameFormat { Unknown, // Nothing FullyQualifiedDN, // Nothing @@ -81,6 +82,7 @@ enum ComputerNameFormat { const ERR_MORE_DATA: i32 = 0xEA; const ERR_INSUFFICIENT_BUFFER: i32 = 0x7A; +const ERR_NONE_MAPPED: i32 = 0x534; #[link(name = "secur32")] extern "system" { @@ -108,149 +110,124 @@ extern "system" { fn GetNativeSystemInfo(system_info: *mut SystemInfo); } -struct LangIter { - array: Vec, - index: usize, -} - -impl Iterator for LangIter { - type Item = String; +fn username() -> Result { + // Step 1. Retreive the entire length of the username + let mut size = 0; + let fail = unsafe { GetUserNameW(ptr::null_mut(), &mut size) == 0 }; + assert!(fail); - fn next(&mut self) -> Option { - if let Some(value) = self.array.get(self.index) { - self.index += 1; - - Some(value.to_string()) - } else { - None - } + if Error::last_os_error().raw_os_error() != Some(ERR_INSUFFICIENT_BUFFER) { + return Err(Error::last_os_error()); } -} - -#[inline(always)] -pub(crate) fn lang() -> impl Iterator { - let mut num_languages = 0; - let mut buffer_size = 0; - let mut buffer; + // Step 2. Allocate memory to put the Windows (UTF-16) string. + let mut name: Vec = + Vec::with_capacity(size.try_into().unwrap_or(std::usize::MAX)); + size = name.capacity().try_into().unwrap_or(std::u32::MAX); + let orig_size = size; + let fail = + unsafe { GetUserNameW(name.as_mut_ptr().cast(), &mut size) == 0 }; + if fail { + return Err(Error::last_os_error()); + } + debug_assert_eq!(orig_size, size); unsafe { - assert_ne!( - GetUserPreferredUILanguages( - 0x08, /* MUI_LANGUAGE_NAME */ - &mut num_languages, - ptr::null_mut(), // List of languages. - &mut buffer_size, - ), - 0 - ); - - buffer = Vec::with_capacity(buffer_size as usize); - - assert_ne!( - GetUserPreferredUILanguages( - 0x08, /* MUI_LANGUAGE_NAME */ - &mut num_languages, - buffer.as_mut_ptr(), // List of languages. - &mut buffer_size, - ), - 0 - ); - - buffer.set_len(buffer_size as usize); + name.set_len(size.try_into().unwrap_or(std::usize::MAX)); } + let terminator = name.pop(); // Remove Trailing Null + debug_assert_eq!(terminator, Some(0u16)); + + // Step 3. Convert to Rust String + Ok(OsString::from_wide(&name)) +} - // We know it ends in two null characters. - buffer.pop(); - buffer.pop(); +fn extended_name(format: ExtendedNameFormat) -> Result { + // Step 1. Retrieve the entire length of the username + let mut buf_size = 0; + let fail = + unsafe { GetUserNameExW(format, ptr::null_mut(), &mut buf_size) == 0 }; - // - let array = String::from_utf16_lossy(&buffer) - .split('\0') - .map(|x| x.to_string()) - .collect(); - let index = 0; + assert!(fail); - LangIter { array, index } -} + let last_err = Error::last_os_error().raw_os_error(); -impl Target for Os { - fn langs(self) -> Vec { - todo!() + if last_err == Some(ERR_NONE_MAPPED) { + return Err(super::err_missing_record()); } - fn realname(self) -> Result { - // Step 1. Retrieve the entire length of the username - let mut buf_size = 0; - let fail = unsafe { - GetUserNameExW( - ExtendedNameFormat::Display, - ptr::null_mut(), - &mut buf_size, - ) == 0 - }; + if last_err != Some(ERR_MORE_DATA) { + return Err(Error::last_os_error()); + } - assert!(fail); + // Step 2. Allocate memory to put the Windows (UTF-16) string. + let mut name: Vec = + Vec::with_capacity(buf_size.try_into().unwrap_or(std::usize::MAX)); + let mut name_len = name.capacity().try_into().unwrap_or(std::u32::MAX); + let fail = unsafe { + GetUserNameExW(format, name.as_mut_ptr().cast(), &mut name_len) == 0 + }; + if fail { + return Err(Error::last_os_error()); + } - if Error::last_os_error().raw_os_error() != Some(ERR_MORE_DATA) { - return Err(Error::last_os_error()); - } + assert_eq!(buf_size, name_len + 1); - // Step 2. Allocate memory to put the Windows (UTF-16) string. - let mut name: Vec = - Vec::with_capacity(buf_size.try_into().unwrap_or(std::usize::MAX)); - let mut name_len = name.capacity().try_into().unwrap_or(std::u32::MAX); - let fail = unsafe { - GetUserNameExW( - ExtendedNameFormat::Display, - name.as_mut_ptr().cast(), - &mut name_len, - ) == 0 - }; - if fail { - return Err(Error::last_os_error()); - } + unsafe { name.set_len(name_len.try_into().unwrap_or(std::usize::MAX)) }; + + // Step 3. Convert to Rust String + Ok(OsString::from_wide(&name)) +} - assert_eq!(buf_size, name_len + 1); +impl Target for Os { + #[inline(always)] + fn langs(self) -> Result { + let mut num_languages = 0; + let mut buffer_size = 0; + let mut buffer; unsafe { - name.set_len(name_len.try_into().unwrap_or(std::usize::MAX)); + assert_ne!( + GetUserPreferredUILanguages( + 0x08, /* MUI_LANGUAGE_NAME */ + &mut num_languages, + ptr::null_mut(), // List of languages. + &mut buffer_size, + ), + 0 + ); + + buffer = Vec::with_capacity(buffer_size as usize); + + assert_ne!( + GetUserPreferredUILanguages( + 0x08, /* MUI_LANGUAGE_NAME */ + &mut num_languages, + buffer.as_mut_ptr(), // List of languages. + &mut buffer_size, + ), + 0 + ); + + buffer.set_len(buffer_size as usize); } - // Step 3. Convert to Rust String - Ok(OsString::from_wide(&name)) - } - - fn username(self) -> Result { - // Step 1. Retreive the entire length of the username - let mut size = 0; - let fail = unsafe { GetUserNameW(ptr::null_mut(), &mut size) == 0 }; - assert!(fail); + // We know it ends in two null characters. + buffer.pop(); + buffer.pop(); - if Error::last_os_error().raw_os_error() - != Some(ERR_INSUFFICIENT_BUFFER) - { - return Err(Error::last_os_error()); - } + // Combine into a single string + Ok(String::from_utf16_lossy(&buffer) + .split('\0') + .collect::>() + .join(";")) + } - // Step 2. Allocate memory to put the Windows (UTF-16) string. - let mut name: Vec = - Vec::with_capacity(size.try_into().unwrap_or(std::usize::MAX)); - size = name.capacity().try_into().unwrap_or(std::u32::MAX); - let orig_size = size; - let fail = - unsafe { GetUserNameW(name.as_mut_ptr().cast(), &mut size) == 0 }; - if fail { - return Err(Error::last_os_error()); - } - debug_assert_eq!(orig_size, size); - unsafe { - name.set_len(size.try_into().unwrap_or(std::usize::MAX)); - } - let terminator = name.pop(); // Remove Trailing Null - debug_assert_eq!(terminator, Some(0u16)); + fn realname(self) -> Result { + extended_name(ExtendedNameFormat::Display) + } - // Step 3. Convert to Rust String - Ok(OsString::from_wide(&name)) + fn username(self) -> Result { + username() } fn devicename(self) -> Result { @@ -267,9 +244,7 @@ impl Target for Os { assert!(fail); - if Error::last_os_error().raw_os_error() - != Some(ERR_INSUFFICIENT_BUFFER) - { + if Error::last_os_error().raw_os_error() != Some(ERR_MORE_DATA) { return Err(Error::last_os_error()); } @@ -310,9 +285,7 @@ impl Target for Os { assert!(fail); - if Error::last_os_error().raw_os_error() - != Some(ERR_INSUFFICIENT_BUFFER) - { + if Error::last_os_error().raw_os_error() != Some(ERR_MORE_DATA) { return Err(Error::last_os_error()); } @@ -472,4 +445,13 @@ impl Target for Os { })?, }) } + + #[inline(always)] + fn account(self) -> Result { + match extended_name(ExtendedNameFormat::UserPrincipal) { + Ok(name) => Ok(name), + Err(e) if e.kind() == ErrorKind::NotFound => username(), + Err(e) => Err(e), + } + } } diff --git a/src/platform.rs b/src/platform.rs new file mode 100644 index 0000000..04d4073 --- /dev/null +++ b/src/platform.rs @@ -0,0 +1,50 @@ +use std::fmt::{self, Display, Formatter}; + +/// The underlying platform for a system +#[allow(missing_docs)] +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum Platform { + Linux, + Bsd, + Windows, + // FIXME: Non-standard casing; Rename to 'Mac' rather than 'MacOs' in + // whoami 2.0.0 + MacOS, + Illumos, + Ios, + Android, + // FIXME: Separate for different Nintendo consoles in whoami 2.0.0, + // currently only used for 3DS + Nintendo, + // FIXME: Currently unused, remove in whoami 2.0.0 + Xbox, + PlayStation, + Fuchsia, + Redox, + Unknown(String), +} + +impl Display for Platform { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Self::Unknown(_) = self { + f.write_str("Unknown: ")?; + } + + f.write_str(match self { + Self::Linux => "Linux", + Self::Bsd => "BSD", + Self::Windows => "Windows", + Self::MacOS => "Mac OS", + Self::Illumos => "illumos", + Self::Ios => "iOS", + Self::Android => "Android", + Self::Nintendo => "Nintendo", + Self::Xbox => "XBox", + Self::PlayStation => "PlayStation", + Self::Fuchsia => "Fuchsia", + Self::Redox => "Redox", + Self::Unknown(a) => a, + }) + } +} diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 0000000..5e48775 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,4 @@ +use std::io::Error; + +/// This crate's convenience type alias for [`Result`](std::result::Result)s +pub type Result = std::result::Result;