diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 9741f113e7..0000000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[alias] -xtask = "run --quiet --package xtask --" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 5f475af574..0000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Docs - -on: - push: - branches: - - master - -env: - CARGO_TERM_COLOR: always - -jobs: - default: - - runs-on: ubuntu-latest - container: - image: archlinux - steps: - - name: Get required packages - run: pacman --noconfirm --noprogressbar -Syu base-devel clang git libpipewire libpulse lm_sensors notmuch openssl rsync - - uses: dtolnay/rust-toolchain@stable - - name: Version information - run: rustc --version; cargo --version - - uses: actions/checkout@v5 - - name: Generate docs - run: cargo doc --all-features --no-deps - - name: Create index.html - run: echo "" > target/doc/index.html - - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@v4 - with: - folder: target/doc # The folder the action should deploy. diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml deleted file mode 100644 index fdf82e1d82..0000000000 --- a/.github/workflows/pre-commit.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: pre-commit - -on: [push, pull_request] - -jobs: - pre-commit: - runs-on: ubuntu-latest - container: - image: archlinux - strategy: - fail-fast: false - matrix: - hook: [ check-toml, - check-yaml, - check-merge-conflict, - check-case-conflict, - detect-private-key, - mixed-line-ending, - trailing-whitespace, - cspell, - typos, - cargo-fmt, - cargo-clippy, - cargo-test, - verify_icon_files - ] - steps: - - name: Get required packages and config git - run: | - pacman --noconfirm --noprogressbar -Syu base-devel clang git libpipewire libpulse lm_sensors notmuch openssl rsync - git config --global --add safe.directory "$GITHUB_WORKSPACE" - - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - with: - components: clippy, rustfmt - - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - uses: pre-commit/action@v3.0.1 - with: - extra_args: ${{ matrix.hook }} --all-files - - diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 099c6d4ee3..0000000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# will have compiled files and executables -/target/ - -# These are backup files generated by rustfmt -**/*.rs.bk - -# Ignore generated manpages -/man diff --git a/.lock b/.lock new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index bf91e28996..0000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,82 +0,0 @@ ---- -# For use with pre-commit. -# See usage instructions at https://pre-commit.com - -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 - hooks: - - id: check-toml - stages: [pre-commit] - - id: check-yaml - stages: [pre-commit] - - id: check-merge-conflict - stages: [pre-commit] - - id: check-case-conflict - stages: [pre-commit] - - id: detect-private-key - stages: [pre-commit] - - id: mixed-line-ending - stages: [pre-commit] - args: ["--fix=lf"] - - id: trailing-whitespace - stages: [pre-commit] - args: [--markdown-linebreak-ext=md] - - repo: https://github.com/crate-ci/committed - rev: v1.0.20 - hooks: - - id: committed - stages: [commit-msg] - # cspell is dictionary based - # Dictionary: A confidence rating is given for how close a word is to one in a dictionary - - repo: https://github.com/streetsidesoftware/cspell-cli - rev: v8.3.0 - hooks: - - id: cspell - # typos is corrections based - # Corrections: Known misspellings that map to their corresponding dictionary word - - repo: https://github.com/crate-ci/typos - rev: v1.18.2 - hooks: - - id: typos - pass_filenames: false - args: [] # this prevents typos from being autocorrected (so you have a chance to review them) - - repo: local - hooks: - - id: cargo-fmt - name: cargo fmt - description: Run cargo fmt - entry: cargo fmt - language: system - types: [rust] - args: ["--"] - require_serial: true - - id: cargo-clippy - name: cargo clippy - description: Run cargo clippy - entry: cargo clippy - language: system - types: [rust] - pass_filenames: false - args: ["--all-features", "--", "-D", "warnings", "-A", "unknown-lints"] - - id: cargo-test - name: cargo test - description: Run cargo test - entry: cargo test - language: system - types: [rust] - pass_filenames: false - args: ["--all-features", "--"] - - repo: local - hooks: - - id: verify_icon_files - name: verify_icon_files - description: Run verify_icon_files.sh - entry: ./verify_icon_files.sh - language: system - files: | - (?x)^( - src/icons\.rs| - files/icons/.*\.toml - )$ - pass_filenames: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 208547ab4f..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,86 +0,0 @@ -# Contributing to i3status-rust - -i3status-rust development is largely community driven. We rely on volunteers to -propose and implement most new features or new blocks, and often encourage users -to get their feet wet with Rust to do so. - -If you would like to propose a new feature or block, it's not a bad idea to -search for relevant issues or open a new one for discussion first. It can often -be helpful to look for up support (or potential criticism) before you embark on -potentially time-consuming coding. - -Bug fixes (or typos!), on the other hand, do not require a corresponding issue --- feel free to open a PR directly if you spot one. - -## Problematic Contributions - -We will *not* generally accept the following: - -- **PRs that just update dependencies to their newest version.** See below. - -## Dependencies - -i3status-rust depends on many crates and a few system dependencies. These -dependencies have allowed us to add many features, but impose a burden on anyone -using this project, especially if built from source (which is currently the -norm). Keep this in mind when proposing new dependencies or updates to existing -ones. - -Good reasons for updating an existing dependency include, in roughly this order: - -1. A newer version fixes a bug we have encountered. - -2. We need/want functionality introduced in a newer version. For example, - "bumping `i3ipc` to 0.8.4 might make our focused window block work with - Sway". - -3. A newer version has substantial performance improvements relevant to this - project; or - -4. A newer version removes a transient dependency. For example, "updating - `thread_local` to 3.6 would remove the `unreachable` crate". - -We generally like to see the following when adding a new crate or system -dependency: - -1. New dependencies are relatively "proven" crates; and - -2. Heavier dependencies are introduced to provide core functionality that will - benefit a large number of users. - -For cases where this is not true, we will consider using Cargo's feature flags -to make the dependency optional, or offer suggestions on how to avoid using the -dependency. - -To make downstream packaging easier, maintainers bump dependencies as part of -the release process. - -## Code and Git Commits - -If you are new to Rust, Cargo and the compiler will be your best friends... -eventually. In the meantime, you can ask for advice on idiomatic code and we -will do our best to help you out, time permitting. - -We will generally squash PRs that have commits like "fix typo", "updates", etc. -To avoid this, make your commit messages clear and the commits themselves as -targeted as possible. Use `git rebase` if you need to do this after the fact. - -Please format your code with `rustfmt` before submitting a PR. Note that `cargo fmt` -unfortunately does not format files which are included via macros (in our case the `define_blocks!` macro in `blocks.rs`), -so if you don't use `rust_analyzer`, you have to run `rustfmt --edition 2021 `. - -We also include a configuration file for [pre-commit](https://pre-commit.com/), -which you can use locally to run several checks including looking for spelling errors -and making sure that `cargo fmt` has been run. -These same checks will be run as a github action, but it may save time to run them locally first. - -## Maintainership - -If you are interested in taking on a more active community role -- e.g. triaging -issues or reviewing pull requests -- please feel free to reach out to us. - -## License - -This project is licensed under the terms of the GPLv3, and any contribution you -make will be understood to be the same. See the [LICENSE](LICENSE) file -for details on these terms. diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index a370f25e66..0000000000 --- a/Cargo.lock +++ /dev/null @@ -1,3845 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "annotate-snippets" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" -dependencies = [ - "unicode-width", - "yansi-term", -] - -[[package]] -name = "anstream" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "anstyle-parse" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" -dependencies = [ - "anstyle", - "windows-sys 0.59.0", -] - -[[package]] -name = "anyhow" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" - -[[package]] -name = "async-broadcast" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-io" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" -dependencies = [ - "async-lock", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-pidfd" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958321a23896f573a3f1809437af5816f3bc90dd2d5ffe8b1cd5645f42230c17" -dependencies = [ - "async-io", - "libc", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "backon" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4fa97bb310c33c811334143cf64c5bb2b7b3c06e453db6b095d7061eff8f113" -dependencies = [ - "fastrand", - "tokio", -] - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "annotate-snippets", - "bitflags 2.9.1", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.87", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "blocking" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" - -[[package]] -name = "calendrical_calculations" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec493b209a1b81fa32312d7ceca1b547d341c7b5f16a3edbf32b1d8b455bbdf" -dependencies = [ - "core_maths", - "displaydoc", -] - -[[package]] -name = "calibright" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0227728b8cdc2a22c2748559ef377dcd3f88044e21138f8f47c54ba0ceacb8" -dependencies = [ - "dirs", - "futures-util", - "log", - "notify", - "regex", - "serde", - "thiserror 2.0.3", - "tokio", - "toml", - "zbus", -] - -[[package]] -name = "cc" -version = "1.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" -dependencies = [ - "shlex", -] - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "charset" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f927b07c74ba84c7e5fe4db2baeb3e996ab2688992e39ac68ce3220a677c7e" -dependencies = [ - "base64", - "encoding_rs", -] - -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "pure-rust-locales", - "serde", - "wasm-bindgen", - "windows-targets 0.52.6", -] - -[[package]] -name = "chrono-tz" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" -dependencies = [ - "chrono", - "chrono-tz-build", - "phf", - "serde", -] - -[[package]] -name = "chrono-tz-build" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" -dependencies = [ - "parse-zoneinfo", - "phf_codegen", -] - -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clap" -version = "4.5.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim 0.11.1", -] - -[[package]] -name = "clap_derive" -version = "4.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "clap_lex" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" - -[[package]] -name = "clap_mangen" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbae9cbfdc5d4fa8711c09bd7b83f644cb48281ac35bf97af3e47b0675864bdf" -dependencies = [ - "clap", - "roff", -] - -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cookie-factory" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" -dependencies = [ - "futures", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core_maths" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" -dependencies = [ - "libm", -] - -[[package]] -name = "cpufeatures" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" -dependencies = [ - "libc", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "darling" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.9.3", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "data-encoding" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.59.0", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "endi" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" - -[[package]] -name = "enumflags2" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "env_filter" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "humantime", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "event-listener" -version = "5.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" - -[[package]] -name = "filetime" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.59.0", -] - -[[package]] -name = "fixed_decimal" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0febbeb1118a9ecdee6e4520ead6b54882e843dd0592ad233247dbee84c53db8" -dependencies = [ - "displaydoc", - "smallvec", - "writeable", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "from_variants" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221a1eb1a3c98980bc1b740f462b3dcf73f4e371cda294986bac72497995a4e3" -dependencies = [ - "from_variants_impl", -] - -[[package]] -name = "from_variants_impl" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e08079fa3c89edec9160ceaa9e7172785468c26c053d12924cce0d5a55c241a" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "gethostname" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "gethostname" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" -dependencies = [ - "libc", - "windows-targets 0.48.5", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "h2" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", -] - -[[package]] -name = "i3status-rs" -version = "0.34.0" -dependencies = [ - "async-trait", - "backon", - "base64", - "bytes", - "calibright", - "chrono", - "chrono-tz", - "clap", - "dirs", - "env_logger", - "futures", - "glob", - "iana-time-zone", - "icalendar", - "icu_calendar", - "icu_datetime", - "icu_locid", - "indexmap", - "inotify", - "itertools 0.13.0", - "libc", - "libpulse-binding", - "log", - "maildir", - "neli", - "neli-wifi", - "nix 0.29.0", - "nom", - "notmuch", - "num-traits", - "oauth2", - "pipewire", - "quick-xml", - "regex", - "reqwest", - "sensors", - "serde", - "serde_json", - "shellexpand", - "signal-hook", - "signal-hook-tokio", - "smart-default", - "sunrise", - "swayipc-async", - "thiserror 2.0.3", - "tokio", - "tokio-util", - "toml", - "unicode-segmentation", - "wayrs-client", - "wayrs-protocols", - "x11rb-async", - "zbus", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icalendar" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2740e54a5de285fcc5eb7f97c75b4fb8842d807f62ecbbe716006676d6181" -dependencies = [ - "chrono", - "chrono-tz", - "iso8601", - "nom", - "uuid", -] - -[[package]] -name = "icu_calendar" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7265b2137f9a36f7634a308d91f984574bbdba8cfd95ceffe1c345552275a8ff" -dependencies = [ - "calendrical_calculations", - "displaydoc", - "icu_calendar_data", - "icu_locid", - "icu_locid_transform", - "icu_provider", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_calendar_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e009b7f0151ee6fb28c40b1283594397e0b7183820793e9ace3dcd13db126d0" - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_datetime" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d115efb85e08df3fd77e77f52e7e087545a783fffba8be80bfa2102f306b1780" -dependencies = [ - "displaydoc", - "either", - "fixed_decimal", - "icu_calendar", - "icu_datetime_data", - "icu_decimal", - "icu_locid", - "icu_locid_transform", - "icu_plurals", - "icu_provider", - "icu_timezone", - "smallvec", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_datetime_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ba7e7f7a01269b9afb0a39eff4f8676f693b55f509b3120e43a0350a9f88bea" - -[[package]] -name = "icu_decimal" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8fd98f86ec0448d85e1edf8884e4e318bb2e121bd733ec929a05c0a5e8b0eb" -dependencies = [ - "displaydoc", - "fixed_decimal", - "icu_decimal_data", - "icu_locid_transform", - "icu_provider", - "writeable", -] - -[[package]] -name = "icu_decimal_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d424c994071c6f5644f999925fc868c85fec82295326e75ad5017bc94b41523" - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_plurals" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a70e7c025dbd5c501b0a5c188cd11666a424f0dadcd4f0a95b7dafde3b114" -dependencies = [ - "displaydoc", - "fixed_decimal", - "icu_locid_transform", - "icu_plurals_data", - "icu_provider", - "zerovec", -] - -[[package]] -name = "icu_plurals_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3e8f775b215d45838814a090a2227247a7431d74e9156407d9c37f6ef0f208" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "icu_timezone" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa91ba6a585939a020c787235daa8aee856d9bceebd6355e283c0c310bc6de96" -dependencies = [ - "displaydoc", - "icu_calendar", - "icu_provider", - "icu_timezone_data", - "tinystr", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_timezone_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c588878c508a3e2ace333b3c50296053e6483c6a7541251b546cc59dcd6ced8e" - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" -dependencies = [ - "equivalent", - "hashbrown", - "serde", -] - -[[package]] -name = "inotify" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" -dependencies = [ - "bitflags 2.9.1", - "futures-core", - "inotify-sys", - "libc", - "tokio", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - -[[package]] -name = "ipnet" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" - -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "iso8601" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153" -dependencies = [ - "nom", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "kqueue" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.172" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" - -[[package]] -name = "libloading" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" -dependencies = [ - "cfg-if", - "windows-targets 0.48.5", -] - -[[package]] -name = "libm" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" - -[[package]] -name = "libpulse-binding" -version = "2.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3557a2dfc380c8f061189a01c6ae7348354e0c9886038dc6c171219c08eaff" -dependencies = [ - "bitflags 1.3.2", - "libc", - "libpulse-sys", - "num-derive", - "num-traits", - "winapi", -] - -[[package]] -name = "libpulse-sys" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc19e110fbf42c17260d30f6d3dc545f58491c7830d38ecb9aaca96e26067a9b" -dependencies = [ - "libc", - "num-derive", - "num-traits", - "pkg-config", - "winapi", -] - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.9.1", - "libc", - "redox_syscall", -] - -[[package]] -name = "libsensors-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1730cc7164b96de1d460c1f87c993430a3dda88ee336f0d2ea9a52097243132" - -[[package]] -name = "libspa" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65f3a4b81b2a2d8c7f300643676202debd1b7c929dbf5c9bb89402ea11d19810" -dependencies = [ - "bitflags 2.9.1", - "cc", - "convert_case", - "cookie-factory", - "libc", - "libspa-sys", - "nix 0.27.1", - "nom", - "system-deps", -] - -[[package]] -name = "libspa-sys" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f" -dependencies = [ - "bindgen", - "cc", - "system-deps", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "litemap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "maildir" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879a6ae6743ab8219fdee64a569094485bfe18434e82b78b27fac5cce09e1437" -dependencies = [ - "gethostname 0.2.3", - "mailparse", -] - -[[package]] -name = "mailparse" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d096594926cab442e054e047eb8c1402f7d5b2272573b97ba68aa40629f9757" -dependencies = [ - "charset", - "data-encoding", - "quoted_printable", -] - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "log", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "neli" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1100229e06604150b3becd61a4965d5c70f3be1759544ea7274166f4be41ef43" -dependencies = [ - "byteorder", - "libc", - "log", - "neli-proc-macros", - "tokio", -] - -[[package]] -name = "neli-proc-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168194d373b1e134786274020dae7fc5513d565ea2ebb9bc9ff17ffb69106d4" -dependencies = [ - "either", - "proc-macro2", - "quote", - "serde", - "syn 1.0.109", -] - -[[package]] -name = "neli-wifi" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea02232cfda62e9523d24babd6c5abddbd65d31aebcf090d7f5b7bfd19bbc70" -dependencies = [ - "neli", - "neli-proc-macros", -] - -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "libc", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "notify" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" -dependencies = [ - "bitflags 2.9.1", - "filetime", - "inotify", - "kqueue", - "libc", - "log", - "mio", - "notify-types", - "walkdir", - "windows-sys 0.59.0", -] - -[[package]] -name = "notify-types" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" - -[[package]] -name = "notmuch" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25d11a2449f4f91cb71b138b241db30765a3b2f595eba0dd6a282b0e961dd44" -dependencies = [ - "from_variants", - "libc", -] - -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "oauth2" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" -dependencies = [ - "base64", - "chrono", - "getrandom", - "http", - "rand", - "reqwest", - "serde", - "serde_json", - "serde_path_to_error", - "sha2", - "thiserror 1.0.69", - "url", -] - -[[package]] -name = "object" -version = "0.36.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "openssl" -version = "0.10.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "pandoc" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463d53d1a77a4291203dbf9d461365609e6857c95bd7d807098bffdc0a02a65c" -dependencies = [ - "itertools 0.12.1", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parse-zoneinfo" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" -dependencies = [ - "regex", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pipewire" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08e645ba5c45109106d56610b3ee60eb13a6f2beb8b74f8dc8186cf261788dda" -dependencies = [ - "anyhow", - "bitflags 2.9.1", - "libc", - "libspa", - "libspa-sys", - "nix 0.27.1", - "once_cell", - "pipewire-sys", - "thiserror 1.0.69", -] - -[[package]] -name = "pipewire-sys" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112" -dependencies = [ - "bindgen", - "libspa-sys", - "system-deps", -] - -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - -[[package]] -name = "polling" -version = "3.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.4.0", - "pin-project-lite", - "rustix", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro-crate" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "pure-rust-locales" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a" - -[[package]] -name = "quick-xml" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbfb3ddf5364c9cfcd65549a1e7b801d0e8d1b14c1a1590a6408aa93cfbfa84" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "quoted_printable" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "redox_syscall" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "redox_users" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" -dependencies = [ - "getrandom", - "libredox", - "thiserror 2.0.3", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "reqwest" -version = "0.12.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "ring" -version = "0.17.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "roff" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustix" -version = "0.38.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.23.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.9.1", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "sensors" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3af4792f6a434642b5805ab1f086a621a369ad06a0d466ab89aeb556ddd6d0" -dependencies = [ - "libc", - "libsensors-sys", -] - -[[package]] -name = "serde" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "serde_json" -version = "1.0.133" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" -dependencies = [ - "itoa", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shellexpand" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" -dependencies = [ - "dirs", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "signal-hook-tokio" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e" -dependencies = [ - "futures-core", - "libc", - "signal-hook", - "tokio", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "smart-default" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "sunrise" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0733c9f1eaa06ed6d103d88e21f784449d08a6733c2ca2b39381cbcbcfe89272" -dependencies = [ - "chrono", -] - -[[package]] -name = "swayipc-async" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef7d92946d5f53db0d8922bb1dfb0da6d718e4f073936bf717906bf3a1c7835" -dependencies = [ - "async-io", - "async-pidfd", - "futures-lite", - "serde", - "serde_json", - "swayipc-types", -] - -[[package]] -name = "swayipc-types" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f6205b8f8ea7cd6244d76adce0a0f842525a13c47376feecf04280bda57231" -dependencies = [ - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[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 = "syn" -version = "2.0.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.9.1", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tempfile" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" -dependencies = [ - "cfg-if", - "fastrand", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" -dependencies = [ - "thiserror-impl 2.0.3", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.45.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" -dependencies = [ - "bitflags 2.9.1", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "uds_windows" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset", - "tempfile", - "winapi", -] - -[[package]] -name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" -dependencies = [ - "getrandom", - "wasm-bindgen", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.87", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wayrs-client" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f83967f2f1eb62adac1a6eb5914b4bf21ce7b5287cc26620197f6eaee1f704" -dependencies = [ - "tokio", - "wayrs-core", - "wayrs-scanner", -] - -[[package]] -name = "wayrs-core" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a2e30dd453ac7005dba842dba3d61cd567e86c2a818770f093d70c8c7bc5c9" -dependencies = [ - "libc", -] - -[[package]] -name = "wayrs-proto-parser" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cf4577ebb06017a5dd3bb8f24e01c88f361f585c7fd43e8bc61a75f01651922" -dependencies = [ - "quick-xml", -] - -[[package]] -name = "wayrs-protocols" -version = "0.14.6+1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b56cfd3628418a51b13c5dc19a348c3e6a72a212531c828470f898ab013ac6c4" -dependencies = [ - "wayrs-client", -] - -[[package]] -name = "wayrs-scanner" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b2f7c560a8d7cbca6af14443be51fb386acd02a558724c85dac8c7bf368d28" -dependencies = [ - "proc-macro2", - "quote", - "wayrs-proto-parser", -] - -[[package]] -name = "web-sys" -version = "0.3.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.6.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" -dependencies = [ - "memchr", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "x11rb" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" -dependencies = [ - "gethostname 0.4.3", - "rustix", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-async" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e550c5f8d07207e35f6b40288ac2debae694395926fb8554542963aeeaa95007" -dependencies = [ - "async-io", - "async-lock", - "blocking", - "event-listener", - "futures-lite", - "tracing", - "x11rb", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" - -[[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "xtask" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "clap_mangen", - "i3status-rs", - "pandoc", -] - -[[package]] -name = "yansi-term" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" -dependencies = [ - "winapi", -] - -[[package]] -name = "yoke" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", - "synstructure", -] - -[[package]] -name = "zbus" -version = "5.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1162094dc63b1629fcc44150bcceeaa80798cd28bcbe7fa987b65a034c258608" -dependencies = [ - "async-broadcast", - "async-recursion", - "async-trait", - "enumflags2", - "event-listener", - "futures-core", - "futures-util", - "hex", - "nix 0.29.0", - "ordered-stream", - "serde", - "serde_repr", - "static_assertions", - "tokio", - "tracing", - "uds_windows", - "windows-sys 0.59.0", - "winnow", - "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd2dcdce3e2727f7d74b7e33b5a89539b3cc31049562137faf7ae4eb86cd16d" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.87", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856b7a38811f71846fd47856ceee8bccaec8399ff53fb370247e66081ace647b" -dependencies = [ - "serde", - "static_assertions", - "winnow", - "zvariant", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "zerofrom" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerotrie" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb594dd55d87335c5f60177cee24f19457a5ec10a065e0a3014722ad252d0a1f" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "zvariant" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1200ee6ac32f1e5a312e455a949a4794855515d34f9909f4a3e082d14e1a56f" -dependencies = [ - "endi", - "enumflags2", - "serde", - "static_assertions", - "winnow", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687e3b97fae6c9104fbbd36c73d27d149abf04fb874e2efbd84838763daa8916" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.87", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20d1d011a38f12360e5fcccceeff5e2c42a8eb7f27f0dcba97a0862ede05c9c6" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "static_assertions", - "syn 2.0.87", - "winnow", -] diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index f78736aa71..0000000000 --- a/Cargo.toml +++ /dev/null @@ -1,108 +0,0 @@ -[package] -name = "i3status-rs" -description = "A feature-rich and resource-friendly replacement for i3status, written in Rust." -repository = "https://github.com/greshake/i3status-rust/" -readme = "README.md" -license = "GPL-3.0-only" -version = "0.34.0" -authors = [ - "Kai Greshake ", - "Contributors on GitHub (https://github.com/greshake/i3status-rust/graphs/contributors)", -] -edition = "2024" - -[workspace] -members = [".", "xtask"] -resolver = "3" - -[features] -default = ["pulseaudio"] -pulseaudio = ["libpulse-binding"] -pipewire = ["dep:pipewire"] -notmuch = ["dep:notmuch"] -maildir = ["dep:maildir", "glob"] -icu_calendar = ["dep:icu_datetime", "dep:icu_calendar", "dep:icu_locid"] -debug_borders = [] # Make widgets' borders visible - -[package.metadata.docs.rs] -features = ["maildir", "notmuch"] -rustdoc-args = ["--cfg", "docsrs"] - -[dependencies] -async-trait = "0.1" -backon = { version = "1.2", default-features = false, features = ["tokio-sleep"] } -base64 = { version = "0.22.1" } -bytes = "1.8" -calibright = { version = "0.1.13", features = ["watch"] } -chrono = { version = "0.4", default-features = false, features = ["clock", "unstable-locales"] } -chrono-tz = { version = "0.10", features = ["serde"] } -clap = { version = "4.0", default-features = false, features = ["std", "derive", "help", "usage"] } -dirs = "6.0" -env_logger = "0.11" -futures = { version = "0.3.31", default-features = false } -glob = { version = "0.3.1", optional = true } -iana-time-zone = "0.1.60" -icalendar = { version = "0.16.2", features = ["chrono-tz"] } -icu_calendar = { version = "1.3.0", optional = true } -icu_datetime = { version = "1.3.0", optional = true } -icu_locid = { version = "1.3.0", optional = true } -indexmap = { version = "2.0", features = ["serde"] } -inotify = "0.11" -itertools = "0.13" -libc = "0.2" -libpulse-binding = { version = "2.0", default-features = false, optional = true } -log = "0.4" -maildir = { version = "0.6", optional = true } -neli = { version = "0.6", features = ["async"] } -neli-wifi = { version = "0.6", features = ["async"] } -nix = { version = "0.29", features = ["fs", "process"] } -nom = "7.1.2" -notmuch = { version = "0.8", optional = true } -oauth2 = { version = "5.0.0", default-features = false, features = ["reqwest"] } -num-traits = "0.2" -pipewire = { version = "0.8", default-features = false, optional = true } -quick-xml = { version = "0.37", features = ["serialize"] } -regex = "1.5" -reqwest = { version = "0.12", features = ["json"] } -sensors = "0.2.2" -serde = { version = "1.0", features = ["derive", "rc"] } -serde_json = "1.0" -shellexpand = "3.0" -signal-hook = "0.3" -signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } -smart-default = "0.7" -sunrise = "2.1" -swayipc-async = "2.0.1" -thiserror = "2.0" -tokio-util = { version = "0.7", features = ["codec"] } -toml = { version = "0.8", features = ["preserve_order"] } -unicode-segmentation = "1.10.1" -wayrs-client = { version = "1.0", features = ["tokio"] } -wayrs-protocols = { version = "0.14", features = ["wlr-foreign-toplevel-management-unstable-v1"] } -zbus = { version = "5", default-features = false, features = ["tokio"] } -x11rb-async = { version = "0.13.1", features = ["xkb"] } - -[dependencies.tokio] -version = "1.43" -features = [ - "fs", - #"io-util", - "io-std", - "macros", - #"net", - #"parking_lot", - "process", - "rt", - "rt-multi-thread", - #"signal", - "sync", - "time", -] - -[profile.release] -lto = "thin" - -[profile.release-debug-info] -inherits = "release" -debug = true - diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 9cecc1d466..0000000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - {project} Copyright (C) {year} {fullname} - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/NEWS.md b/NEWS.md deleted file mode 100644 index d768cbdc56..0000000000 --- a/NEWS.md +++ /dev/null @@ -1,1273 +0,0 @@ -# i3status-rust 0.34.0 - -### New Blocks and Features - -* `.eng` formatter: add show parameter -* when using theme overrides you can now reference a color name defined in `~/.Xresources`, e.g., `x:background` looks for a line like `*background: #aabbcc` in `~/.Xresources` (see also [.Xresources](https://wiki.debian.org/Xresources)) -* Add support for wheel left and right clicks -* New `disk_iostats` block, provides disk I/O statistics -* `keyboard_layout`: Add xkb events support -* `keyboard_layout`: Deprecated setxkbmap and xkbswitch in favor of xkbevent, will be removed in 0.35.0 -* `xrandr`: Use icon progression for brightness_icon -* `pomodoro`: Add support for setting several kinds of formats -* `packages`: Add support for `xbps` package manager -* `hueshift`: added icon -* Add global geolocator - -### Bug Fixes and Improvements - -* Fixed `bad event` errors in `focused_window` block. -* Formatting: treat key as a shortcut for key:true -* Documented widgets in music and nvidia_gpu blocks -* Bumped version of `swayipc-async` so that `Input`s with `DragLock::EnabledSticky` can be parsed - -### Breaking Changes - -* Removed `apt`, `dnf`, and `pacman` blocks, use `packages` block instead - -# i3status-rust 0.33.2 - -### New Blocks and Features - -* Sound: add `format_alt` configuration option. -* `.eng` formatter: add `range` parameter to customize format based on the value of a placeholder. For example, `format = " $icon $swap_used.eng(range:1..) |"` will hide the block if swap is not used. -* Net: add `nameserver` placeholder for DNS information. -* Weather: add support for the US National Weather Service. -* New `scratchpad` block which shows the number of windows in i3/sway scratchpad. -* New `.tally` and `.duration` formatters (refer to [docs](https://docs.rs/i3status-rs/latest/i3status_rs/formatting/index.html) for more info). -* Add new `calendar` block which can pull from multiple `CalDav` calendars. - -### Bug Fixes and Improvements - -* Sound: correctly show headphones icon when `headphones_indicator = true` and headphones are connected. -* Time: fix timezone abbreviation (%Z). -* Battery: give priority to charging over empty. -* Time: fix divide by zero when the interval is less than a second. -* Fix Bluetooth block not working when device has no icon. -* Datetime formatter: raise error for invalid format instead of crashing. - -### Deprecation Warnings - -* Battery: `time` has been deprecated in favor of `time_remaining`. -* Tea timer: `hours`, `minutes`, and `seconds` have been deprecated in favor of `time`. -* Uptime: `text` has been deprecated in favor of `uptime`. - -# i3status-rust 0.33.1 - -### New Blocks and Features - -* Memory: add zram, zswap support (#2018). -* Music: allow asymmetric seek steps (#2019). - -### Bug Fixes and Improvements - -* Time: snap seconds to the multiple of interval (#2005). -* Privacy(Pipewire): fix status bar freezing (#2024). -* Privacy(v4l): change device scan method (#2009). -* Kdeconnect: fix device_id parameter (#2033). -* AMD GPU: better error message on device not found (#2035). - -# i3status-rust 0.33.0 - -### Breaking Changes - -* Kdeconnect: removed `hide_disconnected` option and `connected` formatting flag (#1860). - -### New Blocks and Features - -* cpu: Add `critical_info`/`warning_info`/`info_info` options (#1983). -* Kdeconnect: add `format_disconnected` and `format_missing` options (#1860). -* Toggle: allow customizing state/theming when on/off by adding `state_on` and `state_off` options (#1974). -* Disk Space: add support for TiB, GiB, MiB and KiB in `alert_unit` (#1977). -* Add new `privacy` block which can detect if your webcam, screen/monitor, microphone, or audio monitor is being captured by another application. Note: only webcams that use v4l can be detected by default, enable the `pipewire` to monitor the use of the other listed kinds of media. -* Add new `packages` block which supports `apt`, `dnf`, and `pacman/aur` - -### Deprecation Warnings -* `apt`, `dnf`, and `pacman` blocks removed in a future release. - -# i3status-rust 0.32.3 - -### New Blocks and Features - -* Weather: add `zip` option for OpenWeatherMap (#1948). -* Weather: add `format_alt` option (#1944). -* Weather: implement forecast (#1944). -* Music: add `format_alt` (#1960). -* Apt: added config option `ignore_updates_regex` to filter the list of updates (#1967). -* Time: add basic support for non Gregorian calendars (#1968). - -### Bug Fixes and Improvements - -* Xrandr: support multiple outputs (#1949). -* Fail if click handler config refers to unknown button. -* Weather: `location` placeholder now works with Met.no if autolocate is enabled (#1950). - -# i3status-rust 0.32.2 - -### Bug Fixes and Improvements - -* Weather: Add icons for night, separated icons for Fog/Mist from Cloudy. -* icons: Add new set of emoji icons. -* Fix "update = true" click event handling for some blocks (e.g. pacman). - -# i3status-rust 0.32.1 - -### Bug Fixes and Improvements - -* Weather(metno): stop using an API which was terminated on August 31, 2023. The functionality of the block is not affected, but all i3status-rust versions older than 0.32.1 will be unable to use met.no weather service. - -# i3status-rust 0.32.0 - -### Breaking Changes - -* Pacman block now creates checkup-db directory per user. This may break your scripts if they rely on the db path. Instead of `/tmp/checkup-db-i3statusrs` it is now `/tmp/checkup-db-i3statusrs-$USER`. - -### Bug Fixes and Improvements - -* Update default memory format. -* Fix inconsistent rounding in `.eng()` formatter. -* AMD GPU: select device automatically if `device` is not set. - -# i3status-rust 0.31.9 - -### New Themes - -* `ctp-frappe` -* `ctp-latte` -* `ctp-macchiato` -* `ctp-mocha` - -### Bug Fixes and Improvements - -* Add missing default net_cellular icon progression in the `"none"` icon set. -* Removed unused default icons in the `"none"` icon set. -* Defer icon lookup until formatting (see [#1774](https://github.com/greshake/i3status-rust/issues/1774)) - -# i3status-rust 0.31.8 - -### Bug Fixes and Improvements - -* Music block now functions properly when a player name contains `-`. - -# i3status-rust 0.31.7 - -### New Blocks and Features - -* Maildir: Support glob expansions in `inboxes` (for example, this now works: `inboxes = ["~/Maildir/account1/*"]`). - -### Bug Fixes and Improvements - -* Battery(sysfs): Handle the case when charge rate is lower than current power usage. -* Keyboard layout: Add support for keyboard layout variant to setxkbmap driver. -* Blocks that make web requests will now do 3 retries before displaying an error. -* Blocks can now recover from "Failed to render full text" errors. - -# i3status-rust 0.31.6 - -### New Blocks and Features - -* Support custom separators in rotating text. Example: `format = " $title.str(max_w:30, rot_interval:0.2, rot_separator:' - ') |"`. - -### Bug Fixes and Improvements - -* Battery(sysfs): calculate battery level based on `{charge,energy}_{now,full}` instead of kernel-provided `capacity` (see [#1906](https://github.com/greshake/i3status-rust/issues/1906)). -* Text formatting now operates on graphemes instead of "chars". This means that symbols like "a̐" are now processed correctly. - -# i3status-rust 0.31.5 - -### Bug Fixes and Improvements - -* Net: do not consider IPs with `scope host` or lower. -* Net: Define an "active" interface as an interface with 1) `state UP` or 2) `state UNKNOWN` but has an IP. Previously only part 1) was considered. -* Net: add `inactive_format`. - -# i3status-rust 0.31.4 - -### Bug Fixes and Improvements - -* Update `Cargo.lock`. - -# i3status-rust 0.31.3 - -### New Blocks and Features - -* Kdeconnect: Add connectivity report (cell network) -* Add vertical option for bar formatting (▁ ▂ ▃ ▄ ▅ ▆ ▇) - -# i3status-rust 0.31.2 - -### New Blocks and Features - -* Vpn: add `mullvad` driver. - -### Bug Fixes and Improvements - -* Don't require `block = "..."` to be the first field. -* Battery: automatically recover from some errors. -* Sound: automatically reconnect to pulseaudio server when connection fails. - -# i3status-rust 0.31.1 - -### Bug Fixes and Improvements - -* Update `material-nf` icon set for Nerd Fonts v3. -* Temperature: the icon now reflects the max temperature (`material-nf` icon set only). - -# i3status-rust 0.31.0 - -### Breaking Changes - -* Sound: `mappings_use_regex` now defaults to `true`. -* `block = "..."` is now required to be the first field of block configs. However, an error in a block's config will not break the entire bar. - -### New Blocks and Features - -* Battery: added `charging_format` config option. - -### Bug Fixes and Improvements - -* Net: fix `missing_format` option. -* Backlight: fix "calibright config error". - -### Packaging - -* The default release profile no longer strips the binary. -* Added `release-debug-info` profile. - -# i3status-rust 0.30.7 - -### Future Compatibility Notes - -* In version 0.31 sound's `mappings_use_regex` will default to `true`. - -* In the future `block = "..."` will be required to be the first field of block configs. - This will be so that block configuration errors will not break the entire bar. - For example, - ```toml - [[block]] - block = "time" - format = "..." - ``` - will work but - ```toml - [[block]] - format = "..." - block = "time" - ``` - will fail. - -### New Blocks and Features - -* Backlight: Add regex `device` name matching, and display/control more than one monitor with the same block. -* Backlight: Add `missing_format` option. -* Sound: add `mappings_use_regex` option which makes the block treat `mappings` as regexps. Defaults to `false`. -* Sound: add `$active_port` placeholder and `active_port_mappings` option. - -### Bug Fixes and Improvements - -* Kdeconnect: do not fail if notifications are not available. -* Fix a panic when formatting a number as tebibytes. -* Custom: set `command` stdin to `null`. This prevents custom commands from stealing click events. -* Fix _some_ rounding errors in `.eng` formatter. - -# i3status-rust 0.30.6 - -### New Blocks and Features - -* New block: `vpn`: Shows the current connection status for VPN networks. Currently only `nordvpn` is supported (#1725). -* Padding character of `eng` formatter is now configurable. For example, `$volume.eng(pad_with:0)` will render as `05%` instead of ` 5%`. -* Bluetooth: added `battery_state` config option which allows to customize block's color in relation to device battery level (#1836). -* Bluetooth: added `$battery_icon` placeholder (#1837). -* Time: Right click on the block to reverse cycle between timezones (#1839). - -### Bug Fixes and Improvements - -* Net: the WiFi icon now reflects the signal strength (`material-nf` icon set only). -* Apt: now works on systems with non-English locales (#1843). -* Notify: support latest SwayNotificationCenter version. - -# i3status-rust 0.30.5 - -### New Blocks and Features - -* New block `amd_gpu`: display the stats of your AMD GPU. -* Battery: filter battery selection by model (#1808). -* External_ip: allow forcing legacy (v4) IP (#1801). - -### Bug Fixes and Improvements - -* Backlight: improve ddcci interactions (#1770). -* Battery: fix the default device for UPower driver. -* Custom: support shell expansions in watch_files. -* Custom_dbus: fix default format. -* `merge_with_next` block option now works with non-native separators, and also fix color of separators. -* Hueshift: step now actually maxes at 500 (#1827) -* Fix `--help` page. -* config,theme,icons: do not look for files relative to the CWD - -### Packaging - -Manual page is no longer provided in the repo. To generate `man/i3status-rs.1` run `cargo xtask generate-manpage`. See [manual_install.md](doc/manual_install.md) for more details. - -# i3status-rust 0.30.4 - -### New Blocks and Features - -* Time: timezone can now be set to a list of values. Click on the block to cycle between timezones. - -### Bug Fixes and Improvements - -* Net: prefer the default device when multiple devices match the regex. -* Cpu: fix panic on systems which do not report CPU frequency. -* Bluetooth: change block color based on battery level. -* Memory: consider ZFS arc cache as available memory. -* Backlight: reconnect after monitor sleeps. -* Nvidia GPU: display unavailable stats as zeros instead of failing. -* Bluetooth: correctly display battery level even if it is not available instantly. -* Net: get SSID from `NL80211_BSS_INFORMATION_ELEMENTS` (makes SSID available on Linux kernel 5.19 and newer). -* Backlight: fallback to sysfs on systems which don't use `systemd-logind`. -* Do not require config file to have a `.toml` extension. - -# i3status-rust 0.30.3 - -### Bug Fixes and Improvements - -* Net: display more relevant IP addresses. -* Net: fix panic on systems with IPv6 disabled. -* Pomodoro: fix a bug which made the block unusable. -* Setting a click handler without `action = "..."` will disable the default block action. - -# i3status-rust 0.30.2 - -### Bug Fixes and Improvements - -* Net: do not fail if `nl80211` is not available. -* Music: make player volume optional (fixes Firefox support). -* Time: actually apply configured locale. - -# i3status-rust 0.30.1 - -### Bug Fixes and Improvements - -* Fix build on 32-bit systems. - -# i3status-rust 0.30.0 - -This is a major release in which the core has been rewritten to be asynchronous, and the formatting system has also been overhauled. - -Block documentation was moved from `docs/blocks.md` to: https://greshake.github.io/i3status-rust/i3status_rs/blocks/index.html -Formatter documentation is available here: https://greshake.github.io/i3status-rust/i3status_rs/formatting/index.html - -Breaking changes are listed below, however you may also want to compare the example config between v0.22 and v0.30 to get a general idea of changes made to the configuration format: - -https://raw.githubusercontent.com/greshake/i3status-rust/v0.22.0/examples/config.toml - -https://raw.githubusercontent.com/greshake/i3status-rust/v0.30.0/examples/config.toml - -### General / top-level breaking changes - -- Placeholders in `format` strings are now denoted by a dollar sign rather than enclosed in brackets. For example, `format = "{percentage}"` would now be `format = "$percentage"`. - -- Icons are now part of the `format` string option as a placeholder in blocks where format is customisable. - If you have modified `format` and would like to keep the same behaviour (icon, whitespace) you need to update the value. For example, - ```toml - [[block]] - block = "cpu" - format = "{utilization}" - ``` - needs to be changed to: - ```toml - [[block]] - block = "cpu" - format = " $icon $utilization " - ``` - -- Icons can now be referenced by name within `format` strings, e.g. `format = " Hello ^icon_ping "` will use the icon `ping` from the icon set that is currently in use. - -- Top-level `theme` and `icons` config options have been removed. For example, - ```toml - theme = "solarized-dark" - icons = "awesome5" - ``` - needs to be changed to: - ```toml - [theme] - theme = "solarized-dark" - [icons] - icons = "awesome5" - ``` - Additionally, the `name` and `file` options have been merged into `theme`/`icons`. For example, - ```toml - [theme] - name = "awesome5" - [icons] - file = "/path/to/my/custom_iconset.toml" - ``` - needs to be changed to: - ```toml - [theme] - theme = "awesome5" - [icons] - icons = "/path/to/my/custom_iconset.toml" - ``` -- Font Awesome v4 must now be specified via `awesome4`, and `awesome` has been removed. - -- Icons `backlight_{empty,full,1,2,...,13}`, `bat_{10,20,...,90,full}`, `cpu_{low,med,high}`, `volume_{empty,half,full}`, `microphone_{empty,half,full}` have been removed as singular icons, and instead implemented as an array. If you used to override any of these icons, override `backlight`, `cpu`, `volume` and `microphone` instead. For example, - ```toml - cpu_low = "\U000F0F86" # nf-md-speedometer_slow - cpu_med = "\U000F0F85" # nf-md-speedometer_medium - cpu_high = "\U000F04C5" # nf-md-speedometer - ``` - becomes - ```toml - cpu = [ - "\U000F0F86", # nf-md-speedometer_slow - "\U000F0F85", # nf-md-speedometer_medium - "\U000F04C5", # nf-md-speedometer - ] - ``` -- when using theme overrides, you can now reference other colours by name which allows you to avoid redefining the same colour twice, for example: - ```toml - [[block]] - block = "sound" - driver = "pulseaudio" - device = "@DEFAULT_SOURCE@" - device_kind = "source" - [block.theme_overrides] - # switch idle and warning around in order to get warning when mic is *not* muted - idle_fg = { link = "warning_fg" } - idle_bg = { link = "warning_bg" } - warning_fg = { link = "idle_fg" } - warning_bg = { link = "idle_bg" } - ``` - -- `scrolling` option has been renamed to `invert_scrolling` and now accepts `true` or `false`. - -- `on_click` is now implemented as `[[block.click]]`. For example, - ```toml - [[block]] - block = "pacman" - on_click = "random_command" - ``` - needs to be changed to: - ```toml - [[block]] - block = "pacman" - [[block.click]] - button = "left" - cmd = "random_command" - ``` - -### Block specific breaking changes - -Block | Changes -----|----------- -apt, dnf, pacman | `hide_when_uptodate` option is removed and now you can use `format_up_to_date = ""` to hide the block -battery | `full_threshold` now defaults to `95` as often batteries never fully charge -battery | requires device name from `/sys/class/power_supply` even when using UPower driver (previously it used the name from the output of `upower --enumerate`) -battery | `hide_missing` option is replaced with `missing_format`. You can set `missing_format = ""` to maintain the behavior -battery | `hide_full` option is removed. You can set `full_format = ""` to maintain the behavior -bluetooth | `hide_disconnected` option is replaced with `disconnected_format`. You can set `disconnected_format = ""` to hide the block -cpu | The custom `info`, `warning` and `critical` thresholds have been removed -custom_dbus | `name` has been renamed to `path` and the DBus object is now at `rs.i3status`/`rs.i3status.custom` rather than `i3.status.rs` -disk_space | `alias` has been removed in favour of using `format` -focused_window | `autohide` is removed. You can format to `" $title.str(w:21) \| Missing "` to display the block when title is missing -focused_window | `max_width` has been removed, and can instead be implemented via the new formatter. For example `max_width = 15; format = "{title}"` is now `format = "$title.str(max_w:15)"` -kdeconnect | now only supports kdeconnect v20.11.80 and newer (December 2020 and newer) -keyboard_layout | `xkbswitch` driver is removed pending re-implementation (see #1512) -memory | `clickable`, `display_type`, `format_mem` and `format_swap` are removed and now you can use `format` and `format_alt` to maintain the behavior -music | `smart_trim`, `max_width` and `marquee` have been removed. All these settings are now configured inside the format string. -music | `buttons` has been removed and is now configured via the new `[[block.click]]` syntax. New analogue `format` placeholders (`$play`/`$next`/`$prev`) have been added -net |`hide_missing` and `hide_inactive` are removed. You can set `missing_format = ""` -net | formatting for `graph_down` and `graph_up` is not yet implemented (see #1555) -notmuch | `name` option is removed and now you can use `format` to set it -temperature | `collapsed` option is removed and now you can use `format_alt = " $icon "` to maintain the behavior -time | `locale` option is removed and now you can use `format` to set it, e.g. `format = " $icon $timestamp.datetime(f:'%d/%m %R', l:fr_BE) "` -toggle | `text` option is removed and now you can use `format` to set it - -### Removed blocks - -- `ibus` block has been removed. Suggested example replacement: - ```toml - [[block]] - block = "custom" - command = "ibus engine" - ``` -- `networkmanager` block has been removed (could be revisited in the future), so `net` block should be used instead. - Note there is no equivalent to `interface_name_exclude` in `net` as it only shows one interface at a time. - - Example of a `networkmanager` config ported to `net`: - - v0.22: - ```toml - [[block]] - block = "networkmanager" - on_click = "alacritty -e nmtui" - interface_name_include = ['br\-[0-9a-f]{12}', 'docker\d+'] - ``` - - v0.30: - ```toml - [[block]] - block = "net" - device = 'br\-[0-9a-f]{12}' - [[block.click]] - button = "left" - cmd = "alacritty -e nmtui" - - [[block]] - block = "net" - device = 'docker\d+' - [[block.click]] - button = "left" - cmd = "alacritty -e nmtui" - ``` - -### New features and bugfixes -- New `service_status` block: monitor the state of a (systemd) service. -- New `tea_timer` block: a simple timer. -- When blocks error they no longer take down the entire bar. Instead, they now enter error mode: "X" will be shown and on left click the full error message will be shown in the bar. -- `apt` block has new `ignore_phased_updates` option. (#1717) -- `battery` now supports `empty_threshold` to specify below which percentage the battery is considered empty, and `empty_format` to use a custom format when the battery is empty. -- `battery` now supports `not_charging_format` config option. (#1685) -- `custom_dbus` block can now be used more than once in your config. -- `custom` block has new config option `persistent` which runs a command in the background and updates the block text for each received output line. -- `focused_window` block now supports most wlroots-based compositors. -- `music` block now supports controlling and displaying the volume for individual players (#1722) -- `music` block now has `interface_name_exclude` and improved `playerctld` support (#1710) -- `net` block now supports regex for `device` (#1601) -- `notify` block now has support for SwayNotificationCenter via `driver = "swaync"` (#1662) -- `weather` block now supports using met.no as an info source (#1577) -- More blocks now support `format` option (custom, custom_dbus, hueshift, maildir, notmuch, pomodoro, time, uptime) -- Some blocks now have debug logs which can be enabled like so: `RUST_LOG=block=debug i3status-rs` where "block" is the block name. -- Default click actions for blocks can now be remapped (#1686) - -### Dependencies that are no longer required - -- `curl` (was previously used in the Github and Weather blocks) - -# i3status-rust 0.22.0 - -### Breaking changes - -* Battery: remove `allow_missing` config option (#1461 by @MaxVerevkin) -* Temperature: sysfs driver removed - -### New Blocks and Features - -* Net block: configurable graph_up/down formatting (#1457 by @veprolet) - -# i3status-rust 0.21.10 - -### New Blocks and Features - -* Expand paths (e.g. `~`->`$HOME`, just like in shell) for many blocks (#1453 by @Henriquelay) - -### Bug Fixes and Improvements - -* Battery: fix availability check for some devices with `sysfs` driver (#1456 by @ferdinandschober) -* Battery: fallback to `charge_level` if `capacity` cannot be calculated (#1458 by @ferdinandschober) - -# i3status-rust 0.21.9 - -### New Blocks and Features - -* New "awesome6" icon set -* Music: `players` option can now accept a list of names (#1452 by @meryacine) - -# i3status-rust 0.21.8 - -### Bug Fixes and Improvements - -* Net: WiFi information should be more reliable now ([e7e2836f](https://github.com/greshake/i3status-rust/commit/e7e2836f823e35ecb507e4af7108dec110cbedaa)) -* Battery: fix missing battery detection for `sysfs` driver ([24f432f](https://github.com/greshake/i3status-rust/commit/24f432fb67e5ba3cadddf5084b60c15e392f5e44)) - -# i3status-rust 0.21.7 - -### New Blocks and Features - -* Icons can now be overridden per block with `icons_overrides` (97a66195f16469a4011a1521fb991bbe943196b6) - -### Bug Fixes and Improvements - -* Battery: be more efficient by enumerating devices less often (#1437 by bim9262) -* Net: use bss signal if wifi signal info is incomplete (4f11d68b1d5147fe2b5285d68653e7091f44f628) -* Sound: check DEVICE_FORM_FACTOR property to determine icons (#1438 by kevinmos) - -# i3status-rust 0.21.6 - -### New Blocks and Features - -* Hueshift: Add wl-gammarelay driver (#1421 by bim9262) - -### Bug Fixes and Improvements - -* Battery: prefer system batteries (BATx/CMBx) when doing auto discovery (3db119a5a2dd12a65a499377cf849d418bfee308) - -# i3status-rust 0.21.5 - -### New Blocks and Features - -* Add `if_command` field to block config to allow conditional enabling of blocks on startup (#1415 by @LordMZTE) - -### Bug Fixes and Improvements - -* Battery: revert to previous default device discovery behaviour (d6fbfd06cc4d078efccb1c559e7eb934d36ffe7a) - -# i3status-rust 0.21.4 - -### Bug Fixes and Improvements - -* Battery: fix issues with finding battery device paths (#1417 by @bim9262) -* Battery: better default values for `device` (c6824727020090bf6eb59cd3bf6f4de0f10179fa) - -# i3status-rust 0.21.3 - -### Bug Fixes and Improvements - -* Temperature: use libsensors bindings instead of sensors binary (#1375 by @MaxVerevkin) -* Hueshift: do not leave zombies (#1411 by @Naarakah) -* Time: reflect timezone changes (72a7284) -* Watson: fix automatic updates (0b810cb and 0b810cb) - -### Deprecation Warnings -* Temperature: `sysfs` driver will be removed in a future release. - -# i3status-rust 0.21.2 - -### New Blocks and Features - -* Add dracula theme (#1408 by @welcoMattic) - -### Bug Fixes and Improvements - -* Battery block: Fix UPower property type mismatch (#1409 by @bim9262) - -# i3status-rust 0.21.0 - -### New Blocks and Features - -* New block: `rofication` (#1356 by @cfsmp3) -* New block: `external_ip` (#1366 by @cfsmp3) -* Xrandr block: new option `format` (it overrides `icons` and `resolution` options which are now deprecated) (ca86a97) -* Battery block: add new apcupsd driver (#1383 by @bim9262) -* Battery block: enable `allow_missing` for the UPower driver (#1378 by @bim9262) -* KeyboardLayout: add support for the xkb-switch keyboard layout reader (#1386 by @roguh) - -### Bug Fixes and Improvements - -* Sound block: fix headphones indicator (#1363 by @codicodi) -* Sound block: named PulseAudio devices now work as expected (#1394 by @bim9262) -* NetworkManager block: escape SSID (#1373 by @nzig) -* Taskwarrior block: use inotify to get instant changes (you will need to set `data_location` option if `taskwarrior` is configured to use a custom data directory) (#1374 by @cfsmp3) -* Battery block: fix spacing (#1389 by @bim9262) -* Hueshift block: replace `killall` with `pkill` (#1398 by @stelcodes) - -### Deprecation Warnings -* Xrandr block: `icon` and `resolution` will be removed in a future release. Use `format` instead. -* Memory block: `icons` will be removed in a future release. Set `icons_format = ""` to disable icons. -* Maildir block: `icon` will be removed in a future release. Set `icons_format = ""` to disable icons. -* Notmuch block: `no_icon` will be removed in a future release. Set `icons_format = ""` to disable icons. - -# i3status-rust 0.20.7 - -### New Blocks and Features - -* Backlight block: new options `minimum`, `maximum`, `cycle` for toggling min/max brightness on click or on scroll (#1349 by @Vanille-N) -* Focused Window block: add `format` string (#1360 by @cfsmp3) - -### Bug Fixes and Improvements - -* icons: Add missing bat_not_available icon (#1361 by @ram02z) -* Docker block: colour errors using Critical state (#1360 by @cfsmp3) - -# i3status-rust 0.20.6 - -### New Blocks and Features - -* Custom block: new `watch watch_files` option that uses inotify to trigger the block to update when one or more specified files are seen to have been modified (#1325 by @BrendanBall) -* CustomDBus block: new `initial_text` option to set the text shown up until the first update is received -* Hueshift block: added support for wlsunset (#1337 by @DerVerruckteFuchs) - -### Bug Fixes and Improvements - -* IBus block: no longer crashes the bar if IBus reports that there is no global engine set on first startup -* Music block: the default text icons are now pango escaped and should cause no errors with i3bar - -# i3status-rust 0.20.5 - -### New Blocks and Features - -* New DNF block for Fedora (#1311 by @sigvei) -* Docker block: allow non-default docker socket files (#1310 by @JTarasovic) -* Sound block: add option to automatically change icon based on output device type (#1313 by @codicodi) - -### Bug Fixes and Improvements - -* Hueshift block: fix sluggishness by updating widget text on interactions (#1320 by @JohnDowson) -* Music block: fix long standing issue where block randomly stops updating (#1327 by @jamesmcm) -* Nvidia block: fix nvidia block falling behind on lines from nvidia-smi (#1296 by @ZachCook) - - -# i3status-rust 0.20.4 - -### New Blocks and Features - -* Github block: new config options `critical`, `warning`, `info`, `good` to colour the block for different notifications (#1286 by @ZachCook) -* Temperature block: new `driver` config option with the option to choose a new backend using sysfs to grab temp info instead of `lm_sensors` (#1286 by @ZachCook) - -### Bug Fixes and Improvements - -* Battery/Kdeconnect block: add more battery icons. For the new battery icons you will need to update your icon files, otherwise it will fallback to the previous icons. (#1282 by @freswa) -* Nvidia block: only run `nvidia-smi` once instead of spawning a new instance for each update (#1286 by @ZachCook) -* Weather block: escape spaces in internally generated URL (#1289 by @rbuch) - -### Deprecation Warnings -`bat_half`, `bat_quarter`, `bat_three_quarters` are likely to be removed in a future release. - -# i3status-rust 0.20.3 - -### Bug Fixes and Improvements - -* Net block: fix SSID escape code decoding (#1274 by @GladOSkar) -* NetworkManager block: update DBus interface for newer versions of NM (#1269 by @mailhost) -* Pomodoro block: fix crash causing by pause icon typo (#1295 by @GladOSkar) -* Temperature block: fix fallback for users with old versions of `lm-sensors` (#1281 by @freswa) -* Icons: Fix `material-nf` icons that caused some blocks to render backwards (#1280 by @freswa) -* Themes: Add ability to unset colors using overrides (#1279 by @GladOSkar and @MaxVerevkin) -* Themes: Fix alternating tint for the `slick` theme (#1284 by @MaxVerevkin) - -If you are manually managing your icon/theme files then you may want to update them now for the above fixes. - -# i3status-rust 0.20.2 - -### Bug Fixes and Improvements - -* Battery block: find battery by default instead of hardcoding "BAT0" (#1258 by @orvij) -* Batter block: new `full_threshold` option for batteries that don't reach 100% (#1261 by @GladOSkar) -* CPU block: add `boost` format key for displaying CPU boost status (#1152 by @indlin) -* Custom block: better error message (#1233 by @jespino) -* Memory block: Count ZFS arc cache to cache to exclude from used memory (#1227 by @GladOSkar) -* Pacman block: fix invocation of fakeroot/pacman command (#1241) -* Pacman block: fix default format string (#1240 by @GladOSkar) -* Pomodoro block: Allow `notify-send` as a notification method -* Fixed missing net block icons for the material icon theme (#1244 by @K4rakara) -* Formatter: allow hiding unit prefixes. For example, `"{key;_K}"` will set the min unit prefix to "K" but hides it from showing. -* Formatter: allow spaces between the value and unit/prefix. For example, `"{key; K*b}"` results in "value Kb" and `"{key; _K*b}"` results in "val b". -* Add short_text support (#1207 by @GladOSkar) - - -### Breaking Changes - -* Pomodoro block: Icons are no longer hardcoded. New icons: `pomodoro_started`, `pomodoro_stopped`, `pomodoro_paused`, `pomodoro_break` have been added to the icon themes in the repo, so you must update your icon theme files if it is not done by your package manager. (#1264) - -### Deprecation Warnings -* Pomodoro block: `use_nag` and `nagbar_path` will be removed in a future release. Use `notifier` and `notifier_path` instead. - -# i3status-rust 0.20.1 - -### Bug Fixes and Improvements - -* Fixed config error messages showing in swaybar but not in i3bar (#1224 by @jthomaschewski) -* Fixed pacman block crash due to stderr output of pacman itself (#1220 by @mpldr) -* Custom block example list has been created and documented (#1223 by @GladOSkar) - -# i3status-rust 0.20.0 - -### Breaking Changes - -Themes/Icons: - -* These have been moved out into files instead of being hardcoded in the Rust source. The following folders are checked in this order: first `$XDG_CONFIG_HOME/i3status-rust/`, next `$HOME/.local/share/i3status-rust/`, finally `/usr/share/i3status-rust/`. If installing manually via cargo, you will need to copy the files to an appropriate location (an `install.sh` script is provided which does this). If installing via the AUR on Arch Linux, the package will install the files to `/usr/share/i3status-rust/` for you, so you do not need to do anything (this should also be true for other distros assuming the package maintainer has packaged i3status-rust correctly). - -* Per block theme overrides have been renamed from `color_overrides` to `theme_overrides` (this was previously undocumented but has since been mentioned in themes.md) - -Formatting: - -* Formatting for all blocks using `format` strings has been overhauled to allow users to customise how numbers and strings are displayed, which was not possible previously. Due to this some blocks may now display slightly differently to previous versions and have been documented below. Refer to the [formatting documentation](doc/blocks.md#formatting) to get more information on the new formatting options. - -Blocks: - -* CPU Utilization block: Due to an overhaul of our internal code, the `per_core` option has been removed. The same configuration can be achieved using the new `{utilization}` format keys. -* Battery and Disk Space blocks: The `{bar}` format key has been removed in favor of the new [bar](doc/blocks.md#formatting#bar-max-value) formatter. For example, to make the Battery block display the current percentage as a 6 character bar with 100% as the max value, set the format string as so: `format = "{percentage:6#100}`. -* Disk Space block: The `{unit}` format key has been removed since the unit of `{free}` and similar format keys don't rely on `unit` configuration option anymore. -* Maildir block: this is now optional and must be enabled at compile time (#1103 by @MaxVerevkin) -* Memory block: all old format keys have been removed, refer to the table below for more details. -* Net block: `use_bits`, `speed_min_unit`, `speed_digits` and `max_ssid_width` configuration options have been removed and require manual intervention to fix your config. `speed_min_unit` is replaced by the [min prefix](doc/blocks.md#min-prefix) formatter. `max_ssid_width` is replaced by the [max width](doc/blocks.md#0max-width) formatter. -* Net block: partially moved from calling external commands to using the netlink interface, which may not work on BSD systems (#1142 by @MaxVerevkin) -* Networkmanager block: `max_ssid_width` config option has been removed, but the behaviour can be restored using the [max width](doc/blocks.md#max-width) formatter. For example, `max_ssid_width = 10` is now achieved with `ap_format = "{ssid^10}"`. -* Sound block: `max_width` config option has been removed, but the behaviour can be restored using the [max width](doc/blocks.md#max-width) formatter. -* Speedtest block: `bytes`, `speed_min_unit` and `speed_digits` configuration options have been removed in favour of the new `format` string formatter. For example, to replicate `bytes=true; speed_min_unit="M", speed_digits=4` use `format = "{speed_down:4*B;M}{speed_up:4*B;M}"` - -Memory block removed format keys: - - Old key | New alternative ----------|--------------- -`{MTg}` | `{mem_total;G}` -`{MTm}` | `{mem_total;M}` -`{MAg}` | `{mem_avail;G}` -`{MAm}` | `{mem_avail;M}` -`{MAp}` | `{mem_avail_percents}` -`{MApi}` | `{mem_avail_percents:1}` -`{MFg}` | `{mem_free;G}` -`{MFm}` | `{mem_free;M}` -`{MFp}` | `{mem_free_percents}` -`{MFpi}` | `{mem_free_percents:1}` -`{Mug}` | `{mem_used;G}` -`{Mum}` | `{mem_used;M}` -`{Mup}` | `{mem_used_percents}` -`{Mupi}` | `{mem_used_percents:1}` -`{MUg}` | `{mem_total_used;G}` -`{MUm}` | `{mem_total_used;M}` -`{MUp}` | `{mem_total_used_percents}` -`{MUpi}` | `{mem_total_used_percents:1}` -`{Cg}` | `{cached;G}` -`{Cm}` | `{cached;M}` -`{Cp}` | `{cached_percent}` -`{Cpi}` | `{cached_percent:1}` -`{Bg}` | `{buffers;G}` -`{Bm}` | `{buffers;M}` -`{Bp}` | `{buffers_percent}` -`{Bpi}` | `{buffers_percent:1}` -`{STg}` | `{swap_total;G}` -`{STm}` | `{swap_total;M}` -`{SFg}` | `{swap_free;G}` -`{SFm}` | `{swap_free;M}` -`{SFp}` | `{swap_free_percents}` -`{SFpi}` | `{swap_free_percents:1}` -`{SUg}` | `{swap_used;G}` -`{SUm}` | `{swap_used;M}` -`{SUp}` | `{swap_used_percents}` -`{SUpi}` | `{swap_used_percents:1}` - -### Deprecation Warnings - -* Disk Space block: the `alias` has been deprecated in favour of using `format` and may be removed in a future release. - -### New Blocks and Features - -* Backlight block: new `invert_icons` config option for people using coloured icons (#1098 by @MaxVerevkin) -* Net block: new `format_alt` option to set an alternative format string to switch between when the block is clicked (#1063 by @MaxVerevkin) -* Nvidia block: new "Power Draw" option (#1154 by @quintenpalmer) -* Sound block: new `{output_description}` format key to show the PulseAudio device description -* Speedtest block: new `format` configuration option to customize the output of the block. -* Temperature block: add fallback for older systems without JSON support (#1070 by @ammgws) -* Weather block: new config option to set display language, and new format key `{weather_verbose}` to display textual verbose description of the weather, e.g. "overcast clouds" (#1169 by @halfcrazy) -* SIGUSR2 signal can now be used to reload i3status-rust in-place without restarting i3/swaybar (#1131 by @MaxVerevkin) -* New compile time feature `debug_borders` for debugging spacing issues (#1083 by @MaxVerevkin) -* New "material-nf" icon set (#1095 by @MaxVerevkin) -* New `icons_format` config option for overriding icon formatting on a per-block basis (#1095 by @MaxVerevkin) - -### Bug Fixes and Improvements - -* Music block: fix `on_collapsed_click` which was broken in a previous release (#1061 by @MaxVerevkin) -* Net block: print "N/A" when trying to get ssid or signal strength using wired connections instead of erroring out (#1068 by @MaxVerevkin) -* Networkmanager block: avoid duplicate device with VPN connections (#1099 by @ravomavain), fix cases where connections would not update (#1119 by TilCreator) -* Sound block: fix spacing for empty format strings (#1071 by @ammgws) - -# i3status-rust 0.14.7 - -Bug fix release for compile error on 32bit systems - -# i3status-rust 0.14.6 - -Fixes bug with loading config from file introduced in 0.14.4 (and also present in 0.14.5) - -# i3status-rust 0.14.5 - -Fixes crash on i3 introduced in 0.14.4 - -# i3status-rust 0.14.4 - -### General Notices - -* Due to a bugfix in the CPU block, when using the `{frequency}` and `{utilization}` format key specifiers, "GHz" and "%" will be appended within the format keys themselves so there is no need to write them in your `format` string anymore. - -### Deprecation Warnings - -* Battery block config option `show` has been deprecated in favour of `format` (deprecated since at least v0.10.0 released in July 2019) - -* Battery block config option `upower` has been deprecated in favour of `device` (deprecated since at least v0.10.0 released in July 2019) - -* CPU Utilization block config option `frequency` has been deprecated in favour of `format` (deprecated since at least v0.10.0 released in July 2019) - -* Network block config options `ssid`, `signal_strength`, `bitrate`, `ip`, `ipv6`, `speed_up`, `speed_down`, `graph_up`, `graph_down` have been deprecated in favour of `format` (deprecated since v0.14.2 released in October 2020) - -* Pacman block format key `{count}` has been deprecated in favour of `{pacman}` (deprecated since v0.14.0 released in June 2020) - -* Taskwarrior block config option `filter_tags` has been deprecated in favour of `filters` (since v0.14.4 - this release) - -### New Blocks and Features - -* `on_click` option is now available for all blocks (#1006 by @edwin0cheng) - -* Github block: new option to hide block when there are no notifications (#1023 by @ammgws) - -* Hueshift block: add support for gammastep (#1027 by @MaxVerevkin) - -* Pacman block: new option to hide block when up to date (#982 by @ammgws) - -* Taskwarrior block: support multiple filters with new `filters` option (#1008 by @matt-snider) - -### Bug Fixes and Improvements - -* Fix config error when using custom themes (#968 by @ammgws) - -* Fix microphone icons in awesome5 (#1017 by @MaxVerevkin) - -* Make blocks using http more resilient (#1011 by @simao) - -* Various performance improvements/optimisations (#1033, #1039 by @MaxVerevkin) - -* Bluetooth: monitor device availability to avoid erroring out block (#986 by @ammgws) - -* CPU block: fix "{frequency}" format in per-core mode (#1031 by @MaxVerevkin) - -* KDEConnect block: support new version of kdeconnect (v20.12.* and above) - -* KeyboardLayout block: support both `{variant}` and `{layout}` when using the sway driver (#1028 by @MaxVerevkin) - -* Music block: handle case when metadata is unavailable (#967 by @ammgws), add workaround for `playerctl` (#973 by @ammgws), various other bugfixes (see #972) - -* Net block: fix overflow panic (#993 by @ammgws), better autodiscovery (#994 by @ammgws), fix issues with parsing JSON output (#998 by @ammgws), `speed_min_unit` is now correctly handled (#1021 by @MaxVerevkin), allow Unicode SSIDs to be displayed correctly (#995 by @2m) - -* Speedtest block: use `speed_digits` to format ping as well (#975 by @GladOSkar), `speed_min_unit` is now correctly handled (#1021 by @MaxVerevkin) - -* Xrandr block: do not leave zombie processes around (#990 by @ammgws) - -# i3status-rust 0.14.3 - -### New Blocks and Features - -* New Apt block for keeping tabs on pending updates on Debian based systems (#943 by @ammgws) - -* New Notify block for controlling/monitoring your notification daemon's do-not-disturb status - -* KeyboardLayout block: add `variant` format specifier for localebus (#940 by @ammgws) - -* Music block: implement format string (#949 by @ammgws), allow right click to cycle between available players (#930 by @ammgws) - -* Implement per-block colour overrides (#947 by @ammgws) - -* New "native" and "semi-native" themes (#938 by @GladOSkar) - -### Bug Fixes and Improvements - -* Add git commit hash to version output (#915 by @ammgws) - -* Replace `uuid` dependency with just `getrandom` (#921 by @ammgws) - -* Fix alternating tint behaviour (#924 by @ammgws, #927 by @GladOSkar) - -* Fix panic when no icon exists for Diskspace, KDEConnect blocks (#908, #910 by @ammgws) - -* Fix spacing for Battery, Sound & NetworkManager blocks (#923 from @Stunkymonkey) - -* Battery block: clamp 'time remaining' values to something more realistic (#912 by @ammgws) - -* KeyboardLayout block: fix crash on sway (#918 by @gdamjan, #939 by @ammgws) - -* Music block: completely overhaul update mechanism (#906 by @ammgws) - -* Net block: do not error out when arrays are empty (#926 by @ammgws) - -* Xrandr block: remove hardcoded icons (#911 by @ammgws) - -# i3status-rust 0.14.2 - -### New Blocks and Features - -* New Hueshift block (#802 by @AkechiShiro) - -* Backlight block: add nonlinear brightness control via new `root_scaling` option (#882 by @dancek) - -* Battery block: add `allow_missing_battery` option (#835 by @Nukesor) - -* Bluetooth block: add `hide_disconnected` option to hide block when device is disconnected (#858 by @ammgws) - -* CPU block: add `on_click` option (#813 by @Dieterbe) - -* Custom block: add signal support (#822 by @Gelox), add `hide_when_empty` option to hide block when output is empty (#860 by @ammgws), add `shell` option to set the shell used (#861 by @ammgws) - -* CustomDBus block: allow setting the icon and state (#757 by @jmgrosen) - -* Disk Space block: add `format` string option (#714 by @jamesmcm) - -* IBus block: add `format` string option (#765 by @ammgws) - -* Music block: add `dynamic_width`option (#787 by @UnkwUsr), add `on_click` (#817 by @Dieterbe), add `hide_when_empty` option (#892 by @ammgws), add `interface_name_exclude` option (#888 by @ammgws) - -* Net block: add `format` string option (#738 by @gurditsbedi) - -* NetworkManager block: add regex filters for interface names (#781 by @omertuc) - -* Sound block: add support for input devices (#740 by @remi-dupre), and new `max_vol` config option (#796 by @ammgws) - -* Temperature block: add `inputs` whitelist (#811 by @arraypad), add `scale` option (#895 by @rjframe) - -* Time block: add `locale` option (#863 by @ammgws) - -### Bug Fixes and Improvements - -* Fix spacing for inline widgets (#866 from @DCsunset) - -* Fix spacing for plain theme (#894 by @Stunkymonkey) - -* Battery block: add `full_format` to show text when battery is full (#785 by @DCsunset) - -* Custom block: ensure `command` and `cycle` are actually mutually exclusive (#899 by @ammgws) - -* Focusedwindow block: fix panic under sway (#792, #793 by @ammgws) - -* IBus block: fix logic for finding dbus address (#759 by @ammgws) - -* KDEConnect block: fix panic (#743 by @v0idifier) - -* Load block: fix cpu count (#859 by @ammgws) - -* Music block: only respond to left clicks (#862 by @ammgws), allow scrolling to seek forward/backward (#873 by @ammgws) - -* Net block: sed awk grep removal (#758 by @themadprofessor, #825 by @hlmtre), fix regex parsing (#821 by @Dieterbe), fix logic for `hide_inactive`/`hide_missing` (#897 by @GladOSkar) - -* NVidia block: fix panics (#771 by @themadprofessor, #807, #846 by @ammgws) - -* Pacman block: fix regex logic (#804 by @PicoJr) - -* TaskWarrior block: don't count deleted items (#788 by @HPrivakos) - -# i3status-rust 0.14.1 - -* Forgot to regenerate Cargo.lock when 0.14.0 was released - -(No features/code changes from 0.14.0) - -# i3status-rust 0.14.0 - -### New Blocks and Features - -* New KDEConnect block (#717 by @ammgws) - -* New CustomDBus block (#687 by @ammgws) - -* New Network Manager block (#641 by @kennylevinsen). This block existed previously but was undocumented until it was overhauled completely by @kennylevinsen) - -* New Taskwarrior block (#600 by @flying7eleven) - -* New GitHub block (#425 by @jlevesy) - -* Keyboard Layout block now supports `sway` (#670 by @ammgws), and also has a new `format` config option (#593 by @thiagokokada) - -* IBus block now allows mapping of displayed engine to user configured value (#576 by @ammgws) - -* Weather block now supports `humidity` and `apparent` (Australian Apparent Temperature) format specifiers (#640 by @ryanswilson59, @ammgws). Location can now also be set by name rather than ID using the new `place` option (#635 by @ammgws). Alternatively, the location can be guessed from your current IP address (#690 by @ammgws) - -* Focused Window block new `show_marks` option to show marks instead of title (#532 by @ammgws) - -* Net and Speedtest blocks now take `speed_min_unit` and `speed_digits` parameters to format speeds (#704, #707 by @GladOSkar, @ammgws). - -* Net block `ssid` config option now supports `iwctl` and `wpa_cli` (#625, #721 by @ammgws). Can now show bitrate for wired devices (#612 by @ammgws). New `ipv6` option (#647 by @ammgws) - -* Pacman block now supports a `critical_updates_regex` parameter to control block state (#613 by @PicoJr), and now supports AUR as well (#658 by @PicoJr) - -* Music block has a new `smart_trim` config option (#654 by @jgbyrne). Artist/title separator can now be customised with the `separator` option (#655 by @ammgws) - -* Sound block now supports a `format` parameter (#618 by @jedahan). Along with that a format qualifier `output_name` was added which will show the name of the sink whose volume is being reported (#712 by @ammgws). ALSA driver: new `device` and `natural_mapping` options (#622 by @ammgws) - -* CPU block now has `per_core` support for `{frequency}`, `{utilization}` (@grim7reaper) - -* Block `interval` config can now take `"once"` in order to run blocks only one time (#684 by @PicoJr) - -* Update font awesome icons to version 5 (#619 by @carloabelli) - -* Add support for progress bars to some blocks (#578 by @carloabelli) - -* Themes can now be read from standalone files (#611 by @atheriel & @PicoJr) - -* New command line option `--never-pause` which will ignore any attempts by i3 to pause the bar when hidden/full-screen (#701 by @ammgws) - -* If no config file path is supplied then we default to XDG_CONFIG_HOME/i3status-rust - -### Bug Fixes and Improvements - -* Net block fixed to support ppp vpn (#570 by @MiniGod). Device is now auto selected by default (#626 by @ammgws). Fixed error in `use_bits` calculation (#704 by @ammgws). Use /sys/class/net//carrier instead of operstate in is_up() (#605 by @happycoder97, @ammgws) - -* Music block artist parsing from metadata fixed (#561 by @Riey) - -* Fix panics for blocks without update intervals (#582 by @ammgws) - -* Nvidia block: make threshold configurable, swap idle/good (#615 by @ammgws). Also fixed utilisation to have a fixed width (#566 by @TheJP) - -* Backlight block now reads from actual_brightness as per kernel docs (#631 by @ammgws), with a special case for amdgpu backlights which don't follow the spec (#643 by @ammgws) - -* Battery block now has a fallback for determining power consumption (#653 by @ammgws), and the time remaining is now only displayed when useful (#591 by @debugloop) - -* Time block fixed to only register left mouse button clicks (#628 by @ammgws) - -* Toggle block fixed to only toggle if command exited successfully (#648 by @ammgws) - -* Fix missing icons for `bat_half` in the none theme (#719 by @varunkashyap) - -* Fix panic in CPU block if >32 CPUs present (#639 @snicket2100) - -* Fix panic in Memory block when wrong placeholder given (#616 by @ammgws) - -* Fix missing `good_bg` and `good_fg` theme overrides (#630 by @carloabelli) - -* Unified handling of stdin and stdout to prevent broken pipe errors (#594 by @Celti) - -* Travis CI will now run clippy for all features and targets (#682 by @rotty) - -* Dependent crates have been updated to their latest versions (#729 by @ammgws, @ignatenkobrain) - -### Documentation - -* Document `info`, `good`, `warning`, `critical` parameters for the Battery block (@ammgws) - -* Document `interval` for Notmuch, Uptime blocks (@ammgws) - -* Fix error in Pomodoro block docs (#646 by @kAworu) - -* Add profiling.md (#649 by @PicoJr) - -* Adds a man page #556 - -# i3status-rust 0.13.1 - -* Dependent crates have been updated to their latest versions to make downstream - packaging easier. This will become part of the normal release process in the - future. (#540 by @rotty, #551 by @atheriel) - -# i3status-rust 0.13.0 - -### New Blocks and Features - -* The Net block now takes a `use_bits` parameter to display speeds in bit-based - instead of byte-based units. (#496 by @hlmtre) - -* The Pacman block now supports a `format` parameter. (#473 by @ifreund) - -* The top-level config now takes a `scrolling` parameter that can be used to - turn on `"natural"` mouse scrolling in the bar. (#494 by @bakhtiyarneyman) - -* The Brightness block will now fall back to using D-Bus for changing the - brightness if it cannot modify it via `sysfs`. (#499 by @majewsky) - -* The Bluetooth block now allows for setting a text `label` parameter to keep - track of devices. (#528 by @jeffw387) - -### Bug Fixes and Improvements - -* Fixes a panic that could sometimes manifest when restarting Pulseaudio. (#484 - by @ammgws) - -* Fixes errors in the Pango markup we generate. (#518 by @ammgws) - -* Fixes a potential panic when the Focused Window block was the only one in the - configuration. (#535 by @ammgws) - -* Fixes potential issues due to not ignoring `stdin` and `stdout` when spawning - child processes. (#530 by @Celti) - -* Improvements to the spacing around icons and IP addresses in the Net block. - (#505 and #507 by @ammgws) - -* Bumps several dependencies to fix security issues and reduce the number of - transitive dependencies, which should improve build times. (#491, #492, #493, - #510, #523 by @ammgws) - -* Updates the installation documentation for Fedora. The project is now in the - official repos! (#488 by @tim77) - -* Simplifies the `udev` rule in the Brightness block docs. (#481 by @hellow554) - -* Fixes a typo in the theme documentation. (#485 by @peeweep) - -* Adds mention in the documentation that the Focused Window block is compatible - with Sway. (#497 by @NilsIrl) - -* Adds documentation for the optional Notmuch mail block. (#527 by @ammgws) - -* Travis CI will now compile the project with all features enabled, which would - have caught several bugs long ago. (#539 by @rotty) - -# i3status-rust 0.12.0 - -### New Blocks and Features - -* Wireguard devices are now correctly identified as VPNs in the net block. (#419 - by @vvrein) - -* The keyboard layout block now has a `kbddbus` driver. (#451 by @sashomasho) - -* Adds a new Pomodoro block. (#453 by @ghedamat) - -### Bug Fixes and Improvements - -* Fixes a panic in the iBus block due to the use of Perl regex features. (#443 - by @ammgws) - -* Fixes more 32-bit build issues (e.g. for armv6 and i686). (#449 and #450 by - @jcgruenhage) - -* We now enforce `cargo fmt` on the codebase and in Travis CI. (#457 by - @atheriel and @kennylevinsen, #474 by @ifreund) - -* Improves parsing of `setxkbmap` output. (#458 by @sashomasho) - -* Improvements to character width calculations in the rotating text widget. - (#437 by @ammgws) - -* Adds Fedora, NixOS, and Void Linux installation info to the `README`. (@tim77 - and @atheriel) - -* The Font Awesome icons now use `bat_quarter` and `bat_three_quarters` for - battery ranges. (#393 by @Ma27) - -* Adds documentation for `hide_missing` and `hide_inactive` in the net block. - (#476 by @bascht) - -# i3status-rust 0.11.0 - -### New Blocks and Features - -* Adds a new Docker block, which can display information about containers - overseen by the Docker daemon. (#413 by @jlevesy) - -* Adds a new Notmuch block for querying information from a Notmuch mail - database. This block is currently an optional feature and must be enabled with - `cargo build --features notmuch`. (#215 by @bobthemighty and @atheriel) - -* The Weather block will now obey the `OPENWEATHERMAP_API_KEY` and - `OPENWEATHERMAP_CITY_ID` environment variables. (#410 by @nicholasfagan) - -* The Net block can now display wifi signal strength. (#418 by @bnjbvr) - -* The project now has improved crate metadata, a proper `CONTRIBUTING.md` file, - and will put release notes in a `NEWS.md` file. (by @atheriel) - -### Bug Fixes and Improvements - -* Updates the `nix` crate to fix broken builds on aarch64 with musl libc (#402). - -* Fixes builds on i686. (#406 by @Gottox) - -* Fixes a potential crash due to missing wind speed or direction in the Weather - block. (#407 by @bramvdbogaerde). - -* Fixes omission of UPower batteries that do not have a `battery_` prefix. (#423 - by @freswa) - -* Fixes our use of now-deprecated dynamic trait and range syntax language - features. (#428 by @duac) - -* Prunes some transient dependencies. (#434 by @ohk2kt3t4 and @ammgws) - -* Fixes our use of a deprecated flag in our `rustfmt` configuration. (#438 by - @ammgws) - -* Internal refactoring to reduce merge conflicts when adding new blocks. (by - @atheriel) - -# i3status-rust 0.10.0 - -* First tagged release. diff --git a/README.md b/README.md deleted file mode 100644 index 11c4f512d5..0000000000 --- a/README.md +++ /dev/null @@ -1,136 +0,0 @@ -# i3status-rust - -![demo1](img/themes/solarized-dark.png) - -`i3status-rs` is a feature-rich and resource-friendly replacement for i3status, written in pure Rust. It provides a way to display "blocks" of system information (time, battery status, volume, etc) on bars that support the [i3bar protocol](https://i3wm.org/docs/i3bar-protocol.html). - -## Install - -Install `i3status-rs` from one of the packages below: - -[![Packaging status](https://repology.org/badge/vertical-allrepos/i3status-rust.svg?columns=5&minversion=0.30.0&exclude_unsupported=1)](https://repology.org/project/i3status-rust/versions) - -* For Fedora/CentOS, you can install from a [community supported](https://github.com/greshake/i3status-rust/issues/2109#issuecomment-2616638134) [COPR](https://copr.fedorainfracloud.org/coprs/alternateved/i3status-rust/). - -* For NixOS, you can also use [Home Manager](https://github.com/nix-community/home-manager): `programs.i3status-rust.enable = true` [see available options](https://nix-community.github.io/home-manager/options.xhtml#opt-programs.i3status-rust.enable) - -* **NOTE:** Installation via `cargo` is not supported. - -Otherwise refer to [manual install](doc/manual_install.md) docs. - -## Configuration - -After installing `i3status-rust`, edit the [example configuration](examples/config.toml) to your liking. -The default location is `$XDG_CONFIG_HOME/i3status-rust/config.toml`. - -There are some optional global configuration variables, defined either at the top-level or in a [TOML table](https://github.com/toml-lang/toml/blob/main/toml.md#table). - -`[icons]` table: -Key | Description | Default -----|-------------|---------- -`icons` | The [icon set](doc/themes.md#available-icon-sets) that should be used. | `"none"` -`[icons.icons_overrides]` | Refer to `Themes and Icons` below. | None - -`[theme]` table: -Key | Description | Default -----|-------------|---------- -`theme` | The [theme](doc/themes.md#available-themes) that should be used. | `"plain"` -`[theme.theme_overrides]` | Refer to `Themes and Icons` below. | None - -Global variables: -Key | Description | Default -----|-------------|---------- -`icons_format` | A string to customise the appearance of each icon. Can be used to edit icons' spacing or specify a font that will be applied only to icons via pango markup. For example, `" {icon} "`. | `" {icon} "` -`invert_scrolling` | Whether to invert the direction of scrolling, useful for touchpad users. | `false` -`error_format` | A string to customise how block errors are displayed. See below for available placeholders. | `"$short_error_message\|X"` -`error_fullscreen_format` | A string to customise how block errors are displayed when clicked. See below for available placeholders. | `"$full_error_message"` - -Available `error_format` and `error_fullscreen_format` placeholders: - -Placeholder | Value ---------------------|------ -full_error_message | The full error message -short_error_message | The short error message, if available - -### Further documentation - -#### Latest release - -- [Blocks](https://docs.rs/i3status-rs/latest/i3status_rs/blocks/index.html) -- [Formatting](https://docs.rs/i3status-rs/latest/i3status_rs/formatting/index.html) -- [Themes and Icons](https://github.com/greshake/i3status-rust/blob/v0.32.0/doc/themes.md) -- [Geolocator](https://docs.rs/i3status-rs/latest/i3status_rs/geolocator/index.html) - -#### Master - -- [Blocks](https://greshake.github.io/i3status-rust/i3status_rs/blocks/index.html) -- [Formatting](https://greshake.github.io/i3status-rust/i3status_rs/formatting/index.html) -- [Themes and Icons](doc/themes.md) -- [Geolocator](https://greshake.github.io/i3status-rust/i3status_rs/geolocator/index.html) - -## Integrate it into i3/sway - -Next, edit your bar configuration to use `i3status-rust`. For example: - -```text -bar { - font pango:DejaVu Sans Mono, FontAwesome 12 - position top - status_command path/to/i3status-rs path/to/your/config.toml - colors { - separator #666666 - background #222222 - statusline #dddddd - focused_workspace #0088CC #0088CC #ffffff - active_workspace #333333 #333333 #ffffff - inactive_workspace #333333 #333333 #888888 - urgent_workspace #2f343a #900000 #ffffff - } -} -``` - -In order to use the built-in support for the Font Awesome icon set, you will need to include it in the `font` parameter, as above. Check to make sure that "FontAwesome" will correctly identify the font by using `fc-match`, e.g. - -```shell -$ fc-match FontAwesome -fontawesome-webfont.ttf: "FontAwesome" "Regular" -``` - -Note that the name of the Font Awesome font may have changed in version 5 or above. -You can use `fc-list` to see the names of your available Awesome Fonts. - -```shell -$ fc-list | grep -i awesome -/usr/share/fonts/TTF/fa-solid-900.ttf: Font Awesome 5 Free,Font Awesome 5 Free Solid:style=Solid -/usr/share/fonts/TTF/fa-regular-400.ttf: Font Awesome 5 Free,Font Awesome 5 Free Regular:style=Regular -``` - -In this example, you have to use `Font Awesome 5 Free` instead of the `FontAwesome 12` in the example configuration above. -You can verify the name again using `fc-match` - -See [#130](https://github.com/greshake/i3status-rust/issues/130) for further discussion. - -Finally, reload i3: `i3 reload`. - -## Behavior - -Each block has a `State` that defines its colors: one of "Idle", "Info", "Good", "Warning", "Critical" or "Error". The state is determined by the logic in each block, for example, the Music block state is "Info" when there is an active player. - -When the state is "Error", a short error will be displayed in the block. The full message can be toggled by clicking on the block (overrides any click actions defined in the config). The block will be restarted after `error_interval` has elapsed. - -i3bar has a "power savings" feature that pauses the bar via SIGSTOP when it is hidden or obscured by a fullscreen container. If this causes [issues](https://github.com/i3/i3/issues/4110) with your bar, try running i3status-rs with the `--never-stop` argument, which changes the signal sent by i3 from SIGSTOP to SIGCONT. - -In addition to the per-block `signal` config option, i3status-rs can be signalled to force an update of all blocks by sending it the SIGUSR1 signal. It can also be restarted in place (useful for testing changes to the config file) by sending it the SIGUSR2 signal. - -## Debugging - -Run `i3status-rust` in a terminal to check the JSON it is outputting. -In addition, some blocks have debug logs that can be enabled like so: `RUST_LOG=block=debug i3status-rs` where "block" is the block name. - -## Contributing - -We welcome new contributors! Take a gander at [CONTRIBUTING.md](CONTRIBUTING.md). - -## License - -This project is licensed under the GPLv3. See the [LICENSE](LICENSE) file for details. diff --git a/_typos.toml b/_typos.toml deleted file mode 100644 index cad317c439..0000000000 --- a/_typos.toml +++ /dev/null @@ -1,27 +0,0 @@ -# With a focus on spell checking source code, most text will be in the form of -# identifiers that are made up of words conjoined via snake_case, CamelCase, -# etc. A typo at the word level might not be a typo as part of an identifier, -# so identifiers get checked and, if not in a dictionary, will then be split -# into words to be checked. - -[files] -extend-exclude = [ - "cspell.yaml", - "target/", -] - -# Identifiers are defined using unicode's XID_Continue which includes [a-zA-Z0-9_]. -[default.extend-identifiers] -hellow554 = "hellow554" - -# Words are split from identifiers on case changes as well as breaks in [a-zA-Z] -# with a special case to handle acronyms. For example, First10HTMLTokens -# would be split as first, html, tokens. -[default.extend-words] -# ue is used in a test to make sure that 'val ue' was parsed correctly -ue = "ue" -# BA is part of a example mac address that is tripping up typos -BA = "BA" -# typos thinks this should be "sought" -seeked = "seeked" - diff --git a/build.rs b/build.rs deleted file mode 100644 index c89ef3414a..0000000000 --- a/build.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::process::Command; - -fn main() { - let hash = Command::new("git") - .args(["rev-parse", "--short", "HEAD"]) - .env("GIT_CONFIG_GLOBAL", "/dev/null") - .output() - .map(|o| String::from_utf8(o.stdout).unwrap()); - let date = Command::new("git") - .args(["log", "--pretty=format:'%ad'", "-n1", "--date=short"]) - .env("GIT_CONFIG_GLOBAL", "/dev/null") - .output() - .map(|o| String::from_utf8(o.stdout).unwrap()); - if let Ok(hash) = hash - && let Ok(date) = date - { - let ver = format!( - "{} (commit {} {})", - env!("CARGO_PKG_VERSION"), - hash.trim(), - date.trim_matches('\'') - ); - println!("cargo:rustc-env=VERSION={ver}"); - } else { - println!("cargo:rustc-env=VERSION={}", env!("CARGO_PKG_VERSION")); - } -} diff --git a/clippy.toml b/clippy.toml deleted file mode 100644 index 5e30523c24..0000000000 --- a/clippy.toml +++ /dev/null @@ -1 +0,0 @@ -type-complexity-threshold = 300 diff --git a/crates.js b/crates.js new file mode 100644 index 0000000000..059c025c35 --- /dev/null +++ b/crates.js @@ -0,0 +1,2 @@ +window.ALL_CRATES = ["i3status_rs"]; +//{"start":21,"fragment_lengths":[13]} \ No newline at end of file diff --git a/cspell.yaml b/cspell.yaml deleted file mode 100644 index 80a7b3bb26..0000000000 --- a/cspell.yaml +++ /dev/null @@ -1,201 +0,0 @@ -version: "0.2" -language: en-US, en-GB -allowCompoundWords: true -ignorePaths: - - _typos.toml - - target/ - - src/blocks/weather/met_no_legends.json -ignoreRegExpList: - # Ignore usernames that are @'d - - /@[\w-]*/g - # Ignore names in zbus::proxy - - /#\[zbus::proxy\([\w\s=\.",\/]*\)\]/gm - # Ignore unicode characters - - /(\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8})/g -words: - - aarch - - alacritty - - alphanum - - alsa - - alsactl - - alsamixer - - amixer - - apcupsd - - aplay - - armv - - arphrd - - backon - - BCHARGE - - bluez - - bokmaal - - bugz - - busctl - - caldav - - ccache - - chrono - - clippy - - CLOEXEC - - cmap - - conn - - consts - - conv - - COPR - - dbpath - - dbus - - ddcci - - ddcutil - - devel - - dmenu - - docsrs - - dtolnay - - dunst - - execvp - - favg - - fcntl - - flurr - - fmax - - fmin - - getaddr - - getenv - - gibi - - giga - - Greshake - - gruvbox - - gsettings - - HHMM - - horiz - - hypot - - ibus - - icalendar - - iface - - ifaces - - ifaddrmsg - - ifinfomsg - - ifla - - ifname - - imgbb - - indx - - inet - - inotify - - iowait - - ipapi - - itertools - - iwctl - - kbdd - - kbddbus - - kibi - - kmon - - libc - - liquidctl - - locid - - macchiato - - maildir - - mebi - - metno - - milli - - mogrify - - mpDris2 - - mpris - - mullvad - - Mupi - - neli - - NLMSG - - nlmsghdr - - nmtui - - noconfirm - - NOFLAGS - - noheader - - NOPASSWD - - noprogressbar - - notif - - Noto - - NZXT - - ONBATT - - OPER - - operstate - - pactl - - pango - - pavucontrol - - percents - - pikaur - - pkce - - pkill - - playerctl - - playerctld - - pomodoros - - posix - - powf - - powi - - pybugz - - recip - - repr - - reqwest - - resolv - - retval - - rofi - - rofication - - rsplit - - rtattr - - rtattrs - - rtmsg - - rtnetlink - - rtnl - - rtprot - - rustc - - rustfmt - - rustify - - scontrols - - scrot - - serde - - setxkbmap - - shmem - - SIGRTMAX - - SIGRTMIN - - SIGUSR - - softirq - - srcery - - srcpkgcache - - ssid - - statusrs - - statvfs - - stdbuf - - strftime - - swayipc - - swaync - - sysfs - - tebi - - tera - - tzname - - tzset - - udev - - uevent - - unshift - - unspec - - upower - - uptodate - - vals - - VCALENDAR - - VEVENT - - wayrs - - wdoll - - WLAN - - wlroots - - wlsunset - - wofi - - wttr - - xbps - - xclip - - xcolors - - xesam - - xkbswitch - - xkbevent - - XKCD - - xrandr - - xresources - - xtask - - zbus - - zram - - zswap - - Zswapped - - zvariant - - zwlr diff --git a/doc/manual_install.md b/doc/manual_install.md deleted file mode 100644 index 36f08e47f6..0000000000 --- a/doc/manual_install.md +++ /dev/null @@ -1,34 +0,0 @@ -## Requirements for Compilation - -- `rustc`, `cargo` and `gcc` -- `libssl-dev` -- `libsensors-dev` -- `libpulse-dev` (required for `pulseaudio` driver of sound block, compile with `--no-default-features` to opt-out) -- `libnotmuch-dev` (required for optional `notmuch` block, compile with `--features notmuch` to opt-in) -- `libpipewire-0.3-dev` and `clang` (required for optional `pipewire` block, compile with `--features pipewire` to opt-in) - -Compilation is only tested with very recent stable versions of `rustc`. If you use a distro with older Rust packages, consider using [rustup](https://rustup.rs/) to install a newer toolchain. - -On systems using alternative (non-glibc) C standard libraries like `musl`, `cargo` must be configured to not link the libc statically. Otherwise, blocks needing to link to system libraries like `temperature`, `sound` (for pulseaudio) and maybe others will not be usable due to segmentation faults. To configure `cargo` for this, just add this to your `~/.cargo/config.toml`: - -```toml -[build] -rustflags = ["-C", "target-feature=-crt-static"] -``` - -## Build and Install from Source - -```shell -$ git clone https://github.com/greshake/i3status-rust -$ cd i3status-rust -$ cargo install --path . --locked -$ ./install.sh -``` - -By default, this will install the binary to `~/.cargo/bin/i3status-rs`, runtime files to `~/.local/share/i3status-rust` and manpage to `~/.local/share/man/man1/i3status-rs.1` - -## Packaging - -Runtime files from `files` directory are expected to be installed in `/usr/share/i3status-rust` or `$XDG_DATA_HOME/i3status-rust`. - -Manual page at `man/i3status-rs.1` can be generated with `cargo xtask generate-manpage` (`pandoc` binary is required). diff --git a/doc/themes.md b/doc/themes.md deleted file mode 100644 index 8d229899f0..0000000000 --- a/doc/themes.md +++ /dev/null @@ -1,161 +0,0 @@ -## Choosing your theme and icon set -To use a theme or icon set other than the default, add them to your configuration file like so: -```toml -[theme] -theme = "solarized-dark" -[icons] -icons = "awesome6" -``` -Both the theme and icon set can be loaded from a separate file. -```toml -[theme] -theme = "" -[icons] -icons = "" -``` -where `` can be either a filename or a full path and will be checked in this order: - -1. If full absolute path given, then use it as is: `/home/foo/custom_theme.toml` -2. If filename given, e.g. "custom_theme.toml", then first check `$XDG_CONFIG_HOME/i3status-rust/themes` -3. Then look for it in `$XDG_DATA_HOME/i3status-rust/themes` -4. Otherwise look for it in `/usr/share/i3status-rust/themes` - -Notes: -- In case with icon sets, the file should be in the `icons` subdirectory instead of `themes`. -- You can omit the `.toml` extension while specifying `file` parameters. -- All the predefined themes are provided as files, so you use them as examples of how to write your own themes/icon sets. - -# Available themes - -Note: screenshots were generated using [this config](../gen-screenshots/screenshot_config.toml) with [this swaybar config](../gen-screenshots/swayconfig_i3rs). - -* `plain` (default) -![plain](../img/themes/plain.png) -* `solarized-dark` -![solarized-dark](../img/themes/solarized-dark.png) -* `solarized-light` -![solarized-light](../img/themes/solarized-light.png) -* `slick` -![slick](../img/themes/slick.png) -* `modern` -![modern](../img/themes/modern.png) -* `bad-wolf` -![bad-wolf](../img/themes/bad-wolf.png) -* `gruvbox-light` -![gruvbox-light](../img/themes/gruvbox-light.png) -* `gruvbox-dark` -![gruvbox-dark](../img/themes/gruvbox-dark.png) -* `space-villain` -![space-villain](../img/themes/space-villain.png) -* `native` (like plain with no background and native separators) -![native](../img/themes/native.png) -* `semi-native` (like native but with background) -![semi-native](../img/themes/semi-native.png) -* `nord-dark` (polar night) -![nord-dark](../img/themes/nord-dark.png) -* `dracula` -![dracula](../img/themes/dracula.png) -* `srcery` -![srcery](../img/themes/srcery.png) -* `ctp-frappe` -![ctp-frappe](../img/themes/ctp-frappe.png) -* `ctp-latte` -![ctp-latte](../img/themes/ctp-latte.png) -* `ctp-macchiato` -![ctp-macchiato](../img/themes/ctp-macchiato.png) -* `ctp-mocha` -![ctp-mocha](../img/themes/ctp-mocha.png) - -# Available icon sets - -* `none` (default. Uses text labels instead of icons) -* `awesome4` (Font Awesome 4.x) -* `awesome5` (Font Awesome 5.x) -* `awesome6` (Font Awesome 6.x) -* `emoji` -* `material` -* `material-nf` (Any font from Nerd Fonts collection) - - **Note**: In order to use the material icon set, you need a patched material icons font which can be found [here](https://gist.github.com/draoncc/3c20d8d4262892ccd2e227eefeafa8ef/raw/3e6e12c213fba1ec28aaa26430c3606874754c30/MaterialIcons-Regular-for-inline.ttf). Make sure to pass it in your i3 configuration bar block. - -## Overriding themes and icon sets - -Create a block in the configuration called `theme` or `icons` like so: - -```toml -[theme] -theme = "solarized-dark" -[theme.overrides] -# Example: redefine `idle` colors -idle_bg = "#123456" -idle_fg = "#abcdef" -# Example: swap `good` and `warning` colors -good_fg = { link = "warning_fg" } -good_bg = { link = "warning_bg" } -warning_fg = { link = "good_fg" } -warning_bg = { link = "good_bg" } - -[icons] -icons = "awesome6" -[icons.overrides] -bat = [ - "| |", - "|¼|", - "|½|", - "|¾|", - "|X|", -] -bat_charging = "|^|" -``` - -Besides global overrides you may also use per-block overrides using the `theme_overrides`, `icons_overrides` and `icons_format` options available for all blocks. -For example: -```toml -[[block]] -block = "cpu" -icons_format = "{icon}" -[block.theme_overrides] -idle_bg = "#123456" -idle_fg = "#abcdef" -[block.icons_overrides] -cpu_boost_on = "ON" -cpu_boost_off = "OFF" -``` - -# Available theme overrides - -All `bg` and `fg` overrides are either - -* html hex color codes like `#000000` or `#789ABC`; a fourth byte for alpha (like `#acbdef42`) works on some systems. `00` is transparent, `FF` is opaque, or -* a reference to another override, e.g., `{ link = "idle-bg" }`, or -* a reference to a color name defined in `~/.Xresources`, e.g., `x:background` looks for a line like `*background: #aabbcc` in `~/.Xresources` (see also [.Xresources](https://wiki.debian.org/Xresources)). - -The tints are added to every second block counting from the right. They will therefore always brighten the block and never darken it. The alpha channel, if it works, can also be alternated in the same way. - -Feel free to take a look at the provided color schemes for reference. - -* `idle_bg` -* `idle_fg` -* `good_bg` -* `good_fg` -* `warning_bg` -* `warning_fg` -* `critical_bg` -* `critical_fg` -* `info_bg` -* `info_fg` -* `alternating_tint_bg` -* `alternating_tint_fg` -* `separator_bg` -* `separator_fg` -* `separator` -* `end_separator` -* `start_separator` - -# Available icon overrides - -These can be directly set to a string containing the desired unicode codepoint(s) or use a TOML escape sequence like `"\uf0f3"` for up to 4-nibble codepoints and `"\U0001f312"` for up to 8-nibble codepoints. - -You can find the codepoints in the documentation of the icon font you're using. - -Refer to individual block's documentation for a list of used icons or [provided icon sets](../files/icons) for a complete list of icons. diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index af7bbf4f2c..0000000000 --- a/examples/README.md +++ /dev/null @@ -1,306 +0,0 @@ -Custom Block Examples -======== - -This is a collection of more or less useful `custom` or `custom_dbus` block configs for use with `i3status-rust`. - -Most of these are specifically designed for `sh` or compatible shells. Incompatible shells like `fish` might not work, in which case a `shell = "sh"` should be added to the block. - -Feel free to add to the list below by sending a PR. Additional scripts can be added to the `scripts` subdirectory. Using blocks with external scripts naturally requires adjusting the script paths to your system. - -## Custom Blocks - -- [Bugs assigned to user](#bugs-assigned-to-user) -- [Capslock status indicator](#capslock-status-indicator) -- [Hostname](#hostname) -- [HTTP Status Code](#http-status-code) -- [Intel GPU Usage](#intel-gpu-usage) -- [Kernel](#kernel) -- [Liquid cooling system status](#Liquid-cooling-system-status) -- [Maintained by user and outdated](#maintained-by-user-and-outdated) -- [Monitors](#monitors) -- [Ping/RTT](#pingrtt) -- [Public IP](#public-ip) -- [Screenshot](#screenshot) -- [Switch GTK Theme](#switch-gtk-theme) -- [System (Suspend/Shutdown/Reboot)](#system-suspendshutdownreboot) -- [User](#user) -- [XKCD](#xkcd) -- [Spotify TUI](#spt) -- [Nextcloud](#nextcloud) -- [Wttr.in](#wttrin) - -### Bugs assigned to user - -Display number of unresolved bugs assigned to user in Bugzilla using `pybugz`. - -```toml -[[block]] -block = "custom" -command = "echo 🐛 $(bugz --quiet --skip-auth search --assigned-to user@example.com | wc -l)" -interval = 3600 -``` - -### Capslock status indicator - -Displays 'CAPS' when Capslock is active, hidden otherwise. -Requires this binding to be added to your sway config: -`bindsym --release Caps_Lock exec pkill -SIGRTMIN+11 i3status-rs` - -```toml -[[block]] -block = "custom" -signal = 11 -command = ''' if [[ "$(cat /sys/class/leds/input*::capslock/brightness | sort --numeric-sort --reverse | uniq --count | awk '{print $2}' | head --lines 1)" -eq 1 ]]; then printf "CAPS"; fi ''' -shell = "bash" -format = " $text" -hide_when_empty = true -``` - -### Hostname - -Show Hostname - -```toml -[[block]] -block = "custom" -command = "cat /etc/hostname" -interval = "once" -``` - -### User - -Show current user - -```toml -[[block]] -block = "custom" -command = "whoami" -interval = "once" -``` - -### Kernel - -Show current kernel and release - -```toml -[[block]] -block = "custom" -command = "echo `uname` `uname -r | tr - . | cut -d. -f1-2`" -interval = "once" -``` - -### Public IP - -Show public IP. Use `curl -4` or `curl -6` to get IPv4 or IPv6 respectively. - -```toml -[[block]] -block = "custom" -command = "echo '\uf0ac ' `curl bot.whatismyipaddress.com`" # assumes fontawesome icons -interval = 60 -``` - -### HTTP Status Code - -Periodically check http status code for a given URL and set block status depending on the HTTP status code. Requires `curl` and [`http-status-code.sh`](scripts/http-status-code.sh). - -```toml -[[block]] -block = "custom" -json = true -command = "~/Projects/i3status-rust/examples/scripts/http-status-code.sh https://example.com" -interval = 60 -``` - -### Ping/RTT - -Check ping periodically. `-c4` means average over 4 pings. Update on click. - -```toml -[[block]] -block = "custom" -json = true -command = ''' echo "{\"icon\":\"ping\",\"text\":\"`ping -c4 1.1.1.1 | tail -n1 | cut -d'/' -f5`\"}" ''' -interval = 60 -[[block.click]] -button = "left" -cmd = "" -``` - -### System (Suspend/Shutdown/Reboot) - -Opens a `dmenu`/`rofi` menu to choose between suspend/poweroff/reboot. Uses `systemd`. - -```toml -[[block]] -block = "custom" -command = "echo \uf011" # assumes fontawesome icons -interval = "once" -[[block.click]] -button = "left" -cmd = "systemctl `echo -e 'suspend\npoweroff\nreboot' | dmenu`" -``` - -### XKCD - -Opens a random xkcd comic in the default browser. Requires working `xdg-open`. - -```toml -[[block]] -block = "custom" -command = "echo xkcd" -interval = "once" -[[block.click]] -button = "left" -cmd = "xdg-open 'https://c.xkcd.com/random/comic/'" -``` - -### Screenshot - -Take a screenshot from an interactively selected area (requires `scrot`), save it, fix it up (requires `imagemagick`) and copy to clipboard (requires `xclip`). - -Optionally upload to imgbb and copy public link (requires `curl`, `jq`, `xclip`). See [`scripts/screenshot.sh`](scripts/screenshot.sh) for details and config. - -```toml -[[block]] -block = "custom" -command = "echo \uf030" # assumes fontawesome icons -interval = "once" -[[block.click]] -button = "left" -cmd = "~/Projects/i3status-rust/examples/scripts/screenshot.sh" -``` - -### Maintained by user and outdated - -List number of packages in repository `REPO` maintained by a given maintainer with newer upstream release. Relies on `jq`. - -```toml -[[block]] -block = "custom" -command = "echo 🦕 $(curl -s 'https://repology.org/api/v1/projects/?inrepo=&maintainer=user@example.com&outdated=1' | jq '. | length')" -interval = 3600 -``` - -### Monitors - -List connected monitors by name, main monitor marked by `*`. Update on signal `SIGRTMIN+4`, e.g. for updating on `udev` events for monitor changes. - -```toml -[[block]] -block = "custom" -command = "xrandr --listmonitors | tail -n+2 | tr '+' ' ' | cut -d' ' -f 4 | tr '\n' ' '" -interval = "once" -signal = 4 -``` - -### Intel GPU Usage - -Shows usage of the Render/3D pipeline of intel GPUs in percent. Requires `intel_gpu_top` installed and added to `/etc/sudoers` with `NOPASSWD` (Instructions see [here](https://unix.stackexchange.com/questions/18830/how-to-run-a-specific-program-as-root-without-a-password-prompt)). Video decode pipeline would be `awk '{print $14 "%"}'`. - -```toml -[[block]] -block = "custom" -command = ''' sudo intel_gpu_top -l | head -n4 | tail -n1 | awk '{print $8 "%"}' ''' -interval = 5 -``` - -### Switch GTK Theme - -Switch between a dark and a light GTK theme using `gsettings`. - -```toml -[[block]] -block = "custom" -cycle = ["gsettings set org.gnome.desktop.interface gtk-theme Adapta; echo \U0001f311", "gsettings set org.gnome.desktop.interface gtk-theme None; echo \U0001f315"] -interval = "once" -[[block.click]] -button = "left" -action = "cycle" -``` - -And if you're feeling adventurous, here's a super sketchy version that also adjusts your `i3status-rs` and `i3bar` color scheme as well. The [`theme-switch.sh`](scripts/theme-switch.sh) script will likely need a lot of adjustments for your system before this works: - -```toml -[[block]] -block = "custom" -command = "cat ~/.config/i3status-rust/mode.txt" -interval = "once" -[[block.click]] -button = "left" -cmd = "~/Projects/i3status-rust/examples/scripts/theme-switch.sh" -``` - -### Pi-hole status - -Displays the status of Pi-hole server and number of ads blocked. Requires `curl`, `jq` and `xdg-open`. - -```toml -[[block]] -block = "custom" -command = ''' curl --max-time 3 --silent 'http://pi.hole/admin/api.php?summary' | jq '{icon:"pi_hole", state: "\(.status | sub("enabled";"Good") | sub("disabled";"Warning"))", text: "\(.status | sub("enabled";"Up") | sub("disabled";"Down")) \(.ads_blocked_today)"}' ''' -json = true -interval = 180 -[[block.click]] -button = "left" -cmd = "xdg-open http://pi.hole" -``` - -**Note:** -Replace `http://pi.hole` with a correct url to your Pi-hole instance. Define icon override for `pi_hole`. -```toml -[icons.overrides] -pi_hole = "" -``` - -### Liquid cooling system status - -Displays liquid temperature (celsius), fan and pump RPM. Requires: `liquidctl`. - -![image](https://user-images.githubusercontent.com/20397027/118128928-7dfe8d00-b436-11eb-96b1-b40f62676933.png) - -_Example for NZXT Kraken X series:_ -```toml -[[block]] -block = "custom" -command = ''' liquidctl --match 'NZXT Kraken X' status | grep -e speed -e temp | awk '{printf "%s ", substr($0, 28,4)}' | awk '{printf " %s %s /%s", substr($0,0,4), substr($0,5,5), substr($0,10,6)}' ''' -interval = 5 -``` - -### Spotify TUI - -Display song with [Spotify TUI](https://github.com/Rigellute/spotify-tui) - -```toml -[[block]] -block = "custom" -command = "spt playback --format" -interval = 3 -[[block.click]] -button = "left" -cmd = "spt playback --toggle" -``` - -### Nextcloud - -Show Nextcloud GUI (if `nextcloud` is already running in background) - -```toml -[[block]] -block = "custom" -command = "echo \uf0c2 Nextcloud" # icon is for nerdfont, replace if other -[[block.click]] -button = "left" -cmd = "nextcloud" -``` - -### Wttr.in - -Minimalistic weather block which uses [wttr.in](https://github.com/chubin/wttr.in) - -```toml -[[block]] -block = "custom" -command = "sed 's/ //' <(curl 'https://wttr.in/?format=1' -s)" -interval = 600 -``` diff --git a/examples/config.toml b/examples/config.toml deleted file mode 100644 index e913363677..0000000000 --- a/examples/config.toml +++ /dev/null @@ -1,45 +0,0 @@ -icons_format = "{icon}" - -[theme] -theme = "solarized-dark" -[theme.overrides] -idle_bg = "#123456" -idle_fg = "#abcdef" - -[icons] -icons = "awesome4" -[icons.overrides] -bat = ["|E|", "|_|", "|=|", "|F|"] -bat_charging = "|^| " - -[[block]] -block = "cpu" -info_cpu = 20 -warning_cpu = 50 -critical_cpu = 90 - -[[block]] -block = "disk_space" -path = "/" -info_type = "available" -alert_unit = "GB" -interval = 20 -warning = 20.0 -alert = 10.0 -format = " $icon root: $available.eng(w:2) " - -[[block]] -block = "memory" -format = " $icon $mem_total_used_percents.eng(w:2) " -format_alt = " $icon_swap $swap_used_percents.eng(w:2) " - -[[block]] -block = "sound" -[[block.click]] -button = "left" -cmd = "pavucontrol" - -[[block]] -block = "time" -interval = 5 -format = " $timestamp.datetime(f:'%a %d/%m %R') " diff --git a/examples/scripts/http-status-code.sh b/examples/scripts/http-status-code.sh deleted file mode 100755 index ca4e78f130..0000000000 --- a/examples/scripts/http-status-code.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -RES=`curl -I $1 | head -n1 | cut -d' ' -f2` - -case $RES in -1*) - STATE="Info";; -2*) - STATE="Good";; -3*) - STATE="Warning";; -*) - STATE="Critical";; -esac - -echo "{\"icon\":\"cogs\",\"state\":\"${STATE}\",\"text\":\"${RES}\"}" diff --git a/examples/scripts/screenshot.sh b/examples/scripts/screenshot.sh deleted file mode 100755 index 582e19e92d..0000000000 --- a/examples/scripts/screenshot.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -mkdir -p ~/Pictures - -# open screenshot tool -scrot -s -q 100 -o ~/Pictures/screenshot.png - -# play a "Camera shutter" sound -paplay /usr/share/sounds/freedesktop/stereo/screen-capture.oga & - -# remove outer pixel rows because they sometimes include the capture border -mogrify -crop +1+1 -crop -1-1 +repage ~/Pictures/screenshot.png - -# copy to clipboard -xclip -sel clip -t image/png -i ~/Pictures/screenshot.png - -# Alternatively: upload to imgbb, keep for 1h -#RES=`curl --location --request POST -F "image=@$HOME/Pictures/screenshot.png" "https://api.imgbb.com/1/upload?expiration=3600&key=YOUR_API_KEY_HERE"` -#echo $RES | jq -r '.data.url' | xclip -sel clip -i diff --git a/examples/scripts/theme-switch.sh b/examples/scripts/theme-switch.sh deleted file mode 100755 index 4fa5222380..0000000000 --- a/examples/scripts/theme-switch.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash - -I3CONF="$HOME/.config/i3/config" -DIR="$HOME/.config/i3status-rust" - -#notify-send "Switching theme" - -MODE=`cat $DIR/mode.txt` - -if [[ $MODE == light* ]] -then - - #notify-send "Switching to dark" - - # Set GTK theme - gsettings set org.gnome.desktop.interface gtk-theme Adapta - - - # Reconfigure i3status-rust - sed -i 's/^name = "solarized-light"$/name = "slick"/' $DIR/config.toml - - # Reconfigure i3 - sed -i 's/background #DDDDDD/background #424242/' $I3CONF - - sleep 0.2 - - pkill -SIGUSR2 i3status-rs - i3-msg reload > /dev/null - - echo dark | tee $DIR/mode.txt - -elif [[ $MODE == dark* ]] -then - - #notify-send "Switching to light" - - # Set GTK theme to an invalid one, which is usually light - gsettings set org.gnome.desktop.interface gtk-theme None - - # Reconfigure i3status-rust - sed -i 's/^name = "slick"$/name = "solarized-light"/' $DIR/config.toml - - # Reconfigure i3 - sed -i 's/background #424242/background #DDDDDD/' $I3CONF - - sleep 0.2 - - pkill -SIGUSR2 i3status-rs - i3-msg reload > /dev/null - - echo light | tee $DIR/mode.txt - - # This needs to be done a bit later for some reason - sleep 1 - - # Set GTK theme to an invalid one, which is usually light - gsettings set org.gnome.desktop.interface gtk-theme None - -fi diff --git a/files/icons/awesome4.toml b/files/icons/awesome4.toml deleted file mode 100644 index 5f265efa87..0000000000 --- a/files/icons/awesome4.toml +++ /dev/null @@ -1,93 +0,0 @@ -# FontAwesome 4: https://fontawesome.com/v4.7.0/cheatsheet/ -backlight = [ - "\U0001f315", - "\U0001f314", - "\U0001f313", - "\U0001f312", - "\U0001f311", -] -bat_charging = "\uf1e6" # fa-plug -bat = [ - "\uf244", # fa-battery-empty - "\uf243", # fa-battery-quarter - "\uf242", # fa-battery-half - "\uf241", # fa-battery-three-quarters - "\uf240", # fa-battery-full -] -bat_not_available = "\uf244" # fa-battery-empty -bell = "\uf0f3" # fa-bell -bell-slash = "\uf1f7" # fa-bell-slash-o -bluetooth = "\uf294" # fa-bluetooth-b -calendar = "\uf073" # fa-calendar -cogs = "\uf085" # fa-cogs -cpu = "\uf0e4" # fa-dashboard -cpu_boost_off = "\uf204" # fa-toggle-off -cpu_boost_on = "\uf205" # fa-toggle-on -disk_drive = "\uf0a0" # fa-hdd-o -docker = "\uf21a" # fa-ship -github = "\uf09b" # fa-github -gpu = "\uf26c" # fa-television -headphones = "\uf025" # fa-headphones -hueshift = "\uf0eb" # fa-lightbulb-o -joystick = "\uf11b" # fa-gamepad -keyboard = "\uf11c" # fa-keyboard-o -mail = "\uf0e0" # fa-envelope -memory_mem = "\uf2db" # fa-microchip -memory_swap = "\uf0a0" # fa-hdd-o -mouse = "\uf245" # fa-mouse-pointer -music = "\uf001" # fa-music -music_next = "\uf051" # fa-forward-step -music_pause = "\uf04c" # fa-pause -music_play = "\uf04b" # fa-play -music_prev = "\uf048" # fa-backward-step -net_bridge = "\uf0e8" # fa-sitemap -net_down = "\u2b07" -net_loopback = "LO" -net_modem = "\uf095" # fa-phone -net_cellular = "\uf012" -net_up = "\u2b06" -net_vpn = "\uf023" # fa-lock -net_wired = "\uf0ac" # fa-globe -net_wireless = "\uf1eb" # fa-wifi -notification = "\uf0a2" # fa-bell-o -phone = "\uf10b" # fa-mobile -phone_disconnected = "\U0001f4f5" # https://unicode-table.com/en/1F4F5/ -ping = "\u21ba" -pomodoro = "\U0001f345" -pomodoro_break = "\uf0f4" # fa-coffee -pomodoro_paused = "\uf04c" # fa-pause -pomodoro_started = "\uf04b" # fa-play -pomodoro_stopped = "\uf04d" # fa-stop -resolution = "\uf096" # fa-square-o -scratchpad = "\uf2d2" # fa-window-restore -tasks = "\uf0ae" # fa-tasks -tea = "\uf0f4" # fa-coffee -thermometer = "\uf2c8" # fa-thermometer-3 -time = "\uf017" # fa-clock-o -toggle_off = "\uf204" # fa-toggle-off -toggle_on = "\uf205" # fa-toggle-on -unknown = "\uf128" # fa-question -update = "\uf062" # fa-arrow-up -uptime = "\uf017" # fa-clock-o -volume = [ - "\uf026", # fa-volume-off - "\uf027", # fa-volume-down - "\uf028", # fa-volume-up -] -volume_muted = "\uf026 \uf00d" -microphone = "\uf130" # fa-microphone -microphone_muted = "\uf131" # fa-microphone-slash -weather_clouds = "\uf0c2" # fa-cloud -weather_clouds_night = "\uf0c2" # fa-cloud -weather_default = "\uf0c2" # fa-cloud -weather_fog = "\uf0c2" # fa-cloud -weather_fog_night = "\uf0c2" # fa-cloud -weather_moon = "\uf186" # fa-moon-o -weather_rain = "\uf043" # fa-tint -weather_rain_night = "\uf043" # fa-tint -weather_snow = "\uf2dc" # fa-snowflake-o -weather_sun = "\uf185" # fa-sun-o -weather_thunder = "\uf0e7" # fa-bolt -weather_thunder_night = "\uf0e7" # fa-bolt -webcam = "\uf03d" # fa-video-camera -xrandr = "\uf26c" # fa-television diff --git a/files/icons/awesome5.toml b/files/icons/awesome5.toml deleted file mode 100644 index 2a878f4a7b..0000000000 --- a/files/icons/awesome5.toml +++ /dev/null @@ -1,93 +0,0 @@ -# FontAwesome 5: https://fontawesome.com/icons?d=gallery&p=2&m=free -backlight = [ - "\U0001f315", - "\U0001f314", - "\U0001f313", - "\U0001f312", - "\U0001f311", -] -bat_charging = "\uf1e6" -bat_not_available = "\uf244" -bat = [ - "\uf244", - "\uf243", - "\uf242", - "\uf241", - "\uf240", -] -bell = "\uf0f3" -bell-slash = "\uf1f6" -bluetooth = "\uf294" -calendar = "\uf073" -cogs = "\uf085" -cpu = "\uf3fd" # fa-tachometer-alt (other variations of this icon are not free) -cpu_boost_on = "\uf205" -cpu_boost_off = "\uf204" -disk_drive = "\uf0a0" -docker = "\uf21a" -github = "\uf09b" -gpu = "\uf26c" -headphones = "\uf025" -hueshift = "\uf0eb" # fa-lightbulb-o -joystick = "\uf11b" -keyboard = "\uf11c" -mail = "\uf0e0" -memory_mem = "\uf2db" -memory_swap = "\uf0a0" -mouse = "\uf245" -music = "\uf001" -music_next = "\uf051" # fa-forward-step -music_pause = "\uf04c" # fa-pause -music_play = "\uf04b" # fa-play -music_prev = "\uf048" # fa-backward-step -net_bridge = "\uf0e8" -net_down = "\uf019" -net_loopback = "LO" -net_modem = "\uf095" -net_cellular = "\uf012" -net_up = "\uf093" -net_vpn = "\uf023" -net_wired = "\uf6ff" -net_wireless = "\uf1eb" -notification = "\uf0f3" -phone = "\uf3cd" -phone_disconnected = "\U0001f4f5" # https://unicode-table.com/en/1F4F5/ -ping = "\uf362" -pomodoro = "\U0001f345" -pomodoro_break = "\uf0f4" # fa-coffee -pomodoro_paused = "\uf04c" # fa-pause -pomodoro_started = "\uf04b" # fa-play -pomodoro_stopped = "\uf04d" # fa-stop -resolution = "\uf096" # fa-square-o -scratchpad = "\uf2d2" # fa-window-restore -tasks = "\uf0ae" -tea = "\uf0f4" -thermometer = "\uf2c8" -time = "\uf017" -toggle_off = "\uf204" -toggle_on = "\uf205" -unknown = "\uf128" -update = "\uf062" -uptime = "\uf2f2" -volume = [ - "\uf026", - "\uf027", - "\uf028", -] -volume_muted = "\uf6a9" -microphone = "\uf3c9" -microphone_muted = "\uf539" -weather_clouds = "\uf0c2" # fa-cloud -weather_default = "\uf0c2" # Cloud symbol as default -weather_clouds_night = "\uf6c3" # fa-cloud-moon -weather_fog = "\uf0c2" # fa-cloud -weather_fog_night = "\uf0c2" # fa-cloud -weather_moon = "\uf186" # fa-moon -weather_rain = "\uf743" # fa-cloud-sun-rain -weather_rain_night = "\uf73c" # fa-cloud-moon-rain -weather_snow = "\uf2dc" # fa-snowflake -weather_sun = "\uf185" # fa-sun -weather_thunder = "\uf0e7" # fa-bolt -weather_thunder_night = "\uf0e7" # fa-bolt -webcam = "\uf03d" # fa-video -xrandr = "\uf26c" diff --git a/files/icons/awesome6.toml b/files/icons/awesome6.toml deleted file mode 100644 index b7b258a6e5..0000000000 --- a/files/icons/awesome6.toml +++ /dev/null @@ -1,97 +0,0 @@ -# FontAwesome 6: https://fontawesome.com/v6/search?m=free -backlight = [ - "\U0001f315", - "\U0001f314", - "\U0001f313", - "\U0001f312", - "\U0001f311", -] -bat_charging = "\uf1e6" -bat_not_available = "\uf244" -bat = [ - "\uf244", - "\uf243", - "\uf242", - "\uf241", - "\uf240", -] -bell = "\uf0f3" -bell-slash = "\uf1f6" -bluetooth = "\uf294" -calendar = "\uf073" -cogs = "\uf085" -cpu = [ # fa-gauge-{min,max} are not free - "\uf624", # fa-gauge - "\uf624", # fa-gauge (2 times so that the gauge will go high at 66% which looks appropriate for the icon fa-gauge-high) - "\uf625", # fa-gauge-high -] -cpu_boost_on = "\uf205" -cpu_boost_off = "\uf204" -disk_drive = "\uf0a0" -docker = "\uf21a" -github = "\uf09b" -gpu = "\uf26c" -headphones = "\uf025" -hueshift = "\uf0eb" # fa-lightbulb-o -joystick = "\uf11b" -keyboard = "\uf11c" -mail = "\uf0e0" -memory_mem = "\uf2db" -memory_swap = "\uf0a0" -mouse = "\uf245" -music = "\uf001" -music_next = "\uf051" # fa-forward-step -music_pause = "\uf04c" # fa-pause -music_play = "\uf04b" # fa-play -music_prev = "\uf048" # fa-backward-step -net_bridge = "\uf0e8" -net_down = "\uf019" -net_loopback = "LO" -net_modem = "\uf095" -net_cellular = "\uf012" -net_up = "\uf093" -net_vpn = "\uf023" -net_wired = "\uf6ff" -net_wireless = "\uf1eb" -notification = "\uf0f3" -phone = "\uf3cd" -phone_disconnected = "\U0001f4f5" # https://unicode-table.com/en/1F4F5/ -ping = "\uf362" -pomodoro = "\U0001f345" -pomodoro_break = "\uf0f4" # fa-coffee -pomodoro_paused = "\uf04c" # fa-pause -pomodoro_started = "\uf04b" # fa-play -pomodoro_stopped = "\uf04d" # fa-stop -resolution = "\uf096" # fa-square-o -scratchpad = "\uf2d2" # fa-window-restore -tasks = "\uf0ae" -tea = "\uf0f4" -thermometer = "\uf2c8" -time = "\uf017" -toggle_off = "\uf204" -toggle_on = "\uf205" -unknown = "\uf128" -update = "\uf062" -uptime = "\uf2f2" -volume = [ - "\uf026", - "\uf027", - "\uf028", -] -volume_muted = "\uf6a9" -microphone = "\uf3c9" -microphone_muted = "\uf539" -weather_clouds = "\uf0c2" # fa-cloud -weather_default = "\uf0c2" # Cloud symbol as default -weather_clouds_night = "\uf6c3" # fa-cloud-moon -weather_fog = "\uf0c2" # fa-cloud -weather_fog_night = "\uf0c2" # fa-cloud -weather_moon = "\uf186" # fa-moon -weather_rain = "\uf743" # fa-cloud-sun-rain -weather_rain_night = "\uf73c" # fa-cloud-moon-rain -weather_snow = "\uf2dc" # fa-snowflake -weather_sun = "\uf185" # fa-sun -weather_thunder = "\uf0e7" # fa-bolt -weather_thunder_night = "\uf0e7" # fa-bolt -webcam = "\uf03d" # fa-video -xrandr = "\uf26c" \ No newline at end of file diff --git a/files/icons/emoji.toml b/files/icons/emoji.toml deleted file mode 100644 index 3a9bdcc1cb..0000000000 --- a/files/icons/emoji.toml +++ /dev/null @@ -1,89 +0,0 @@ -backlight = [ - "🌕", - "🌔", - "🌓", - "🌒", - "🌑", -] -bat_charging = "🔌" -bat = [ - "🪫", - "🔋", -] -bat_not_available = "🪫" -bell = "🔔" -bell-slash = "🔕" -bluetooth = "🔵🦷" -calendar = "📅" -cogs = "⚙️" -cpu = "🤖" -cpu_boost_off = "🐢" -cpu_boost_on = "🐇" -disk_drive = "💽" -docker = "🚢" -github = "🐙🐱" -gpu = "🖥️" -headphones = "🎧" -hueshift = "💡" -joystick = "🎮" -keyboard = "⌨️" -mail = "📨" -memory_mem = "💭" -memory_swap = "💽" -mouse = "🖱️" -music = "🎵" -music_next = "⏭️" -music_pause = "⏸️" -music_play = "▶️" -music_prev = "⏮️" -net_bridge = "🌉" -net_cellular = "📶" -net_down = "⬇️" -net_loopback = "➰🔙" -net_modem = "☎️" -net_up = "⬆️" -net_vpn = "🔒" -net_wired = "🌐" -net_wireless = "🛜" -notification = "🔔" -phone = "📱" -phone_disconnected = "📵" -ping = "🏓" -pomodoro = "🍅" -pomodoro_break = "☕" -pomodoro_paused = "⏸️" -pomodoro_started = "▶️" -pomodoro_stopped = "⏹️" -resolution = "🔳" -scratchpad = "🗔" -tasks = "✅" -tea = "☕" -thermometer = "🌡️" -time = "🕑" -toggle_off = "🔴" -toggle_on = "🟢" -unknown = "❓" -update = "⬆️" -uptime = "🕑" -volume = [ - "🔈", - "🔉", - "🔊", -] -volume_muted = "🔇" -microphone = "🎤🟢" -microphone_muted = "🎤🔴" -weather_clouds = "☁️" -weather_clouds_night = "☁️" -weather_default = "☁️" -weather_fog = "🌁" -weather_fog_night = "🌁" -weather_moon = "🌜" -weather_rain = "🌧️" -weather_rain_night = "🌧️" -weather_snow = "🌨️" -weather_sun = "🌞" -weather_thunder = "🌩️" -weather_thunder_night = "🌩️" -webcam = "🎥" -xrandr = "🖥️" diff --git a/files/icons/material-nf.toml b/files/icons/material-nf.toml deleted file mode 100644 index 4013ba4e52..0000000000 --- a/files/icons/material-nf.toml +++ /dev/null @@ -1,134 +0,0 @@ -# Material from NerdFont -# https://www.nerdfonts.com/cheat-sheet -backlight = [ - "\ue38d", # nf-weather-moon_new - "\ue3d4", # nf-weather-moon_alt_waxing_gibbous_6 - "\ue3d3", # nf-weather-moon_alt_waxing_gibbous_5 - "\ue3d2", # nf-weather-moon_alt_waxing_gibbous_4 - "\ue3d1", # nf-weather-moon_alt_waxing_gibbous_3 - "\ue3d0", # nf-weather-moon_alt_waxing_gibbous_2 - "\ue3cf", # nf-weather-moon_alt_waxing_gibbous_1 - "\ue3ce", # nf-weather-moon_alt_first_quarter - "\ue3cd", # nf-weather-moon_alt_waxing_crescent_6 - "\ue3cc", # nf-weather-moon_alt_waxing_crescent_5 - "\ue3cb", # nf-weather-moon_alt_waxing_crescent_4 - "\ue3ca", # nf-weather-moon_alt_waxing_crescent_3 - "\ue3c9", # nf-weather-moon_alt_waxing_crescent_2 - "\ue3c8", # nf-weather-moon_alt_waxing_crescent_1 - "\ue39b", # nf-weather-moon_full -] -bat_charging = "\U000f0084" # nf-md-battery_charging -bat_not_available = "\U000f0091" # nf-md-battery_unknown -bat = [ - "\U000f007a", # nf-md-battery_10 - "\U000f007b", # nf-md-battery_20 - "\U000f007c", # nf-md-battery_30 - "\U000f007d", # nf-md-battery_40 - "\U000f007e", # nf-md-battery_50 - "\U000f007f", # nf-md-battery_60 - "\U000f0080", # nf-md-battery_70 - "\U000f0081", # nf-md-battery_80 - "\U000f0082", # nf-md-battery_90 - "\U000f0079", # nf-md-battery -] -bell = "\U000f009c" # nf-md-bell_outline -bell-slash = "\U000f009b" # nf-md-bell_off -bluetooth = "\U000f00af" # nf-md-bluetooth -calendar = "\U000f00ed" # nf-md-calendar -cogs = "\U000f0493" # nf-md-cog -cpu = [ - "\U000F0F86", # nf-md-speedometer_slow - "\U000F0F85", # nf-md-speedometer_medium - "\U000F04C5", # nf-md-speedometer -] -cpu_boost_on = "\U000f0521" # nf-md-toggle_switch -cpu_boost_off = "\U000f0a19" # nf-md-toggle_switch_off_outline -disk_drive = "\U000f02ca" # nf-md-harddisk -docker = "\uf308" # nf-linux-docker -github = "\U000f02a4" # nf-md-github -gpu = "\U000f0379" # nf-md-monitor -headphones = "\U000f02cb" # nf-md-headphones -hueshift = "\U000f0336" # nf-md-lightbulb_outline -joystick = "\U000f0297" # nf-md-gamepad_variant -keyboard = "\U000f030c" # nf-md-keyboard -mail = "\U000f01ee" # nf-md-email -memory_mem = "\U000f035b" # nf-md-memory -memory_swap = "\U000f02ca" # nf-md-harddisk -mouse = "\U000f037d" # nf-md-mouse -music = "\U000f075a" # nf-md-music -music_next = "\U000f04ad" # nf-md-skip_next -music_pause = "\U000f03e4" # nf-md-pause -music_play = "\U000f040a" # nf-md-play -music_prev = "\U000f04ae" # nf-md-skip_previous -net_bridge = "\U000f04aa" # nf-md-sitemap -net_down = "\U000f01da" # nf-md-download -net_loopback = "\U000f006f" # nf-md-backup_restore -net_modem = "\U000f03f2" # nf-md-phone -net_cellular = [ - "\U000F08FD", # nf-md-network_strength_off_outline - "\U000F08FE", # nf-md-network_strength_outline - "\U000F08F4", # nf-md-network_strength_1 - "\U000F08F6", # nf-md-network_strength_2 - "\U000F08F8", # nf-md-network_strength_3 - "\U000F08FA", # nf-md-network_strength_4 -] -net_up = "\U000f0552" # nf-md-upload -net_vpn = "\U000f0582" # nf-md-vpn -net_wired = "\U000f0200" # nf-md-ethernet -net_wireless = [ - "\U000F092F", # nf-md-wifi_strength_outline - "\U000F091F", # nf-md-wifi_strength_1 - "\U000F0922", # nf-md-wifi_strength_2 - "\U000F0925", # nf-md-wifi_strength_3 - "\U000F0928", # nf-md-wifi_strength_4 -] -notification = "\U000f009c" # nf-md-bell_outline -phone = "\U000f03f2" # nf-md-phone -phone_disconnected = "\U000f0658" # nf-md-phone_minus -ping = "\U000f051f" # nf-md-timer_sand -pomodoro = "\ue001" # nf-pom-pomodoro_done -pomodoro_break = "\U000f0176" # nf-md-coffee -pomodoro_paused = "\U000f03e4" # nf-md-pause -pomodoro_started = "\U000f040a" # nf-md-play -pomodoro_stopped = "\U000f04db" # nf-md-stop -resolution = "\U000f0293" # nf-md-fullscreen -scratchpad = "\U000f05b2" # nf-md-window_restore -tasks = "\U000f05c7" # nf-md-playlist_check -tea = "\U000f0d9e" # nf-md-tea -thermometer = [ - "\U000f10c3", # nf-md-thermometer_low - "\U000f050f", # nf-md-thermometer - "\U000f10c2", # nf-md-thermometer_high -] -time = "\U000f0150" # nf-md-clock_outline -toggle_off = "\U000f0a19" # nf-md-toggle_switch_off_outline -toggle_on = "\U000f0521" # nf-md-toggle_switch -unknown = "\U000f0186" # nf-md-comment_question_outline | TODO: Make default? -update = "\U000f03d5" # nf-md-package_up -uptime = "\U000f0153" # nf-md-clock_in -volume_muted = "\U000f075f" # nf-md-volume_mute -volume = [ - "\U000f057f", # nf-md-volume_low - "\U000f0580", # nf-md-volume_medium - "\U000f057e", # nf-md-volume_high -] -microphone_muted = "\U000f036d" # nf-md-microphone_off -microphone = [ - "\U000f036e", # nf-md-microphone_outline - "\U000f036c", # nf-md-microphone - "\U000f036c", # nf-md-microphone -] -weather_clouds = "\ue33d" # nf-weather-cloud -weather_clouds_night = "\ue37e" # nf-weather-night_alt_cloudy -weather_default = "\ue33d" # Cloud symbol as default -weather_fog = "\ue313" # nf-weather-fog -weather_fog_night = "\ue346" # nf-weather-night_fog -weather_moon = "\uf186" # nf-fa-moon_o -weather_rain = "\ue371" # nf-weather-raindrop -weather_rain_night = "\ue325" # nf-weather-night_alt_rain -weather_snow = "\ue36f" # nf-weather-snowflake_cold -weather_sun = "\ue30d" # nf-weather-day_sunny -weather_thunder = "\ue31d" # nf-weather-thunderstorm -weather_thunder_night = "\ue32a" # nf-weather-night_alt_thunderstorm -webcam = "\U000f0567" # nf-md-video -xrandr = "\U000f037a" # nf-md-monitor_multiple diff --git a/files/icons/material.toml b/files/icons/material.toml deleted file mode 100644 index feee90defd..0000000000 --- a/files/icons/material.toml +++ /dev/null @@ -1,114 +0,0 @@ -# Material Design icons by Google -# https://github.com/google/material-design-icons/blob/master/font/MaterialIcons-Regular.codepoints -backlight = [ - "\ue1ad", # brightness_low - "\ue3a6", # brightness_1 - "\ue3a7", # brightness_2 - "\ue3a8", # brightness_3 - "\ue3a9", # brightness_4 - "\ue3aa", # brightness_5 - "\ue3ab", # brightness_6 - "\ue3c8", # brightness_7 - "\ue1ac", # brightness_high -] -bat_charging = "\ue3ac" # battery_charging_full -bat_not_available = "\ue1a6" # battery_unknown -bat = [ - "\ue19c", # battery_alert - "\ue1a5", # battery_std - "\ue1a5", # battery_std - "\ue1a5", # battery_std - "\ue1a5", # battery_std - "\ue1a5", # battery_std - "\ue1a5", # battery_std - "\ue1a5", # battery_std - "\ue1a5", # battery_std - "\ue1a4", # battery_full -] -bell = "\ue7f4" # notifications -bell-slash = "\ue7f8" # notifications_paused -bluetooth = "\ue1a7" # bluetooth -calendar = "\ue935" # calendar_today | TODO: broken? -cogs = "\ue8b8" # settings -cpu = "\ue640" # network_check -cpu_boost_on = "\ue837" # radio_button_on -cpu_boost_off = "\ue836" # radio_button_off -disk_drive = "\ue1db" # storage -docker = "\ue532" # directions_boat -github = "\ue86f" # code -gpu = "\ue333" # tv -headphones = "\ue60f" # bluetooth_audio -hueshift = "\ue0f0" # lightbulb -joystick = "\ue30f" # gamepad -keyboard = "\ue312" # keyboard -mail = "\ue0be" # email -memory_mem = "\ue322" # memory -memory_swap = "\ue8d4" # swap_horiz -mouse = "\ue323" # mouse -music = "\ue405" # music_note -music_next = "\ue044" # skip_next -music_pause = "\ue034" # skip_next -music_play = "\ue037" # play_arrow -music_prev = "\ue045" # skip_previous -net_bridge = "\uefe6" # cable | TODO: broken? -net_down = "\uf090" # download -net_loopback = "\ue028" # loop -net_modem = "\uefe6" # cable | TODO: broken? -net_cellular = [ - "\ue1d0", # signal_cellular_off - "\uf0a8", # signal_cellular_0_bar - "\uf0a9", # signal_cellular_1_bar - "\uf0aa", # signal_cellular_2_bar - "\uf0ab", # signal_cellular_3_bar - "\ue1c8", # signal_cellular_4_bar -] -net_up = "\uf09b" # upload -net_vpn = "\ue0da" # vpn_key -net_wired = "\uefe6" # cable | TODO: broken? -net_wireless = "\ue63e" # wifi | TODO: progression based on signal strength -notification = "\ue7f7" # notifications_active -phone = "\ue324" # phone_android -phone_disconnected = "\ue339" # device_unknown -ping = "\ue62a" # system_update -pomodoro = "\U0001f345" # https://unicode-table.com/en/1F4F5/ -pomodoro_break = "\uefef" # coffee | TODO: broken? -pomodoro_paused = "\ue034" # pause -pomodoro_started = "\ue037" # play_arrow -pomodoro_stopped = "\uef6a" # play_disabled ef6a | TODO: broken? -resolution = "\uf152" # crop-square-rounded -scratchpad = "\ue883" # flip_to_front -tasks = "\ue8f9" # work -tea = "\uefef" # coffee -thermometer = "\ue1ff" # device_thermostat | TODO: broken? -time = "\ue192" # access_time -toggle_off = "\ue836" # radio_button_on -toggle_on = "\ue837" # radio_button_on -unknown = "\ueb8b" # question_mark | TODO: broken? -update = "\ue8d7" # system_update_alt -uptime = "\ue425" # timer -volume = [ - "\ue04e", # volume_mute - "\ue04d", # volume_down - "\ue050", # volume_up -] -volume_muted = "\ue04f" # volume_off -microphone = [ - "\ue02a", # mic_none - "\ue029", # mic - "\ue029", # mic -] -microphone_muted = "\ue02b" # mic_off -weather_clouds = "\ue42d" # wb_cloudy -weather_clouds_night = "\uea46" # nights_stay -weather_default = "\ue42d" # wb_cloudy -weather_fog = "\ue42d" # wb_cloudy -weather_fog_night = "\ue42d" # wb_cloudy -weather_moon = "\uf03d" # nightlight -weather_rain = "\ue798" # water_drop -weather_rain_night = "\ue798" # water_drop -weather_snow = "\ueb3b" # ac_unit -weather_sun = "\ue430" # wb_sunny -weather_thunder = "\uebdb" # thunderstorm -weather_thunder_night = "\uebdb" # thunderstorm -webcam = "\ue04b" # videocam -xrandr = "\ue31e" # laptop diff --git a/files/themes/bad-wolf.toml b/files/themes/bad-wolf.toml deleted file mode 100644 index b5fe595ae4..0000000000 --- a/files/themes/bad-wolf.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#444444" -idle_fg = "#f5f5f5" -info_bg = "#626262" -info_fg = "#ffd680" -good_bg = "#afff00" -good_fg = "#000000" -warning_bg = "#ffaf00" -warning_fg = "#000000" -critical_bg = "#d70000" -critical_fg = "#000000" -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/ctp-frappe.toml b/files/themes/ctp-frappe.toml deleted file mode 100644 index 1409fb558f..0000000000 --- a/files/themes/ctp-frappe.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#414559" -idle_fg = "#cad3f5" -info_bg = "#8caaee" -info_fg = "#303446" -good_bg = "#a6d189" -good_fg = "#303446" -warning_bg = "#e5c890" -warning_fg = "#303446" -critical_bg = "#e78284" -critical_fg = "#303446" -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/ctp-latte.toml b/files/themes/ctp-latte.toml deleted file mode 100644 index b55148d9c7..0000000000 --- a/files/themes/ctp-latte.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#ccd0da" -idle_fg = "#4c4f69" -info_bg = "#1e66f5" -info_fg = "#eff1f5" -good_bg = "#40a02b" -good_fg = "#eff1f5" -warning_bg = "#df8e1d" -warning_fg = "#eff1f5" -critical_bg = "#d20f39" -critical_fg = "#eff1f5" -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/ctp-macchiato.toml b/files/themes/ctp-macchiato.toml deleted file mode 100644 index 302d719d92..0000000000 --- a/files/themes/ctp-macchiato.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#363a4f" -idle_fg = "#cad3f5" -info_bg = "#8aadf4" -info_fg = "#24273a" -good_bg = "#a6da95" -good_fg = "#24273a" -warning_bg = "#eed49f" -warning_fg = "#24273a" -critical_bg = "#ed8796" -critical_fg = "#24273a" -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/ctp-mocha.toml b/files/themes/ctp-mocha.toml deleted file mode 100644 index c51aeeedc7..0000000000 --- a/files/themes/ctp-mocha.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#313244" -idle_fg = "#cdd6f4" -info_bg = "#89b4fa" -info_fg = "#1e1e2e" -good_bg = "#a6e3a1" -good_fg = "#1e1e2e" -warning_bg = "#f9e2af" -warning_fg = "#1e1e2e" -critical_bg = "#f38ba8" -critical_fg = "#1e1e2e" -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/dracula.toml b/files/themes/dracula.toml deleted file mode 100644 index 996fad9b65..0000000000 --- a/files/themes/dracula.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#282a36" -idle_fg = "#f8f8f2" -info_bg = "#8be9fd" -info_fg = "#282a36" -good_bg = "#50fa7b" -good_fg = "#282a36" -warning_bg = "#f1fa8c" -warning_fg = "#282a36" -critical_bg = "#ff5555" -critical_fg = "#282a36" -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/gruvbox-dark.toml b/files/themes/gruvbox-dark.toml deleted file mode 100644 index e4f14aebf2..0000000000 --- a/files/themes/gruvbox-dark.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#282828" -idle_fg = "#ebdbb2" -info_bg = "#458588" -info_fg = "#ebdbb2" -good_bg = "#98971a" -good_fg = "#ebdbb2" -warning_bg = "#d79921" -warning_fg = "#ebdbb2" -critical_bg = "#cc241d" -critical_fg = "#ebdbb2" -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/gruvbox-light.toml b/files/themes/gruvbox-light.toml deleted file mode 100644 index 0f8156769c..0000000000 --- a/files/themes/gruvbox-light.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#fbf1c7" -idle_fg = "#3c3836" -info_bg = "#458588" -info_fg = "#fbf1c7" -good_bg = "#98971a" -good_fg = "#fbf1c7" -warning_bg = "#d79921" -warning_fg = "#fbf1c7" -critical_bg = "#cc241d" -critical_fg = "#fbf1c7" -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/modern.toml b/files/themes/modern.toml deleted file mode 100644 index 450c7161f8..0000000000 --- a/files/themes/modern.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#222D32" -idle_fg = "#CFD8DC" -info_bg = "#449CDB" -info_fg = "#1D1F21" -good_bg = "#99b938" -good_fg = "#1D1F21" -warning_bg = "#FE7E29" -warning_fg = "#1D1F21" -critical_bg = "#ff5252" -critical_fg = "#1D1F21" -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/native.toml b/files/themes/native.toml deleted file mode 100644 index d7a17d2429..0000000000 --- a/files/themes/native.toml +++ /dev/null @@ -1 +0,0 @@ -# Nothing here diff --git a/files/themes/nord-dark.toml b/files/themes/nord-dark.toml deleted file mode 100644 index 6b9878955f..0000000000 --- a/files/themes/nord-dark.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#2e3440" # nord0 -idle_fg = "#81a1c1" # light blue -info_bg = "#5e81ac" # dark blue -info_fg = "#2e3440" # nord0 -good_bg = "#a3be86" # green -good_fg = "#2e3440" # nord0 -warning_bg = "#ebcb8b" # yellow -warning_fg = "#2e3440" # nord0 -critical_bg = "#bf616a" # red -critical_fg = "#2e3440" # nord0 -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/plain.toml b/files/themes/plain.toml deleted file mode 100644 index 75b586c037..0000000000 --- a/files/themes/plain.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#000000" -idle_fg = "#93a1a1" -info_bg = "#000000" -info_fg = "#93a1a1" -good_bg = "#000000" -good_fg = "#859900" -warning_bg = "#000000" -warning_fg = "#b58900" -critical_bg = "#000000" -critical_fg = "#dc322f" -separator = "|" -separator_bg = "#000000" -separator_fg = "#a9a9a9" diff --git a/files/themes/semi-native.toml b/files/themes/semi-native.toml deleted file mode 100644 index 861624c4e9..0000000000 --- a/files/themes/semi-native.toml +++ /dev/null @@ -1,5 +0,0 @@ -idle_fg = "#93a1a1" -info_fg = "#93a1a1" -good_fg = "#859900" -warning_fg = "#b58900" -critical_fg = "#dc322f" diff --git a/files/themes/slick.toml b/files/themes/slick.toml deleted file mode 100644 index ddfd6a89cf..0000000000 --- a/files/themes/slick.toml +++ /dev/null @@ -1,15 +0,0 @@ -idle_bg = "#424242" -idle_fg = "#ffffff" -info_bg = "#2196f3" -info_fg = "#ffffff" -good_bg = "#8bc34a" -good_fg = "#000000" -warning_bg = "#ffc107" -warning_fg = "#000000" -critical_bg = "#f44336" -critical_fg = "#ffffff" -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" -alternating_tint_bg = "#11111100" -alternating_tint_fg = "#11111100" diff --git a/files/themes/solarized-dark.toml b/files/themes/solarized-dark.toml deleted file mode 100644 index c4d3f145df..0000000000 --- a/files/themes/solarized-dark.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#002b36" # base03 -idle_fg = "#93a1a1" # base1 -info_bg = "#268bd2" # blue -info_fg = "#002b36" # base03 -good_bg = "#859900" # green -good_fg = "#002b36" # base03 -warning_bg = "#b58900" # yellow -warning_fg = "#002b36" # base03 -critical_bg = "#dc322f" # red -critical_fg = "#002b36" # base03 -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/solarized-light.toml b/files/themes/solarized-light.toml deleted file mode 100644 index 32b8693f72..0000000000 --- a/files/themes/solarized-light.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#fdf6e3" # base3 -idle_fg = "#586e75" # base01 -info_bg = "#268bd2" # blue -info_fg = "#fdf6e3" # base3 -good_bg = "#859900" # green -good_fg = "#fdf6e3" # base3 -warning_bg = "#b58900" # yellow -warning_fg = "#fdf6e3" # base3 -critical_bg = "#dc322f" # red -critical_fg = "#fdf6e3" # base3 -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/space-villain.toml b/files/themes/space-villain.toml deleted file mode 100644 index 1249007472..0000000000 --- a/files/themes/space-villain.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#06060f" # Rich black -idle_fg = "#c1c1c1" # Silver -info_bg = "#00223f" # Maastricht Blue -info_fg = "#c1c1c1" # Silver -good_bg = "#394049" # Arsenic -good_fg = "#c1c1c1" # Silver -warning_bg = "#2d1637" # Dark Purple -warning_fg = "#c1c1c1" # Silver -critical_bg = "#c1c1c1" # Silver -critical_fg = "#2c1637" # Dark Purple -separator = "\ue0b2" -separator_bg = "auto" -separator_fg = "auto" diff --git a/files/themes/srcery.toml b/files/themes/srcery.toml deleted file mode 100644 index 98817abf20..0000000000 --- a/files/themes/srcery.toml +++ /dev/null @@ -1,13 +0,0 @@ -idle_bg = "#1C1B19" # Black -idle_fg = "#FCE8C3" # Bright White -info_bg = "#1C1B19" # Black -info_fg = "#FCE8C3" # Bright White -good_bg = "#1C1B19" # Black -good_fg = "#98BC37" # Bright Green -warning_bg = "#1C1B19" # Black -warning_fg = "#FED06E" # Bright Yellow -critical_bg = "#1C1B19" # Black -critical_fg = "#F75341" # Bright Red -separator = "" -separator_bg = "auto" -separator_fg = "auto" diff --git a/gen-screenshots/gen-screenshots.sh b/gen-screenshots/gen-screenshots.sh deleted file mode 100755 index d4ebf02bcd..0000000000 --- a/gen-screenshots/gen-screenshots.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -cd "$(dirname "$0")" - -SOCK_FILE=sway_i3rs.sock - -set_theme() { - theme_name=$1 - sed -i -r "s/theme = .*/theme = \"$theme_name\"/" screenshot_config.toml -} - -cleanup() { - # Reset theme so that config is not changed - set_theme srcery - # Remove the socket file - rm -f $SOCK_FILE -} - -trap cleanup EXIT - -# Screenshot area depends on the current monitor's position and size, and we only want the relevant part of the bar -read x y w <<< $(swaymsg -t get_outputs | jq --raw-output ".. | objects | select(.focused == true) | .rect | \"\(.x) \(.y) \(.width)\"") -BAR_COORDS="$(($w/2 + $x)),$y $(($w/2))x16" - -swaymsg fullscreen off -SWAYSOCK=$SOCK_FILE I3RS_PWD=$PWD sway --config swayconfig_i3rs & -sleep 1 -swaymsg fullscreen toggle - -for theme in ../files/themes/*; do - theme_name=$(basename $theme .toml) - if [ -f ../img/themes/"$theme_name".png ]; then - echo Image for theme $theme_name already exists, skipping - continue - fi - set_theme $theme_name - pkill -SIGUSR2 i3status-rs - sleep 1 - grim -g "$BAR_COORDS" ../img/themes/"$theme_name".png -done - -SWAYSOCK=$SOCK_FILE swaymsg exit - diff --git a/gen-screenshots/screenshot_config.toml b/gen-screenshots/screenshot_config.toml deleted file mode 100644 index 180c691c3f..0000000000 --- a/gen-screenshots/screenshot_config.toml +++ /dev/null @@ -1,29 +0,0 @@ -[theme] -theme = "srcery" -[icons] -icons = "awesome6" - -[[block]] -block = "custom" -command = "echo '{\"icon\":\"time\",\"state\":\"Idle\", \"text\": \"Fri 19/05 18:22\"}'" -json = true - -[[block]] -block = "custom" -command = "echo '{\"icon\":\"music\",\"state\":\"Info\", \"text\": \"Becoming The Bull | A\"}'" -json = true - -[[block]] -block = "custom" -command = "echo '{\"icon\":\"cogs\",\"state\":\"Critical\", \"text\": \"7.76 3.29 2.20\"}'" -json = true - -[[block]] -block = "custom" -command = "echo '{\"icon\":\"cpu\",\"state\":\"Warning\", \"text\": \"85%\"}'" -json = true - -[[block]] -block = "custom" -command = "echo '{\"icon\":\"memory_mem\",\"state\":\"Idle\", \"text\": \"45.58%\"}'" -json = true diff --git a/gen-screenshots/swayconfig_i3rs b/gen-screenshots/swayconfig_i3rs deleted file mode 100644 index 039e5eaf03..0000000000 --- a/gen-screenshots/swayconfig_i3rs +++ /dev/null @@ -1,17 +0,0 @@ -bar { - position top - height 16 - status_padding 0 - tray_output none - font pango:DejaVu Sans Mono, Font Awesome 6 Free 10 - #font pango:DejaVu Sans Mono for Powerline, Font Awesome 6 Free 10 - status_command i3status-rs $I3RS_PWD/screenshot_config.toml - - colors { - statusline #00ff00 - background #676767 - inactive_workspace #32323200 #32323200 #5c5c5c - } -} - -include /etc/sway/config.d/* diff --git a/help.html b/help.html new file mode 100644 index 0000000000..0e0b41ddbe --- /dev/null +++ b/help.html @@ -0,0 +1 @@ +Help

Rustdoc help

Back
\ No newline at end of file diff --git a/i3status_rs/all.html b/i3status_rs/all.html new file mode 100644 index 0000000000..06f71be326 --- /dev/null +++ b/i3status_rs/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Structs

Enums

Traits

Macros

Functions

Type Aliases

Statics

Constants

\ No newline at end of file diff --git a/i3status_rs/blocks/amd_gpu/fn.run.html b/i3status_rs/blocks/amd_gpu/fn.run.html new file mode 100644 index 0000000000..e681f9ed33 --- /dev/null +++ b/i3status_rs/blocks/amd_gpu/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::amd_gpu - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/amd_gpu/index.html b/i3status_rs/blocks/amd_gpu/index.html new file mode 100644 index 0000000000..ee388c4605 --- /dev/null +++ b/i3status_rs/blocks/amd_gpu/index.html @@ -0,0 +1,26 @@ +i3status_rs::blocks::amd_gpu - Rust

Module amd_gpu

Source
Expand description

Display the stats of your AMD GPU

+

§Configuration

+ + + + +
KeyValuesDefault
deviceThe device in /sys/class/drm/ to read from.Any AMD card
formatA string to customise the output of this block. See below for available placeholders." $icon $utilization "
format_altIf set, block will switch between format and format_alt on every clickNone
intervalUpdate interval in seconds5
+
+ + + + + +
PlaceholderValueTypeUnit
iconA static iconIcon-
utilizationGPU utilizationNumber%
vram_totalTotal VRAMNumberBytes
vram_usedUsed VRAMNumberBytes
vram_used_percentsUsed VRAM / Total VRAMNumber%
+
+ +
ActionDescriptionDefault button
toggle_formatToggles between format and format_altLeft
+

§Example

[[block]]
+block = "amd_gpu"
+format = " $icon $utilization "
+format_alt = " $icon MEM: $vram_used_percents ($vram_used/$vram_total) "
+interval = 1

§Icons Used

+
    +
  • gpu
  • +
+

Structs§

Config
Device

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/amd_gpu/sidebar-items.js b/i3status_rs/blocks/amd_gpu/sidebar-items.js new file mode 100644 index 0000000000..4f942000be --- /dev/null +++ b/i3status_rs/blocks/amd_gpu/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config","Device"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/amd_gpu/struct.Config.html b/i3status_rs/blocks/amd_gpu/struct.Config.html new file mode 100644 index 0000000000..afb288a297 --- /dev/null +++ b/i3status_rs/blocks/amd_gpu/struct.Config.html @@ -0,0 +1,42 @@ +Config in i3status_rs::blocks::amd_gpu - Rust

Struct Config

Source
pub struct Config {
+    pub device: Option<String>,
+    pub format: FormatConfig,
+    pub format_alt: Option<FormatConfig>,
+    pub interval: Seconds,
+}

Fields§

§device: Option<String>§format: FormatConfig§format_alt: Option<FormatConfig>§interval: Seconds

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { device: Default::default(), format: Default::default(), format_alt: Default::default(), interval: 5.into() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/amd_gpu/struct.Device.html b/i3status_rs/blocks/amd_gpu/struct.Device.html new file mode 100644 index 0000000000..52d2fd3020 --- /dev/null +++ b/i3status_rs/blocks/amd_gpu/struct.Device.html @@ -0,0 +1,32 @@ +Device in i3status_rs::blocks::amd_gpu - Rust

Struct Device

Source
pub struct Device { /* private fields */ }

Auto Trait Implementations§

§

impl Freeze for Device

§

impl RefUnwindSafe for Device

§

impl Send for Device

§

impl Sync for Device

§

impl Unpin for Device

§

impl UnwindSafe for Device

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/backlight/fn.run.html b/i3status_rs/blocks/backlight/fn.run.html new file mode 100644 index 0000000000..0e909c856c --- /dev/null +++ b/i3status_rs/blocks/backlight/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::backlight - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/backlight/index.html b/i3status_rs/blocks/backlight/index.html new file mode 100644 index 0000000000..22f49be5d2 --- /dev/null +++ b/i3status_rs/blocks/backlight/index.html @@ -0,0 +1,54 @@ +i3status_rs::blocks::backlight - Rust

Module backlight

Source
Expand description

The brightness of a backlight device

+

This block reads brightness information directly from the filesystem, so it works under both +X11 and Wayland. The block uses inotify to listen for changes in the device’s brightness +directly, so there is no need to set an update interval. This block uses DBus to set brightness +level using the mouse wheel, but will fallback to sysfs if systemd-logind is not used.

+

§Root scaling

+

Some devices expose raw values that are best handled with nonlinear scaling. The human perception of lightness is close to the cube root of relative luminance, so settings for root_scaling between 2.4 and 3.0 are worth trying. For devices with few discrete steps this should be 1.0 (linear). More information: https://en.wikipedia.org/wiki/Lightness

+

§Configuration

+ + + + + + + + + + + +
KeyValuesDefault
deviceA regex to match against /sys/class/backlight devices to read brightness information from (can match 1 or more devices). When there is no device specified, this block will display information for all devices found in the /sys/class/backlight directory.Default device
formatA string to customise the output of this block. See below for available placeholders." $icon $brightness "
missing_formatA string to customise the output of this block. No placeholders available" no backlight devices "
step_widthThe brightness increment to use when scrolling, in percent5
minimumThe minimum brightness that can be scrolled down to5
maximumThe maximum brightness that can be scrolled up to100
cycleThe brightnesses to cycle through on each click[minimum, maximum]
root_scalingScaling exponent reciprocal (ie. root)1.0
invert_iconsInvert icons’ ordering, useful if you have colorful emojifalse
ddcci_sleep_multiplierSee ddcutil documentation1.0
ddcci_max_tries_write_readThe maximum number of times to attempt writing to or reading from a ddcci monitor10
+
+ + +
PlaceholderValueTypeUnit
iconIcon based on backlight’s stateIcon-
brightnessCurrent brightnessNumber%
+
+ + + +
ActionDefault button
cycleLeft
brightness_upWheel Up
brightness_downWheel Down
+

§Example

[[block]]
+block = "backlight"
+device = "intel_backlight"
+

Hide missing backlight:

+
[[block]]
+block = "backlight"
+missing_format = ""

§calibright

+

Additional display brightness calibration can be set in $XDG_CONFIG_HOME/calibright/config.toml +See https://github.com/bim9262/calibright for more details. +This block will override any global config set in $XDG_CONFIG_HOME/calibright/config.toml

+

§D-Bus Fallback

+

If you don’t use systemd-logind i3status-rust will attempt to set the brightness +using sysfs. In order to do this you’ll need to have write permission. +You can do this by writing a udev rule for your system.

+

First, check that your user is a member of the “video” group using the +groups command. Then add a rule in the /etc/udev/rules.d/ directory +containing the following, for example in backlight.rules:

+
ACTION=="add", SUBSYSTEM=="backlight", GROUP="video", MODE="0664"
+

This will allow the video group to modify all backlight devices. You will +also need to restart for this rule to take effect.

+

§Icons Used

+
    +
  • backlight (as a progression)
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/backlight/sidebar-items.js b/i3status_rs/blocks/backlight/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/backlight/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/backlight/struct.Config.html b/i3status_rs/blocks/backlight/struct.Config.html new file mode 100644 index 0000000000..93f086ff7f --- /dev/null +++ b/i3status_rs/blocks/backlight/struct.Config.html @@ -0,0 +1,49 @@ +Config in i3status_rs::blocks::backlight - Rust

Struct Config

Source
pub struct Config {
+    pub device: Option<String>,
+    pub format: FormatConfig,
+    pub missing_format: FormatConfig,
+    pub step_width: f64,
+    pub minimum: f64,
+    pub maximum: f64,
+    pub cycle: Option<Vec<f64>>,
+    pub invert_icons: bool,
+    pub root_scaling: Option<f64>,
+    pub ddcci_sleep_multiplier: Option<f64>,
+    pub ddcci_max_tries_write_read: Option<u8>,
+}

Fields§

§device: Option<String>§format: FormatConfig§missing_format: FormatConfig§step_width: f64§minimum: f64§maximum: f64§cycle: Option<Vec<f64>>§invert_icons: bool§root_scaling: Option<f64>§ddcci_sleep_multiplier: Option<f64>§ddcci_max_tries_write_read: Option<u8>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { device: Default::default(), format: Default::default(), missing_format: Default::default(), step_width: 5.0, minimum: 5.0, maximum: 100.0, cycle: Default::default(), invert_icons: Default::default(), root_scaling: Default::default(), ddcci_sleep_multiplier: Default::default(), ddcci_max_tries_write_read: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/battery/enum.BatteryDriver.html b/i3status_rs/blocks/battery/enum.BatteryDriver.html new file mode 100644 index 0000000000..c98fd59fd8 --- /dev/null +++ b/i3status_rs/blocks/battery/enum.BatteryDriver.html @@ -0,0 +1,40 @@ +BatteryDriver in i3status_rs::blocks::battery - Rust

Enum BatteryDriver

Source
pub enum BatteryDriver {
+    Sysfs,
+    ApcUps,
+    Upower,
+}

Variants§

§

Sysfs

§

ApcUps

§

Upower

Trait Implementations§

Source§

impl Debug for BatteryDriver

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for BatteryDriver

Source§

fn default() -> Self

Return BatteryDriver::Sysfs

+
Source§

impl<'de> Deserialize<'de> for BatteryDriver

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/battery/fn.run.html b/i3status_rs/blocks/battery/fn.run.html new file mode 100644 index 0000000000..6f007b991d --- /dev/null +++ b/i3status_rs/blocks/battery/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::battery - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/battery/index.html b/i3status_rs/blocks/battery/index.html new file mode 100644 index 0000000000..7dada9e76a --- /dev/null +++ b/i3status_rs/blocks/battery/index.html @@ -0,0 +1,49 @@ +i3status_rs::blocks::battery - Rust

Module battery

Source
Expand description

Information about the internal power supply

+

This block can display the current battery state (Full, Charging or Discharging), percentage +charged and estimate time until (dis)charged for an internal power supply.

+

§Configuration

+ + + + + + + + + + + + + + + + +
KeyValuesDefault
devicesysfs/UPower: The device in /sys/class/power_supply/ to read from (can also be “DisplayDevice” for UPower, which is a single logical power source representing all physical power sources. This is for example useful if your system has multiple batteries, in which case the DisplayDevice behaves as if you had a single larger battery.). apc_ups: IPv4Address:port or hostname:portsysfs: the first battery device found in /sys/class/power_supply, with “BATx” or “CMBx” entries taking precedence. apc_ups: “localhost:3551”. upower: DisplayDevice
driverOne of "sysfs", "apc_ups", or "upower""sysfs"
modelIf present, the contents of /sys/class/power_supply/.../model_name must match this value. Typical use is to select by model name on devices that change their path.N/A
intervalUpdate interval, in seconds. Only relevant for driver = “sysfs” or “apc_ups”.10
formatA string to customise the output of this block. See below for available placeholders." $icon $percentage "
full_formatSame as format but for when the battery is full" $icon "
charging_formatSame as format but for when the battery is chargingLinks to format
empty_formatSame as format but for when the battery is empty" $icon "
not_charging_formatSame as format but for when the battery is not charging. Defaults to the full battery icon as many batteries report this status when they are full." $icon "
missing_formatSame as format if the battery cannot be found." $icon "
infoMinimum battery level, where state is set to info60
goodMinimum battery level, where state is set to good60
warningMinimum battery level, where state is set to warning30
criticalMinimum battery level, where state is set to critical15
full_thresholdPercentage above which the battery is considered full (full_format shown)95
empty_thresholdPercentage below which the battery is considered empty7.5
+
+ + + + + +
PlaceholderValueTypeUnit
iconIcon based on battery’s stateIcon-
percentageBattery level, in percentNumberPercents
time_remainingTime remaining until (dis)charge is complete. Presented only if battery’s status is (dis)charging.Duration-
timeTime remaining until (dis)charge is complete. Presented only if battery’s status is (dis)charging.String DEPRECATED-
powerPower consumption by the battery or from the power supply when chargingString or FloatWatts
+
+

time has been deprecated in favor of time_remaining.

+

§Examples

+

Basic usage:

+
[[block]]
+block = "battery"
+format = " $icon $percentage "
[[block]]
+block = "battery"
+format = " $percentage {$time_remaining.dur(hms:true, min_unit:m) |}"
+device = "DisplayDevice"
+driver = "upower"
+

Hide missing battery:

+
[[block]]
+block = "battery"
+missing_format = ""

§Icons Used

+
    +
  • bat (as a progression)
  • +
  • bat_charging (as a progression)
  • +
  • bat_not_available
  • +
+

Structs§

Config

Enums§

BatteryDriver

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/battery/sidebar-items.js b/i3status_rs/blocks/battery/sidebar-items.js new file mode 100644 index 0000000000..e4be11ecb9 --- /dev/null +++ b/i3status_rs/blocks/battery/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["BatteryDriver"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/battery/struct.Config.html b/i3status_rs/blocks/battery/struct.Config.html new file mode 100644 index 0000000000..2eacfb41e9 --- /dev/null +++ b/i3status_rs/blocks/battery/struct.Config.html @@ -0,0 +1,54 @@ +Config in i3status_rs::blocks::battery - Rust

Struct Config

Source
pub struct Config {
Show 16 fields + pub device: Option<String>, + pub driver: BatteryDriver, + pub model: Option<String>, + pub interval: Seconds, + pub format: FormatConfig, + pub full_format: FormatConfig, + pub charging_format: FormatConfig, + pub empty_format: FormatConfig, + pub not_charging_format: FormatConfig, + pub missing_format: FormatConfig, + pub info: f64, + pub good: f64, + pub warning: f64, + pub critical: f64, + pub full_threshold: f64, + pub empty_threshold: f64, +
}

Fields§

§device: Option<String>§driver: BatteryDriver§model: Option<String>§interval: Seconds§format: FormatConfig§full_format: FormatConfig§charging_format: FormatConfig§empty_format: FormatConfig§not_charging_format: FormatConfig§missing_format: FormatConfig§info: f64§good: f64§warning: f64§critical: f64§full_threshold: f64§empty_threshold: f64

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { device: Default::default(), driver: Default::default(), model: Default::default(), interval: 10.into(), format: Default::default(), full_format: Default::default(), charging_format: Default::default(), empty_format: Default::default(), not_charging_format: Default::default(), missing_format: Default::default(), info: 60.0, good: 60.0, warning: 30.0, critical: 15.0, full_threshold: 95.0, empty_threshold: 7.5 }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/bluetooth/fn.run.html b/i3status_rs/blocks/bluetooth/fn.run.html new file mode 100644 index 0000000000..5675a26a92 --- /dev/null +++ b/i3status_rs/blocks/bluetooth/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::bluetooth - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/bluetooth/index.html b/i3status_rs/blocks/bluetooth/index.html new file mode 100644 index 0000000000..7eca7075c1 --- /dev/null +++ b/i3status_rs/blocks/bluetooth/index.html @@ -0,0 +1,45 @@ +i3status_rs::blocks::bluetooth - Rust

Module bluetooth

Source
Expand description

Monitor Bluetooth device

+

This block displays the connectivity of a given Bluetooth device and the battery level if this +is supported. Relies on the Bluez D-Bus API.

+

When the device can be identified as an audio headset, a keyboard, joystick, or mouse, use the +relevant icon. Otherwise, fall back on the generic Bluetooth symbol.

+

Right-clicking the block will attempt to connect (or disconnect) the device.

+

Note: battery level information is not reported for some devices. Enabling experimental +features of bluez +may fix it.

+

§Configuration

+ + + + + +
KeyValuesDefault
macMAC address of the Bluetooth deviceRequired
adapter_macMAC Address of the Bluetooth adapter (in case your device was connected to multiple currently available adapters)None
formatA string to customise the output of this block. See below for available placeholders." $icon $name{ $percentage|} "
disconnected_formatA string to customise the output of this block. See below for available placeholders." $icon{ $name|} "
battery_stateA mapping from battery percentage to block’s state (color). See example below.0..15 -> critical, 16..30 -> warning, 31..60 -> info, 61..100 -> good
+
+ + + + + +
PlaceholderValueTypeUnit
iconIcon based on what type of device is connectedIcon-
nameDevice’s nameText-
percentageDevice’s battery level (may be absent if the device is not supported)Number%
battery_iconBattery icon (may be absent if the device is not supported)Icon-
availablePresent if the device is availableFlag-
+
+ +
ActionDefault button
toggleRight
+

§Examples

+

This example just shows the icon when device is connected.

+
[[block]]
+block = "bluetooth"
+mac = "00:18:09:92:1B:BA"
+disconnected_format = ""
+format = " $icon "
+[block.battery_state]
+"0..20" = "critical"
+"21..70" = "warning"
+"71..100" = "good"

§Icons Used

+
    +
  • headphones for bluetooth devices identifying as “audio-card”, “audio-headset” or “audio-headphones”
  • +
  • joystick for bluetooth devices identifying as “input-gaming”
  • +
  • keyboard for bluetooth devices identifying as “input-keyboard”
  • +
  • mouse for bluetooth devices identifying as “input-mouse”
  • +
  • bluetooth for all other devices
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/bluetooth/sidebar-items.js b/i3status_rs/blocks/bluetooth/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/bluetooth/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/bluetooth/struct.Config.html b/i3status_rs/blocks/bluetooth/struct.Config.html new file mode 100644 index 0000000000..90f26df762 --- /dev/null +++ b/i3status_rs/blocks/bluetooth/struct.Config.html @@ -0,0 +1,40 @@ +Config in i3status_rs::blocks::bluetooth - Rust

Struct Config

Source
pub struct Config {
+    pub mac: String,
+    pub adapter_mac: Option<String>,
+    pub format: FormatConfig,
+    pub disconnected_format: FormatConfig,
+    pub battery_state: Option<RangeMap<u8, State>>,
+}

Fields§

§mac: String§adapter_mac: Option<String>§format: FormatConfig§disconnected_format: FormatConfig§battery_state: Option<RangeMap<u8, State>>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Config

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/calendar/enum.AuthConfig.html b/i3status_rs/blocks/calendar/enum.AuthConfig.html new file mode 100644 index 0000000000..280a1fcc97 --- /dev/null +++ b/i3status_rs/blocks/calendar/enum.AuthConfig.html @@ -0,0 +1,41 @@ +AuthConfig in i3status_rs::blocks::calendar - Rust

Enum AuthConfig

Source
pub enum AuthConfig {
+    Unauthenticated,
+    Basic(BasicAuthConfig),
+    OAuth2(OAuth2Config),
+}

Variants§

§

Unauthenticated

§

Basic(BasicAuthConfig)

§

OAuth2(OAuth2Config)

Trait Implementations§

Source§

impl Clone for AuthConfig

Source§

fn clone(&self) -> AuthConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for AuthConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for AuthConfig

Source§

fn default() -> AuthConfig

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for AuthConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/calendar/enum.CalendarError.html b/i3status_rs/blocks/calendar/enum.CalendarError.html new file mode 100644 index 0000000000..77bf557f81 --- /dev/null +++ b/i3status_rs/blocks/calendar/enum.CalendarError.html @@ -0,0 +1,44 @@ +CalendarError in i3status_rs::blocks::calendar - Rust

Enum CalendarError

Source
pub enum CalendarError {
+    Http(Error),
+    Deserialize(DeError),
+    Parsing(String),
+    AuthRequired,
+    Io(Error),
+    Serialize(Error),
+    RequestToken(String),
+    StoreToken(TokenStoreError),
+}

Variants§

§

Http(Error)

§

Deserialize(DeError)

§

Parsing(String)

§

AuthRequired

§

Io(Error)

§

Serialize(Error)

§

RequestToken(String)

§

StoreToken(TokenStoreError)

Trait Implementations§

Source§

impl Debug for CalendarError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for CalendarError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for CalendarError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<DeError> for CalendarError

Source§

fn from(source: DeError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for CalendarError

Source§

fn from(source: Error) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for CalendarError

Source§

fn from(source: Error) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for CalendarError

Source§

fn from(source: Error) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToString for T
where + T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
§

impl<T> ToStringFallible for T
where + T: Display,

§

fn try_to_string(&self) -> Result<String, TryReserveError>

ToString::to_string, but without panic on OOM.

+
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/calendar/fn.run.html b/i3status_rs/blocks/calendar/fn.run.html new file mode 100644 index 0000000000..cc9c5bacf9 --- /dev/null +++ b/i3status_rs/blocks/calendar/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::calendar - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/calendar/index.html b/i3status_rs/blocks/calendar/index.html new file mode 100644 index 0000000000..f944020b99 --- /dev/null +++ b/i3status_rs/blocks/calendar/index.html @@ -0,0 +1,131 @@ +i3status_rs::blocks::calendar - Rust

Module calendar

Source
Expand description

Calendar

+

This block displays upcoming calendar events retrieved from a CalDav ICalendar server.

+

§Configuration

+ + + + + + + + + + +
KeyValuesDefault
next_event_formatA string to customize the output of this block when there is a next event in the calendar. See below for available placeholders." $icon $start.datetime(f:‘%a %H:%M’) $summary "
ongoing_event_formatA string to customize the output of this block when an event is ongoing." $icon $summary (ends at $end.datetime(f:‘%H:%M’)) "
no_events_formatA string to customize the output of this block when there are no events" $icon "
redirect_formatA string to customize the output of this block when the authorization is asked" $icon Check your web browser "
fetch_intervalFetch events interval in seconds60
alternate_events_intervalAlternate overlapping events interval in seconds10
events_within_hoursNumber of hours to look for events in the future48
sourceArray of sources to pull calendars from[]
warning_thresholdWarning threshold in seconds for the upcoming event300
browser_cmdCommand to open event details in a browser. The block passes the HTML link as an argument"xdg-open"
+

§Source Configuration

+ + + +
KeyValuesDefault
urlCalDav calendar server URLN/A
authAuthentication configuration (unauthenticated, basic, or oauth2)unauthenticated
calendarsList of calendar names to monitor. If empty, all calendars will be fetched.[]
+
+

Note: Currently only one source is supported

+
+ +
ActionDescriptionDefault button
open_linkOpens the HTML link of the eventLeft
+

§Examples

§Unauthenticated

[[block]]
+block = "calendar"
+next_event_format = " $icon $start.datetime(f:'%a %H:%M') $summary "
+ongoing_event_format = " $icon $summary (ends at $end.datetime(f:'%H:%M')) "
+no_events_format = " $icon no events "
+fetch_interval = 30
+alternate_events_interval = 10
+events_within_hours = 48
+warning_threshold = 600
+browser_cmd = "firefox"
+[[block.source]]
+url = "https://caldav.example.com/calendar/"
+calendars = ["user/calendar"]
+[block.source.auth]
+type = "unauthenticated"

§Basic Authentication

[[block]]
+block = "calendar"
+next_event_format = " $icon $start.datetime(f:'%a %H:%M') $summary "
+ongoing_event_format = " $icon $summary (ends at $end.datetime(f:'%H:%M')) "
+no_events_format = " $icon no events "
+fetch_interval = 30
+alternate_events_interval = 10
+events_within_hours = 48
+warning_threshold = 600
+browser_cmd = "firefox"
+[[block.source]]
+url = "https://caldav.example.com/calendar/"
+calendars = [ "Holidays" ]
+[block.source.auth]
+type = "basic"
+username = "your_username"
+password = "your_password"
+

Note: You can also configure the username and password in a separate TOML file.

+

~/.config/i3status-rust/example_credentials.toml

+
username = "my-username"
+password = "my-password"
+

Source auth configuration with credentials_path:

+
[block.source.auth]
+type = "basic"
+credentials_path = "~/.config/i3status-rust/example_credentials.toml"

§OAuth2 Authentication (Google Calendar)

+

To access the CalDav API of Google, follow these steps to enable the API and obtain the client_id and client_secret:

+
    +
  1. Go to the Google Cloud Console: Navigate to the Google Cloud Console.
  2. +
  3. Create a New Project: If you don’t already have a project, click on the project dropdown and select “New Project”. Give your project a name and click “Create”.
  4. +
  5. Enable the CalDAV API: In the project dashboard, go to the “APIs & Services” > “Library”. Search for “CalDAV API” and click on it, then click “Enable”.
  6. +
  7. Set Up OAuth Consent Screen: Go to “APIs & Services” > “OAuth consent screen”. Fill out the required information and save.
  8. +
  9. Create Credentials: +
      +
    • Navigate to “APIs & Services” > “Credentials”.
    • +
    • Click “Create Credentials” and select “OAuth 2.0 Client IDs”.
    • +
    • Configure the consent screen if you haven’t already.
    • +
    • Set the application type to “Web application”.
    • +
    • Add your authorized redirect URIs. For example, http://localhost:8080.
    • +
    • Click “Create” and note down the client_id and client_secret.
    • +
    +
  10. +
  11. Download the Credentials: Click on the download icon next to your OAuth 2.0 Client ID to download the JSON file containing your client ID and client secret. Use these values in your configuration.
  12. +
+
[[block]]
+block = "calendar"
+next_event_format = " $icon $start.datetime(f:'%a %H:%M') $summary "
+ongoing_event_format = " $icon $summary (ends at $end.datetime(f:'%H:%M')) "
+no_events_format = " $icon no events "
+fetch_interval = 30
+alternate_events_interval = 10
+events_within_hours = 48
+warning_threshold = 600
+browser_cmd = "firefox"
+[[block.source]]
+url = "https://apidata.googleusercontent.com/caldav/v2/"
+calendars = ["primary"]
+[block.source.auth]
+type = "oauth2"
+client_id = "your_client_id"
+client_secret = "your_client_secret"
+auth_url = "https://accounts.google.com/o/oauth2/auth"
+token_url = "https://oauth2.googleapis.com/token"
+auth_token = "~/.config/i3status-rust/calendar.auth_token"
+redirect_port = 8080
+scopes = ["https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/calendar.events"]
+

Note: You can also configure the client_id and client_secret in a separate TOML file.

+

~/.config/i3status-rust/google_credentials.toml

+
client_id = "my-client_id"
+client_secret = "my-client_secret"
+

Source auth configuration with credentials_path:

+
[block.source.auth]
+type = "oauth2"
+credentials_path = "~/.config/i3status-rust/google_credentials.toml"
+auth_url = "https://accounts.google.com/o/oauth2/auth"
+token_url = "https://oauth2.googleapis.com/token"
+auth_token = "~/.config/i3status-rust/calendar.auth_token"
+redirect_port = 8080
+scopes = ["https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/calendar.events"]

§Format Configuration

+

The format configuration is a string that can include placeholders to be replaced with dynamic content. +Placeholders can be:

+
    +
  • $summary: Summary of the event
  • +
  • $description: Description of the event
  • +
  • $url: Url of the event
  • +
  • $location: Location of the event
  • +
  • $start: Start time of the event
  • +
  • $end: End time of the event
  • +
+

§Icons Used

+
    +
  • calendar
  • +
+

Structs§

BasicAuthConfig
BasicCredentials
Config
OAuth2Config
OAuth2Credentials
SourceConfig

Enums§

AuthConfig
CalendarError

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/calendar/sidebar-items.js b/i3status_rs/blocks/calendar/sidebar-items.js new file mode 100644 index 0000000000..95de447d1d --- /dev/null +++ b/i3status_rs/blocks/calendar/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["AuthConfig","CalendarError"],"fn":["run"],"struct":["BasicAuthConfig","BasicCredentials","Config","OAuth2Config","OAuth2Credentials","SourceConfig"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/calendar/struct.BasicAuthConfig.html b/i3status_rs/blocks/calendar/struct.BasicAuthConfig.html new file mode 100644 index 0000000000..df1431a504 --- /dev/null +++ b/i3status_rs/blocks/calendar/struct.BasicAuthConfig.html @@ -0,0 +1,39 @@ +BasicAuthConfig in i3status_rs::blocks::calendar - Rust

Struct BasicAuthConfig

Source
pub struct BasicAuthConfig {
+    pub credentials: BasicCredentials,
+    pub credentials_path: Option<ShellString>,
+}

Fields§

§credentials: BasicCredentials§credentials_path: Option<ShellString>

Trait Implementations§

Source§

impl Clone for BasicAuthConfig

Source§

fn clone(&self) -> BasicAuthConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for BasicAuthConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for BasicAuthConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/calendar/struct.BasicCredentials.html b/i3status_rs/blocks/calendar/struct.BasicCredentials.html new file mode 100644 index 0000000000..34e9b125dc --- /dev/null +++ b/i3status_rs/blocks/calendar/struct.BasicCredentials.html @@ -0,0 +1,42 @@ +BasicCredentials in i3status_rs::blocks::calendar - Rust

Struct BasicCredentials

Source
pub struct BasicCredentials {
+    pub username: Option<String>,
+    pub password: Option<String>,
+}

Fields§

§username: Option<String>§password: Option<String>

Trait Implementations§

Source§

impl Clone for BasicCredentials

Source§

fn clone(&self) -> BasicCredentials

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for BasicCredentials

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for BasicCredentials

Source§

fn default() -> Self

Return BasicCredentials { username: Default::default(), password: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for BasicCredentials

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/calendar/struct.Config.html b/i3status_rs/blocks/calendar/struct.Config.html new file mode 100644 index 0000000000..09f89378ef --- /dev/null +++ b/i3status_rs/blocks/calendar/struct.Config.html @@ -0,0 +1,48 @@ +Config in i3status_rs::blocks::calendar - Rust

Struct Config

Source
pub struct Config {
+    pub next_event_format: FormatConfig,
+    pub ongoing_event_format: FormatConfig,
+    pub no_events_format: FormatConfig,
+    pub redirect_format: FormatConfig,
+    pub fetch_interval: Seconds,
+    pub alternate_events_interval: Seconds,
+    pub events_within_hours: u32,
+    pub source: Vec<SourceConfig>,
+    pub warning_threshold: u32,
+    pub browser_cmd: ShellString,
+}

Fields§

§next_event_format: FormatConfig§ongoing_event_format: FormatConfig§no_events_format: FormatConfig§redirect_format: FormatConfig§fetch_interval: Seconds§alternate_events_interval: Seconds§events_within_hours: u32§source: Vec<SourceConfig>§warning_threshold: u32§browser_cmd: ShellString

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { next_event_format: Default::default(), ongoing_event_format: Default::default(), no_events_format: Default::default(), redirect_format: Default::default(), fetch_interval: 60.into(), alternate_events_interval: 10.into(), events_within_hours: 48, source: Default::default(), warning_threshold: 300, browser_cmd: "xdg-open".into() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/calendar/struct.OAuth2Config.html b/i3status_rs/blocks/calendar/struct.OAuth2Config.html new file mode 100644 index 0000000000..71fdde84be --- /dev/null +++ b/i3status_rs/blocks/calendar/struct.OAuth2Config.html @@ -0,0 +1,47 @@ +OAuth2Config in i3status_rs::blocks::calendar - Rust

Struct OAuth2Config

Source
pub struct OAuth2Config {
+    pub credentials: OAuth2Credentials,
+    pub credentials_path: Option<ShellString>,
+    pub auth_url: String,
+    pub token_url: String,
+    pub auth_token: ShellString,
+    pub redirect_port: u16,
+    pub scopes: Vec<Scope>,
+}

Fields§

§credentials: OAuth2Credentials§credentials_path: Option<ShellString>§auth_url: String§token_url: String§auth_token: ShellString§redirect_port: u16§scopes: Vec<Scope>

Trait Implementations§

Source§

impl Clone for OAuth2Config

Source§

fn clone(&self) -> OAuth2Config

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for OAuth2Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for OAuth2Config

Source§

fn default() -> Self

Return OAuth2Config { credentials: Default::default(), credentials_path: Default::default(), auth_url: Default::default(), token_url: Default::default(), auth_token: "~/.config/i3status-rust/calendar.auth_token".into(), redirect_port: 8080, scopes: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for OAuth2Config

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/calendar/struct.OAuth2Credentials.html b/i3status_rs/blocks/calendar/struct.OAuth2Credentials.html new file mode 100644 index 0000000000..3abe629ab5 --- /dev/null +++ b/i3status_rs/blocks/calendar/struct.OAuth2Credentials.html @@ -0,0 +1,42 @@ +OAuth2Credentials in i3status_rs::blocks::calendar - Rust

Struct OAuth2Credentials

Source
pub struct OAuth2Credentials {
+    pub client_id: Option<String>,
+    pub client_secret: Option<String>,
+}

Fields§

§client_id: Option<String>§client_secret: Option<String>

Trait Implementations§

Source§

impl Clone for OAuth2Credentials

Source§

fn clone(&self) -> OAuth2Credentials

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for OAuth2Credentials

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for OAuth2Credentials

Source§

fn default() -> Self

Return OAuth2Credentials { client_id: Default::default(), client_secret: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for OAuth2Credentials

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/calendar/struct.SourceConfig.html b/i3status_rs/blocks/calendar/struct.SourceConfig.html new file mode 100644 index 0000000000..4596701efc --- /dev/null +++ b/i3status_rs/blocks/calendar/struct.SourceConfig.html @@ -0,0 +1,43 @@ +SourceConfig in i3status_rs::blocks::calendar - Rust

Struct SourceConfig

Source
pub struct SourceConfig {
+    pub url: String,
+    pub auth: AuthConfig,
+    pub calendars: Vec<String>,
+}

Fields§

§url: String§auth: AuthConfig§calendars: Vec<String>

Trait Implementations§

Source§

impl Clone for SourceConfig

Source§

fn clone(&self) -> SourceConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SourceConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for SourceConfig

Source§

fn default() -> Self

Return SourceConfig { url: Default::default(), auth: Default::default(), calendars: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for SourceConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/cpu/fn.run.html b/i3status_rs/blocks/cpu/fn.run.html new file mode 100644 index 0000000000..0cb2bb6d6c --- /dev/null +++ b/i3status_rs/blocks/cpu/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::cpu - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/cpu/index.html b/i3status_rs/blocks/cpu/index.html new file mode 100644 index 0000000000..079aec1cf7 --- /dev/null +++ b/i3status_rs/blocks/cpu/index.html @@ -0,0 +1,36 @@ +i3status_rs::blocks::cpu - Rust

Module cpu

Source
Expand description

CPU statistics

+

§Configuration

+ + + + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders." $icon $utilization "
format_altIf set, block will switch between format and format_alt on every clickNone
intervalUpdate interval in seconds5
info_cpuPercentage of CPU usage, where state is set to info30.0
warning_cpuPercentage of CPU usage, where state is set to warning60.0
critical_cpuPercentage of CPU usage, where state is set to critical90.0
+
+ + + + + + + + +
PlaceholderValueTypeUnit
iconAn iconIcon-
utilizationAverage CPU utilizationNumber%
utilization<N>Utilization of Nth logical CPUNumber%
barchartUtilization of all logical CPUs presented as a barchartText-
frequencyAverage CPU frequency (may be absent if CPU is not supported)NumberHz
frequency<N>Frequency of Nth logical CPU (may be absent if CPU is not supported)NumberHz
max_frequencyMax frequency of all logical CPUsNumberHz
boostCPU turbo boost status (may be absent if CPU is not supported)Text-
+
+ +
ActionDescriptionDefault button
toggle_formatToggles between format and format_altLeft
+

§Example

[[block]]
+block = "cpu"
+interval = 1
+format = " $icon $barchart $utilization "
+format_alt = " $icon $frequency{ $boost|} "
+info_cpu = 20
+warning_cpu = 50
+critical_cpu = 90

§Icons Used

+
    +
  • cpu (as a progression)
  • +
  • cpu_boost_on
  • +
  • cpu_boost_off
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/cpu/sidebar-items.js b/i3status_rs/blocks/cpu/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/cpu/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/cpu/struct.Config.html b/i3status_rs/blocks/cpu/struct.Config.html new file mode 100644 index 0000000000..7fab0ae988 --- /dev/null +++ b/i3status_rs/blocks/cpu/struct.Config.html @@ -0,0 +1,44 @@ +Config in i3status_rs::blocks::cpu - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub format_alt: Option<FormatConfig>,
+    pub interval: Seconds,
+    pub info_cpu: f64,
+    pub warning_cpu: f64,
+    pub critical_cpu: f64,
+}

Fields§

§format: FormatConfig§format_alt: Option<FormatConfig>§interval: Seconds§info_cpu: f64§warning_cpu: f64§critical_cpu: f64

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), format_alt: Default::default(), interval: 5.into(), info_cpu: 30.0, warning_cpu: 60.0, critical_cpu: 90.0 }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/custom/fn.run.html b/i3status_rs/blocks/custom/fn.run.html new file mode 100644 index 0000000000..db081601dd --- /dev/null +++ b/i3status_rs/blocks/custom/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::custom - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/custom/index.html b/i3status_rs/blocks/custom/index.html new file mode 100644 index 0000000000..e9caf04a84 --- /dev/null +++ b/i3status_rs/blocks/custom/index.html @@ -0,0 +1,64 @@ +i3status_rs::blocks::custom - Rust

Module custom

Source
Expand description

The output of a custom shell command

+

For further customisation, use the json option and have the shell command output valid JSON in the schema below:

+
{"icon": "...", "state": "...", "text": "...", "short_text": "..."}
+

icon is optional (default “”) +state is optional, it may be Idle, Info, Good, Warning, Critical (default Idle) +short_text is optional.

+

§Configuration

+ + + + + + + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders."{ $icon|} $text.pango-str() "
commandShell command to execute & displayNone
persistentRun command in the background; update display for each output line of the commandfalse
cycleCommands to execute and change when the button is clickedNone
intervalUpdate interval in seconds (or “once” to update only once)10
jsonUse JSON from command output to format the block. If the JSON is not valid, the block will error out.false
watch_filesWatch files to trigger update on file modification. Supports path expansions e.g. ~.None
hide_when_emptyHides the block when the command output (or json text field) is emptyfalse
shellSpecify the shell to use when running commands$SHELL if set, otherwise fallback to sh
+
+ + + +
PlaceholderValueTypeUnit
iconValue of icon field from JSON output when it’s non-emptyIcon-
textOutput of the script or text field from JSON outputText
short_textshort_text field from JSON outputText
+
+ +
ActionDefault button
cycleLeft
+

§Examples

+

Display temperature, update every 10 seconds:

+
[[block]]
+block = "custom"
+command = ''' cat /sys/class/thermal/thermal_zone0/temp | awk '{printf("%.1f\n",$1/1000)}' '''
+

Cycle between “ON” and “OFF”, update every 1 second, run next cycle command when block is clicked:

+
[[block]]
+block = "custom"
+cycle = ["echo ON", "echo OFF"]
+interval = 1
+[[block.click]]
+button = "left"
+action = "cycle"
+

Use JSON output:

+
[[block]]
+block = "custom"
+command = "echo '{\"icon\":\"weather_thunder\",\"state\":\"Critical\", \"text\": \"Danger!\"}'"
+json = true
+

Display kernel, update the block only once:

+
[[block]]
+block = "custom"
+command = "uname -r"
+interval = "once"
+

Display the screen brightness on an intel machine and update this only when pkill -SIGRTMIN+4 i3status-rs is called:

+
[[block]]
+block = "custom"
+command = ''' cat /sys/class/backlight/intel_backlight/brightness | awk '{print $1}' '''
+signal = 4
+interval = "once"
+

Update block when one or more specified files are modified:

+
[[block]]
+block = "custom"
+command = "cat custom_status"
+watch_files = ["custom_status"]
+interval = "once"

§TODO:

+
    +
  • Use shellexpand
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/custom/sidebar-items.js b/i3status_rs/blocks/custom/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/custom/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/custom/struct.Config.html b/i3status_rs/blocks/custom/struct.Config.html new file mode 100644 index 0000000000..f6e9167257 --- /dev/null +++ b/i3status_rs/blocks/custom/struct.Config.html @@ -0,0 +1,47 @@ +Config in i3status_rs::blocks::custom - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub command: Option<String>,
+    pub persistent: bool,
+    pub cycle: Option<Vec<String>>,
+    pub interval: Seconds,
+    pub json: bool,
+    pub hide_when_empty: bool,
+    pub shell: Option<String>,
+    pub watch_files: Vec<ShellString>,
+}

Fields§

§format: FormatConfig§command: Option<String>§persistent: bool§cycle: Option<Vec<String>>§interval: Seconds§json: bool§hide_when_empty: bool§shell: Option<String>§watch_files: Vec<ShellString>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), command: Default::default(), persistent: Default::default(), cycle: Default::default(), interval: 10.into(), json: Default::default(), hide_when_empty: Default::default(), shell: Default::default(), watch_files: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/custom_dbus/fn.run.html b/i3status_rs/blocks/custom_dbus/fn.run.html new file mode 100644 index 0000000000..83f953cc9c --- /dev/null +++ b/i3status_rs/blocks/custom_dbus/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::custom_dbus - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/custom_dbus/index.html b/i3status_rs/blocks/custom_dbus/index.html new file mode 100644 index 0000000000..d70fcbec78 --- /dev/null +++ b/i3status_rs/blocks/custom_dbus/index.html @@ -0,0 +1,38 @@ +i3status_rs::blocks::custom_dbus - Rust

Module custom_dbus

Source
Expand description

A block controlled by the DBus

+

This block creates a new DBus object in rs.i3status service. This object implements +rs.i3status.custom interface which allows you to set block’s icon, text and state.

+

Output of busctl --user introspect rs.i3status /<path> rs.i3status.custom:

+
NAME                                TYPE      SIGNATURE RESULT/VALUE FLAGS
+rs.i3status.custom                  interface -         -            -
+.SetIcon                            method    s         s            -
+.SetState                           method    s         s            -
+.SetText                            method    ss        s            -

§Configuration

+ +
KeyValuesDefault
formatA string to customise the output of this block."{ $icon|}{ $text.pango-str()|} "
+
+ + + +
PlaceholderValueTypeUnit
iconValue of icon set via SetIcon if the value is non-empty string.Icon-
textValue of the first string from SetTextText-
short_textValue of the second string from SetTextText-
+

§Example

+

Config:

+
[[block]]
+block = "custom_dbus"
+path = "/my_path"
+

Usage:

+
# set full text to 'hello' and short text to 'hi'
+busctl --user call rs.i3status /my_path rs.i3status.custom SetText ss hello hi
+# set icon to 'music'
+busctl --user call rs.i3status /my_path rs.i3status.custom SetIcon s music
+# set state to 'good'
+busctl --user call rs.i3status /my_path rs.i3status.custom SetState s good
+

Because it’s impossible to publish objects to the same name from different +processes, having multiple dbus blocks in different bars won’t work. As a workaround, +you can set the env var I3RS_DBUS_NAME to set the interface a bar works on to +differentiate between different processes. For example, setting this to ‘top’, will allow you +to use rs.i3status.top.

+

§TODO

+
    +
  • Send a signal on click?
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/custom_dbus/sidebar-items.js b/i3status_rs/blocks/custom_dbus/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/custom_dbus/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/custom_dbus/struct.Config.html b/i3status_rs/blocks/custom_dbus/struct.Config.html new file mode 100644 index 0000000000..e4924d8148 --- /dev/null +++ b/i3status_rs/blocks/custom_dbus/struct.Config.html @@ -0,0 +1,37 @@ +Config in i3status_rs::blocks::custom_dbus - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub path: String,
+}

Fields§

§format: FormatConfig§path: String

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Config

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/disk_iostats/fn.run.html b/i3status_rs/blocks/disk_iostats/fn.run.html new file mode 100644 index 0000000000..8cb6fec68f --- /dev/null +++ b/i3status_rs/blocks/disk_iostats/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::disk_iostats - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/disk_iostats/index.html b/i3status_rs/blocks/disk_iostats/index.html new file mode 100644 index 0000000000..e9f9310197 --- /dev/null +++ b/i3status_rs/blocks/disk_iostats/index.html @@ -0,0 +1,21 @@ +i3status_rs::blocks::disk_iostats - Rust

Module disk_iostats

Source
Expand description

Disk I/O statistics

+

§Configuration

+ + + + +
KeyValuesDefault
deviceBlock device name to monitor (as specified in /dev/)If not set, device will be automatically selected every interval
formatA string to customise the output of this block. See below for available placeholders." $icon $speed_read.eng(prefix:K) $speed_write.eng(prefix:K) "
intervalUpdate interval in seconds2
missing_formatSame as format but for when the device is missing" × "
+
+ + + + +
PlaceholderValueTypeUnit
iconA static iconIcon-
deviceThe name of deviceText-
speed_readRead speedNumberBytes per second
speed_writeWrite speedNumberBytes per second
+

§Examples

[[block]]
+block = "disk_iostats"
+device = "sda"
+format = " $icon $speed_write.eng(prefix:K) "

§Icons Used

+
    +
  • disk_drive
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/disk_iostats/sidebar-items.js b/i3status_rs/blocks/disk_iostats/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/disk_iostats/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/disk_iostats/struct.Config.html b/i3status_rs/blocks/disk_iostats/struct.Config.html new file mode 100644 index 0000000000..4ae084f246 --- /dev/null +++ b/i3status_rs/blocks/disk_iostats/struct.Config.html @@ -0,0 +1,42 @@ +Config in i3status_rs::blocks::disk_iostats - Rust

Struct Config

Source
pub struct Config {
+    pub device: Option<String>,
+    pub interval: Seconds,
+    pub format: FormatConfig,
+    pub missing_format: FormatConfig,
+}

Fields§

§device: Option<String>§interval: Seconds§format: FormatConfig§missing_format: FormatConfig

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { device: Default::default(), interval: 2.into(), format: Default::default(), missing_format: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/disk_space/enum.InfoType.html b/i3status_rs/blocks/disk_space/enum.InfoType.html new file mode 100644 index 0000000000..a2d4b7dd2d --- /dev/null +++ b/i3status_rs/blocks/disk_space/enum.InfoType.html @@ -0,0 +1,42 @@ +InfoType in i3status_rs::blocks::disk_space - Rust

Enum InfoType

Source
pub enum InfoType {
+    Available,
+    Free,
+    Used,
+}

Variants§

§

Available

§

Free

§

Used

Trait Implementations§

Source§

impl Clone for InfoType

Source§

fn clone(&self) -> InfoType

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for InfoType

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for InfoType

Source§

fn default() -> Self

Return InfoType::Available

+
Source§

impl<'de> Deserialize<'de> for InfoType

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Copy for InfoType

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/disk_space/fn.run.html b/i3status_rs/blocks/disk_space/fn.run.html new file mode 100644 index 0000000000..20311be193 --- /dev/null +++ b/i3status_rs/blocks/disk_space/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::disk_space - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/disk_space/index.html b/i3status_rs/blocks/disk_space/index.html new file mode 100644 index 0000000000..63cd311552 --- /dev/null +++ b/i3status_rs/blocks/disk_space/index.html @@ -0,0 +1,45 @@ +i3status_rs::blocks::disk_space - Rust

Module disk_space

Source
Expand description

Disk usage statistics

+

§Configuration

+ + + + + + + + +
KeyValuesDefault
pathPath to collect information from. Supports path expansions e.g. ~."/"
intervalUpdate time in seconds20
formatA string to customise the output of this block. See below for available placeholders." $icon $available "
format_altIf set, block will switch between format and format_alt on every clickNone
warningA value which will trigger warning block state20.0
alertA value which will trigger critical block state10.0
info_typeDetermines which information will affect the block state. Possible values are "available", "free" and "used""available"
alert_unitThe unit of alert and warning options. If not set, percents are used. Possible values are "B", "KB", "KiB", "MB", "MiB", "GB", "Gib", "TB" and "TiB"None
+
+ + + + + + + +
PlaceholderValueTypeUnit
iconA static iconIcon-
pathThe value of path optionText-
percentageFree or used percentage. Depends on info_typeNumber%
totalTotal disk spaceNumberBytes
usedUsed disk spaceNumberBytes
freeFree disk spaceNumberBytes
availableAvailable disk space (free disk space minus reserved system space)NumberBytes
+
+ +
ActionDescriptionDefault button
toggle_formatToggles between format and format_altLeft
+

§Examples

[[block]]
+block = "disk_space"
+info_type = "available"
+alert_unit = "GB"
+alert = 10.0
+warning = 15.0
+format = " $icon $available "
+format_alt = " $icon $available / $total "
+

Update block on right click:

+
[[block]]
+block = "disk_space"
+[[block.click]]
+button = "right"
+update = true
+

Show the block only if less than 10GB is available:

+
[[block]]
+block = "disk_space"
+format = " $free.eng(range:..10e9) |"

§Icons Used

+
    +
  • disk_drive
  • +
+

Structs§

Config

Enums§

InfoType

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/disk_space/sidebar-items.js b/i3status_rs/blocks/disk_space/sidebar-items.js new file mode 100644 index 0000000000..3ea6e166e6 --- /dev/null +++ b/i3status_rs/blocks/disk_space/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["InfoType"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/disk_space/struct.Config.html b/i3status_rs/blocks/disk_space/struct.Config.html new file mode 100644 index 0000000000..00aa9c488e --- /dev/null +++ b/i3status_rs/blocks/disk_space/struct.Config.html @@ -0,0 +1,46 @@ +Config in i3status_rs::blocks::disk_space - Rust

Struct Config

Source
pub struct Config {
+    pub path: ShellString,
+    pub info_type: InfoType,
+    pub format: FormatConfig,
+    pub format_alt: Option<FormatConfig>,
+    pub alert_unit: Option<String>,
+    pub interval: Seconds,
+    pub warning: f64,
+    pub alert: f64,
+}

Fields§

§path: ShellString§info_type: InfoType§format: FormatConfig§format_alt: Option<FormatConfig>§alert_unit: Option<String>§interval: Seconds§warning: f64§alert: f64

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { path: "/".into(), info_type: Default::default(), format: Default::default(), format_alt: Default::default(), alert_unit: Default::default(), interval: 20.into(), warning: 20.0, alert: 10.0 }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/docker/fn.run.html b/i3status_rs/blocks/docker/fn.run.html new file mode 100644 index 0000000000..d5feef9b5e --- /dev/null +++ b/i3status_rs/blocks/docker/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::docker - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/docker/index.html b/i3status_rs/blocks/docker/index.html new file mode 100644 index 0000000000..255394b43f --- /dev/null +++ b/i3status_rs/blocks/docker/index.html @@ -0,0 +1,22 @@ +i3status_rs::blocks::docker - Rust

Module docker

Source
Expand description

Local docker daemon status

+

§Configuration

+ + + +
KeyValuesDefault
intervalUpdate interval, in seconds.5
formatA string to customise the output of this block. See below for available placeholders." $icon $running.eng(w:1) "
socket_pathThe path to the docker socket. Supports path expansions e.g. ~."/var/run/docker.sock"
+
+ + + + + + +
KeyValueTypeUnit
iconA static iconIcon-
totalTotal containers on the hostNumber-
runningContainers running on the hostNumber-
stoppedContainers stopped on the hostNumber-
pausedContainers paused on the hostNumber-
imagesTotal images on the hostNumber-
+

§Example

[[block]]
+block = "docker"
+interval = 2
+format = " $icon $running/$total "

§Icons Used

+
    +
  • docker
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/docker/sidebar-items.js b/i3status_rs/blocks/docker/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/docker/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/docker/struct.Config.html b/i3status_rs/blocks/docker/struct.Config.html new file mode 100644 index 0000000000..d15142a8b5 --- /dev/null +++ b/i3status_rs/blocks/docker/struct.Config.html @@ -0,0 +1,41 @@ +Config in i3status_rs::blocks::docker - Rust

Struct Config

Source
pub struct Config {
+    pub interval: Seconds,
+    pub format: FormatConfig,
+    pub socket_path: ShellString,
+}

Fields§

§interval: Seconds§format: FormatConfig§socket_path: ShellString

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { interval: 5.into(), format: Default::default(), socket_path: "/var/run/docker.sock".into() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/enum.BlockConfig.html b/i3status_rs/blocks/enum.BlockConfig.html new file mode 100644 index 0000000000..ca2fe25a7c --- /dev/null +++ b/i3status_rs/blocks/enum.BlockConfig.html @@ -0,0 +1,84 @@ +BlockConfig in i3status_rs::blocks - Rust

Enum BlockConfig

Source
pub enum BlockConfig {
+
Show 45 variants amd_gpu(Config), + backlight(Config), + battery(Config), + bluetooth(Config), + calendar(Config), + cpu(Config), + custom(Config), + custom_dbus(Config), + disk_iostats(Config), + disk_space(Config), + docker(Config), + external_ip(Config), + focused_window(Config), + github(Config), + hueshift(Config), + kdeconnect(Config), + load(Config), + maildir(Config), + menu(Config), + memory(Config), + music(Config), + net(Config), + notify(Config), + notmuch(Config), + nvidia_gpu(Config), + packages(Config), + pomodoro(Config), + privacy(Config), + rofication(Config), + service_status(Config), + scratchpad(Config), + sound(Config), + speedtest(Config), + keyboard_layout(Config), + taskwarrior(Config), + temperature(Config), + time(Config), + tea_timer(Config), + toggle(Config), + uptime(Config), + vpn(Config), + watson(Config), + weather(Config), + xrandr(Config), + Err(&'static str, Error), +
}

Variants§

§

amd_gpu(Config)

§

backlight(Config)

§

battery(Config)

§

bluetooth(Config)

§

calendar(Config)

§

cpu(Config)

§

custom(Config)

§

custom_dbus(Config)

§

disk_iostats(Config)

§

disk_space(Config)

§

docker(Config)

§

external_ip(Config)

§

focused_window(Config)

§

github(Config)

§

hueshift(Config)

§

kdeconnect(Config)

§

load(Config)

§

maildir(Config)

§

menu(Config)

§

memory(Config)

§

music(Config)

§

net(Config)

§

notify(Config)

§

notmuch(Config)

§

nvidia_gpu(Config)

§

packages(Config)

§

pomodoro(Config)

§

privacy(Config)

§

rofication(Config)

§

service_status(Config)

§

scratchpad(Config)

§

sound(Config)

§

speedtest(Config)

§

keyboard_layout(Config)

§

taskwarrior(Config)

§

temperature(Config)

§

time(Config)

§

tea_timer(Config)

§

toggle(Config)

§

uptime(Config)

§

vpn(Config)

§

watson(Config)

§

weather(Config)

§

xrandr(Config)

§

Err(&'static str, Error)

Implementations§

Source§

impl BlockConfig

Source

pub fn name(&self) -> &'static str

Source

pub fn spawn( + self, + api: CommonApi, + futures: &mut FuturesUnordered<Pin<Box<dyn Future<Output = ()>>>>, +)

Trait Implementations§

Source§

impl Debug for BlockConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for BlockConfig

Source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/external_ip/fn.run.html b/i3status_rs/blocks/external_ip/fn.run.html new file mode 100644 index 0000000000..bfb5491635 --- /dev/null +++ b/i3status_rs/blocks/external_ip/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::external_ip - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/external_ip/index.html b/i3status_rs/blocks/external_ip/index.html new file mode 100644 index 0000000000..41724bf2ef --- /dev/null +++ b/i3status_rs/blocks/external_ip/index.html @@ -0,0 +1,49 @@ +i3status_rs::blocks::external_ip - Rust

Module external_ip

Source
Expand description

External IP address and various information about it

+

§Configuration

+ + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders." $ip $country_flag "
intervalInterval in seconds for automatic updates300
with_network_managerIf ‘true’, listen for NetworkManager events and update the IP immediately if there was a changetrue
use_ipv4If ‘true’, use IPv4 for obtaining all infofalse
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyValueTypeUnit
ipThe external IP address, as seen from a remote serverText-
versionIPv4 or IPv6Text-
cityCity name, such as “San Francisco”Text-
regionRegion name, such as “California”Text-
region_codeRegion code, such as “CA” for CaliforniaText-
countryCountry code (2 letter, ISO 3166-1 alpha-2)Text-
country_nameShort country nameText-
country_codeCountry code (2 letter, ISO 3166-1 alpha-2)Text-
country_code_iso3Country code (3 letter, ISO 3166-1 alpha-3)Text-
country_capitalCapital of the countryText-
country_tldCountry specific TLD (top-level domain)Text-
continent_codeContinent codeText-
in_euRegion code, such as “CA”Flag-
postalZIP / Postal codeText-
latitudeLatitudeNumber- (TODO: make degrees?)
longitudeLongitudeNumber- (TODO: make degrees?)
timezoneCityText-
utc_offsetUTC offset (with daylight saving time) as +HHMM or -HHMM (HH is hours, MM is minutes)Text-
country_calling_codeCountry calling code (dial in code, comma separated)Text-
currencyCurrency code (ISO 4217)Text-
currency_nameCurrency nameText-
languagesLanguages spoken (comma separated 2 or 3 letter ISO 639 code with optional hyphen separated country suffix)Text-
country_areaArea of the country (in sq km)Number-
country_populationPopulation of the countryNumber-
timezoneTime zoneText-
orgOrganizationText-
asnAutonomous system (AS)Text-
country_flagFlag of the countryText (glyph)-
+

§Example

[[block]]
+block = "external_ip"
+format = " $ip $country_code "

§Notes

+

All the information comes from https://ipapi.co/json/ +Check their documentation here: https://ipapi.co/api/#complete-location5

+

The IP is queried, 1) When i3status-rs starts, 2) When a signal is received +on D-Bus about a network configuration change, 3) Every 5 minutes. This +periodic refresh exists to catch IP updates that don’t trigger a notification, +for example due to a IP refresh at the router.

+

Flags: They are not icons but unicode glyphs. You will need a font that +includes them. Tested with: https://www.babelstone.co.uk/Fonts/Flags.html

+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/external_ip/sidebar-items.js b/i3status_rs/blocks/external_ip/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/external_ip/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/external_ip/struct.Config.html b/i3status_rs/blocks/external_ip/struct.Config.html new file mode 100644 index 0000000000..dc114acfb9 --- /dev/null +++ b/i3status_rs/blocks/external_ip/struct.Config.html @@ -0,0 +1,42 @@ +Config in i3status_rs::blocks::external_ip - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub interval: Seconds,
+    pub with_network_manager: bool,
+    pub use_ipv4: bool,
+}

Fields§

§format: FormatConfig§interval: Seconds§with_network_manager: bool§use_ipv4: bool

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), interval: 300.into(), with_network_manager: true, use_ipv4: false }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/focused_window/enum.Driver.html b/i3status_rs/blocks/focused_window/enum.Driver.html new file mode 100644 index 0000000000..6772322b5f --- /dev/null +++ b/i3status_rs/blocks/focused_window/enum.Driver.html @@ -0,0 +1,40 @@ +Driver in i3status_rs::blocks::focused_window - Rust

Enum Driver

Source
pub enum Driver {
+    Auto,
+    SwayIpc,
+    WlrToplevelManagement,
+}

Variants§

§

Auto

§

SwayIpc

§

WlrToplevelManagement

Trait Implementations§

Source§

impl Debug for Driver

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Driver

Source§

fn default() -> Self

Return Driver::Auto

+
Source§

impl<'de> Deserialize<'de> for Driver

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Driver

§

impl RefUnwindSafe for Driver

§

impl Send for Driver

§

impl Sync for Driver

§

impl Unpin for Driver

§

impl UnwindSafe for Driver

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/focused_window/fn.run.html b/i3status_rs/blocks/focused_window/fn.run.html new file mode 100644 index 0000000000..e4ea8d76d3 --- /dev/null +++ b/i3status_rs/blocks/focused_window/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::focused_window - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/focused_window/index.html b/i3status_rs/blocks/focused_window/index.html new file mode 100644 index 0000000000..2c4001ef9e --- /dev/null +++ b/i3status_rs/blocks/focused_window/index.html @@ -0,0 +1,22 @@ +i3status_rs::blocks::focused_window - Rust

Module focused_window

Source
Expand description

Currently focused window

+

This block displays the title and/or the active marks (when used with sway/i3) of the currently +focused window. Supported WMs are: sway, i3 and most wlroots-based compositors. See driver +option for more info.

+

§Configuration

+ + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders." $title.str(max_w:21) |"
driverWhich driver to use. Available values: sway_ipc - for i3 and sway, wlr_toplevel_management - for Wayland compositors that implement wlr-foreign-toplevel-management-unstable-v1, auto - try to automatically guess which driver to use."auto"
+
+ + + +
PlaceholderValueTypeUnit
titleWindow’s title (may be absent)Text-
marksWindow’s marks (present only with sway/i3)Text-
visible_marksWindow’s marks that do not start with _ (present only with sway/i3)Text-
+

§Example

[[block]]
+block = "focused_window"
+[block.format]
+full = " $title.str(max_w:15) |"
+short = " $title.str(max_w:10) |"
+

This example instead of hiding block when the window’s title is empty displays “Missing”

+
[[block]]
+block = "focused_window"
+format = " $title.str(0,21) | Missing "

Structs§

Config

Enums§

Driver

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/focused_window/sidebar-items.js b/i3status_rs/blocks/focused_window/sidebar-items.js new file mode 100644 index 0000000000..a0b43ff439 --- /dev/null +++ b/i3status_rs/blocks/focused_window/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Driver"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/focused_window/struct.Config.html b/i3status_rs/blocks/focused_window/struct.Config.html new file mode 100644 index 0000000000..558967ea41 --- /dev/null +++ b/i3status_rs/blocks/focused_window/struct.Config.html @@ -0,0 +1,40 @@ +Config in i3status_rs::blocks::focused_window - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub driver: Driver,
+}

Fields§

§format: FormatConfig§driver: Driver

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), driver: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/github/fn.run.html b/i3status_rs/blocks/github/fn.run.html new file mode 100644 index 0000000000..f600310133 --- /dev/null +++ b/i3status_rs/blocks/github/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::github - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/github/index.html b/i3status_rs/blocks/github/index.html new file mode 100644 index 0000000000..3c5879b543 --- /dev/null +++ b/i3status_rs/blocks/github/index.html @@ -0,0 +1,45 @@ +i3status_rs::blocks::github - Rust

Module github

Source
Expand description

The number of GitHub notifications

+

This block shows the unread notification count for a GitHub account. A GitHub personal access token with the “notifications” scope is required, and must be passed using the I3RS_GITHUB_TOKEN environment variable or token configuration option. Optionally the colour of the block is determined by the highest notification in the following lists from highest to lowest: critical,warning,info,good

+

§Configuration

+ + + + + + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders." $icon $total.eng(w:1) "
intervalUpdate interval in seconds30
tokenA GitHub personal access token with the “notifications” scopeNone
hide_if_total_is_zeroHide this block if the total count of notifications is zerofalse
criticalList of notification types that change the block to the critical colourNone
warningList of notification types that change the block to the warning colourNone
infoList of notification types that change the block to the info colourNone
goodList of notification types that change the block to the good colourNone
+
+

All the placeholders are numbers without a unit.

+
+ + + + + + + + + + + + + + +
PlaceholderValue
iconA static icon
totalThe total number of notifications
assignYou were assigned to the issue
authorYou created the thread
commentYou commented on the thread
ci_activityA GitHub Actions workflow run that you triggered was completed
invitationYou accepted an invitation to contribute to the repository
manualYou subscribed to the thread (via an issue or pull request)
mentionYou were specifically @mentioned in the content
review_requestedYou, or a team you’re a member of, were requested to review a pull request
security_alertGitHub discovered a security vulnerability in your repository
state_changeYou changed the thread state (for example, closing an issue or merging a pull request)
subscribedYou’re watching the repository
team_mentionYou were on a team that was mentioned
+

§Examples

[[block]]
+block = "github"
+format = " $icon $total.eng(w:1)|$mention.eng(w:1) "
+interval = 60
+token = "..."
[[block]]
+block = "github"
+token = "..."
+format = " $icon $total.eng(w:1) "
+info = ["total"]
+warning = ["mention","review_requested"]
+hide_if_total_is_zero = true

§Icons Used

+
    +
  • github
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/github/sidebar-items.js b/i3status_rs/blocks/github/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/github/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/github/struct.Config.html b/i3status_rs/blocks/github/struct.Config.html new file mode 100644 index 0000000000..5e0a3579c4 --- /dev/null +++ b/i3status_rs/blocks/github/struct.Config.html @@ -0,0 +1,46 @@ +Config in i3status_rs::blocks::github - Rust

Struct Config

Source
pub struct Config {
+    pub interval: Seconds,
+    pub format: FormatConfig,
+    pub token: Option<String>,
+    pub hide_if_total_is_zero: bool,
+    pub good: Option<Vec<String>>,
+    pub info: Option<Vec<String>>,
+    pub warning: Option<Vec<String>>,
+    pub critical: Option<Vec<String>>,
+}

Fields§

§interval: Seconds§format: FormatConfig§token: Option<String>§hide_if_total_is_zero: bool§good: Option<Vec<String>>§info: Option<Vec<String>>§warning: Option<Vec<String>>§critical: Option<Vec<String>>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { interval: 60.into(), format: Default::default(), token: Default::default(), hide_if_total_is_zero: Default::default(), good: Default::default(), info: Default::default(), warning: Default::default(), critical: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/hueshift/enum.HueShifter.html b/i3status_rs/blocks/hueshift/enum.HueShifter.html new file mode 100644 index 0000000000..b369b6b6ba --- /dev/null +++ b/i3status_rs/blocks/hueshift/enum.HueShifter.html @@ -0,0 +1,43 @@ +HueShifter in i3status_rs::blocks::hueshift - Rust

Enum HueShifter

Source
pub enum HueShifter {
+    Redshift,
+    Sct,
+    Gammastep,
+    Wlsunset,
+    WlGammarelay,
+    WlGammarelayRs,
+}

Variants§

§

Redshift

§

Sct

§

Gammastep

§

Wlsunset

§

WlGammarelay

§

WlGammarelayRs

Trait Implementations§

Source§

impl Clone for HueShifter

Source§

fn clone(&self) -> HueShifter

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for HueShifter

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for HueShifter

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Copy for HueShifter

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/hueshift/fn.run.html b/i3status_rs/blocks/hueshift/fn.run.html new file mode 100644 index 0000000000..2f38646734 --- /dev/null +++ b/i3status_rs/blocks/hueshift/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::hueshift - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/hueshift/index.html b/i3status_rs/blocks/hueshift/index.html new file mode 100644 index 0000000000..0634c21617 --- /dev/null +++ b/i3status_rs/blocks/hueshift/index.html @@ -0,0 +1,42 @@ +i3status_rs::blocks::hueshift - Rust

Module hueshift

Source
Expand description

Manage display temperature

+

This block displays the current color temperature in Kelvin. When scrolling upon the block the color temperature is changed. +A left click on the block sets the color temperature to click_temp that is by default to 6500K. +A right click completely resets the color temperature to its default value (6500K).

+

§Configuration

+ + + + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders." $temperature "
stepThe step color temperature is in/decreased in Kelvin.100
hue_shifterProgram used to control screen color.Detect automatically
max_tempMax color temperature in Kelvin.10000
min_tempMin color temperature in Kelvin.1000
click_tempLeft click color temperature in Kelvin.6500
+
+ +
PlaceholderValueTypeUnit
temperatureCurrent temperatureNumber-
+
+ + + + +
ActionDefault button
set_click_tempLeft
resetRight
temperature_upWheel Up
temperature_downWheel Down
+

§Available Hue Shifters

+ + + + + + +
NameSupports
"redshift"X11
"sct"X11
"gammastep"X11 and Wayland
"wl_gammarelay"Wayland
"wl_gammarelay_rs"Wayland
"wlsunset"Wayland
+
+

Note that at the moment, only wl_gammarelay and +wl_gammarelay_rs +subscribe to the events and update the bar when the temperature is modified externally. Also, +these are the only drivers at the moment that work under Wayland without flickering.

+

§Example

[[block]]
+block = "hueshift"
+hue_shifter = "redshift"
+step = 50
+click_temp = 3500
+

A hard limit is set for the max_temp to 10000K and the same for the min_temp which is 1000K. +The step has a hard limit as well, defined to 500K to avoid too brutal changes.

+

Structs§

Config

Enums§

HueShifter

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/hueshift/sidebar-items.js b/i3status_rs/blocks/hueshift/sidebar-items.js new file mode 100644 index 0000000000..4fb47737d9 --- /dev/null +++ b/i3status_rs/blocks/hueshift/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["HueShifter"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/hueshift/struct.Config.html b/i3status_rs/blocks/hueshift/struct.Config.html new file mode 100644 index 0000000000..11b92efe17 --- /dev/null +++ b/i3status_rs/blocks/hueshift/struct.Config.html @@ -0,0 +1,46 @@ +Config in i3status_rs::blocks::hueshift - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub interval: Seconds,
+    pub max_temp: u16,
+    pub min_temp: u16,
+    pub current_temp: u16,
+    pub hue_shifter: Option<HueShifter>,
+    pub step: u16,
+    pub click_temp: u16,
+}

Fields§

§format: FormatConfig§interval: Seconds§max_temp: u16§min_temp: u16§current_temp: u16§hue_shifter: Option<HueShifter>§step: u16§click_temp: u16

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), interval: 5.into(), max_temp: 10_000, min_temp: 1_000, current_temp: 6_500, hue_shifter: Default::default(), step: 100, click_temp: 6_500 }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/index.html b/i3status_rs/blocks/index.html new file mode 100644 index 0000000000..f5df62b81d --- /dev/null +++ b/i3status_rs/blocks/index.html @@ -0,0 +1,26 @@ +i3status_rs::blocks - Rust

Module blocks

Source
Expand description

The collection of blocks

+

Blocks are defined as a TOML array of tables: [[block]]

+
+ + + + + + + + + + + +
KeyDescriptionDefault
blockName of the i3status-rs block you want to use. See modules below for valid block names.-
signalSignal value that causes an update for this block with 0 corresponding to -SIGRTMIN+0 and the largest value being -SIGRTMAXNone
if_commandOnly display the block if the supplied command returns 0 on startup.None
merge_with_nextIf true this will group the block with the next one, so rendering such as alternating_tint will apply to the whole groupfalse
icons_formatOverrides global icons_formatNone
error_formatOverrides global error_formatNone
error_fullscreen_formatOverrides global error_fullscreen_formatNone
error_intervalHow long to wait until restarting the block after an error occurred.5
[block.theme_overrides]Same as the top-level config option, but for this block only. Refer to Themes and Icons below.None
[block.icons_overrides]Same as the top-level config option, but for this block only. Refer to Themes and Icons below.None
[[block.click]]Set or override click action for the block. See below for details.Block default / None
+
+

Per block click configuration [[block.click]]:

+
+ + + + + + +
KeyDescriptionDefault
buttonleft, middle, right, up/wheel_up, down/wheel_down, wheel_left, wheel_right, forward, back or double_left.-
widgetTo which part of the block this entry applies (accepts regex)"block"
cmdCommand to run when the mouse button event is detected.None
actionWhich block action to triggerNone
syncWhether to wait for command to exit or not.false
updateWhether to update the block on click.false
+

Modules§

amd_gpu
Display the stats of your AMD GPU
backlight
The brightness of a backlight device
battery
Information about the internal power supply
bluetooth
Monitor Bluetooth device
calendar
Calendar
cpu
CPU statistics
custom
The output of a custom shell command
custom_dbus
A block controlled by the DBus
disk_iostats
Disk I/O statistics
disk_space
Disk usage statistics
docker
Local docker daemon status
external_ip
External IP address and various information about it
focused_window
Currently focused window
github
The number of GitHub notifications
hueshift
Manage display temperature
kdeconnect
KDEConnect indicator
keyboard_layout
Keyboard layout indicator
load
System load average
maildir
Unread mail. Only supports maildir format.
memory
Memory and swap usage
menu
A custom menu
music
The current song title and artist
net
Network information
notify
Display and toggle the state of notifications daemon
notmuch
Count of notmuch messages
nvidia_gpu
Display the stats of your NVidia GPU
packages
Pending updates for different package manager like apt, pacman, etc.
pomodoro
A pomodoro timer
privacy
Privacy Monitor
rofication
The number of pending notifications in rofication-daemon
scratchpad
Scratchpad indicator
service_status
Display the status of a service
sound
Volume level
speedtest
Ping, download, and upload speeds
taskwarrior
The number of tasks from the taskwarrior list
tea_timer
Timer
temperature
The system temperature
time
The current time.
toggle
A Toggle block
uptime
System’s uptime
vpn
Shows the current connection status for VPN networks
watson
Watson statistics
weather
Current weather
xrandr
X11 screen information

Structs§

BlockError
An error which originates from a block
CommonApi

Enums§

BlockConfig

Type Aliases§

BlockAction
\ No newline at end of file diff --git a/i3status_rs/blocks/kdeconnect/fn.run.html b/i3status_rs/blocks/kdeconnect/fn.run.html new file mode 100644 index 0000000000..72abfc5adf --- /dev/null +++ b/i3status_rs/blocks/kdeconnect/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::kdeconnect - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/kdeconnect/index.html b/i3status_rs/blocks/kdeconnect/index.html new file mode 100644 index 0000000000..26d773f8e1 --- /dev/null +++ b/i3status_rs/blocks/kdeconnect/index.html @@ -0,0 +1,40 @@ +i3status_rs::blocks::kdeconnect - Rust

Module kdeconnect

Source
Expand description

KDEConnect indicator

+

Display info from the currently connected device in KDEConnect, updated asynchronously.

+

Block colours are updated based on the battery level, unless all bat_* thresholds are set to 0, +in which case the block colours will depend on the notification count instead.

+

§Configuration

+ + + + + + + + +
KeyValuesDefault
device_idDevice ID as per the output of kdeconnect --list-devices.Chooses the first found device, if any.
formatA string to customise the output of this block. See below for available placeholders." $icon $name{ $bat_icon $bat_charge|}{ $notif_icon|} "
format_disconnectedSame as format but when device is disconnected" $icon "
format_missingSame as format but when device does not exist" $icon x "
bat_infoMin battery level below which state is set to info.60
bat_goodMin battery level below which state is set to good.60
bat_warningMin battery level below which state is set to warning.30
bat_criticalMin battery level below which state is set to critical.15
+
+ + + + + + + + + +
PlaceholderValueTypeUnit
iconIcon based on connection’s statusIcon-
bat_iconBattery level indicator (only when connected and if supported)Icon-
bat_chargeBattery charge level (only when connected and if supported)Number%
network_iconCell Network indicator (only when connected and if supported)Icon-
network_typeCell Network type (only when connected and if supported)Text-
network_strengthCell Network level (only when connected and if supported)Number%
notif_iconOnly when connected and there are notificationsIcon-
notif_countNumber of notifications on your phone (only when connected and non-zero)Number-
nameName of your device as reported by KDEConnect (if available)Text-
+

§Example

+

Do not show the name, do not set the “good” state.

+
[[block]]
+block = "kdeconnect"
+format = " $icon {$bat_icon $bat_charge |}{$notif_icon |}{$network_icon$network_strength $network_type |}"
+bat_good = 101

§Icons Used

+
    +
  • bat (as a progression)
  • +
  • bat_charging (as a progression)
  • +
  • net_cellular (as a progression)
  • +
  • notification
  • +
  • phone
  • +
  • phone_disconnected
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/kdeconnect/sidebar-items.js b/i3status_rs/blocks/kdeconnect/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/kdeconnect/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/kdeconnect/struct.Config.html b/i3status_rs/blocks/kdeconnect/struct.Config.html new file mode 100644 index 0000000000..03697aff1c --- /dev/null +++ b/i3status_rs/blocks/kdeconnect/struct.Config.html @@ -0,0 +1,46 @@ +Config in i3status_rs::blocks::kdeconnect - Rust

Struct Config

Source
pub struct Config {
+    pub device_id: Option<String>,
+    pub format: FormatConfig,
+    pub disconnected_format: FormatConfig,
+    pub missing_format: FormatConfig,
+    pub bat_good: u8,
+    pub bat_info: u8,
+    pub bat_warning: u8,
+    pub bat_critical: u8,
+}

Fields§

§device_id: Option<String>§format: FormatConfig§disconnected_format: FormatConfig§missing_format: FormatConfig§bat_good: u8§bat_info: u8§bat_warning: u8§bat_critical: u8

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { device_id: Default::default(), format: Default::default(), disconnected_format: Default::default(), missing_format: Default::default(), bat_good: 60, bat_info: 60, bat_warning: 30, bat_critical: 15 }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/keyboard_layout/enum.KeyboardLayoutDriver.html b/i3status_rs/blocks/keyboard_layout/enum.KeyboardLayoutDriver.html new file mode 100644 index 0000000000..fac2851cf5 --- /dev/null +++ b/i3status_rs/blocks/keyboard_layout/enum.KeyboardLayoutDriver.html @@ -0,0 +1,45 @@ +KeyboardLayoutDriver in i3status_rs::blocks::keyboard_layout - Rust

Enum KeyboardLayoutDriver

Source
pub enum KeyboardLayoutDriver {
+    XkbEvent,
+    SetXkbMap,
+    XkbSwitch,
+    LocaleBus,
+    KbddBus,
+    Sway,
+}

Variants§

§

XkbEvent

§

SetXkbMap

§

XkbSwitch

§

LocaleBus

§

KbddBus

§

Sway

Trait Implementations§

Source§

impl Clone for KeyboardLayoutDriver

Source§

fn clone(&self) -> KeyboardLayoutDriver

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for KeyboardLayoutDriver

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for KeyboardLayoutDriver

Source§

fn default() -> Self

Return KeyboardLayoutDriver::XkbEvent

+
Source§

impl<'de> Deserialize<'de> for KeyboardLayoutDriver

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Copy for KeyboardLayoutDriver

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/keyboard_layout/fn.run.html b/i3status_rs/blocks/keyboard_layout/fn.run.html new file mode 100644 index 0000000000..5ee19af7bf --- /dev/null +++ b/i3status_rs/blocks/keyboard_layout/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::keyboard_layout - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/keyboard_layout/index.html b/i3status_rs/blocks/keyboard_layout/index.html new file mode 100644 index 0000000000..de95ab86f2 --- /dev/null +++ b/i3status_rs/blocks/keyboard_layout/index.html @@ -0,0 +1,60 @@ +i3status_rs::blocks::keyboard_layout - Rust

Module keyboard_layout

Source
Expand description

Keyboard layout indicator

+

Six drivers are available:

+
    +
  • xkbevent which can read asynchronous updates from the x11 events
  • +
  • setxkbmap (alias for xkbevent) DEPRECATED
  • +
  • xkbswitch (alias for xkbevent) DEPRECATED
  • +
  • localebus which can read asynchronous updates from the systemd org.freedesktop.locale1 D-Bus path
  • +
  • kbddbus which uses kbdd to monitor per-window layout changes via DBus
  • +
  • sway which can read asynchronous updates from the sway IPC
  • +
+

setxkbmap and xkbswitch are deprecated and will be removed in v0.35.0.

+

Which of these methods is appropriate will depend on your system setup.

+

§Configuration

+ + + + + +
KeyValuesDefault
driverOne of "xkbevent", "setxkbmap", "xkbswitch", "localebus", "kbddbus" or "sway", depending on your system."xkbevent"
interval DEPRECATEDUpdate interval, in seconds. Only used by the "setxkbmap" driver.60
formatA string to customise the output of this block. See below for available placeholders." $layout "
sway_kb_identifierIdentifier of the device you want to monitor, as found in the output of swaymsg -t get_inputs.Defaults to first input found
mappingsMap layout (variant) to custom short name.None
+
+

interval is deprecated and will be removed in v0.35.0.

+
+ + +
KeyValueType
layoutKeyboard layout nameString
variantKeyboard variant name or N/A if not applicableString
+

§Examples

+

Listen to D-Bus for changes:

+
[[block]]
+block = "keyboard_layout"
+driver = "localebus"
+

Listen to kbdd for changes, the text is in the following format: +“English (US)” - {$layout ($variant)} +use block.mappings to override with shorter names as shown below. +Also use format = “ $layout ($variant) “ to see the full text to map, +or you can use: +dbus-monitor interface=ru.gentoo.kbdd +to see the exact variant spelling

+
[[block]]
+block = "keyboard_layout"
+driver = "kbddbus"
+[block.mappings]
+"English (US)" = "us"
+"Bulgarian (new phonetic)" = "bg"
+

Listen to sway for changes:

+
[[block]]
+block = "keyboard_layout"
+driver = "sway"
+sway_kb_identifier = "1133:49706:Gaming_Keyboard_G110"
+

Listen to sway for changes and override mappings:

+
[[block]]
+block = "keyboard_layout"
+driver = "sway"
+format = " $layout "
+[block.mappings]
+"English (Workman)" = "EN"
+"Russian (N/A)" = "RU"
+

Listen to xkb events for changes:

+
[[block]]
+block = "keyboard_layout"
+driver = "xkbevent"

Structs§

Config

Enums§

KeyboardLayoutDriver

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/keyboard_layout/sidebar-items.js b/i3status_rs/blocks/keyboard_layout/sidebar-items.js new file mode 100644 index 0000000000..4fcefdee26 --- /dev/null +++ b/i3status_rs/blocks/keyboard_layout/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["KeyboardLayoutDriver"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/keyboard_layout/struct.Config.html b/i3status_rs/blocks/keyboard_layout/struct.Config.html new file mode 100644 index 0000000000..47a86c2e75 --- /dev/null +++ b/i3status_rs/blocks/keyboard_layout/struct.Config.html @@ -0,0 +1,43 @@ +Config in i3status_rs::blocks::keyboard_layout - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub driver: KeyboardLayoutDriver,
+    pub interval: Seconds,
+    pub sway_kb_identifier: Option<String>,
+    pub mappings: Option<HashMap<String, String>>,
+}

Fields§

§format: FormatConfig§driver: KeyboardLayoutDriver§interval: Seconds§sway_kb_identifier: Option<String>§mappings: Option<HashMap<String, String>>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), driver: Default::default(), interval: 60.into(), sway_kb_identifier: Default::default(), mappings: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/load/fn.run.html b/i3status_rs/blocks/load/fn.run.html new file mode 100644 index 0000000000..11e1f4c3e4 --- /dev/null +++ b/i3status_rs/blocks/load/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::load - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/load/index.html b/i3status_rs/blocks/load/index.html new file mode 100644 index 0000000000..46c47923ae --- /dev/null +++ b/i3status_rs/blocks/load/index.html @@ -0,0 +1,22 @@ +i3status_rs::blocks::load - Rust

Module load

Source
Expand description

System load average

+

§Configuration

+ + + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders." $icon $1m.eng(w:4) "
intervalUpdate interval in seconds3
infoMinimum load, where state is set to info0.3
warningMinimum load, where state is set to warning0.6
criticalMinimum load, where state is set to critical0.9
+
+ + + + +
PlaceholderValueTypeUnit
iconA static iconIcon-
1m1 minute load averageNumber-
5m5 minute load averageNumber-
15m15 minute load averageNumber-
+

§Example

[[block]]
+block = "load"
+format = " $icon 1min avg: $1m.eng(w:4) "
+interval = 1

§Icons Used

+
    +
  • cogs
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/load/sidebar-items.js b/i3status_rs/blocks/load/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/load/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/load/struct.Config.html b/i3status_rs/blocks/load/struct.Config.html new file mode 100644 index 0000000000..168bc021ea --- /dev/null +++ b/i3status_rs/blocks/load/struct.Config.html @@ -0,0 +1,43 @@ +Config in i3status_rs::blocks::load - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub interval: Seconds,
+    pub info: f64,
+    pub warning: f64,
+    pub critical: f64,
+}

Fields§

§format: FormatConfig§interval: Seconds§info: f64§warning: f64§critical: f64

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), interval: 3.into(), info: 0.3, warning: 0.6, critical: 0.9 }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/maildir/enum.MailType.html b/i3status_rs/blocks/maildir/enum.MailType.html new file mode 100644 index 0000000000..3491b89ca0 --- /dev/null +++ b/i3status_rs/blocks/maildir/enum.MailType.html @@ -0,0 +1,40 @@ +MailType in i3status_rs::blocks::maildir - Rust

Enum MailType

Source
pub enum MailType {
+    New,
+    Cur,
+    All,
+}

Variants§

§

New

§

Cur

§

All

Trait Implementations§

Source§

impl Clone for MailType

Source§

fn clone(&self) -> MailType

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MailType

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for MailType

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/maildir/fn.run.html b/i3status_rs/blocks/maildir/fn.run.html new file mode 100644 index 0000000000..997b588d88 --- /dev/null +++ b/i3status_rs/blocks/maildir/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::maildir - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/maildir/index.html b/i3status_rs/blocks/maildir/index.html new file mode 100644 index 0000000000..4d32384af2 --- /dev/null +++ b/i3status_rs/blocks/maildir/index.html @@ -0,0 +1,25 @@ +i3status_rs::blocks::maildir - Rust

Module maildir

Source
Expand description

Unread mail. Only supports maildir format.

+

Note that you need to enable maildir feature to use this block:

+
cargo build --release --features maildir

§Configuration

+ + + + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders." $icon $status "
inboxesList of maildir inboxes to look for mails in. Supports path/glob expansions (e.g. ~ and *).Required
threshold_warningNumber of unread mails where state is set to warning.1
threshold_criticalNumber of unread mails where state is set to critical.10
intervalUpdate interval, in seconds.5
display_typeWhich part of the maildir to count: "new", "cur", or "all"."new"
+
+ + +
PlaceholderValueTypeUnit
iconA static iconIcon-
statusNumber of emailsNumber-
+

§Examples

[[block]]
+block = "maildir"
+interval = 60
+inboxes = ["~/mail/local", "~/maildir/account1/*"]
+threshold_warning = 1
+threshold_critical = 10
+display_type = "new"

§Icons Used

+
    +
  • mail
  • +
+

Structs§

Config

Enums§

MailType

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/maildir/sidebar-items.js b/i3status_rs/blocks/maildir/sidebar-items.js new file mode 100644 index 0000000000..026cf19b9c --- /dev/null +++ b/i3status_rs/blocks/maildir/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["MailType"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/maildir/struct.Config.html b/i3status_rs/blocks/maildir/struct.Config.html new file mode 100644 index 0000000000..aea286f959 --- /dev/null +++ b/i3status_rs/blocks/maildir/struct.Config.html @@ -0,0 +1,44 @@ +Config in i3status_rs::blocks::maildir - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub interval: Seconds,
+    pub inboxes: Vec<String>,
+    pub threshold_warning: usize,
+    pub threshold_critical: usize,
+    pub display_type: MailType,
+}

Fields§

§format: FormatConfig§interval: Seconds§inboxes: Vec<String>§threshold_warning: usize§threshold_critical: usize§display_type: MailType

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), interval: 5.into(), inboxes: Default::default(), threshold_warning: 1, threshold_critical: 10, display_type: MailType::New }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/memory/fn.run.html b/i3status_rs/blocks/memory/fn.run.html new file mode 100644 index 0000000000..e4674c241d --- /dev/null +++ b/i3status_rs/blocks/memory/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::memory - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/memory/index.html b/i3status_rs/blocks/memory/index.html new file mode 100644 index 0000000000..607dc1397a --- /dev/null +++ b/i3status_rs/blocks/memory/index.html @@ -0,0 +1,58 @@ +i3status_rs::blocks::memory - Rust

Module memory

Source
Expand description

Memory and swap usage

+

§Configuration

+ + + + + + + +
KeyValuesDefault
formatA string to customise the output of this block when in “Memory” view. See below for available placeholders." $icon $mem_used.eng(prefix:Mi)/$mem_total.eng(prefix:Mi)($mem_used_percents.eng(w:2)) "
format_altIf set, block will switch between format and format_alt on every clickNone
intervalUpdate interval in seconds5
warning_memPercentage of memory usage, where state is set to warning80.0
warning_swapPercentage of swap usage, where state is set to warning80.0
critical_memPercentage of memory usage, where state is set to critical95.0
critical_swapPercentage of swap usage, where state is set to critical95.0
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlaceholderValueTypeUnit
iconMemory iconIcon-
icon_swapSwap iconIcon-
mem_totalTotal physical ram availableNumberBytes
mem_freeFree memory not yet used by the kernel or userspace (in general you should use mem_avail)NumberBytes
mem_free_percentsas above but as a percentage of total memoryNumberPercents
mem_availKernel estimate of usable free memory which includes cached memory and buffersNumberBytes
mem_avail_percentsas above but as a percentage of total memoryNumberPercents
mem_total_usedmem_total - mem_freeNumberBytes
mem_total_used_percentsas above but as a percentage of total memoryNumberPercents
mem_usedMemory used, excluding cached memory and buffers; same as htop’s green barNumberBytes
mem_used_percentsas above but as a percentage of total memoryNumberPercents
buffersBuffers, similar to htop’s blue barNumberBytes
buffers_percentas above but as a percentage of total memoryNumberPercents
cachedCached memory (taking into account ZFS ARC cache), similar to htop’s yellow barNumberBytes
cached_percentas above but as a percentage of total memoryNumberPercents
swap_totalSwap totalNumberBytes
swap_freeSwap freeNumberBytes
swap_free_percentsas above but as a percentage of total memoryNumberPercents
swap_usedSwap usedNumberBytes
swap_used_percentsas above but as a percentage of total memoryNumberPercents
zram_compressedCompressed zram memory usageNumberBytes
zram_decompressedDecompressed zram memory usageNumberBytes
‘zram_comp_ratio’Ratio of the decompressed/compressed zram memoryNumber-
zswap_compressedCompressed zswap memory usage (>=Linux 5.19)NumberBytes
zswap_decompressedDecompressed zswap memory usage (>=Linux 5.19)NumberBytes
zswap_decompressed_percentsas above but as a percentage of total zswap memory (>=Linux 5.19)NumberPercents
‘zswap_comp_ratio’Ratio of the decompressed/compressed zswap memory (>=Linux 5.19)Number-
+
+ +
ActionDescriptionDefault button
toggle_formatToggles between format and format_altLeft
+

§Examples

[[block]]
+block = "memory"
+format = " $icon $mem_used_percents.eng(w:1) "
+format_alt = " $icon_swap $swap_free.eng(w:3,u:B,p:Mi)/$swap_total.eng(w:3,u:B,p:Mi)($swap_used_percents.eng(w:2)) "
+interval = 30
+warning_mem = 70
+critical_mem = 90
+

Show swap and hide if it is zero:

+
[[block]]
+block = "memory"
+format = " $icon $swap_used.eng(range:1..) |"

§Icons Used

+
    +
  • memory_mem
  • +
  • memory_swap
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/memory/sidebar-items.js b/i3status_rs/blocks/memory/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/memory/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/memory/struct.Config.html b/i3status_rs/blocks/memory/struct.Config.html new file mode 100644 index 0000000000..e1a75f0779 --- /dev/null +++ b/i3status_rs/blocks/memory/struct.Config.html @@ -0,0 +1,45 @@ +Config in i3status_rs::blocks::memory - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub format_alt: Option<FormatConfig>,
+    pub interval: Seconds,
+    pub warning_mem: f64,
+    pub warning_swap: f64,
+    pub critical_mem: f64,
+    pub critical_swap: f64,
+}

Fields§

§format: FormatConfig§format_alt: Option<FormatConfig>§interval: Seconds§warning_mem: f64§warning_swap: f64§critical_mem: f64§critical_swap: f64

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), format_alt: Default::default(), interval: 5.into(), warning_mem: 80.0, warning_swap: 80.0, critical_mem: 95.0, critical_swap: 95.0 }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/menu/fn.run.html b/i3status_rs/blocks/menu/fn.run.html new file mode 100644 index 0000000000..2d8277d7e8 --- /dev/null +++ b/i3status_rs/blocks/menu/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::menu - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/menu/index.html b/i3status_rs/blocks/menu/index.html new file mode 100644 index 0000000000..54be1aca8c --- /dev/null +++ b/i3status_rs/blocks/menu/index.html @@ -0,0 +1,23 @@ +i3status_rs::blocks::menu - Rust

Module menu

Source
Expand description

A custom menu

+

This block allows you to quickly run a custom shell command. Left-click on this block to +activate it, then scroll through configured items. Left-click on the item to run it and +optionally confirm your action by left-clicking again. Right-click any time to deactivate this +block.

+

§Configuration

+ + +
KeyValuesDefault
textText that will be displayed when the block is inactive.Required
itemsA list of “items”. See examples below.Required
+

§Example

[[block]]
+block = "menu"
+text = "\uf011"
+[[block.items]]
+display = " -&gt;   Sleep   &lt;-"
+cmd = "systemctl suspend"
+[[block.items]]
+display = " -&gt; Power Off &lt;-"
+cmd = "poweroff"
+confirm_msg = "Are you sure you want to power off?"
+[[block.items]]
+display = " -&gt;  Reboot   &lt;-"
+cmd = "reboot"
+confirm_msg = "Are you sure you want to reboot?"

Structs§

Config
Item

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/menu/sidebar-items.js b/i3status_rs/blocks/menu/sidebar-items.js new file mode 100644 index 0000000000..33c875ccb0 --- /dev/null +++ b/i3status_rs/blocks/menu/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config","Item"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/menu/struct.Config.html b/i3status_rs/blocks/menu/struct.Config.html new file mode 100644 index 0000000000..714f23b094 --- /dev/null +++ b/i3status_rs/blocks/menu/struct.Config.html @@ -0,0 +1,37 @@ +Config in i3status_rs::blocks::menu - Rust

Struct Config

Source
pub struct Config {
+    pub text: String,
+    pub items: Vec<Item>,
+}

Fields§

§text: String§items: Vec<Item>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Config

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/menu/struct.Item.html b/i3status_rs/blocks/menu/struct.Item.html new file mode 100644 index 0000000000..88e06096b3 --- /dev/null +++ b/i3status_rs/blocks/menu/struct.Item.html @@ -0,0 +1,40 @@ +Item in i3status_rs::blocks::menu - Rust

Struct Item

Source
pub struct Item {
+    pub display: String,
+    pub cmd: String,
+    pub confirm_msg: Option<String>,
+}

Fields§

§display: String§cmd: String§confirm_msg: Option<String>

Trait Implementations§

Source§

impl Clone for Item

Source§

fn clone(&self) -> Item

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Item

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Item

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Item

§

impl RefUnwindSafe for Item

§

impl Send for Item

§

impl Sync for Item

§

impl Unpin for Item

§

impl UnwindSafe for Item

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/music/enum.PlayerName.html b/i3status_rs/blocks/music/enum.PlayerName.html new file mode 100644 index 0000000000..2fb569d404 --- /dev/null +++ b/i3status_rs/blocks/music/enum.PlayerName.html @@ -0,0 +1,41 @@ +PlayerName in i3status_rs::blocks::music - Rust

Enum PlayerName

Source
pub enum PlayerName {
+    Single(String),
+    Multiple(Vec<String>),
+}

Variants§

§

Single(String)

§

Multiple(Vec<String>)

Trait Implementations§

Source§

impl Clone for PlayerName

Source§

fn clone(&self) -> PlayerName

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PlayerName

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for PlayerName

Source§

fn default() -> Self

Return PlayerName::Multiple(Default::default())

+
Source§

impl<'de> Deserialize<'de> for PlayerName

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/music/fn.run.html b/i3status_rs/blocks/music/fn.run.html new file mode 100644 index 0000000000..c0c06638ae --- /dev/null +++ b/i3status_rs/blocks/music/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::music - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/music/index.html b/i3status_rs/blocks/music/index.html new file mode 100644 index 0000000000..4cc5a0e208 --- /dev/null +++ b/i3status_rs/blocks/music/index.html @@ -0,0 +1,111 @@ +i3status_rs::blocks::music - Rust

Module music

Source
Expand description

The current song title and artist

+

Also provides buttons for play/pause, previous and next.

+

Supports all music players that implement the MediaPlayer2 Interface. This includes:

+
    +
  • Spotify
  • +
  • VLC
  • +
  • mpd (via mpDris2)
  • +
+

and many others.

+

By default the block tracks all players available on the MPRIS bus. Right clicking on the block +will cycle it to the next player. You can pin the widget to a given player via the “player” +setting.

+

§Configuration

+ + + + + + + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders." $icon {$combo.str(max_w:25,rot_interval:0.5) $play |}"
format_altIf set, block will switch between format and format_alt on every clickNone
playerName(s) of the music player(s) MPRIS interface. This can be either a music player name or an array of music player names. Run busctl --user list | grep "org.mpris.MediaPlayer2." | cut -d' ' -f1 and the name is the part after “org.mpris.MediaPlayer2.”.None
interface_name_excludeA list of regex patterns for player MPRIS interface names to ignore.["playerctld"]
separatorString to insert between artist and title." - "
seek_step_secsPositive number of seconds to seek forward/backward when scrolling on the bar. Does not need to be an integer.1
seek_forward_step_secsPositive number of seconds to seek forward when scrolling on the bar. Does not need to be an integer.seek_step_secs
seek_backward_step_secsPositive number of seconds to seek backward when scrolling on the bar. Does not need to be an integer.seek_step_secs
volume_stepThe percent volume level is increased/decreased for the selected audio device when scrolling. Capped automatically at 50.5
+
+

Note: All placeholders except icon can be absent. See the examples below to learn how to handle this.

+
+ + + + + + + + + + + + + +
PlaceholderValueType
iconA static iconIcon
artistCurrent artistText
titleCurrent titleText
urlCurrent song urlText
comboResolves to “$artist[sep]$title", "$artist", "$title", or "$url" depending on what information is available. [sep] is set by separator option.Text
playerName of the current player (taken from the last part of its MPRIS bus name)Text
availTotal number of players available to switch betweenNumber
curThe current player index of the available playersNumber
playPlay/Pause buttonClickable icon
nextNext buttonClickable icon
prevPrevious buttonClickable icon
volume_iconIcon based on volume. Missing if unsupported.Icon
volumeCurrent volume. Missing if muted or unsupported.Number
+
+ + + +
WidgetPlaceholder
play_pause_btn$play
next_btn$next
prev_btn$prev
+
+ + + + + + + + + +
ActionDefault button
play_pauseLeft on play_pause_btn
nextLeft on next_btn
prevLeft on prev_btn
next_playerRight
seek_forwardWheel Up
seek_backwardWheel Down
volume_up-
volume_down-
toggle_formatLeft
+

§Examples

+

Show the currently playing song on Spotify only, with play & next buttons and limit the width +to 20 characters:

+
[[block]]
+block = "music"
+format = " $icon {$combo.str(max_w:20) $play $next |}"
+player = "spotify"
+

Same thing for any compatible player, takes the first active on the bus, but ignores “mpd” or anything with “kdeconnect” in the name:

+
[[block]]
+block = "music"
+format = " $icon {$combo.str(max_w:20) $play $next |}"
+interface_name_exclude = [".*kdeconnect.*", "mpd"]
+

Same as above, but displays with rotating text

+
[[block]]
+block = "music"
+format = " $icon {$combo.str(max_w:20,rot_interval:0.5) $play $next |}"
+interface_name_exclude = [".*kdeconnect.*", "mpd"]
+

Click anywhere to play/pause, middle click to toggle format:

+
[[block]]
+block = "music"
+format = " format 1 "
+format_alt = " format 2 "
+[[block.click]]
+button = "left"
+action = "play_pause"
+[[block.click]]
+button = "middle"
+widget = "."
+action = "toggle_format"
+

Scroll to change the player volume, use the forward and back buttons to seek:

+
[[block]]
+block = "music"
+format = " $icon $volume_icon $combo $play $next| "
+seek_step_secs = 10
+[[block.click]]
+button = "up"
+action = "volume_up"
+[[block.click]]
+button = "down"
+action = "volume_down"
+[[block.click]]
+button = "forward"
+action = "seek_forward"
+[[block.click]]
+button = "back"
+action = "seek_backward"

§Icons Used

+
    +
  • music
  • +
  • music_next
  • +
  • music_play
  • +
  • music_prev
  • +
  • volume_muted
  • +
  • volume (as a progression)
  • +
+

Structs§

Config

Enums§

PlayerName

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/music/sidebar-items.js b/i3status_rs/blocks/music/sidebar-items.js new file mode 100644 index 0000000000..d9a3811596 --- /dev/null +++ b/i3status_rs/blocks/music/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["PlayerName"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/music/struct.Config.html b/i3status_rs/blocks/music/struct.Config.html new file mode 100644 index 0000000000..fd2a3a4af4 --- /dev/null +++ b/i3status_rs/blocks/music/struct.Config.html @@ -0,0 +1,47 @@ +Config in i3status_rs::blocks::music - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub format_alt: Option<FormatConfig>,
+    pub player: PlayerName,
+    pub interface_name_exclude: Vec<String>,
+    pub separator: String,
+    pub seek_step_secs: Seconds<false>,
+    pub seek_forward_step_secs: Option<Seconds<false>>,
+    pub seek_backward_step_secs: Option<Seconds<false>>,
+    pub volume_step: f64,
+}

Fields§

§format: FormatConfig§format_alt: Option<FormatConfig>§player: PlayerName§interface_name_exclude: Vec<String>§separator: String§seek_step_secs: Seconds<false>§seek_forward_step_secs: Option<Seconds<false>>§seek_backward_step_secs: Option<Seconds<false>>§volume_step: f64

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), format_alt: Default::default(), player: Default::default(), interface_name_exclude: vec!["playerctld".into()], separator: " - ".into(), seek_step_secs: 1.into(), seek_forward_step_secs: Default::default(), seek_backward_step_secs: Default::default(), volume_step: 5.0 }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/net/fn.run.html b/i3status_rs/blocks/net/fn.run.html new file mode 100644 index 0000000000..5513d15587 --- /dev/null +++ b/i3status_rs/blocks/net/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::net - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/net/index.html b/i3status_rs/blocks/net/index.html new file mode 100644 index 0000000000..6714f02ea4 --- /dev/null +++ b/i3status_rs/blocks/net/index.html @@ -0,0 +1,46 @@ +i3status_rs::blocks::net - Rust

Module net

Source
Expand description

Network information

+

This block uses sysfs and netlink and thus does not require any external dependencies.

+

§Configuration

+ + + + + + +
KeyValuesDefault
deviceNetwork interface to monitor (as specified in /sys/class/net/). Supports regex.If not set, device will be automatically selected every interval
intervalUpdate interval in seconds2
formatA string to customise the output of this block. See below for available placeholders." $icon ^icon_net_down $speed_down.eng(prefix:K) ^icon_net_up $speed_up.eng(prefix:K) "
format_altIf set, block will switch between format and format_alt on every clickNone
inactive_formatSame as format but for when the interface is inactive" $icon Down "
missing_formatSame as format but for when the device is missing" × "
+
+ +
ActionDescriptionDefault button
toggle_formatToggles between format and format_altLeft
+
+ + + + + + + + + + + + + +
PlaceholderValueTypeUnit
iconIcon based on device’s typeIcon-
speed_downDownload speedNumberBytes per second
speed_upUpload speedNumberBytes per second
graph_downDownload speed graphText-
graph_upUpload speed graphText-
deviceThe name of deviceText-
ssidNetfork SSID (WiFi only)Text-
frequencyWiFi frequencyNumberHz
signal_strengthWiFi signalNumber%
bitrateWiFi connection bitrateNumberBits per second
ipIPv4 address of the ifaceText-
ipv6IPv6 address of the ifaceText-
nameserverNameserverText-
+

§Example

+

Display WiFi info if available

+
[[block]]
+block = "net"
+format = " $icon {$signal_strength $ssid $frequency|Wired connection} via $device "
+

Display exact device

+
[[block]]
+block = "net"
+device = "^wlo0$"

§Icons Used

+
    +
  • net_loopback
  • +
  • net_vpn
  • +
  • net_wired
  • +
  • net_wireless (as a progression)
  • +
  • net_up
  • +
  • net_down
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/net/sidebar-items.js b/i3status_rs/blocks/net/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/net/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/net/struct.Config.html b/i3status_rs/blocks/net/struct.Config.html new file mode 100644 index 0000000000..e2ec5cad8e --- /dev/null +++ b/i3status_rs/blocks/net/struct.Config.html @@ -0,0 +1,44 @@ +Config in i3status_rs::blocks::net - Rust

Struct Config

Source
pub struct Config {
+    pub device: Option<String>,
+    pub interval: Seconds,
+    pub format: FormatConfig,
+    pub format_alt: Option<FormatConfig>,
+    pub inactive_format: FormatConfig,
+    pub missing_format: FormatConfig,
+}

Fields§

§device: Option<String>§interval: Seconds§format: FormatConfig§format_alt: Option<FormatConfig>§inactive_format: FormatConfig§missing_format: FormatConfig

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { device: Default::default(), interval: 2.into(), format: Default::default(), format_alt: Default::default(), inactive_format: Default::default(), missing_format: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/notify/enum.DriverType.html b/i3status_rs/blocks/notify/enum.DriverType.html new file mode 100644 index 0000000000..ff767b64f7 --- /dev/null +++ b/i3status_rs/blocks/notify/enum.DriverType.html @@ -0,0 +1,39 @@ +DriverType in i3status_rs::blocks::notify - Rust

Enum DriverType

Source
pub enum DriverType {
+    Dunst,
+    SwayNC,
+}

Variants§

§

Dunst

§

SwayNC

Trait Implementations§

Source§

impl Debug for DriverType

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for DriverType

Source§

fn default() -> Self

Return DriverType::Dunst

+
Source§

impl<'de> Deserialize<'de> for DriverType

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/notify/fn.run.html b/i3status_rs/blocks/notify/fn.run.html new file mode 100644 index 0000000000..d3ea7eecb3 --- /dev/null +++ b/i3status_rs/blocks/notify/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::notify - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/notify/index.html b/i3status_rs/blocks/notify/index.html new file mode 100644 index 0000000000..e084067332 --- /dev/null +++ b/i3status_rs/blocks/notify/index.html @@ -0,0 +1,39 @@ +i3status_rs::blocks::notify - Rust

Module notify

Source
Expand description

Display and toggle the state of notifications daemon

+

Left-clicking on this block will enable/disable notifications.

+

§Configuration

+ + +
KeyValuesDefault
driverWhich notifications daemon is running. Available drivers are: "dunst" and "swaync""dunst"
formatA string to customise the output of this block. See below for available placeholders." $icon "
+
+ + + +
PlaceholderValueTypeUnit
iconIcon based on notification’s stateIcon-
notification_count1The number of notification (omitted if 0)Number-
pausedPresent only if notifications are disabledFlag-
+
+ + +
ActionDefault button
toggle_pausedLeft
show-
+

§Examples

+

How to use paused flag

+
[[block]]
+block = "notify"
+format = " $icon {$paused{Off}|On} "
+

How to use notification_count

+
[[block]]
+block = "notify"
+format = " $icon {($notification_count.eng(w:1)) |}"
+

How to remap actions

+
[[block]]
+block = "notify"
+driver = "swaync"
+[[block.click]]
+button = "left"
+action = "show"
+[[block.click]]
+button = "right"
+action = "toggle_paused"

§Icons Used

+
    +
  • bell
  • +
  • bell-slash
  • +
+

  1. when using notification_count with the dunst driver use dunst > 1.9.0 

Structs§

Config

Enums§

DriverType

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/notify/sidebar-items.js b/i3status_rs/blocks/notify/sidebar-items.js new file mode 100644 index 0000000000..7cba7410b2 --- /dev/null +++ b/i3status_rs/blocks/notify/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["DriverType"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/notify/struct.Config.html b/i3status_rs/blocks/notify/struct.Config.html new file mode 100644 index 0000000000..cda454e3ab --- /dev/null +++ b/i3status_rs/blocks/notify/struct.Config.html @@ -0,0 +1,39 @@ +Config in i3status_rs::blocks::notify - Rust

Struct Config

Source
pub struct Config {
+    pub driver: DriverType,
+    pub format: FormatConfig,
+}

Fields§

§driver: DriverType§format: FormatConfig

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Config

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/notmuch/fn.run.html b/i3status_rs/blocks/notmuch/fn.run.html new file mode 100644 index 0000000000..3b29ccc0b2 --- /dev/null +++ b/i3status_rs/blocks/notmuch/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::notmuch - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/notmuch/index.html b/i3status_rs/blocks/notmuch/index.html new file mode 100644 index 0000000000..5106b56173 --- /dev/null +++ b/i3status_rs/blocks/notmuch/index.html @@ -0,0 +1,30 @@ +i3status_rs::blocks::notmuch - Rust

Module notmuch

Source
Expand description

Count of notmuch messages

+

This block queries a notmuch database and displays the count of messages.

+

The simplest configuration will return the total count of messages in the notmuch database stored at $HOME/.mail

+

Note that you need to enable notmuch feature to use this block:

+
cargo build --release --features notmuch

§Configuration

+ + + + + + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders." $icon $count "
maildirPath to the directory containing the notmuch database. Supports path expansions e.g. ~.~/.mail
queryQuery to run on the database.""
threshold_criticalMail count that triggers critical state.99999
threshold_warningMail count that triggers warning state.99999
threshold_goodMail count that triggers good state.99999
threshold_infoMail count that triggers info state.99999
intervalUpdate interval in seconds.10
+
+ + +
PlaceholderValueTypeUnit
iconA static iconIcon-
countNumber of messages for the queryNumber-
+

§Example

[[block]]
+block = "notmuch"
+query = "tag:alert and not tag:trash"
+threshold_warning = 1
+threshold_critical = 10
+[[block.click]]
+button = "left"
+update = true

§Icons Used

+
    +
  • mail
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/notmuch/sidebar-items.js b/i3status_rs/blocks/notmuch/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/notmuch/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/notmuch/struct.Config.html b/i3status_rs/blocks/notmuch/struct.Config.html new file mode 100644 index 0000000000..7d921e4b7b --- /dev/null +++ b/i3status_rs/blocks/notmuch/struct.Config.html @@ -0,0 +1,46 @@ +Config in i3status_rs::blocks::notmuch - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub interval: Seconds,
+    pub maildir: ShellString,
+    pub query: String,
+    pub threshold_warning: u32,
+    pub threshold_critical: u32,
+    pub threshold_info: u32,
+    pub threshold_good: u32,
+}

Fields§

§format: FormatConfig§interval: Seconds§maildir: ShellString§query: String§threshold_warning: u32§threshold_critical: u32§threshold_info: u32§threshold_good: u32

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), interval: 10.into(), maildir: "~/.mail".into(), query: Default::default(), threshold_warning: u32::MAX, threshold_critical: u32::MAX, threshold_info: u32::MAX, threshold_good: u32::MAX }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/nvidia_gpu/fn.run.html b/i3status_rs/blocks/nvidia_gpu/fn.run.html new file mode 100644 index 0000000000..d737f4d11b --- /dev/null +++ b/i3status_rs/blocks/nvidia_gpu/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::nvidia_gpu - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/nvidia_gpu/index.html b/i3status_rs/blocks/nvidia_gpu/index.html new file mode 100644 index 0000000000..6431b2d395 --- /dev/null +++ b/i3status_rs/blocks/nvidia_gpu/index.html @@ -0,0 +1,49 @@ +i3status_rs::blocks::nvidia_gpu - Rust

Module nvidia_gpu

Source
Expand description

Display the stats of your NVidia GPU

+

By default show_temperature shows the used memory. Clicking the left mouse on the +“temperature” part of the block will alternate it between showing used or total available +memory.

+

Clicking the left mouse button on the “fan speed” part of the block will cause it to enter into +a fan speed setting mode. In this mode you can scroll the mouse wheel over the block to change +the fan speeds, and left click to exit the mode.

+

Requires nvidia-smi for displaying info and nvidia_settings for setting fan speed.

+

§Configuration

+ + + + + + + +
KeyValuesDefault
gpu_idGPU id in system.0
formatA string to customise the output of this block. See below for available placeholders." $icon $utilization $memory $temperature "
intervalUpdate interval in seconds.1
idleMaximum temperature, below which state is set to idle50
goodMaximum temperature, below which state is set to good70
infoMaximum temperature, below which state is set to info75
warningMaximum temperature, below which state is set to warning80
+
+ + + + + + + + +
PlaceholderTypeUnit
iconIcon-
nameText-
utilizationNumberPercents
memoryNumberBytes
temperatureNumberDegrees
fan_speedNumberPercents
clocksNumberHertz
powerNumberWatts
+
+ + +
WidgetPlaceholder
mem_btn$memory
fan_btn$fan_speed
+
+ + + + +
ActionDefault button
toggle_mem_totalLeft on mem_btn
toggle_fan_controlledLeft on fan_btn
fan_speed_upWheel Up on fan_btn
fan_speed_downWheel Down on fan_btn
+

§Example

[[block]]
+block = "nvidia_gpu"
+interval = 1
+format = " $icon GT 1030 $utilization $temperature $clocks "

§Icons Used

+
    +
  • gpu
  • +
+

§TODO

+
    +
  • Provide a mappings option similar to keyboard_layout’s to map GPU names to labels?
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/nvidia_gpu/sidebar-items.js b/i3status_rs/blocks/nvidia_gpu/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/nvidia_gpu/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/nvidia_gpu/struct.Config.html b/i3status_rs/blocks/nvidia_gpu/struct.Config.html new file mode 100644 index 0000000000..c653fc3aa7 --- /dev/null +++ b/i3status_rs/blocks/nvidia_gpu/struct.Config.html @@ -0,0 +1,45 @@ +Config in i3status_rs::blocks::nvidia_gpu - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub interval: Seconds,
+    pub gpu_id: u64,
+    pub idle: u32,
+    pub good: u32,
+    pub info: u32,
+    pub warning: u32,
+}

Fields§

§format: FormatConfig§interval: Seconds§gpu_id: u64§idle: u32§good: u32§info: u32§warning: u32

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), interval: 1.into(), gpu_id: 0, idle: 50, good: 70, info: 75, warning: 80 }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/packages/apt/index.html b/i3status_rs/blocks/packages/apt/index.html new file mode 100644 index 0000000000..e688063406 --- /dev/null +++ b/i3status_rs/blocks/packages/apt/index.html @@ -0,0 +1 @@ +i3status_rs::blocks::packages::apt - Rust

Module apt

Source

Structs§

Apt
\ No newline at end of file diff --git a/i3status_rs/blocks/packages/apt/sidebar-items.js b/i3status_rs/blocks/packages/apt/sidebar-items.js new file mode 100644 index 0000000000..f3f8273ce6 --- /dev/null +++ b/i3status_rs/blocks/packages/apt/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["Apt"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/packages/apt/struct.Apt.html b/i3status_rs/blocks/packages/apt/struct.Apt.html new file mode 100644 index 0000000000..08212363a8 --- /dev/null +++ b/i3status_rs/blocks/packages/apt/struct.Apt.html @@ -0,0 +1,37 @@ +Apt in i3status_rs::blocks::packages::apt - Rust

Struct Apt

Source
pub struct Apt { /* private fields */ }

Implementations§

Source§

impl Apt

Source

pub async fn new(ignore_phased_updates: bool) -> Result<Self>

Trait Implementations§

Source§

impl Backend for Apt

Source§

fn name(&self) -> Cow<'static, str>

Source§

fn get_updates_list<'life0, 'async_trait>( + &'life0 self, +) -> Pin<Box<dyn Future<Output = Result<Vec<String>>> + Send + 'async_trait>>
where + Self: 'async_trait, + 'life0: 'async_trait,

Source§

impl Default for Apt

Source§

fn default() -> Apt

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl Freeze for Apt

§

impl RefUnwindSafe for Apt

§

impl Send for Apt

§

impl Sync for Apt

§

impl Unpin for Apt

§

impl UnwindSafe for Apt

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/packages/dnf/index.html b/i3status_rs/blocks/packages/dnf/index.html new file mode 100644 index 0000000000..00f0b82281 --- /dev/null +++ b/i3status_rs/blocks/packages/dnf/index.html @@ -0,0 +1 @@ +i3status_rs::blocks::packages::dnf - Rust

Module dnf

Source

Structs§

Dnf
\ No newline at end of file diff --git a/i3status_rs/blocks/packages/dnf/sidebar-items.js b/i3status_rs/blocks/packages/dnf/sidebar-items.js new file mode 100644 index 0000000000..1d6af04d32 --- /dev/null +++ b/i3status_rs/blocks/packages/dnf/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["Dnf"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/packages/dnf/struct.Dnf.html b/i3status_rs/blocks/packages/dnf/struct.Dnf.html new file mode 100644 index 0000000000..64fe116dab --- /dev/null +++ b/i3status_rs/blocks/packages/dnf/struct.Dnf.html @@ -0,0 +1,37 @@ +Dnf in i3status_rs::blocks::packages::dnf - Rust

Struct Dnf

Source
pub struct Dnf;

Implementations§

Source§

impl Dnf

Source

pub fn new() -> Self

Trait Implementations§

Source§

impl Backend for Dnf

Source§

fn name(&self) -> Cow<'static, str>

Source§

fn get_updates_list<'life0, 'async_trait>( + &'life0 self, +) -> Pin<Box<dyn Future<Output = Result<Vec<String>>> + Send + 'async_trait>>
where + Self: 'async_trait, + 'life0: 'async_trait,

Source§

impl Default for Dnf

Source§

fn default() -> Dnf

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl Freeze for Dnf

§

impl RefUnwindSafe for Dnf

§

impl Send for Dnf

§

impl Sync for Dnf

§

impl Unpin for Dnf

§

impl UnwindSafe for Dnf

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/packages/enum.PackageManager.html b/i3status_rs/blocks/packages/enum.PackageManager.html new file mode 100644 index 0000000000..dd3436cfe6 --- /dev/null +++ b/i3status_rs/blocks/packages/enum.PackageManager.html @@ -0,0 +1,43 @@ +PackageManager in i3status_rs::blocks::packages - Rust

Enum PackageManager

Source
pub enum PackageManager {
+    Apt,
+    Pacman,
+    Aur,
+    Dnf,
+    Xbps,
+}

Variants§

§

Apt

§

Pacman

§

Aur

§

Dnf

§

Xbps

Trait Implementations§

Source§

impl Clone for PackageManager

Source§

fn clone(&self) -> PackageManager

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PackageManager

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for PackageManager

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for PackageManager

Source§

fn eq(&self, other: &PackageManager) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for PackageManager

Source§

impl StructuralPartialEq for PackageManager

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/packages/fn.has_matching_update.html b/i3status_rs/blocks/packages/fn.has_matching_update.html new file mode 100644 index 0000000000..03f714ac04 --- /dev/null +++ b/i3status_rs/blocks/packages/fn.has_matching_update.html @@ -0,0 +1 @@ +has_matching_update in i3status_rs::blocks::packages - Rust

Function has_matching_update

Source
pub fn has_matching_update(updates: &[String], regex: &Regex) -> bool
\ No newline at end of file diff --git a/i3status_rs/blocks/packages/fn.run.html b/i3status_rs/blocks/packages/fn.run.html new file mode 100644 index 0000000000..d146e2fd55 --- /dev/null +++ b/i3status_rs/blocks/packages/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::packages - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/packages/index.html b/i3status_rs/blocks/packages/index.html new file mode 100644 index 0000000000..c05505c878 --- /dev/null +++ b/i3status_rs/blocks/packages/index.html @@ -0,0 +1,141 @@ +i3status_rs::blocks::packages - Rust

Module packages

Source
Expand description

Pending updates for different package manager like apt, pacman, etc.

+

Currently these package managers are available:

+
    +
  • apt for Debian/Ubuntu based system
  • +
  • pacman for Arch based system
  • +
  • aur for Arch based system
  • +
  • dnf for Fedora based system
  • +
  • xbps for Void Linux
  • +
+

§Configuration

+ + + + + + + + + + +
KeyValuesDefault
intervalUpdate interval in seconds.600
package_managerPackage manager to check for updatesAutomatically derived from format templates, but can be used to influence the $total value
formatA string to customise the output of this block. See below for available placeholders." $icon $total.eng(w:1) "
format_singularSame as format, but for when exactly one update is available." $icon $total.eng(w:1) "
format_up_to_dateSame as format, but for when no updates are available." $icon $total.eng(w:1) "
warning_updates_regexDisplay block as warning if updates matching regex are available.None
critical_updates_regexDisplay block as critical if updates matching regex are available.None
ignore_updates_regexDoesn’t include updates matching regex in the count.None
ignore_phased_updatesDoesn’t include potentially held back phased updates in the count. (For Debian/Ubuntu based system)false
aur_commandAUR command to check available updates, which outputs in the same format as pacman. e.g. yay -Qua (For Arch based system)Required if $aur are used
+
+ + + + + + + +
PlaceholderValueTypeUnit
iconA static iconIcon-
aptNumber of updates available in Debian/Ubuntu based systemNumber-
pacmanNumber of updates available in Arch based systemNumber-
aurNumber of updates available in Arch based systemNumber-
dnfNumber of updates available in Fedora based systemNumber-
xbpsNumber of updates available in Void LinuxNumber-
totalNumber of updates available in all package manager listedNumber-
+

§Apt

+

Behind the scenes this uses apt, and in order to run it without root privileges i3status-rust will create its own package database in /tmp/i3rs-apt/ which may take up several MB or more. If you have a custom apt config then this block may not work as expected - in that case please open an issue.

+

Tip: You can grab the list of available updates using APT_CONFIG=/tmp/i3rs-apt/apt.conf apt list --upgradable

+

§Pacman

+

Requires fakeroot to be installed (only required for pacman).

+

Tip: You can grab the list of available updates using fakeroot pacman -Qu --dbpath /tmp/checkup-db-i3statusrs-$USER/. +If you have the CHECKUPDATES_DB env var set on your system then substitute that dir instead.

+

Note: pikaur may hang the whole block if there is no internet connectivity reference. In that case, try a different AUR helper.

+

§Pacman hook

+

Tip: On Arch Linux you can setup a pacman hook to signal i3status-rs to update after packages +have been upgraded, so you won’t have stale info in your pacman block.

+

In the block configuration, set signal = 1 (or other number if 1 is being used by some +other block):

+
[[block]]
+block = "packages"
+signal = 1
+

Create /etc/pacman.d/hooks/i3status-rust.hook with the below contents:

+
[Trigger]
+Operation = Upgrade
+Type = Package
+Target = *
+
+[Action]
+When = PostTransaction
+Exec = /usr/bin/pkill -SIGRTMIN+1 i3status-rs

§Example

+

Apt only config

+
[[block]]
+block = "packages"
+interval = 1800
+package_manager = ["apt"]
+format = " $icon $apt updates available"
+format_singular = " $icon One update available "
+format_up_to_date = " $icon system up to date "
+[[block.click]]
+# shows dmenu with cached available updates. Any dmenu alternative should also work.
+button = "left"
+cmd = "APT_CONFIG=/tmp/i3rs-apt/apt.conf apt list --upgradable | tail -n +2 | rofi -dmenu"
+[[block.click]]
+# Updates the block on right click
+button = "right"
+update = true
+

Pacman only config:

+
[[block]]
+block = "packages"
+package_manager = ["pacman"]
+interval = 600
+format = " $icon $pacman updates available "
+format_singular = " $icon $pacman update available "
+format_up_to_date = " $icon system up to date "
+[[block.click]]
+# pop-up a menu showing the available updates. Replace wofi with your favourite menu command.
+button = "left"
+cmd = "fakeroot pacman -Qu --dbpath /tmp/checkup-db-i3statusrs-$USER/ | wofi --show dmenu"
+[[block.click]]
+# Updates the block on right click
+button = "right"
+update = true
+

Pacman and AUR helper config:

+
[[block]]
+block = "packages"
+package_manager = ["pacman", "aur"]
+interval = 600
+error_interval = 300
+format = " $icon $pacman + $aur = $total updates available "
+format_singular = " $icon $total update available "
+format_up_to_date = " $icon system up to date "
+# aur_command should output available updates to stdout (ie behave as echo -ne "update\n")
+aur_command = "yay -Qua"
+

Dnf only config:

+
[[block]]
+block = "packages"
+package_manager = ["dnf"]
+interval = 1800
+format = " $icon $dnf.eng(w:1) updates available "
+format_singular = " $icon One update available "
+format_up_to_date = " $icon system up to date "
+[[block.click]]
+# shows dmenu with cached available updates. Any dmenu alternative should also work.
+button = "left"
+cmd = "dnf list -q --upgrades | tail -n +2 | rofi -dmenu"
+

Xbps only config:

+
[[block]]
+block = "packages"
+package_manager = ["xbps"]
+interval = 1800
+format = " $icon $xbps.eng(w:1) updates available "
+format_singular = " $icon One update available "
+format_up_to_date = " $icon system up to date "
+[[block.click]]
+# shows dmenu with available updates. Any dmenu alternative should also work.
+button = "left"
+cmd = "xbps-install -Mun | dmenu -l 10"
+

Multiple package managers config:

+

Update the list of pending updates every thirty minutes (1800 seconds):

+
[[block]]
+block = "packages"
+package_manager = ["apt", "pacman", "aur", "dnf", "xbps"]
+interval = 1800
+format = " $icon $apt + $pacman + $aur + $dnf + $xbps = $total updates available "
+format_singular = " $icon One update available "
+format_up_to_date = " $icon system up to date "
+# If a linux update is available, but no ZFS package, it won't be possible to
+# actually perform a system upgrade, so we show a warning.
+warning_updates_regex = "(linux|linux-lts|linux-zen)"
+# If ZFS is available, we know that we can and should do an upgrade, so we show
+# the status as critical.
+critical_updates_regex = "(zfs|zfs-lts)"

§Icons Used

+
    +
  • update
  • +
+

Modules§

apt
dnf
pacman
xbps

Structs§

Config

Enums§

PackageManager

Traits§

Backend

Functions§

has_matching_update
run
\ No newline at end of file diff --git a/i3status_rs/blocks/packages/pacman/index.html b/i3status_rs/blocks/packages/pacman/index.html new file mode 100644 index 0000000000..ea9c469a51 --- /dev/null +++ b/i3status_rs/blocks/packages/pacman/index.html @@ -0,0 +1 @@ +i3status_rs::blocks::packages::pacman - Rust
\ No newline at end of file diff --git a/i3status_rs/blocks/packages/pacman/sidebar-items.js b/i3status_rs/blocks/packages/pacman/sidebar-items.js new file mode 100644 index 0000000000..ab92ae6713 --- /dev/null +++ b/i3status_rs/blocks/packages/pacman/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"static":["PACMAN_DB","PACMAN_UPDATES_DB"],"struct":["Aur","Pacman"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/packages/pacman/static.PACMAN_DB.html b/i3status_rs/blocks/packages/pacman/static.PACMAN_DB.html new file mode 100644 index 0000000000..ba78029efe --- /dev/null +++ b/i3status_rs/blocks/packages/pacman/static.PACMAN_DB.html @@ -0,0 +1 @@ +PACMAN_DB in i3status_rs::blocks::packages::pacman - Rust

Static PACMAN_DB

Source
pub static PACMAN_DB: LazyLock<PathBuf>
\ No newline at end of file diff --git a/i3status_rs/blocks/packages/pacman/static.PACMAN_UPDATES_DB.html b/i3status_rs/blocks/packages/pacman/static.PACMAN_UPDATES_DB.html new file mode 100644 index 0000000000..8c67d14c79 --- /dev/null +++ b/i3status_rs/blocks/packages/pacman/static.PACMAN_UPDATES_DB.html @@ -0,0 +1 @@ +PACMAN_UPDATES_DB in i3status_rs::blocks::packages::pacman - Rust

Static PACMAN_UPDATES_DB

Source
pub static PACMAN_UPDATES_DB: LazyLock<PathBuf>
\ No newline at end of file diff --git a/i3status_rs/blocks/packages/pacman/struct.Aur.html b/i3status_rs/blocks/packages/pacman/struct.Aur.html new file mode 100644 index 0000000000..ee8c23bf2d --- /dev/null +++ b/i3status_rs/blocks/packages/pacman/struct.Aur.html @@ -0,0 +1,36 @@ +Aur in i3status_rs::blocks::packages::pacman - Rust

Struct Aur

Source
pub struct Aur { /* private fields */ }

Implementations§

Source§

impl Aur

Source

pub fn new(aur_command: String) -> Self

Trait Implementations§

Source§

impl Backend for Aur

Source§

fn name(&self) -> Cow<'static, str>

Source§

fn get_updates_list<'life0, 'async_trait>( + &'life0 self, +) -> Pin<Box<dyn Future<Output = Result<Vec<String>>> + Send + 'async_trait>>
where + Self: 'async_trait, + 'life0: 'async_trait,

Auto Trait Implementations§

§

impl Freeze for Aur

§

impl RefUnwindSafe for Aur

§

impl Send for Aur

§

impl Sync for Aur

§

impl Unpin for Aur

§

impl UnwindSafe for Aur

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/packages/pacman/struct.Pacman.html b/i3status_rs/blocks/packages/pacman/struct.Pacman.html new file mode 100644 index 0000000000..21e888a0a3 --- /dev/null +++ b/i3status_rs/blocks/packages/pacman/struct.Pacman.html @@ -0,0 +1,36 @@ +Pacman in i3status_rs::blocks::packages::pacman - Rust

Struct Pacman

Source
pub struct Pacman;

Implementations§

Source§

impl Pacman

Source

pub async fn new() -> Result<Self>

Trait Implementations§

Source§

impl Backend for Pacman

Source§

fn name(&self) -> Cow<'static, str>

Source§

fn get_updates_list<'life0, 'async_trait>( + &'life0 self, +) -> Pin<Box<dyn Future<Output = Result<Vec<String>>> + Send + 'async_trait>>
where + Self: 'async_trait, + 'life0: 'async_trait,

Auto Trait Implementations§

§

impl Freeze for Pacman

§

impl RefUnwindSafe for Pacman

§

impl Send for Pacman

§

impl Sync for Pacman

§

impl Unpin for Pacman

§

impl UnwindSafe for Pacman

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/packages/sidebar-items.js b/i3status_rs/blocks/packages/sidebar-items.js new file mode 100644 index 0000000000..176befa29f --- /dev/null +++ b/i3status_rs/blocks/packages/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["PackageManager"],"fn":["has_matching_update","run"],"mod":["apt","dnf","pacman","xbps"],"struct":["Config"],"trait":["Backend"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/packages/struct.Config.html b/i3status_rs/blocks/packages/struct.Config.html new file mode 100644 index 0000000000..b3aec41f02 --- /dev/null +++ b/i3status_rs/blocks/packages/struct.Config.html @@ -0,0 +1,50 @@ +Config in i3status_rs::blocks::packages - Rust

Struct Config

Source
pub struct Config {
+    pub interval: Seconds,
+    pub package_manager: Vec<PackageManager>,
+    pub format: FormatConfig,
+    pub format_singular: FormatConfig,
+    pub format_up_to_date: FormatConfig,
+    pub warning_updates_regex: Option<String>,
+    pub critical_updates_regex: Option<String>,
+    pub ignore_updates_regex: Option<String>,
+    pub ignore_phased_updates: bool,
+    pub aur_command: Option<String>,
+}

Fields§

§interval: Seconds§package_manager: Vec<PackageManager>§format: FormatConfig§format_singular: FormatConfig§format_up_to_date: FormatConfig§warning_updates_regex: Option<String>§critical_updates_regex: Option<String>§ignore_updates_regex: Option<String>§ignore_phased_updates: bool§aur_command: Option<String>

Trait Implementations§

Source§

impl Clone for Config

Source§

fn clone(&self) -> Config

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { interval: 600.into(), package_manager: Default::default(), format: Default::default(), format_singular: Default::default(), format_up_to_date: Default::default(), warning_updates_regex: Default::default(), critical_updates_regex: Default::default(), ignore_updates_regex: Default::default(), ignore_phased_updates: Default::default(), aur_command: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/packages/trait.Backend.html b/i3status_rs/blocks/packages/trait.Backend.html new file mode 100644 index 0000000000..ad89c11d04 --- /dev/null +++ b/i3status_rs/blocks/packages/trait.Backend.html @@ -0,0 +1,13 @@ +Backend in i3status_rs::blocks::packages - Rust

Trait Backend

Source
pub trait Backend {
+    // Required methods
+    fn name(&self) -> Cow<'static, str>;
+    fn get_updates_list<'life0, 'async_trait>(
+        &'life0 self,
+    ) -> Pin<Box<dyn Future<Output = Result<Vec<String>>> + Send + 'async_trait>>
+       where Self: 'async_trait,
+             'life0: 'async_trait;
+}

Required Methods§

Source

fn name(&self) -> Cow<'static, str>

Source

fn get_updates_list<'life0, 'async_trait>( + &'life0 self, +) -> Pin<Box<dyn Future<Output = Result<Vec<String>>> + Send + 'async_trait>>
where + Self: 'async_trait, + 'life0: 'async_trait,

Implementors§

\ No newline at end of file diff --git a/i3status_rs/blocks/packages/xbps/index.html b/i3status_rs/blocks/packages/xbps/index.html new file mode 100644 index 0000000000..c47d8d62ad --- /dev/null +++ b/i3status_rs/blocks/packages/xbps/index.html @@ -0,0 +1 @@ +i3status_rs::blocks::packages::xbps - Rust

Module xbps

Source

Structs§

Xbps
\ No newline at end of file diff --git a/i3status_rs/blocks/packages/xbps/sidebar-items.js b/i3status_rs/blocks/packages/xbps/sidebar-items.js new file mode 100644 index 0000000000..4ea7b83669 --- /dev/null +++ b/i3status_rs/blocks/packages/xbps/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["Xbps"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/packages/xbps/struct.Xbps.html b/i3status_rs/blocks/packages/xbps/struct.Xbps.html new file mode 100644 index 0000000000..633cee1f0f --- /dev/null +++ b/i3status_rs/blocks/packages/xbps/struct.Xbps.html @@ -0,0 +1,37 @@ +Xbps in i3status_rs::blocks::packages::xbps - Rust

Struct Xbps

Source
pub struct Xbps;

Implementations§

Source§

impl Xbps

Source

pub fn new() -> Self

Trait Implementations§

Source§

impl Backend for Xbps

Source§

fn name(&self) -> Cow<'static, str>

Source§

fn get_updates_list<'life0, 'async_trait>( + &'life0 self, +) -> Pin<Box<dyn Future<Output = Result<Vec<String>>> + Send + 'async_trait>>
where + Self: 'async_trait, + 'life0: 'async_trait,

Source§

impl Default for Xbps

Source§

fn default() -> Xbps

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl Freeze for Xbps

§

impl RefUnwindSafe for Xbps

§

impl Send for Xbps

§

impl Sync for Xbps

§

impl Unpin for Xbps

§

impl UnwindSafe for Xbps

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/pomodoro/fn.run.html b/i3status_rs/blocks/pomodoro/fn.run.html new file mode 100644 index 0000000000..17b99ed4dd --- /dev/null +++ b/i3status_rs/blocks/pomodoro/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::pomodoro - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/pomodoro/index.html b/i3status_rs/blocks/pomodoro/index.html new file mode 100644 index 0000000000..b9ec865a86 --- /dev/null +++ b/i3status_rs/blocks/pomodoro/index.html @@ -0,0 +1,46 @@ +i3status_rs::blocks::pomodoro - Rust

Module pomodoro

Source
Expand description

A pomodoro timer

+

§Technique

+

There are six steps in the original technique:

+
    +
  1. Decide on the task to be done.
  2. +
  3. Set the pomodoro timer (traditionally to 25 minutes).
  4. +
  5. Work on the task.
  6. +
  7. End work when the timer rings and put a checkmark on a piece of paper.
  8. +
  9. If you have fewer than four checkmarks, take a short break (3–5 minutes) and then return to step 2.
  10. +
  11. After four pomodoros, take a longer break (15–30 minutes), reset your checkmark count to zero, then go to step 1.
  12. +
+

§Configuration

+ + + + + + + +
KeyValuesDefault
formatThe format used when in idle, prompt, or notify states" $icon{ $message|} "
pomodoro_formatThe format used when the pomodoro is running or paused" $icon $status_icon{ $completed_pomodoros.tally()|} $time_remaining.duration(hms:true) "
break_formatThe format used when the pomodoro is during the break" $icon $status_icon Break: $time_remaining.duration(hms:true) "
messageMessage when timer expires"Pomodoro over! Take a break!"
break_messageMessage when break is over"Break over! Time to work!"
notify_cmdA shell command to run as a notifier. {msg} will be substituted with either message or break_message.None
blocking_cmdIs notify_cmd blocking? If it is, then pomodoro block will wait until the command finishes before proceeding. Otherwise, you will have to click on the block in order to proceed.false
+
+ + + + + +
PlaceholderValueTypeSupported by
iconA static iconIconAll formats
status_iconAn icon that reflects the pomodoro stateIconpomodoro_format, break_format
messageCurrent messageTextformat
time_remainingHow much time is left (minutes)Durationpomodoro_format, break_format
completed_pomodorosThe number of completed pomodorosNumberpomodoro_format
+

§Example

+

Use swaynag as a notifier:

+
[[block]]
+block = "pomodoro"
+notify_cmd = "swaynag -m '{msg}'"
+blocking_cmd = true
+

Use notify-send as a notifier:

+
[[block]]
+block = "pomodoro"
+notify_cmd = "notify-send '{msg}'"
+blocking_cmd = false

§Icons Used

+
    +
  • pomodoro
  • +
  • pomodoro_started
  • +
  • pomodoro_stopped
  • +
  • pomodoro_paused
  • +
  • pomodoro_break
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/pomodoro/sidebar-items.js b/i3status_rs/blocks/pomodoro/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/pomodoro/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/pomodoro/struct.Config.html b/i3status_rs/blocks/pomodoro/struct.Config.html new file mode 100644 index 0000000000..164c568090 --- /dev/null +++ b/i3status_rs/blocks/pomodoro/struct.Config.html @@ -0,0 +1,45 @@ +Config in i3status_rs::blocks::pomodoro - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub pomodoro_format: FormatConfig,
+    pub break_format: FormatConfig,
+    pub message: String,
+    pub break_message: String,
+    pub notify_cmd: Option<String>,
+    pub blocking_cmd: bool,
+}

Fields§

§format: FormatConfig§pomodoro_format: FormatConfig§break_format: FormatConfig§message: String§break_message: String§notify_cmd: Option<String>§blocking_cmd: bool

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), pomodoro_format: Default::default(), break_format: Default::default(), message: "Pomodoro over! Take a break!".into(), break_message: "Break over! Time to work!".into(), notify_cmd: Default::default(), blocking_cmd: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/privacy/enum.PrivacyDriver.html b/i3status_rs/blocks/privacy/enum.PrivacyDriver.html new file mode 100644 index 0000000000..1f57077ac2 --- /dev/null +++ b/i3status_rs/blocks/privacy/enum.PrivacyDriver.html @@ -0,0 +1,37 @@ +PrivacyDriver in i3status_rs::blocks::privacy - Rust

Enum PrivacyDriver

Source
pub enum PrivacyDriver {
+    Pipewire(Config),
+    V4l(Config),
+}

Variants§

§

Pipewire(Config)

§

V4l(Config)

Trait Implementations§

Source§

impl Debug for PrivacyDriver

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for PrivacyDriver

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/privacy/fn.run.html b/i3status_rs/blocks/privacy/fn.run.html new file mode 100644 index 0000000000..c0014ee972 --- /dev/null +++ b/i3status_rs/blocks/privacy/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::privacy - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/privacy/index.html b/i3status_rs/blocks/privacy/index.html new file mode 100644 index 0000000000..b13252fcce --- /dev/null +++ b/i3status_rs/blocks/privacy/index.html @@ -0,0 +1,49 @@ +i3status_rs::blocks::privacy - Rust

Module privacy

Source
Expand description

Privacy Monitor

+

§Configuration

+ + + +
KeyValuesDefault
driverThe configuration of a driver (see below).Required
formatFormat string."{ $icon_audio |}{ $icon_audio_sink |}{ $icon_video |}{ $icon_webcam |}{ $icon_unknown |}"
format_altFormat string."{ $icon_audio $info_audio |}{ $icon_audio_sink $info_audio_sink |}{ $icon_video $info_video |}{ $icon_webcam $info_webcam |}{ $icon_unknown $info_unknown |}"
+

§pipewire Options (requires the pipewire feature to be enabled)

+ + + + +
KeyValuesRequiredDefault
namepipewireYesNone
exclude_outputAn output node to ignore, example: ["HD Pro Webcam C920"]No[]
exclude_inputAn input node to ignore, example: ["openrgb"]No[]
displayWhich node field should be used as a display name, options: name, description, nicknameNoname
+

§vl4 Options

+ + + +
KeyValuesRequiredDefault
namevl4YesNone
exclude_deviceA device to ignore, example: ["/dev/video5"]No[]
exclude_consumerProcesses to ignoreNo["pipewire", "wireplumber"]
+

§Available Format Keys

+ + +
PlaceholderValueTypeUnit
icon_{audio,audio_sink,video,webcam,unknown}A static iconIcon-
info_{audio,audio_sink,video,webcam,unknown}The mapping of which source are being consumedText-
+
+

You can use the suffixes noted above to get the following:

+
+ + + + + +
SuffixDescription
audioCaptured audio (ex. Mic)
audio_sinkAudio captured from a sink (ex. openrgb)
videoVideo capture (ex. screen capture)
webcamWebcam capture
unknownAnything else
+

§Available Actions

+ +
ActionDescriptionDefault button
toggle_formatToggles between format and format_altLeft
+

§Example

[[block]]
+block = "privacy"
+[[block.driver]]
+name = "v4l"
+[[block.driver]]
+name = "pipewire"
+exclude_input = ["openrgb"]
+display = "nickname"

§Icons Used

+
    +
  • microphone
  • +
  • volume
  • +
  • xrandr
  • +
  • webcam
  • +
  • unknown
  • +
+

Structs§

Config

Enums§

PrivacyDriver

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/privacy/sidebar-items.js b/i3status_rs/blocks/privacy/sidebar-items.js new file mode 100644 index 0000000000..888c1c18b1 --- /dev/null +++ b/i3status_rs/blocks/privacy/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["PrivacyDriver"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/privacy/struct.Config.html b/i3status_rs/blocks/privacy/struct.Config.html new file mode 100644 index 0000000000..280b59658f --- /dev/null +++ b/i3status_rs/blocks/privacy/struct.Config.html @@ -0,0 +1,38 @@ +Config in i3status_rs::blocks::privacy - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub format_alt: FormatConfig,
+    pub driver: Vec<PrivacyDriver>,
+}

Fields§

§format: FormatConfig§format_alt: FormatConfig§driver: Vec<PrivacyDriver>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Config

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/rofication/fn.run.html b/i3status_rs/blocks/rofication/fn.run.html new file mode 100644 index 0000000000..f12f4c67d1 --- /dev/null +++ b/i3status_rs/blocks/rofication/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::rofication - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/rofication/index.html b/i3status_rs/blocks/rofication/index.html new file mode 100644 index 0000000000..2d8c18613a --- /dev/null +++ b/i3status_rs/blocks/rofication/index.html @@ -0,0 +1,22 @@ +i3status_rs::blocks::rofication - Rust

Module rofication

Source
Expand description

The number of pending notifications in rofication-daemon

+

A different color is used if there are critical notifications.

+

§Configuration

+ + + +
KeyValuesDefault
intervalRefresh rate in seconds.1
formatA string to customise the output of this block. See below for placeholders." $icon $num.eng(w:1) "
socket_pathSocket path for the rofication daemon. Supports path expansions e.g. ~."/tmp/rofi_notification_daemon"
+
+ + +
PlaceholderValueTypeUnit
iconA static iconIcon-
numNumber of pending notificationsNumber-
+

§Example

[[block]]
+block = "rofication"
+interval = 1
+socket_path = "/tmp/rofi_notification_daemon"
+[[block.click]]
+button = "left"
+cmd = "rofication-gui"

§Icons Used

+
    +
  • bell
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/rofication/sidebar-items.js b/i3status_rs/blocks/rofication/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/rofication/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/rofication/struct.Config.html b/i3status_rs/blocks/rofication/struct.Config.html new file mode 100644 index 0000000000..a5079c6adb --- /dev/null +++ b/i3status_rs/blocks/rofication/struct.Config.html @@ -0,0 +1,41 @@ +Config in i3status_rs::blocks::rofication - Rust

Struct Config

Source
pub struct Config {
+    pub interval: Seconds,
+    pub socket_path: ShellString,
+    pub format: FormatConfig,
+}

Fields§

§interval: Seconds§socket_path: ShellString§format: FormatConfig

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { interval: 1.into(), socket_path: "/tmp/rofi_notification_daemon".into(), format: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/scratchpad/fn.run.html b/i3status_rs/blocks/scratchpad/fn.run.html new file mode 100644 index 0000000000..984fd3c71e --- /dev/null +++ b/i3status_rs/blocks/scratchpad/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::scratchpad - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/scratchpad/index.html b/i3status_rs/blocks/scratchpad/index.html new file mode 100644 index 0000000000..352e0d420d --- /dev/null +++ b/i3status_rs/blocks/scratchpad/index.html @@ -0,0 +1,14 @@ +i3status_rs::blocks::scratchpad - Rust

Module scratchpad

Source
Expand description

Scratchpad indicator

+

§Configuration

+ +
KeyValuesDefault
formatA string to customise the output of this block` $icon $count.eng(range:1..)
+
+ + +
PlaceholderValueTypeUnit
iconA static iconIcon-
countNumber of windows in scratchpadNumber-
+

§Example

[[block]]
+block = "scratchpad"

§Icons Used

+
    +
  • scratchpad
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/scratchpad/sidebar-items.js b/i3status_rs/blocks/scratchpad/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/scratchpad/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/scratchpad/struct.Config.html b/i3status_rs/blocks/scratchpad/struct.Config.html new file mode 100644 index 0000000000..7efbc77db9 --- /dev/null +++ b/i3status_rs/blocks/scratchpad/struct.Config.html @@ -0,0 +1,39 @@ +Config in i3status_rs::blocks::scratchpad - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+}

Fields§

§format: FormatConfig

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/service_status/enum.DriverType.html b/i3status_rs/blocks/service_status/enum.DriverType.html new file mode 100644 index 0000000000..303b05159b --- /dev/null +++ b/i3status_rs/blocks/service_status/enum.DriverType.html @@ -0,0 +1,38 @@ +DriverType in i3status_rs::blocks::service_status - Rust

Enum DriverType

Source
pub enum DriverType {
+    Systemd,
+}

Variants§

§

Systemd

Trait Implementations§

Source§

impl Debug for DriverType

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for DriverType

Source§

fn default() -> Self

Return DriverType::Systemd

+
Source§

impl<'de> Deserialize<'de> for DriverType

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/service_status/fn.run.html b/i3status_rs/blocks/service_status/fn.run.html new file mode 100644 index 0000000000..36d7e960c3 --- /dev/null +++ b/i3status_rs/blocks/service_status/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::service_status - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/service_status/index.html b/i3status_rs/blocks/service_status/index.html new file mode 100644 index 0000000000..36557aa881 --- /dev/null +++ b/i3status_rs/blocks/service_status/index.html @@ -0,0 +1,28 @@ +i3status_rs::blocks::service_status - Rust

Module service_status

Source
Expand description

Display the status of a service

+

Right now only systemd is supported.

+

§Configuration

+ + + + + + + +
KeyValuesDefault
driverWhich init system is running the service. Available drivers are: "systemd""systemd"
serviceThe name of the serviceRequired
userIf true, monitor the status of a user service instead of a system service.false
active_formatA string to customise the output of this block. See below for available placeholders." $service active "
inactive_formatA string to customise the output of this block. See below for available placeholders." $service inactive "
active_stateA valid StateState::Idle
inactive_stateA valid StateState::Critical
+
+ +
PlaceholderValueTypeUnit
serviceThe name of the serviceText-
+

§Example

+

Example using an icon:

+
[[block]]
+block = "service_status"
+service = "cups"
+active_format = " ^icon_tea "
+inactive_format = " no ^icon_tea "
+

Example overriding the default inactive_state:

+
[[block]]
+block = "service_status"
+service = "shadow"
+active_format = ""
+inactive_format = " Integrity of password and group files failed "
+inactive_state = "Warning"

Structs§

Config

Enums§

DriverType

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/service_status/sidebar-items.js b/i3status_rs/blocks/service_status/sidebar-items.js new file mode 100644 index 0000000000..7cba7410b2 --- /dev/null +++ b/i3status_rs/blocks/service_status/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["DriverType"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/service_status/struct.Config.html b/i3status_rs/blocks/service_status/struct.Config.html new file mode 100644 index 0000000000..38f05b0205 --- /dev/null +++ b/i3status_rs/blocks/service_status/struct.Config.html @@ -0,0 +1,44 @@ +Config in i3status_rs::blocks::service_status - Rust

Struct Config

Source
pub struct Config {
+    pub driver: DriverType,
+    pub service: String,
+    pub user: bool,
+    pub active_format: FormatConfig,
+    pub inactive_format: FormatConfig,
+    pub active_state: Option<State>,
+    pub inactive_state: Option<State>,
+}

Fields§

§driver: DriverType§service: String§user: bool§active_format: FormatConfig§inactive_format: FormatConfig§active_state: Option<State>§inactive_state: Option<State>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Config

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/sidebar-items.js b/i3status_rs/blocks/sidebar-items.js new file mode 100644 index 0000000000..6e2850003c --- /dev/null +++ b/i3status_rs/blocks/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["BlockConfig"],"mod":["amd_gpu","backlight","battery","bluetooth","calendar","cpu","custom","custom_dbus","disk_iostats","disk_space","docker","external_ip","focused_window","github","hueshift","kdeconnect","keyboard_layout","load","maildir","memory","menu","music","net","notify","notmuch","nvidia_gpu","packages","pomodoro","privacy","rofication","scratchpad","service_status","sound","speedtest","taskwarrior","tea_timer","temperature","time","toggle","uptime","vpn","watson","weather","xrandr"],"struct":["BlockError","CommonApi"],"type":["BlockAction"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/sound/enum.DeviceKind.html b/i3status_rs/blocks/sound/enum.DeviceKind.html new file mode 100644 index 0000000000..9fcbf4dd2a --- /dev/null +++ b/i3status_rs/blocks/sound/enum.DeviceKind.html @@ -0,0 +1,48 @@ +DeviceKind in i3status_rs::blocks::sound - Rust

Enum DeviceKind

Source
pub enum DeviceKind {
+    Sink,
+    Source,
+}

Variants§

§

Sink

§

Source

Implementations§

Source§

impl DeviceKind

Source

pub fn default_name(self) -> Cow<'static, str>

Trait Implementations§

Source§

impl Clone for DeviceKind

Source§

fn clone(&self) -> DeviceKind

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for DeviceKind

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for DeviceKind

Source§

fn default() -> Self

Return DeviceKind::Sink

+
Source§

impl<'de> Deserialize<'de> for DeviceKind

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Hash for DeviceKind

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for DeviceKind

Source§

fn eq(&self, other: &DeviceKind) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for DeviceKind

Source§

impl Eq for DeviceKind

Source§

impl StructuralPartialEq for DeviceKind

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/sound/enum.SoundDriver.html b/i3status_rs/blocks/sound/enum.SoundDriver.html new file mode 100644 index 0000000000..efd6f414d5 --- /dev/null +++ b/i3status_rs/blocks/sound/enum.SoundDriver.html @@ -0,0 +1,42 @@ +SoundDriver in i3status_rs::blocks::sound - Rust

Enum SoundDriver

Source
pub enum SoundDriver {
+    Auto,
+    Alsa,
+    PulseAudio,
+}

Variants§

§

Auto

§

Alsa

§

PulseAudio

Trait Implementations§

Source§

impl Clone for SoundDriver

Source§

fn clone(&self) -> SoundDriver

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SoundDriver

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for SoundDriver

Source§

fn default() -> Self

Return SoundDriver::Auto

+
Source§

impl<'de> Deserialize<'de> for SoundDriver

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Copy for SoundDriver

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/sound/fn.run.html b/i3status_rs/blocks/sound/fn.run.html new file mode 100644 index 0000000000..ec62523ebc --- /dev/null +++ b/i3status_rs/blocks/sound/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::sound - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/sound/index.html b/i3status_rs/blocks/sound/index.html new file mode 100644 index 0000000000..e5ec7275a2 --- /dev/null +++ b/i3status_rs/blocks/sound/index.html @@ -0,0 +1,68 @@ +i3status_rs::blocks::sound - Rust

Module sound

Source
Expand description

Volume level

+

This block displays the volume level (according to PulseAudio or ALSA). Right click to toggle mute, scroll to adjust volume.

+

Requires a PulseAudio installation or alsa-utils for ALSA.

+

Note that if you are using PulseAudio commands (such as pactl) to control your volume, you should select the "pulseaudio" (or "auto") driver to see volume changes that exceed 100%.

+

§Configuration

+ + + + + + + + + + + + + + +
KeyValuesDefault
driver"auto", "pulseaudio", "alsa"."auto" (Pulseaudio with ALSA fallback)
formatA string to customise the output of this block. See below for available placeholders." $icon {$volume.eng(w:2) |}"
format_altIf set, block will switch between format and format_alt on every click.None
namePulseAudio device name, or the ALSA control name as found in the output of amixer -D yourdevice scontrols.PulseAudio: @DEFAULT_SINK@ / ALSA: Master
deviceALSA device name, usually in the form “hw:X” or “hw:X,Y” where X is the card number and Y is the device number as found in the output of aplay -l.default
device_kindPulseAudio device kind: source or sink."sink"
natural_mappingWhen using the ALSA driver, display the “mapped volume” as given by alsamixer/amixer -M, which represents the volume level more naturally with respect for the human ear.false
step_widthThe percent volume level is increased/decreased for the selected audio device when scrolling. Capped automatically at 50.5
max_volMax volume in percent that can be set via scrolling. Note it can still be set above this value if changed by another application.None
show_volume_when_mutedShow the volume even if it is currently muted.false
headphones_indicatorChange icon when headphones are plugged in (pulseaudio only)false
mappingsMap output_name to a custom name.None
mappings_use_regexLet mappings match using regex instead of string equality. The replacement will be regex aware and can contain capture groups.true
active_port_mappingsMap active_port to a custom name. The replacement will be regex aware and can contain capture groups.None
+
+ + + + + +
PlaceholderValueTypeUnit
iconIcon based on volumeIcon-
volumeCurrent volume. Missing if muted.Number%
output_namePulseAudio or ALSA device nameText-
output_descriptionPulseAudio device description, will fallback to output_name if no description is available and will be overwritten by mappings (mappings will still use output_name)Text-
active_portActive port (same as information in Ports section of pactl list cards). Will be absent if not supported by driver or if mapped to "" in active_port_mappings.Text-
+
+ + + + +
ActionDefault button
toggle_formatLeft
toggle_muteRight
volume_downWheel Down
volume_upWheel Up
+

§Examples

+

Change the default scrolling step width to 3 percent:

+
[[block]]
+block = "sound"
+step_width = 3
+

Change the output name shown:

+
[[block]]
+block = "sound"
+format = " $icon $output_name{ $volume|} "
+[block.mappings]
+"alsa_output.usb-Harman_Multimedia_JBL_Pebbles_1.0.0-00.analog-stereo" = "Speakers"
+"alsa_output.pci-0000_00_1b.0.analog-stereo" = "Headset"
+

Since the default value for the device_kind key is sink, +to display microphone block you have to use the source value:

+
[[block]]
+block = "sound"
+driver = "pulseaudio"
+device_kind = "source"
+

Display warning in block if microphone if using the wrong port:

+
[[block]]
+block = "sound"
+driver = "pulseaudio"
+device_kind = "source"
+format = " $icon { $volume|} {$active_port |}"
+[block.active_port_mappings]
+"analog-input-rear-mic" = "" # Mapping to an empty string makes `$active_port` absent
+"analog-input-front-mic" = "ERR!"

§Icons Used

+
    +
  • microphone_muted (as a progression)
  • +
  • microphone (as a progression)
  • +
  • volume_muted (as a progression)
  • +
  • volume (as a progression)
  • +
  • headphones
  • +
+

Structs§

Config

Enums§

DeviceKind
SoundDriver

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/sound/sidebar-items.js b/i3status_rs/blocks/sound/sidebar-items.js new file mode 100644 index 0000000000..d53e3a0890 --- /dev/null +++ b/i3status_rs/blocks/sound/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["DeviceKind","SoundDriver"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/sound/struct.Config.html b/i3status_rs/blocks/sound/struct.Config.html new file mode 100644 index 0000000000..b5dea859d6 --- /dev/null +++ b/i3status_rs/blocks/sound/struct.Config.html @@ -0,0 +1,52 @@ +Config in i3status_rs::blocks::sound - Rust

Struct Config

Source
pub struct Config {
Show 14 fields + pub driver: SoundDriver, + pub name: Option<String>, + pub device: Option<String>, + pub device_kind: DeviceKind, + pub natural_mapping: bool, + pub step_width: u32, + pub format: FormatConfig, + pub format_alt: Option<FormatConfig>, + pub headphones_indicator: bool, + pub show_volume_when_muted: bool, + pub mappings: Option<IndexMap<String, String>>, + pub mappings_use_regex: bool, + pub max_vol: Option<u32>, + pub active_port_mappings: IndexMap<SerdeRegex, String>, +
}

Fields§

§driver: SoundDriver§name: Option<String>§device: Option<String>§device_kind: DeviceKind§natural_mapping: bool§step_width: u32§format: FormatConfig§format_alt: Option<FormatConfig>§headphones_indicator: bool§show_volume_when_muted: bool§mappings: Option<IndexMap<String, String>>§mappings_use_regex: bool§max_vol: Option<u32>§active_port_mappings: IndexMap<SerdeRegex, String>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { driver: Default::default(), name: Default::default(), device: Default::default(), device_kind: Default::default(), natural_mapping: Default::default(), step_width: 5, format: Default::default(), format_alt: Default::default(), headphones_indicator: Default::default(), show_volume_when_muted: Default::default(), mappings: Default::default(), mappings_use_regex: true, max_vol: Default::default(), active_port_mappings: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/speedtest/fn.run.html b/i3status_rs/blocks/speedtest/fn.run.html new file mode 100644 index 0000000000..8f509523d8 --- /dev/null +++ b/i3status_rs/blocks/speedtest/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::speedtest - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/speedtest/index.html b/i3status_rs/blocks/speedtest/index.html new file mode 100644 index 0000000000..3b8396f6a5 --- /dev/null +++ b/i3status_rs/blocks/speedtest/index.html @@ -0,0 +1,28 @@ +i3status_rs::blocks::speedtest - Rust

Module speedtest

Source
Expand description

Ping, download, and upload speeds

+

This block which requires speedtest-cli.

+

§Configuration

+ + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders." ^icon_ping $ping ^icon_net_down $speed_down ^icon_net_up $speed_up "
intervalUpdate interval in seconds1800
+
+ + + +
PlaceholderValueTypeUnit
pingPing delayNumberSeconds
speed_downDownload speedNumberBits per second
speed_upUpload speedNumberBits per second
+

§Example

+

Show only ping (with an icon)

+
[[block]]
+block = "speedtest"
+interval = 1800
+format = " ^icon_ping $ping "
+

Hide ping and display speed in bytes per second each using 4 characters (without icons)

+
[[block]]
+block = "speedtest"
+interval = 1800
+format = " $speed_down.eng(w:4,u:B) $speed_up(w:4,u:B) "

§Icons Used

+
    +
  • ping
  • +
  • net_down
  • +
  • net_up
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/speedtest/sidebar-items.js b/i3status_rs/blocks/speedtest/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/speedtest/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/speedtest/struct.Config.html b/i3status_rs/blocks/speedtest/struct.Config.html new file mode 100644 index 0000000000..d2de4ead4e --- /dev/null +++ b/i3status_rs/blocks/speedtest/struct.Config.html @@ -0,0 +1,40 @@ +Config in i3status_rs::blocks::speedtest - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub interval: Seconds,
+}

Fields§

§format: FormatConfig§interval: Seconds

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), interval: 1800.into() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/struct.BlockError.html b/i3status_rs/blocks/struct.BlockError.html new file mode 100644 index 0000000000..5059a86769 --- /dev/null +++ b/i3status_rs/blocks/struct.BlockError.html @@ -0,0 +1,40 @@ +BlockError in i3status_rs::blocks - Rust

Struct BlockError

Source
pub struct BlockError {
+    pub block_id: usize,
+    pub block_name: &'static str,
+    pub error: Error,
+}
Expand description

An error which originates from a block

+

Fields§

§block_id: usize§block_name: &'static str§error: Error

Trait Implementations§

Source§

impl Debug for BlockError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for BlockError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for BlockError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToString for T
where + T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
§

impl<T> ToStringFallible for T
where + T: Display,

§

fn try_to_string(&self) -> Result<String, TryReserveError>

ToString::to_string, but without panic on OOM.

+
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/struct.CommonApi.html b/i3status_rs/blocks/struct.CommonApi.html new file mode 100644 index 0000000000..6cbe41a44e --- /dev/null +++ b/i3status_rs/blocks/struct.CommonApi.html @@ -0,0 +1,45 @@ +CommonApi in i3status_rs::blocks - Rust

Struct CommonApi

Source
pub struct CommonApi { /* private fields */ }

Implementations§

Source§

impl CommonApi

Source

pub fn set_widget(&self, widget: Widget) -> Result<()>

Sends the widget to be displayed.

+
Source

pub fn hide(&self) -> Result<()>

Hides the block. Send new widget to make it visible again.

+
Source

pub fn set_error(&self, error: Error) -> Result<()>

Sends the error to be displayed.

+
Source

pub fn set_default_actions( + &self, + actions: &'static [(MouseButton, Option<&'static str>, &'static str)], +) -> Result<()>

Source

pub fn get_actions(&self) -> Result<UnboundedReceiver<BlockAction>>

Source

pub async fn wait_for_update_request(&self)

Source

pub async fn find_ip_location( + &self, + client: &Client, + interval: Duration, +) -> Result<IPAddressInfo>

No-op if last API call was made in the last interval seconds.

+

Trait Implementations§

Source§

impl Clone for CommonApi

Source§

fn clone(&self) -> CommonApi

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/taskwarrior/fn.run.html b/i3status_rs/blocks/taskwarrior/fn.run.html new file mode 100644 index 0000000000..37f297b3f9 --- /dev/null +++ b/i3status_rs/blocks/taskwarrior/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::taskwarrior - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/taskwarrior/index.html b/i3status_rs/blocks/taskwarrior/index.html new file mode 100644 index 0000000000..d1df89e716 --- /dev/null +++ b/i3status_rs/blocks/taskwarrior/index.html @@ -0,0 +1,46 @@ +i3status_rs::blocks::taskwarrior - Rust

Module taskwarrior

Source
Expand description

The number of tasks from the taskwarrior list

+

Clicking the right mouse button on the icon cycles the view of the block through the user’s filters.

+

§Configuration

+ + + + + + + + +
KeyValuesDefault
intervalUpdate interval in seconds600 (10min)
warning_thresholdThe threshold of pending (or started) tasks when the block turns into a warning state10
critical_thresholdThe threshold of pending (or started) tasks when the block turns into a critical state20
filtersA list of tables describing filters (see bellow)[{name = "pending", filter = "-COMPLETED -DELETED"}]
formatA string to customise the output of this block. See below for available placeholders." $icon $count.eng(w:1) "
format_singularSame as format but for when exactly one task is pending." $icon $count.eng(w:1) "
format_everything_doneSame as format but for when all tasks are completed." $icon $count.eng(w:1) "
data_locationDirectory in which taskwarrior stores its data files. Supports path expansions e.g. ~."~/.task"
+

§Filter configuration

+ + + +
KeyValuesDefault
nameThe name of the filter
filterSpecifies the criteria that must be met for a task to be counted towards this filter
config_overrideAn array containing configuration overrides, useful for explicitly setting context or other configuration variables[]
+

§Placeholders

+ + + +
PlaceholderValueTypeUnit
iconA static iconIcon-
countThe number of tasks matching current filterNumber-
filter_nameThe name of current filterText-
+

§Actions

+ +
ActionDefault button
next_filterRight
+

§Example

+

In this example, block will be hidden if count is zero.

+
[[block]]
+block = "taskwarrior"
+interval = 60
+format = " $icon count.eng(w:1) tasks "
+format_singular = " $icon 1 task "
+format_everything_done = ""
+warning_threshold = 10
+critical_threshold = 20
+[[block.filters]]
+name = "today"
+filter = "+PENDING +OVERDUE or +DUETODAY"
+[[block.filters]]
+name = "some-project"
+filter = "project:some-project +PENDING"
+config_override = ["rc.context:none"]

§Icons Used

+
    +
  • tasks
  • +
+

Structs§

Config
Filter

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/taskwarrior/sidebar-items.js b/i3status_rs/blocks/taskwarrior/sidebar-items.js new file mode 100644 index 0000000000..66fd1a26fd --- /dev/null +++ b/i3status_rs/blocks/taskwarrior/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config","Filter"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/taskwarrior/struct.Config.html b/i3status_rs/blocks/taskwarrior/struct.Config.html new file mode 100644 index 0000000000..2909bca1fc --- /dev/null +++ b/i3status_rs/blocks/taskwarrior/struct.Config.html @@ -0,0 +1,45 @@ +Config in i3status_rs::blocks::taskwarrior - Rust

Struct Config

Source
pub struct Config {
+    pub interval: Seconds,
+    pub warning_threshold: u32,
+    pub critical_threshold: u32,
+    pub filters: Vec<Filter>,
+    pub format: FormatConfig,
+    pub format_singular: FormatConfig,
+    pub format_everything_done: FormatConfig,
+    pub data_location: ShellString,
+}

Fields§

§interval: Seconds§warning_threshold: u32§critical_threshold: u32§filters: Vec<Filter>§format: FormatConfig§format_singular: FormatConfig§format_everything_done: FormatConfig§data_location: ShellString

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/taskwarrior/struct.Filter.html b/i3status_rs/blocks/taskwarrior/struct.Filter.html new file mode 100644 index 0000000000..97da91414a --- /dev/null +++ b/i3status_rs/blocks/taskwarrior/struct.Filter.html @@ -0,0 +1,41 @@ +Filter in i3status_rs::blocks::taskwarrior - Rust

Struct Filter

Source
pub struct Filter {
+    pub name: String,
+    pub filter: String,
+    pub config_override: Vec<String>,
+}

Fields§

§name: String§filter: String§config_override: Vec<String>

Trait Implementations§

Source§

impl Clone for Filter

Source§

fn clone(&self) -> Filter

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Filter

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Filter

Source§

fn default() -> Filter

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for Filter

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Filter

§

impl RefUnwindSafe for Filter

§

impl Send for Filter

§

impl Sync for Filter

§

impl Unpin for Filter

§

impl UnwindSafe for Filter

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/tea_timer/fn.run.html b/i3status_rs/blocks/tea_timer/fn.run.html new file mode 100644 index 0000000000..e68b9fbc5d --- /dev/null +++ b/i3status_rs/blocks/tea_timer/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::tea_timer - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/tea_timer/index.html b/i3status_rs/blocks/tea_timer/index.html new file mode 100644 index 0000000000..f6015dbe42 --- /dev/null +++ b/i3status_rs/blocks/tea_timer/index.html @@ -0,0 +1,29 @@ +i3status_rs::blocks::tea_timer - Rust

Module tea_timer

Source
Expand description

Timer

+

§Configuration

+ + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders." $icon {$time.duration(hms:true) |}"
incrementThe numbers of seconds to add each time the block is clicked.30
done_cmdA command to run in sh when timer finishes.None
+
+ + + + + +
PlaceholderValueTypeUnit
iconA static iconIcon-
timeThe time remaining on the timerDuration-
hours DEPRECATEDThe hours remaining on the timerTexth
minutes DEPRECATEDThe minutes remaining on the timerTextmn
seconds DEPRECATEDThe seconds remaining on the timerTexts
+
+

time, hours, minutes, and seconds are unset when the timer is inactive.

+

hours, minutes, and seconds have been deprecated in favor of time.

+
+ + + +
ActionDefault button
incrementLeft / Wheel Up
decrementWheel Down
resetRight
+

§Example

[[block]]
+block = "tea_timer"
+format = " $icon {$minutes:$seconds |}"
+done_cmd = "notify-send 'Timer Finished'"

§Icons Used

+
    +
  • tea
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/tea_timer/sidebar-items.js b/i3status_rs/blocks/tea_timer/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/tea_timer/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/tea_timer/struct.Config.html b/i3status_rs/blocks/tea_timer/struct.Config.html new file mode 100644 index 0000000000..202ba70579 --- /dev/null +++ b/i3status_rs/blocks/tea_timer/struct.Config.html @@ -0,0 +1,41 @@ +Config in i3status_rs::blocks::tea_timer - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub increment: Option<u64>,
+    pub done_cmd: Option<String>,
+}

Fields§

§format: FormatConfig§increment: Option<u64>§done_cmd: Option<String>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), increment: Default::default(), done_cmd: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/temperature/enum.TemperatureScale.html b/i3status_rs/blocks/temperature/enum.TemperatureScale.html new file mode 100644 index 0000000000..00ba9ac3fe --- /dev/null +++ b/i3status_rs/blocks/temperature/enum.TemperatureScale.html @@ -0,0 +1,46 @@ +TemperatureScale in i3status_rs::blocks::temperature - Rust

Enum TemperatureScale

Source
pub enum TemperatureScale {
+    Celsius,
+    Fahrenheit,
+}

Variants§

§

Celsius

§

Fahrenheit

Implementations§

Trait Implementations§

Source§

impl Clone for TemperatureScale

Source§

fn clone(&self) -> TemperatureScale

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for TemperatureScale

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for TemperatureScale

Source§

fn default() -> Self

Return TemperatureScale::Celsius

+
Source§

impl<'de> Deserialize<'de> for TemperatureScale

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for TemperatureScale

Source§

fn eq(&self, other: &TemperatureScale) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for TemperatureScale

Source§

impl Eq for TemperatureScale

Source§

impl StructuralPartialEq for TemperatureScale

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/temperature/fn.run.html b/i3status_rs/blocks/temperature/fn.run.html new file mode 100644 index 0000000000..3630751d63 --- /dev/null +++ b/i3status_rs/blocks/temperature/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::temperature - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/temperature/index.html b/i3status_rs/blocks/temperature/index.html new file mode 100644 index 0000000000..77134b5fea --- /dev/null +++ b/i3status_rs/blocks/temperature/index.html @@ -0,0 +1,42 @@ +i3status_rs::blocks::temperature - Rust

Module temperature

Source
Expand description

The system temperature

+

This block displays the system temperature, based on libsensors library.

+

This block has two modes: “collapsed”, which uses only color as an indicator, and “expanded”, +which shows the content of a format string. The average, minimum, and maximum temperatures +are computed using all sensors displayed by sensors, or optionally filtered by chip and +inputs.

+

Requires libsensors and appropriate kernel modules for your hardware.

+

Run sensors command to list available chips and inputs.

+

Note that the colour of the block is always determined by the maximum temperature across all +sensors, not the average. You may need to keep this in mind if you have a misbehaving sensor.

+

§Configuration

+ + + + + + + + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders" $icon $average avg, $max max "
format_altIf set, block will switch between format and format_alt on every clickNone
intervalUpdate interval in seconds5
scaleEither "celsius" or "fahrenheit""celsius"
goodMaximum temperature to set state to good20 °C (68 °F)
idleMaximum temperature to set state to idle45 °C (113 °F)
infoMaximum temperature to set state to info60 °C (140 °F)
warningMaximum temperature to set state to warning. Beyond this temperature, state is set to critical80 °C (176 °F)
chipNarrows the results to a given chip name. * may be used as a wildcard.None
inputsNarrows the results to individual inputs reported by each chip.None
+
+ +
ActionDescriptionDefault button
toggle_formatToggles between format and format_altLeft
+
+ + + +
PlaceholderValueTypeUnit
minMinimum temperature among all inputsNumberDegrees
averageAverage temperature among all inputsNumberDegrees
maxMaximum temperature among all inputsNumberDegrees
+
+

Note that when block is collapsed, no placeholders are provided.

+

§Example

[[block]]
+block = "temperature"
+format = " $icon $max max "
+format_alt = " $icon $min min, $max max, $average avg "
+interval = 10
+chip = "*-isa-*"

§Icons Used

+
    +
  • thermometer
  • +
+

Structs§

Config

Enums§

TemperatureScale

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/temperature/sidebar-items.js b/i3status_rs/blocks/temperature/sidebar-items.js new file mode 100644 index 0000000000..c6632b6bab --- /dev/null +++ b/i3status_rs/blocks/temperature/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["TemperatureScale"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/temperature/struct.Config.html b/i3status_rs/blocks/temperature/struct.Config.html new file mode 100644 index 0000000000..b4c171d157 --- /dev/null +++ b/i3status_rs/blocks/temperature/struct.Config.html @@ -0,0 +1,48 @@ +Config in i3status_rs::blocks::temperature - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub format_alt: Option<FormatConfig>,
+    pub interval: Seconds,
+    pub scale: TemperatureScale,
+    pub good: Option<f64>,
+    pub idle: Option<f64>,
+    pub info: Option<f64>,
+    pub warning: Option<f64>,
+    pub chip: Option<String>,
+    pub inputs: Option<Vec<String>>,
+}

Fields§

§format: FormatConfig§format_alt: Option<FormatConfig>§interval: Seconds§scale: TemperatureScale§good: Option<f64>§idle: Option<f64>§info: Option<f64>§warning: Option<f64>§chip: Option<String>§inputs: Option<Vec<String>>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), format_alt: Default::default(), interval: 5.into(), scale: Default::default(), good: Default::default(), idle: Default::default(), info: Default::default(), warning: Default::default(), chip: Default::default(), inputs: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/time/enum.Timezone.html b/i3status_rs/blocks/time/enum.Timezone.html new file mode 100644 index 0000000000..3728901d16 --- /dev/null +++ b/i3status_rs/blocks/time/enum.Timezone.html @@ -0,0 +1,39 @@ +Timezone in i3status_rs::blocks::time - Rust

Enum Timezone

Source
pub enum Timezone {
+    Timezone(Tz),
+    Timezones(Vec<Tz>),
+}

Variants§

§

Timezone(Tz)

§

Timezones(Vec<Tz>)

Trait Implementations§

Source§

impl Clone for Timezone

Source§

fn clone(&self) -> Timezone

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Timezone

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Timezone

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/time/fn.run.html b/i3status_rs/blocks/time/fn.run.html new file mode 100644 index 0000000000..cb8f00e9b2 --- /dev/null +++ b/i3status_rs/blocks/time/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::time - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/time/index.html b/i3status_rs/blocks/time/index.html new file mode 100644 index 0000000000..72f66dad33 --- /dev/null +++ b/i3status_rs/blocks/time/index.html @@ -0,0 +1,32 @@ +i3status_rs::blocks::time - Rust

Module time

Source
Expand description

The current time.

+

§Configuration

+ + + +
KeyValuesDefault
formatFormat string. See chrono docs for all options." $icon $timestamp.datetime() "
intervalUpdate interval in seconds10
timezoneA timezone specifier (e.g. “Europe/Lisbon”)Local timezone
+
+ + +
PlaceholderValueTypeUnit
iconA static iconIcon-
timestampThe current timeDatetime-
+
+ + +
ActionDefault button
next_timezoneLeft
prev_timezoneRight
+

§Example

[[block]]
+block = "time"
+interval = 60
+[block.format]
+full = " $icon $timestamp.datetime(f:'%a %Y-%m-%d %R %Z', l:fr_BE) "
+short = " $icon $timestamp.datetime(f:%R) "

§Non Gregorian calendars

+

You can use calendars other than the Gregorian calendar by adding the calendar specifier in the locale string. When using +this feature you can’t use chrono style format string, and you should use one of the options provided by +the icu4x crate: short, medium, long, full.

+

** Only available using feature icu_calendar. **

+

§Example

[[block]]
+block = "time"
+interval = 60
+format = "$timestamp.datetime(locale:'fa_IR-u-ca-persian', f:'full')"

§Icons Used

+
    +
  • time
  • +
+

Structs§

Config

Enums§

Timezone

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/time/sidebar-items.js b/i3status_rs/blocks/time/sidebar-items.js new file mode 100644 index 0000000000..edbb115e22 --- /dev/null +++ b/i3status_rs/blocks/time/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Timezone"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/time/struct.Config.html b/i3status_rs/blocks/time/struct.Config.html new file mode 100644 index 0000000000..586e236dc5 --- /dev/null +++ b/i3status_rs/blocks/time/struct.Config.html @@ -0,0 +1,41 @@ +Config in i3status_rs::blocks::time - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub interval: Seconds,
+    pub timezone: Option<Timezone>,
+}

Fields§

§format: FormatConfig§interval: Seconds§timezone: Option<Timezone>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), interval: 10.into(), timezone: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/toggle/fn.run.html b/i3status_rs/blocks/toggle/fn.run.html new file mode 100644 index 0000000000..25cbb41554 --- /dev/null +++ b/i3status_rs/blocks/toggle/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::toggle - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/toggle/index.html b/i3status_rs/blocks/toggle/index.html new file mode 100644 index 0000000000..ce12b67c9e --- /dev/null +++ b/i3status_rs/blocks/toggle/index.html @@ -0,0 +1,42 @@ +i3status_rs::blocks::toggle - Rust

Module toggle

Source
Expand description

A Toggle block

+

You can add commands to be executed to disable the toggle (command_off), and to enable it +(command_on). If these command exit with a non-zero status, the block will not be toggled and +the block state will be changed to critical to give a visual warning of the failure. You also need to +specify a command to determine the state of the toggle (command_state). When the command outputs +nothing, the toggle is disabled, otherwise enabled. By specifying the interval property you can +let the command_state be executed continuously.

+

To run those commands, the shell form $SHELL environment variable is used. If such variable +is not presented, sh is used.

+

§Configuration

+ + + + + + + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders" $icon "
command_onShell command to enable the toggleRequired
command_offShell command to disable the toggleRequired
command_stateShell command to determine the state. Empty output => No, otherwise => Yes.Required
icon_onIcon override for the toggle button while on"toggle_on"
icon_offIcon override for the toggle button while off"toggle_off"
intervalUpdate interval in seconds. If not set, command_state will run only on click.None
state_onState (color) of this block while onidle
state_offState (color) of this block while offidle
+
+ +
PlaceholderValueTypeUnit
iconIcon based on toggle’s stateIcon-
+
+ +
ActionDefault button
toggleLeft
+

§Examples

+

This is what can be used to toggle an external monitor configuration:

+
[[block]]
+block = "toggle"
+format = " $icon 4k "
+command_state = "xrandr | grep 'DP1 connected 38' | grep -v eDP1"
+command_on = "~/.screenlayout/4kmon_default.sh"
+command_off = "~/.screenlayout/builtin.sh"
+interval = 5
+state_on = "good"
+state_off = "warning"

§Icons Used

+
    +
  • toggle_off
  • +
  • toggle_on
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/toggle/sidebar-items.js b/i3status_rs/blocks/toggle/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/toggle/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/toggle/struct.Config.html b/i3status_rs/blocks/toggle/struct.Config.html new file mode 100644 index 0000000000..4ba8864e3e --- /dev/null +++ b/i3status_rs/blocks/toggle/struct.Config.html @@ -0,0 +1,44 @@ +Config in i3status_rs::blocks::toggle - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub command_on: String,
+    pub command_off: String,
+    pub command_state: String,
+    pub icon_on: Option<String>,
+    pub icon_off: Option<String>,
+    pub interval: Option<u64>,
+    pub state_on: Option<State>,
+    pub state_off: Option<State>,
+}

Fields§

§format: FormatConfig§command_on: String§command_off: String§command_state: String§icon_on: Option<String>§icon_off: Option<String>§interval: Option<u64>§state_on: Option<State>§state_off: Option<State>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Config

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/type.BlockAction.html b/i3status_rs/blocks/type.BlockAction.html new file mode 100644 index 0000000000..53108edd9d --- /dev/null +++ b/i3status_rs/blocks/type.BlockAction.html @@ -0,0 +1,6 @@ +BlockAction in i3status_rs::blocks - Rust

Type Alias BlockAction

Source
pub type BlockAction = Cow<'static, str>;

Aliased Type§

pub enum BlockAction {
+    Borrowed(&'static str),
+    Owned(String),
+}

Variants§

§1.0.0

Borrowed(&'static str)

Borrowed data.

+
§1.0.0

Owned(String)

Owned data.

+
\ No newline at end of file diff --git a/i3status_rs/blocks/uptime/fn.run.html b/i3status_rs/blocks/uptime/fn.run.html new file mode 100644 index 0000000000..a598007c3a --- /dev/null +++ b/i3status_rs/blocks/uptime/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::uptime - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/uptime/index.html b/i3status_rs/blocks/uptime/index.html new file mode 100644 index 0000000000..85fb4f67f9 --- /dev/null +++ b/i3status_rs/blocks/uptime/index.html @@ -0,0 +1,21 @@ +i3status_rs::blocks::uptime - Rust

Module uptime

Source
Expand description

System’s uptime

+

This block displays system uptime in terms of two biggest units, so minutes and seconds, or +hours and minutes or days and hours or weeks and days.

+

§Configuration

+ + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders" $icon $uptime "
intervalUpdate interval in seconds60
+
+ + + +
PlaceholderValueTypeUnit
iconA static iconIcon-
text DEPRECATEDCurrent uptimeText-
uptimeCurrent uptimeDuration-
+
+

text has been deprecated in favor of uptime.

+

§Example

[[block]]
+block = "uptime"
+interval = 3600 # update every hour

§Used Icons

+
    +
  • uptime
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/uptime/sidebar-items.js b/i3status_rs/blocks/uptime/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/uptime/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/uptime/struct.Config.html b/i3status_rs/blocks/uptime/struct.Config.html new file mode 100644 index 0000000000..6f8030b8aa --- /dev/null +++ b/i3status_rs/blocks/uptime/struct.Config.html @@ -0,0 +1,40 @@ +Config in i3status_rs::blocks::uptime - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub interval: Seconds,
+}

Fields§

§format: FormatConfig§interval: Seconds

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), interval: 60.into() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/vpn/enum.DriverType.html b/i3status_rs/blocks/vpn/enum.DriverType.html new file mode 100644 index 0000000000..4912f5bda7 --- /dev/null +++ b/i3status_rs/blocks/vpn/enum.DriverType.html @@ -0,0 +1,39 @@ +DriverType in i3status_rs::blocks::vpn - Rust

Enum DriverType

Source
pub enum DriverType {
+    Nordvpn,
+    Mullvad,
+}

Variants§

§

Nordvpn

§

Mullvad

Trait Implementations§

Source§

impl Debug for DriverType

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for DriverType

Source§

fn default() -> Self

Return DriverType::Nordvpn

+
Source§

impl<'de> Deserialize<'de> for DriverType

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/vpn/fn.run.html b/i3status_rs/blocks/vpn/fn.run.html new file mode 100644 index 0000000000..40c6a6af42 --- /dev/null +++ b/i3status_rs/blocks/vpn/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::vpn - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/vpn/index.html b/i3status_rs/blocks/vpn/index.html new file mode 100644 index 0000000000..50ef8120dc --- /dev/null +++ b/i3status_rs/blocks/vpn/index.html @@ -0,0 +1,48 @@ +i3status_rs::blocks::vpn - Rust

Module vpn

Source
Expand description

Shows the current connection status for VPN networks

+

This widget toggles the connection on left click.

+

§Configuration

+ + + + + + +
KeyValuesDefault
driverWhich vpn should be used . Available drivers are: "nordvpn" and "mullvad""nordvpn"
intervalUpdate interval in seconds.10
format_connectedA string to customise the output in case the network is connected. See below for available placeholders." VPN: $icon "
format_disconnectedA string to customise the output in case the network is disconnected. See below for available placeholders." VPN: $icon "
state_connectedThe widgets state if the vpn network is connected.info
state_disconnectedThe widgets state if the vpn network is disconnectedidle
+
+ + + +
PlaceholderValueTypeUnit
iconA static iconIcon-
countryCountry currently connected toText-
flagCountry specific flag (depends on a font supporting them)Text-
+
+ +
ActionDefault buttonDescription
toggleLefttoggles the vpn network connection
+

§Drivers

§nordvpn

+

Behind the scenes the nordvpn driver uses the nordvpn command line binary. In order for this to work +properly the binary should be executable without root privileges.

+

§Mullvad

+

Behind the scenes the mullvad driver uses the mullvad command line binary. In order for this to work properly the binary should be executable and mullvad daemon should be running.

+

§Example

+

Shows the current vpn network state:

+
[[block]]
+block = "vpn"
+driver = "nordvpn"
+interval = 10
+format_connected = "VPN: $icon "
+format_disconnected = "VPN: $icon "
+state_connected = "good"
+state_disconnected = "warning"
+

Possible values for state_connected and state_disconnected:

+
warning
+critical
+good
+info
+idle

§Icons Used

+
    +
  • net_vpn
  • +
  • net_wired
  • +
  • net_down
  • +
  • country code flags (if supported by font)
  • +
+

Flags: They are not icons but unicode glyphs. You will need a font that +includes them. Tested with: https://www.babelstone.co.uk/Fonts/Flags.html

+

Structs§

Config

Enums§

DriverType

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/vpn/sidebar-items.js b/i3status_rs/blocks/vpn/sidebar-items.js new file mode 100644 index 0000000000..7cba7410b2 --- /dev/null +++ b/i3status_rs/blocks/vpn/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["DriverType"],"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/vpn/struct.Config.html b/i3status_rs/blocks/vpn/struct.Config.html new file mode 100644 index 0000000000..a613f805dc --- /dev/null +++ b/i3status_rs/blocks/vpn/struct.Config.html @@ -0,0 +1,44 @@ +Config in i3status_rs::blocks::vpn - Rust

Struct Config

Source
pub struct Config {
+    pub driver: DriverType,
+    pub interval: Seconds,
+    pub format_connected: FormatConfig,
+    pub format_disconnected: FormatConfig,
+    pub state_connected: State,
+    pub state_disconnected: State,
+}

Fields§

§driver: DriverType§interval: Seconds§format_connected: FormatConfig§format_disconnected: FormatConfig§state_connected: State§state_disconnected: State

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { driver: Default::default(), interval: 10.into(), format_connected: Default::default(), format_disconnected: Default::default(), state_connected: Default::default(), state_disconnected: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/watson/fn.deserialize_local_timestamp.html b/i3status_rs/blocks/watson/fn.deserialize_local_timestamp.html new file mode 100644 index 0000000000..9a09b4a7fa --- /dev/null +++ b/i3status_rs/blocks/watson/fn.deserialize_local_timestamp.html @@ -0,0 +1,4 @@ +deserialize_local_timestamp in i3status_rs::blocks::watson - Rust

Function deserialize_local_timestamp

Source
pub fn deserialize_local_timestamp<'de, D>(
+    deserializer: D,
+) -> Result<DateTime<Local>, D::Error>
where + D: Deserializer<'de>,
\ No newline at end of file diff --git a/i3status_rs/blocks/watson/fn.run.html b/i3status_rs/blocks/watson/fn.run.html new file mode 100644 index 0000000000..0727ce1e69 --- /dev/null +++ b/i3status_rs/blocks/watson/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::watson - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/watson/index.html b/i3status_rs/blocks/watson/index.html new file mode 100644 index 0000000000..64220f2d7e --- /dev/null +++ b/i3status_rs/blocks/watson/index.html @@ -0,0 +1,22 @@ +i3status_rs::blocks::watson - Rust

Module watson

Source
Expand description

Watson statistics

+

Watson is a simple CLI time tracking application. This block will show the name of your current active project, tags and optionally recorded time. Clicking the widget will toggle the show_time variable dynamically.

+

§Configuration

+ + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders`“ $text
show_timeWhether to show recorded time.false
state_pathPath to the Watson state file. Supports path expansions e.g. ~.$XDG_CONFIG_HOME/watson/state
intervalUpdate interval, in seconds.60
+
+ +
PlaceholderValueTypeUnit
textCurrent activityText-
+
+ +
ActionDescriptionDefault button
toggle_show_timeToggle the value of show_timeLeft
+

§Example

[[block]]
+block = "watson"
+show_time = true
+state_path = "~/.config/watson/state"

§TODO

+
    +
  • Extend functionality: start / stop watson using this block
  • +
+

Structs§

Config

Functions§

deserialize_local_timestamp
run
\ No newline at end of file diff --git a/i3status_rs/blocks/watson/sidebar-items.js b/i3status_rs/blocks/watson/sidebar-items.js new file mode 100644 index 0000000000..ca60e45262 --- /dev/null +++ b/i3status_rs/blocks/watson/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["deserialize_local_timestamp","run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/watson/struct.Config.html b/i3status_rs/blocks/watson/struct.Config.html new file mode 100644 index 0000000000..7ca2ecf790 --- /dev/null +++ b/i3status_rs/blocks/watson/struct.Config.html @@ -0,0 +1,42 @@ +Config in i3status_rs::blocks::watson - Rust

Struct Config

Source
pub struct Config {
+    pub format: FormatConfig,
+    pub state_path: Option<ShellString>,
+    pub interval: Seconds,
+    pub show_time: bool,
+}

Fields§

§format: FormatConfig§state_path: Option<ShellString>§interval: Seconds§show_time: bool

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { format: Default::default(), state_path: Default::default(), interval: 60.into(), show_time: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/weather/enum.WeatherService.html b/i3status_rs/blocks/weather/enum.WeatherService.html new file mode 100644 index 0000000000..7bf4515e90 --- /dev/null +++ b/i3status_rs/blocks/weather/enum.WeatherService.html @@ -0,0 +1,38 @@ +WeatherService in i3status_rs::blocks::weather - Rust

Enum WeatherService

Source
pub enum WeatherService {
+    OpenWeatherMap(Config),
+    MetNo(Config),
+    Nws(Config),
+}

Variants§

§

OpenWeatherMap(Config)

§

MetNo(Config)

§

Nws(Config)

Trait Implementations§

Source§

impl Debug for WeatherService

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for WeatherService

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/weather/fn.run.html b/i3status_rs/blocks/weather/fn.run.html new file mode 100644 index 0000000000..caa14d0b62 --- /dev/null +++ b/i3status_rs/blocks/weather/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::weather - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/weather/index.html b/i3status_rs/blocks/weather/index.html new file mode 100644 index 0000000000..86429be076 --- /dev/null +++ b/i3status_rs/blocks/weather/index.html @@ -0,0 +1,111 @@ +i3status_rs::blocks::weather - Rust

Module weather

Source
Expand description

Current weather

+

This block displays local weather and temperature information. In order to use this block, you +will need access to a supported weather API service. At the time of writing, OpenWeatherMap, +met.no, and the US National Weather Service are supported.

+

Configuring this block requires configuring a weather service, which may require API keys and +other parameters.

+

If using the autolocate feature, set the autolocate update interval such that you do not exceed ipapi.co’s free daily limit of 1000 hits. Or use autolocate_interval = "once" to only run on initialization.

+

§Configuration

+ + + + + + +
KeyValuesDefault
serviceThe configuration of a weather service (see below).Required
formatA string to customise the output of this block. See below for available placeholders. Text may need to be escaped, refer to Escaping Text." $icon $weather $temp "
format_altIf set, block will switch between format and format_alt on every clickNone
intervalUpdate interval, in seconds.600
autolocateGets your location using the ipapi.co IP location service (no API key required). If the API call fails then the block will fallback to service specific location config.false
autolocate_intervalUpdate interval for autolocate in seconds or “once”interval
+

§OpenWeatherMap Options

+

To use the service you will need a (free) API key.

+
+ + + + + + + + + +
KeyValuesRequiredDefault
nameopenweathermap.YesNone
api_keyYour OpenWeatherMap API key.YesNone
coordinatesGPS latitude longitude coordinates as a tuple, example: ["39.2362","9.3317"]Yes*None
city_idOpenWeatherMap’s ID for the city. (Deprecated)Yes*None
placeOpenWeatherMap ‘By {city name},{state code},{country code}’ search query. See here. Consumes an additional API callYes*None
zipOpenWeatherMap ‘By {zip code},{country code}’ search query. See here. Consumes an additional API callYes*None
unitsEither "metric" or "imperial".No"metric"
langLanguage code. See here. Currently only affects weather_verbose key.No"en"
forecast_hoursHow many hours should be forecast (must be increments of 3 hours, max 120 hours)No12
+
+

One of coordinates, city_id, place, or zip is required. If more than one are supplied, coordinates takes precedence over city_id which takes precedence over place which takes precedence over zip.

+

The options api_key, city_id, place, zip, can be omitted from configuration, +in which case they must be provided in the environment variables +OPENWEATHERMAP_API_KEY, OPENWEATHERMAP_CITY_ID, OPENWEATHERMAP_PLACE, OPENWEATHERMAP_ZIP.

+

Forecasts are only fetched if forecast_hours > 0 and the format has keys related to forecast.

+

§met.no Options

+ + + + + +
KeyValuesRequiredDefault
namemetno.YesNone
coordinatesGPS latitude longitude coordinates as a tuple, example: ["39.2362","9.3317"]Required if autolocate = falseNone
langLanguage code: en, nn or nbNoen
altitudeMeters above sea level of the groundNoApproximated by server
forecast_hoursHow many hours should be forecastNo12
+
+

Met.no does not support location name, but if autolocate is enabled then autolocate’s city value is used.

+

§US National Weather Service Options

+ + + + +
KeyValuesRequiredDefault
namenws.YesNone
coordinatesGPS latitude longitude coordinates as a tuple, example: ["39.2362","9.3317"]Required if autolocate = falseNone
forecast_hoursHow many hours should be forecastNo12
unitsEither "metric" or "imperial".No"metric"
+
+

Forecasts gather statistics from each hour between now and the forecast_hours value, and +provide predicted weather at the set number of hours into the future.

+

§Available Format Keys

+ + + + + + + + + + + + +
KeyValueTypeUnit
locationLocation name (exact format depends on the service)Text-
icon{,_ffin}Icon representing the weatherIcon-
weather{,_ffin}Textual brief description of the weather, e.g. “Raining”Text-
weather_verbose{,_ffin}Textual verbose description of the weather, e.g. “overcast clouds”Text-
temp{,_{favg,fmin,fmax,ffin}}TemperatureNumberdegrees
apparent{,_{favg,fmin,fmax,ffin}}Australian Apparent TemperatureNumberdegrees
humidity{,_{favg,fmin,fmax,ffin}}HumidityNumber%
wind{,_{favg,fmin,fmax,ffin}}Wind speedNumber-
wind_kmh{,_{favg,fmin,fmax,ffin}}Wind speed. The wind speed in km/hNumber-
direction{,_{favg,fmin,fmax,ffin}}Wind direction, e.g. “NE”Text-
sunriseTime of sunriseDateTime-
sunsetTime of sunsetDateTime-
+
+

You can use the suffixes noted above to get the following:

+
+ + + + + +
SuffixDescription
NoneCurrent weather
_favgAverage forecast value
_fminMinimum forecast value
_fmaxMaximum forecast value
_ffinFinal forecast value
+
+ +
ActionDescriptionDefault button
toggle_formatToggles between format and format_altLeft
+

§Examples

+

Show detailed weather in San Francisco through the OpenWeatherMap service:

+
[[block]]
+block = "weather"
+format = " $icon $weather ($location) $temp, $wind m/s $direction "
+format_alt = " $icon_ffin Forecast (9 hour avg) {$temp_favg ({$temp_fmin}-{$temp_fmax})|Unavailable} "
+[block.service]
+name = "openweathermap"
+api_key = "XXX"
+city_id = "5398563"
+units = "metric"
+forecast_hours = 9
+

Show sunrise and sunset times in null island

+
[[block]]
+block = "weather"
+format = "up $sunrise.datetime(f:'%R') down $sunset.datetime(f:'%R')"
+[block.service]
+name = "metno"
+coordinates = ["0", "0"]

§Used Icons

+
    +
  • weather_sun (when weather is reported as “Clear” during the day)
  • +
  • weather_moon (when weather is reported as “Clear” at night)
  • +
  • weather_clouds (when weather is reported as “Clouds” during the day)
  • +
  • weather_clouds_night (when weather is reported as “Clouds” at night)
  • +
  • weather_fog (when weather is reported as “Fog” or “Mist” during the day)
  • +
  • weather_fog_night (when weather is reported as “Fog” or “Mist” at night)
  • +
  • weather_rain (when weather is reported as “Rain” or “Drizzle” during the day)
  • +
  • weather_rain_night (when weather is reported as “Rain” or “Drizzle” at night)
  • +
  • weather_snow (when weather is reported as “Snow”)
  • +
  • weather_thunder (when weather is reported as “Thunderstorm” during the day)
  • +
  • weather_thunder_night (when weather is reported as “Thunderstorm” at night)
  • +
+

Modules§

met_no
nws
Support for using the US National Weather Service API.
open_weather_map

Structs§

Config

Enums§

WeatherService

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/weather/met_no/index.html b/i3status_rs/blocks/weather/met_no/index.html new file mode 100644 index 0000000000..935034c406 --- /dev/null +++ b/i3status_rs/blocks/weather/met_no/index.html @@ -0,0 +1 @@ +i3status_rs::blocks::weather::met_no - Rust

Module met_no

Source

Structs§

Config
\ No newline at end of file diff --git a/i3status_rs/blocks/weather/met_no/sidebar-items.js b/i3status_rs/blocks/weather/met_no/sidebar-items.js new file mode 100644 index 0000000000..4cadad7737 --- /dev/null +++ b/i3status_rs/blocks/weather/met_no/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/weather/met_no/struct.Config.html b/i3status_rs/blocks/weather/met_no/struct.Config.html new file mode 100644 index 0000000000..bab2147ab8 --- /dev/null +++ b/i3status_rs/blocks/weather/met_no/struct.Config.html @@ -0,0 +1,37 @@ +Config in i3status_rs::blocks::weather::met_no - Rust

Struct Config

Source
pub struct Config { /* private fields */ }

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { coordinates: Default::default(), altitude: Default::default(), lang: Default::default(), forecast_hours: 12 }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/weather/nws/index.html b/i3status_rs/blocks/weather/nws/index.html new file mode 100644 index 0000000000..7a8ae6a286 --- /dev/null +++ b/i3status_rs/blocks/weather/nws/index.html @@ -0,0 +1,10 @@ +i3status_rs::blocks::weather::nws - Rust

Module nws

Source
Expand description

Support for using the US National Weather Service API.

+

The API is documented here. +There is a corresponding OpenAPI document. The forecast +descriptions are translated into the set of supported icons as best as possible, and a more +complete summary forecast is available in the weather_verbose format key. The full NWS list +of icons and corresponding descriptions can be found here, +though these are slated for deprecation.

+

All data is gathered using the hourly weather forecast service, after resolving from latitude & +longitude coordinates to a specific forecast office and grid point.

+

Structs§

Config
\ No newline at end of file diff --git a/i3status_rs/blocks/weather/nws/sidebar-items.js b/i3status_rs/blocks/weather/nws/sidebar-items.js new file mode 100644 index 0000000000..4cadad7737 --- /dev/null +++ b/i3status_rs/blocks/weather/nws/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/weather/nws/struct.Config.html b/i3status_rs/blocks/weather/nws/struct.Config.html new file mode 100644 index 0000000000..bf09f3c4dc --- /dev/null +++ b/i3status_rs/blocks/weather/nws/struct.Config.html @@ -0,0 +1,37 @@ +Config in i3status_rs::blocks::weather::nws - Rust

Struct Config

Source
pub struct Config { /* private fields */ }

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { coordinates: Default::default(), forecast_hours: 12, units: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/weather/open_weather_map/fn.deserialize_forecast_hours.html b/i3status_rs/blocks/weather/open_weather_map/fn.deserialize_forecast_hours.html new file mode 100644 index 0000000000..559effc33f --- /dev/null +++ b/i3status_rs/blocks/weather/open_weather_map/fn.deserialize_forecast_hours.html @@ -0,0 +1,4 @@ +deserialize_forecast_hours in i3status_rs::blocks::weather::open_weather_map - Rust

Function deserialize_forecast_hours

Source
pub fn deserialize_forecast_hours<'de, D>(
+    deserializer: D,
+) -> Result<usize, D::Error>
where + D: Deserializer<'de>,
\ No newline at end of file diff --git a/i3status_rs/blocks/weather/open_weather_map/index.html b/i3status_rs/blocks/weather/open_weather_map/index.html new file mode 100644 index 0000000000..a451bc6ecf --- /dev/null +++ b/i3status_rs/blocks/weather/open_weather_map/index.html @@ -0,0 +1 @@ +i3status_rs::blocks::weather::open_weather_map - Rust

Module open_weather_map

Source

Structs§

Config

Functions§

deserialize_forecast_hours
\ No newline at end of file diff --git a/i3status_rs/blocks/weather/open_weather_map/sidebar-items.js b/i3status_rs/blocks/weather/open_weather_map/sidebar-items.js new file mode 100644 index 0000000000..a45924dbe8 --- /dev/null +++ b/i3status_rs/blocks/weather/open_weather_map/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["deserialize_forecast_hours"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/weather/open_weather_map/struct.Config.html b/i3status_rs/blocks/weather/open_weather_map/struct.Config.html new file mode 100644 index 0000000000..73214a1fdd --- /dev/null +++ b/i3status_rs/blocks/weather/open_weather_map/struct.Config.html @@ -0,0 +1,37 @@ +Config in i3status_rs::blocks::weather::open_weather_map - Rust

Struct Config

Source
pub struct Config { /* private fields */ }

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { api_key: Default::default(), city_id: Default::default(), place: Default::default(), zip: Default::default(), coordinates: Default::default(), units: Default::default(), lang: ("en").into(), forecast_hours: 12 }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/weather/sidebar-items.js b/i3status_rs/blocks/weather/sidebar-items.js new file mode 100644 index 0000000000..2d62e8c0a0 --- /dev/null +++ b/i3status_rs/blocks/weather/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["WeatherService"],"fn":["run"],"mod":["met_no","nws","open_weather_map"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/weather/struct.Config.html b/i3status_rs/blocks/weather/struct.Config.html new file mode 100644 index 0000000000..e0d8ea5041 --- /dev/null +++ b/i3status_rs/blocks/weather/struct.Config.html @@ -0,0 +1,41 @@ +Config in i3status_rs::blocks::weather - Rust

Struct Config

Source
pub struct Config {
+    pub interval: Seconds,
+    pub format: FormatConfig,
+    pub format_alt: Option<FormatConfig>,
+    pub service: WeatherService,
+    pub autolocate: bool,
+    pub autolocate_interval: Option<Seconds>,
+}

Fields§

§interval: Seconds§format: FormatConfig§format_alt: Option<FormatConfig>§service: WeatherService§autolocate: bool§autolocate_interval: Option<Seconds>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Config

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/blocks/xrandr/fn.run.html b/i3status_rs/blocks/xrandr/fn.run.html new file mode 100644 index 0000000000..896042cb08 --- /dev/null +++ b/i3status_rs/blocks/xrandr/fn.run.html @@ -0,0 +1 @@ +run in i3status_rs::blocks::xrandr - Rust

Function run

Source
pub async fn run(config: &Config, api: &CommonApi) -> Result<()>
\ No newline at end of file diff --git a/i3status_rs/blocks/xrandr/index.html b/i3status_rs/blocks/xrandr/index.html new file mode 100644 index 0000000000..cf6d3207fe --- /dev/null +++ b/i3status_rs/blocks/xrandr/index.html @@ -0,0 +1,31 @@ +i3status_rs::blocks::xrandr - Rust

Module xrandr

Source
Expand description

X11 screen information

+

X11 screen information (name, brightness, resolution). With a click you can toggle through your active screens and with wheel up and down you can adjust the selected screens brightness. Regarding brightness control, xrandr changes the brightness of the display using gamma rather than changing the brightness in hardware, so if that is not desirable then consider using the backlight block instead.

+

NOTE: Some users report issues (e.g. here and here when using this block. The cause is currently unknown, however setting a higher update interval may help.

+

§Configuration

+ + + + +
KeyValuesDefault
formatA string to customise the output of this block. See below for available placeholders." $icon $display $brightness_icon $brightness "
step_widthThe steps brightness is in/decreased for the selected screen (When greater than 50 it gets limited to 50).5
intervalUpdate interval in seconds.5
invert_iconsInvert icons’ ordering, useful if you have colorful emojifalse
+
+ + + + + + +
PlaceholderValueTypeUnit
iconA static iconIcon-
displayThe name of a monitorText-
brightnessThe brightness of a monitorNumber%
brightness_iconA static iconIcon-
resolutionThe resolution of a monitorText-
res_iconA static iconIcon-
+
+ + + +
ActionDefault button
cycle_outputsLeft
brightness_upWheel Up
brightness_downWheel Down
+

§Example

[[block]]
+block = "xrandr"
+format = " $icon $brightness $resolution "

§Used Icons

+
    +
  • xrandr
  • +
  • backlight
  • +
  • resolution
  • +
+

Structs§

Config

Functions§

run
\ No newline at end of file diff --git a/i3status_rs/blocks/xrandr/sidebar-items.js b/i3status_rs/blocks/xrandr/sidebar-items.js new file mode 100644 index 0000000000..1677ccfd20 --- /dev/null +++ b/i3status_rs/blocks/xrandr/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["run"],"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/blocks/xrandr/struct.Config.html b/i3status_rs/blocks/xrandr/struct.Config.html new file mode 100644 index 0000000000..75c83ee752 --- /dev/null +++ b/i3status_rs/blocks/xrandr/struct.Config.html @@ -0,0 +1,42 @@ +Config in i3status_rs::blocks::xrandr - Rust

Struct Config

Source
pub struct Config {
+    pub interval: Seconds,
+    pub format: FormatConfig,
+    pub step_width: u32,
+    pub invert_icons: bool,
+}

Fields§

§interval: Seconds§format: FormatConfig§step_width: u32§invert_icons: bool

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Return Config { interval: 5.into(), format: Default::default(), step_width: 5, invert_icons: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for Config
where + Config: Default,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/click/enum.MouseButton.html b/i3status_rs/click/enum.MouseButton.html new file mode 100644 index 0000000000..06574198bf --- /dev/null +++ b/i3status_rs/click/enum.MouseButton.html @@ -0,0 +1,58 @@ +MouseButton in i3status_rs::click - Rust

Enum MouseButton

Source
pub enum MouseButton {
+    Left,
+    Middle,
+    Right,
+    WheelUp,
+    WheelDown,
+    WheelLeft,
+    WheelRight,
+    Forward,
+    Back,
+    DoubleLeft,
+}
Expand description

Can be one of left, middle, right, up/wheel_up, down/wheel_down, wheel_left, wheel_right, forward, back or double_left.

+

Note that in order for double clicks to be registered, you have to set double_click_delay to a +non-zero value. 200 might be a good choice. Note that enabling this functionality will +make left clicks less responsive and feel a bit laggy.

+

Variants§

§

Left

§

Middle

§

Right

§

WheelUp

§

WheelDown

§

WheelLeft

§

WheelRight

§

Forward

§

Back

§

DoubleLeft

Trait Implementations§

Source§

impl Clone for MouseButton

Source§

fn clone(&self) -> MouseButton

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MouseButton

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for MouseButton

Source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Hash for MouseButton

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for MouseButton

Source§

fn eq(&self, other: &MouseButton) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for MouseButton

Source§

impl Eq for MouseButton

Source§

impl StructuralPartialEq for MouseButton

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/click/index.html b/i3status_rs/click/index.html new file mode 100644 index 0000000000..4899a487fb --- /dev/null +++ b/i3status_rs/click/index.html @@ -0,0 +1 @@ +i3status_rs::click - Rust

Module click

Source

Structs§

ClickConfigEntry
ClickHandler
PostActions

Enums§

MouseButton
Can be one of left, middle, right, up/wheel_up, down/wheel_down, wheel_left, wheel_right, forward, back or double_left.
\ No newline at end of file diff --git a/i3status_rs/click/sidebar-items.js b/i3status_rs/click/sidebar-items.js new file mode 100644 index 0000000000..6de8da75c4 --- /dev/null +++ b/i3status_rs/click/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["MouseButton"],"struct":["ClickConfigEntry","ClickHandler","PostActions"]}; \ No newline at end of file diff --git a/i3status_rs/click/struct.ClickConfigEntry.html b/i3status_rs/click/struct.ClickConfigEntry.html new file mode 100644 index 0000000000..bfbc579e02 --- /dev/null +++ b/i3status_rs/click/struct.ClickConfigEntry.html @@ -0,0 +1,36 @@ +ClickConfigEntry in i3status_rs::click - Rust

Struct ClickConfigEntry

Source
pub struct ClickConfigEntry { /* private fields */ }

Trait Implementations§

Source§

impl Clone for ClickConfigEntry

Source§

fn clone(&self) -> ClickConfigEntry

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClickConfigEntry

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for ClickConfigEntry

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/click/struct.ClickHandler.html b/i3status_rs/click/struct.ClickHandler.html new file mode 100644 index 0000000000..e0bbb4e74e --- /dev/null +++ b/i3status_rs/click/struct.ClickHandler.html @@ -0,0 +1,37 @@ +ClickHandler in i3status_rs::click - Rust

Struct ClickHandler

Source
pub struct ClickHandler(/* private fields */);

Implementations§

Trait Implementations§

Source§

impl Clone for ClickHandler

Source§

fn clone(&self) -> ClickHandler

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClickHandler

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ClickHandler

Source§

fn default() -> ClickHandler

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for ClickHandler

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/click/struct.PostActions.html b/i3status_rs/click/struct.PostActions.html new file mode 100644 index 0000000000..c6e27bae98 --- /dev/null +++ b/i3status_rs/click/struct.PostActions.html @@ -0,0 +1,37 @@ +PostActions in i3status_rs::click - Rust

Struct PostActions

Source
pub struct PostActions {
+    pub action: Option<String>,
+    pub update: bool,
+}

Fields§

§action: Option<String>§update: bool

Trait Implementations§

Source§

impl Clone for PostActions

Source§

fn clone(&self) -> PostActions

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PostActions

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/config/index.html b/i3status_rs/config/index.html new file mode 100644 index 0000000000..a610d7efe4 --- /dev/null +++ b/i3status_rs/config/index.html @@ -0,0 +1 @@ +i3status_rs::config - Rust
\ No newline at end of file diff --git a/i3status_rs/config/sidebar-items.js b/i3status_rs/config/sidebar-items.js new file mode 100644 index 0000000000..376334e104 --- /dev/null +++ b/i3status_rs/config/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["BlockConfigEntry","CommonBlockConfig","Config","SharedConfig"]}; \ No newline at end of file diff --git a/i3status_rs/config/struct.BlockConfigEntry.html b/i3status_rs/config/struct.BlockConfigEntry.html new file mode 100644 index 0000000000..a9e3a06ef7 --- /dev/null +++ b/i3status_rs/config/struct.BlockConfigEntry.html @@ -0,0 +1,37 @@ +BlockConfigEntry in i3status_rs::config - Rust

Struct BlockConfigEntry

Source
pub struct BlockConfigEntry {
+    pub common: CommonBlockConfig,
+    pub config: BlockConfig,
+}

Fields§

§common: CommonBlockConfig§config: BlockConfig

Trait Implementations§

Source§

impl Debug for BlockConfigEntry

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for BlockConfigEntry

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/config/struct.CommonBlockConfig.html b/i3status_rs/config/struct.CommonBlockConfig.html new file mode 100644 index 0000000000..05412e9fc2 --- /dev/null +++ b/i3status_rs/config/struct.CommonBlockConfig.html @@ -0,0 +1,48 @@ +CommonBlockConfig in i3status_rs::config - Rust

Struct CommonBlockConfig

Source
pub struct CommonBlockConfig {
+    pub click: ClickHandler,
+    pub signal: Option<i32>,
+    pub icons_format: Option<String>,
+    pub theme_overrides: Option<ThemeOverrides>,
+    pub icons_overrides: Option<HashMap<String, Icon>>,
+    pub merge_with_next: bool,
+    pub error_interval: u64,
+    pub error_format: Config,
+    pub error_fullscreen_format: Config,
+    pub if_command: Option<String>,
+}

Fields§

§click: ClickHandler§signal: Option<i32>§icons_format: Option<String>§theme_overrides: Option<ThemeOverrides>§icons_overrides: Option<HashMap<String, Icon>>§merge_with_next: bool§error_interval: u64§error_format: Config§error_fullscreen_format: Config§if_command: Option<String>

Trait Implementations§

Source§

impl Debug for CommonBlockConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for CommonBlockConfig

Source§

fn default() -> Self

Return CommonBlockConfig { click: Default::default(), signal: Default::default(), icons_format: Default::default(), theme_overrides: Default::default(), icons_overrides: Default::default(), merge_with_next: Default::default(), error_interval: 5, error_format: Default::default(), error_fullscreen_format: Default::default(), if_command: Default::default() }

+
Source§

impl<'de> Deserialize<'de> for CommonBlockConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/config/struct.Config.html b/i3status_rs/config/struct.Config.html new file mode 100644 index 0000000000..ad2aa07529 --- /dev/null +++ b/i3status_rs/config/struct.Config.html @@ -0,0 +1,44 @@ +Config in i3status_rs::config - Rust

Struct Config

Source
pub struct Config {
+    pub shared: SharedConfig,
+    pub invert_scrolling: bool,
+    pub geolocator: Arc<Geolocator>,
+    pub double_click_delay: u64,
+    pub error_format: Config,
+    pub error_fullscreen_format: Config,
+    pub blocks: Vec<BlockConfigEntry>,
+}

Fields§

§shared: SharedConfig§invert_scrolling: bool

Set to true to invert mouse wheel direction

+
§geolocator: Arc<Geolocator>§double_click_delay: u64

The maximum delay (ms) between two clicks that are considered as double click

+
§error_format: Config§error_fullscreen_format: Config§blocks: Vec<BlockConfigEntry>

Trait Implementations§

Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Config

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/config/struct.SharedConfig.html b/i3status_rs/config/struct.SharedConfig.html new file mode 100644 index 0000000000..4cd28ce83c --- /dev/null +++ b/i3status_rs/config/struct.SharedConfig.html @@ -0,0 +1,41 @@ +SharedConfig in i3status_rs::config - Rust

Struct SharedConfig

Source
pub struct SharedConfig {
+    pub theme: Arc<Theme>,
+    pub icons: Arc<Icons>,
+    pub icons_format: Arc<String>,
+}

Fields§

§theme: Arc<Theme>§icons: Arc<Icons>§icons_format: Arc<String>

Implementations§

Source§

impl SharedConfig

Source

pub fn get_icon(&self, icon: &str, value: Option<f64>) -> Result<String>

Trait Implementations§

Source§

impl Clone for SharedConfig

Source§

fn clone(&self) -> SharedConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SharedConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for SharedConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for SharedConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/errors/index.html b/i3status_rs/errors/index.html new file mode 100644 index 0000000000..42fa219de0 --- /dev/null +++ b/i3status_rs/errors/index.html @@ -0,0 +1,2 @@ +i3status_rs::errors - Rust

Module errors

Source

Structs§

BoxErrorWrapper
Error
Error type

Traits§

ErrorContext
StdError
Error is a trait representing the basic expectations for error values, +i.e., values of type E in Result<T, E>.
ToSerdeError

Type Aliases§

Result
Result type returned from functions that can have our Errors.
\ No newline at end of file diff --git a/i3status_rs/errors/sidebar-items.js b/i3status_rs/errors/sidebar-items.js new file mode 100644 index 0000000000..191d0e8012 --- /dev/null +++ b/i3status_rs/errors/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["BoxErrorWrapper","Error"],"trait":["ErrorContext","StdError","ToSerdeError"],"type":["Result"]}; \ No newline at end of file diff --git a/i3status_rs/errors/struct.BoxErrorWrapper.html b/i3status_rs/errors/struct.BoxErrorWrapper.html new file mode 100644 index 0000000000..db7c01b193 --- /dev/null +++ b/i3status_rs/errors/struct.BoxErrorWrapper.html @@ -0,0 +1,35 @@ +BoxErrorWrapper in i3status_rs::errors - Rust

Struct BoxErrorWrapper

Source
pub struct BoxErrorWrapper(pub Box<dyn StdError + Send + Sync + 'static>);

Tuple Fields§

§0: Box<dyn StdError + Send + Sync + 'static>

Trait Implementations§

Source§

impl Debug for BoxErrorWrapper

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for BoxErrorWrapper

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for BoxErrorWrapper

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToString for T
where + T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
§

impl<T> ToStringFallible for T
where + T: Display,

§

fn try_to_string(&self) -> Result<String, TryReserveError>

ToString::to_string, but without panic on OOM.

+
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/errors/struct.Error.html b/i3status_rs/errors/struct.Error.html new file mode 100644 index 0000000000..8bba892cf7 --- /dev/null +++ b/i3status_rs/errors/struct.Error.html @@ -0,0 +1,41 @@ +Error in i3status_rs::errors - Rust

Struct Error

Source
pub struct Error {
+    pub message: Option<Cow<'static, str>>,
+    pub cause: Option<Arc<dyn StdError + Send + Sync + 'static>>,
+}
Expand description

Error type

+

Fields§

§message: Option<Cow<'static, str>>§cause: Option<Arc<dyn StdError + Send + Sync + 'static>>

Implementations§

Source§

impl Error

Source

pub fn new<T: Into<Cow<'static, str>>>(message: T) -> Self

Trait Implementations§

Source§

impl Clone for Error

Source§

fn clone(&self) -> Error

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Error

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Error

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for Error

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Error> for Error

Source§

fn from(err: Error) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for FormatError

Source§

fn from(source: Error) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl Freeze for Error

§

impl !RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl !UnwindSafe for Error

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where + T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
§

impl<T> ToStringFallible for T
where + T: Display,

§

fn try_to_string(&self) -> Result<String, TryReserveError>

ToString::to_string, but without panic on OOM.

+
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/errors/trait.ErrorContext.html b/i3status_rs/errors/trait.ErrorContext.html new file mode 100644 index 0000000000..68b5fb4dfc --- /dev/null +++ b/i3status_rs/errors/trait.ErrorContext.html @@ -0,0 +1,14 @@ +ErrorContext in i3status_rs::errors - Rust

Trait ErrorContext

Source
pub trait ErrorContext<T> {
+    // Required methods
+    fn error<M: Into<Cow<'static, str>>>(self, message: M) -> Result<T>;
+    fn or_error<M: Into<Cow<'static, str>>, F: FnOnce() -> M>(
+        self,
+        f: F,
+    ) -> Result<T>;
+}

Required Methods§

Source

fn error<M: Into<Cow<'static, str>>>(self, message: M) -> Result<T>

Source

fn or_error<M: Into<Cow<'static, str>>, F: FnOnce() -> M>( + self, + f: F, +) -> Result<T>

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementations on Foreign Types§

Source§

impl<T> ErrorContext<T> for Option<T>

Source§

fn error<M: Into<Cow<'static, str>>>(self, message: M) -> Result<T>

Source§

fn or_error<M: Into<Cow<'static, str>>, F: FnOnce() -> M>( + self, + f: F, +) -> Result<T>

Implementors§

Source§

impl<T, E: StdError + Send + Sync + 'static> ErrorContext<T> for Result<T, E>

\ No newline at end of file diff --git a/i3status_rs/errors/trait.StdError.html b/i3status_rs/errors/trait.StdError.html new file mode 100644 index 0000000000..81f19b799c --- /dev/null +++ b/i3status_rs/errors/trait.StdError.html @@ -0,0 +1,362 @@ +StdError in i3status_rs::errors - Rust

Trait StdError

1.0.0 · Source
pub trait StdError: Debug + Display {
+    // Provided methods
+    fn source(&self) -> Option<&(dyn Error + 'static)> { ... }
+    fn description(&self) -> &str { ... }
+    fn cause(&self) -> Option<&dyn Error> { ... }
+    fn provide<'a>(&'a self, request: &mut Request<'a>) { ... }
+}
Expand description

Error is a trait representing the basic expectations for error values, +i.e., values of type E in Result<T, E>.

+

Errors must describe themselves through the Display and Debug +traits. Error messages are typically concise lowercase sentences without +trailing punctuation:

+ +
let err = "NaN".parse::<u32>().unwrap_err();
+assert_eq!(err.to_string(), "invalid digit found in string");
+

Errors may provide cause information. Error::source() is generally +used when errors cross “abstraction boundaries”. If one module must report +an error that is caused by an error from a lower-level module, it can allow +accessing that error via Error::source(). This makes it possible for the +high-level module to provide its own errors while also revealing some of the +implementation for debugging.

+

§Example

+

Implementing the Error trait only requires that Debug and Display are implemented too.

+ +
use std::error::Error;
+use std::fmt;
+use std::path::PathBuf;
+
+#[derive(Debug)]
+struct ReadConfigError {
+    path: PathBuf
+}
+
+impl fmt::Display for ReadConfigError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let path = self.path.display();
+        write!(f, "unable to read configuration at {path}")
+    }
+}
+
+impl Error for ReadConfigError {}
+

Provided Methods§

1.30.0 · Source

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any.

+
§Examples
+
use std::error::Error;
+use std::fmt;
+
+#[derive(Debug)]
+struct SuperError {
+    source: SuperErrorSideKick,
+}
+
+impl fmt::Display for SuperError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "SuperError is here!")
+    }
+}
+
+impl Error for SuperError {
+    fn source(&self) -> Option<&(dyn Error + 'static)> {
+        Some(&self.source)
+    }
+}
+
+#[derive(Debug)]
+struct SuperErrorSideKick;
+
+impl fmt::Display for SuperErrorSideKick {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "SuperErrorSideKick is here!")
+    }
+}
+
+impl Error for SuperErrorSideKick {}
+
+fn get_super_error() -> Result<(), SuperError> {
+    Err(SuperError { source: SuperErrorSideKick })
+}
+
+fn main() {
+    match get_super_error() {
+        Err(e) => {
+            println!("Error: {e}");
+            println!("Caused by: {}", e.source().unwrap());
+        }
+        _ => println!("No error"),
+    }
+}
+
1.0.0 · Source

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
+
if let Err(e) = "xc".parse::<u32>() {
+    // Print `e` itself, no need for description().
+    eprintln!("Error: {e}");
+}
+
1.0.0 · Source

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)

Provides type-based access to context intended for error reports.

+

Used in conjunction with Request::provide_value and Request::provide_ref to extract +references to member variables from dyn Error trait objects.

+
§Example
+
#![feature(error_generic_member_access)]
+use core::fmt;
+use core::error::{request_ref, Request};
+
+#[derive(Debug)]
+enum MyLittleTeaPot {
+    Empty,
+}
+
+#[derive(Debug)]
+struct MyBacktrace {
+    // ...
+}
+
+impl MyBacktrace {
+    fn new() -> MyBacktrace {
+        // ...
+    }
+}
+
+#[derive(Debug)]
+struct Error {
+    backtrace: MyBacktrace,
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "Example Error")
+    }
+}
+
+impl std::error::Error for Error {
+    fn provide<'a>(&'a self, request: &mut Request<'a>) {
+        request
+            .provide_ref::<MyBacktrace>(&self.backtrace);
+    }
+}
+
+fn main() {
+    let backtrace = MyBacktrace::new();
+    let error = Error { backtrace };
+    let dyn_error = &error as &dyn std::error::Error;
+    let backtrace_ref = request_ref::<MyBacktrace>(dyn_error).unwrap();
+
+    assert!(core::ptr::eq(&error.backtrace, backtrace_ref));
+    assert!(request_ref::<MyLittleTeaPot>(dyn_error).is_none());
+}
+

Implementations§

Source§

impl dyn Error

1.3.0 · Source

pub fn is<T>(&self) -> bool
where + T: Error + 'static,

Returns true if the inner type is the same as T.

+
1.3.0 · Source

pub fn downcast_ref<T>(&self) -> Option<&T>
where + T: Error + 'static,

Returns some reference to the inner value if it is of type T, or +None if it isn’t.

+
1.3.0 · Source

pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
where + T: Error + 'static,

Returns some mutable reference to the inner value if it is of type T, or +None if it isn’t.

+
Source§

impl dyn Error + Send

1.3.0 · Source

pub fn is<T>(&self) -> bool
where + T: Error + 'static,

Forwards to the method defined on the type dyn Error.

+
1.3.0 · Source

pub fn downcast_ref<T>(&self) -> Option<&T>
where + T: Error + 'static,

Forwards to the method defined on the type dyn Error.

+
1.3.0 · Source

pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
where + T: Error + 'static,

Forwards to the method defined on the type dyn Error.

+
Source§

impl dyn Error + Sync + Send

1.3.0 · Source

pub fn is<T>(&self) -> bool
where + T: Error + 'static,

Forwards to the method defined on the type dyn Error.

+
1.3.0 · Source

pub fn downcast_ref<T>(&self) -> Option<&T>
where + T: Error + 'static,

Forwards to the method defined on the type dyn Error.

+
1.3.0 · Source

pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
where + T: Error + 'static,

Forwards to the method defined on the type dyn Error.

+
Source§

impl dyn Error

Source

pub fn sources(&self) -> Source<'_>

🔬This is a nightly-only experimental API. (error_iter)

Returns an iterator starting with the current error and continuing with +recursively calling Error::source.

+

If you want to omit the current error and only use its sources, +use skip(1).

+
§Examples
+
#![feature(error_iter)]
+use std::error::Error;
+use std::fmt;
+
+#[derive(Debug)]
+struct A;
+
+#[derive(Debug)]
+struct B(Option<Box<dyn Error + 'static>>);
+
+impl fmt::Display for A {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "A")
+    }
+}
+
+impl fmt::Display for B {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "B")
+    }
+}
+
+impl Error for A {}
+
+impl Error for B {
+    fn source(&self) -> Option<&(dyn Error + 'static)> {
+        self.0.as_ref().map(|e| e.as_ref())
+    }
+}
+
+let b = B(Some(Box::new(A)));
+
+// let err : Box<Error> = b.into(); // or
+let err = &b as &dyn Error;
+
+let mut iter = err.sources();
+
+assert_eq!("B".to_string(), iter.next().unwrap().to_string());
+assert_eq!("A".to_string(), iter.next().unwrap().to_string());
+assert!(iter.next().is_none());
+assert!(iter.next().is_none());
+
Source§

impl dyn Error

1.3.0 · Source

pub fn downcast<T>(self: Box<dyn Error>) -> Result<Box<T>, Box<dyn Error>>
where + T: Error + 'static,

Attempts to downcast the box to a concrete type.

+
Source§

impl dyn Error + Send

1.3.0 · Source

pub fn downcast<T>( + self: Box<dyn Error + Send>, +) -> Result<Box<T>, Box<dyn Error + Send>>
where + T: Error + 'static,

Attempts to downcast the box to a concrete type.

+
Source§

impl dyn Error + Sync + Send

1.3.0 · Source

pub fn downcast<T>( + self: Box<dyn Error + Sync + Send>, +) -> Result<Box<T>, Box<dyn Error + Sync + Send>>
where + T: Error + 'static,

Attempts to downcast the box to a concrete type.

+

Trait Implementations§

1.6.0 · Source§

impl<'a> From<&str> for Box<dyn Error + 'a>

Source§

fn from(err: &str) -> Box<dyn Error + 'a>

Converts a str into a box of dyn Error.

+
§Examples
+
use std::error::Error;
+
+let a_str_error = "a str error";
+let a_boxed_error = Box::<dyn Error>::from(a_str_error);
+assert!(size_of::<Box<dyn Error>>() == size_of_val(&a_boxed_error))
+
1.0.0 · Source§

impl<'a> From<&str> for Box<dyn Error + Sync + Send + 'a>

Source§

fn from(err: &str) -> Box<dyn Error + Sync + Send + 'a>

Converts a str into a box of dyn Error + Send + Sync.

+
§Examples
+
use std::error::Error;
+
+let a_str_error = "a str error";
+let a_boxed_error = Box::<dyn Error + Send + Sync>::from(a_str_error);
+assert!(
+    size_of::<Box<dyn Error + Send + Sync>>() == size_of_val(&a_boxed_error))
+
1.22.0 · Source§

impl<'a, 'b> From<Cow<'b, str>> for Box<dyn Error + 'a>

Source§

fn from(err: Cow<'b, str>) -> Box<dyn Error + 'a>

Converts a Cow into a box of dyn Error.

+
§Examples
+
use std::error::Error;
+use std::borrow::Cow;
+
+let a_cow_str_error = Cow::from("a str error");
+let a_boxed_error = Box::<dyn Error>::from(a_cow_str_error);
+assert!(size_of::<Box<dyn Error>>() == size_of_val(&a_boxed_error))
+
1.22.0 · Source§

impl<'a, 'b> From<Cow<'b, str>> for Box<dyn Error + Sync + Send + 'a>

Source§

fn from(err: Cow<'b, str>) -> Box<dyn Error + Sync + Send + 'a>

Converts a Cow into a box of dyn Error + Send + Sync.

+
§Examples
+
use std::error::Error;
+use std::borrow::Cow;
+
+let a_cow_str_error = Cow::from("a str error");
+let a_boxed_error = Box::<dyn Error + Send + Sync>::from(a_cow_str_error);
+assert!(
+    size_of::<Box<dyn Error + Send + Sync>>() == size_of_val(&a_boxed_error))
+
1.0.0 · Source§

impl<'a, E> From<E> for Box<dyn Error + 'a>
where + E: Error + 'a,

Source§

fn from(err: E) -> Box<dyn Error + 'a>

Converts a type of Error into a box of dyn Error.

+
§Examples
+
use std::error::Error;
+use std::fmt;
+
+#[derive(Debug)]
+struct AnError;
+
+impl fmt::Display for AnError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "An error")
+    }
+}
+
+impl Error for AnError {}
+
+let an_error = AnError;
+assert!(0 == size_of_val(&an_error));
+let a_boxed_error = Box::<dyn Error>::from(an_error);
+assert!(size_of::<Box<dyn Error>>() == size_of_val(&a_boxed_error))
+
1.0.0 · Source§

impl<'a, E> From<E> for Box<dyn Error + Sync + Send + 'a>
where + E: Error + Send + Sync + 'a,

Source§

fn from(err: E) -> Box<dyn Error + Sync + Send + 'a>

Converts a type of Error + Send + Sync into a box of +dyn Error + Send + Sync.

+
§Examples
+
use std::error::Error;
+use std::fmt;
+
+#[derive(Debug)]
+struct AnError;
+
+impl fmt::Display for AnError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "An error")
+    }
+}
+
+impl Error for AnError {}
+
+unsafe impl Send for AnError {}
+
+unsafe impl Sync for AnError {}
+
+let an_error = AnError;
+assert!(0 == size_of_val(&an_error));
+let a_boxed_error = Box::<dyn Error + Send + Sync>::from(an_error);
+assert!(
+    size_of::<Box<dyn Error + Send + Sync>>() == size_of_val(&a_boxed_error))
+
1.6.0 · Source§

impl<'a> From<String> for Box<dyn Error + 'a>

Source§

fn from(str_err: String) -> Box<dyn Error + 'a>

Converts a String into a box of dyn Error.

+
§Examples
+
use std::error::Error;
+
+let a_string_error = "a string error".to_string();
+let a_boxed_error = Box::<dyn Error>::from(a_string_error);
+assert!(size_of::<Box<dyn Error>>() == size_of_val(&a_boxed_error))
+
1.0.0 · Source§

impl<'a> From<String> for Box<dyn Error + Sync + Send + 'a>

Source§

fn from(err: String) -> Box<dyn Error + Sync + Send + 'a>

Converts a String into a box of dyn Error + Send + Sync.

+
§Examples
+
use std::error::Error;
+
+let a_string_error = "a string error".to_string();
+let a_boxed_error = Box::<dyn Error + Send + Sync>::from(a_string_error);
+assert!(
+    size_of::<Box<dyn Error + Send + Sync>>() == size_of_val(&a_boxed_error))
+
§

impl Value for dyn Error

§

fn record(&self, key: &Field, visitor: &mut dyn Visit)

Visits this value with the given Visitor.
§

impl Value for dyn Error + Send

§

fn record(&self, key: &Field, visitor: &mut dyn Visit)

Visits this value with the given Visitor.
§

impl Value for dyn Error + Sync + Send

§

fn record(&self, key: &Field, visitor: &mut dyn Visit)

Visits this value with the given Visitor.
§

impl Value for dyn Error + Sync

§

fn record(&self, key: &Field, visitor: &mut dyn Visit)

Visits this value with the given Visitor.

Implementors§

1.65.0 · Source§

impl !Error for &str

Source§

impl Error for CalendarError

Source§

impl Error for FormatError

1.8.0 · Source§

impl Error for Infallible

1.17.0 · Source§

impl Error for FromBytesWithNulError

1.86.0 · Source§

impl Error for GetDisjointMutError

1.0.0 · Source§

impl Error for VarError

1.89.0 · Source§

impl Error for std::fs::TryLockError

1.15.0 · Source§

impl Error for RecvTimeoutError

1.0.0 · Source§

impl Error for std::sync::mpsc::TryRecvError

Source§

impl Error for RoundingError

Source§

impl Error for FromHexError

Source§

impl Error for url::parser::ParseError

Source§

impl Error for BernoulliError

Source§

impl Error for WeightedError

Source§

impl Error for !

Source§

impl Error for BlockError

Source§

impl Error for UnorderedKeyError

1.57.0 · Source§

impl Error for alloc::collections::TryReserveError

1.58.0 · Source§

impl Error for FromVecWithNulError

1.7.0 · Source§

impl Error for IntoStringError

1.0.0 · Source§

impl Error for NulError

1.0.0 · Source§

impl Error for FromUtf8Error

1.0.0 · Source§

impl Error for FromUtf16Error

1.28.0 · Source§

impl Error for LayoutError

Source§

impl Error for AllocError

1.34.0 · Source§

impl Error for TryFromSliceError

1.13.0 · Source§

impl Error for BorrowError

1.13.0 · Source§

impl Error for BorrowMutError

1.34.0 · Source§

impl Error for CharTryFromError

1.20.0 · Source§

impl Error for ParseCharError

1.9.0 · Source§

impl Error for DecodeUtf16Error

1.59.0 · Source§

impl Error for TryFromCharError

1.69.0 · Source§

impl Error for FromBytesUntilNulError

1.11.0 · Source§

impl Error for core::fmt::Error

1.4.0 · Source§

impl Error for core::net::parser::AddrParseError

1.0.0 · Source§

impl Error for ParseFloatError

1.0.0 · Source§

impl Error for ParseIntError

1.34.0 · Source§

impl Error for TryFromIntError

1.0.0 · Source§

impl Error for ParseBoolError

1.0.0 · Source§

impl Error for Utf8Error

1.66.0 · Source§

impl Error for TryFromFloatSecsError

Source§

impl Error for ExpandError

1.44.0 · Source§

impl Error for proc_macro::LexError

1.0.0 · Source§

impl Error for JoinPathsError

1.56.0 · Source§

impl Error for WriterPanicked

1.0.0 · Source§

impl Error for std::io::error::Error

Source§

impl Error for NormalizeError

1.7.0 · Source§

impl Error for StripPrefixError

Source§

impl Error for ExitStatusError

1.0.0 · Source§

impl Error for std::sync::mpsc::RecvError

1.26.0 · Source§

impl Error for AccessError

1.8.0 · Source§

impl Error for SystemTimeError

Source§

impl Error for chrono::format::ParseError

Source§

impl Error for ParseMonthError

Source§

impl Error for OutOfRange

Source§

impl Error for OutOfRangeError

Source§

impl Error for ParseWeekdayError

Source§

impl Error for getrandom::error::Error

Source§

impl Error for GlobError

Source§

impl Error for PatternError

Source§

impl Error for PrefixLenError

Source§

impl Error for ipnet::parser::AddrParseError

Source§

impl Error for log::ParseLevelError

Source§

impl Error for SetLoggerError

Source§

impl Error for FromStrError

Source§

impl Error for openssl::error::Error

Source§

impl Error for ErrorStack

Source§

impl Error for openssl::ssl::error::Error

Source§

impl Error for X509VerifyResult

Source§

impl Error for proc_macro2::LexError

Source§

impl Error for serde::de::value::Error

Source§

impl Error for serde_json::error::Error

Source§

impl Error for syn::error::Error

Source§

impl Error for uuid::error::Error

Source§

impl Error for ReadError

Source§

impl Error for rand_core::error::Error

Source§

impl Error for BoxErrorWrapper

Source§

impl Error for i3status_rs::errors::Error

§

impl Error for Aborted

§

impl Error for AcquireError

§

impl Error for AddrParseError

§

impl Error for AnyDelimiterCodecError

§

impl Error for AttrError

§

impl Error for BindError

§

impl Error for BuildError

§

impl Error for BuildError

§

impl Error for BuildError

§

impl Error for BuildError

§

impl Error for BuildError

§

impl Error for CacheError

§

impl Error for CalibrightError

§

impl Error for Canceled

§

impl Error for CapacityOverflowError

§

impl Error for CaseFoldError

§

impl Error for Code

§

impl Error for ConfigurationError

§

impl Error for ConnectError

§

impl Error for ConnectError

§

impl Error for ConnectionError

§

impl Error for DatetimeParseError

§

impl Error for DeError

§

impl Error for DeError

§

impl Error for DecodeError

§

impl Error for DecodeError

§

impl Error for DecodeSliceError

§

impl Error for DeserializeError

§

impl Error for DisplayParsingError

§

impl Error for Elapsed

§

impl Error for Elapsed

§

impl Error for EncodeSliceError

§

impl Error for EncodingError

§

impl Error for EnterError

§

impl Error for Errno

§

impl Error for Errno

§

impl Error for Errno

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for Error

§

impl Error for ErrorKind

§

impl Error for Errors

§

impl Error for EscapeError

§

impl Error for GenError

§

impl Error for GetTimezoneError

§

impl Error for GroupInfoError

§

impl Error for IdsExhausted

§

impl Error for IllFormedError

§

impl Error for InvalidBackoff

§

impl Error for InvalidBufferSize

§

impl Error for InvalidDnsNameError

§

impl Error for InvalidHeaderName

§

impl Error for InvalidHeaderValue

§

impl Error for InvalidLength

§

impl Error for InvalidMethod

§

impl Error for InvalidNameError

§

impl Error for InvalidOutputSize

§

impl Error for InvalidStatusCode

§

impl Error for InvalidUri

§

impl Error for InvalidUriParts

§

impl Error for JoinError

§

impl Error for LengthDelimitedCodecError

§

impl Error for LengthLimitError

§

impl Error for LibsensorsError

§

impl Error for LinesCodecError

§

impl Error for MailEntryError

§

impl Error for MailParseError

§

impl Error for MaildirError

§

impl Error for MatchError

§

impl Error for MatchError

§

impl Error for MatchesError

§

impl Error for MaxSizeReached

§

impl Error for NamespaceError

§

impl Error for None

§

impl Error for PAErr

§

impl Error for ParseAlphabetError

§

impl Error for ParseCharRefError

§

impl Error for ParseError

§

impl Error for ParseError

§

impl Error for ParseError

§

impl Error for ParseError

§

impl Error for ParseLevelError

§

impl Error for ParseLevelFilterError

§

impl Error for ParseValueError

§

impl Error for PatternIDError

§

impl Error for PatternIDError

§

impl Error for PatternSetInsertError

§

impl Error for PeekHeaderError

§

impl Error for PopError

§

impl Error for QuotedPrintableError

§

impl Error for RecvError

§

impl Error for RecvError

§

impl Error for RecvError

§

impl Error for RecvError

§

impl Error for RecvError

§

impl Error for RecvMessageError

§

impl Error for ReplyError

§

impl Error for ReplyOrIdError

§

impl Error for ReuniteError

§

impl Error for ReuniteError

§

impl Error for SeError

§

impl Error for SendError

§

impl Error for SerError

§

impl Error for SerializeError

§

impl Error for SetGlobalDefaultError

§

impl Error for SmallIndexError

§

impl Error for SpawnError

§

impl Error for SpecificationError

§

impl Error for StartError

§

impl Error for StateIDError

§

impl Error for StateIDError

§

impl Error for Status

§

impl Error for SyntaxError

§

impl Error for ToStrError

§

impl Error for TomlError

§

impl Error for TryAcquireError

§

impl Error for TryCurrentError

§

impl Error for TryLockError

§

impl Error for TryRecvError

§

impl Error for TryRecvError

§

impl Error for TryRecvError

§

impl Error for TryRecvError

§

impl Error for TryRecvError

§

impl Error for TryRecvError

§

impl Error for TryReserveError

§

impl Error for UnicodeWordBoundaryError

§

impl Error for UnicodeWordError

§

impl Error for WrappedError

Source§

impl<'a, K, V> Error for alloc::collections::btree::map::entry::OccupiedError<'a, K, V>
where + K: Debug + Ord, + V: Debug,

Source§

impl<'a, K, V> Error for std::collections::hash::map::OccupiedError<'a, K, V>
where + K: Debug, + V: Debug,

1.51.0 · Source§

impl<'a, T> Error for &'a T
where + T: Error + ?Sized,

1.8.0 · Source§

impl<E> Error for Box<E>
where + E: Error,

Source§

impl<E> Error for serde_path_to_error::Error<E>
where + E: Error,

§

impl<E> Error for Err<E>
where + E: Debug,

§

impl<E> Error for LookupError<E>
where + E: Error + 'static,

§

impl<F> Error for Error<F>
where + F: ErrorFormatter,

§

impl<I> Error for Error<I>
where + I: Debug + Display,

§

impl<I> Error for ExactlyOneError<I>
where + I: Iterator + Debug, + <I as Iterator>::Item: Debug,

§

impl<I> Error for InputError<I>
where + I: Clone + Debug + Display + Sync + Send + 'static,

§

impl<I> Error for VerboseError<I>
where + I: Debug + Display,

§

impl<I, C> Error for TreeError<I, C>
where + I: Stream + Clone + Debug + Display + Sync + Send + 'static, + C: Display + Debug,

Source§

impl<L, R> Error for Either<L, R>
where + L: Error, + R: Error,

Either implements Error if both L and R implement it.

+

Requires crate feature "use_std"

+
§

impl<RE> Error for HttpClientError<RE>
where + RE: Error + 'static, + Box<RE>: Error + 'static, + HttpClientError<RE>: Debug + Display,

§

impl<RE, T> Error for RequestTokenError<RE, T>
where + RE: Error + 'static, + T: ErrorResponse + 'static, + RequestTokenError<RE, T>: Debug + Display,

Source§

impl<S> Error for openssl::ssl::error::HandshakeError<S>
where + S: Debug,

§

impl<S> Error for HandshakeError<S>
where + S: Any + Debug,

Source§

impl<T> Error for std::sync::mpmc::error::SendTimeoutError<T>

1.0.0 · Source§

impl<T> Error for std::sync::mpsc::TrySendError<T>

1.0.0 · Source§

impl<T> Error for std::sync::poison::TryLockError<T>

Source§

impl<T> Error for ThinBox<T>
where + T: Error + ?Sized,

1.52.0 · Source§

impl<T> Error for Arc<T>
where + T: Error + ?Sized,

1.0.0 · Source§

impl<T> Error for std::sync::mpsc::SendError<T>

1.0.0 · Source§

impl<T> Error for PoisonError<T>

§

impl<T> Error for AsyncFdTryNewError<T>

§

impl<T> Error for CreationError<T>
where + T: Debug,

§

impl<T> Error for CreationError<T>
where + T: Debug,

§

impl<T> Error for ForcePushError<T>
where + T: Debug,

§

impl<T> Error for PollSendError<T>
where + T: Debug,

§

impl<T> Error for PushError<T>
where + T: Debug,

§

impl<T> Error for ReuniteError<T>
where + T: Any,

§

impl<T> Error for SendError<T>

§

impl<T> Error for SendError<T>

§

impl<T> Error for SendError<T>

§

impl<T> Error for SendError<T>

§

impl<T> Error for SendError<T>
where + T: Debug,

§

impl<T> Error for SendTimeoutError<T>

§

impl<T> Error for SetError<T>
where + T: Debug,

§

impl<T> Error for TrySendError<T>

§

impl<T> Error for TrySendError<T>

§

impl<T> Error for TrySendError<T>

§

impl<T> Error for TrySendError<T>
where + T: Any,

§

impl<T, E> Error for TryChunksError<T, E>
where + E: Debug + Display,

§

impl<T, E> Error for TryReadyChunksError<T, E>
where + E: Debug + Display,

§

impl<T, Item> Error for ReuniteError<T, Item>
where + T: Any,

§

impl<T, P> Error for NlError<T, P>
where + T: Debug, + P: Debug,

§

impl<T, P> Error for Nlmsgerr<T, P>
where + T: Debug, + P: Debug,

1.0.0 · Source§

impl<W> Error for IntoInnerError<W>
where + W: Send + Debug,

\ No newline at end of file diff --git a/i3status_rs/errors/trait.ToSerdeError.html b/i3status_rs/errors/trait.ToSerdeError.html new file mode 100644 index 0000000000..e13bc13d2a --- /dev/null +++ b/i3status_rs/errors/trait.ToSerdeError.html @@ -0,0 +1,5 @@ +ToSerdeError in i3status_rs::errors - Rust

Trait ToSerdeError

Source
pub trait ToSerdeError<T> {
+    // Required method
+    fn serde_error<E: Error>(self) -> Result<T, E>;
+}

Required Methods§

Source

fn serde_error<E: Error>(self) -> Result<T, E>

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§

Source§

impl<T, F> ToSerdeError<T> for Result<T, F>
where + F: Display,

\ No newline at end of file diff --git a/i3status_rs/errors/type.Result.html b/i3status_rs/errors/type.Result.html new file mode 100644 index 0000000000..d8ab2e1269 --- /dev/null +++ b/i3status_rs/errors/type.Result.html @@ -0,0 +1,11 @@ +Result in i3status_rs::errors - Rust

Type Alias Result

Source
pub type Result<T, E = Error> = Result<T, E>;
Expand description

Result type returned from functions that can have our Errors.

+

Aliased Type§

pub enum Result<T, E = Error> {
+    Ok(T),
+    Err(E),
+}

Variants§

§1.0.0

Ok(T)

Contains the success value

+
§1.0.0

Err(E)

Contains the error value

+

Trait Implementations§

Source§

impl<T, E: StdError + Send + Sync + 'static> ErrorContext<T> for Result<T, E>

Source§

fn error<M: Into<Cow<'static, str>>>(self, message: M) -> Result<T>

Source§

fn or_error<M: Into<Cow<'static, str>>, F: FnOnce() -> M>( + self, + f: F, +) -> Result<T>

Source§

impl<T, F> ToSerdeError<T> for Result<T, F>
where + F: Display,

Source§

fn serde_error<E: Error>(self) -> Result<T, E>

\ No newline at end of file diff --git a/i3status_rs/escape/index.html b/i3status_rs/escape/index.html new file mode 100644 index 0000000000..a67bf30363 --- /dev/null +++ b/i3status_rs/escape/index.html @@ -0,0 +1,2 @@ +i3status_rs::escape - Rust

Module escape

Source
Expand description

Simple json escaping

+

Traits§

CollectEscaped
Escaped
\ No newline at end of file diff --git a/i3status_rs/escape/sidebar-items.js b/i3status_rs/escape/sidebar-items.js new file mode 100644 index 0000000000..a729e6f116 --- /dev/null +++ b/i3status_rs/escape/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"trait":["CollectEscaped","Escaped"]}; \ No newline at end of file diff --git a/i3status_rs/escape/trait.CollectEscaped.html b/i3status_rs/escape/trait.CollectEscaped.html new file mode 100644 index 0000000000..de7a9b2123 --- /dev/null +++ b/i3status_rs/escape/trait.CollectEscaped.html @@ -0,0 +1,13 @@ +CollectEscaped in i3status_rs::escape - Rust

Trait CollectEscaped

Source
pub trait CollectEscaped {
+    // Required method
+    fn collect_pango_escaped_into<T: Write>(self, out: &mut T);
+
+    // Provided method
+    fn collect_pango_escaped<T: Write + Default>(self) -> T
+       where Self: Sized { ... }
+}

Required Methods§

Source

fn collect_pango_escaped_into<T: Write>(self, out: &mut T)

Write escaped version of self to out

+

Provided Methods§

Source

fn collect_pango_escaped<T: Write + Default>(self) -> T
where + Self: Sized,

Write escaped version of self to a new buffer

+

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§

Source§

impl<I, R> CollectEscaped for I
where + I: Iterator<Item = R>, + R: AsRef<str>,

\ No newline at end of file diff --git a/i3status_rs/escape/trait.Escaped.html b/i3status_rs/escape/trait.Escaped.html new file mode 100644 index 0000000000..2faf9b2ea4 --- /dev/null +++ b/i3status_rs/escape/trait.Escaped.html @@ -0,0 +1,11 @@ +Escaped in i3status_rs::escape - Rust

Trait Escaped

Source
pub trait Escaped {
+    // Required method
+    fn pango_escaped_into<T: Write>(self, out: &mut T);
+
+    // Provided method
+    fn pango_escaped<T: Write + Default>(self) -> T
+       where Self: Sized { ... }
+}

Required Methods§

Source

fn pango_escaped_into<T: Write>(self, out: &mut T)

Write escaped version of self to out

+

Provided Methods§

Source

fn pango_escaped<T: Write + Default>(self) -> T
where + Self: Sized,

Write escaped version of self to a new buffer

+

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§

Source§

impl<R: AsRef<str>> Escaped for R

\ No newline at end of file diff --git a/i3status_rs/formatting/config/index.html b/i3status_rs/formatting/config/index.html new file mode 100644 index 0000000000..8e7fb87a29 --- /dev/null +++ b/i3status_rs/formatting/config/index.html @@ -0,0 +1 @@ +i3status_rs::formatting::config - Rust

Module config

Source

Structs§

Config
\ No newline at end of file diff --git a/i3status_rs/formatting/config/sidebar-items.js b/i3status_rs/formatting/config/sidebar-items.js new file mode 100644 index 0000000000..4cadad7737 --- /dev/null +++ b/i3status_rs/formatting/config/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["Config"]}; \ No newline at end of file diff --git a/i3status_rs/formatting/config/struct.Config.html b/i3status_rs/formatting/config/struct.Config.html new file mode 100644 index 0000000000..fbb2272d43 --- /dev/null +++ b/i3status_rs/formatting/config/struct.Config.html @@ -0,0 +1,44 @@ +Config in i3status_rs::formatting::config - Rust

Struct Config

Source
pub struct Config {
+    pub full: Option<FormatTemplate>,
+    pub short: Option<FormatTemplate>,
+}

Fields§

§full: Option<FormatTemplate>§short: Option<FormatTemplate>

Implementations§

Source§

impl Config

Source

pub fn with_default(&self, default_full: &str) -> Result<Format>

Source

pub fn with_defaults( + &self, + default_full: &str, + default_short: &str, +) -> Result<Format>

Source

pub fn with_default_config(&self, default_config: &Self) -> Format

Source

pub fn with_default_format(&self, default_format: &Format) -> Format

Trait Implementations§

Source§

impl Clone for Config

Source§

fn clone(&self) -> Config

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Config

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for Config

Source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl FromStr for Config

Source§

type Err = Error

The associated error which can be returned from parsing.
Source§

fn from_str(s: &str) -> Result<Self, Self::Err>

Parses a string s to return a value of this type. Read more

Auto Trait Implementations§

§

impl Freeze for Config

§

impl !RefUnwindSafe for Config

§

impl Send for Config

§

impl Sync for Config

§

impl Unpin for Config

§

impl !UnwindSafe for Config

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/enum.FormatError.html b/i3status_rs/formatting/enum.FormatError.html new file mode 100644 index 0000000000..175354b83f --- /dev/null +++ b/i3status_rs/formatting/enum.FormatError.html @@ -0,0 +1,43 @@ +FormatError in i3status_rs::formatting - Rust

Enum FormatError

Source
pub enum FormatError {
+    PlaceholderNotFound(String),
+    IncompatibleFormatter {
+        ty: &'static str,
+        fmt: &'static str,
+    },
+    NumberOutOfRange(f64),
+    Other(Error),
+}

Variants§

§

PlaceholderNotFound(String)

§

IncompatibleFormatter

Fields

§ty: &'static str
§fmt: &'static str
§

NumberOutOfRange(f64)

§

Other(Error)

Trait Implementations§

Source§

impl Debug for FormatError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for FormatError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for FormatError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Error> for FormatError

Source§

fn from(source: Error) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToString for T
where + T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
§

impl<T> ToStringFallible for T
where + T: Display,

§

fn try_to_string(&self) -> Result<String, TryReserveError>

ToString::to_string, but without panic on OOM.

+
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/bar/struct.BarFormatter.html b/i3status_rs/formatting/formatter/bar/struct.BarFormatter.html new file mode 100644 index 0000000000..995a255656 --- /dev/null +++ b/i3status_rs/formatting/formatter/bar/struct.BarFormatter.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/struct.BarFormatter.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/constant.DEFAULT_DURATION_FORMATTER.html b/i3status_rs/formatting/formatter/constant.DEFAULT_DURATION_FORMATTER.html new file mode 100644 index 0000000000..3c6c45521e --- /dev/null +++ b/i3status_rs/formatting/formatter/constant.DEFAULT_DURATION_FORMATTER.html @@ -0,0 +1 @@ +DEFAULT_DURATION_FORMATTER in i3status_rs::formatting::formatter - Rust

Constant DEFAULT_DURATION_FORMATTER

Source
pub const DEFAULT_DURATION_FORMATTER: DurationFormatter;
\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/constant.DEFAULT_FLAG_FORMATTER.html b/i3status_rs/formatting/formatter/constant.DEFAULT_FLAG_FORMATTER.html new file mode 100644 index 0000000000..bfc1f484a4 --- /dev/null +++ b/i3status_rs/formatting/formatter/constant.DEFAULT_FLAG_FORMATTER.html @@ -0,0 +1 @@ +DEFAULT_FLAG_FORMATTER in i3status_rs::formatting::formatter - Rust

Constant DEFAULT_FLAG_FORMATTER

Source
pub const DEFAULT_FLAG_FORMATTER: FlagFormatter;
\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/constant.DEFAULT_NUMBER_FORMATTER.html b/i3status_rs/formatting/formatter/constant.DEFAULT_NUMBER_FORMATTER.html new file mode 100644 index 0000000000..7c46a92027 --- /dev/null +++ b/i3status_rs/formatting/formatter/constant.DEFAULT_NUMBER_FORMATTER.html @@ -0,0 +1 @@ +DEFAULT_NUMBER_FORMATTER in i3status_rs::formatting::formatter - Rust

Constant DEFAULT_NUMBER_FORMATTER

Source
pub const DEFAULT_NUMBER_FORMATTER: EngFormatter;
\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/constant.DEFAULT_STRING_FORMATTER.html b/i3status_rs/formatting/formatter/constant.DEFAULT_STRING_FORMATTER.html new file mode 100644 index 0000000000..1d582750ce --- /dev/null +++ b/i3status_rs/formatting/formatter/constant.DEFAULT_STRING_FORMATTER.html @@ -0,0 +1 @@ +DEFAULT_STRING_FORMATTER in i3status_rs::formatting::formatter - Rust

Constant DEFAULT_STRING_FORMATTER

Source
pub const DEFAULT_STRING_FORMATTER: StrFormatter;
\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/datetime/enum.DatetimeFormatter.html b/i3status_rs/formatting/formatter/datetime/enum.DatetimeFormatter.html new file mode 100644 index 0000000000..005a1b420c --- /dev/null +++ b/i3status_rs/formatting/formatter/datetime/enum.DatetimeFormatter.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/enum.DatetimeFormatter.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/datetime/static.DEFAULT_DATETIME_FORMATTER.html b/i3status_rs/formatting/formatter/datetime/static.DEFAULT_DATETIME_FORMATTER.html new file mode 100644 index 0000000000..6b266012df --- /dev/null +++ b/i3status_rs/formatting/formatter/datetime/static.DEFAULT_DATETIME_FORMATTER.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/static.DEFAULT_DATETIME_FORMATTER.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/duration/constant.DEFAULT_DURATION_FORMATTER.html b/i3status_rs/formatting/formatter/duration/constant.DEFAULT_DURATION_FORMATTER.html new file mode 100644 index 0000000000..b43e0b1a72 --- /dev/null +++ b/i3status_rs/formatting/formatter/duration/constant.DEFAULT_DURATION_FORMATTER.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/constant.DEFAULT_DURATION_FORMATTER.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/duration/struct.DurationFormatter.html b/i3status_rs/formatting/formatter/duration/struct.DurationFormatter.html new file mode 100644 index 0000000000..c02a298253 --- /dev/null +++ b/i3status_rs/formatting/formatter/duration/struct.DurationFormatter.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/struct.DurationFormatter.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/eng/constant.DEFAULT_NUMBER_FORMATTER.html b/i3status_rs/formatting/formatter/eng/constant.DEFAULT_NUMBER_FORMATTER.html new file mode 100644 index 0000000000..d43e06efb1 --- /dev/null +++ b/i3status_rs/formatting/formatter/eng/constant.DEFAULT_NUMBER_FORMATTER.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/constant.DEFAULT_NUMBER_FORMATTER.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/eng/struct.EngFormatter.html b/i3status_rs/formatting/formatter/eng/struct.EngFormatter.html new file mode 100644 index 0000000000..003e63290d --- /dev/null +++ b/i3status_rs/formatting/formatter/eng/struct.EngFormatter.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/struct.EngFormatter.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/enum.DatetimeFormatter.html b/i3status_rs/formatting/formatter/enum.DatetimeFormatter.html new file mode 100644 index 0000000000..186a8f1b87 --- /dev/null +++ b/i3status_rs/formatting/formatter/enum.DatetimeFormatter.html @@ -0,0 +1,45 @@ +DatetimeFormatter in i3status_rs::formatting::formatter - Rust

Enum DatetimeFormatter

Source
pub enum DatetimeFormatter {
+    Chrono {
+        items: Vec<Item<'static>>,
+        locale: Option<Locale>,
+    },
+    Icu {
+        length: Date,
+        locale: Locale,
+    },
+}

Variants§

§

Chrono

Fields

§items: Vec<Item<'static>>
§locale: Option<Locale>
§

Icu

Fields

§length: Date
§locale: Locale

Trait Implementations§

Source§

impl Debug for DatetimeFormatter

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Formatter for DatetimeFormatter

Source§

fn format( + &self, + val: &Value, + _config: &SharedConfig, +) -> Result<String, FormatError>

Source§

fn interval(&self) -> Option<Duration>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/flag/constant.DEFAULT_FLAG_FORMATTER.html b/i3status_rs/formatting/formatter/flag/constant.DEFAULT_FLAG_FORMATTER.html new file mode 100644 index 0000000000..099af918ca --- /dev/null +++ b/i3status_rs/formatting/formatter/flag/constant.DEFAULT_FLAG_FORMATTER.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/constant.DEFAULT_FLAG_FORMATTER.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/flag/struct.FlagFormatter.html b/i3status_rs/formatting/formatter/flag/struct.FlagFormatter.html new file mode 100644 index 0000000000..f18d715915 --- /dev/null +++ b/i3status_rs/formatting/formatter/flag/struct.FlagFormatter.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/struct.FlagFormatter.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/fn.new_formatter.html b/i3status_rs/formatting/formatter/fn.new_formatter.html new file mode 100644 index 0000000000..9f3dd831dc --- /dev/null +++ b/i3status_rs/formatting/formatter/fn.new_formatter.html @@ -0,0 +1 @@ +new_formatter in i3status_rs::formatting::formatter - Rust

Function new_formatter

Source
pub fn new_formatter(name: &str, args: &[Arg<'_>]) -> Result<Box<dyn Formatter>>
\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/index.html b/i3status_rs/formatting/formatter/index.html new file mode 100644 index 0000000000..74a04af849 --- /dev/null +++ b/i3status_rs/formatting/formatter/index.html @@ -0,0 +1 @@ +i3status_rs::formatting::formatter - Rust
\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/pango/struct.PangoStrFormatter.html b/i3status_rs/formatting/formatter/pango/struct.PangoStrFormatter.html new file mode 100644 index 0000000000..fdea7cbab3 --- /dev/null +++ b/i3status_rs/formatting/formatter/pango/struct.PangoStrFormatter.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/struct.PangoStrFormatter.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/sidebar-items.js b/i3status_rs/formatting/formatter/sidebar-items.js new file mode 100644 index 0000000000..33885bcc80 --- /dev/null +++ b/i3status_rs/formatting/formatter/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["DEFAULT_DURATION_FORMATTER","DEFAULT_FLAG_FORMATTER","DEFAULT_NUMBER_FORMATTER","DEFAULT_STRING_FORMATTER"],"enum":["DatetimeFormatter"],"fn":["new_formatter"],"static":["DEFAULT_DATETIME_FORMATTER"],"struct":["BarFormatter","DurationFormatter","EngFormatter","FlagFormatter","PangoStrFormatter","StrFormatter","TallyFormatter"],"trait":["Formatter"]}; \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/static.DEFAULT_DATETIME_FORMATTER.html b/i3status_rs/formatting/formatter/static.DEFAULT_DATETIME_FORMATTER.html new file mode 100644 index 0000000000..dc1c7d0941 --- /dev/null +++ b/i3status_rs/formatting/formatter/static.DEFAULT_DATETIME_FORMATTER.html @@ -0,0 +1 @@ +DEFAULT_DATETIME_FORMATTER in i3status_rs::formatting::formatter - Rust

Static DEFAULT_DATETIME_FORMATTER

Source
pub static DEFAULT_DATETIME_FORMATTER: LazyLock<DatetimeFormatter>
\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/str/constant.DEFAULT_STRING_FORMATTER.html b/i3status_rs/formatting/formatter/str/constant.DEFAULT_STRING_FORMATTER.html new file mode 100644 index 0000000000..3be97a9e08 --- /dev/null +++ b/i3status_rs/formatting/formatter/str/constant.DEFAULT_STRING_FORMATTER.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/constant.DEFAULT_STRING_FORMATTER.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/str/struct.StrFormatter.html b/i3status_rs/formatting/formatter/str/struct.StrFormatter.html new file mode 100644 index 0000000000..c9f63a0656 --- /dev/null +++ b/i3status_rs/formatting/formatter/str/struct.StrFormatter.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/struct.StrFormatter.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/struct.BarFormatter.html b/i3status_rs/formatting/formatter/struct.BarFormatter.html new file mode 100644 index 0000000000..2cb9bd8590 --- /dev/null +++ b/i3status_rs/formatting/formatter/struct.BarFormatter.html @@ -0,0 +1,36 @@ +BarFormatter in i3status_rs::formatting::formatter - Rust

Struct BarFormatter

Source
pub struct BarFormatter { /* private fields */ }

Trait Implementations§

Source§

impl Debug for BarFormatter

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Formatter for BarFormatter

Source§

fn format( + &self, + val: &Value, + _config: &SharedConfig, +) -> Result<String, FormatError>

Source§

fn interval(&self) -> Option<Duration>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/struct.DurationFormatter.html b/i3status_rs/formatting/formatter/struct.DurationFormatter.html new file mode 100644 index 0000000000..3d56e69ef4 --- /dev/null +++ b/i3status_rs/formatting/formatter/struct.DurationFormatter.html @@ -0,0 +1,37 @@ +DurationFormatter in i3status_rs::formatting::formatter - Rust

Struct DurationFormatter

Source
pub struct DurationFormatter { /* private fields */ }

Trait Implementations§

Source§

impl Debug for DurationFormatter

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for DurationFormatter

Source§

fn default() -> DurationFormatter

Returns the “default value” for a type. Read more
Source§

impl Formatter for DurationFormatter

Source§

fn format( + &self, + val: &Value, + _config: &SharedConfig, +) -> Result<String, FormatError>

Source§

fn interval(&self) -> Option<Duration>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/struct.EngFormatter.html b/i3status_rs/formatting/formatter/struct.EngFormatter.html new file mode 100644 index 0000000000..e773b55d14 --- /dev/null +++ b/i3status_rs/formatting/formatter/struct.EngFormatter.html @@ -0,0 +1,36 @@ +EngFormatter in i3status_rs::formatting::formatter - Rust

Struct EngFormatter

Source
pub struct EngFormatter { /* private fields */ }

Trait Implementations§

Source§

impl Debug for EngFormatter

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Formatter for EngFormatter

Source§

fn format( + &self, + val: &Value, + _config: &SharedConfig, +) -> Result<String, FormatError>

Source§

fn interval(&self) -> Option<Duration>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/struct.FlagFormatter.html b/i3status_rs/formatting/formatter/struct.FlagFormatter.html new file mode 100644 index 0000000000..d6e45e48ff --- /dev/null +++ b/i3status_rs/formatting/formatter/struct.FlagFormatter.html @@ -0,0 +1,36 @@ +FlagFormatter in i3status_rs::formatting::formatter - Rust

Struct FlagFormatter

Source
pub struct FlagFormatter;

Trait Implementations§

Source§

impl Debug for FlagFormatter

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Formatter for FlagFormatter

Source§

fn format( + &self, + val: &Value, + _config: &SharedConfig, +) -> Result<String, FormatError>

Source§

fn interval(&self) -> Option<Duration>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/struct.PangoStrFormatter.html b/i3status_rs/formatting/formatter/struct.PangoStrFormatter.html new file mode 100644 index 0000000000..255f4d031d --- /dev/null +++ b/i3status_rs/formatting/formatter/struct.PangoStrFormatter.html @@ -0,0 +1,36 @@ +PangoStrFormatter in i3status_rs::formatting::formatter - Rust

Struct PangoStrFormatter

Source
pub struct PangoStrFormatter;

Trait Implementations§

Source§

impl Debug for PangoStrFormatter

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Formatter for PangoStrFormatter

Source§

fn format( + &self, + val: &Value, + config: &SharedConfig, +) -> Result<String, FormatError>

Source§

fn interval(&self) -> Option<Duration>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/struct.StrFormatter.html b/i3status_rs/formatting/formatter/struct.StrFormatter.html new file mode 100644 index 0000000000..82ffed2e50 --- /dev/null +++ b/i3status_rs/formatting/formatter/struct.StrFormatter.html @@ -0,0 +1,36 @@ +StrFormatter in i3status_rs::formatting::formatter - Rust

Struct StrFormatter

Source
pub struct StrFormatter { /* private fields */ }

Trait Implementations§

Source§

impl Debug for StrFormatter

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Formatter for StrFormatter

Source§

fn format( + &self, + val: &Value, + config: &SharedConfig, +) -> Result<String, FormatError>

Source§

fn interval(&self) -> Option<Duration>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/struct.TallyFormatter.html b/i3status_rs/formatting/formatter/struct.TallyFormatter.html new file mode 100644 index 0000000000..52c4793224 --- /dev/null +++ b/i3status_rs/formatting/formatter/struct.TallyFormatter.html @@ -0,0 +1,36 @@ +TallyFormatter in i3status_rs::formatting::formatter - Rust

Struct TallyFormatter

Source
pub struct TallyFormatter { /* private fields */ }

Trait Implementations§

Source§

impl Debug for TallyFormatter

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Formatter for TallyFormatter

Source§

fn format( + &self, + val: &Value, + _config: &SharedConfig, +) -> Result<String, FormatError>

Source§

fn interval(&self) -> Option<Duration>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/formatter/tally/struct.TallyFormatter.html b/i3status_rs/formatting/formatter/tally/struct.TallyFormatter.html new file mode 100644 index 0000000000..a0c11ef05b --- /dev/null +++ b/i3status_rs/formatting/formatter/tally/struct.TallyFormatter.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../i3status_rs/formatting/formatter/struct.TallyFormatter.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/formatting/formatter/trait.Formatter.html b/i3status_rs/formatting/formatter/trait.Formatter.html new file mode 100644 index 0000000000..144625ebdd --- /dev/null +++ b/i3status_rs/formatting/formatter/trait.Formatter.html @@ -0,0 +1,18 @@ +Formatter in i3status_rs::formatting::formatter - Rust

Trait Formatter

Source
pub trait Formatter:
+    Debug
+    + Send
+    + Sync {
+    // Required method
+    fn format(
+        &self,
+        val: &Value,
+        config: &SharedConfig,
+    ) -> Result<String, FormatError>;
+
+    // Provided method
+    fn interval(&self) -> Option<Duration> { ... }
+}

Required Methods§

Source

fn format( + &self, + val: &Value, + config: &SharedConfig, +) -> Result<String, FormatError>

Provided Methods§

Implementors§

\ No newline at end of file diff --git a/i3status_rs/formatting/index.html b/i3status_rs/formatting/index.html new file mode 100644 index 0000000000..e7351f67cf --- /dev/null +++ b/i3status_rs/formatting/index.html @@ -0,0 +1,94 @@ +i3status_rs::formatting - Rust

Module formatting

Source
Expand description

§Formatting system

+

Many blocks have a format configuration option, which allows to heavily customize the block’s +appearance. In short, each block with format option provides a set of values, which are +displayed according to format. format’s value is just a text with embedded variables. +Similarly to PHP and shell, variable name must start with a $: +this is a variable: -> $var <-.

+

Also, format strings can embed icons. For example, ^icon_ping in " ^icon_ping $ping " gets +substituted with a “ping” icon from your icon set. For a complete list of icons, see +this.

+

§Types

+

The allowed types of variables are:

+
+ + + + + +
TypeDefault formatter
Textstr
Numbereng
Datetimedatetime
Durationduration
FlagN/A
+

§Formatters

+

A formatter is something that converts a value into a text. Because there are many ways to do +this, a number of formatters is available. Formatter can be specified using the syntax similar +to method calls in many programming languages: <variable>.<formatter>(<args>). For example: +$title.str(min_w:10, max_w:20).

+

Note: for arguments that accept a boolean value, just specifying the argument will be treated as arg:true.

+

§str - Format text

+ + + + + +
ArgumentDescriptionDefault value
min_width or min_wif text is shorter it will be padded using spaces0
max_width or max_wif text is longer it will be truncatedInfinity
width or wText will be exactly this length by padding or truncating as neededN/A
rot_intervalif text is longer than max_width it will be rotated every rot_interval seconds, if setNone
rot_separatorif text is longer than max_width it will be rotated with this seporator"|"
+
+

Note: width just changes the values of both min_width and max_width to be the same. Use width +if you want the values to be the same, or the other two otherwise. Don’t mix width with +min_width or max_width.

+

§eng - Format numbers using engineering notation

+ + + + + + + + + + + +
ArgumentDescriptionDefault value
width or wthe resulting text will be at least width characters long2
unit or usome values have a unit, and it is possible to convert them by setting this optionN/A
hide_unithide the unit symbolfalse
unit_spacehave a whitespace before unit symbolfalse
prefix or pspecify this argument if you want to set the minimal SI prefixN/A
hide_prefixhide the prefix symbolfalse
prefix_spacehave a whitespace before prefix symbolfalse
force_prefixforce the prefix value instead of setting a “minimal prefix”false
pad_withthe character that is used to pad the number to be width long (a space)
rangea range of allowed values, in the format <start>..<end>, inclusive. Both start and end are optional. Can be used to, for example, hide the block when the value is not in a given range...
showshow this value. Can be used with range for conditional formattingtrue
+

§bar - Display numbers as progress bars

+ + + +
ArgumentDescriptionDefault value
width or wthe width of the bar (in characters)5 (1 for vertical)
max_valuewhich value is treated as “full”. For example, for battery level 100 is full.100
vertical or vwhether to render the bar vertically or notfalse
+

§tally - Display numbers as tally marks

+ +
ArgumentDescriptionDefault value
style or sOne of chinese_counting_rods/ccr, chinese_tally/ct, western_tally/wt, western_tally_ungrouped/wtuwestern_tally
+

§pango-str - Just display the text without pango markup escaping

+

No arguments.

+

§datetime - Display datetime

+ + +
ArgumentDescriptionDefault value
format or fchrono docs for all options.'%a %d/%m %R'
locale or lLocale to apply when formatting the timeSystem locale
+

§duration/dur - Format durations

+ + + + + + + + +
ArgumentDescriptionDefault value
hmsShould the format be hours:minutes:seconds.millisecondsfalse
max_unitThe largest unit to display the duration with (see below for the list of all possible units)hms ? h : y
min_unitThe smallest unit to display the duration with (see below for the list of all possible units)s
unitsThe number of units to displaymin(# of units between max_unit and `min_unit``, 2)
round_upRound up to the nearest minimum displayed unittrue
unit_spaceShould there be a space between the value and unit symbol (not allowed when hms:true)false
pad_withThe character that is used to pad the numbershms ? 0 : (a space)
leading_zeroesIf fewer than units are non-zero should leading numbers that have a value of zero be showntrue
+
+ + + + + + + +
UnitDescription
yyears
wweeks
ddays
hhours
mminutes
sseconds
msmilliseconds
+

§Handling missing placeholders and incorrect types

+

Some blocks allow missing placeholders, for example bluetooth’s +“percentage” may be absent if the device is not supported. To handle such cases it is possible +to queue multiple formats together by using | symbol: <something that can fail>|<otherwise try this>|<or this>.

+

In addition, formats can be recursive. To set a format inside of another format, place it +inside of {}. For example, in Percentage: {$percentage|N/A} the text “Percentage: “ will be +always displayed, followed by the actual percentage or “N/A” in case percentage is not +available. This example does exactly the same thing as Percentage: $percentage|Percentage: N/A

+

§How to use flags

+

Some blocks provide flags, which can be used to change the format based on some criteria. For +example, taskwarrior defines done if the count is zero. In +general, flags are used in this way:

+
$a{a is set}|$b$c{b and c are set}|${b|c}{b or c is set}|neither flag is set

Modules§

config
formatter
parse
prefix
scheduling
template
unit
value

Structs§

Format
Fragment
Metadata

Enums§

FormatError

Type Aliases§

Values
\ No newline at end of file diff --git a/i3status_rs/formatting/parse/enum.Token.html b/i3status_rs/formatting/parse/enum.Token.html new file mode 100644 index 0000000000..2bccbfcf9e --- /dev/null +++ b/i3status_rs/formatting/parse/enum.Token.html @@ -0,0 +1,42 @@ +Token in i3status_rs::formatting::parse - Rust

Enum Token

Source
pub enum Token<'a> {
+    Text(String),
+    Placeholder(Placeholder<'a>),
+    Icon(&'a str),
+    Recursive(FormatTemplate<'a>),
+}

Variants§

§

Text(String)

§

Placeholder(Placeholder<'a>)

§

Icon(&'a str)

§

Recursive(FormatTemplate<'a>)

Trait Implementations§

Source§

impl<'a> Debug for Token<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'a> PartialEq for Token<'a>

Source§

fn eq(&self, other: &Token<'a>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl TryFrom<Token<'_>> for Token

Source§

type Error = Error

The type returned in the event of a conversion error.
Source§

fn try_from(value: Token<'_>) -> Result<Self, Self::Error>

Performs the conversion.
Source§

impl<'a> Eq for Token<'a>

Source§

impl<'a> StructuralPartialEq for Token<'a>

Auto Trait Implementations§

§

impl<'a> Freeze for Token<'a>

§

impl<'a> RefUnwindSafe for Token<'a>

§

impl<'a> Send for Token<'a>

§

impl<'a> Sync for Token<'a>

§

impl<'a> Unpin for Token<'a>

§

impl<'a> UnwindSafe for Token<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/parse/fn.parse_full.html b/i3status_rs/formatting/parse/fn.parse_full.html new file mode 100644 index 0000000000..171f7ec457 --- /dev/null +++ b/i3status_rs/formatting/parse/fn.parse_full.html @@ -0,0 +1 @@ +parse_full in i3status_rs::formatting::parse - Rust

Function parse_full

Source
pub fn parse_full(i: &str) -> Result<FormatTemplate<'_>>
\ No newline at end of file diff --git a/i3status_rs/formatting/parse/index.html b/i3status_rs/formatting/parse/index.html new file mode 100644 index 0000000000..4caedec6bd --- /dev/null +++ b/i3status_rs/formatting/parse/index.html @@ -0,0 +1 @@ +i3status_rs::formatting::parse - Rust
\ No newline at end of file diff --git a/i3status_rs/formatting/parse/sidebar-items.js b/i3status_rs/formatting/parse/sidebar-items.js new file mode 100644 index 0000000000..0cd90765fe --- /dev/null +++ b/i3status_rs/formatting/parse/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Token"],"fn":["parse_full"],"struct":["Arg","FormatTemplate","Formatter","Placeholder","TokenList"]}; \ No newline at end of file diff --git a/i3status_rs/formatting/parse/struct.Arg.html b/i3status_rs/formatting/parse/struct.Arg.html new file mode 100644 index 0000000000..4e53116cb9 --- /dev/null +++ b/i3status_rs/formatting/parse/struct.Arg.html @@ -0,0 +1,42 @@ +Arg in i3status_rs::formatting::parse - Rust

Struct Arg

Source
pub struct Arg<'a> {
+    pub key: &'a str,
+    pub val: Option<&'a str>,
+}

Fields§

§key: &'a str§val: Option<&'a str>

Implementations§

Source§

impl Arg<'_>

Source

pub fn parse_value<T>(&self) -> Result<T>
where + T: FromStr + 'static, + T::Err: StdError + Send + Sync + 'static,

Trait Implementations§

Source§

impl<'a> Debug for Arg<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'a> PartialEq for Arg<'a>

Source§

fn eq(&self, other: &Arg<'a>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl<'a> Eq for Arg<'a>

Source§

impl<'a> StructuralPartialEq for Arg<'a>

Auto Trait Implementations§

§

impl<'a> Freeze for Arg<'a>

§

impl<'a> RefUnwindSafe for Arg<'a>

§

impl<'a> Send for Arg<'a>

§

impl<'a> Sync for Arg<'a>

§

impl<'a> Unpin for Arg<'a>

§

impl<'a> UnwindSafe for Arg<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/parse/struct.FormatTemplate.html b/i3status_rs/formatting/parse/struct.FormatTemplate.html new file mode 100644 index 0000000000..7344018a89 --- /dev/null +++ b/i3status_rs/formatting/parse/struct.FormatTemplate.html @@ -0,0 +1,37 @@ +FormatTemplate in i3status_rs::formatting::parse - Rust

Struct FormatTemplate

Source
pub struct FormatTemplate<'a>(pub Vec<TokenList<'a>>);

Tuple Fields§

§0: Vec<TokenList<'a>>

Trait Implementations§

Source§

impl<'a> Debug for FormatTemplate<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'a> PartialEq for FormatTemplate<'a>

Source§

fn eq(&self, other: &FormatTemplate<'a>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl TryFrom<FormatTemplate<'_>> for FormatTemplate

Source§

type Error = Error

The type returned in the event of a conversion error.
Source§

fn try_from(value: FormatTemplate<'_>) -> Result<Self, Self::Error>

Performs the conversion.
Source§

impl<'a> Eq for FormatTemplate<'a>

Source§

impl<'a> StructuralPartialEq for FormatTemplate<'a>

Auto Trait Implementations§

§

impl<'a> Freeze for FormatTemplate<'a>

§

impl<'a> RefUnwindSafe for FormatTemplate<'a>

§

impl<'a> Send for FormatTemplate<'a>

§

impl<'a> Sync for FormatTemplate<'a>

§

impl<'a> Unpin for FormatTemplate<'a>

§

impl<'a> UnwindSafe for FormatTemplate<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/parse/struct.Formatter.html b/i3status_rs/formatting/parse/struct.Formatter.html new file mode 100644 index 0000000000..b78c076bfc --- /dev/null +++ b/i3status_rs/formatting/parse/struct.Formatter.html @@ -0,0 +1,40 @@ +Formatter in i3status_rs::formatting::parse - Rust

Struct Formatter

Source
pub struct Formatter<'a> {
+    pub name: &'a str,
+    pub args: Vec<Arg<'a>>,
+}

Fields§

§name: &'a str§args: Vec<Arg<'a>>

Trait Implementations§

Source§

impl<'a> Debug for Formatter<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'a> PartialEq for Formatter<'a>

Source§

fn eq(&self, other: &Formatter<'a>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl<'a> Eq for Formatter<'a>

Source§

impl<'a> StructuralPartialEq for Formatter<'a>

Auto Trait Implementations§

§

impl<'a> Freeze for Formatter<'a>

§

impl<'a> RefUnwindSafe for Formatter<'a>

§

impl<'a> Send for Formatter<'a>

§

impl<'a> Sync for Formatter<'a>

§

impl<'a> Unpin for Formatter<'a>

§

impl<'a> UnwindSafe for Formatter<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/parse/struct.Placeholder.html b/i3status_rs/formatting/parse/struct.Placeholder.html new file mode 100644 index 0000000000..f6f42c87d8 --- /dev/null +++ b/i3status_rs/formatting/parse/struct.Placeholder.html @@ -0,0 +1,40 @@ +Placeholder in i3status_rs::formatting::parse - Rust

Struct Placeholder

Source
pub struct Placeholder<'a> {
+    pub name: &'a str,
+    pub formatter: Option<Formatter<'a>>,
+}

Fields§

§name: &'a str§formatter: Option<Formatter<'a>>

Trait Implementations§

Source§

impl<'a> Debug for Placeholder<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'a> PartialEq for Placeholder<'a>

Source§

fn eq(&self, other: &Placeholder<'a>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl<'a> Eq for Placeholder<'a>

Source§

impl<'a> StructuralPartialEq for Placeholder<'a>

Auto Trait Implementations§

§

impl<'a> Freeze for Placeholder<'a>

§

impl<'a> RefUnwindSafe for Placeholder<'a>

§

impl<'a> Send for Placeholder<'a>

§

impl<'a> Sync for Placeholder<'a>

§

impl<'a> Unpin for Placeholder<'a>

§

impl<'a> UnwindSafe for Placeholder<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/parse/struct.TokenList.html b/i3status_rs/formatting/parse/struct.TokenList.html new file mode 100644 index 0000000000..85129421ef --- /dev/null +++ b/i3status_rs/formatting/parse/struct.TokenList.html @@ -0,0 +1,37 @@ +TokenList in i3status_rs::formatting::parse - Rust

Struct TokenList

Source
pub struct TokenList<'a>(pub Vec<Token<'a>>);

Tuple Fields§

§0: Vec<Token<'a>>

Trait Implementations§

Source§

impl<'a> Debug for TokenList<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'a> PartialEq for TokenList<'a>

Source§

fn eq(&self, other: &TokenList<'a>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl TryFrom<TokenList<'_>> for TokenList

Source§

type Error = Error

The type returned in the event of a conversion error.
Source§

fn try_from(value: TokenList<'_>) -> Result<Self, Self::Error>

Performs the conversion.
Source§

impl<'a> Eq for TokenList<'a>

Source§

impl<'a> StructuralPartialEq for TokenList<'a>

Auto Trait Implementations§

§

impl<'a> Freeze for TokenList<'a>

§

impl<'a> RefUnwindSafe for TokenList<'a>

§

impl<'a> Send for TokenList<'a>

§

impl<'a> Sync for TokenList<'a>

§

impl<'a> Unpin for TokenList<'a>

§

impl<'a> UnwindSafe for TokenList<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/prefix/enum.Prefix.html b/i3status_rs/formatting/prefix/enum.Prefix.html new file mode 100644 index 0000000000..4a4481e1cb --- /dev/null +++ b/i3status_rs/formatting/prefix/enum.Prefix.html @@ -0,0 +1,79 @@ +Prefix in i3status_rs::formatting::prefix - Rust

Enum Prefix

Source
pub enum Prefix {
+
Show 13 variants Nano, + Micro, + Milli, + One, + OneButBinary, + Kilo, + Kibi, + Mega, + Mebi, + Giga, + Gibi, + Tera, + Tebi, +
}
Expand description

SI prefix

+

Variants§

§

Nano

n

+
§

Micro

u

+
§

Milli

m

+
§

One

1

+
§

OneButBinary

1i +1i is a special prefix which means “one but binary”. 1i is to 1 as Ki is to K.

+
§

Kilo

K

+
§

Kibi

Ki

+
§

Mega

M

+
§

Mebi

Mi

+
§

Giga

G

+
§

Gibi

Gi

+
§

Tera

T

+
§

Tebi

Ti

+

Implementations§

Source§

impl Prefix

Source

pub fn min_available() -> Self

Source

pub fn max_available() -> Self

Source

pub fn max(self, other: Self) -> Self

Source

pub fn apply(self, value: f64) -> f64

Source

pub fn eng(number: f64) -> Self

Source

pub fn eng_binary(number: f64) -> Self

Source

pub fn is_binary(&self) -> bool

Trait Implementations§

Source§

impl Clone for Prefix

Source§

fn clone(&self) -> Prefix

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Prefix

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Prefix

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl FromStr for Prefix

Source§

type Err = Error

The associated error which can be returned from parsing.
Source§

fn from_str(s: &str) -> Result<Self>

Parses a string s to return a value of this type. Read more
Source§

impl Ord for Prefix

Source§

fn cmp(&self, other: &Prefix) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where + Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for Prefix

Source§

fn eq(&self, other: &Prefix) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl PartialOrd for Prefix

Source§

fn partial_cmp(&self, other: &Prefix) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the +<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > +operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by +the >= operator. Read more
Source§

impl Copy for Prefix

Source§

impl Eq for Prefix

Source§

impl StructuralPartialEq for Prefix

Auto Trait Implementations§

§

impl Freeze for Prefix

§

impl RefUnwindSafe for Prefix

§

impl Send for Prefix

§

impl Sync for Prefix

§

impl Unpin for Prefix

§

impl UnwindSafe for Prefix

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<Q, K> Comparable<K> for Q
where + Q: Ord + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn compare(&self, key: &K) -> Ordering

Compare self to key and return their ordering.
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where + T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
§

impl<T> ToStringFallible for T
where + T: Display,

§

fn try_to_string(&self) -> Result<String, TryReserveError>

ToString::to_string, but without panic on OOM.

+
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/prefix/index.html b/i3status_rs/formatting/prefix/index.html new file mode 100644 index 0000000000..78ce2fb96e --- /dev/null +++ b/i3status_rs/formatting/prefix/index.html @@ -0,0 +1 @@ +i3status_rs::formatting::prefix - Rust

Module prefix

Source

Enums§

Prefix
SI prefix
\ No newline at end of file diff --git a/i3status_rs/formatting/prefix/sidebar-items.js b/i3status_rs/formatting/prefix/sidebar-items.js new file mode 100644 index 0000000000..b803622e81 --- /dev/null +++ b/i3status_rs/formatting/prefix/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Prefix"]}; \ No newline at end of file diff --git a/i3status_rs/formatting/scheduling/fn.manage_widgets_updates.html b/i3status_rs/formatting/scheduling/fn.manage_widgets_updates.html new file mode 100644 index 0000000000..08c5efa089 --- /dev/null +++ b/i3status_rs/formatting/scheduling/fn.manage_widgets_updates.html @@ -0,0 +1 @@ +manage_widgets_updates in i3status_rs::formatting::scheduling - Rust

Function manage_widgets_updates

Source
pub fn manage_widgets_updates() -> (UnboundedSender<(usize, Vec<u64>)>, Pin<Box<dyn Stream<Item = Vec<usize>>>>)
\ No newline at end of file diff --git a/i3status_rs/formatting/scheduling/index.html b/i3status_rs/formatting/scheduling/index.html new file mode 100644 index 0000000000..c8e49bc52f --- /dev/null +++ b/i3status_rs/formatting/scheduling/index.html @@ -0,0 +1 @@ +i3status_rs::formatting::scheduling - Rust

Module scheduling

Source

Functions§

manage_widgets_updates
\ No newline at end of file diff --git a/i3status_rs/formatting/scheduling/sidebar-items.js b/i3status_rs/formatting/scheduling/sidebar-items.js new file mode 100644 index 0000000000..1c8332f320 --- /dev/null +++ b/i3status_rs/formatting/scheduling/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["manage_widgets_updates"]}; \ No newline at end of file diff --git a/i3status_rs/formatting/sidebar-items.js b/i3status_rs/formatting/sidebar-items.js new file mode 100644 index 0000000000..1d184e839b --- /dev/null +++ b/i3status_rs/formatting/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["FormatError"],"mod":["config","formatter","parse","prefix","scheduling","template","unit","value"],"struct":["Format","Fragment","Metadata"],"type":["Values"]}; \ No newline at end of file diff --git a/i3status_rs/formatting/struct.Format.html b/i3status_rs/formatting/struct.Format.html new file mode 100644 index 0000000000..bf9f821ab0 --- /dev/null +++ b/i3status_rs/formatting/struct.Format.html @@ -0,0 +1,38 @@ +Format in i3status_rs::formatting - Rust

Struct Format

Source
pub struct Format { /* private fields */ }

Implementations§

Source§

impl Format

Source

pub fn contains_key(&self, key: &str) -> bool

Source

pub fn intervals(&self) -> Vec<u64>

Source

pub fn render( + &self, + values: &Values, + config: &SharedConfig, +) -> Result<(Vec<Fragment>, Vec<Fragment>)>

Trait Implementations§

Source§

impl Clone for Format

Source§

fn clone(&self) -> Format

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Format

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Format

§

impl !RefUnwindSafe for Format

§

impl Send for Format

§

impl Sync for Format

§

impl Unpin for Format

§

impl !UnwindSafe for Format

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/struct.Fragment.html b/i3status_rs/formatting/struct.Fragment.html new file mode 100644 index 0000000000..258f7b07e3 --- /dev/null +++ b/i3status_rs/formatting/struct.Fragment.html @@ -0,0 +1,38 @@ +Fragment in i3status_rs::formatting - Rust

Struct Fragment

Source
pub struct Fragment {
+    pub text: String,
+    pub metadata: Metadata,
+}

Fields§

§text: String§metadata: Metadata

Implementations§

Trait Implementations§

Source§

impl Clone for Fragment

Source§

fn clone(&self) -> Fragment

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Fragment

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Fragment

Source§

fn default() -> Fragment

Returns the “default value” for a type. Read more
Source§

impl From<String> for Fragment

Source§

fn from(text: String) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/struct.Metadata.html b/i3status_rs/formatting/struct.Metadata.html new file mode 100644 index 0000000000..edd1bda65c --- /dev/null +++ b/i3status_rs/formatting/struct.Metadata.html @@ -0,0 +1,44 @@ +Metadata in i3status_rs::formatting - Rust

Struct Metadata

Source
pub struct Metadata {
+    pub instance: Option<&'static str>,
+    pub underline: bool,
+    pub italic: bool,
+}

Fields§

§instance: Option<&'static str>§underline: bool§italic: bool

Implementations§

Trait Implementations§

Source§

impl Clone for Metadata

Source§

fn clone(&self) -> Metadata

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Metadata

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Metadata

Source§

fn default() -> Metadata

Returns the “default value” for a type. Read more
Source§

impl PartialEq for Metadata

Source§

fn eq(&self, other: &Metadata) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for Metadata

Source§

impl Eq for Metadata

Source§

impl StructuralPartialEq for Metadata

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/template/enum.Token.html b/i3status_rs/formatting/template/enum.Token.html new file mode 100644 index 0000000000..3a05d0dc94 --- /dev/null +++ b/i3status_rs/formatting/template/enum.Token.html @@ -0,0 +1,42 @@ +Token in i3status_rs::formatting::template - Rust

Enum Token

Source
pub enum Token {
+    Text(String),
+    Recursive(FormatTemplate),
+    Placeholder {
+        name: String,
+        formatter: Option<Box<dyn Formatter>>,
+    },
+    Icon {
+        name: String,
+    },
+}

Variants§

§

Text(String)

§

Recursive(FormatTemplate)

§

Placeholder

Fields

§name: String
§formatter: Option<Box<dyn Formatter>>
§

Icon

Fields

§name: String

Trait Implementations§

Source§

impl Debug for Token

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl TryFrom<Token<'_>> for Token

Source§

type Error = Error

The type returned in the event of a conversion error.
Source§

fn try_from(value: Token<'_>) -> Result<Self, Self::Error>

Performs the conversion.

Auto Trait Implementations§

§

impl Freeze for Token

§

impl !RefUnwindSafe for Token

§

impl Send for Token

§

impl Sync for Token

§

impl Unpin for Token

§

impl !UnwindSafe for Token

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/template/index.html b/i3status_rs/formatting/template/index.html new file mode 100644 index 0000000000..5fae0d3028 --- /dev/null +++ b/i3status_rs/formatting/template/index.html @@ -0,0 +1 @@ +i3status_rs::formatting::template - Rust

Module template

Source

Structs§

FormatTemplate
TokenList

Enums§

Token
\ No newline at end of file diff --git a/i3status_rs/formatting/template/sidebar-items.js b/i3status_rs/formatting/template/sidebar-items.js new file mode 100644 index 0000000000..c65ee6d7cc --- /dev/null +++ b/i3status_rs/formatting/template/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Token"],"struct":["FormatTemplate","TokenList"]}; \ No newline at end of file diff --git a/i3status_rs/formatting/template/struct.FormatTemplate.html b/i3status_rs/formatting/template/struct.FormatTemplate.html new file mode 100644 index 0000000000..a76c457dac --- /dev/null +++ b/i3status_rs/formatting/template/struct.FormatTemplate.html @@ -0,0 +1,39 @@ +FormatTemplate in i3status_rs::formatting::template - Rust

Struct FormatTemplate

Source
pub struct FormatTemplate(/* private fields */);

Implementations§

Source§

impl FormatTemplate

Source

pub fn contains_key(&self, key: &str) -> bool

Source

pub fn render( + &self, + values: &Values, + config: &SharedConfig, +) -> Result<Vec<Fragment>, FormatError>

Source

pub fn init_intervals(&self, intervals: &mut Vec<u64>)

Trait Implementations§

Source§

impl Clone for FormatTemplate

Source§

fn clone(&self) -> FormatTemplate

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for FormatTemplate

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for FormatTemplate

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl FromStr for FormatTemplate

Source§

type Err = Error

The associated error which can be returned from parsing.
Source§

fn from_str(s: &str) -> Result<Self>

Parses a string s to return a value of this type. Read more
Source§

impl TryFrom<FormatTemplate<'_>> for FormatTemplate

Source§

type Error = Error

The type returned in the event of a conversion error.
Source§

fn try_from(value: FormatTemplate<'_>) -> Result<Self, Self::Error>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/template/struct.TokenList.html b/i3status_rs/formatting/template/struct.TokenList.html new file mode 100644 index 0000000000..01cc224e91 --- /dev/null +++ b/i3status_rs/formatting/template/struct.TokenList.html @@ -0,0 +1,36 @@ +TokenList in i3status_rs::formatting::template - Rust

Struct TokenList

Source
pub struct TokenList(pub Vec<Token>);

Tuple Fields§

§0: Vec<Token>

Implementations§

Source§

impl TokenList

Source

pub fn render( + &self, + values: &Values, + config: &SharedConfig, +) -> Result<Vec<Fragment>, FormatError>

Trait Implementations§

Source§

impl Debug for TokenList

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl TryFrom<TokenList<'_>> for TokenList

Source§

type Error = Error

The type returned in the event of a conversion error.
Source§

fn try_from(value: TokenList<'_>) -> Result<Self, Self::Error>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/type.Values.html b/i3status_rs/formatting/type.Values.html new file mode 100644 index 0000000000..5f7b79ddd8 --- /dev/null +++ b/i3status_rs/formatting/type.Values.html @@ -0,0 +1 @@ +Values in i3status_rs::formatting - Rust

Type Alias Values

Source
pub type Values = HashMap<Cow<'static, str>, Value>;

Aliased Type§

pub struct Values { /* private fields */ }
\ No newline at end of file diff --git a/i3status_rs/formatting/unit/enum.Unit.html b/i3status_rs/formatting/unit/enum.Unit.html new file mode 100644 index 0000000000..031f897e40 --- /dev/null +++ b/i3status_rs/formatting/unit/enum.Unit.html @@ -0,0 +1,59 @@ +Unit in i3status_rs::formatting::unit - Rust

Enum Unit

Source
pub enum Unit {
+    Bytes,
+    Bits,
+    Percents,
+    Degrees,
+    Seconds,
+    Watts,
+    Hertz,
+    None,
+}

Variants§

§

Bytes

B

+
§

Bits

b

+
§

Percents

%

+
§

Degrees

deg

+
§

Seconds

s

+
§

Watts

W

+
§

Hertz

Hz

+
§

None

``

+

Implementations§

Source§

impl Unit

Source

pub fn convert(self, value: f64, unit: Self) -> Result<f64>

Source

pub fn clamp_prefix(self, prefix: Prefix) -> Prefix

Trait Implementations§

Source§

impl Clone for Unit

Source§

fn clone(&self) -> Unit

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Unit

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Unit

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl FromStr for Unit

Source§

type Err = Error

The associated error which can be returned from parsing.
Source§

fn from_str(s: &str) -> Result<Self>

Parses a string s to return a value of this type. Read more
Source§

impl PartialEq for Unit

Source§

fn eq(&self, other: &Unit) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for Unit

Source§

impl Eq for Unit

Source§

impl StructuralPartialEq for Unit

Auto Trait Implementations§

§

impl Freeze for Unit

§

impl RefUnwindSafe for Unit

§

impl Send for Unit

§

impl Sync for Unit

§

impl Unpin for Unit

§

impl UnwindSafe for Unit

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where + T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
§

impl<T> ToStringFallible for T
where + T: Display,

§

fn try_to_string(&self) -> Result<String, TryReserveError>

ToString::to_string, but without panic on OOM.

+
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/unit/index.html b/i3status_rs/formatting/unit/index.html new file mode 100644 index 0000000000..c86cf5457e --- /dev/null +++ b/i3status_rs/formatting/unit/index.html @@ -0,0 +1 @@ +i3status_rs::formatting::unit - Rust

Module unit

Source

Enums§

Unit
\ No newline at end of file diff --git a/i3status_rs/formatting/unit/sidebar-items.js b/i3status_rs/formatting/unit/sidebar-items.js new file mode 100644 index 0000000000..57865ed4f4 --- /dev/null +++ b/i3status_rs/formatting/unit/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Unit"]}; \ No newline at end of file diff --git a/i3status_rs/formatting/value/enum.ValueInner.html b/i3status_rs/formatting/value/enum.ValueInner.html new file mode 100644 index 0000000000..47a5e33464 --- /dev/null +++ b/i3status_rs/formatting/value/enum.ValueInner.html @@ -0,0 +1,44 @@ +ValueInner in i3status_rs::formatting::value - Rust

Enum ValueInner

Source
pub enum ValueInner {
+    Text(String),
+    Icon(Cow<'static, str>, Option<f64>),
+    Number {
+        val: f64,
+        unit: Unit,
+    },
+    Datetime(DateTime<Utc>, Option<Tz>),
+    Duration(Duration),
+    Flag,
+}

Variants§

§

Text(String)

§

Icon(Cow<'static, str>, Option<f64>)

§

Number

Fields

§val: f64
§unit: Unit
§

Datetime(DateTime<Utc>, Option<Tz>)

§

Duration(Duration)

§

Flag

Implementations§

Source§

impl ValueInner

Source

pub fn type_name(&self) -> &'static str

Trait Implementations§

Source§

impl Clone for ValueInner

Source§

fn clone(&self) -> ValueInner

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ValueInner

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/value/index.html b/i3status_rs/formatting/value/index.html new file mode 100644 index 0000000000..59ad425f36 --- /dev/null +++ b/i3status_rs/formatting/value/index.html @@ -0,0 +1 @@ +i3status_rs::formatting::value - Rust

Module value

Source

Structs§

Value

Enums§

ValueInner

Traits§

IntoF64
\ No newline at end of file diff --git a/i3status_rs/formatting/value/sidebar-items.js b/i3status_rs/formatting/value/sidebar-items.js new file mode 100644 index 0000000000..162ba3b7cb --- /dev/null +++ b/i3status_rs/formatting/value/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ValueInner"],"struct":["Value"],"trait":["IntoF64"]}; \ No newline at end of file diff --git a/i3status_rs/formatting/value/struct.Value.html b/i3status_rs/formatting/value/struct.Value.html new file mode 100644 index 0000000000..38c04b621d --- /dev/null +++ b/i3status_rs/formatting/value/struct.Value.html @@ -0,0 +1,47 @@ +Value in i3status_rs::formatting::value - Rust

Struct Value

Source
pub struct Value {
+    pub inner: ValueInner,
+    pub metadata: Metadata,
+}

Fields§

§inner: ValueInner§metadata: Metadata

Implementations§

Source§

impl Value

Constructors

+
Source

pub fn new(val: ValueInner) -> Self

Source

pub fn flag() -> Self

Source

pub fn datetime(datetime: DateTime<Utc>, tz: Option<Tz>) -> Self

Source

pub fn duration(duration: Duration) -> Self

Source

pub fn icon<S>(name: S) -> Self
where + S: Into<Cow<'static, str>>,

Source

pub fn icon_progression<S>(name: S, value: f64) -> Self
where + S: Into<Cow<'static, str>>,

Source

pub fn icon_progression_bound<S>( + name: S, + value: f64, + low: f64, + high: f64, +) -> Self
where + S: Into<Cow<'static, str>>,

Source

pub fn text(text: String) -> Self

Source

pub fn number_unit(val: impl IntoF64, unit: Unit) -> Self

Source

pub fn bytes(val: impl IntoF64) -> Self

Source

pub fn bits(val: impl IntoF64) -> Self

Source

pub fn percents(val: impl IntoF64) -> Self

Source

pub fn degrees(val: impl IntoF64) -> Self

Source

pub fn seconds(val: impl IntoF64) -> Self

Source

pub fn watts(val: impl IntoF64) -> Self

Source

pub fn hertz(val: impl IntoF64) -> Self

Source

pub fn number(val: impl IntoF64) -> Self

Source§

impl Value

Set options

+
Source

pub fn with_instance(self, instance: &'static str) -> Self

Source

pub fn underline(self, val: bool) -> Self

Source

pub fn italic(self, val: bool) -> Self

Source

pub fn default_formatter(&self) -> &'static dyn Formatter

Trait Implementations§

Source§

impl Clone for Value

Source§

fn clone(&self) -> Value

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Value

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Value

§

impl RefUnwindSafe for Value

§

impl Send for Value

§

impl Sync for Value

§

impl Unpin for Value

§

impl UnwindSafe for Value

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/formatting/value/trait.IntoF64.html b/i3status_rs/formatting/value/trait.IntoF64.html new file mode 100644 index 0000000000..8b785f0c7d --- /dev/null +++ b/i3status_rs/formatting/value/trait.IntoF64.html @@ -0,0 +1,4 @@ +IntoF64 in i3status_rs::formatting::value - Rust

Trait IntoF64

Source
pub trait IntoF64 {
+    // Required method
+    fn into_f64(self) -> f64;
+}

Required Methods§

Source

fn into_f64(self) -> f64

Implementations on Foreign Types§

Source§

impl IntoF64 for f32

Source§

fn into_f64(self) -> f64

Source§

impl IntoF64 for f64

Source§

fn into_f64(self) -> f64

Source§

impl IntoF64 for i8

Source§

fn into_f64(self) -> f64

Source§

impl IntoF64 for i16

Source§

fn into_f64(self) -> f64

Source§

impl IntoF64 for i32

Source§

fn into_f64(self) -> f64

Source§

impl IntoF64 for i64

Source§

fn into_f64(self) -> f64

Source§

impl IntoF64 for isize

Source§

fn into_f64(self) -> f64

Source§

impl IntoF64 for u8

Source§

fn into_f64(self) -> f64

Source§

impl IntoF64 for u16

Source§

fn into_f64(self) -> f64

Source§

impl IntoF64 for u32

Source§

fn into_f64(self) -> f64

Source§

impl IntoF64 for u64

Source§

fn into_f64(self) -> f64

Source§

impl IntoF64 for usize

Source§

fn into_f64(self) -> f64

Implementors§

\ No newline at end of file diff --git a/i3status_rs/geolocator/enum.GeolocatorBackend.html b/i3status_rs/geolocator/enum.GeolocatorBackend.html new file mode 100644 index 0000000000..18507e1759 --- /dev/null +++ b/i3status_rs/geolocator/enum.GeolocatorBackend.html @@ -0,0 +1,41 @@ +GeolocatorBackend in i3status_rs::geolocator - Rust

Enum GeolocatorBackend

Source
pub enum GeolocatorBackend {
+    Ipapi(Config),
+    Ip2Location(Config),
+}

Variants§

§

Ipapi(Config)

§

Ip2Location(Config)

Trait Implementations§

Source§

impl Clone for GeolocatorBackend

Source§

fn clone(&self) -> GeolocatorBackend

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for GeolocatorBackend

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for GeolocatorBackend

Source§

fn default() -> Self

Return GeolocatorBackend::Ipapi(Default::default())

+
Source§

impl<'de> Deserialize<'de> for GeolocatorBackend

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl From<GeolocatorBackend> for Geolocator

Source§

fn from(backend: GeolocatorBackend) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/geolocator/index.html b/i3status_rs/geolocator/index.html new file mode 100644 index 0000000000..8bdb7f2040 --- /dev/null +++ b/i3status_rs/geolocator/index.html @@ -0,0 +1,25 @@ +i3status_rs::geolocator - Rust

Module geolocator

Source
Expand description

Geolocation service

+

This global module can be used to provide geolocation information +to blocks that support it.

+

ipapi.co is the default geolocator service.

+

§Configuration

§ipapi.co Options

+ +
KeyValuesRequiredDefault
geolocatoripapiYesNone
+

§Ip2Location.io Options

+ + +
KeyValuesRequiredDefault
geolocatorip2locationYesNone
api_keyYour Ip2Location.io API key.NoNone
+
+

An api key is not required to get back basic information from ip2location.io. +However, to get more additional information, an api key is required. +See pricing for more information.

+

The api_key option can be omitted from configuration, in which case it +can be provided in the environment variable IP2LOCATION_API_KEY

+

§Examples

+

Use the default geolocator service:

+
[geolocator]
+geolocator = "ipapi"
+

Use Ip2Location.io

+
[geolocator]
+geolocator = "ip2location"
+api_key = "XXX"

Structs§

Geolocator
IPAddressInfo

Enums§

GeolocatorBackend
\ No newline at end of file diff --git a/i3status_rs/geolocator/sidebar-items.js b/i3status_rs/geolocator/sidebar-items.js new file mode 100644 index 0000000000..8e58390abf --- /dev/null +++ b/i3status_rs/geolocator/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["GeolocatorBackend"],"struct":["Geolocator","IPAddressInfo"]}; \ No newline at end of file diff --git a/i3status_rs/geolocator/struct.Geolocator.html b/i3status_rs/geolocator/struct.Geolocator.html new file mode 100644 index 0000000000..7bb6fc4d17 --- /dev/null +++ b/i3status_rs/geolocator/struct.Geolocator.html @@ -0,0 +1,40 @@ +Geolocator in i3status_rs::geolocator - Rust

Struct Geolocator

Source
pub struct Geolocator { /* private fields */ }

Implementations§

Source§

impl Geolocator

Source

pub fn name(&self) -> Cow<'static, str>

Source

pub async fn find_ip_location( + &self, + client: &Client, + interval: Duration, +) -> Result<IPAddressInfo>

No-op if last API call was made in the last interval seconds.

+

Trait Implementations§

Source§

impl Debug for Geolocator

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Geolocator

Source§

fn default() -> Geolocator

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for Geolocator

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl From<GeolocatorBackend> for Geolocator

Source§

fn from(backend: GeolocatorBackend) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/geolocator/struct.IPAddressInfo.html b/i3status_rs/geolocator/struct.IPAddressInfo.html new file mode 100644 index 0000000000..683106ffc5 --- /dev/null +++ b/i3status_rs/geolocator/struct.IPAddressInfo.html @@ -0,0 +1,64 @@ +IPAddressInfo in i3status_rs::geolocator - Rust

Struct IPAddressInfo

Source
pub struct IPAddressInfo {
Show 26 fields + pub ip: String, + pub latitude: f64, + pub longitude: f64, + pub city: String, + pub version: Option<String>, + pub region: Option<String>, + pub region_code: Option<String>, + pub country: Option<String>, + pub country_name: Option<String>, + pub country_code: Option<String>, + pub country_code_iso3: Option<String>, + pub country_capital: Option<String>, + pub country_tld: Option<String>, + pub continent_code: Option<String>, + pub in_eu: Option<bool>, + pub postal: Option<String>, + pub timezone: Option<String>, + pub utc_offset: Option<String>, + pub country_calling_code: Option<String>, + pub currency: Option<String>, + pub currency_name: Option<String>, + pub languages: Option<String>, + pub country_area: Option<f64>, + pub country_population: Option<f64>, + pub asn: Option<String>, + pub org: Option<String>, +
}

Fields§

§ip: String§latitude: f64§longitude: f64§city: String§version: Option<String>§region: Option<String>§region_code: Option<String>§country: Option<String>§country_name: Option<String>§country_code: Option<String>§country_code_iso3: Option<String>§country_capital: Option<String>§country_tld: Option<String>§continent_code: Option<String>§in_eu: Option<bool>§postal: Option<String>§timezone: Option<String>§utc_offset: Option<String>§country_calling_code: Option<String>§currency: Option<String>§currency_name: Option<String>§languages: Option<String>§country_area: Option<f64>§country_population: Option<f64>§asn: Option<String>§org: Option<String>

Trait Implementations§

Source§

impl Clone for IPAddressInfo

Source§

fn clone(&self) -> IPAddressInfo

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for IPAddressInfo

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for IPAddressInfo

Source§

fn default() -> IPAddressInfo

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for IPAddressInfo

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/icons/enum.Icon.html b/i3status_rs/icons/enum.Icon.html new file mode 100644 index 0000000000..943c1a5f8a --- /dev/null +++ b/i3status_rs/icons/enum.Icon.html @@ -0,0 +1,39 @@ +Icon in i3status_rs::icons - Rust

Enum Icon

Source
pub enum Icon {
+    Single(String),
+    Progression(Vec<String>),
+}

Variants§

§

Single(String)

§

Progression(Vec<String>)

Trait Implementations§

Source§

impl Clone for Icon

Source§

fn clone(&self) -> Icon

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Icon

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Icon

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl From<&'static str> for Icon

Source§

fn from(value: &'static str) -> Self

Converts to this type from the input type.
Source§

impl<const N: usize> From<[&str; N]> for Icon

Source§

fn from(value: [&str; N]) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl Freeze for Icon

§

impl RefUnwindSafe for Icon

§

impl Send for Icon

§

impl Sync for Icon

§

impl Unpin for Icon

§

impl UnwindSafe for Icon

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/icons/index.html b/i3status_rs/icons/index.html new file mode 100644 index 0000000000..590e62e187 --- /dev/null +++ b/i3status_rs/icons/index.html @@ -0,0 +1 @@ +i3status_rs::icons - Rust

Module icons

Source

Structs§

Icons

Enums§

Icon
\ No newline at end of file diff --git a/i3status_rs/icons/sidebar-items.js b/i3status_rs/icons/sidebar-items.js new file mode 100644 index 0000000000..589dbadc97 --- /dev/null +++ b/i3status_rs/icons/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Icon"],"struct":["Icons"]}; \ No newline at end of file diff --git a/i3status_rs/icons/struct.Icons.html b/i3status_rs/icons/struct.Icons.html new file mode 100644 index 0000000000..521fc8cb2b --- /dev/null +++ b/i3status_rs/icons/struct.Icons.html @@ -0,0 +1,37 @@ +Icons in i3status_rs::icons - Rust

Struct Icons

Source
pub struct Icons(pub HashMap<String, Icon>);

Tuple Fields§

§0: HashMap<String, Icon>

Implementations§

Source§

impl Icons

Source

pub fn from_file(file: &str) -> Result<Self>

Source

pub fn apply_overrides(&mut self, overrides: HashMap<String, Icon>)

Source

pub fn get(&self, icon: &str, value: Option<f64>) -> Option<&str>

Trait Implementations§

Source§

impl Clone for Icons

Source§

fn clone(&self) -> Icons

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Icons

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Icons

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for Icons

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

§

impl Freeze for Icons

§

impl RefUnwindSafe for Icons

§

impl Send for Icons

§

impl Sync for Icons

§

impl Unpin for Icons

§

impl UnwindSafe for Icons

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/index.html b/i3status_rs/index.html new file mode 100644 index 0000000000..916629e3bb --- /dev/null +++ b/i3status_rs/index.html @@ -0,0 +1,4 @@ +i3status_rs - Rust

Crate i3status_rs

Source

Re-exports§

pub use env_logger;
pub use serde_json;
pub use tokio;

Modules§

blocks
The collection of blocks
click
config
errors
escape
Simple json escaping
formatting
Formatting system
geolocator
Geolocation service
icons
protocol
themes
util
widget

Macros§

map
Example

Structs§

BarState
Block
CliArgs
A feature-rich and resource-friendly replacement for i3status(1), written in Rust. The +i3status-rs program writes a stream of configurable “blocks” of system information (time, +battery status, volume, etc.) to standard output in the JSON format understood by i3bar(1) and +sway-bar(5).
\ No newline at end of file diff --git a/i3status_rs/macro.map!.html b/i3status_rs/macro.map!.html new file mode 100644 index 0000000000..515cd23ef6 --- /dev/null +++ b/i3status_rs/macro.map!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.map.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/macro.map.html b/i3status_rs/macro.map.html new file mode 100644 index 0000000000..49b0434422 --- /dev/null +++ b/i3status_rs/macro.map.html @@ -0,0 +1,19 @@ +map in i3status_rs - Rust

Macro map

Source
macro_rules! map {
+    (@extend $map:ident $( $([$($cond_tokens:tt)*])? $key:literal => $value:expr ),* $(,)?) => { ... };
+    (@extend $map:ident $( $key:expr => $value:expr ),* $(,)?) => { ... };
+    (@insert $map:ident, $key:expr, $value:expr) => { ... };
+    (@insert $map:ident, $key:expr, $value:expr, if $cond:expr) => { ... };
+    (@insert $map:ident, $key:expr, $value:expr, if let $pat:pat = $match_on:expr) => { ... };
+    ($($tt:tt)*) => { ... };
+}
Expand description

§Example

+
let opt = Some(1);
+let m: HashMap<&'static str, String> = map! {
+    "key" => "value",
+    [if true] "hello" => "world",
+    [if let Some(x) = opt] "opt" => x.to_string(),
+};
+map! { @extend m
+    "new key" => "new value",
+    "one" => "more",
+}
+
\ No newline at end of file diff --git a/i3status_rs/protocol/fn.init.html b/i3status_rs/protocol/fn.init.html new file mode 100644 index 0000000000..3be6854184 --- /dev/null +++ b/i3status_rs/protocol/fn.init.html @@ -0,0 +1 @@ +init in i3status_rs::protocol - Rust

Function init

Source
pub fn init(never_pause: bool)
\ No newline at end of file diff --git a/i3status_rs/protocol/i3bar_block/enum.I3BarBlockAlign.html b/i3status_rs/protocol/i3bar_block/enum.I3BarBlockAlign.html new file mode 100644 index 0000000000..fb2a3356e8 --- /dev/null +++ b/i3status_rs/protocol/i3bar_block/enum.I3BarBlockAlign.html @@ -0,0 +1,39 @@ +I3BarBlockAlign in i3status_rs::protocol::i3bar_block - Rust

Enum I3BarBlockAlign

Source
pub enum I3BarBlockAlign {
+    Center,
+    Right,
+    Left,
+}

Variants§

§

Center

§

Right

§

Left

Trait Implementations§

Source§

impl Clone for I3BarBlockAlign

Source§

fn clone(&self) -> I3BarBlockAlign

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for I3BarBlockAlign

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Serialize for I3BarBlockAlign

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl Copy for I3BarBlockAlign

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/protocol/i3bar_block/enum.I3BarBlockMinWidth.html b/i3status_rs/protocol/i3bar_block/enum.I3BarBlockMinWidth.html new file mode 100644 index 0000000000..6b2a3ee525 --- /dev/null +++ b/i3status_rs/protocol/i3bar_block/enum.I3BarBlockMinWidth.html @@ -0,0 +1,38 @@ +I3BarBlockMinWidth in i3status_rs::protocol::i3bar_block - Rust

Enum I3BarBlockMinWidth

Source
pub enum I3BarBlockMinWidth {
+    Pixels(usize),
+    Text(String),
+}

Variants§

§

Pixels(usize)

§

Text(String)

Trait Implementations§

Source§

impl Clone for I3BarBlockMinWidth

Source§

fn clone(&self) -> I3BarBlockMinWidth

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for I3BarBlockMinWidth

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Serialize for I3BarBlockMinWidth

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/protocol/i3bar_block/index.html b/i3status_rs/protocol/i3bar_block/index.html new file mode 100644 index 0000000000..630b846915 --- /dev/null +++ b/i3status_rs/protocol/i3bar_block/index.html @@ -0,0 +1 @@ +i3status_rs::protocol::i3bar_block - Rust

Module i3bar_block

Source

Structs§

I3BarBlock
Represent block as described in https://i3wm.org/docs/i3bar-protocol.html

Enums§

I3BarBlockAlign
I3BarBlockMinWidth
\ No newline at end of file diff --git a/i3status_rs/protocol/i3bar_block/sidebar-items.js b/i3status_rs/protocol/i3bar_block/sidebar-items.js new file mode 100644 index 0000000000..a062d1813b --- /dev/null +++ b/i3status_rs/protocol/i3bar_block/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["I3BarBlockAlign","I3BarBlockMinWidth"],"struct":["I3BarBlock"]}; \ No newline at end of file diff --git a/i3status_rs/protocol/i3bar_block/struct.I3BarBlock.html b/i3status_rs/protocol/i3bar_block/struct.I3BarBlock.html new file mode 100644 index 0000000000..ff7c8a0b21 --- /dev/null +++ b/i3status_rs/protocol/i3bar_block/struct.I3BarBlock.html @@ -0,0 +1,62 @@ +I3BarBlock in i3status_rs::protocol::i3bar_block - Rust

Struct I3BarBlock

Source
pub struct I3BarBlock {
Show 17 fields + pub full_text: String, + pub short_text: String, + pub color: Color, + pub background: Color, + pub border: Option<String>, + pub border_top: Option<usize>, + pub border_right: Option<usize>, + pub border_bottom: Option<usize>, + pub border_left: Option<usize>, + pub min_width: Option<I3BarBlockMinWidth>, + pub align: Option<I3BarBlockAlign>, + pub name: Option<String>, + pub instance: String, + pub urgent: Option<bool>, + pub separator: Option<bool>, + pub separator_block_width: Option<usize>, + pub markup: Option<String>, +
}
Expand description

Represent block as described in https://i3wm.org/docs/i3bar-protocol.html

+

Fields§

§full_text: String§short_text: String§color: Color§background: Color§border: Option<String>§border_top: Option<usize>§border_right: Option<usize>§border_bottom: Option<usize>§border_left: Option<usize>§min_width: Option<I3BarBlockMinWidth>§align: Option<I3BarBlockAlign>§name: Option<String>

This project uses name field to uniquely identify each “logical block”. For example two +“config blocks” merged using merge_with_next will have the same name. This information +could be used by some bar frontends (such as i3bar-river) and will be ignored by i3bar +and swaybar.

+
§instance: String

This project uses instance field to uniquely identify each block and optionally a part +of the block, e.g. a “button”. The format is {block_id}:{optional_widget_name}. This info +is used when dispatching click events.

+
§urgent: Option<bool>§separator: Option<bool>§separator_block_width: Option<usize>§markup: Option<String>

Trait Implementations§

Source§

impl Clone for I3BarBlock

Source§

fn clone(&self) -> I3BarBlock

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for I3BarBlock

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for I3BarBlock

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Serialize for I3BarBlock

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/protocol/i3bar_event/fn.events_stream.html b/i3status_rs/protocol/i3bar_event/fn.events_stream.html new file mode 100644 index 0000000000..aac05e51e9 --- /dev/null +++ b/i3status_rs/protocol/i3bar_event/fn.events_stream.html @@ -0,0 +1,4 @@ +events_stream in i3status_rs::protocol::i3bar_event - Rust

Function events_stream

Source
pub fn events_stream(
+    invert_scrolling: bool,
+    double_click_delay: Duration,
+) -> Pin<Box<dyn Stream<Item = I3BarEvent>>>
\ No newline at end of file diff --git a/i3status_rs/protocol/i3bar_event/index.html b/i3status_rs/protocol/i3bar_event/index.html new file mode 100644 index 0000000000..7dee7e52a8 --- /dev/null +++ b/i3status_rs/protocol/i3bar_event/index.html @@ -0,0 +1 @@ +i3status_rs::protocol::i3bar_event - Rust

Module i3bar_event

Source

Structs§

I3BarEvent

Functions§

events_stream
\ No newline at end of file diff --git a/i3status_rs/protocol/i3bar_event/sidebar-items.js b/i3status_rs/protocol/i3bar_event/sidebar-items.js new file mode 100644 index 0000000000..532a2b4137 --- /dev/null +++ b/i3status_rs/protocol/i3bar_event/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["events_stream"],"struct":["I3BarEvent"]}; \ No newline at end of file diff --git a/i3status_rs/protocol/i3bar_event/struct.I3BarEvent.html b/i3status_rs/protocol/i3bar_event/struct.I3BarEvent.html new file mode 100644 index 0000000000..ee22fd50cc --- /dev/null +++ b/i3status_rs/protocol/i3bar_event/struct.I3BarEvent.html @@ -0,0 +1,43 @@ +I3BarEvent in i3status_rs::protocol::i3bar_event - Rust

Struct I3BarEvent

Source
pub struct I3BarEvent {
+    pub id: usize,
+    pub instance: Option<String>,
+    pub button: MouseButton,
+}

Fields§

§id: usize§instance: Option<String>§button: MouseButton

Trait Implementations§

Source§

impl Clone for I3BarEvent

Source§

fn clone(&self) -> I3BarEvent

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for I3BarEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for I3BarEvent

Source§

fn eq(&self, other: &I3BarEvent) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for I3BarEvent

Source§

impl StructuralPartialEq for I3BarEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/protocol/index.html b/i3status_rs/protocol/index.html new file mode 100644 index 0000000000..a13840526f --- /dev/null +++ b/i3status_rs/protocol/index.html @@ -0,0 +1 @@ +i3status_rs::protocol - Rust

Module protocol

Source

Modules§

i3bar_block
i3bar_event

Functions§

init
\ No newline at end of file diff --git a/i3status_rs/protocol/sidebar-items.js b/i3status_rs/protocol/sidebar-items.js new file mode 100644 index 0000000000..9b87724919 --- /dev/null +++ b/i3status_rs/protocol/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["init"],"mod":["i3bar_block","i3bar_event"]}; \ No newline at end of file diff --git a/i3status_rs/sidebar-items.js b/i3status_rs/sidebar-items.js new file mode 100644 index 0000000000..6486f70c16 --- /dev/null +++ b/i3status_rs/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"macro":["map"],"mod":["blocks","click","config","errors","escape","formatting","geolocator","icons","protocol","themes","util","widget"],"struct":["BarState","Block","CliArgs"]}; \ No newline at end of file diff --git a/i3status_rs/struct.BarState.html b/i3status_rs/struct.BarState.html new file mode 100644 index 0000000000..8ce791d62c --- /dev/null +++ b/i3status_rs/struct.BarState.html @@ -0,0 +1,35 @@ +BarState in i3status_rs - Rust

Struct BarState

Source
pub struct BarState { /* private fields */ }

Implementations§

Source§

impl BarState

Source

pub fn new(config: Config) -> Self

Source

pub async fn spawn_block( + &mut self, + block_config: BlockConfigEntry, +) -> Result<()>

Source

pub async fn run_event_loop(self, restart: fn() -> !) -> Result<(), BlockError>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/struct.Block.html b/i3status_rs/struct.Block.html new file mode 100644 index 0000000000..cfab244546 --- /dev/null +++ b/i3status_rs/struct.Block.html @@ -0,0 +1,32 @@ +Block in i3status_rs - Rust

Struct Block

Source
pub struct Block { /* private fields */ }

Trait Implementations§

Source§

impl Debug for Block

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Block

§

impl !RefUnwindSafe for Block

§

impl Send for Block

§

impl Sync for Block

§

impl Unpin for Block

§

impl !UnwindSafe for Block

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/struct.CliArgs.html b/i3status_rs/struct.CliArgs.html new file mode 100644 index 0000000000..666744ef9b --- /dev/null +++ b/i3status_rs/struct.CliArgs.html @@ -0,0 +1,77 @@ +CliArgs in i3status_rs - Rust

Struct CliArgs

Source
pub struct CliArgs {
+    pub config: String,
+    pub never_pause: bool,
+    pub no_init: bool,
+    pub blocking_threads: usize,
+}
Expand description

A feature-rich and resource-friendly replacement for i3status(1), written in Rust. The +i3status-rs program writes a stream of configurable “blocks” of system information (time, +battery status, volume, etc.) to standard output in the JSON format understood by i3bar(1) and +sway-bar(5).

+

Fields§

§config: String

Sets a TOML config file

+
    +
  1. +

    If full absolute path given, then use it as is: /home/foo/i3rs-config.toml

    +
  2. +
  3. +

    If filename given, e.g. “custom_theme.toml”, then first look in $XDG_CONFIG_HOME/i3status-rust

    +
  4. +
  5. +

    Then look for it in $XDG_DATA_HOME/i3status-rust

    +
  6. +
  7. +

    Otherwise look for it in /usr/share/i3status-rust

    +
  8. +
+
§never_pause: bool

Ignore any attempts by i3 to pause the bar when hidden/fullscreen

+
§no_init: bool

Do not send the init sequence

+
§blocking_threads: usize

The maximum number of blocking threads spawned by tokio

+

Trait Implementations§

Source§

impl Args for CliArgs

Source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
Source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self via +[FromArgMatches::from_arg_matches_mut] Read more
Source§

fn augment_args_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate self via +[FromArgMatches::update_from_arg_matches_mut] Read more
Source§

impl CommandFactory for CliArgs

Source§

fn command<'b>() -> Command

Build a [Command] that can instantiate Self. Read more
Source§

fn command_for_update<'b>() -> Command

Build a [Command] that can update self. Read more
Source§

impl Debug for CliArgs

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl FromArgMatches for CliArgs

Source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
Source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches, +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
Source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches, +) -> Result<(), Error>

Assign values from ArgMatches to self.
Source§

fn update_from_arg_matches_mut( + &mut self, + __clap_arg_matches: &mut ArgMatches, +) -> Result<(), Error>

Assign values from ArgMatches to self.
Source§

impl Parser for CliArgs

§

fn parse() -> Self

Parse from std::env::args_os(), [exit][Error::exit] on error.
§

fn try_parse() -> Result<Self, Error>

Parse from std::env::args_os(), return Err on error.
§

fn parse_from<I, T>(itr: I) -> Self
where + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone,

Parse from iterator, [exit][Error::exit] on error.
§

fn try_parse_from<I, T>(itr: I) -> Result<Self, Error>
where + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone,

Parse from iterator, return Err on error.
§

fn update_from<I, T>(&mut self, itr: I)
where + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone,

Update from iterator, [exit][Error::exit] on error. Read more
§

fn try_update_from<I, T>(&mut self, itr: I) -> Result<(), Error>
where + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone,

Update from iterator, return Err on error.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/themes/color/enum.Color.html b/i3status_rs/themes/color/enum.Color.html new file mode 100644 index 0000000000..7941cd92f3 --- /dev/null +++ b/i3status_rs/themes/color/enum.Color.html @@ -0,0 +1,45 @@ +Color in i3status_rs::themes::color - Rust

Enum Color

Source
pub enum Color {
+    None,
+    Auto,
+    Rgba(Rgba),
+    Hsva(Hsva),
+}

Variants§

§

None

§

Auto

§

Rgba(Rgba)

§

Hsva(Hsva)

Implementations§

Source§

impl Color

Source

pub fn skip_ser(&self) -> bool

Trait Implementations§

Source§

impl Add for Color

Source§

type Output = Color

The resulting type after applying the + operator.
Source§

fn add(self, rhs: Self) -> Self::Output

Performs the + operation. Read more
Source§

impl Clone for Color

Source§

fn clone(&self) -> Color

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Color

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Color

Source§

fn default() -> Self

Return Color::None

+
Source§

impl<'de> Deserialize<'de> for Color

Source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl FromStr for Color

Source§

type Err = Error

The associated error which can be returned from parsing.
Source§

fn from_str(color: &str) -> Result<Self, Self::Err>

Parses a string s to return a value of this type. Read more
Source§

impl PartialEq for Color

Source§

fn eq(&self, other: &Color) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Serialize for Color

Source§

fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where + S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl Copy for Color

Source§

impl StructuralPartialEq for Color

Auto Trait Implementations§

§

impl Freeze for Color

§

impl RefUnwindSafe for Color

§

impl Send for Color

§

impl Sync for Color

§

impl Unpin for Color

§

impl UnwindSafe for Color

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/themes/color/fn.approx.html b/i3status_rs/themes/color/fn.approx.html new file mode 100644 index 0000000000..59ff81b617 --- /dev/null +++ b/i3status_rs/themes/color/fn.approx.html @@ -0,0 +1 @@ +approx in i3status_rs::themes::color - Rust

Function approx

Source
pub fn approx(a: f64, b: f64) -> bool
\ No newline at end of file diff --git a/i3status_rs/themes/color/index.html b/i3status_rs/themes/color/index.html new file mode 100644 index 0000000000..8588065701 --- /dev/null +++ b/i3status_rs/themes/color/index.html @@ -0,0 +1 @@ +i3status_rs::themes::color - Rust

Module color

Source

Structs§

Hsva
An HSVA color (hue, saturation, value, alpha).
Rgba
An RGBA color (red, green, blue, alpha).

Enums§

Color

Functions§

approx
\ No newline at end of file diff --git a/i3status_rs/themes/color/sidebar-items.js b/i3status_rs/themes/color/sidebar-items.js new file mode 100644 index 0000000000..fd32a45be1 --- /dev/null +++ b/i3status_rs/themes/color/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Color"],"fn":["approx"],"struct":["Hsva","Rgba"]}; \ No newline at end of file diff --git a/i3status_rs/themes/color/struct.Hsva.html b/i3status_rs/themes/color/struct.Hsva.html new file mode 100644 index 0000000000..192e6d9c0a --- /dev/null +++ b/i3status_rs/themes/color/struct.Hsva.html @@ -0,0 +1,47 @@ +Hsva in i3status_rs::themes::color - Rust

Struct Hsva

Source
pub struct Hsva {
+    pub h: f64,
+    pub s: f64,
+    pub v: f64,
+    pub a: u8,
+}
Expand description

An HSVA color (hue, saturation, value, alpha).

+

Fields§

§h: f64§s: f64§v: f64§a: u8

Implementations§

Source§

impl Hsva

Source

pub fn new(h: f64, s: f64, v: f64, a: u8) -> Self

Create a new HSVA color.

+

h: hue component (0 to 360)

+

s: saturation component (0 to 1)

+

v: value component (0 to 1)

+

a: alpha component (0 to 255).

+

Trait Implementations§

Source§

impl Add for Hsva

Source§

type Output = Hsva

The resulting type after applying the + operator.
Source§

fn add(self, rhs: Self) -> Self::Output

Performs the + operation. Read more
Source§

impl Clone for Hsva

Source§

fn clone(&self) -> Hsva

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Hsva

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Hsva

Source§

fn default() -> Hsva

Returns the “default value” for a type. Read more
Source§

impl From<Hsva> for Rgba

Source§

fn from(hsva: Hsva) -> Self

Converts to this type from the input type.
Source§

impl From<Rgba> for Hsva

Source§

fn from(rgba: Rgba) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for Hsva

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for Hsva

Auto Trait Implementations§

§

impl Freeze for Hsva

§

impl RefUnwindSafe for Hsva

§

impl Send for Hsva

§

impl Sync for Hsva

§

impl Unpin for Hsva

§

impl UnwindSafe for Hsva

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/themes/color/struct.Rgba.html b/i3status_rs/themes/color/struct.Rgba.html new file mode 100644 index 0000000000..30ac7cc0db --- /dev/null +++ b/i3status_rs/themes/color/struct.Rgba.html @@ -0,0 +1,53 @@ +Rgba in i3status_rs::themes::color - Rust

Struct Rgba

Source
pub struct Rgba {
+    pub r: u8,
+    pub g: u8,
+    pub b: u8,
+    pub a: u8,
+}
Expand description

An RGBA color (red, green, blue, alpha).

+

Fields§

§r: u8§g: u8§b: u8§a: u8

Implementations§

Source§

impl Rgba

Source

pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self

Create a new RGBA color.

+

r: red component (0 to 255).

+

g: green component (0 to 255).

+

b: blue component (0 to 255).

+

a: alpha component (0 to 255).

+
Source

pub fn from_hex(hex: u32) -> Self

Create a new RGBA color from the hex value.

+

let cyan = Rgba::from_hex(0xffffff);

+

Trait Implementations§

Source§

impl Add for Rgba

Source§

type Output = Rgba

The resulting type after applying the + operator.
Source§

fn add(self, rhs: Self) -> Self::Output

Performs the + operation. Read more
Source§

impl Clone for Rgba

Source§

fn clone(&self) -> Rgba

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Rgba

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Rgba

Source§

fn default() -> Rgba

Returns the “default value” for a type. Read more
Source§

impl From<Hsva> for Rgba

Source§

fn from(hsva: Hsva) -> Self

Converts to this type from the input type.
Source§

impl From<Rgba> for Hsva

Source§

fn from(rgba: Rgba) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for Rgba

Source§

fn eq(&self, other: &Rgba) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for Rgba

Source§

impl Eq for Rgba

Source§

impl StructuralPartialEq for Rgba

Auto Trait Implementations§

§

impl Freeze for Rgba

§

impl RefUnwindSafe for Rgba

§

impl Send for Rgba

§

impl Sync for Rgba

§

impl Unpin for Rgba

§

impl UnwindSafe for Rgba

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/themes/enum.ColorOrLink.html b/i3status_rs/themes/enum.ColorOrLink.html new file mode 100644 index 0000000000..5e65ab40bb --- /dev/null +++ b/i3status_rs/themes/enum.ColorOrLink.html @@ -0,0 +1,41 @@ +ColorOrLink in i3status_rs::themes - Rust

Enum ColorOrLink

Source
pub enum ColorOrLink {
+    Color(Color),
+    Link {
+        link: String,
+    },
+}

Variants§

§

Color(Color)

Fields

§link: String

Trait Implementations§

Source§

fn clone(&self) -> ColorOrLink

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/themes/index.html b/i3status_rs/themes/index.html new file mode 100644 index 0000000000..3310198b5d --- /dev/null +++ b/i3status_rs/themes/index.html @@ -0,0 +1 @@ +i3status_rs::themes - Rust
\ No newline at end of file diff --git a/i3status_rs/themes/separator/enum.Separator.html b/i3status_rs/themes/separator/enum.Separator.html new file mode 100644 index 0000000000..715e44e81b --- /dev/null +++ b/i3status_rs/themes/separator/enum.Separator.html @@ -0,0 +1,46 @@ +Separator in i3status_rs::themes::separator - Rust

Enum Separator

Source
pub enum Separator {
+    Native,
+    Custom(String),
+}

Variants§

§

Native

§

Custom(String)

Trait Implementations§

Source§

impl Clone for Separator

Source§

fn clone(&self) -> Separator

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Separator

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Separator

Source§

fn default() -> Self

Return Separator::Native

+
Source§

impl<'de> Deserialize<'de> for Separator

Source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl FromStr for Separator

Source§

type Err = Error

The associated error which can be returned from parsing.
Source§

fn from_str(separator: &str) -> Result<Self, Self::Err>

Parses a string s to return a value of this type. Read more
Source§

impl PartialEq for Separator

Source§

fn eq(&self, other: &Separator) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for Separator

Source§

impl StructuralPartialEq for Separator

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/themes/separator/index.html b/i3status_rs/themes/separator/index.html new file mode 100644 index 0000000000..3822fc7ad3 --- /dev/null +++ b/i3status_rs/themes/separator/index.html @@ -0,0 +1 @@ +i3status_rs::themes::separator - Rust

Module separator

Source

Enums§

Separator
\ No newline at end of file diff --git a/i3status_rs/themes/separator/sidebar-items.js b/i3status_rs/themes/separator/sidebar-items.js new file mode 100644 index 0000000000..3137fe3bb8 --- /dev/null +++ b/i3status_rs/themes/separator/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Separator"]}; \ No newline at end of file diff --git a/i3status_rs/themes/sidebar-items.js b/i3status_rs/themes/sidebar-items.js new file mode 100644 index 0000000000..d522b20bc8 --- /dev/null +++ b/i3status_rs/themes/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ColorOrLink"],"mod":["color","separator","xresources"],"struct":["Theme","ThemeInner","ThemeOverrides","ThemeUserConfig"]}; \ No newline at end of file diff --git a/i3status_rs/themes/struct.Theme.html b/i3status_rs/themes/struct.Theme.html new file mode 100644 index 0000000000..a8d37053d1 --- /dev/null +++ b/i3status_rs/themes/struct.Theme.html @@ -0,0 +1,37 @@ +Theme in i3status_rs::themes - Rust

Struct Theme

Source
pub struct Theme(pub ThemeInner);

Tuple Fields§

§0: ThemeInner

Implementations§

Source§

impl Theme

Source

pub fn get_colors(&self, state: State) -> (Color, Color)

Source

pub fn apply_overrides(&mut self, overrides: ThemeOverrides) -> Result<()>

Trait Implementations§

Source§

impl Clone for Theme

Source§

fn clone(&self) -> Theme

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Theme

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Theme

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Deref for Theme

Source§

type Target = ThemeInner

The resulting type after dereferencing.
Source§

fn deref(&self) -> &Self::Target

Dereferences the value.
Source§

impl DerefMut for Theme

Source§

fn deref_mut(&mut self) -> &mut Self::Target

Mutably dereferences the value.
Source§

impl TryFrom<ThemeUserConfig> for Theme

Source§

type Error = Error

The type returned in the event of a conversion error.
Source§

fn try_from(user_config: ThemeUserConfig) -> Result<Self, Self::Error>

Performs the conversion.

Auto Trait Implementations§

§

impl Freeze for Theme

§

impl RefUnwindSafe for Theme

§

impl Send for Theme

§

impl Sync for Theme

§

impl Unpin for Theme

§

impl UnwindSafe for Theme

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<P, T> Receiver for P
where + P: Deref<Target = T> + ?Sized, + T: ?Sized,

Source§

type Target = T

🔬This is a nightly-only experimental API. (arbitrary_self_types)
The target type on which the method may be called.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/themes/struct.ThemeInner.html b/i3status_rs/themes/struct.ThemeInner.html new file mode 100644 index 0000000000..f2a832efa2 --- /dev/null +++ b/i3status_rs/themes/struct.ThemeInner.html @@ -0,0 +1,56 @@ +ThemeInner in i3status_rs::themes - Rust

Struct ThemeInner

Source
pub struct ThemeInner {
Show 17 fields + pub idle_bg: Color, + pub idle_fg: Color, + pub info_bg: Color, + pub info_fg: Color, + pub good_bg: Color, + pub good_fg: Color, + pub warning_bg: Color, + pub warning_fg: Color, + pub critical_bg: Color, + pub critical_fg: Color, + pub separator: Separator, + pub separator_bg: Color, + pub separator_fg: Color, + pub alternating_tint_bg: Color, + pub alternating_tint_fg: Color, + pub end_separator: Separator, + pub start_separator: Separator, +
}

Fields§

§idle_bg: Color§idle_fg: Color§info_bg: Color§info_fg: Color§good_bg: Color§good_fg: Color§warning_bg: Color§warning_fg: Color§critical_bg: Color§critical_fg: Color§separator: Separator§separator_bg: Color§separator_fg: Color§alternating_tint_bg: Color§alternating_tint_fg: Color§end_separator: Separator§start_separator: Separator

Trait Implementations§

Source§

impl Clone for ThemeInner

Source§

fn clone(&self) -> ThemeInner

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ThemeInner

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ThemeInner

Source§

fn default() -> ThemeInner

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for ThemeInner

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/themes/struct.ThemeOverrides.html b/i3status_rs/themes/struct.ThemeOverrides.html new file mode 100644 index 0000000000..c388f828f8 --- /dev/null +++ b/i3status_rs/themes/struct.ThemeOverrides.html @@ -0,0 +1,55 @@ +ThemeOverrides in i3status_rs::themes - Rust

Struct ThemeOverrides

Source
pub struct ThemeOverrides {
Show 17 fields + pub idle_bg: Option<ColorOrLink>, + pub idle_fg: Option<ColorOrLink>, + pub info_bg: Option<ColorOrLink>, + pub info_fg: Option<ColorOrLink>, + pub good_bg: Option<ColorOrLink>, + pub good_fg: Option<ColorOrLink>, + pub warning_bg: Option<ColorOrLink>, + pub warning_fg: Option<ColorOrLink>, + pub critical_bg: Option<ColorOrLink>, + pub critical_fg: Option<ColorOrLink>, + pub separator: Option<Separator>, + pub separator_bg: Option<ColorOrLink>, + pub separator_fg: Option<ColorOrLink>, + pub alternating_tint_bg: Option<ColorOrLink>, + pub alternating_tint_fg: Option<ColorOrLink>, + pub end_separator: Option<Separator>, + pub start_separator: Option<Separator>, +
}

Fields§

§idle_bg: Option<ColorOrLink>§idle_fg: Option<ColorOrLink>§info_bg: Option<ColorOrLink>§info_fg: Option<ColorOrLink>§good_bg: Option<ColorOrLink>§good_fg: Option<ColorOrLink>§warning_bg: Option<ColorOrLink>§warning_fg: Option<ColorOrLink>§critical_bg: Option<ColorOrLink>§critical_fg: Option<ColorOrLink>§separator: Option<Separator>§separator_bg: Option<ColorOrLink>§separator_fg: Option<ColorOrLink>§alternating_tint_bg: Option<ColorOrLink>§alternating_tint_fg: Option<ColorOrLink>§end_separator: Option<Separator>§start_separator: Option<Separator>

Trait Implementations§

Source§

impl Clone for ThemeOverrides

Source§

fn clone(&self) -> ThemeOverrides

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ThemeOverrides

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ThemeOverrides

Source§

fn default() -> ThemeOverrides

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for ThemeOverrides

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/themes/struct.ThemeUserConfig.html b/i3status_rs/themes/struct.ThemeUserConfig.html new file mode 100644 index 0000000000..2cc7faef42 --- /dev/null +++ b/i3status_rs/themes/struct.ThemeUserConfig.html @@ -0,0 +1,39 @@ +ThemeUserConfig in i3status_rs::themes - Rust

Struct ThemeUserConfig

Source
pub struct ThemeUserConfig {
+    pub theme: Option<String>,
+    pub overrides: Option<ThemeOverrides>,
+}

Fields§

§theme: Option<String>§overrides: Option<ThemeOverrides>

Trait Implementations§

Source§

impl Default for ThemeUserConfig

Source§

fn default() -> ThemeUserConfig

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for ThemeUserConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl TryFrom<ThemeUserConfig> for Theme

Source§

type Error = Error

The type returned in the event of a conversion error.
Source§

fn try_from(user_config: ThemeUserConfig) -> Result<Self, Self::Error>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/themes/xresources/fn.get_color.html b/i3status_rs/themes/xresources/fn.get_color.html new file mode 100644 index 0000000000..b61c43b074 --- /dev/null +++ b/i3status_rs/themes/xresources/fn.get_color.html @@ -0,0 +1 @@ +get_color in i3status_rs::themes::xresources - Rust

Function get_color

Source
pub fn get_color(name: &str) -> Result<Option<&String>, Error>
\ No newline at end of file diff --git a/i3status_rs/themes/xresources/index.html b/i3status_rs/themes/xresources/index.html new file mode 100644 index 0000000000..067055c4ed --- /dev/null +++ b/i3status_rs/themes/xresources/index.html @@ -0,0 +1 @@ +i3status_rs::themes::xresources - Rust

Module xresources

Source

Functions§

get_color
\ No newline at end of file diff --git a/i3status_rs/themes/xresources/sidebar-items.js b/i3status_rs/themes/xresources/sidebar-items.js new file mode 100644 index 0000000000..14aa008a20 --- /dev/null +++ b/i3status_rs/themes/xresources/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["get_color"]}; \ No newline at end of file diff --git a/i3status_rs/util/fn.country_flag_from_iso_code.html b/i3status_rs/util/fn.country_flag_from_iso_code.html new file mode 100644 index 0000000000..c4912c22c5 --- /dev/null +++ b/i3status_rs/util/fn.country_flag_from_iso_code.html @@ -0,0 +1,2 @@ +country_flag_from_iso_code in i3status_rs::util - Rust

Function country_flag_from_iso_code

Source
pub fn country_flag_from_iso_code(country_code: &str) -> String
Expand description

Convert 2 letter country code to Unicode

+
\ No newline at end of file diff --git a/i3status_rs/util/fn.default.html b/i3status_rs/util/fn.default.html new file mode 100644 index 0000000000..269d0f8f25 --- /dev/null +++ b/i3status_rs/util/fn.default.html @@ -0,0 +1,3 @@ +default in i3status_rs::util - Rust

Function default

Source
pub fn default<T: Default>() -> T
Expand description

A shortcut for Default::default() +See https://github.com/rust-lang/rust/issues/73014

+
\ No newline at end of file diff --git a/i3status_rs/util/fn.deserialize_toml_file.html b/i3status_rs/util/fn.deserialize_toml_file.html new file mode 100644 index 0000000000..876c667063 --- /dev/null +++ b/i3status_rs/util/fn.deserialize_toml_file.html @@ -0,0 +1,3 @@ +deserialize_toml_file in i3status_rs::util - Rust

Function deserialize_toml_file

Source
pub fn deserialize_toml_file<T, P>(path: P) -> Result<T>
where + T: DeserializeOwned, + P: AsRef<Path>,
\ No newline at end of file diff --git a/i3status_rs/util/fn.find_file.html b/i3status_rs/util/fn.find_file.html new file mode 100644 index 0000000000..efc474b187 --- /dev/null +++ b/i3status_rs/util/fn.find_file.html @@ -0,0 +1,13 @@ +find_file in i3status_rs::util - Rust

Function find_file

Source
pub fn find_file(
+    file: &str,
+    subdir: Option<&str>,
+    extension: Option<&str>,
+) -> Option<PathBuf>
Expand description

Tries to find a file in standard locations:

+
    +
  • Fist try to find a file by full path (only if path is absolute)
  • +
  • Then try XDG_CONFIG_HOME (e.g. ~/.config)
  • +
  • Then try XDG_DATA_HOME (e.g. ~/.local/share/)
  • +
  • Then try /usr/share/
  • +
+

Automatically append an extension if not presented.

+
\ No newline at end of file diff --git a/i3status_rs/util/fn.format_bar_graph.html b/i3status_rs/util/fn.format_bar_graph.html new file mode 100644 index 0000000000..62a25d4e1a --- /dev/null +++ b/i3status_rs/util/fn.format_bar_graph.html @@ -0,0 +1 @@ +format_bar_graph in i3status_rs::util - Rust

Function format_bar_graph

Source
pub fn format_bar_graph(content: &[f64]) -> String
\ No newline at end of file diff --git a/i3status_rs/util/fn.has_command.html b/i3status_rs/util/fn.has_command.html new file mode 100644 index 0000000000..e976ff8347 --- /dev/null +++ b/i3status_rs/util/fn.has_command.html @@ -0,0 +1 @@ +has_command in i3status_rs::util - Rust

Function has_command

Source
pub async fn has_command(command: &str) -> Result<bool>
\ No newline at end of file diff --git a/i3status_rs/util/fn.new_dbus_connection.html b/i3status_rs/util/fn.new_dbus_connection.html new file mode 100644 index 0000000000..b6bcaded96 --- /dev/null +++ b/i3status_rs/util/fn.new_dbus_connection.html @@ -0,0 +1 @@ +new_dbus_connection in i3status_rs::util - Rust

Function new_dbus_connection

Source
pub async fn new_dbus_connection() -> Result<Connection>
\ No newline at end of file diff --git a/i3status_rs/util/fn.new_system_dbus_connection.html b/i3status_rs/util/fn.new_system_dbus_connection.html new file mode 100644 index 0000000000..5a98197b9b --- /dev/null +++ b/i3status_rs/util/fn.new_system_dbus_connection.html @@ -0,0 +1 @@ +new_system_dbus_connection in i3status_rs::util - Rust

Function new_system_dbus_connection

Source
pub async fn new_system_dbus_connection() -> Result<Connection>
\ No newline at end of file diff --git a/i3status_rs/util/fn.read_file.html b/i3status_rs/util/fn.read_file.html new file mode 100644 index 0000000000..572b492d50 --- /dev/null +++ b/i3status_rs/util/fn.read_file.html @@ -0,0 +1 @@ +read_file in i3status_rs::util - Rust

Function read_file

Source
pub async fn read_file(path: impl AsRef<Path>) -> Result<String>
\ No newline at end of file diff --git a/i3status_rs/util/index.html b/i3status_rs/util/index.html new file mode 100644 index 0000000000..0647ba89b3 --- /dev/null +++ b/i3status_rs/util/index.html @@ -0,0 +1,2 @@ +i3status_rs::util - Rust

Module util

Source

Macros§

map
Example

Traits§

StreamExtDebounced

Functions§

country_flag_from_iso_code
Convert 2 letter country code to Unicode
default
A shortcut for Default::default() +See https://github.com/rust-lang/rust/issues/73014
deserialize_toml_file
find_file
Tries to find a file in standard locations:
format_bar_graph
has_command
new_dbus_connection
new_system_dbus_connection
read_file
\ No newline at end of file diff --git a/i3status_rs/util/macro.map!.html b/i3status_rs/util/macro.map!.html new file mode 100644 index 0000000000..515cd23ef6 --- /dev/null +++ b/i3status_rs/util/macro.map!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.map.html...

+ + + \ No newline at end of file diff --git a/i3status_rs/util/macro.map.html b/i3status_rs/util/macro.map.html new file mode 100644 index 0000000000..81fef1b773 --- /dev/null +++ b/i3status_rs/util/macro.map.html @@ -0,0 +1,19 @@ +map in i3status_rs::util - Rust

Macro map

Source
macro_rules! map {
+    (@extend $map:ident $( $([$($cond_tokens:tt)*])? $key:literal => $value:expr ),* $(,)?) => { ... };
+    (@extend $map:ident $( $key:expr => $value:expr ),* $(,)?) => { ... };
+    (@insert $map:ident, $key:expr, $value:expr) => { ... };
+    (@insert $map:ident, $key:expr, $value:expr, if $cond:expr) => { ... };
+    (@insert $map:ident, $key:expr, $value:expr, if let $pat:pat = $match_on:expr) => { ... };
+    ($($tt:tt)*) => { ... };
+}
Expand description

§Example

+
let opt = Some(1);
+let m: HashMap<&'static str, String> = map! {
+    "key" => "value",
+    [if true] "hello" => "world",
+    [if let Some(x) = opt] "opt" => x.to_string(),
+};
+map! { @extend m
+    "new key" => "new value",
+    "one" => "more",
+}
+
\ No newline at end of file diff --git a/i3status_rs/util/sidebar-items.js b/i3status_rs/util/sidebar-items.js new file mode 100644 index 0000000000..70cd868886 --- /dev/null +++ b/i3status_rs/util/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["country_flag_from_iso_code","default","deserialize_toml_file","find_file","format_bar_graph","has_command","new_dbus_connection","new_system_dbus_connection","read_file"],"macro":["map"],"trait":["StreamExtDebounced"]}; \ No newline at end of file diff --git a/i3status_rs/util/trait.StreamExtDebounced.html b/i3status_rs/util/trait.StreamExtDebounced.html new file mode 100644 index 0000000000..20c1a7f71a --- /dev/null +++ b/i3status_rs/util/trait.StreamExtDebounced.html @@ -0,0 +1,4 @@ +StreamExtDebounced in i3status_rs::util - Rust

Trait StreamExtDebounced

Source
pub trait StreamExtDebounced: StreamExt {
+    // Required method
+    fn next_debounced(&mut self) -> impl Future<Output = Option<Self::Item>>;
+}

Required Methods§

Source

fn next_debounced(&mut self) -> impl Future<Output = Option<Self::Item>>

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§

Source§

impl<T: StreamExt + Unpin> StreamExtDebounced for T

\ No newline at end of file diff --git a/i3status_rs/widget/enum.State.html b/i3status_rs/widget/enum.State.html new file mode 100644 index 0000000000..f4956cdf91 --- /dev/null +++ b/i3status_rs/widget/enum.State.html @@ -0,0 +1,50 @@ +State in i3status_rs::widget - Rust

Enum State

Source
pub enum State {
+    Idle,
+    Info,
+    Good,
+    Warning,
+    Critical,
+}
Expand description

State of the widget. Affects the theming.

+

Variants§

§

Idle

§

Info

§

Good

§

Warning

§

Critical

Trait Implementations§

Source§

impl Clone for State

Source§

fn clone(&self) -> State

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for State

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for State

Source§

fn default() -> Self

Return State::Idle

+
Source§

impl<'de> Deserialize<'de> for State

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for State

Source§

fn eq(&self, other: &State) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for State

Source§

impl Eq for State

Source§

impl StructuralPartialEq for State

Auto Trait Implementations§

§

impl Freeze for State

§

impl RefUnwindSafe for State

§

impl Send for State

§

impl Sync for State

§

impl Unpin for State

§

impl UnwindSafe for State

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where + T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/i3status_rs/widget/index.html b/i3status_rs/widget/index.html new file mode 100644 index 0000000000..1e2dcaac20 --- /dev/null +++ b/i3status_rs/widget/index.html @@ -0,0 +1 @@ +i3status_rs::widget - Rust

Module widget

Source

Structs§

Widget

Enums§

State
State of the widget. Affects the theming.
\ No newline at end of file diff --git a/i3status_rs/widget/sidebar-items.js b/i3status_rs/widget/sidebar-items.js new file mode 100644 index 0000000000..5d0815e40b --- /dev/null +++ b/i3status_rs/widget/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["State"],"struct":["Widget"]}; \ No newline at end of file diff --git a/i3status_rs/widget/struct.Widget.html b/i3status_rs/widget/struct.Widget.html new file mode 100644 index 0000000000..bc273eceda --- /dev/null +++ b/i3status_rs/widget/struct.Widget.html @@ -0,0 +1,43 @@ +Widget in i3status_rs::widget - Rust

Struct Widget

Source
pub struct Widget {
+    pub state: State,
+    /* private fields */
+}

Fields§

§state: State

Implementations§

Source§

impl Widget

Source

pub fn new() -> Self

Source

pub fn with_text(self, text: String) -> Self

Source

pub fn with_state(self, state: State) -> Self

Source

pub fn with_format(self, format: Format) -> Self

Source

pub fn set_text(&mut self, text: String)

Source

pub fn set_format(&mut self, format: Format)

Source

pub fn set_values(&mut self, new_values: Values)

Source

pub fn intervals(&self) -> Vec<u64>

Source

pub fn get_data( + &self, + shared_config: &SharedConfig, + id: usize, +) -> Result<Vec<I3BarBlock>>

Construct I3BarBlock from this widget

+

Trait Implementations§

Source§

impl Clone for Widget

Source§

fn clone(&self) -> Widget

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Widget

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Widget

Source§

fn default() -> Widget

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl Freeze for Widget

§

impl !RefUnwindSafe for Widget

§

impl Send for Widget

§

impl Sync for Widget

§

impl Unpin for Widget

§

impl !UnwindSafe for Widget

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
§

impl<T> NoneValue for T
where + T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> PolicyExt for T
where + T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return +Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where + T: Policy<B, E>, + P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns +Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where + V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

impl<T> ErasedDestructor for T
where + T: 'static,

§

impl<T> MaybeSendSync for T

\ No newline at end of file diff --git a/img/example_bar.png b/img/example_bar.png deleted file mode 100644 index 8ff0debfa3..0000000000 Binary files a/img/example_bar.png and /dev/null differ diff --git a/img/themes/bad-wolf.png b/img/themes/bad-wolf.png deleted file mode 100644 index bbe2b2c22d..0000000000 Binary files a/img/themes/bad-wolf.png and /dev/null differ diff --git a/img/themes/ctp-frappe.png b/img/themes/ctp-frappe.png deleted file mode 100644 index 091fe9eadb..0000000000 Binary files a/img/themes/ctp-frappe.png and /dev/null differ diff --git a/img/themes/ctp-latte.png b/img/themes/ctp-latte.png deleted file mode 100644 index eb76b43a39..0000000000 Binary files a/img/themes/ctp-latte.png and /dev/null differ diff --git a/img/themes/ctp-macchiato.png b/img/themes/ctp-macchiato.png deleted file mode 100644 index 9089c5a3d6..0000000000 Binary files a/img/themes/ctp-macchiato.png and /dev/null differ diff --git a/img/themes/ctp-mocha.png b/img/themes/ctp-mocha.png deleted file mode 100644 index f613e1b8a9..0000000000 Binary files a/img/themes/ctp-mocha.png and /dev/null differ diff --git a/img/themes/dracula.png b/img/themes/dracula.png deleted file mode 100644 index 7b1f7bd6bc..0000000000 Binary files a/img/themes/dracula.png and /dev/null differ diff --git a/img/themes/gruvbox-dark.png b/img/themes/gruvbox-dark.png deleted file mode 100644 index 891814cf52..0000000000 Binary files a/img/themes/gruvbox-dark.png and /dev/null differ diff --git a/img/themes/gruvbox-light.png b/img/themes/gruvbox-light.png deleted file mode 100644 index 556a202418..0000000000 Binary files a/img/themes/gruvbox-light.png and /dev/null differ diff --git a/img/themes/modern.png b/img/themes/modern.png deleted file mode 100644 index 042e0e908d..0000000000 Binary files a/img/themes/modern.png and /dev/null differ diff --git a/img/themes/native.png b/img/themes/native.png deleted file mode 100644 index 91bf8f9703..0000000000 Binary files a/img/themes/native.png and /dev/null differ diff --git a/img/themes/nord-dark.png b/img/themes/nord-dark.png deleted file mode 100644 index 4d7ac20d89..0000000000 Binary files a/img/themes/nord-dark.png and /dev/null differ diff --git a/img/themes/plain.png b/img/themes/plain.png deleted file mode 100644 index 02bf9ba44b..0000000000 Binary files a/img/themes/plain.png and /dev/null differ diff --git a/img/themes/semi-native.png b/img/themes/semi-native.png deleted file mode 100644 index 25cc52f67f..0000000000 Binary files a/img/themes/semi-native.png and /dev/null differ diff --git a/img/themes/slick.png b/img/themes/slick.png deleted file mode 100644 index 4e6c4e63eb..0000000000 Binary files a/img/themes/slick.png and /dev/null differ diff --git a/img/themes/solarized-dark.png b/img/themes/solarized-dark.png deleted file mode 100644 index d0da6dafb4..0000000000 Binary files a/img/themes/solarized-dark.png and /dev/null differ diff --git a/img/themes/solarized-light.png b/img/themes/solarized-light.png deleted file mode 100644 index e38df850df..0000000000 Binary files a/img/themes/solarized-light.png and /dev/null differ diff --git a/img/themes/space-villain.png b/img/themes/space-villain.png deleted file mode 100644 index ba5996802f..0000000000 Binary files a/img/themes/space-villain.png and /dev/null differ diff --git a/img/themes/srcery.png b/img/themes/srcery.png deleted file mode 100644 index 33c8f413e5..0000000000 Binary files a/img/themes/srcery.png and /dev/null differ diff --git a/index.html b/index.html new file mode 100644 index 0000000000..378afe93f5 --- /dev/null +++ b/index.html @@ -0,0 +1 @@ + diff --git a/install.sh b/install.sh deleted file mode 100755 index c3279fae19..0000000000 --- a/install.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -# Use this script when installing via `cargo` in order to be able to use the -# default icons/themes. If installed via a package manager you do not need to -# run this script. - -set -x - -XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} - -# Themes -mkdir -p $XDG_DATA_HOME/i3status-rust -cp -r files/* $XDG_DATA_HOME/i3status-rust/ - -# Manpage -cargo xtask generate-manpage -mkdir -p $XDG_DATA_HOME/man/man1/ -cp man/i3status-rs.1 $XDG_DATA_HOME/man/man1/i3status-rs.1 diff --git a/release-process.md b/release-process.md deleted file mode 100644 index 08646fcdba..0000000000 --- a/release-process.md +++ /dev/null @@ -1,5 +0,0 @@ -- Make sure NEWS.md contains changelog for users -- Bump version in Cargo.toml and rebuild to update Cargo.lock -- Draft new release on GitHub and create new tag for the release (make sure to prepend "v") -- Release -- Publish to crates.io diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 1b32d8c0ab..0000000000 --- a/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -# This file is empty on purpose in order to enforce rustfmt default settings diff --git a/search-index.js b/search-index.js new file mode 100644 index 0000000000..918015d246 --- /dev/null +++ b/search-index.js @@ -0,0 +1,4 @@ +var searchIndex = new Map(JSON.parse('[["i3status_rs",{"t":"FFFNNNNNOCNNNNNNCNNCOECCNNCNNNNNCNCNNNQONONNNCNENCENNNNNNNNNNNCNNNCIGFPFPPCPNNNCPCPOOCPNNNNNNCPNNNCPCPCPNCPCPCPOCPNNNNCPNNNNCPNCPNNNCPCPCPCPCPCPCPNCPCPCPCPNNNCPCPCPCPCPCPNNNCPNCPCPCPCPCPNNCPNNNNNNNNNNCPCPNNNNCPCPCPFFNNNNNNNNONOONNONNNNNHNNNNNNNNFNNNOOONNONONNOOOONNOHONNNNPGFPPNNNNNNOONNNNOOOONNONNOOOOONNOOONNNNHNNNNNNNNOFONONNNONONNONHNNNNGPPFFGFPPPPFFPPPFPPONNNNNNNNOOONNNNNNNNNNNNNNNNOOOONNNNNNNNNNNNNNNNNNOOOONNNNNNNNNNNNNOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNOONNNNNNONNNNNNNNOOOHONONNNNNNNONNNNNNNNNNNNNNNNNNNNNNNNNOONNNNNNNNOFNNNONNNOONOONNNHNNNNOFNNNOONNNONOONONNOHONNNNOFNNNNNONNNOHNNNNFNNNNNONONONONNHNNNNPFPGPOONNNNNNNNNNNNNNNOONNOONNNNNNOHNNNNNNNNNOFNNNNNNONONNNHONNNNFNNNNNNONONNNHNNNONOPFGPPNNNNNNNNNNONNONNNNNNNNHNNNNNNNNFNNNONNNONOOOONNNHONNNNOFPGPPPPPNNNNNNONNNONNNNNONNOONNOONNNHONNNNNNNNNFNOOOONNNNOONONNONNHNNNNFPGPPPPPNNNNNNNNNNNNNONNONNONNONNNNHONNNNNNNNNFNNNONNNONOONNNHNNNNOPFPGPNNNNNNNNNNNNONNONNOONNNNNHOONNNNNNNNNFNNNOONNNOONONNNHNNNNOOFFNNNNNNNNNOONNONNNNNNONNHONNNNNNNNNFPGPNNNNNNNNNNNNNNNOONNONNNNNNOHOOOONNNNNNNONNFNNNNNONOONOONONNHNNNNFGPPNNNNNNNNNNONNONNNNNNNNHNNNNNNNNFNNNNNNONONONNOHOOOONNNNFNNNNNNONOOOOONNNHNNNNOPPKFPGPPNNCONNNNNNNNNNONNNCNNNOOONNMHOOONNMNNNOCHNNNNNNNNNNOCFNNNNNNNNNNNNNNNFNNNNNNNNNNNNNNNFJJFNNNNNNNNNNNNNNNNNNNNNNNNNNFNNNNNNNNNNNNNNNFNONNOONNNONNOONNOHNNNNFPGPNNNNNNNNONNOONNNNNNHNNNNNNNNFNNNNNNONONNNHONNNNFNNNNNNONNNNHNNNNFGPOONNNNNNNNNNONNNNOONNNNNNHONNNNNNONNPPFGPPGPONNNNNNNNNNNNNNNNNNNNNNOOONNNNNNNOONNNNONNNOOOOONNNNNNHOONNNNNNNNNNNNNNFNNNNNNONONNNHNNNNFFNNNNNNNNNOOONNNNOONNOOONNONNONNNNHNNNNNNNNNOFNNNNNONONONNNHNNNNPFPGNNNNNNONNNNNNNNNNNNNOONNNOOOOONNNNNNHONNNNNNNNNOFGPPNNNNNNNNNNNNNNONNONNNNNHONNNNNNNNNFNNNOOONNONOOONNHOONNNNFNNNNNNONONNNHNNNNFGPPNNNNNNNNNNONNOONNONNNNNNHOONNNNNNNNFNNNNNHNONONNNHOONNNNFPPPGNNOONNNNNNNNOONNONNCCCNNHONNNNNNNNFNNNNNNNNNNNNNNFNNNNNNNNNNNNNNFNNNNNHNNNNNNNNNFNNNNNNONONONNHONNNNPFFPPPPGFPPPPPONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNONNNNFFFFNNNNONNNNNNNNONNNOONNNNNNOOOOOONNNNNNNNONOOOOONNNNOONNNNNNOOOONNNNNNNNNNNNNNNNNFPFKPIKKNNNNNNNONNNNNNNNNNNNNMNNNNNNNNNNNNONNNMNNNNNNMNNNNNNNNNNNNNNNNKKNMNMFGFPFPPPINNNNNNNNNNNNNNNNNNNNNCNNNNNNNNNNNNNCNNNNNNONNNNNNOONNNNNNCCNCNCONNNNNNNNNNNNNNNNNOCCNNNNOOFNNNNNNNNNNNONNNONNNNNNNNNFPJSSSSGFFFKPFFFNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMNNNNNNNNNNNNNNNNNNNNNNNNNNHNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNOOOOFFFPFPPPGFNNNNNNONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNONNNNNNNNNNNNOOONNNNNNHNNNNNNNNNNNNNNNNNNNONNNNNNPPPPPPPPPPPGPPNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNHFPPPPGFNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNOOOPPPPPPPGPNNNNNNNNNNNNNNNNNNNNNNNNNPPPPKPPFGNNNNNNNNNNNNNNNNNNNNNNNNNNNONNMNONNNNNNNNNNNNNNNNNNNNNNOOFGFPPNNNONNNNNNONNNNNNOOOOOOOOOOOONNNNNNNNNNNNNNONNNOOOONNNNNNNOOOOONNNNNNNNNNNOONNNGFPPNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNCCHPFGGPPPPONNNOOOOOONNNNNNNNNNNNNNNONNNNNNNOONNNOOONNNNOONNNONNNNNNNNNNNNONNNFNNNONNNNNNNHNNOONNNNNNNPGPFFFFOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNCOOOONNNNNNNNNNOONNNNNNNNNNOOOOOOOOOOOONNNNNNNNNNNNNNOCOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNOOOOCOPGFPPFPOONNNNNNHONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNOONNNNNNNNNNNOONNNNNNNNNNNNNNONNNPPGNNNNNNNNNNNNNNNNNNNNNNNHKHHHHHHQHHMHPPPPGPFNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNONNNNNNNNNNNNN","n":["BarState","Block","CliArgs","and","","","augment_args","augment_args_for_update","blocking_threads","blocks","borrow","","","borrow_mut","","","click","command","command_for_update","config","","env_logger","errors","escape","fmt","","formatting","from","","","from_arg_matches","from_arg_matches_mut","geolocator","group_id","icons","into","","","map","never_pause","new","no_init","or","","","protocol","run_event_loop","serde_json","spawn_block","themes","tokio","try_from","","","try_into","","","type_id","","","update_from_arg_matches","update_from_arg_matches_mut","util","vzip","","","widget","BlockAction","BlockConfig","BlockError","Borrowed","CommonApi","Err","Owned","amd_gpu","","and","","","backlight","","battery","","block_id","block_name","bluetooth","","borrow","","","borrow_mut","","","calendar","","clone","clone_into","clone_to_uninit","cpu","","custom","","custom_dbus","","deserialize","disk_iostats","","disk_space","","docker","","error","external_ip","","find_ip_location","fmt","","","focused_window","","from","","","get_actions","github","","hide","hueshift","","into","","","kdeconnect","","keyboard_layout","","load","","maildir","","memory","","menu","","music","","name","net","","notify","","notmuch","","nvidia_gpu","","or","","","packages","","pomodoro","","privacy","","rofication","","scratchpad","","service_status","","set_default_actions","set_error","set_widget","sound","","spawn","speedtest","","taskwarrior","","tea_timer","","temperature","","time","","to_owned","to_string","toggle","","try_from","","","try_into","","","try_to_string","type_id","","","uptime","","vpn","","vzip","","","wait_for_update_request","watson","","weather","","xrandr","","Config","Device","and","","borrow","","borrow_mut","","default","deserialize","device","fmt","format","format_alt","from","","interval","into","","null_value","or","","run","try_from","","try_into","","type_id","","vzip","","Config","and","borrow","borrow_mut","cycle","ddcci_max_tries_write_read","ddcci_sleep_multiplier","default","deserialize","device","fmt","format","from","into","invert_icons","maximum","minimum","missing_format","null_value","or","root_scaling","run","step_width","try_from","try_into","type_id","vzip","ApcUps","BatteryDriver","Config","Sysfs","Upower","and","","borrow","","borrow_mut","","charging_format","critical","default","","deserialize","","device","driver","empty_format","empty_threshold","fmt","","format","from","","full_format","full_threshold","good","info","interval","into","","missing_format","model","not_charging_format","null_value","","or","","run","try_from","","try_into","","type_id","","vzip","","warning","Config","adapter_mac","and","battery_state","borrow","borrow_mut","deserialize","disconnected_format","fmt","format","from","into","mac","or","run","try_from","try_into","type_id","vzip","AuthConfig","AuthRequired","Basic","BasicAuthConfig","BasicCredentials","CalendarError","Config","Deserialize","Http","Io","OAuth2","OAuth2Config","OAuth2Credentials","Parsing","RequestToken","Serialize","SourceConfig","StoreToken","Unauthenticated","alternate_events_interval","and","","","","","","","","auth","auth_token","auth_url","borrow","","","","","","","","borrow_mut","","","","","","","","browser_cmd","calendars","client_id","client_secret","clone","","","","","","clone_into","","","","","","clone_to_uninit","","","","","","credentials","","credentials_path","","default","","","","","","deserialize","","","","","","","events_within_hours","fetch_interval","fmt","","","","","","","","","from","","","","","","","","","","","","into","","","","","","","","next_event_format","no_events_format","null_value","","","","","","ongoing_event_format","or","","","","","","","","password","redirect_format","redirect_port","run","scopes","source","","to_owned","","","","","","to_string","token_url","try_from","","","","","","","","try_into","","","","","","","","try_to_string","type_id","","","","","","","","url","username","vzip","","","","","","","","warning_threshold","Config","and","borrow","borrow_mut","critical_cpu","default","deserialize","fmt","format","format_alt","from","info_cpu","interval","into","null_value","or","run","try_from","try_into","type_id","vzip","warning_cpu","Config","and","borrow","borrow_mut","command","cycle","default","deserialize","fmt","format","from","hide_when_empty","interval","into","json","null_value","or","persistent","run","shell","try_from","try_into","type_id","vzip","watch_files","Config","and","borrow","borrow_mut","deserialize","fmt","format","from","into","or","path","run","try_from","try_into","type_id","vzip","Config","and","borrow","borrow_mut","default","deserialize","device","fmt","format","from","interval","into","missing_format","null_value","or","run","try_from","try_into","type_id","vzip","Available","Config","Free","InfoType","Used","alert","alert_unit","and","","borrow","","borrow_mut","","clone","clone_into","clone_to_uninit","default","","deserialize","","fmt","","format","format_alt","from","","info_type","interval","into","","null_value","","or","","path","run","to_owned","try_from","","try_into","","type_id","","vzip","","warning","Config","and","borrow","borrow_mut","default","deserialize","fmt","format","from","interval","into","null_value","or","run","socket_path","try_from","try_into","type_id","vzip","Config","and","borrow","borrow_mut","default","deserialize","fmt","format","from","interval","into","null_value","or","run","try_from","try_into","type_id","use_ipv4","vzip","with_network_manager","Auto","Config","Driver","SwayIpc","WlrToplevelManagement","and","","borrow","","borrow_mut","","default","","deserialize","","driver","fmt","","format","from","","into","","null_value","","or","","run","try_from","","try_into","","type_id","","vzip","","Config","and","borrow","borrow_mut","critical","default","deserialize","fmt","format","from","good","hide_if_total_is_zero","info","interval","into","null_value","or","run","token","try_from","try_into","type_id","vzip","warning","Config","Gammastep","HueShifter","Redshift","Sct","WlGammarelay","WlGammarelayRs","Wlsunset","and","","borrow","","borrow_mut","","click_temp","clone","clone_into","clone_to_uninit","current_temp","default","deserialize","","fmt","","format","from","","hue_shifter","interval","into","","max_temp","min_temp","null_value","or","","run","step","to_owned","try_from","","try_into","","type_id","","vzip","","Config","and","bat_critical","bat_good","bat_info","bat_warning","borrow","borrow_mut","default","deserialize","device_id","disconnected_format","fmt","format","from","into","missing_format","null_value","or","run","try_from","try_into","type_id","vzip","Config","KbddBus","KeyboardLayoutDriver","LocaleBus","SetXkbMap","Sway","XkbEvent","XkbSwitch","and","","borrow","","borrow_mut","","clone","clone_into","clone_to_uninit","default","","deserialize","","driver","fmt","","format","from","","interval","into","","mappings","null_value","","or","","run","sway_kb_identifier","to_owned","try_from","","try_into","","type_id","","vzip","","Config","and","borrow","borrow_mut","critical","default","deserialize","fmt","format","from","info","interval","into","null_value","or","run","try_from","try_into","type_id","vzip","warning","All","Config","Cur","MailType","New","and","","borrow","","borrow_mut","","clone","clone_into","clone_to_uninit","default","deserialize","","display_type","fmt","","format","from","","inboxes","interval","into","","null_value","or","","run","threshold_critical","threshold_warning","to_owned","try_from","","try_into","","type_id","","vzip","","Config","and","borrow","borrow_mut","critical_mem","critical_swap","default","deserialize","fmt","format","format_alt","from","interval","into","null_value","or","run","try_from","try_into","type_id","vzip","warning_mem","warning_swap","Config","Item","and","","borrow","","borrow_mut","","clone","clone_into","clone_to_uninit","cmd","confirm_msg","deserialize","","display","fmt","","from","","into","","items","or","","run","text","to_owned","try_from","","try_into","","type_id","","vzip","","Config","Multiple","PlayerName","Single","and","","borrow","","borrow_mut","","clone","clone_into","clone_to_uninit","default","","deserialize","","fmt","","format","format_alt","from","","interface_name_exclude","into","","null_value","","or","","player","run","seek_backward_step_secs","seek_forward_step_secs","seek_step_secs","separator","to_owned","try_from","","try_into","","type_id","","volume_step","vzip","","Config","and","borrow","borrow_mut","default","deserialize","device","fmt","format","format_alt","from","inactive_format","interval","into","missing_format","null_value","or","run","try_from","try_into","type_id","vzip","Config","DriverType","Dunst","SwayNC","and","","borrow","","borrow_mut","","default","","deserialize","","driver","fmt","","format","from","","into","","null_value","","or","","run","try_from","","try_into","","type_id","","vzip","","Config","and","borrow","borrow_mut","default","deserialize","fmt","format","from","interval","into","maildir","null_value","or","query","run","threshold_critical","threshold_good","threshold_info","threshold_warning","try_from","try_into","type_id","vzip","Config","and","borrow","borrow_mut","default","deserialize","fmt","format","from","good","gpu_id","idle","info","interval","into","null_value","or","run","try_from","try_into","type_id","vzip","warning","Apt","Aur","Backend","Config","Dnf","PackageManager","Pacman","Xbps","and","","apt","aur_command","borrow","","borrow_mut","","clone","","clone_into","","clone_to_uninit","","critical_updates_regex","default","deserialize","","dnf","eq","fmt","","format","format_singular","format_up_to_date","from","","get_updates_list","has_matching_update","ignore_phased_updates","ignore_updates_regex","interval","into","","name","null_value","or","","package_manager","pacman","run","to_owned","","try_from","","try_into","","type_id","","vzip","","warning_updates_regex","xbps","Apt","and","borrow","borrow_mut","default","from","get_updates_list","into","name","new","null_value","or","try_from","try_into","type_id","vzip","Dnf","and","borrow","borrow_mut","default","from","get_updates_list","into","name","new","null_value","or","try_from","try_into","type_id","vzip","Aur","PACMAN_DB","PACMAN_UPDATES_DB","Pacman","and","","borrow","","borrow_mut","","from","","get_updates_list","","into","","name","","new","","or","","try_from","","try_into","","type_id","","vzip","","Xbps","and","borrow","borrow_mut","default","from","get_updates_list","into","name","new","null_value","or","try_from","try_into","type_id","vzip","Config","and","blocking_cmd","borrow","borrow_mut","break_format","break_message","default","deserialize","fmt","format","from","into","message","notify_cmd","null_value","or","pomodoro_format","run","try_from","try_into","type_id","vzip","Config","Pipewire","PrivacyDriver","V4l","and","","borrow","","borrow_mut","","deserialize","","driver","fmt","","format","format_alt","from","","into","","or","","run","try_from","","try_into","","type_id","","vzip","","Config","and","borrow","borrow_mut","default","deserialize","fmt","format","from","interval","into","null_value","or","run","socket_path","try_from","try_into","type_id","vzip","Config","and","borrow","borrow_mut","default","deserialize","fmt","format","from","into","null_value","or","run","try_from","try_into","type_id","vzip","Config","DriverType","Systemd","active_format","active_state","and","","borrow","","borrow_mut","","default","","deserialize","","driver","fmt","","from","","inactive_format","inactive_state","into","","null_value","","or","","run","service","try_from","","try_into","","type_id","","user","vzip","","Alsa","Auto","Config","DeviceKind","PulseAudio","Sink","SoundDriver","Source","active_port_mappings","and","","","borrow","","","borrow_mut","","","clone","","clone_into","","clone_to_uninit","","default","","","default_name","deserialize","","","device","device_kind","driver","eq","equivalent","","","fmt","","","format","format_alt","from","","","hash","headphones_indicator","into","","","mappings","mappings_use_regex","max_vol","name","natural_mapping","null_value","","","or","","","run","show_volume_when_muted","step_width","to_owned","","try_from","","","try_into","","","type_id","","","vzip","","","Config","and","borrow","borrow_mut","default","deserialize","fmt","format","from","interval","into","null_value","or","run","try_from","try_into","type_id","vzip","Config","Filter","and","","borrow","","borrow_mut","","clone","clone_into","clone_to_uninit","config_override","critical_threshold","data_location","default","","deserialize","","filter","filters","fmt","","format","format_everything_done","format_singular","from","","interval","into","","name","null_value","","or","","run","to_owned","try_from","","try_into","","type_id","","vzip","","warning_threshold","Config","and","borrow","borrow_mut","default","deserialize","done_cmd","fmt","format","from","increment","into","null_value","or","run","try_from","try_into","type_id","vzip","Celsius","Config","Fahrenheit","TemperatureScale","and","","borrow","","borrow_mut","","chip","clone","clone_into","clone_to_uninit","default","","deserialize","","eq","equivalent","","","fmt","","format","format_alt","from","","from_celsius","good","idle","info","inputs","interval","into","","null_value","","or","","run","scale","to_owned","try_from","","try_into","","type_id","","vzip","","warning","Config","Timezone","","Timezones","and","","borrow","","borrow_mut","","clone","clone_into","clone_to_uninit","default","deserialize","","fmt","","format","from","","interval","into","","null_value","or","","run","timezone","to_owned","try_from","","try_into","","type_id","","vzip","","Config","and","borrow","borrow_mut","command_off","command_on","command_state","deserialize","fmt","format","from","icon_off","icon_on","interval","into","or","run","state_off","state_on","try_from","try_into","type_id","vzip","Config","and","borrow","borrow_mut","default","deserialize","fmt","format","from","interval","into","null_value","or","run","try_from","try_into","type_id","vzip","Config","DriverType","Mullvad","Nordvpn","and","","borrow","","borrow_mut","","default","","deserialize","","driver","fmt","","format_connected","format_disconnected","from","","interval","into","","null_value","","or","","run","state_connected","state_disconnected","try_from","","try_into","","type_id","","vzip","","Config","and","borrow","borrow_mut","default","deserialize","deserialize_local_timestamp","fmt","format","from","interval","into","null_value","or","run","show_time","state_path","try_from","try_into","type_id","vzip","Config","MetNo","Nws","OpenWeatherMap","WeatherService","and","","autolocate","autolocate_interval","borrow","","borrow_mut","","deserialize","","fmt","","format","format_alt","from","","interval","into","","met_no","nws","open_weather_map","or","","run","service","try_from","","try_into","","type_id","","vzip","","Config","and","borrow","borrow_mut","default","deserialize","fmt","from","into","null_value","or","try_from","try_into","type_id","vzip","Config","and","borrow","borrow_mut","default","deserialize","fmt","from","into","null_value","or","try_from","try_into","type_id","vzip","Config","and","borrow","borrow_mut","default","deserialize","deserialize_forecast_hours","fmt","from","into","null_value","or","try_from","try_into","type_id","vzip","Config","and","borrow","borrow_mut","default","deserialize","fmt","format","from","interval","into","invert_icons","null_value","or","run","step_width","try_from","try_into","type_id","vzip","Back","ClickConfigEntry","ClickHandler","DoubleLeft","Forward","Left","Middle","MouseButton","PostActions","Right","WheelDown","WheelLeft","WheelRight","WheelUp","action","and","","","","borrow","","","","borrow_mut","","","","clone","","","","clone_into","","","","clone_to_uninit","","","","default","deserialize","","","eq","equivalent","","","fmt","","","","from","","","","handle","hash","into","","","","null_value","or","","","","to_owned","","","","try_from","","","","try_into","","","","type_id","","","","update","vzip","","","","BlockConfigEntry","CommonBlockConfig","Config","SharedConfig","and","","","","blocks","borrow","","","","borrow_mut","","","","click","clone","clone_into","clone_to_uninit","common","config","default","","deserialize","","","","double_click_delay","error_format","","error_fullscreen_format","","error_interval","fmt","","","","from","","","","geolocator","get_icon","icons","icons_format","","icons_overrides","if_command","into","","","","invert_scrolling","merge_with_next","null_value","","or","","","","shared","signal","theme","theme_overrides","to_owned","try_from","","","","try_into","","","","type_id","","","","vzip","","","","BoxErrorWrapper","Err","Error","ErrorContext","Ok","Result","StdError","ToSerdeError","and","","borrow","","borrow_mut","","cause","","clone","clone_into","clone_to_uninit","description","downcast","","","downcast_mut","","","downcast_ref","","","error","","fmt","","","","from","","into","","is","","","message","new","or","","or_error","","provide","record","","","","serde_error","","source","sources","to_owned","to_string","","try_from","","try_into","","try_to_string","","type_id","","vzip","","CollectEscaped","Escaped","collect_pango_escaped","collect_pango_escaped_into","pango_escaped","pango_escaped_into","Format","FormatError","Fragment","IncompatibleFormatter","Metadata","NumberOutOfRange","Other","PlaceholderNotFound","Values","and","","","","borrow","","","","borrow_mut","","","","clone","","","clone_into","","","clone_to_uninit","","","config","contains_key","default","","eq","equivalent","","","fmt","","","","","formatted_text","formatter","from","","","","","","instance","intervals","into","","","","is_default","italic","metadata","null_value","","or","","","","parse","prefix","render","scheduling","source","template","text","to_owned","","","to_string","try_from","","","","try_into","","","","try_to_string","type_id","","","","underline","unit","value","vzip","","","","fmt","ty","Config","and","borrow","borrow_mut","clone","clone_into","clone_to_uninit","default","deserialize","fmt","from","from_str","full","into","null_value","or","short","to_owned","try_from","try_into","type_id","vzip","with_default","with_default_config","with_default_format","with_defaults","BarFormatter","Chrono","DEFAULT_DATETIME_FORMATTER","DEFAULT_DURATION_FORMATTER","DEFAULT_FLAG_FORMATTER","DEFAULT_NUMBER_FORMATTER","DEFAULT_STRING_FORMATTER","DatetimeFormatter","DurationFormatter","EngFormatter","FlagFormatter","Formatter","Icu","PangoStrFormatter","StrFormatter","TallyFormatter","and","","","","","","","","borrow","","","","","","","","borrow_mut","","","","","","","","default","fmt","","","","","","","","format","","","","","","","","","from","","","","","","","","interval","","into","","","","","","","","new_formatter","null_value","or","","","","","","","","try_from","","","","","","","","try_into","","","","","","","","type_id","","","","","","","","vzip","","","","","","","","items","length","locale","","Arg","FormatTemplate","Formatter","Icon","Placeholder","","Recursive","Text","Token","TokenList","and","","","","","","args","borrow","","","","","","borrow_mut","","","","","","eq","","","","","","equivalent","","","","","","","","","","","","","","","","","","fmt","","","","","","formatter","from","","","","","","into","","","","","","key","name","","or","","","","","","parse_full","parse_value","try_from","","","","","","try_into","","","","","","type_id","","","","","","val","vzip","","","","","","Gibi","Giga","Kibi","Kilo","Mebi","Mega","Micro","Milli","Nano","One","OneButBinary","Prefix","Tebi","Tera","and","apply","borrow","borrow_mut","clone","clone_into","clone_to_uninit","cmp","compare","eng","eng_binary","eq","equivalent","","","fmt","","from","from_str","into","is_binary","max","max_available","min_available","or","partial_cmp","to_owned","to_string","try_from","try_into","try_to_string","type_id","vzip","manage_widgets_updates","FormatTemplate","Icon","Placeholder","Recursive","Text","Token","TokenList","and","","","borrow","","","borrow_mut","","","clone","clone_into","clone_to_uninit","contains_key","default","fmt","","","from","","","from_str","init_intervals","into","","","null_value","or","","","render","","to_owned","try_from","","","","","","try_into","","","type_id","","","vzip","","","formatter","name","","Bits","Bytes","Degrees","Hertz","None","Percents","Seconds","Unit","Watts","and","borrow","borrow_mut","clamp_prefix","clone","clone_into","clone_to_uninit","convert","eq","equivalent","","","fmt","","from","from_str","into","or","to_owned","to_string","try_from","try_into","try_to_string","type_id","vzip","Datetime","Duration","Flag","Icon","IntoF64","Number","Text","Value","ValueInner","and","","bits","borrow","","borrow_mut","","bytes","clone","","clone_into","","clone_to_uninit","","datetime","default_formatter","degrees","duration","flag","fmt","","from","","hertz","icon","icon_progression","icon_progression_bound","inner","into","","into_f64","italic","metadata","new","number","number_unit","or","","percents","seconds","text","to_owned","","try_from","","try_into","","type_id","","type_name","underline","vzip","","watts","with_instance","unit","val","Geolocator","GeolocatorBackend","IPAddressInfo","Ip2Location","Ipapi","and","","","asn","borrow","","","borrow_mut","","","city","clone","","clone_into","","clone_to_uninit","","continent_code","country","country_area","country_calling_code","country_capital","country_code","country_code_iso3","country_name","country_population","country_tld","currency","currency_name","default","","","deserialize","","","find_ip_location","fmt","","","from","","","","in_eu","into","","","ip","languages","latitude","longitude","name","null_value","","","or","","","org","postal","region","region_code","timezone","to_owned","","try_from","","","try_into","","","type_id","","","utc_offset","version","vzip","","","Icon","Icons","Progression","Single","and","","apply_overrides","borrow","","borrow_mut","","clone","","clone_into","","clone_to_uninit","","default","deserialize","","fmt","","from","","","","from_file","get","into","","null_value","or","","to_owned","","try_from","","try_into","","type_id","","vzip","","i3bar_block","i3bar_event","init","Center","I3BarBlock","I3BarBlockAlign","I3BarBlockMinWidth","Left","Pixels","Right","Text","align","and","","","background","border","border_bottom","border_left","border_right","border_top","borrow","","","borrow_mut","","","clone","","","clone_into","","","clone_to_uninit","","","color","default","fmt","","","from","","","full_text","instance","into","","","markup","min_width","name","null_value","or","","","separator","separator_block_width","serialize","","","short_text","to_owned","","","try_from","","","try_into","","","type_id","","","urgent","vzip","","","I3BarEvent","and","borrow","borrow_mut","button","clone","clone_into","clone_to_uninit","eq","equivalent","","","events_stream","fmt","from","id","instance","into","or","to_owned","try_from","try_into","type_id","vzip","Color","ColorOrLink","Link","Theme","ThemeInner","ThemeOverrides","ThemeUserConfig","alternating_tint_bg","","alternating_tint_fg","","and","","","","","apply_overrides","borrow","","","","","borrow_mut","","","","","clone","","","","clone_into","","","","clone_to_uninit","","","","color","critical_bg","","critical_fg","","default","","","","deref","deref_mut","deserialize","","","","end_separator","","fmt","","","","from","","","","","get_colors","good_bg","","good_fg","","idle_bg","","idle_fg","","info_bg","","info_fg","","into","","","","","null_value","","","","or","","","","","overrides","separator","","","separator_bg","","separator_fg","","start_separator","","theme","to_owned","","","","try_from","","","","","","try_into","","","","","type_id","","","","","vzip","","","","","warning_bg","","warning_fg","","xresources","link","Auto","Color","Hsva","","None","Rgba","","a","","add","","","and","","","approx","b","borrow","","","borrow_mut","","","clone","","","clone_into","","","clone_to_uninit","","","default","","","deserialize","eq","","","equivalent","","","fmt","","","from","","","","","from_hex","from_str","g","h","into","","","new","","null_value","","","or","","","r","s","serialize","skip_ser","to_owned","","","try_from","","","try_into","","","type_id","","","v","vzip","","","Custom","Native","Separator","and","borrow","borrow_mut","clone","clone_into","clone_to_uninit","default","deserialize","eq","equivalent","","","fmt","from","from_str","into","null_value","or","to_owned","try_from","try_into","type_id","vzip","get_color","StreamExtDebounced","country_flag_from_iso_code","default","deserialize_toml_file","find_file","format_bar_graph","has_command","map","new_dbus_connection","new_system_dbus_connection","next_debounced","read_file","Critical","Good","Idle","Info","State","Warning","Widget","and","","borrow","","borrow_mut","","clone","","clone_into","","clone_to_uninit","","default","","deserialize","eq","equivalent","","","fmt","","from","","get_data","intervals","into","","new","null_value","","or","","set_format","set_text","set_values","state","to_owned","","try_from","","try_into","","type_id","","vzip","","with_format","with_state","with_text"],"q":[[0,"i3status_rs"],[67,"i3status_rs::blocks"],[214,"i3status_rs::blocks::amd_gpu"],[245,"i3status_rs::blocks::backlight"],[272,"i3status_rs::blocks::battery"],[322,"i3status_rs::blocks::bluetooth"],[341,"i3status_rs::blocks::calendar"],[526,"i3status_rs::blocks::cpu"],[548,"i3status_rs::blocks::custom"],[573,"i3status_rs::blocks::custom_dbus"],[589,"i3status_rs::blocks::disk_iostats"],[609,"i3status_rs::blocks::disk_space"],[655,"i3status_rs::blocks::docker"],[674,"i3status_rs::blocks::external_ip"],[694,"i3status_rs::blocks::focused_window"],[730,"i3status_rs::blocks::github"],[754,"i3status_rs::blocks::hueshift"],[801,"i3status_rs::blocks::kdeconnect"],[825,"i3status_rs::blocks::keyboard_layout"],[871,"i3status_rs::blocks::load"],[892,"i3status_rs::blocks::maildir"],[934,"i3status_rs::blocks::memory"],[957,"i3status_rs::blocks::menu"],[993,"i3status_rs::blocks::music"],[1039,"i3status_rs::blocks::net"],[1061,"i3status_rs::blocks::notify"],[1096,"i3status_rs::blocks::notmuch"],[1120,"i3status_rs::blocks::nvidia_gpu"],[1143,"i3status_rs::blocks::packages"],[1204,"i3status_rs::blocks::packages::apt"],[1220,"i3status_rs::blocks::packages::dnf"],[1236,"i3status_rs::blocks::packages::pacman"],[1266,"i3status_rs::blocks::packages::xbps"],[1282,"i3status_rs::blocks::pomodoro"],[1305,"i3status_rs::blocks::privacy"],[1337,"i3status_rs::blocks::rofication"],[1356,"i3status_rs::blocks::scratchpad"],[1373,"i3status_rs::blocks::service_status"],[1412,"i3status_rs::blocks::sound"],[1491,"i3status_rs::blocks::speedtest"],[1509,"i3status_rs::blocks::taskwarrior"],[1555,"i3status_rs::blocks::tea_timer"],[1574,"i3status_rs::blocks::temperature"],[1626,"i3status_rs::blocks::time"],[1664,"i3status_rs::blocks::toggle"],[1687,"i3status_rs::blocks::uptime"],[1705,"i3status_rs::blocks::vpn"],[1744,"i3status_rs::blocks::watson"],[1765,"i3status_rs::blocks::weather"],[1804,"i3status_rs::blocks::weather::met_no"],[1819,"i3status_rs::blocks::weather::nws"],[1834,"i3status_rs::blocks::weather::open_weather_map"],[1850,"i3status_rs::blocks::xrandr"],[1870,"i3status_rs::click"],[1957,"i3status_rs::config"],[2040,"i3status_rs::errors"],[2110,"i3status_rs::escape"],[2116,"i3status_rs::formatting"],[2213,"i3status_rs::formatting::FormatError"],[2215,"i3status_rs::formatting::config"],[2241,"i3status_rs::formatting::formatter"],[2359,"i3status_rs::formatting::formatter::DatetimeFormatter"],[2363,"i3status_rs::formatting::parse"],[2471,"i3status_rs::formatting::prefix"],[2518,"i3status_rs::formatting::scheduling"],[2519,"i3status_rs::formatting::template"],[2573,"i3status_rs::formatting::template::Token"],[2576,"i3status_rs::formatting::unit"],[2610,"i3status_rs::formatting::value"],[2674,"i3status_rs::formatting::value::ValueInner"],[2676,"i3status_rs::geolocator"],[2760,"i3status_rs::icons"],[2803,"i3status_rs::protocol"],[2806,"i3status_rs::protocol::i3bar_block"],[2881,"i3status_rs::protocol::i3bar_event"],[2905,"i3status_rs::themes"],[3038,"i3status_rs::themes::ColorOrLink"],[3039,"i3status_rs::themes::color"],[3124,"i3status_rs::themes::separator"],[3150,"i3status_rs::themes::xresources"],[3151,"i3status_rs::util"],[3163,"i3status_rs::widget"],[3219,"tower_http::follow_redirect::policy::and"],[3220,"tower_http::follow_redirect::policy"],[3221,"clap_builder::builder::command"],[3222,"alloc::string"],[3223,"core::fmt"],[3224,"clap_builder::parser::matches::arg_matches"],[3225,"clap_builder"],[3226,"core::result"],[3227,"clap_builder::util::id"],[3228,"core::option"],[3229,"tower_http::follow_redirect::policy::or"],[3230,"core::any"],[3231,"serde::de"],[3232,"reqwest::async_impl::client"],[3233,"core::time"],[3234,"tokio::sync::mpsc::unbounded"],[3235,"core::future::future"],[3236,"alloc::boxed"],[3237,"core::pin"],[3238,"futures_util::stream::futures_unordered"],[3239,"alloc::collections"],[3240,"alloc::vec"],[3241,"quick_xml::errors::serialize"],[3242,"reqwest::error"],[3243,"std::io::error"],[3244,"serde_json::error"],[3245,"regex::regex::string"],[3246,"alloc::borrow"],[3247,"std::sync::lazy_lock"],[3248,"indexmap::map"],[3249,"core::hash"],[3250,"chrono::offset::local"],[3251,"chrono::datetime"],[3252,"alloc::sync"],[3253,"core::convert"],[3254,"core::marker"],[3255,"core::ops::function"],[3256,"core::error"],[3257,"tracing_core::field"],[3258,"core::default"],[3259,"i3status_rs::formatting::formatter::duration"],[3260,"i3status_rs::formatting::formatter::flag"],[3261,"i3status_rs::formatting::formatter::eng"],[3262,"i3status_rs::formatting::formatter::str"],[3263,"i3status_rs::formatting::formatter::bar"],[3264,"i3status_rs::formatting::formatter::tally"],[3265,"i3status_rs::formatting::formatter::datetime"],[3266,"i3status_rs::formatting::formatter::pango"],[3267,"icu_datetime::options::length"],[3268,"icu_locid::locale"],[3269,"core::str::traits"],[3270,"core::cmp"],[3271,"futures_core::stream"],[3272,"chrono::offset::utc"],[3273,"chrono_tz::timezones"],[3274,"std::collections::hash::map"],[3275,"serde::ser"],[3276,"std::path"],[3277,"zbus::connection"]],"i":"```BfhAf111`210210`11`1```10`21011`1`210`121210`2`2``21021021011`210````Dj`Cn1`00CbCj`2`211`2210210`2000`2`2`22`2`2`21`20211`22100`20`2210`2`2`2`2`2`2`22`2`2`2`2210`2`2`2`2`2`2000`22`2`2`2`2`201`22102101210`2`22100`2`2`2``AN`Fb101000000010010010`10101010`Ff0000000000000000000`00000Fl``000Fj101000101000001001000000100001010`101010100`Fn000000000000`0000`HbGd````1110``111`10G`20Gb2GfGjGlGn4337546321075463210542246321046321046321031315463205463210557754632107777754632107546321055546320575463210053`37546321073754632107546321077546321040754632105`I`00000000000000`00000`Ib0000000000000000`000000`Id000000000`0000`If0000000000000`0000Ij`0`0Ih00101011110101010001000101010`1010101010`Il00000000000`00000`In00000000000`000000J```000Jb10101010010010101010`10101010`Jd000000000000000`000000`Jh`000000Jf10100111001010010001000010`0110101010`Jj00000000000000000`0000`Jl`000000Jn1010111101001001001001010`0110101010`K`0000000000000`00000Kb`0`00Kd10101110100100100010010`00110101010`Kf00000000000000`000000``KhKj101011111101101010010`0110101010`Kl`00Kn1010111101010001001010100`00001101010010`L`000000000000000`0000``Lb00Ld10101010010010101010`10101010`Lf0000000000000`00000000`Lh000000000000000`00000Ln0``0`000Ll`010101010100010`11000010M``1112101211``21212121211``Mf00000000000000`Mh00000000000000````MlMn101010101010101010101010`N`00000000000000`Nb0000000000000000`0000`Nd`00Nf10101001000101010`10101010`Nh00000000000`00000`Nj0000000000`0000``NnNl010101010100101000101010`0101010010Of0``0Od`0O`12012012012121212011200001111120001201012000000120120`0012120120120120`Oj00000000000`0000``OlOn1010111100101010100001001011010`1101010100`A@`000000000000`0000A@d`0`0A@b1010011110101111100010100000101010`01101010100``A@f00A@h101011101010010010010`0110101010`A@j00000000000000`000000`A@l00000000000`0000``AA`0A@n10101010100100010010101`0001010101`AAd0000`0000000`000000`AAl00`0AAj00101010100010010```10`010101010`AAn0000000000000`AB`0000000000000`ABb0000`000000000`ABd000000000000`00000E```0000``00000ABf10ABhABj32103210321032103210131033333210321013321013210321032103210321023210````BdAC`CfABn332103210022211203210330300321032103222000321030203210302023210321032103210`Cd``0```AClDb1010Hn11110000000000ACd43322323211122320411111ADf5223434343434343``ADn0AEd0```AEl`000`0AEfAEhAEj32103210210210210`2100000332101`332110023210001103210``2`3`1210332103210332100``3210AF`0`Fd000000000000000000000000`AFn``````````0```AFjAFl2AFbAFfAFdAG`AFh6574321065743210465743210AGb76854321768543210176854321`57685432176854321768543217685432176854321AGhAGj10```AHd`000``AGfAH`AHb3AHfAHh34325104325104325104443332225551110004325102432510432510432432510`44325104325104325104432510AHl0000000000`00000000000000000000000000000000000``AIh000``AIdAIf210210211111102102111021102101110022102102102AIj0AIlAIn000000`00000000000000000000000000AGd000`00``AJ`10010100101010000001010000001AJb111111211112121212211211AJh0```AJj0DhAJl21102102112121211111111111110210201021002110211110102102111111210210210211102``AK`0AJn10010101010100101011100010010101010101```AKj```0AKl10AKf021000000021021021021021000210210002100000210002100210210210210021`ABl0000000000`00000000000ALf`0````AL`ALb10ALd2ALh2411302413024132413241324`32321302113024321324130241323232323232130241302130240`32323232013241130241302413024130243232`ALlAKh``00`0ALnAM`102102`110210210210210210221021111021100212101021010210210221021021021020102ALj0`00000000000000000000000```````````AMl`AAb000`0`Ef101010101010111111010100010010100000101010101000","f":"```{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00{ff}0{hj}`{l{{l{c}}}{}}00{{{l{n}}}{{l{nc}}}{}}00`{{}f}0`{hA`}```{{{l{h}}{l{nAb}}}Ad}{{{l{Af}}{l{nAb}}}Ad}`{cc{}}00{{{l{Ah}}}{{Al{hAj}}}}{{{l{nAh}}}{{Al{hAj}}}}`{{}{{B`{An}}}}`{{}c{}}00`{hBb}{BdBf}1{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00`{{Bf{Bn{}{{Bj{Bl}}}}}{{Cd{C`Cb}}}}`{{{l{nBf}}Cf}{{Cd{C`}}}}``{c{{Al{e}}}{}{}}00{{}{{Al{c}}}{}}00{lCh}00{{{l{nh}}{l{Ah}}}{{Al{C`Aj}}}}{{{l{nh}}{l{nAh}}}{{Al{C`Aj}}}}`{{}c{}}00``````````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00````{Cbj}{Cbl}``{l{{l{c}}}{}}00{{{l{n}}}{{l{nc}}}{}}00``{{{l{Cj}}}Cj}{{l{l{nc}}}C`{}}{{lCl}C`}``````{c{{Cd{Cn}}}D`}``````{CbDb}``{{{l{Cj}}{l{Dd}}Df}{{Cd{Dh}}}}{{{l{Cn}}{l{nAb}}}Ad}{{{l{Cb}}{l{nAb}}}Ad}0``{cc{}}00{{{l{Cj}}}{{Cd{{Dl{Dj}}}}}}``{{{l{Cj}}}{{Cd{C`}}}}``{{}c{}}00``````````````{{{l{Cn}}}{{l{Dn}}}}````````{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00````````````{{{l{Cj}}{l{{Ed{{Eb{E`{B`{{l{Dn}}}}{l{Dn}}}}}}}}}{{Cd{C`}}}}{{{l{Cj}}Db}{{Cd{C`}}}}{{{l{Cj}}Ef}{{Cd{C`}}}}``{{CnCj{l{n{En{{El{{Ej{Eh}}}}}}}}}C`}``````````{lc{}}{lA`}``{c{{Al{e}}}{}{}}00{{}{{Al{c}}}{}}00{l{{Al{A`F`}}}}{lCh}00````{{}c{}}00{{{l{Cj}}}C`}````````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{}Fb}{c{{Al{Fb}}}D`}{FbB`}{{{l{Fb}}{l{nAb}}}Ad}{FbFd}2{cc{}}0`{{}c{}}0{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{Fb}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{FfB`}00{{}Ff}{c{{Al{Ff}}}D`}2{{{l{Ff}}{l{nAb}}}Ad}{FfFd}{cc{}}{{}c{}}{FfBb}{FfFh}04{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}:{{{l{Ff}}{l{Cj}}}{{Cd{C`}}}}3{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}`````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{FjFd}{FjFh}{{}Fl}{{}Fj}{c{{Al{Fl}}}D`}{c{{Al{Fj}}}D`}{FjB`}{FjFl}76{{{l{Fl}}{l{nAb}}}Ad}{{{l{Fj}}{l{nAb}}}Ad}9{cc{}}0:999`{{}c{}}0;5;{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{Fj}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0{FjFh}`{FnB`}{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}1{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{c{{Al{Fn}}}D`}{FnFd}{{{l{Fn}}{l{nAb}}}Ad}1{cc{}}{{}c{}}{FnA`}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{Fn}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}````````````````````>>>>>>>>{GbGd}`{GfA`}????????>>>>>>>>`{GbGh}{GjB`}0{{{l{Gb}}}Gb}{{{l{Gd}}}Gd}{{{l{Gf}}}Gf}{{{l{Gj}}}Gj}{{{l{Gl}}}Gl}{{{l{Gn}}}Gn}{{l{l{nc}}}C`{}}00000{{lCl}C`}00000{GfGj}{GlGn}{GfB`}{GlB`}{{}G`}{{}Gb}{{}Gd}{{}Gf}{{}Gj}{{}Gn}{c{{Al{G`}}}D`}{c{{Al{Gb}}}D`}{c{{Al{Gd}}}D`}{c{{Al{Gf}}}D`}{c{{Al{Gj}}}D`}{c{{Al{Gl}}}D`}{c{{Al{Gn}}}D`}{G`H`}`{{{l{Hb}}{l{nAb}}}Ad}0{{{l{G`}}{l{nAb}}}Ad}{{{l{Gb}}{l{nAb}}}Ad}{{{l{Gd}}{l{nAb}}}Ad}{{{l{Gf}}{l{nAb}}}Ad}{{{l{Gj}}{l{nAb}}}Ad}{{{l{Gl}}{l{nAb}}}Ad}{{{l{Gn}}{l{nAb}}}Ad}{HdHb}{HfHb}{HhHb}{HjHb}{cc{}}0000000{{}c{}}0000000{G`Fd}0{{}c{}}000001{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0000000{GnB`}3{GfHl}{{{l{G`}}{l{Cj}}}{{Cd{C`}}}}{GfGh}{{{l{Hb}}}{{B`{{l{Hn}}}}}}{G`Gh}{lc{}}00000{lA`}{GfA`}{c{{Al{e}}}{}{}}0000000{{}{{Al{c}}}{}}0000000{l{{Al{A`F`}}}}{lCh}0000000{GbA`}={{}c{}}0000000{G`H`}`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{I`Fh}{{}I`}{c{{Al{I`}}}D`}{{{l{I`}}{l{nAb}}}Ad}{I`Fd}{I`B`}{cc{}}6`{{}c{}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{I`}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}>`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{IbB`}0{{}Ib}{c{{Al{Ib}}}D`}{{{l{Ib}}{l{nAb}}}Ad}{IbFd}{cc{}}{IbBb}`{{}c{}}1{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}3{{{l{Ib}}{l{Cj}}}{{Cd{C`}}}}:{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}{IbGh}`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{c{{Al{Id}}}D`}{{{l{Id}}{l{nAb}}}Ad}{IdFd}{cc{}}?={IdA`}{{{l{Id}}{l{Cj}}}{{Cd{C`}}}}=<;:`876{{}If}{c{{Al{If}}}D`}{IfB`}{{{l{If}}{l{nAb}}}Ad}{IfFd}7`{{}c{}}1{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{If}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}`````{IhFh}{IhB`}{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{{l{Ij}}}Ij}{{l{l{nc}}}C`{}}{{lCl}C`}{{}Ih}{{}Ij}{c{{Al{Ih}}}D`}{c{{Al{Ij}}}D`}{{{l{Ih}}{l{nAb}}}Ad}{{{l{Ij}}{l{nAb}}}Ad}{IhFd}={cc{}}0{IhIj}`{{}c{}}0{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0`{{{l{Ih}}{l{Cj}}}{{Cd{C`}}}}{lc{}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0{IhFh}`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{}Il}{c{{Al{Il}}}D`}{{{l{Il}}{l{nAb}}}Ad}{IlFd}{cc{}}`{{}c{}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{Il}}{l{Cj}}}{{Cd{C`}}}}`{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}`?>={{}In}{c{{Al{In}}}D`}{{{l{In}}{l{nAb}}}Ad}{InFd}<`;:9{{{l{In}}{l{Cj}}}{{Cd{C`}}}}876{InBb}60`````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{}J`}{{}Jb}{c{{Al{J`}}}D`}{c{{Al{Jb}}}D`}{JbJ`}{{{l{J`}}{l{nAb}}}Ad}{{{l{Jb}}{l{nAb}}}Ad}{JbFd}{cc{}}0{{}c{}}0{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{Jb}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{JdB`}{{}Jd}{c{{Al{Jd}}}D`}{{{l{Jd}}{l{nAb}}}Ad}{JdFd}{cc{}}5{JdBb}6`{{}c{}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{Jd}}{l{Cj}}}{{Cd{C`}}}}:{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}>````````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{JfHl}{{{l{Jh}}}Jh}{{l{l{nc}}}C`{}}{{lCl}C`}3{{}Jf}{c{{Al{Jh}}}D`}{c{{Al{Jf}}}D`}{{{l{Jh}}{l{nAb}}}Ad}{{{l{Jf}}{l{nAb}}}Ad}{JfFd}{cc{}}0{JfB`}`{{}c{}}0<<{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{Jf}}{l{Cj}}}{{Cd{C`}}}}?{lc{}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{JjCl}000{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{}Jj}{c{{Al{Jj}}}D`}{JjB`}{JjFd}{{{l{Jj}}{l{nAb}}}Ad}1{cc{}}{{}c{}}3{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{Jj}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}````````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{{l{Jl}}}Jl}{{l{l{nc}}}C`{}}{{lCl}C`}{{}Jl}{{}Jn}{c{{Al{Jl}}}D`}{c{{Al{Jn}}}D`}{JnJl}{{{l{Jl}}{l{nAb}}}Ad}{{{l{Jn}}{l{nAb}}}Ad}{JnFd}{cc{}}0`{{}c{}}0{JnB`}{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{Jn}}{l{Cj}}}{{Cd{C`}}}}3{lc{}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{K`Fh}{{}K`}{c{{Al{K`}}}D`}{{{l{K`}}{l{nAb}}}Ad}{K`Fd}{cc{}}5`{{}c{}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{K`}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}=`````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{{l{Kb}}}Kb}{{l{l{nc}}}C`{}}{{lCl}C`}{{}Kd}{c{{Al{Kb}}}D`}{c{{Al{Kd}}}D`}{KdKb}{{{l{Kb}}{l{nAb}}}Ad}{{{l{Kd}}{l{nAb}}}Ad}{KdFd}{cc{}}0{KdGh}`{{}c{}}0{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{Kd}}{l{Cj}}}{{Cd{C`}}}}{Kdj}0{lc{}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{KfFh}0{{}Kf}{c{{Al{Kf}}}D`}{{{l{Kf}}{l{nAb}}}Ad}{KfFd}{KfB`}{cc{}}`{{}c{}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{Kf}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}>>``{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{{l{Kh}}}Kh}{{l{l{nc}}}C`{}}{{lCl}C`}{KhA`}{KhB`}{c{{Al{Kh}}}D`}{c{{Al{Kj}}}D`}3{{{l{Kh}}{l{nAb}}}Ad}{{{l{Kj}}{l{nAb}}}Ad}{cc{}}0{{}c{}}0{KjGh}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{Kj}}{l{Cj}}}{{Cd{C`}}}}{KjA`}{lc{}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{{l{Kl}}}Kl}{{l{l{nc}}}C`{}}{{lCl}C`}{{}Kl}{{}Kn}{c{{Al{Kl}}}D`}{c{{Al{Kn}}}D`}{{{l{Kl}}{l{nAb}}}Ad}{{{l{Kn}}{l{nAb}}}Ad}{KnFd}{KnB`}{cc{}}0{KnGh}{{}c{}}0{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{KnKl}{{{l{Kn}}{l{Cj}}}{{Cd{C`}}}}77`{KnA`}{lc{}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{KnFh}{{}c{}}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{}L`}{c{{Al{L`}}}D`}{L`B`}{{{l{L`}}{l{nAb}}}Ad}{L`Fd}2{cc{}}1`{{}c{}}2{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{L`}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{}Lb}{{}Ld}{c{{Al{Lb}}}D`}{c{{Al{Ld}}}D`}{LdLb}{{{l{Lb}}{l{nAb}}}Ad}{{{l{Ld}}{l{nAb}}}Ad}{LdFd}{cc{}}0{{}c{}}0{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{Ld}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{}Lf}{c{{Al{Lf}}}D`}{{{l{Lf}}{l{nAb}}}Ad}{LfFd}?`>`=<{LfA`}{{{l{Lf}}{l{Cj}}}{{Cd{C`}}}}{LfH`}000=<;:`987{{}Lh}{c{{Al{Lh}}}D`}{{{l{Lh}}{l{nAb}}}Ad}{LhFd}{cc{}}{LhH`}{LhLj}11`{{}c{}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{Lh}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}9````````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0`{LlB`}{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{{l{Ln}}}Ln}{{{l{Ll}}}Ll}{{l{l{nc}}}C`{}}0{{lCl}C`}06{{}Ll}{c{{Al{Ln}}}D`}{c{{Al{Ll}}}D`}`{{{l{Ln}}{l{Ln}}}Bb}{{{l{Ln}}{l{nAb}}}Ad}{{{l{Ll}}{l{nAb}}}Ad}{LlFd}00{cc{}}0{{{l{M`}}}{{El{{Ej{Eh}}}}}}{{{l{{Ed{A`}}}}{l{Mb}}}Bb}{LlBb}{LlB`}`{{}c{}}0{{{l{M`}}}{{Md{Dn}}}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{LlGh}`{{{l{Ll}}{l{Cj}}}{{Cd{C`}}}}{lc{}}0{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0;``{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{}Mf}{cc{}}{{{l{Mf}}}{{El{{Ej{Eh}}}}}}{{}c{}}{{{l{Mf}}}{{Md{Dn}}}}{Bb{{Cd{Mf}}}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}>=<;`:98{{}Mh}7{{{l{Mh}}}{{El{{Ej{Eh}}}}}}6{{{l{Mh}}}{{Md{Dn}}}}243{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}`{{}Mj}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{cc{}}0{{{l{Ml}}}{{El{{Ej{Eh}}}}}}{{{l{Mn}}}{{El{{Ej{Eh}}}}}}{{}c{}}0{{{l{Ml}}}{{Md{Dn}}}}{{{l{Mn}}}{{Md{Dn}}}}{{}{{Cd{Ml}}}}{A`Mn}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0`?>={{}N`}={{{l{N`}}}{{El{{Ej{Eh}}}}}};{{{l{N`}}}{{Md{Dn}}}}2{{}c{}}87654`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{NbBb}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{NbFd}{NbA`}{{}Nb}{c{{Al{Nb}}}D`}{{{l{Nb}}{l{nAb}}}Ad}4{cc{}}{{}c{}}5{NbB`}<{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}8{{{l{Nb}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{c{{Al{Nd}}}D`}{c{{Al{Nf}}}D`}{NfGh}{{{l{Nd}}{l{nAb}}}Ad}{{{l{Nf}}{l{nAb}}}Ad}{NfFd}0{cc{}}0{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{Nf}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{}Nh}{c{{Al{Nh}}}D`}{{{l{Nh}}{l{nAb}}}Ad}{NhFd}>`={{}c{}}={{{l{Nh}}{l{Cj}}}{{Cd{C`}}}}`<;:9`876{{}Nj}{c{{Al{Nj}}}D`}{{{l{Nj}}{l{nAb}}}Ad}{NjFd}{cc{}}{{}c{}}7{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{Nj}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}```{NlFd}{NlB`}{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{}Nn}{{}Nl}{c{{Al{Nn}}}D`}{c{{Al{Nl}}}D`}{NlNn}{{{l{Nn}}{l{nAb}}}Ad}{{{l{Nl}}{l{nAb}}}Ad}{cc{}}0<;{{}c{}}0{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{Nl}}{l{Cj}}}{{Cd{C`}}}}{NlA`}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{NlBb}{{}c{}}0````````{O`Ob}{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00{l{{l{c}}}{}}00{{{l{n}}}{{l{nc}}}{}}00{{{l{Od}}}Od}{{{l{Of}}}Of}{{l{l{nc}}}C`{}}0{{lCl}C`}0{{}Od}{{}Of}{{}O`}{Od{{Md{Dn}}}}{c{{Al{Od}}}D`}{c{{Al{Of}}}D`}{c{{Al{O`}}}D`}{O`B`}{O`Od}{O`Of}{{{l{Od}}{l{Od}}}Bb}{{l{l{c}}}Bb{}}00{{{l{Od}}{l{nAb}}}Ad}{{{l{Of}}{l{nAb}}}Ad}{{{l{O`}}{l{nAb}}}Ad}{O`Fd}8{cc{}}00{{{l{Od}}{l{nc}}}C`Oh}{O`Bb}{{}c{}}00<1<<1{{}c{}}00{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00{{{l{O`}}{l{Cj}}}{{Cd{C`}}}}4{O`H`}{lc{}}0{c{{Al{e}}}{}{}}00{{}{{Al{c}}}{}}00{lCh}00{{}c{}}00`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{}Oj}{c{{Al{Oj}}}D`}{{{l{Oj}}{l{nAb}}}Ad}{OjFd}{cc{}}`{{}c{}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{Oj}}{l{Cj}}}{{Cd{C`}}}}?>=<``;;::99{{{l{Ol}}}Ol}{{l{l{nc}}}C`{}}{{lCl}C`}{OlGh}{OnH`}`{{}Ol}{{}On}{c{{Al{Ol}}}D`}{c{{Al{On}}}D`}{OlA`}{OnGh}{{{l{Ol}}{l{nAb}}}Ad}{{{l{On}}{l{nAb}}}Ad}{OnFd}00{cc{}}0`{{}c{}}06{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{On}}{l{Cj}}}{{Cd{C`}}}}{lc{}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0{OnH`}`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{}A@`}{c{{Al{A@`}}}D`}{A@`B`}{{{l{A@`}}{l{nAb}}}Ad}{A@`Fd}{cc{}}3{{}c{}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{A@`}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{A@bB`}{{{l{A@d}}}A@d}{{l{l{nc}}}C`{}}{{lCl}C`}{{}A@d}{{}A@b}{c{{Al{A@d}}}D`}{c{{Al{A@b}}}D`}{{{l{A@d}}{l{A@d}}}Bb}{{l{l{c}}}Bb{}}00{{{l{A@d}}{l{nAb}}}Ad}{{{l{A@b}}{l{nAb}}}Ad}{A@bFd}<{cc{}}0{{A@dFh}Fh}>>>>`{{}c{}}0{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{A@b}}{l{Cj}}}{{Cd{C`}}}}{A@bA@d}{lc{}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0{A@bB`}````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{{l{A@f}}}A@f}{{l{l{nc}}}C`{}}{{lCl}C`}{{}A@h}{c{{Al{A@f}}}D`}{c{{Al{A@h}}}D`}{{{l{A@f}}{l{nAb}}}Ad}{{{l{A@h}}{l{nAb}}}Ad}{A@hFd}{cc{}}0`{{}c{}}0{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{A@h}}{l{Cj}}}{{Cd{C`}}}}{A@hB`}{lc{}}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{A@jA`}00{c{{Al{A@j}}}D`}{{{l{A@j}}{l{nAb}}}Ad}{A@jFd}{cc{}}{A@jB`}00{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{A@j}}{l{Cj}}}{{Cd{C`}}}}33?>=<`;:9{{}A@l}{c{{Al{A@l}}}D`}{{{l{A@l}}{l{nAb}}}Ad}{A@lFd}8`6{{}c{}}6{{{l{A@l}}{l{Cj}}}{{Cd{C`}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{}A@n}{{}AA`}{c{{Al{A@n}}}D`}{c{{Al{AA`}}}D`}{A@nAA`}{{{l{A@n}}{l{nAb}}}Ad}{{{l{AA`}}{l{nAb}}}Ad}{A@nFd}0{cc{}}0`{{}c{}}0{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{A@n}}{l{Cj}}}{{Cd{C`}}}}{A@nAAb}0{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{}AAd}{c{{Al{AAd}}}D`}{c{{Cd{{AAh{AAf}}}}}D`}{{{l{AAd}}{l{nAb}}}Ad}{AAdFd}{cc{}}`{{}c{}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{AAd}}{l{Cj}}}{{Cd{C`}}}}{AAdBb}{AAdB`}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}`````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{AAjBb}{AAjB`}{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{c{{Al{AAl}}}D`}{c{{Al{AAj}}}D`}{{{l{AAl}}{l{nAb}}}Ad}{{{l{AAj}}{l{nAb}}}Ad}{AAjFd}7{cc{}}0`{{}c{}}0```{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{AAj}}{l{Cj}}}{{Cd{C`}}}}{AAjAAl}{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{}AAn}{c{{Al{AAn}}}D`}{{{l{AAn}}{l{nAb}}}Ad}>={{}c{}}=:987`654{{}AB`}{c{{Al{AB`}}}D`}{{{l{AB`}}{l{nAb}}}Ad}{cc{}}{{}c{}}5{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{}ABb}{c{{Al{ABb}}}D`}{c{{Cd{j}}}D`}{{{l{ABb}}{l{nAb}}}Ad}=<{{}c{}}<;:98`765{{}ABd}{c{{Al{ABd}}}D`}{{{l{ABd}}{l{nAb}}}Ad}{ABdFd}{cc{}}`{{}c{}}{ABdBb}7{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{ABd}}{l{Cj}}}{{Cd{C`}}}}{ABdH`}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}``````````````{ABfB`}{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}000{l{{l{c}}}{}}000{{{l{n}}}{{l{nc}}}{}}000{{{l{E`}}}E`}{{{l{ABf}}}ABf}{{{l{ABh}}}ABh}{{{l{ABj}}}ABj}{{l{l{nc}}}C`{}}000{{lCl}C`}000{{}ABh}{c{{Cd{E`}}}D`}{c{{Al{ABh}}}D`}{c{{Al{ABj}}}D`}{{{l{E`}}{l{E`}}}Bb}{{l{l{c}}}Bb{}}00{{{l{E`}}{l{nAb}}}Ad}{{{l{ABf}}{l{nAb}}}Ad}{{{l{ABh}}{l{nAb}}}Ad}{{{l{ABj}}{l{nAb}}}Ad}{cc{}}000{{{l{ABh}}{l{ABl}}}{{Cd{{B`{ABf}}}}}}{{{l{E`}}{l{nc}}}C`Oh}{{}c{}}000{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}000{lc{}}000{c{{Al{e}}}{}{}}000{{}{{Al{c}}}{}}000{lCh}000{ABfBb}{{}c{}}000````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}000{BdGh}{l{{l{c}}}{}}000{{{l{n}}}{{l{nc}}}{}}000{ABnABh}{{{l{AC`}}}AC`}{{l{l{nc}}}C`{}}{{lCl}C`}{CfABn}{CfCn}{{}AC`}{{}ABn}{c{{Al{Bd}}}D`}{c{{Al{AC`}}}D`}{c{{Al{Cf}}}D`}{c{{Al{ABn}}}D`}{BdLj}{BdFd}{ABnFd}10{ABnLj}{{{l{Bd}}{l{nAb}}}Ad}{{{l{AC`}}{l{nAb}}}Ad}{{{l{Cf}}{l{nAb}}}Ad}{{{l{ABn}}{l{nAb}}}Ad}{cc{}}000{BdACb}{{{l{AC`}}{l{Dn}}{B`{Fh}}}{{Cd{A`}}}}{AC`ACb}0{ABnB`}00{{}c{}}000{BdBb}{ABnBb}{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}000{BdAC`}676{lc{}}{c{{Al{e}}}{}{}}000{{}{{Al{c}}}{}}000{lCh}000{{}c{}}000````````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{{l{Hn}}}{{B`{{l{Hn}}}}}}{DbB`}{{{l{Db}}}Db}{{l{l{nc}}}C`{}}{{lCl}C`}{{{l{Hn}}}{{l{Dn}}}}{{{Ej{Hn}}}{{Al{{Ej{c}}{Ej{Hn}}}}}Hn}00{{{l{nHn}}}{{B`{{l{nc}}}}}Hn}00{{{l{Hn}}}{{B`{{l{c}}}}}Hn}00{{ACdc}{{Cd{e}}}{{ACf{{Md{Dn}}}}}{}}{{{Cd{ce}}g}{{Cd{c}}}{}{HnAChACj}{{ACf{{Md{Dn}}}}}}{{{l{ACl}}{l{nAb}}}Ad}0{{{l{Db}}{l{nAb}}}Ad}0{cc{}}0{{}c{}}0{{{l{Hn}}}Bb}00>{cDb{{ACf{{Md{Dn}}}}}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{ACde}{{Cd{g}}}{{ACf{{Md{Dn}}}}}{{ACn{}{{Bj{c}}}}}{}}{{{Cd{ce}}i}{{Cd{c}}}{}{HnAChACj}{{ACf{{Md{Dn}}}}}{{ACn{}{{Bj{g}}}}}}{{{l{Hn}}{l{nAD`}}}C`}{{{l{Hn}}{l{ADb}}{l{nADd}}}C`}000{ADf{{Cd{ce}}}{}ADh}{{{Cd{ce}}}{{Cd{cg}}}{}ADjADh}{{{l{Hn}}}{{B`{{l{Hn}}}}}}{{{l{Hn}}}ADl}{lc{}}{lA`}0{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{l{{Al{A`F`}}}}0{lCh}0{{}c{}}0``{ADnc{AE`AEb}}{{ADn{l{nc}}}C`AE`}{AEdc{AE`AEb}}{{AEd{l{nc}}}C`AE`}`````````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}000{l{{l{c}}}{}}000{{{l{n}}}{{l{nc}}}{}}000{{{l{AEf}}}AEf}{{{l{AEh}}}AEh}{{{l{AEj}}}AEj}{{l{l{nc}}}C`{}}00{{lCl}C`}00`{{{l{AEf}}{l{Dn}}}Bb}{{}AEh}{{}AEj}{{{l{AEj}}{l{AEj}}}Bb}{{l{l{c}}}Bb{}}00{{{l{AEl}}{l{nAb}}}Ad}0{{{l{AEf}}{l{nAb}}}Ad}{{{l{AEh}}{l{nAb}}}Ad}{{{l{AEj}}{l{nAb}}}Ad}{{{l{AEh}}}A`}`{DbAEl}{cc{}}0{A`AEh}11{AEjB`}{{{l{AEf}}}{{Gh{Lj}}}}{{}c{}}000{{{l{AEj}}}Bb}{AEjBb}{AEhAEj}{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}000``{{{l{AEf}}{l{AEn}}{l{AC`}}}{{Cd{{Eb{{Gh{AEh}}{Gh{AEh}}}}}}}}`{{{l{AEl}}}{{B`{{l{Hn}}}}}}`{AEhA`}{lc{}}00{lA`}{c{{Al{e}}}{}{}}000{{}{{Al{c}}}{}}000{l{{Al{A`F`}}}}{lCh}000<``{{}c{}}000{AF`l}0`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{{l{Fd}}}Fd}{{l{l{nc}}}C`{}}{{lCl}C`}{{}Fd}{c{{Cd{Fd}}}D`}{{{l{Fd}}{l{nAb}}}Ad}{cc{}}{{{l{Dn}}}{{Cd{Fdc}}}{}}{FdB`}{{}c{}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}3{lc{}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}{{{l{Fd}}{l{Dn}}}{{Cd{AEf}}}}{{{l{Fd}}{l{Fd}}}AEf}{{{l{Fd}}{l{AEf}}}AEf}{{{l{Fd}}{l{Dn}}{l{Dn}}}{{Cd{AEf}}}}``{{}Mj}{{}AFb}{{}AFd}{{}AFf}{{}AFh}`````````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0000000{l{{l{c}}}{}}0000000{{{l{n}}}{{l{nc}}}{}}00000006{{{l{AFj}}{l{nAb}}}Ad}{{{l{AFl}}{l{nAb}}}Ad}{{{l{AFn}}{l{nAb}}}Ad}{{{l{AFb}}{l{nAb}}}Ad}{{{l{AFf}}{l{nAb}}}Ad}{{{l{AFd}}{l{nAb}}}Ad}{{{l{AG`}}{l{nAb}}}Ad}{{{l{AFh}}{l{nAb}}}Ad}{{{l{AGb}}{l{AGd}}{l{AC`}}}{{Cd{A`AEl}}}}{{{l{AFj}}{l{AGd}}{l{AC`}}}{{Cd{A`AEl}}}}{{{l{AFl}}{l{AGd}}{l{AC`}}}{{Cd{A`AEl}}}}{{{l{AFn}}{l{AGd}}{l{AC`}}}{{Cd{A`AEl}}}}{{{l{AFb}}{l{AGd}}{l{AC`}}}{{Cd{A`AEl}}}}{{{l{AFf}}{l{AGd}}{l{AC`}}}{{Cd{A`AEl}}}}{{{l{AFd}}{l{AGd}}{l{AC`}}}{{Cd{A`AEl}}}}{{{l{AG`}}{l{AGd}}{l{AC`}}}{{Cd{A`AEl}}}}{{{l{AFh}}{l{AGd}}{l{AC`}}}{{Cd{A`AEl}}}}{cc{}}0000000{{{l{AGb}}}{{B`{Df}}}}{{{l{AFh}}}{{B`{Df}}}}{{}c{}}0000000{{{l{Dn}}{l{{Ed{AGf}}}}}{{Cd{{Ej{AGb}}}}}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0000000{c{{Al{e}}}{}{}}0000000{{}{{Al{c}}}{}}0000000{lCh}0000000{{}c{}}0000000{AGhGh}{AGjAGl}{AGhB`}{AGjAGn}``````````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00000{AH`Gh}{l{{l{c}}}{}}00000{{{l{n}}}{{l{nc}}}{}}00000{{{l{AGf}}{l{AGf}}}Bb}{{{l{AH`}}{l{AH`}}}Bb}{{{l{AHb}}{l{AHb}}}Bb}{{{l{AHd}}{l{AHd}}}Bb}{{{l{AHf}}{l{AHf}}}Bb}{{{l{AHh}}{l{AHh}}}Bb}{{l{l{c}}}Bb{}}00000000000000000{{{l{AGf}}{l{nAb}}}Ad}{{{l{AH`}}{l{nAb}}}Ad}{{{l{AHb}}{l{nAb}}}Ad}{{{l{AHd}}{l{nAb}}}Ad}{{{l{AHf}}{l{nAb}}}Ad}{{{l{AHh}}{l{nAb}}}Ad}{AHbB`}{cc{}}00000{{}c{}}00000{AGfl}{AH`l}{AHbl}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00000{{{l{Dn}}}{{Cd{AHh}}}}{{{l{AGf}}}{{Cd{c}}}AHj}{c{{Al{e}}}{}{}}00000{{}{{Al{c}}}{}}00000{lCh}00000{AGfB`}{{}c{}}00000``````````````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{AHlFh}Fh}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{{l{AHl}}}AHl}{{l{l{nc}}}C`{}}{{lCl}C`}{{{l{AHl}}{l{AHl}}}AHn}{{l{l{c}}}AHn{}}{FhAHl}0{{{l{AHl}}{l{AHl}}}Bb}{{l{l{c}}}Bb{}}00{{{l{AHl}}{l{nAb}}}Ad}0{cc{}}{{{l{Dn}}}{{Cd{AHl}}}}{{}c{}}{{{l{AHl}}}Bb}{{AHlAHl}AHl}{{}AHl}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{{{l{AHl}}{l{AHl}}}{{B`{AHn}}}}{lc{}}{lA`}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{l{{Al{A`F`}}}}{lCh}{{}c{}}{{}{{Eb{{AI`{{Eb{j{Gh{Lj}}}}}}{El{{Ej{AIb}}}}}}}}```````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00{l{{l{c}}}{}}00{{{l{n}}}{{l{nc}}}{}}00{{{l{AId}}}AId}{{l{l{nc}}}C`{}}{{lCl}C`}{{{l{AId}}{l{Dn}}}Bb}{{}AId}{{{l{AId}}{l{nAb}}}Ad}{{{l{AIf}}{l{nAb}}}Ad}{{{l{AIh}}{l{nAb}}}Ad}{cc{}}00{{{l{Dn}}}{{Cd{AId}}}}{{{l{AId}}{l{n{Gh{Lj}}}}}C`}{{}c{}}00{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00{{{l{AId}}{l{AEn}}{l{AC`}}}{{Cd{{Gh{AEh}}AEl}}}}{{{l{AIf}}{l{AEn}}{l{AC`}}}{{Cd{{Gh{AEh}}AEl}}}}{lc{}}{c{{Al{e}}}{}{}}{AHh{{Cd{AIdc}}}{}}1{AHf{{Cd{AIfc}}}{}}2{AHd{{Cd{AIhc}}}{}}{{}{{Al{c}}}{}}00{lCh}00{{}c{}}00{AIjB`}{AIjA`}{AIlA`}`````````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{AInAHl}AHl}{{{l{AIn}}}AIn}{{l{l{nc}}}C`{}}{{lCl}C`}{{AInFhAIn}{{Cd{Fh}}}}{{{l{AIn}}{l{AIn}}}Bb}{{l{l{c}}}Bb{}}00{{{l{AIn}}{l{nAb}}}Ad}0{cc{}}{{{l{Dn}}}{{Cd{AIn}}}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{lc{}}{lA`}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{l{{Al{A`F`}}}}{lCh}{{}c{}}`````````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{cAJ`AJb}{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}02{{{l{AJ`}}}AJ`}{{{l{AGd}}}AGd}{{l{l{nc}}}C`{}}0{{lCl}C`}0{{{AAh{AJd}}{B`{AJf}}}AJ`}{{{l{AJ`}}}{{l{AGb}}}}8{DfAJ`}{{}AJ`}{{{l{AJ`}}{l{nAb}}}Ad}{{{l{AGd}}{l{nAb}}}Ad}{cc{}}0={cAJ`{{ACf{{Md{Dn}}}}}}{{cFh}AJ`{{ACf{{Md{Dn}}}}}}{{cFhFhFh}AJ`{{ACf{{Md{Dn}}}}}}{AJ`AGd}{{}c{}}0{AJbFh}{{AJ`Bb}AJ`}{AJ`AEj}{AGdAJ`}{cAJ`AJb}{{cAIn}AJ`AJb}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}022{A`AJ`}{lc{}}0{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{{l{AGd}}}{{l{Dn}}}};{{}c{}}09{{AJ`{l{Dn}}}AJ`}{AJhAIn}{AJhFh}`````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00{DhB`}{l{{l{c}}}{}}00{{{l{n}}}{{l{nc}}}{}}00{DhA`}{{{l{Dh}}}Dh}{{{l{AJj}}}AJj}{{l{l{nc}}}C`{}}0{{lCl}C`}0777777777777{{}Dh}{{}AJl}{{}AJj}{c{{Al{Dh}}}D`}{c{{Al{AJl}}}D`}{c{{Al{AJj}}}D`}{{{l{AJl}}{l{Dd}}Df}{{Cd{Dh}}}}{{{l{Dh}}{l{nAb}}}Ad}{{{l{AJl}}{l{nAb}}}Ad}{{{l{AJj}}{l{nAb}}}Ad}{cc{}}{AJjAJl}11{DhB`}{{}c{}}00{DhA`}2{DhFh}0{{{l{AJl}}}{{Md{Dn}}}}{{}c{}}00{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0066666{lc{}}0{c{{Al{e}}}{}{}}00{{}{{Al{c}}}{}}00{lCh}00::{{}c{}}00````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{nAJn}}{AKb{A`AK`}}}C`}{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{{l{AJn}}}AJn}{{{l{AK`}}}AK`}{{l{l{nc}}}C`{}}0{{lCl}C`}0{{}AJn}{c{{Al{AJn}}}D`}{c{{Al{AK`}}}D`}{{{l{AJn}}{l{nAb}}}Ad}{{{l{AK`}}{l{nAb}}}Ad}{cc{}}{{{l{Dn}}}AK`}1{{{AKd{{l{Dn}}}}}AK`}{{{l{Dn}}}{{Cd{AJn}}}}{{{l{AJn}}{l{Dn}}{B`{Fh}}}{{B`{{l{Dn}}}}}}{{}c{}}0{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{lc{}}0{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0``{BbC`}````````{AKfB`}{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00{AKfAKh}22222{l{{l{c}}}{}}00{{{l{n}}}{{l{nc}}}{}}00{{{l{AKf}}}AKf}{{{l{AKj}}}AKj}{{{l{AKl}}}AKl}{{l{l{nc}}}C`{}}00{{lCl}C`}007{{}AKf}{{{l{AKf}}{l{nAb}}}Ad}{{{l{AKj}}{l{nAb}}}Ad}{{{l{AKl}}{l{nAb}}}Ad}{cc{}}00{AKfA`}0{{}c{}}00{AKfB`}00{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0022{{{l{AKf}}c}AlAKn}{{{l{AKj}}c}AlAKn}{{{l{AKl}}c}AlAKn}7{lc{}}00{c{{Al{e}}}{}{}}00{{}{{Al{c}}}{}}00{lCh}009{{}c{}}00`{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{ABlE`}{{{l{ABl}}}ABl}{{l{l{nc}}}C`{}}{{lCl}C`}{{{l{ABl}}{l{ABl}}}Bb}{{l{l{c}}}Bb{}}00{{BbDf}{{El{{Ej{AIb}}}}}}{{{l{ABl}}{l{nAb}}}Ad}{cc{}}{ABlj}{ABlB`}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{lc{}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}```````{AL`AKh}{ALbB`}10{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0000{{{l{nALd}}ALb}{{Cd{C`}}}}{l{{l{c}}}{}}0000{{{l{n}}}{{l{nc}}}{}}0000{{{l{ALd}}}ALd}{{{l{AL`}}}AL`}{{{l{ALb}}}ALb}{{{l{ALf}}}ALf}{{l{l{nc}}}C`{}}000{{lCl}C`}000`;:;:{{}ALd}{{}AL`}{{}ALh}{{}ALb}{{{l{ALd}}}{{l{c}}}{}}{{{l{nALd}}}{{l{nc}}}{}}{c{{Al{AL`}}}D`}{c{{Al{ALh}}}D`}{c{{Al{ALb}}}D`}{c{{Cd{ALf}}}D`}{AL`ALj}{ALbB`}{{{l{ALd}}{l{nAb}}}Ad}{{{l{AL`}}{l{nAb}}}Ad}{{{l{ALb}}{l{nAb}}}Ad}{{{l{ALf}}{l{nAb}}}Ad}{cc{}}0000{{{l{ALd}}AAb}{{Eb{AKhAKh}}}}{AL`AKh}70707070707{{}c{}}0000{{}c{}}000{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0000{ALhB`}`<;4;4;<;0{lc{}}000{ALh{{Cd{ALdc}}}{}}{c{{Al{e}}}{}{}}0000{{}{{Al{c}}}{}}0000{lCh}0000{{}c{}}0000:{ALbB`};0`{ALlA`}```````{ALnCl}{AM`Cl}{{ALnALn}c{}}{{AM`AM`}c{}}{{AKhAKh}c{}}{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}00{{FhFh}Bb}6{l{{l{c}}}{}}00{{{l{n}}}{{l{nc}}}{}}00{{{l{ALn}}}ALn}{{{l{AM`}}}AM`}{{{l{AKh}}}AKh}{{l{l{nc}}}C`{}}00{{lCl}C`}00{{}ALn}{{}AM`}{{}AKh}{c{{Cd{AKh}}}D`}{{{l{ALn}}{l{ALn}}}Bb}{{{l{AM`}}{l{AM`}}}Bb}{{{l{AKh}}{l{AKh}}}Bb}{{l{l{c}}}Bb{}}00{{{l{ALn}}{l{nAb}}}Ad}{{{l{AM`}}{l{nAb}}}Ad}{{{l{AKh}}{l{nAb}}}Ad}{AM`ALn}{cc{}}0{ALnAM`}1{H`ALn}{{{l{Dn}}}{{Cd{AKhc}}}{}}{ALnCl}{AM`Fh}{{}c{}}00{{ClClClCl}ALn}{{FhFhFhCl}AM`}{{}c{}}00{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0065{{{l{AKh}}c}CdAKn}{{{l{AKh}}}Bb}{lc{}}00{c{{Al{e}}}{}{}}00{{}{{Al{c}}}{}}00{lCh}00;{{}c{}}00```{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{l{{l{c}}}{}}{{{l{n}}}{{l{nc}}}{}}{{{l{ALj}}}ALj}{{l{l{nc}}}C`{}}{{lCl}C`}{{}ALj}{c{{Cd{ALj}}}D`}{{{l{ALj}}{l{ALj}}}Bb}{{l{l{c}}}Bb{}}00{{{l{ALj}}{l{nAb}}}Ad}{cc{}}{{{l{Dn}}}{{Cd{ALjc}}}{}}{{}c{}}{{}c{}}{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}{lc{}}{c{{Al{e}}}{}{}}{{}{{Al{c}}}{}}{lCh}{{}c{}}{{{l{Dn}}}{{Cd{{B`{{l{A`}}}}Db}}}}`{{{l{Dn}}}A`}{{}cAEb}{c{{Cd{e}}}{{AMd{AMb}}}AMf}{{{l{Dn}}{B`{{l{Dn}}}}{B`{{l{Dn}}}}}{{B`{AMh}}}}{{{l{{Ed{Fh}}}}}A`}{{{l{Dn}}}{{Cd{Bb}}}}`{{}{{Cd{AMj}}}}0{{{l{nAMl}}}{{`{{Eh{}{{Bj{{B`{c}}}}}}}}}{}}{c{{AMn{A`}}}{{AMd{AMb}}}}```````{g{{b{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{l{{l{c}}}{}}0{{{l{n}}}{{l{nc}}}{}}0{{{l{Ef}}}Ef}{{{l{AAb}}}AAb}{{l{l{nc}}}C`{}}0{{lCl}C`}0{{}Ef}{{}AAb}{c{{Al{AAb}}}D`}{{{l{AAb}}{l{AAb}}}Bb}{{l{l{c}}}Bb{}}00{{{l{Ef}}{l{nAb}}}Ad}{{{l{AAb}}{l{nAb}}}Ad}{cc{}}0{{{l{Ef}}{l{AC`}}j}{{Cd{{Gh{AKf}}}}}}{{{l{Ef}}}{{Gh{Lj}}}}{{}c{}}0:{{}c{}}0{g{{Bh{ig}}}{}{}{{d{ce}}}{{d{ce}}}}0{{{l{nEf}}AEf}C`}{{{l{nEf}}A`}C`}{{{l{nEf}}AEn}C`}{EfAAb}{lc{}}0{c{{Al{e}}}{}{}}0{{}{{Al{c}}}{}}0{lCh}0{{}c{}}0{{EfAEf}Ef}{{EfAAb}Ef}{{EfA`}Ef}","D":"CKb","p":[[5,"And",3219],[10,"Policy",3220],[5,"Command",3221],[5,"CliArgs",0],[1,"usize"],[1,"reference",null,null,1],[0,"mut"],[5,"String",3222],[5,"Formatter",3223],[8,"Result",3223],[5,"Block",0],[5,"ArgMatches",3224],[8,"Error",3225],[6,"Result",3226,null,1],[5,"Id",3227],[6,"Option",3228,null,1],[1,"bool"],[5,"Config",1957],[5,"BarState",0],[5,"Or",3229],[17,"Output"],[1,"never"],[1,"fn"],[1,"unit"],[5,"BlockError",67],[8,"Result",2040],[5,"BlockConfigEntry",1957],[5,"TypeId",3230],[5,"CommonApi",67],[1,"u8"],[6,"BlockConfig",67],[10,"Deserializer",3231],[5,"Error",2040],[5,"Client",3232],[5,"Duration",3233],[5,"IPAddressInfo",2676],[8,"BlockAction",67],[5,"UnboundedReceiver",3234],[1,"str"],[6,"MouseButton",1870],[1,"tuple",null,null,1],[1,"slice"],[5,"Widget",3163],[10,"Future",3235,null,1],[5,"Box",3236,null,1],[5,"Pin",3237],[5,"FuturesUnordered",3238],[5,"TryReserveError",3239],[5,"Config",214],[5,"Config",2215],[5,"Config",245],[1,"f64"],[5,"Config",272],[6,"BatteryDriver",272],[5,"Config",322],[5,"Config",341],[5,"SourceConfig",341],[6,"AuthConfig",341],[5,"OAuth2Config",341],[5,"Vec",3240],[5,"OAuth2Credentials",341],[5,"BasicAuthConfig",341],[5,"BasicCredentials",341],[1,"u32"],[6,"CalendarError",341],[6,"DeError",3241],[5,"Error",3242],[5,"Error",3243],[5,"Error",3244],[1,"u16"],[10,"StdError",2040],[5,"Config",526],[5,"Config",548],[5,"Config",573],[5,"Config",589],[5,"Config",609],[6,"InfoType",609],[5,"Config",655],[5,"Config",674],[6,"Driver",694],[5,"Config",694],[5,"Config",730],[5,"Config",754],[6,"HueShifter",754],[5,"Config",801],[6,"KeyboardLayoutDriver",825],[5,"Config",825],[5,"Config",871],[6,"MailType",892],[5,"Config",892],[5,"Config",934],[5,"Item",957],[5,"Config",957],[6,"PlayerName",993],[5,"Config",993],[5,"Config",1039],[6,"DriverType",1061],[5,"Config",1061],[5,"Config",1096],[5,"Config",1120],[1,"u64"],[5,"Config",1143],[6,"PackageManager",1143],[10,"Backend",1143],[5,"Regex",3245],[6,"Cow",3246],[5,"Apt",1204],[5,"Dnf",1220],[5,"LazyLock",3247],[5,"Pacman",1236],[5,"Aur",1236],[5,"Xbps",1266],[5,"Config",1282],[6,"PrivacyDriver",1305],[5,"Config",1305],[5,"Config",1337],[5,"Config",1356],[5,"Config",1373],[6,"DriverType",1373],[5,"Config",1412],[5,"IndexMap",3248],[6,"DeviceKind",1412],[6,"SoundDriver",1412],[10,"Hasher",3249],[5,"Config",1491],[5,"Filter",1509],[5,"Config",1509],[5,"Config",1555],[5,"Config",1574],[6,"TemperatureScale",1574],[6,"Timezone",1626],[5,"Config",1626],[5,"Config",1664],[5,"Config",1687],[5,"Config",1705],[6,"DriverType",1705],[6,"State",3163],[5,"Config",1744],[5,"Local",3250],[5,"DateTime",3251],[5,"Config",1765],[6,"WeatherService",1765],[5,"Config",1804],[5,"Config",1819],[5,"Config",1834],[5,"Config",1850],[5,"PostActions",1870],[5,"ClickHandler",1870],[5,"ClickConfigEntry",1870],[5,"I3BarEvent",2881],[5,"CommonBlockConfig",1957],[5,"SharedConfig",1957],[5,"Arc",3252,null,1],[10,"ErrorContext",2040],[10,"Into",3253,null,1],[10,"Send",3254],[10,"Sync",3254],[5,"BoxErrorWrapper",2040],[10,"FnOnce",3255],[5,"Request",3256],[5,"Field",3257],[10,"Visit",3257],[10,"ToSerdeError",2040],[10,"Error",3231],[10,"Display",3223],[5,"Source",3256],[10,"CollectEscaped",2110],[10,"Write",3223],[10,"Default",3258],[10,"Escaped",2110],[5,"Format",2116],[5,"Fragment",2116],[5,"Metadata",2116],[6,"FormatError",2116],[8,"Values",2116],[15,"IncompatibleFormatter",2213],[5,"DurationFormatter",2241,3259],[5,"FlagFormatter",2241,3260],[5,"EngFormatter",2241,3261],[5,"StrFormatter",2241,3262],[5,"BarFormatter",2241,3263],[5,"TallyFormatter",2241,3264],[6,"DatetimeFormatter",2241,3265],[5,"PangoStrFormatter",2241,3266],[10,"Formatter",2241],[6,"ValueInner",2610],[5,"Arg",2363],[15,"Chrono",2359],[15,"Icu",2359],[6,"Date",3267],[5,"Locale",3268],[5,"Formatter",2363],[5,"Placeholder",2363],[6,"Token",2363],[5,"TokenList",2363],[5,"FormatTemplate",2363],[10,"FromStr",3269],[6,"Prefix",2471],[6,"Ordering",3270],[5,"UnboundedSender",3234],[10,"Stream",3271],[5,"FormatTemplate",2519],[5,"TokenList",2519],[6,"Token",2519],[15,"Placeholder",2573],[15,"Icon",2573],[6,"Unit",2576],[5,"Value",2610],[10,"IntoF64",2610],[5,"Utc",3272],[6,"Tz",3273],[15,"Number",2674],[6,"GeolocatorBackend",2676],[5,"Geolocator",2676],[5,"Icons",2760],[6,"Icon",2760],[5,"HashMap",3274],[1,"array"],[5,"I3BarBlock",2806],[6,"Color",3039],[6,"I3BarBlockAlign",2806],[6,"I3BarBlockMinWidth",2806],[10,"Serializer",3275],[5,"ThemeInner",2905],[5,"ThemeOverrides",2905],[5,"Theme",2905],[6,"ColorOrLink",2905],[5,"ThemeUserConfig",2905],[6,"Separator",3124],[15,"Link",3038],[5,"Rgba",3039],[5,"Hsva",3039],[5,"Path",3276],[10,"AsRef",3253],[10,"DeserializeOwned",3231],[5,"PathBuf",3276],[5,"Connection",3277],[10,"StreamExtDebounced",3151],[8,"Result",3243,null,1],[5,"Device",214]],"r":[[2241,3263],[2242,3265],[2243,3265],[2244,3259],[2245,3260],[2246,3261],[2247,3262],[2248,3265],[2249,3259],[2250,3261],[2251,3260],[2253,3265],[2254,3266],[2255,3262],[2256,3264],[2257,3263],[2258,3264],[2259,3265],[2260,3259],[2261,3261],[2262,3260],[2263,3266],[2264,3262],[2265,3263],[2266,3264],[2267,3265],[2268,3259],[2269,3261],[2270,3260],[2271,3266],[2272,3262],[2273,3263],[2274,3264],[2275,3265],[2276,3259],[2277,3261],[2278,3260],[2279,3266],[2280,3262],[2281,3259],[2282,3263],[2283,3264],[2284,3265],[2285,3259],[2286,3261],[2287,3260],[2288,3266],[2289,3262],[2291,3263],[2292,3264],[2293,3265],[2294,3259],[2295,3261],[2296,3260],[2297,3266],[2298,3262],[2299,3263],[2300,3264],[2301,3265],[2302,3259],[2303,3261],[2304,3260],[2305,3266],[2306,3262],[2308,3262],[2309,3263],[2310,3264],[2311,3265],[2312,3259],[2313,3261],[2314,3260],[2315,3266],[2316,3262],[2318,3259],[2319,3263],[2320,3264],[2321,3265],[2322,3259],[2323,3261],[2324,3260],[2325,3266],[2326,3262],[2327,3263],[2328,3264],[2329,3265],[2330,3259],[2331,3261],[2332,3260],[2333,3266],[2334,3262],[2335,3263],[2336,3264],[2337,3265],[2338,3259],[2339,3261],[2340,3260],[2341,3266],[2342,3262],[2343,3263],[2344,3264],[2345,3265],[2346,3259],[2347,3261],[2348,3260],[2349,3266],[2350,3262],[2351,3263],[2352,3264],[2353,3265],[2354,3259],[2355,3261],[2356,3260],[2357,3266],[2358,3262],[3158,0]],"b":[[116,"impl-Display-for-BlockError"],[117,"impl-Debug-for-BlockError"],[429,"impl-Display-for-CalendarError"],[430,"impl-Debug-for-CalendarError"],[438,"impl-From%3CDeError%3E-for-CalendarError"],[439,"impl-From%3CError%3E-for-CalendarError"],[440,"impl-From%3CError%3E-for-CalendarError"],[441,"impl-From%3CError%3E-for-CalendarError"],[2060,"impl-dyn+Error+%2B+Send+%2B+Sync"],[2061,"impl-dyn+Error"],[2062,"impl-dyn+Error+%2B+Send"],[2063,"impl-dyn+Error"],[2064,"impl-dyn+Error+%2B+Send"],[2065,"impl-dyn+Error+%2B+Send+%2B+Sync"],[2066,"impl-dyn+Error"],[2067,"impl-dyn+Error+%2B+Send"],[2068,"impl-dyn+Error+%2B+Send+%2B+Sync"],[2071,"impl-Debug-for-BoxErrorWrapper"],[2072,"impl-Display-for-BoxErrorWrapper"],[2073,"impl-Debug-for-Error"],[2074,"impl-Display-for-Error"],[2079,"impl-dyn+Error+%2B+Send+%2B+Sync"],[2080,"impl-dyn+Error+%2B+Send"],[2081,"impl-dyn+Error"],[2089,"impl-Value-for-dyn+Error+%2B+Sync"],[2090,"impl-Value-for-dyn+Error+%2B+Send"],[2091,"impl-Value-for-dyn+Error+%2B+Send+%2B+Sync"],[2092,"impl-Value-for-dyn+Error"],[2154,"impl-Debug-for-FormatError"],[2155,"impl-Display-for-FormatError"],[2500,"impl-Display-for-Prefix"],[2501,"impl-Debug-for-Prefix"],[2597,"impl-Display-for-Unit"],[2598,"impl-Debug-for-Unit"],[2783,"impl-From%3C%26str%3E-for-Icon"],[2785,"impl-From%3C%5B%26str;+N%5D%3E-for-Icon"]],"c":"OjAAAAEAAAAAAAEAEAAAAAcIDAg=","e":"OzAAAAEAALoKAQEAAAIABAAEAAsACQAWAAEAGQABAB8AAQAiAAEAKQAAACsAGgBIAAEATAADAFEAAABTAAIAVwAGAF8AAwBkAAAAZgAAAGgAAQBrAAAAbQAAAG8AAQByAAAAdAACAHgAAAB8AAAAfgAAAIEAAACGAAAAiAAAAIoAAACMAAAAjgAAAJAAAACSAAEAlQAAAJcAAACZAAAAmwADAKAAAACiAAAApAAAAKYAAACoAAAAqgABAK8AAQCyAAAAtAAAALYAAAC4AAAAugACAL4ABgDGAAIAygAAAMwABADSAAAA1AAAANYACADgAAQA5wAAAOoAEgD+AAMABAEZACABCAArAQQAMgEaAE8BTwChAQAApQEVAMsBLwD8ARcAFQIDABoCAQAdAg0ALAICADACAQAzAhEARwIKAFMCAwBYAgAAWgIXAHQCBQB8AgEAgAITAJUCAgCZAgAAmwILAKgCAgCsAgAArgITAMQCBQDOAhEA4QICAOUCAwDqAhsABwMEAA4DAQASAxcAKwMEADIDGABNAwUAVQMAAFgDFABuAwIAcgMBAHUDFQCMAwUAlAMBAJgDFACuAwMAswMAALUDGgDUAxoA8QMFAPkDAAD8AxcAFQQEABsEAQAeBBEAMQQGADwEEABOBAIAUgQAAFQEEABmBAIAagQEAHAEHgCQBAgAmwQEAKIEFwC7BAAAvQQMAMsEAADNBBEA4QQBAOUEEgD5BAAA+wQOAAsFAgAQBRoALwUOAD8FAgBDBQAARQULAFIFAgBXBREAagUFAHIFAQB2BSYAoAUPALMFAQC4BR8A2QUCAN0FAADfBR8AAQYAAAQGEwAZBgMAHgYAACAGFAA3BgkAQwYFAEsGHABpBgQAcAYAAHMGFwCMBgIAkAYLAJ0GAgChBgAAowYQALYGBgC/BgAAwgYSANYGAwDbBgAA3QYbAPsGAAD+BgAAAAcQABIHAQAWBwkAIQcBACUHCQAwBwIANQcJAEAHAgBEBwAARgcPAFcHKgCGBwEAjAcxAL8HAwDEBwgA0QcGAN0HHAD8BwAAAAgMABYIBQAjCAUAKggFADIIBgA7CAUARQgtAHUIAAB4CAEAfggbAJsIFgCzCAEAtghFAAQJAQAOCWkAhAkjALYJEADICQAAygkJANUJGgDzCQEA+AkYABgKAAAaCg0AKQoAACsKBAAxCh8AUwoEAFoKPgCaCgIAngoCAKIKAAClCgAAqQo1AOAKAADiCgIA5woQAPkKIwAgCwAAJQsBACgLJwBRCwEAVAtBAJsLDACtCzQA4wsBAOYLGwADDAoAEAwAABMMAgAbDCIAPwwFAEYMAABIDAgAUwwAAFUMAQBYDAcAYQwOAHEMBgB7DAAAfgwVAA==","P":[[3,"B,E,P,T"],[6,""],[10,"T"],[17,""],[27,"T"],[30,""],[35,"U"],[39,""],[42,"B,E,P,T"],[46,""],[51,"U,T"],[54,"U"],[57,""],[63,"V"],[76,"B,E,P,T"],[83,""],[87,"T"],[95,""],[96,"T"],[97,""],[104,"D"],[111,""],[120,"T"],[123,""],[129,"U"],[146,""],[155,"B,E,P,T"],[170,""],[186,"T"],[187,""],[190,"U,T"],[193,"U"],[196,""],[204,"V"],[207,""],[216,"B,E,P,T"],[218,"T"],[222,""],[223,"__D"],[224,""],[228,"T"],[230,""],[231,"U"],[233,"T"],[234,"B,E,P,T"],[236,""],[237,"U,T"],[239,"U"],[241,""],[243,"V"],[246,"B,E,P,T"],[247,"T"],[249,""],[253,"__D"],[254,""],[257,"T"],[258,"U"],[259,""],[263,"T"],[264,"B,E,P,T"],[265,""],[268,"U,T"],[269,"U"],[270,""],[271,"V"],[277,"B,E,P,T"],[279,"T"],[283,""],[287,"__D"],[289,""],[296,"T"],[298,""],[303,"U"],[305,""],[308,"T"],[310,"B,E,P,T"],[312,""],[313,"U,T"],[315,"U"],[317,""],[319,"V"],[321,""],[324,"B,E,P,T"],[325,""],[326,"T"],[328,"__D"],[329,""],[332,"T"],[333,"U"],[334,""],[335,"B,E,P,T"],[336,""],[337,"U,T"],[338,"U"],[339,""],[340,"V"],[360,""],[361,"B,E,P,T"],[369,""],[372,"T"],[388,""],[398,"T"],[404,""],[420,"__D"],[427,""],[442,"T"],[450,"U"],[458,""],[460,"T"],[466,""],[467,"B,E,P,T"],[475,""],[482,"T"],[488,""],[490,"U,T"],[498,"U"],[506,""],[517,"V"],[525,""],[527,"B,E,P,T"],[528,"T"],[530,""],[532,"__D"],[533,""],[536,"T"],[537,""],[539,"U"],[540,"T"],[541,"B,E,P,T"],[542,""],[543,"U,T"],[544,"U"],[545,""],[546,"V"],[547,""],[549,"B,E,P,T"],[550,"T"],[552,""],[555,"__D"],[556,""],[558,"T"],[559,""],[561,"U"],[562,""],[563,"T"],[564,"B,E,P,T"],[565,""],[568,"U,T"],[569,"U"],[570,""],[571,"V"],[572,""],[574,"B,E,P,T"],[575,"T"],[577,"__D"],[578,""],[580,"T"],[581,"U"],[582,"B,E,P,T"],[583,""],[585,"U,T"],[586,"U"],[587,""],[588,"V"],[590,"B,E,P,T"],[591,"T"],[593,""],[594,"__D"],[595,""],[598,"T"],[599,""],[600,"U"],[601,""],[602,"T"],[603,"B,E,P,T"],[604,""],[605,"U,T"],[606,"U"],[607,""],[608,"V"],[614,""],[616,"B,E,P,T"],[618,"T"],[622,""],[623,"T"],[624,""],[627,"__D"],[629,""],[633,"T"],[635,""],[637,"U"],[639,"T"],[641,"B,E,P,T"],[643,""],[645,"T"],[646,"U,T"],[648,"U"],[650,""],[652,"V"],[654,""],[656,"B,E,P,T"],[657,"T"],[659,""],[660,"__D"],[661,""],[663,"T"],[664,""],[665,"U"],[666,"T"],[667,"B,E,P,T"],[668,""],[670,"U,T"],[671,"U"],[672,""],[673,"V"],[675,"B,E,P,T"],[676,"T"],[678,""],[679,"__D"],[680,""],[682,"T"],[683,""],[684,"U"],[685,"T"],[686,"B,E,P,T"],[687,""],[688,"U,T"],[689,"U"],[690,""],[692,"V"],[693,""],[699,"B,E,P,T"],[701,"T"],[705,""],[707,"__D"],[709,""],[713,"T"],[715,"U"],[717,"T"],[719,"B,E,P,T"],[721,""],[722,"U,T"],[724,"U"],[726,""],[728,"V"],[731,"B,E,P,T"],[732,"T"],[734,""],[736,"__D"],[737,""],[739,"T"],[740,""],[744,"U"],[745,"T"],[746,"B,E,P,T"],[747,""],[749,"U,T"],[750,"U"],[751,""],[752,"V"],[753,""],[762,"B,E,P,T"],[764,"T"],[768,""],[770,"T"],[771,""],[774,"__D"],[776,""],[779,"T"],[781,""],[783,"U"],[785,""],[787,"T"],[788,"B,E,P,T"],[790,""],[792,"T"],[793,"U,T"],[795,"U"],[797,""],[799,"V"],[802,"B,E,P,T"],[803,""],[807,"T"],[809,""],[810,"__D"],[811,""],[815,"T"],[816,"U"],[817,""],[818,"T"],[819,"B,E,P,T"],[820,""],[821,"U,T"],[822,"U"],[823,""],[824,"V"],[833,"B,E,P,T"],[835,"T"],[839,""],[840,"T"],[841,""],[844,"__D"],[846,""],[850,"T"],[852,""],[853,"U"],[855,""],[856,"T"],[858,"B,E,P,T"],[860,""],[862,"T"],[863,"U,T"],[865,"U"],[867,""],[869,"V"],[872,"B,E,P,T"],[873,"T"],[875,""],[877,"__D"],[878,""],[880,"T"],[881,""],[883,"U"],[884,"T"],[885,"B,E,P,T"],[886,""],[887,"U,T"],[888,"U"],[889,""],[890,"V"],[891,""],[897,"B,E,P,T"],[899,"T"],[903,""],[904,"T"],[905,""],[907,"__D"],[909,""],[913,"T"],[915,""],[917,"U"],[919,"T"],[920,"B,E,P,T"],[922,""],[925,"T"],[926,"U,T"],[928,"U"],[930,""],[932,"V"],[935,"B,E,P,T"],[936,"T"],[938,""],[941,"__D"],[942,""],[945,"T"],[946,""],[947,"U"],[948,"T"],[949,"B,E,P,T"],[950,""],[951,"U,T"],[952,"U"],[953,""],[954,"V"],[955,""],[959,"B,E,P,T"],[961,"T"],[965,""],[966,"T"],[967,""],[970,"__D"],[972,""],[975,"T"],[977,"U"],[979,""],[980,"B,E,P,T"],[982,""],[984,"T"],[985,"U,T"],[987,"U"],[989,""],[991,"V"],[997,"B,E,P,T"],[999,"T"],[1003,""],[1004,"T"],[1005,""],[1008,"__D"],[1010,""],[1014,"T"],[1016,""],[1017,"U"],[1019,"T"],[1021,"B,E,P,T"],[1023,""],[1029,"T"],[1030,"U,T"],[1032,"U"],[1034,""],[1037,"V"],[1040,"B,E,P,T"],[1041,"T"],[1043,""],[1044,"__D"],[1045,""],[1049,"T"],[1050,""],[1052,"U"],[1053,""],[1054,"T"],[1055,"B,E,P,T"],[1056,""],[1057,"U,T"],[1058,"U"],[1059,""],[1060,"V"],[1065,"B,E,P,T"],[1067,"T"],[1071,""],[1073,"__D"],[1075,""],[1079,"T"],[1081,"U"],[1083,"T"],[1085,"B,E,P,T"],[1087,""],[1088,"U,T"],[1090,"U"],[1092,""],[1094,"V"],[1097,"B,E,P,T"],[1098,"T"],[1100,""],[1101,"__D"],[1102,""],[1104,"T"],[1105,""],[1106,"U"],[1107,""],[1108,"T"],[1109,"B,E,P,T"],[1110,""],[1116,"U,T"],[1117,"U"],[1118,""],[1119,"V"],[1121,"B,E,P,T"],[1122,"T"],[1124,""],[1125,"__D"],[1126,""],[1128,"T"],[1129,""],[1134,"U"],[1135,"T"],[1136,"B,E,P,T"],[1137,""],[1138,"U,T"],[1139,"U"],[1140,""],[1141,"V"],[1142,""],[1151,"B,E,P,T"],[1154,""],[1155,"T"],[1159,""],[1161,"T"],[1163,""],[1167,"__D"],[1170,""],[1176,"T"],[1178,""],[1183,"U"],[1185,""],[1186,"T"],[1187,"B,E,P,T"],[1189,""],[1192,"T"],[1194,"U,T"],[1196,"U"],[1198,""],[1200,"V"],[1202,""],[1205,"B,E,P,T"],[1206,"T"],[1208,""],[1209,"T"],[1210,""],[1211,"U"],[1212,""],[1214,"T"],[1215,"B,E,P,T"],[1216,"U,T"],[1217,"U"],[1218,""],[1219,"V"],[1221,"B,E,P,T"],[1222,"T"],[1224,""],[1225,"T"],[1226,""],[1227,"U"],[1228,""],[1230,"T"],[1231,"B,E,P,T"],[1232,"U,T"],[1233,"U"],[1234,""],[1235,"V"],[1237,""],[1240,"B,E,P,T"],[1242,"T"],[1248,""],[1250,"U"],[1252,""],[1256,"B,E,P,T"],[1258,"U,T"],[1260,"U"],[1262,""],[1264,"V"],[1267,"B,E,P,T"],[1268,"T"],[1270,""],[1271,"T"],[1272,""],[1273,"U"],[1274,""],[1276,"T"],[1277,"B,E,P,T"],[1278,"U,T"],[1279,"U"],[1280,""],[1281,"V"],[1283,"B,E,P,T"],[1284,""],[1285,"T"],[1287,""],[1290,"__D"],[1291,""],[1293,"T"],[1294,"U"],[1295,""],[1297,"T"],[1298,"B,E,P,T"],[1299,""],[1301,"U,T"],[1302,"U"],[1303,""],[1304,"V"],[1309,"B,E,P,T"],[1311,"T"],[1315,"__D"],[1317,""],[1322,"T"],[1324,"U"],[1326,"B,E,P,T"],[1328,""],[1329,"U,T"],[1331,"U"],[1333,""],[1335,"V"],[1338,"B,E,P,T"],[1339,"T"],[1341,""],[1342,"__D"],[1343,""],[1345,"T"],[1346,""],[1347,"U"],[1348,"T"],[1349,"B,E,P,T"],[1350,""],[1352,"U,T"],[1353,"U"],[1354,""],[1355,"V"],[1357,"B,E,P,T"],[1358,"T"],[1360,""],[1361,"__D"],[1362,""],[1364,"T"],[1365,"U"],[1366,"T"],[1367,"B,E,P,T"],[1368,""],[1369,"U,T"],[1370,"U"],[1371,""],[1372,"V"],[1376,""],[1378,"B,E,P,T"],[1380,"T"],[1384,""],[1386,"__D"],[1388,""],[1391,"T"],[1393,""],[1395,"U"],[1397,"T"],[1399,"B,E,P,T"],[1401,""],[1403,"U,T"],[1405,"U"],[1407,""],[1410,"V"],[1420,""],[1421,"B,E,P,T"],[1424,"T"],[1430,""],[1432,"T"],[1434,""],[1440,"__D"],[1443,""],[1447,"K"],[1450,""],[1455,"T"],[1458,"__H"],[1459,""],[1460,"U"],[1463,""],[1468,"T"],[1471,"B,E,P,T"],[1474,""],[1477,"T"],[1479,"U,T"],[1482,"U"],[1485,""],[1488,"V"],[1492,"B,E,P,T"],[1493,"T"],[1495,""],[1496,"__D"],[1497,""],[1499,"T"],[1500,""],[1501,"U"],[1502,"T"],[1503,"B,E,P,T"],[1504,""],[1505,"U,T"],[1506,"U"],[1507,""],[1508,"V"],[1511,"B,E,P,T"],[1513,"T"],[1517,""],[1518,"T"],[1519,""],[1525,"__D"],[1527,""],[1534,"T"],[1536,""],[1537,"U"],[1539,""],[1540,"T"],[1542,"B,E,P,T"],[1544,""],[1545,"T"],[1546,"U,T"],[1548,"U"],[1550,""],[1552,"V"],[1554,""],[1556,"B,E,P,T"],[1557,"T"],[1559,""],[1560,"__D"],[1561,""],[1564,"T"],[1565,""],[1566,"U"],[1567,"T"],[1568,"B,E,P,T"],[1569,""],[1570,"U,T"],[1571,"U"],[1572,""],[1573,"V"],[1578,"B,E,P,T"],[1580,"T"],[1584,""],[1586,"T"],[1587,""],[1590,"__D"],[1592,""],[1593,"K"],[1596,""],[1600,"T"],[1602,""],[1608,"U"],[1610,"T"],[1612,"B,E,P,T"],[1614,""],[1616,"T"],[1617,"U,T"],[1619,"U"],[1621,""],[1623,"V"],[1625,""],[1630,"B,E,P,T"],[1632,"T"],[1636,""],[1637,"T"],[1638,""],[1640,"__D"],[1642,""],[1645,"T"],[1647,""],[1648,"U"],[1650,"T"],[1651,"B,E,P,T"],[1653,""],[1655,"T"],[1656,"U,T"],[1658,"U"],[1660,""],[1662,"V"],[1665,"B,E,P,T"],[1666,"T"],[1668,""],[1671,"__D"],[1672,""],[1674,"T"],[1675,""],[1678,"U"],[1679,"B,E,P,T"],[1680,""],[1683,"U,T"],[1684,"U"],[1685,""],[1686,"V"],[1688,"B,E,P,T"],[1689,"T"],[1691,""],[1692,"__D"],[1693,""],[1695,"T"],[1696,""],[1697,"U"],[1698,"T"],[1699,"B,E,P,T"],[1700,""],[1701,"U,T"],[1702,"U"],[1703,""],[1704,"V"],[1709,"B,E,P,T"],[1711,"T"],[1715,""],[1717,"__D"],[1719,""],[1724,"T"],[1726,""],[1727,"U"],[1729,"T"],[1731,"B,E,P,T"],[1733,""],[1736,"U,T"],[1738,"U"],[1740,""],[1742,"V"],[1745,"B,E,P,T"],[1746,"T"],[1748,""],[1749,"__D"],[1750,"D"],[1751,""],[1753,"T"],[1754,""],[1755,"U"],[1756,"T"],[1757,"B,E,P,T"],[1758,""],[1761,"U,T"],[1762,"U"],[1763,""],[1764,"V"],[1770,"B,E,P,T"],[1772,""],[1774,"T"],[1778,"__D"],[1780,""],[1784,"T"],[1786,""],[1787,"U"],[1792,"B,E,P,T"],[1794,""],[1796,"U,T"],[1798,"U"],[1800,""],[1802,"V"],[1805,"B,E,P,T"],[1806,"T"],[1808,""],[1809,"__D"],[1810,""],[1811,"T"],[1812,"U"],[1813,"T"],[1814,"B,E,P,T"],[1815,"U,T"],[1816,"U"],[1817,""],[1818,"V"],[1820,"B,E,P,T"],[1821,"T"],[1823,""],[1824,"__D"],[1825,""],[1826,"T"],[1827,"U"],[1828,"T"],[1829,"B,E,P,T"],[1830,"U,T"],[1831,"U"],[1832,""],[1833,"V"],[1835,"B,E,P,T"],[1836,"T"],[1838,""],[1839,"__D"],[1840,"D"],[1841,""],[1842,"T"],[1843,"U"],[1844,"T"],[1845,"B,E,P,T"],[1846,"U,T"],[1847,"U"],[1848,""],[1849,"V"],[1851,"B,E,P,T"],[1852,"T"],[1854,""],[1855,"__D"],[1856,""],[1858,"T"],[1859,""],[1860,"U"],[1861,""],[1862,"T"],[1863,"B,E,P,T"],[1864,""],[1866,"U,T"],[1867,"U"],[1868,""],[1869,"V"],[1884,""],[1885,"B,E,P,T"],[1889,"T"],[1897,""],[1901,"T"],[1905,""],[1910,"D"],[1911,"__D"],[1913,""],[1914,"K"],[1917,""],[1921,"T"],[1925,""],[1926,"__H"],[1927,"U"],[1931,"T"],[1932,"B,E,P,T"],[1936,"T"],[1940,"U,T"],[1944,"U"],[1948,""],[1953,"V"],[1961,"B,E,P,T"],[1965,""],[1966,"T"],[1974,""],[1976,"T"],[1977,""],[1982,"__D"],[1986,""],[1996,"T"],[2000,""],[2007,"U"],[2011,""],[2013,"T"],[2015,"B,E,P,T"],[2019,""],[2023,"T"],[2024,"U,T"],[2028,"U"],[2032,""],[2036,"V"],[2048,"B,E,P,T"],[2050,"T"],[2054,""],[2057,"T"],[2058,""],[2060,"T"],[2069,"M,T"],[2070,"T,E,M"],[2071,""],[2075,"T"],[2077,"U"],[2079,""],[2083,"T"],[2084,"B,E,P,T"],[2086,"M,F,T"],[2087,"T,E,M,F"],[2088,""],[2093,"T,E"],[2094,"T,F,E"],[2095,""],[2097,"T"],[2098,""],[2100,"U,T"],[2102,"U"],[2104,""],[2108,"V"],[2112,"T"],[2125,"B,E,P,T"],[2129,"T"],[2137,""],[2140,"T"],[2143,""],[2151,"K"],[2154,""],[2162,"T"],[2164,""],[2165,"T"],[2167,""],[2169,"U"],[2173,""],[2176,"T"],[2178,"B,E,P,T"],[2184,""],[2189,"T"],[2192,""],[2193,"U,T"],[2197,"U"],[2201,""],[2209,"V"],[2213,""],[2216,"B,E,P,T"],[2217,"T"],[2219,""],[2220,"T"],[2221,""],[2223,"D"],[2224,""],[2225,"T"],[2226,"FromStr::Err"],[2227,""],[2228,"U"],[2229,"T"],[2230,"B,E,P,T"],[2231,""],[2232,"T"],[2233,"U,T"],[2234,"U"],[2235,""],[2236,"V"],[2237,""],[2257,"B,E,P,T"],[2265,"T"],[2281,""],[2299,"T"],[2307,""],[2309,"U"],[2317,""],[2318,"T"],[2319,"B,E,P,T"],[2327,"U,T"],[2335,"U"],[2343,""],[2351,"V"],[2359,""],[2373,"B,E,P,T"],[2379,""],[2380,"T"],[2392,""],[2398,"K"],[2416,""],[2423,"T"],[2429,"U"],[2435,""],[2438,"B,E,P,T"],[2444,""],[2445,"T"],[2446,"U,T"],[2452,"U"],[2458,""],[2465,"V"],[2485,"B,E,P,T"],[2486,""],[2487,"T"],[2489,""],[2490,"T"],[2491,""],[2493,"K"],[2494,""],[2497,"K"],[2500,""],[2502,"T"],[2503,""],[2504,"U"],[2505,""],[2509,"B,E,P,T"],[2510,""],[2511,"T"],[2512,""],[2513,"U,T"],[2514,"U"],[2515,""],[2517,"V"],[2518,""],[2526,"B,E,P,T"],[2529,"T"],[2535,""],[2536,"T"],[2537,""],[2543,"T"],[2546,""],[2548,"U"],[2551,"T"],[2552,"B,E,P,T"],[2555,""],[2557,"T"],[2558,"U,T"],[2559,"TryFrom::Error"],[2560,"U,T"],[2561,"TryFrom::Error"],[2562,"U,T"],[2563,"TryFrom::Error"],[2564,"U"],[2567,""],[2570,"V"],[2573,""],[2585,"B,E,P,T"],[2586,"T"],[2588,""],[2590,"T"],[2591,""],[2594,"K"],[2597,""],[2599,"T"],[2600,""],[2601,"U"],[2602,"B,E,P,T"],[2603,"T"],[2604,""],[2605,"U,T"],[2606,"U"],[2607,""],[2609,"V"],[2619,"B,E,P,T"],[2621,""],[2622,"T"],[2626,""],[2629,"T"],[2631,""],[2640,"T"],[2642,""],[2643,"S"],[2646,""],[2647,"U"],[2649,""],[2655,"B,E,P,T"],[2657,""],[2660,"T"],[2662,"U,T"],[2664,"U"],[2666,""],[2670,"V"],[2672,""],[2681,"B,E,P,T"],[2684,""],[2685,"T"],[2691,""],[2694,"T"],[2696,""],[2713,"__D"],[2716,""],[2720,"T"],[2721,""],[2722,"T"],[2724,""],[2725,"U"],[2728,""],[2733,"T"],[2736,"B,E,P,T"],[2739,""],[2744,"T"],[2746,"U,T"],[2749,"U"],[2752,""],[2757,"V"],[2764,"B,E,P,T"],[2766,""],[2767,"T"],[2771,""],[2773,"T"],[2775,""],[2778,"__D"],[2780,""],[2782,"T"],[2783,""],[2784,"T"],[2785,""],[2788,"U"],[2790,"T"],[2791,"B,E,P,T"],[2793,"T"],[2795,"U,T"],[2797,"U"],[2799,""],[2801,"V"],[2805,""],[2815,"B,E,P,T"],[2818,""],[2824,"T"],[2830,""],[2833,"T"],[2836,""],[2844,"T"],[2847,""],[2849,"U"],[2852,""],[2855,"T"],[2856,"B,E,P,T"],[2859,""],[2861,"__S"],[2864,""],[2865,"T"],[2868,"U,T"],[2871,"U"],[2874,""],[2878,"V"],[2882,"B,E,P,T"],[2883,"T"],[2885,""],[2887,"T"],[2888,""],[2890,"K"],[2893,""],[2895,"T"],[2896,""],[2898,"U"],[2899,"B,E,P,T"],[2900,"T"],[2901,"U,T"],[2902,"U"],[2903,""],[2904,"V"],[2912,""],[2916,"B,E,P,T"],[2921,""],[2922,"T"],[2932,""],[2936,"T"],[2940,""],[2953,"Deref::Target"],[2955,"__D"],[2958,"D"],[2959,""],[2965,"T"],[2970,""],[2983,"U"],[2988,"T"],[2992,"B,E,P,T"],[2997,""],[3008,"T"],[3012,"TryFrom::Error"],[3013,"U,T"],[3018,"U"],[3023,""],[3028,"V"],[3033,""],[3048,"Add::Output"],[3051,"B,E,P,T"],[3054,""],[3056,"T"],[3062,""],[3065,"T"],[3068,""],[3074,"D"],[3075,""],[3078,"K"],[3081,""],[3085,"T"],[3087,""],[3088,"T"],[3089,""],[3090,"FromStr::Err"],[3091,""],[3093,"U"],[3096,""],[3098,"T"],[3101,"B,E,P,T"],[3104,""],[3106,"S"],[3107,""],[3108,"T"],[3111,"U,T"],[3114,"U"],[3117,""],[3121,"V"],[3127,"B,E,P,T"],[3128,"T"],[3130,""],[3131,"T"],[3132,""],[3134,"D"],[3135,""],[3136,"K"],[3139,""],[3140,"T"],[3141,"FromStr::Err"],[3142,"U"],[3143,"T"],[3144,"B,E,P,T"],[3145,"T"],[3146,"U,T"],[3147,"U"],[3148,""],[3149,"V"],[3150,""],[3153,"T"],[3154,"P,T"],[3155,""],[3161,"Stream::Item"],[3162,""],[3170,"B,E,P,T"],[3172,"T"],[3176,""],[3178,"T"],[3180,""],[3184,"__D"],[3185,""],[3186,"K"],[3189,""],[3191,"T"],[3193,""],[3195,"U"],[3197,""],[3198,"T"],[3200,"B,E,P,T"],[3202,""],[3206,"T"],[3208,"U,T"],[3210,"U"],[3212,""],[3214,"V"],[3216,""]]}]]')); +if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; +else if (window.initSearch) window.initSearch(searchIndex); +//{"start":39,"fragment_lengths":[86088]} \ No newline at end of file diff --git a/search.desc/i3status_rs/i3status_rs-desc-0-.js b/search.desc/i3status_rs/i3status_rs-desc-0-.js new file mode 100644 index 0000000000..c0250473f0 --- /dev/null +++ b/search.desc/i3status_rs/i3status_rs-desc-0-.js @@ -0,0 +1 @@ +searchState.loadedDescShard("i3status_rs", 0, "A feature-rich and resource-friendly replacement for …\nThe maximum number of blocking threads spawned by tokio\nThe collection of blocks\nSets a TOML config file\nSimple json escaping\nFormatting system\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nGeolocation service\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nExample\nIgnore any attempts by i3 to pause the bar when …\nDo not send the init sequence\nAn error which originates from a block\nBorrowed data.\nOwned data.\nDisplay the stats of your AMD GPU\nThe brightness of a backlight device\nInformation about the internal power supply\nMonitor Bluetooth device\nCalendar\nCPU statistics\nThe output of a custom shell command\nA block controlled by the DBus\nDisk I/O statistics\nDisk usage statistics\nLocal docker daemon status\nExternal IP address and various information about it\nNo-op if last API call was made in the last interval …\nCurrently focused window\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nThe number of GitHub notifications\nHides the block. Send new widget to make it visible again.\nManage display temperature\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nKDEConnect indicator\nKeyboard layout indicator\nSystem load average\nUnread mail. Only supports maildir format.\nMemory and swap usage\nA custom menu\nThe current song title and artist\nNetwork information\nDisplay and toggle the state of notifications daemon\nCount of notmuch messages\nDisplay the stats of your NVidia GPU\nPending updates for different package manager like apt, …\nA pomodoro timer\nPrivacy Monitor\nThe number of pending notifications in rofication-daemon\nScratchpad indicator\nDisplay the status of a service\nSends the error to be displayed.\nSends the widget to be displayed.\nVolume level\nPing, download, and upload speeds\nThe number of tasks from the taskwarrior list\nTimer\nThe system temperature\nThe current time.\nA Toggle block\nToString::to_string, but without panic on OOM.\nSystem’s uptime\nShows the current connection status for VPN networks\nWatson statistics\nCurrent weather\nX11 screen information\nReturn …\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn BatteryDriver::Sysfs\nReturn …\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturn …\nReturn …\nReturn …\nReturn …\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nToString::to_string, but without panic on OOM.\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturn InfoType::Available\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn Driver::Auto\nReturn …\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn KeyboardLayoutDriver::XkbEvent\nReturn …\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn PlayerName::Multiple(Default::default())\nReturn …\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn DriverType::Dunst\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturns the argument unchanged.\nCalls U::from(self).\nReturns the argument unchanged.\nCalls U::from(self).\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn Config { format: Default::default() }\nReturns the argument unchanged.\nCalls U::from(self).\nReturn DriverType::Systemd\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn DeviceKind::Sink\nReturn SoundDriver::Auto\nReturn …\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn TemperatureScale::Celsius\nReturn …\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturn DriverType::Nordvpn\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nSupport for using the US National Weather Service API.\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nReturn …\nReturns the argument unchanged.\nCalls U::from(self).\nCan be one of left, middle, right, up/wheel_up, down/…\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nReturn …\nThe maximum delay (ms) between two clicks that are …\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nSet to true to invert mouse wheel direction\nContains the error value\nError type\nContains the success value\nResult type returned from functions that can have our Error…\nError is a trait representing the basic expectations for …\nAttempts to downcast the box to a concrete type.\nAttempts to downcast the box to a concrete type.\nAttempts to downcast the box to a concrete type.\nReturns some mutable reference to the inner value if it is …\nForwards to the method defined on the type dyn Error.\nForwards to the method defined on the type dyn Error.\nReturns some reference to the inner value if it is of type …\nForwards to the method defined on the type dyn Error.\nForwards to the method defined on the type dyn Error.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nForwards to the method defined on the type dyn Error.\nForwards to the method defined on the type dyn Error.\nReturns true if the inner type is the same as T.\nProvides type-based access to context intended for error …\nReturns the lower-level source of this error, if any.\nReturns an iterator starting with the current error and …\nToString::to_string, but without panic on OOM.\nToString::to_string, but without panic on OOM.\nWrite escaped version of self to a new buffer\nWrite escaped version of self to out\nWrite escaped version of self to a new buffer\nWrite escaped version of self to out\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nToString::to_string, but without panic on OOM.\nReturns the argument unchanged.\nCalls U::from(self).\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nGi\nG\nKi\nK\nMi\nM\nu\nm\nn\n1\n1i 1i is a special prefix which means “one but binary”…\nSI prefix\nTi\nT\nReturns the argument unchanged.\nCalls U::from(self).\nToString::to_string, but without panic on OOM.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nb\nB\ndeg\nHz\n``\n%\ns\nW\nReturns the argument unchanged.\nCalls U::from(self).\nToString::to_string, but without panic on OOM.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nReturn GeolocatorBackend::Ipapi(Default::default())\nNo-op if last API call was made in the last interval …\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nRepresent block as described in …\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nThis project uses instance field to uniquely identify each …\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nThis project uses name field to uniquely identify each “…\nReturns the argument unchanged.\nCalls U::from(self).\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nAn HSVA color (hue, saturation, value, alpha).\nAn RGBA color (red, green, blue, alpha).\nReturn Color::None\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nCreate a new RGBA color from the hex value.\nCalls U::from(self).\nCalls U::from(self).\nCalls U::from(self).\nCreate a new RGBA color.\nCreate a new HSVA color.\nReturn Separator::Native\nReturns the argument unchanged.\nCalls U::from(self).\nConvert 2 letter country code to Unicode\nA shortcut for Default::default() See …\nTries to find a file in standard locations:\nExample\nState of the widget. Affects the theming.\nReturn State::Idle\nReturns the argument unchanged.\nReturns the argument unchanged.\nConstruct I3BarBlock from this widget\nCalls U::from(self).\nCalls U::from(self).") \ No newline at end of file diff --git a/settings.html b/settings.html new file mode 100644 index 0000000000..807472f0c0 --- /dev/null +++ b/settings.html @@ -0,0 +1 @@ +Settings

Rustdoc settings

Back
\ No newline at end of file diff --git a/src-files.js b/src-files.js new file mode 100644 index 0000000000..3b521e7e0d --- /dev/null +++ b/src-files.js @@ -0,0 +1,2 @@ +createSrcSidebar('[["i3status_rs",["",[["blocks",[["battery",[],["apc_ups.rs","sysfs.rs","upower.rs"]],["calendar",[],["auth.rs","caldav.rs"]],["focused_window",[],["sway_ipc.rs","wlr_toplevel_management.rs"]],["kdeconnect",[],["battery.rs","connectivity_report.rs"]],["keyboard_layout",[],["kbdd_bus.rs","locale_bus.rs","sway.rs","xkb_event.rs"]],["music",[],["zbus_mpris.rs","zbus_playerctld.rs"]],["packages",[],["apt.rs","dnf.rs","pacman.rs","xbps.rs"]],["privacy",[],["pipewire.rs","v4l.rs"]],["sound",[],["alsa.rs","pulseaudio.rs"]],["vpn",[],["mullvad.rs","nordvpn.rs"]],["weather",[],["met_no.rs","nws.rs","open_weather_map.rs"]]],["amd_gpu.rs","backlight.rs","battery.rs","bluetooth.rs","calendar.rs","cpu.rs","custom.rs","custom_dbus.rs","disk_iostats.rs","disk_space.rs","docker.rs","external_ip.rs","focused_window.rs","github.rs","hueshift.rs","kdeconnect.rs","keyboard_layout.rs","load.rs","maildir.rs","memory.rs","menu.rs","music.rs","net.rs","notify.rs","notmuch.rs","nvidia_gpu.rs","packages.rs","pomodoro.rs","prelude.rs","privacy.rs","rofication.rs","scratchpad.rs","service_status.rs","sound.rs","speedtest.rs","taskwarrior.rs","tea_timer.rs","temperature.rs","time.rs","toggle.rs","uptime.rs","vpn.rs","watson.rs","weather.rs","xrandr.rs"]],["formatting",[["formatter",[],["bar.rs","datetime.rs","duration.rs","eng.rs","flag.rs","pango.rs","str.rs","tally.rs"]]],["config.rs","formatter.rs","parse.rs","prefix.rs","scheduling.rs","template.rs","unit.rs","value.rs"]],["geolocator",[],["ip2location.rs","ipapi.rs"]],["protocol",[],["i3bar_block.rs","i3bar_event.rs"]],["themes",[],["color.rs","separator.rs","xresources.rs"]]],["blocks.rs","click.rs","config.rs","errors.rs","escape.rs","formatting.rs","geolocator.rs","icons.rs","lib.rs","netlink.rs","protocol.rs","signals.rs","subprocess.rs","themes.rs","util.rs","widget.rs","wrappers.rs"]]]]'); +//{"start":19,"fragment_lengths":[1847]} \ No newline at end of file diff --git a/src/blocks.rs b/src/blocks.rs deleted file mode 100644 index 43755a5c3b..0000000000 --- a/src/blocks.rs +++ /dev/null @@ -1,285 +0,0 @@ -//! The collection of blocks -//! -//! Blocks are defined as a [TOML array of tables](https://github.com/toml-lang/toml/blob/main/toml.md#user-content-array-of-tables): `[[block]]` -//! -//! Key | Description | Default -//! ----|-------------|---------- -//! `block` | Name of the i3status-rs block you want to use. See [modules](#modules) below for valid block names. | - -//! `signal` | Signal value that causes an update for this block with `0` corresponding to `-SIGRTMIN+0` and the largest value being `-SIGRTMAX` | None -//! `if_command` | Only display the block if the supplied command returns 0 on startup. | None -//! `merge_with_next` | If true this will group the block with the next one, so rendering such as alternating_tint will apply to the whole group | `false` -//! `icons_format` | Overrides global `icons_format` | None -//! `error_format` | Overrides global `error_format` | None -//! `error_fullscreen_format` | Overrides global `error_fullscreen_format` | None -//! `error_interval` | How long to wait until restarting the block after an error occurred. | `5` -//! `[block.theme_overrides]` | Same as the top-level config option, but for this block only. Refer to `Themes and Icons` below. | None -//! `[block.icons_overrides]` | Same as the top-level config option, but for this block only. Refer to `Themes and Icons` below. | None -//! `[[block.click]]` | Set or override click action for the block. See below for details. | Block default / None -//! -//! Per block click configuration `[[block.click]]`: -//! -//! Key | Description | Default -//! ----|-------------|---------- -//! `button` | `left`, `middle`, `right`, `up`/`wheel_up`, `down`/`wheel_down`, `wheel_left`, `wheel_right`, `forward`, `back` or [`double_left`](MouseButton). | - -//! `widget` | To which part of the block this entry applies (accepts regex) | `"block"` -//! `cmd` | Command to run when the mouse button event is detected. | None -//! `action` | Which block action to trigger | None -//! `sync` | Whether to wait for command to exit or not. | `false` -//! `update` | Whether to update the block on click. | `false` - -mod prelude; - -use futures::future::FutureExt as _; -use futures::stream::FuturesUnordered; -use serde::de::{self, Deserialize}; -use tokio::sync::{Notify, mpsc}; - -use std::borrow::Cow; -use std::sync::Arc; -use std::time::Duration; - -use crate::click::MouseButton; -use crate::errors::*; -use crate::geolocator::{Geolocator, IPAddressInfo}; -use crate::widget::Widget; -use crate::{BoxedFuture, Request, RequestCmd}; - -macro_rules! define_blocks { - { - $( - $(#[cfg(feature = $feat: literal)])? - $(#[deprecated($($dep_k: ident = $dep_v: literal),+)])? - $block: ident $(,)? - )* - } => { - $( - $(#[cfg(feature = $feat)])? - $(#[cfg_attr(docsrs, doc(cfg(feature = $feat)))])? - $(#[deprecated($($dep_k = $dep_v),+)])? - pub mod $block; - )* - - #[derive(Debug)] - pub enum BlockConfig { - $( - $(#[cfg(feature = $feat)])? - #[allow(non_camel_case_types)] - #[allow(deprecated)] - $block($block::Config), - )* - Err(&'static str, Error), - } - - impl BlockConfig { - pub fn name(&self) -> &'static str { - match self { - $( - $(#[cfg(feature = $feat)])? - Self::$block { .. } => stringify!($block), - )* - Self::Err(name, _err) => name, - } - } - - pub fn spawn(self, api: CommonApi, futures: &mut FuturesUnordered>) { - match self { - $( - $(#[cfg(feature = $feat)])? - #[allow(deprecated)] - Self::$block(config) => futures.push(async move { - while let Err(err) = $block::run(&config, &api).await { - if api.set_error(err).is_err() { - return; - } - tokio::select! { - _ = tokio::time::sleep(api.error_interval) => (), - _ = api.wait_for_update_request() => (), - } - } - }.boxed_local()), - )* - Self::Err(_name, err) => { - let _ = api.set_error(Error { - message: Some("Configuration error".into()), - cause: Some(Arc::new(err)), - }); - }, - } - } - } - - impl<'de> Deserialize<'de> for BlockConfig { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - use de::Error as _; - - let mut table = toml::Table::deserialize(deserializer)?; - let block_name = table.remove("block").ok_or_else(|| D::Error::missing_field("block"))?; - let block_name = block_name.as_str().ok_or_else(|| D::Error::custom("block must be a string"))?; - - match block_name { - $( - $(#[cfg(feature = $feat)])? - #[allow(deprecated)] - stringify!($block) => match $block::Config::deserialize(table) { - Ok(config) => Ok(BlockConfig::$block(config)), - Err(err) => Ok(BlockConfig::Err(stringify!($block), crate::errors::Error::new(err.to_string()))), - } - $( - #[cfg(not(feature = $feat))] - stringify!($block) => Err(D::Error::custom(format!( - "block {} is behind a feature gate '{}' which must be enabled at compile time", - stringify!($block), - $feat, - ))), - )? - )* - other => Err(D::Error::custom(format!("unknown block '{other}'"))) - } - } - } - }; -} - -define_blocks!( - amd_gpu, - backlight, - battery, - bluetooth, - calendar, - cpu, - custom, - custom_dbus, - disk_iostats, - disk_space, - docker, - external_ip, - focused_window, - github, - hueshift, - kdeconnect, - load, - #[cfg(feature = "maildir")] - maildir, - menu, - memory, - music, - net, - notify, - #[cfg(feature = "notmuch")] - notmuch, - nvidia_gpu, - packages, - pomodoro, - privacy, - rofication, - service_status, - scratchpad, - sound, - speedtest, - keyboard_layout, - taskwarrior, - temperature, - time, - tea_timer, - toggle, - uptime, - vpn, - watson, - weather, - xrandr, -); - -/// An error which originates from a block -#[derive(Debug, thiserror::Error)] -#[error("In block {}: {}", .block_name, .error)] -pub struct BlockError { - pub block_id: usize, - pub block_name: &'static str, - pub error: Error, -} - -pub type BlockAction = Cow<'static, str>; - -#[derive(Clone)] -pub struct CommonApi { - pub(crate) id: usize, - pub(crate) update_request: Arc, - pub(crate) request_sender: mpsc::UnboundedSender, - pub(crate) error_interval: Duration, - pub(crate) geolocator: Arc, -} - -impl CommonApi { - /// Sends the widget to be displayed. - pub fn set_widget(&self, widget: Widget) -> Result<()> { - self.request_sender - .send(Request { - block_id: self.id, - cmd: RequestCmd::SetWidget(widget), - }) - .error("Failed to send Request") - } - - /// Hides the block. Send new widget to make it visible again. - pub fn hide(&self) -> Result<()> { - self.request_sender - .send(Request { - block_id: self.id, - cmd: RequestCmd::UnsetWidget, - }) - .error("Failed to send Request") - } - - /// Sends the error to be displayed. - pub fn set_error(&self, error: Error) -> Result<()> { - self.request_sender - .send(Request { - block_id: self.id, - cmd: RequestCmd::SetError(error), - }) - .error("Failed to send Request") - } - - pub fn set_default_actions( - &self, - actions: &'static [(MouseButton, Option<&'static str>, &'static str)], - ) -> Result<()> { - self.request_sender - .send(Request { - block_id: self.id, - cmd: RequestCmd::SetDefaultActions(actions), - }) - .error("Failed to send Request") - } - - pub fn get_actions(&self) -> Result> { - let (tx, rx) = mpsc::unbounded_channel(); - self.request_sender - .send(Request { - block_id: self.id, - cmd: RequestCmd::SubscribeToActions(tx), - }) - .error("Failed to send Request")?; - Ok(rx) - } - - pub async fn wait_for_update_request(&self) { - self.update_request.notified().await; - } - - fn locator_name(&self) -> Cow<'static, str> { - self.geolocator.name() - } - - /// No-op if last API call was made in the last `interval` seconds. - pub async fn find_ip_location( - &self, - client: &reqwest::Client, - interval: Duration, - ) -> Result { - self.geolocator.find_ip_location(client, interval).await - } -} diff --git a/src/blocks/amd_gpu.rs b/src/blocks/amd_gpu.rs deleted file mode 100644 index be72cc4703..0000000000 --- a/src/blocks/amd_gpu.rs +++ /dev/null @@ -1,191 +0,0 @@ -//! Display the stats of your AMD GPU -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `device` | The device in `/sys/class/drm/` to read from. | Any AMD card -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $utilization "` -//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None` -//! `interval` | Update interval in seconds | `5` -//! -//! Placeholder | Value | Type | Unit -//! ---------------------|-------------------------------------|--------|------------ -//! `icon` | A static icon | Icon | - -//! `utilization` | GPU utilization | Number | % -//! `vram_total` | Total VRAM | Number | Bytes -//! `vram_used` | Used VRAM | Number | Bytes -//! `vram_used_percents` | Used VRAM / Total VRAM | Number | % -//! -//! Action | Description | Default button -//! ----------------|-------------------------------------------|--------------- -//! `toggle_format` | Toggles between `format` and `format_alt` | Left -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "amd_gpu" -//! format = " $icon $utilization " -//! format_alt = " $icon MEM: $vram_used_percents ($vram_used/$vram_total) " -//! interval = 1 -//! ``` -//! -//! # Icons Used -//! - `gpu` - -use std::path::PathBuf; -use std::str::FromStr; - -use tokio::fs::read_dir; - -use super::prelude::*; -use crate::util::read_file; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub device: Option, - pub format: FormatConfig, - pub format_alt: Option, - #[default(5.into())] - pub interval: Seconds, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?; - - let mut format = config.format.with_default(" $icon $utilization ")?; - let mut format_alt = match &config.format_alt { - Some(f) => Some(f.with_default("")?), - None => None, - }; - - let device = match &config.device { - Some(name) => Device::new(name)?, - None => Device::default_card() - .await - .error("failed to get default GPU")? - .error("no GPU found")?, - }; - - loop { - let mut widget = Widget::new().with_format(format.clone()); - - let info = device.read_info().await?; - - widget.set_values(map! { - "icon" => Value::icon("gpu"), - "utilization" => Value::percents(info.utilization_percents), - "vram_total" => Value::bytes(info.vram_total_bytes), - "vram_used" => Value::bytes(info.vram_used_bytes), - "vram_used_percents" => Value::percents(info.vram_used_bytes / info.vram_total_bytes * 100.0), - }); - - widget.state = match info.utilization_percents { - x if x > 90.0 => State::Critical, - x if x > 60.0 => State::Warning, - x if x > 30.0 => State::Info, - _ => State::Idle, - }; - - api.set_widget(widget)?; - - loop { - select! { - _ = sleep(config.interval.0) => break, - _ = api.wait_for_update_request() => break, - Some(action) = actions.recv() => match action.as_ref() { - "toggle_format" => { - if let Some(ref mut format_alt) = format_alt { - std::mem::swap(format_alt, &mut format); - break; - } - } - _ => (), - } - } - } - } -} - -pub struct Device { - path: PathBuf, -} - -struct GpuInfo { - utilization_percents: f64, - vram_total_bytes: f64, - vram_used_bytes: f64, -} - -impl Device { - fn new(name: &str) -> Result { - let path = PathBuf::from(format!("/sys/class/drm/{name}/device")); - - if !path.exists() { - Err(Error::new(format!("Device {name} not found"))) - } else { - Ok(Self { path }) - } - } - - async fn default_card() -> std::io::Result> { - let mut dir = read_dir("/sys/class/drm").await?; - - while let Some(entry) = dir.next_entry().await? { - let name = entry.file_name(); - let Some(name) = name.to_str() else { continue }; - if !name.starts_with("card") { - continue; - } - - let mut path = entry.path(); - path.push("device"); - - if let Ok(uevent) = read_file(path.join("uevent")).await - && uevent.contains("PCI_ID=1002") - { - return Ok(Some(Self { path })); - } - } - - Ok(None) - } - - async fn read_prop(&self, prop: &str) -> Option { - read_file(self.path.join(prop)) - .await - .ok() - .and_then(|x| x.parse().ok()) - } - - async fn read_info(&self) -> Result { - Ok(GpuInfo { - utilization_percents: self - .read_prop::("gpu_busy_percent") - .await - .error("Failed to read gpu_busy_percent")?, - vram_total_bytes: self - .read_prop::("mem_info_vram_total") - .await - .error("Failed to read mem_info_vram_total")?, - vram_used_bytes: self - .read_prop::("mem_info_vram_used") - .await - .error("Failed to read mem_info_vram_used")?, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_non_existing_gpu_device() { - let device = Device::new("/nope"); - assert!(device.is_err()); - } -} diff --git a/src/blocks/backlight.rs b/src/blocks/backlight.rs deleted file mode 100644 index 9997263e26..0000000000 --- a/src/blocks/backlight.rs +++ /dev/null @@ -1,241 +0,0 @@ -//! The brightness of a backlight device -//! -//! This block reads brightness information directly from the filesystem, so it works under both -//! X11 and Wayland. The block uses `inotify` to listen for changes in the device's brightness -//! directly, so there is no need to set an update interval. This block uses DBus to set brightness -//! level using the mouse wheel, but will [fallback to sysfs](#d-bus-fallback) if `systemd-logind` is not used. -//! -//! # Root scaling -//! -//! Some devices expose raw values that are best handled with nonlinear scaling. The human perception of lightness is close to the cube root of relative luminance, so settings for `root_scaling` between 2.4 and 3.0 are worth trying. For devices with few discrete steps this should be 1.0 (linear). More information: -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `device` | A regex to match against `/sys/class/backlight` devices to read brightness information from (can match 1 or more devices). When there is no `device` specified, this block will display information for all devices found in the `/sys/class/backlight` directory. | Default device -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $brightness "` -//! `missing_format` | A string to customise the output of this block. No placeholders available | `" no backlight devices "` -//! `step_width` | The brightness increment to use when scrolling, in percent | `5` -//! `minimum` | The minimum brightness that can be scrolled down to | `5` -//! `maximum` | The maximum brightness that can be scrolled up to | `100` -//! `cycle` | The brightnesses to cycle through on each click | `[minimum, maximum]` -//! `root_scaling` | Scaling exponent reciprocal (ie. root) | `1.0` -//! `invert_icons` | Invert icons' ordering, useful if you have colorful emoji | `false` -//! `ddcci_sleep_multiplier` | [See ddcutil documentation](https://www.ddcutil.com/performance_options/#option-sleep-multiplier) | `1.0` -//! `ddcci_max_tries_write_read` | The maximum number of times to attempt writing to or reading from a ddcci monitor | `10` -//! -//! Placeholder | Value | Type | Unit -//! -------------|-------------------------------------------|--------|--------------- -//! `icon` | Icon based on backlight's state | Icon | - -//! `brightness` | Current brightness | Number | % -//! -//! Action | Default button -//! ------------------|--------------- -//! `cycle` | Left -//! `brightness_up` | Wheel Up -//! `brightness_down` | Wheel Down -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "backlight" -//! device = "intel_backlight" -//! ``` -//! -//! Hide missing backlight: -//! -//! ```toml -//! [[block]] -//! block = "backlight" -//! missing_format = "" -//! ``` -//! -//! # calibright -//! -//! Additional display brightness calibration can be set in `$XDG_CONFIG_HOME/calibright/config.toml` -//! See for more details. -//! This block will override any global config set in `$XDG_CONFIG_HOME/calibright/config.toml` -//! -//! # D-Bus Fallback -//! -//! If you don't use `systemd-logind` i3status-rust will attempt to set the brightness -//! using sysfs. In order to do this you'll need to have write permission. -//! You can do this by writing a `udev` rule for your system. -//! -//! First, check that your user is a member of the "video" group using the -//! `groups` command. Then add a rule in the `/etc/udev/rules.d/` directory -//! containing the following, for example in `backlight.rules`: -//! -//! ```text -//! ACTION=="add", SUBSYSTEM=="backlight", GROUP="video", MODE="0664" -//! ``` -//! -//! This will allow the video group to modify all backlight devices. You will -//! also need to restart for this rule to take effect. -//! -//! # Icons Used -//! - `backlight` (as a progression) - -use std::sync::Arc; - -use calibright::{CalibrightBuilder, CalibrightConfig, CalibrightError, DeviceConfig}; - -use super::prelude::*; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub device: Option, - pub format: FormatConfig, - pub missing_format: FormatConfig, - #[default(5.0)] - pub step_width: f64, - #[default(5.0)] - pub minimum: f64, - #[default(100.0)] - pub maximum: f64, - pub cycle: Option>, - pub invert_icons: bool, - //Calibright config settings - pub root_scaling: Option, - pub ddcci_sleep_multiplier: Option, - pub ddcci_max_tries_write_read: Option, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[ - (MouseButton::Left, None, "cycle"), - (MouseButton::WheelUp, None, "brightness_up"), - (MouseButton::WheelDown, None, "brightness_down"), - ])?; - - let format = config.format.with_default(" $icon $brightness ")?; - let missing_format = config - .missing_format - .with_default(" no backlight devices ")?; - - let default_cycle = &[config.minimum, config.maximum]; - let mut cycle = config - .cycle - .as_deref() - .unwrap_or(default_cycle) - .iter() - .map(|x| x / 100.0) - .cycle(); - - let step_width = config.step_width / 100.0; - let minimum = config.minimum / 100.0; - let maximum = config.maximum / 100.0; - - let mut calibright_defaults = DeviceConfig::default(); - - if let Some(root_scaling) = config.root_scaling { - calibright_defaults.root_scaling = root_scaling; - } - - if let Some(ddcci_sleep_multiplier) = config.ddcci_sleep_multiplier { - calibright_defaults.ddcci_sleep_multiplier = ddcci_sleep_multiplier; - } - - if let Some(ddcci_max_tries_write_read) = config.ddcci_max_tries_write_read { - calibright_defaults.ddcci_max_tries_write_read = ddcci_max_tries_write_read; - } - - let calibright_config = CalibrightConfig::new_with_defaults(&calibright_defaults) - .await - .error("calibright config error")?; - - let mut calibright = CalibrightBuilder::new() - .with_device_regex(config.device.as_deref().unwrap_or(".")) - .with_config(calibright_config) - .with_poll_interval(api.error_interval) - .build() - .await - .error("Failed to init calibright")?; - - // This is used to display the error, if there is one - let mut block_error: Option = None; - - let mut brightness = calibright - .get_brightness() - .await - .map_err(|e| block_error = Some(e)) - .unwrap_or_default(); - - loop { - match block_error { - Some(CalibrightError::NoDevices) => { - let widget = Widget::new() - .with_format(missing_format.clone()) - .with_state(State::Critical); - api.set_widget(widget)?; - } - Some(e) => { - api.set_error(Error { - message: None, - cause: Some(Arc::new(e)), - })?; - } - None => { - let mut widget = Widget::new().with_format(format.clone()); - let mut icon_value = brightness; - if config.invert_icons { - icon_value = 1.0 - icon_value; - } - widget.set_values(map! { - "icon" => Value::icon_progression("backlight", icon_value), - "brightness" => Value::percents((brightness * 100.0).round()) - }); - api.set_widget(widget)?; - } - } - - loop { - select! { - // Calibright can recover from errors, just keep reading the next event. - _ = calibright.next() => { - block_error = calibright - .get_brightness() - .await - .map(|new_brightness| {brightness = new_brightness;}) - .err(); - - break; - }, - Some(action) = actions.recv() => match action.as_ref() { - "cycle" => { - if let Some(cycle_brightness) = cycle.next() { - brightness = cycle_brightness; - block_error = calibright - .set_brightness(brightness) - .await - .err(); - break; - - } - } - "brightness_up" => { - brightness = (brightness + step_width).clamp(minimum, maximum); - block_error = calibright - .set_brightness(brightness) - .await - .err(); - break; - } - "brightness_down" => { - brightness = (brightness - step_width).clamp(minimum, maximum); - block_error = calibright - .set_brightness(brightness) - .await - .err(); - break; - } - _ => (), - } - } - } - } -} diff --git a/src/blocks/battery.rs b/src/blocks/battery.rs deleted file mode 100644 index 1d809fd85b..0000000000 --- a/src/blocks/battery.rs +++ /dev/null @@ -1,309 +0,0 @@ -//! Information about the internal power supply -//! -//! This block can display the current battery state (Full, Charging or Discharging), percentage -//! charged and estimate time until (dis)charged for an internal power supply. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `device` | sysfs/UPower: The device in `/sys/class/power_supply/` to read from (can also be "DisplayDevice" for UPower, which is a single logical power source representing all physical power sources. This is for example useful if your system has multiple batteries, in which case the DisplayDevice behaves as if you had a single larger battery.). apc_ups: IPv4Address:port or hostname:port | sysfs: the first battery device found in /sys/class/power_supply, with "BATx" or "CMBx" entries taking precedence. apc_ups: "localhost:3551". upower: `DisplayDevice` -//! `driver` | One of `"sysfs"`, `"apc_ups"`, or `"upower"` | `"sysfs"` -//! `model` | If present, the contents of `/sys/class/power_supply/.../model_name` must match this value. Typical use is to select by model name on devices that change their path. | N/A -//! `interval` | Update interval, in seconds. Only relevant for driver = "sysfs" or "apc_ups". | `10` -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $percentage "` -//! `full_format` | Same as `format` but for when the battery is full | `" $icon "` -//! `charging_format` | Same as `format` but for when the battery is charging | Links to `format` -//! `empty_format` | Same as `format` but for when the battery is empty | `" $icon "` -//! `not_charging_format` | Same as `format` but for when the battery is not charging. Defaults to the full battery icon as many batteries report this status when they are full. | `" $icon "` -//! `missing_format` | Same as `format` if the battery cannot be found. | `" $icon "` -//! `info` | Minimum battery level, where state is set to info | `60` -//! `good` | Minimum battery level, where state is set to good | `60` -//! `warning` | Minimum battery level, where state is set to warning | `30` -//! `critical` | Minimum battery level, where state is set to critical | `15` -//! `full_threshold` | Percentage above which the battery is considered full (`full_format` shown) | `95` -//! `empty_threshold` | Percentage below which the battery is considered empty | `7.5` -//! -//! Placeholder | Value | Type | Unit -//! -------------|-------------------------------------------------------------------------|-------------------|----- -//! `icon` | Icon based on battery's state | Icon | - -//! `percentage` | Battery level, in percent | Number | Percents -//! `time_remaining` | Time remaining until (dis)charge is complete. Presented only if battery's status is (dis)charging. | Duration | - -//! `time` | Time remaining until (dis)charge is complete. Presented only if battery's status is (dis)charging. | String *DEPRECATED* | - -//! `power` | Power consumption by the battery or from the power supply when charging | String or Float | Watts -//! -//! `time` has been deprecated in favor of `time_remaining`. -//! -//! # Examples -//! -//! Basic usage: -//! -//! ```toml -//! [[block]] -//! block = "battery" -//! format = " $icon $percentage " -//! ``` -//! -//! ```toml -//! [[block]] -//! block = "battery" -//! format = " $percentage {$time_remaining.dur(hms:true, min_unit:m) |}" -//! device = "DisplayDevice" -//! driver = "upower" -//! ``` -//! -//! Hide missing battery: -//! -//! ```toml -//! [[block]] -//! block = "battery" -//! missing_format = "" -//! ``` -//! -//! # Icons Used -//! - `bat` (as a progression) -//! - `bat_charging` (as a progression) -//! - `bat_not_available` - -use regex::Regex; -use std::convert::Infallible; -use std::str::FromStr; - -use super::prelude::*; - -mod apc_ups; -mod sysfs; -mod upower; - -// make_log_macro!(debug, "battery"); - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub device: Option, - pub driver: BatteryDriver, - pub model: Option, - #[default(10.into())] - pub interval: Seconds, - pub format: FormatConfig, - pub full_format: FormatConfig, - pub charging_format: FormatConfig, - pub empty_format: FormatConfig, - pub not_charging_format: FormatConfig, - pub missing_format: FormatConfig, - #[default(60.0)] - pub info: f64, - #[default(60.0)] - pub good: f64, - #[default(30.0)] - pub warning: f64, - #[default(15.0)] - pub critical: f64, - #[default(95.0)] - pub full_threshold: f64, - #[default(7.5)] - pub empty_threshold: f64, -} - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(rename_all = "snake_case")] -pub enum BatteryDriver { - #[default] - Sysfs, - ApcUps, - Upower, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config.format.with_default(" $icon $percentage ")?; - let format_full = config.full_format.with_default(" $icon ")?; - let charging_format = config.charging_format.with_default_format(&format); - let format_empty = config.empty_format.with_default(" $icon ")?; - let format_not_charging = config.not_charging_format.with_default(" $icon ")?; - let missing_format = config.missing_format.with_default(" $icon ")?; - - let dev_name = DeviceName::new(config.device.clone())?; - let mut device: Box = match config.driver { - BatteryDriver::Sysfs => Box::new(sysfs::Device::new( - dev_name, - config.model.clone(), - config.interval, - )), - BatteryDriver::ApcUps => Box::new(apc_ups::Device::new(dev_name, config.interval).await?), - BatteryDriver::Upower => { - Box::new(upower::Device::new(dev_name, config.model.clone()).await?) - } - }; - - loop { - let mut info = device.get_info().await?; - - if let Some(info) = &mut info { - if info.capacity >= config.full_threshold { - info.status = BatteryStatus::Full; - } else if info.capacity <= config.empty_threshold - && info.status != BatteryStatus::Charging - { - info.status = BatteryStatus::Empty; - } - } - - match info { - Some(info) => { - let mut widget = Widget::new(); - - widget.set_format(match info.status { - BatteryStatus::Empty => format_empty.clone(), - BatteryStatus::Full => format_full.clone(), - BatteryStatus::Charging => charging_format.clone(), - BatteryStatus::NotCharging => format_not_charging.clone(), - _ => format.clone(), - }); - - let mut values = map!( - "percentage" => Value::percents(info.capacity) - ); - - info.power - .map(|p| values.insert("power".into(), Value::watts(p))); - info.time_remaining.inspect(|&t| { - map! { @extend values - "time" => Value::text( - format!( - "{}:{:02}", - (t / 3600.) as i32, - (t % 3600. / 60.) as i32 - ), - ), - "time_remaining" => Value::duration( - Duration::from_secs(t as u64), - ), - } - }); - - let (icon_name, icon_value, state) = match (info.status, info.capacity) { - (BatteryStatus::Empty, _) => ("bat", 0.0, State::Critical), - (BatteryStatus::Full | BatteryStatus::NotCharging, _) => { - ("bat", 1.0, State::Idle) - } - (status, capacity) => ( - if status == BatteryStatus::Charging { - "bat_charging" - } else { - "bat" - }, - capacity / 100.0, - if status == BatteryStatus::Charging { - State::Good - } else if capacity <= config.critical { - State::Critical - } else if capacity <= config.warning { - State::Warning - } else if capacity <= config.info { - State::Info - } else if capacity > config.good { - State::Good - } else { - State::Idle - }, - ), - }; - - values.insert( - "icon".into(), - Value::icon_progression(icon_name, icon_value), - ); - - widget.set_values(values); - widget.state = state; - api.set_widget(widget)?; - } - None => { - let mut widget = Widget::new() - .with_format(missing_format.clone()) - .with_state(State::Critical); - widget.set_values(map!("icon" => Value::icon("bat_not_available"))); - api.set_widget(widget)?; - } - } - - select! { - update = device.wait_for_change() => update?, - _ = api.wait_for_update_request() => (), - } - } -} - -#[async_trait] -trait BatteryDevice { - async fn get_info(&mut self) -> Result>; - async fn wait_for_change(&mut self) -> Result<()>; -} - -/// `Option`, but more intuitive -#[derive(Debug)] -enum DeviceName { - Any, - Regex(Regex), -} - -impl DeviceName { - fn new(pat: Option) -> Result { - Ok(match pat { - None => Self::Any, - Some(pat) => Self::Regex(pat.parse().error("failed to parse regex")?), - }) - } - - fn matches(&self, name: &str) -> bool { - match self { - Self::Any => true, - Self::Regex(pat) => pat.is_match(name), - } - } - - fn exact(&self) -> Option<&str> { - match self { - Self::Any => None, - Self::Regex(pat) => Some(pat.as_str()), - } - } -} - -#[derive(Debug, Clone, Copy)] -struct BatteryInfo { - /// Current status, e.g. "charging", "discharging", etc. - status: BatteryStatus, - /// The capacity in percents - capacity: f64, - /// Power consumption in watts - power: Option, - /// Time in seconds - time_remaining: Option, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, SmartDefault)] -enum BatteryStatus { - Charging, - Discharging, - Empty, - Full, - NotCharging, - #[default] - Unknown, -} - -impl FromStr for BatteryStatus { - type Err = Infallible; - - fn from_str(s: &str) -> Result { - Ok(match s { - "Charging" => Self::Charging, - "Discharging" => Self::Discharging, - "Empty" => Self::Empty, - "Full" => Self::Full, - "Not charging" => Self::NotCharging, - _ => Self::Unknown, - }) - } -} diff --git a/src/blocks/battery/apc_ups.rs b/src/blocks/battery/apc_ups.rs deleted file mode 100644 index e34761a487..0000000000 --- a/src/blocks/battery/apc_ups.rs +++ /dev/null @@ -1,161 +0,0 @@ -use bytes::Bytes; -use futures::SinkExt as _; - -use serde::de; -use tokio::net::TcpStream; -use tokio::time::Interval; -use tokio_util::codec::{Framed, LengthDelimitedCodec}; - -use super::{BatteryDevice, BatteryInfo, BatteryStatus, DeviceName}; -use crate::blocks::prelude::*; - -make_log_macro!(debug, "battery[apc_ups]"); - -#[derive(Debug, SmartDefault)] -enum Value { - String(String), - // The value is a percentage (0-100) - Percent(f64), - Watts(f64), - Seconds(f64), - #[default] - None, -} - -impl<'de> Deserialize<'de> for Value { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - for unit in ["Percent", "Watts", "Seconds", "Minutes", "Hours"] { - if let Some(stripped) = s.strip_suffix(unit) { - let value = stripped.trim().parse::().map_err(de::Error::custom)?; - return Ok(match unit { - "Percent" => Value::Percent(value), - "Watts" => Value::Watts(value), - "Seconds" => Value::Seconds(value), - "Minutes" => Value::Seconds(value * 60.0), - "Hours" => Value::Seconds(value * 3600.0), - _ => unreachable!(), - }); - } - } - Ok(Value::String(s)) - } -} - -#[derive(Debug, Deserialize, Default)] -#[serde(rename_all = "UPPERCASE", default)] -struct Properties { - status: Value, - bcharge: Value, - nompower: Value, - loadpct: Value, - timeleft: Value, -} - -pub(super) struct Device { - addr: String, - interval: Interval, -} - -impl Device { - pub(super) async fn new(dev_name: DeviceName, interval: Seconds) -> Result { - let addr = dev_name.exact().unwrap_or("localhost:3551"); - Ok(Self { - addr: addr.to_string(), - interval: interval.timer(), - }) - } - - async fn get_status(&mut self) -> Result { - let mut conn = Framed::new( - TcpStream::connect(&self.addr) - .await - .error("Failed to connect to socket")?, - LengthDelimitedCodec::builder() - .length_field_type::() - .new_codec(), - ); - - conn.send(Bytes::from_static(b"status")) - .await - .error("Could not send message to socket")?; - conn.close().await.error("Could not close socket sink")?; - - let mut map = serde_json::Map::new(); - - while let Some(frame) = conn.next().await { - let frame = frame.error("Failed to read from socket")?; - if frame.is_empty() { - continue; - } - let line = std::str::from_utf8(&frame).error("Failed to convert to UTF-8")?; - let Some((key, value)) = line.split_once(':') else { - debug!("Invalid field format: {line:?}"); - continue; - }; - map.insert( - key.trim().to_uppercase(), - serde_json::Value::String(value.trim().to_string()), - ); - } - - serde_json::from_value(serde_json::Value::Object(map)).error("Failed to deserialize") - } -} - -#[async_trait] -impl BatteryDevice for Device { - async fn get_info(&mut self) -> Result> { - let status_data = self - .get_status() - .await - .map_err(|e| { - debug!("{e}"); - e - }) - .unwrap_or_default(); - - let Value::String(status_str) = status_data.status else { - return Ok(None); - }; - - let status = match &*status_str { - "ONBATT" => BatteryStatus::Discharging, - "ONLINE" => BatteryStatus::Charging, - _ => BatteryStatus::Unknown, - }; - - // Even if the connection is valid, in the first few seconds - // after apcupsd starts BCHARGE may not be present - let Value::Percent(capacity) = status_data.bcharge else { - return Ok(None); - }; - - let power = match (status_data.nompower, status_data.loadpct) { - (Value::Watts(nominal_power), Value::Percent(load_percent)) => { - Some(nominal_power * load_percent / 100.0) - } - _ => None, - }; - - let time_remaining = match status_data.timeleft { - Value::Seconds(time_left) => Some(time_left), - _ => None, - }; - - Ok(Some(BatteryInfo { - status, - capacity, - power, - time_remaining, - })) - } - - async fn wait_for_change(&mut self) -> Result<()> { - self.interval.tick().await; - Ok(()) - } -} diff --git a/src/blocks/battery/sysfs.rs b/src/blocks/battery/sysfs.rs deleted file mode 100644 index 67bb7e2956..0000000000 --- a/src/blocks/battery/sysfs.rs +++ /dev/null @@ -1,281 +0,0 @@ -use std::convert::Infallible; -use std::path::{Path, PathBuf}; -use std::str::FromStr; - -use tokio::fs::read_dir; -use tokio::time::Interval; - -use super::{BatteryDevice, BatteryInfo, BatteryStatus, DeviceName}; -use crate::blocks::prelude::*; -use crate::util::read_file; - -make_log_macro!(debug, "battery"); - -/// Path for the power supply devices -const POWER_SUPPLY_DEVICES_PATH: &str = "/sys/class/power_supply"; - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -enum CapacityLevel { - Full, - High, - Normal, - Low, - Critical, - Unknown, -} - -impl FromStr for CapacityLevel { - type Err = Infallible; - - fn from_str(s: &str) -> Result { - Ok(match s { - "Full" => Self::Full, - "High" => Self::High, - "Normal" => Self::Normal, - "Low" => Self::Low, - "Critical" => Self::Critical, - _ => Self::Unknown, - }) - } -} - -impl CapacityLevel { - fn percentage(self) -> Option { - match self { - CapacityLevel::Full => Some(100.0), - CapacityLevel::High => Some(75.0), - CapacityLevel::Normal => Some(50.0), - CapacityLevel::Low => Some(25.0), - CapacityLevel::Critical => Some(5.0), - CapacityLevel::Unknown => None, - } - } -} - -/// Represents a physical power supply device, as known to sysfs. -/// -pub(super) struct Device { - dev_name: DeviceName, - dev_path: Option, - dev_model: Option, - interval: Interval, -} - -impl Device { - pub(super) fn new(dev_name: DeviceName, dev_model: Option, interval: Seconds) -> Self { - Self { - dev_name, - dev_path: None, - dev_model, - interval: interval.timer(), - } - } - - /// Returns `self.dev_path` if it is still available. Otherwise, find any device that matches - /// `self.dev_name`. - async fn get_device_path(&mut self) -> Result> { - if let Some(path) = &self.dev_path - && Self::device_available(path).await - { - debug!("battery '{}' is still available", path.display()); - return Ok(self.dev_path.as_deref()); - } - - let mut matching_battery = None; - - let mut sysfs_dir = read_dir(POWER_SUPPLY_DEVICES_PATH) - .await - .error("failed to read /sys/class/power_supply directory")?; - while let Some(dir) = sysfs_dir - .next_entry() - .await - .error("failed to read /sys/class/power_supply directory")? - { - let name = dir.file_name(); - let name = name.to_str().error("non UTF-8 battery path")?; - - let path = dir.path(); - - if !self.dev_name.matches(name) - || Self::read_prop::(&path, "type").await.as_deref() != Some("Battery") - || !Self::device_available(&path).await - { - continue; - } - - let model_name = Self::read_prop::(&path, "model_name").await; - debug!( - "battery '{}', model={:?}", - path.display(), - model_name.as_deref() - ); - if let Some(dev_model) = &self.dev_model - && model_name.as_deref() != Some(dev_model.as_str()) - { - debug!("Skipping based on model."); - continue; - } - - debug!( - "Found matching battery: '{}' matches {:?}", - path.display(), - self.dev_name - ); - - // Better to default to the system battery, rather than possibly a keyboard or mouse battery. - // System batteries usually start with BAT or CMB. - if name.starts_with("BAT") || name.starts_with("CMB") { - return Ok(Some(self.dev_path.insert(path))); - } else { - matching_battery = Some(path); - } - } - - Ok(match matching_battery { - Some(path) => Some(self.dev_path.insert(path)), - None => { - debug!("No batteries found"); - None - } - }) - } - - async fn read_prop(path: &Path, prop: &str) -> Option { - read_file(path.join(prop)) - .await - .ok() - .and_then(|x| x.parse().ok()) - } - - async fn device_available(path: &Path) -> bool { - // If `scope` is `Device`, then this is HID, in which case we don't have to check the - // `present` property, because the existence of the device directory implies that the device - // is available - Self::read_prop::(path, "scope").await.as_deref() == Some("Device") - || Self::read_prop::(path, "present").await == Some(1) - } -} - -#[async_trait] -impl BatteryDevice for Device { - async fn get_info(&mut self) -> Result> { - // Check if the battery is available - let path = match self.get_device_path().await? { - Some(path) => path, - None => return Ok(None), - }; - - // Read all the necessary data - let ( - status, - capacity_level, - capacity, - charge_now, - charge_full, - energy_now, - energy_full, - power_now, - current_now, - voltage_now, - time_to_empty, - time_to_full, - ) = tokio::join!( - Self::read_prop::(path, "status"), - Self::read_prop::(path, "capacity_level"), - Self::read_prop::(path, "capacity"), - Self::read_prop::(path, "charge_now"), // uAh - Self::read_prop::(path, "charge_full"), // uAh - Self::read_prop::(path, "energy_now"), // uWh - Self::read_prop::(path, "energy_full"), // uWh - Self::read_prop::(path, "power_now"), // uW - Self::read_prop::(path, "current_now"), // uA - Self::read_prop::(path, "voltage_now"), // uV - Self::read_prop::(path, "time_to_empty"), // seconds - Self::read_prop::(path, "time_to_full"), // seconds - ); - - if !Self::device_available(path).await { - // Device became unavailable while we were reading data from it. The simplest thing we - // can do now is to pretend it wasn't available to begin with. - debug!("battery suddenly unavailable"); - return Ok(None); - } - - debug!("status = {:?}", status); - debug!("capacity_level = {:?}", capacity_level); - debug!("capacity = {:?}", capacity); - debug!("charge_now = {:?}", charge_now); - debug!("charge_full = {:?}", charge_full); - debug!("energy_now = {:?}", energy_now); - debug!("energy_full = {:?}", energy_full); - debug!("power_now = {:?}", power_now); - debug!("current_now = {:?}", current_now); - debug!("voltage_now = {:?}", voltage_now); - debug!("time_to_empty = {:?}", time_to_empty); - debug!("time_to_full = {:?}", time_to_full); - - let charge_now = charge_now.map(|c| c * 1e-6); // uAh -> Ah - let charge_full = charge_full.map(|c| c * 1e-6); // uAh -> Ah - let energy_now = energy_now.map(|e| e * 1e-6); // uWh -> Wh - let energy_full = energy_full.map(|e| e * 1e-6); // uWh -> Wh - let power_now = power_now.map(|e| e * 1e-6); // uW -> W - let current_now = current_now.map(|e| (e * 1e-6).abs()); // uA -> A - let voltage_now = voltage_now.map(|e| e * 1e-6); // uV -> V - - let status = status.unwrap_or_default(); - - // Prefer `charge_now/charge_full` and `energy_now/energy_full` because `capacity` is - // calculated using `_full_design`, which is not practical (#1410, #1906). - let calc_capacity = |now, full| Some(now? / full? * 100.0); - let capacity = calc_capacity(charge_now, charge_full) - .or_else(|| calc_capacity(energy_now, energy_full)) - .or(capacity) - .or_else(|| capacity_level.and_then(CapacityLevel::percentage)) - .error("Failed to get capacity")?; - - // A * V = W - let power = power_now - .or_else(|| current_now.zip(voltage_now).map(|(c, v)| c * v)) - .filter(|&p| p != 0.0); - - // Ah * V = Wh - // Wh / W = h - let time_remaining = match status { - BatteryStatus::Charging => - { - #[allow(clippy::unnecessary_lazy_evaluations)] - time_to_full.or_else(|| match (energy_now, energy_full, power) { - (Some(en), Some(ef), Some(p)) => Some((ef - en) / p * 3600.0), - _ => match (charge_now, charge_full, voltage_now, power) { - (Some(cn), Some(cf), Some(v), Some(p)) => Some((cf - cn) * v / p * 3600.0), - _ => None, - }, - }) - } - BatteryStatus::Discharging => - { - #[allow(clippy::unnecessary_lazy_evaluations)] - time_to_empty.or_else(|| match (energy_now, power) { - (Some(en), Some(p)) => Some(en / p * 3600.0), - _ => match (charge_now, voltage_now, power) { - (Some(cn), Some(v), Some(p)) => Some(cn * v / p * 3600.0), - _ => None, - }, - }) - } - _ => None, - }; - - Ok(Some(BatteryInfo { - status, - capacity, - power, - time_remaining, - })) - } - - async fn wait_for_change(&mut self) -> Result<()> { - self.interval.tick().await; - Ok(()) - } -} diff --git a/src/blocks/battery/upower.rs b/src/blocks/battery/upower.rs deleted file mode 100644 index 74d9deaab7..0000000000 --- a/src/blocks/battery/upower.rs +++ /dev/null @@ -1,264 +0,0 @@ -use tokio::try_join; -use zbus::fdo::{PropertiesChangedStream, PropertiesProxy}; -use zbus::{Connection, zvariant}; -use zvariant::ObjectPath; - -use super::{BatteryDevice, BatteryInfo, BatteryStatus, DeviceName}; -use crate::blocks::prelude::*; -use crate::util::new_system_dbus_connection; - -const DISPLAY_DEVICE_PATH: ObjectPath = - ObjectPath::from_static_str_unchecked("/org/freedesktop/UPower/devices/DisplayDevice"); - -struct DeviceConnection { - device_proxy: DeviceProxy<'static>, - changes: PropertiesChangedStream, -} - -impl DeviceConnection { - async fn new( - dbus_conn: &Connection, - device: &DeviceName, - expected_model: Option<&str>, - ) -> Result> { - let device_proxy = - if device.exact().is_none_or(|d| d == "DisplayDevice") && expected_model.is_none() { - DeviceProxy::builder(dbus_conn) - .path(DISPLAY_DEVICE_PATH) - .unwrap() - .build() - .await - .error("Failed to create DeviceProxy")? - } else { - let mut res = None; - for path in UPowerProxy::new(dbus_conn) - .await - .error("Failed to create UPowerProxy")? - .enumerate_devices() - .await - .error("Failed to retrieve UPower devices")? - { - let proxy = DeviceProxy::builder(dbus_conn) - .path(path) - .unwrap() - .build() - .await - .error("Failed to create DeviceProxy")?; - - // Filter by model if needed - if let Some(expected_model) = &expected_model - && let Ok(device_model) = proxy.model().await - && !expected_model.eq(&device_model) - { - continue; - } - // Verify device type - // https://upower.freedesktop.org/docs/Device.html#Device:Type - // consider any peripheral, UPS and internal battery - let device_type = proxy.type_().await.error("Failed to get device's type")?; - if device_type == 1 { - continue; - } - let name = proxy - .native_path() - .await - .error("Failed to get device's native path")?; - if device.matches(&name) { - res = Some(proxy); - break; - } - } - match res { - Some(res) => res, - None => return Ok(None), - } - }; - - let changes = PropertiesProxy::builder(dbus_conn) - .destination("org.freedesktop.UPower") - .unwrap() - .path(device_proxy.inner().path().to_owned()) - .unwrap() - .build() - .await - .error("Failed to create PropertiesProxy")? - .receive_properties_changed() - .await - .error("Failed to create PropertiesChangedStream")?; - - Ok(Some(DeviceConnection { - device_proxy, - changes, - })) - } -} - -pub(super) struct Device { - dbus_conn: Connection, - device: DeviceName, - dev_model: Option, - device_conn: Option, - device_added_stream: DeviceAddedStream, - device_removed_stream: DeviceRemovedStream, -} - -impl Device { - pub(super) async fn new(device: DeviceName, dev_model: Option) -> Result { - let dbus_conn = new_system_dbus_connection().await?; - - let device_conn = DeviceConnection::new(&dbus_conn, &device, dev_model.as_deref()).await?; - - let upower_proxy = UPowerProxy::new(&dbus_conn) - .await - .error("Could not create UPowerProxy")?; - - let (device_added_stream, device_removed_stream) = try_join! { - upower_proxy.receive_device_added(), - upower_proxy.receive_device_removed() - } - .error("Could not create signal stream")?; - - Ok(Self { - dbus_conn, - device, - dev_model, - device_conn, - device_added_stream, - device_removed_stream, - }) - } -} - -#[async_trait] -impl BatteryDevice for Device { - async fn get_info(&mut self) -> Result> { - match &self.device_conn { - None => Ok(None), - Some(device_conn) => { - match try_join! { - device_conn.device_proxy.percentage(), - device_conn.device_proxy.energy_rate(), - device_conn.device_proxy.state(), - device_conn.device_proxy.time_to_full(), - device_conn.device_proxy.time_to_empty(), - } { - Err(_) => Ok(None), - Ok((capacity, power, state, time_to_full, time_to_empty)) => { - let status = match state { - 1 => BatteryStatus::Charging, - 2 | 6 => BatteryStatus::Discharging, - 3 => BatteryStatus::Empty, - 4 => BatteryStatus::Full, - 5 => BatteryStatus::NotCharging, - _ => BatteryStatus::Unknown, - }; - - let time_remaining = match status { - BatteryStatus::Charging => Some(time_to_full as f64), - BatteryStatus::Discharging => Some(time_to_empty as f64), - _ => None, - }; - - Ok(Some(BatteryInfo { - status, - capacity, - power: Some(power), - time_remaining, - })) - } - } - } - } - } - - async fn wait_for_change(&mut self) -> Result<()> { - match &mut self.device_conn { - Some(device_conn) => loop { - select! { - _ = self.device_added_stream.next() => {}, - _ = device_conn.changes.next() => { - break; - }, - Some(msg) = self.device_removed_stream.next() => { - let args = msg.args().unwrap(); - if args.device().as_ref() == device_conn.device_proxy.inner().path().as_ref() { - self.device_conn = None; - break; - } - }, - } - }, - None => loop { - select! { - _ = self.device_removed_stream.next() => {}, - _ = self.device_added_stream.next() => { - if let Some(device_conn) = - DeviceConnection::new(&self.dbus_conn, &self.device, self.dev_model.as_deref()).await? - { - self.device_conn = Some(device_conn); - break; - } - }, - } - }, - } - - Ok(()) - } -} - -#[zbus::proxy( - interface = "org.freedesktop.UPower.Device", - default_service = "org.freedesktop.UPower" -)] -trait Device { - #[zbus(property)] - fn energy_rate(&self) -> zbus::Result; - - #[zbus(property)] - fn is_present(&self) -> zbus::Result; - - #[zbus(property)] - fn native_path(&self) -> zbus::Result; - - #[zbus(property)] - fn model(&self) -> zbus::Result; - - #[zbus(property)] - fn online(&self) -> zbus::Result; - - #[zbus(property)] - fn percentage(&self) -> zbus::Result; - - #[zbus(property)] - fn state(&self) -> zbus::Result; - - #[zbus(property)] - fn time_to_empty(&self) -> zbus::Result; - - #[zbus(property)] - fn time_to_full(&self) -> zbus::Result; - - #[zbus(property, name = "Type")] - fn type_(&self) -> zbus::Result; -} - -#[zbus::proxy( - interface = "org.freedesktop.UPower", - default_service = "org.freedesktop.UPower", - default_path = "/org/freedesktop/UPower" -)] -trait UPower { - fn enumerate_devices(&self) -> zbus::Result>; - - fn get_display_device(&self) -> zbus::Result; - - #[zbus(signal)] - fn device_added(&self, device: zvariant::OwnedObjectPath) -> zbus::Result<()>; - - #[zbus(signal)] - fn device_removed(&self, device: zvariant::OwnedObjectPath) -> zbus::Result<()>; - - #[zbus(property)] - fn on_battery(&self) -> zbus::Result; -} diff --git a/src/blocks/bluetooth.rs b/src/blocks/bluetooth.rs deleted file mode 100644 index 427b8d5c93..0000000000 --- a/src/blocks/bluetooth.rs +++ /dev/null @@ -1,436 +0,0 @@ -//! Monitor Bluetooth device -//! -//! This block displays the connectivity of a given Bluetooth device and the battery level if this -//! is supported. Relies on the Bluez D-Bus API. -//! -//! When the device can be identified as an audio headset, a keyboard, joystick, or mouse, use the -//! relevant icon. Otherwise, fall back on the generic Bluetooth symbol. -//! -//! Right-clicking the block will attempt to connect (or disconnect) the device. -//! -//! Note: battery level information is not reported for some devices. [Enabling experimental -//! features of `bluez`](https://wiki.archlinux.org/title/bluetooth#Enabling_experimental_features) -//! may fix it. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `mac` | MAC address of the Bluetooth device | **Required** -//! `adapter_mac` | MAC Address of the Bluetooth adapter (in case your device was connected to multiple currently available adapters) | `None` -//! `format` | A string to customise the output of this block. See below for available placeholders. | \" $icon $name{ $percentage\|} \" -//! `disconnected_format` | A string to customise the output of this block. See below for available placeholders. | \" $icon{ $name\|} \" -//! `battery_state` | A mapping from battery percentage to block's [state](State) (color). See example below. | 0..15 -> critical, 16..30 -> warning, 31..60 -> info, 61..100 -> good -//! -//! Placeholder | Value | Type | Unit -//! ---------------|-----------------------------------------------------------------------|--------|------ -//! `icon` | Icon based on what type of device is connected | Icon | - -//! `name` | Device's name | Text | - -//! `percentage` | Device's battery level (may be absent if the device is not supported) | Number | % -//! `battery_icon` | Battery icon (may be absent if the device is not supported) | Icon | - -//! `available` | Present if the device is available | Flag | - -//! -//! Action | Default button -//! ---------|--------------- -//! `toggle` | Right -//! -//! # Examples -//! -//! This example just shows the icon when device is connected. -//! -//! ```toml -//! [[block]] -//! block = "bluetooth" -//! mac = "00:18:09:92:1B:BA" -//! disconnected_format = "" -//! format = " $icon " -//! [block.battery_state] -//! "0..20" = "critical" -//! "21..70" = "warning" -//! "71..100" = "good" -//! ``` -//! -//! # Icons Used -//! - `headphones` for bluetooth devices identifying as "audio-card", "audio-headset" or "audio-headphones" -//! - `joystick` for bluetooth devices identifying as "input-gaming" -//! - `keyboard` for bluetooth devices identifying as "input-keyboard" -//! - `mouse` for bluetooth devices identifying as "input-mouse" -//! - `bluetooth` for all other devices - -use zbus::fdo::{DBusProxy, ObjectManagerProxy, PropertiesProxy}; - -use super::prelude::*; -use crate::wrappers::RangeMap; - -make_log_macro!(debug, "bluetooth"); - -#[derive(Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct Config { - pub mac: String, - #[serde(default)] - pub adapter_mac: Option, - #[serde(default)] - pub format: FormatConfig, - #[serde(default)] - pub disconnected_format: FormatConfig, - #[serde(default)] - pub battery_state: Option>, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Right, None, "toggle")])?; - - let format = config.format.with_default(" $icon $name{ $percentage|} ")?; - let disconnected_format = config - .disconnected_format - .with_default(" $icon{ $name|} ")?; - - let mut monitor = DeviceMonitor::new(config.mac.clone(), config.adapter_mac.clone()).await?; - - let battery_states = config.battery_state.clone().unwrap_or_else(|| { - vec![ - (0..=15, State::Critical), - (16..=30, State::Warning), - (31..=60, State::Info), - (61..=100, State::Good), - ] - .into() - }); - - loop { - match monitor.get_device_info().await { - // Available - Some(device) => { - debug!("Device available, info: {device:?}"); - - let mut widget = Widget::new(); - - let values = map! { - "icon" => Value::icon(device.icon), - "name" => Value::text(device.name), - "available" => Value::flag(), - [if let Some(p) = device.battery_percentage] "percentage" => Value::percents(p), - [if let Some(p) = device.battery_percentage] - "battery_icon" => Value::icon_progression("bat", p as f64 / 100.0), - }; - - if device.connected { - widget.set_format(format.clone()); - widget.state = battery_states - .get(&device.battery_percentage.unwrap_or(100)) - .copied() - .unwrap_or(State::Good); - } else { - widget.set_format(disconnected_format.clone()); - widget.state = State::Idle; - } - - widget.set_values(values); - api.set_widget(widget)?; - } - // Unavailable - None => { - debug!("Showing device as unavailable"); - let mut widget = Widget::new().with_format(disconnected_format.clone()); - widget.set_values(map!("icon" => Value::icon("bluetooth"))); - api.set_widget(widget)?; - } - } - - loop { - select! { - res = monitor.wait_for_change() => { - res?; - break; - }, - Some(action) = actions.recv() => match action.as_ref() { - "toggle" => { - if let Some(dev) = &monitor.device - && let Ok(connected) = dev.device.connected().await { - if connected { - let _ = dev.device.disconnect().await; - } else { - let _ = dev.device.connect().await; - } - break; - } - } - _ => (), - } - } - } - } -} - -struct DeviceMonitor { - mac: String, - adapter_mac: Option, - manager_proxy: ObjectManagerProxy<'static>, - device: Option, -} - -struct Device { - props: PropertiesProxy<'static>, - device: Device1Proxy<'static>, - battery: Battery1Proxy<'static>, -} - -#[derive(Debug)] -struct DeviceInfo { - connected: bool, - icon: &'static str, - name: String, - battery_percentage: Option, -} - -impl DeviceMonitor { - async fn new(mac: String, adapter_mac: Option) -> Result { - let dbus_conn = new_system_dbus_connection().await?; - let manager_proxy = ObjectManagerProxy::builder(&dbus_conn) - .destination("org.bluez") - .and_then(|x| x.path("/")) - .unwrap() - .build() - .await - .error("Failed to create ObjectManagerProxy")?; - let device = Device::try_find(&manager_proxy, &mac, adapter_mac.as_deref()).await?; - Ok(Self { - mac, - adapter_mac, - manager_proxy, - device, - }) - } - - async fn wait_for_change(&mut self) -> Result<()> { - match &mut self.device { - None => { - let mut interface_added = self - .manager_proxy - .receive_interfaces_added() - .await - .error("Failed to monitor interfaces")?; - loop { - interface_added - .next() - .await - .error("Stream ended unexpectedly")?; - if let Some(device) = Device::try_find( - &self.manager_proxy, - &self.mac, - self.adapter_mac.as_deref(), - ) - .await? - { - self.device = Some(device); - debug!("Device has been added"); - return Ok(()); - } - } - } - Some(device) => { - let mut updates = device - .props - .receive_properties_changed() - .await - .error("Failed to receive updates")?; - - let mut interface_added = self - .manager_proxy - .receive_interfaces_added() - .await - .error("Failed to monitor interfaces")?; - - let mut interface_removed = self - .manager_proxy - .receive_interfaces_removed() - .await - .error("Failed to monitor interfaces")?; - - let mut bluez_owner_changed = - DBusProxy::new(self.manager_proxy.inner().connection()) - .await - .error("Failed to create DBusProxy")? - .receive_name_owner_changed_with_args(&[(0, "org.bluez")]) - .await - .unwrap(); - - loop { - select! { - _ = updates.next_debounced() => { - debug!("Got update for device"); - return Ok(()); - } - Some(event) = interface_added.next() => { - let args = event.args().error("Failed to get the args")?; - if args.object_path() == device.device.inner().path() { - debug!("Interfaces added: {:?}", args.interfaces_and_properties().keys()); - return Ok(()); - } - } - Some(event) = interface_removed.next() => { - let args = event.args().error("Failed to get the args")?; - if args.object_path() == device.device.inner().path() { - self.device = None; - debug!("Device is no longer available"); - return Ok(()); - } - } - Some(event) = bluez_owner_changed.next() => { - let args = event.args().error("Failed to get the args")?; - if args.new_owner.is_none() { - self.device = None; - debug!("org.bluez disappeared"); - return Ok(()); - } - } - } - } - } - } - } - - async fn get_device_info(&mut self) -> Option { - let device = self.device.as_ref()?; - - let Ok((connected, name)) = - tokio::try_join!(device.device.connected(), device.device.name(),) - else { - debug!("failed to fetch device info, assuming device or bluez disappeared"); - self.device = None; - return None; - }; - - //icon can be null, so ignore errors when fetching it - let icon: &str = match device.device.icon().await.ok().as_deref() { - Some("audio-card" | "audio-headset" | "audio-headphones") => "headphones", - Some("input-gaming") => "joystick", - Some("input-keyboard") => "keyboard", - Some("input-mouse") => "mouse", - _ => "bluetooth", - }; - - Some(DeviceInfo { - connected, - icon, - name, - battery_percentage: device.battery.percentage().await.ok(), - }) - } -} - -impl Device { - async fn try_find( - manager_proxy: &ObjectManagerProxy<'_>, - mac: &str, - adapter_mac: Option<&str>, - ) -> Result> { - let Ok(devices) = manager_proxy.get_managed_objects().await else { - debug!("could not get the list of managed objects"); - return Ok(None); - }; - - debug!("all managed devices: {:?}", devices); - - let root_object: Option = match adapter_mac { - Some(adapter_mac) => { - let mut adapter_path = None; - for (path, interfaces) in &devices { - let adapter_interface = match interfaces.get("org.bluez.Adapter1") { - Some(i) => i, - None => continue, // Not an adapter - }; - let addr: &str = adapter_interface - .get("Address") - .and_then(|a| a.downcast_ref().ok()) - .unwrap(); - if addr == adapter_mac { - adapter_path = Some(path); - break; - } - } - match adapter_path { - Some(path) => Some(format!("{}/", path.as_str())), - None => return Ok(None), - } - } - None => None, - }; - - debug!("root object: {:?}", root_object); - - for (path, interfaces) in devices { - if let Some(root) = &root_object - && !path.starts_with(root) - { - continue; - } - - let Some(device_interface) = interfaces.get("org.bluez.Device1") else { - // Not a device - continue; - }; - - let addr: &str = device_interface - .get("Address") - .and_then(|a| a.downcast_ref().ok()) - .unwrap(); - if addr != mac { - continue; - } - - debug!("Found device with path {:?}", path); - - return Ok(Some(Self { - props: PropertiesProxy::builder(manager_proxy.inner().connection()) - .destination("org.bluez") - .and_then(|x| x.path(path.clone())) - .unwrap() - .build() - .await - .error("Failed to create PropertiesProxy")?, - device: Device1Proxy::builder(manager_proxy.inner().connection()) - // No caching because https://github.com/greshake/i3status-rust/issues/1565#issuecomment-1379308681 - .cache_properties(zbus::proxy::CacheProperties::No) - .path(path.clone()) - .unwrap() - .build() - .await - .error("Failed to create Device1Proxy")?, - battery: Battery1Proxy::builder(manager_proxy.inner().connection()) - .cache_properties(zbus::proxy::CacheProperties::No) - .path(path) - .unwrap() - .build() - .await - .error("Failed to create Battery1Proxy")?, - })); - } - - debug!("No device found"); - Ok(None) - } -} - -#[zbus::proxy(interface = "org.bluez.Device1", default_service = "org.bluez")] -trait Device1 { - fn connect(&self) -> zbus::Result<()>; - fn disconnect(&self) -> zbus::Result<()>; - - #[zbus(property)] - fn connected(&self) -> zbus::Result; - - #[zbus(property)] - fn name(&self) -> zbus::Result; - - #[zbus(property)] - fn icon(&self) -> zbus::Result; -} - -#[zbus::proxy(interface = "org.bluez.Battery1", default_service = "org.bluez")] -trait Battery1 { - #[zbus(property)] - fn percentage(&self) -> zbus::Result; -} diff --git a/src/blocks/calendar.rs b/src/blocks/calendar.rs deleted file mode 100644 index 14f581d66e..0000000000 --- a/src/blocks/calendar.rs +++ /dev/null @@ -1,610 +0,0 @@ -//! Calendar -//! -//! This block displays upcoming calendar events retrieved from a CalDav ICalendar server. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `next_event_format` | A string to customize the output of this block when there is a next event in the calendar. See below for available placeholders. | \" $icon $start.datetime(f:'%a %H:%M') $summary \" -//! `ongoing_event_format` | A string to customize the output of this block when an event is ongoing. | \" $icon $summary (ends at $end.datetime(f:'%H:%M')) \" -//! `no_events_format` | A string to customize the output of this block when there are no events | \" $icon \" -//! `redirect_format` | A string to customize the output of this block when the authorization is asked | \" $icon Check your web browser \" -//! `fetch_interval` | Fetch events interval in seconds | `60` -//! `alternate_events_interval` | Alternate overlapping events interval in seconds | `10` -//! `events_within_hours` | Number of hours to look for events in the future | `48` -//! `source` | Array of sources to pull calendars from | `[]` -//! `warning_threshold` | Warning threshold in seconds for the upcoming event | `300` -//! `browser_cmd` | Command to open event details in a browser. The block passes the HTML link as an argument | `"xdg-open"` -//! -//! # Source Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `url` | CalDav calendar server URL | N/A -//! `auth` | Authentication configuration (unauthenticated, basic, or oauth2) | `unauthenticated` -//! `calendars` | List of calendar names to monitor. If empty, all calendars will be fetched. | `[]` -//! -//! Note: Currently only one source is supported -//! -//! Action | Description | Default button -//! ----------------|-------------------------------------------|--------------- -//! `open_link` | Opens the HTML link of the event | Left -//! -//! # Examples -//! -//! ## Unauthenticated -//! -//! ```toml -//! [[block]] -//! block = "calendar" -//! next_event_format = " $icon $start.datetime(f:'%a %H:%M') $summary " -//! ongoing_event_format = " $icon $summary (ends at $end.datetime(f:'%H:%M')) " -//! no_events_format = " $icon no events " -//! fetch_interval = 30 -//! alternate_events_interval = 10 -//! events_within_hours = 48 -//! warning_threshold = 600 -//! browser_cmd = "firefox" -//! [[block.source]] -//! url = "https://caldav.example.com/calendar/" -//! calendars = ["user/calendar"] -//! [block.source.auth] -//! type = "unauthenticated" -//! ``` -//! -//! ## Basic Authentication -//! -//! ```toml -//! [[block]] -//! block = "calendar" -//! next_event_format = " $icon $start.datetime(f:'%a %H:%M') $summary " -//! ongoing_event_format = " $icon $summary (ends at $end.datetime(f:'%H:%M')) " -//! no_events_format = " $icon no events " -//! fetch_interval = 30 -//! alternate_events_interval = 10 -//! events_within_hours = 48 -//! warning_threshold = 600 -//! browser_cmd = "firefox" -//! [[block.source]] -//! url = "https://caldav.example.com/calendar/" -//! calendars = [ "Holidays" ] -//! [block.source.auth] -//! type = "basic" -//! username = "your_username" -//! password = "your_password" -//! ``` -//! -//! Note: You can also configure the `username` and `password` in a separate TOML file. -//! -//! `~/.config/i3status-rust/example_credentials.toml` -//! ```toml -//! username = "my-username" -//! password = "my-password" -//! ``` -//! -//! Source auth configuration with `credentials_path`: -//! -//! ```toml -//! [block.source.auth] -//! type = "basic" -//! credentials_path = "~/.config/i3status-rust/example_credentials.toml" -//! ``` -//! -//! ## OAuth2 Authentication (Google Calendar) -//! -//! To access the CalDav API of Google, follow these steps to enable the API and obtain the `client_id` and `client_secret`: -//! 1. **Go to the Google Cloud Console**: Navigate to the [Google Cloud Console](https://console.cloud.google.com/). -//! 2. **Create a New Project**: If you don't already have a project, click on the project dropdown and select "New Project". Give your project a name and click "Create". -//! 3. **Enable the CalDAV API**: In the project dashboard, go to the "APIs & Services" > "Library". Search for "CalDAV API" and click on it, then click "Enable". -//! 4. **Set Up OAuth Consent Screen**: Go to "APIs & Services" > "OAuth consent screen". Fill out the required information and save. -//! 5. **Create Credentials**: -//! - Navigate to "APIs & Services" > "Credentials". -//! - Click "Create Credentials" and select "OAuth 2.0 Client IDs". -//! - Configure the consent screen if you haven't already. -//! - Set the application type to "Web application". -//! - Add your authorized redirect URIs. For example, `http://localhost:8080`. -//! - Click "Create" and note down the `client_id` and `client_secret`. -//! 6. **Download the Credentials**: Click on the download icon next to your OAuth 2.0 Client ID to download the JSON file containing your client ID and client secret. Use these values in your configuration. -//! -//! ```toml -//! [[block]] -//! block = "calendar" -//! next_event_format = " $icon $start.datetime(f:'%a %H:%M') $summary " -//! ongoing_event_format = " $icon $summary (ends at $end.datetime(f:'%H:%M')) " -//! no_events_format = " $icon no events " -//! fetch_interval = 30 -//! alternate_events_interval = 10 -//! events_within_hours = 48 -//! warning_threshold = 600 -//! browser_cmd = "firefox" -//! [[block.source]] -//! url = "https://apidata.googleusercontent.com/caldav/v2/" -//! calendars = ["primary"] -//! [block.source.auth] -//! type = "oauth2" -//! client_id = "your_client_id" -//! client_secret = "your_client_secret" -//! auth_url = "https://accounts.google.com/o/oauth2/auth" -//! token_url = "https://oauth2.googleapis.com/token" -//! auth_token = "~/.config/i3status-rust/calendar.auth_token" -//! redirect_port = 8080 -//! scopes = ["https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/calendar.events"] -//! ``` -//! -//! Note: You can also configure the `client_id` and `client_secret` in a separate TOML file. -//! -//! `~/.config/i3status-rust/google_credentials.toml` -//! ```toml -//! client_id = "my-client_id" -//! client_secret = "my-client_secret" -//! ``` -//! -//! Source auth configuration with `credentials_path`: -//! -//! ```toml -//! [block.source.auth] -//! type = "oauth2" -//! credentials_path = "~/.config/i3status-rust/google_credentials.toml" -//! auth_url = "https://accounts.google.com/o/oauth2/auth" -//! token_url = "https://oauth2.googleapis.com/token" -//! auth_token = "~/.config/i3status-rust/calendar.auth_token" -//! redirect_port = 8080 -//! scopes = ["https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/calendar.events"] -//! ``` -//! -//! # Format Configuration -//! -//! The format configuration is a string that can include placeholders to be replaced with dynamic content. -//! Placeholders can be: -//! - `$summary`: Summary of the event -//! - `$description`: Description of the event -//! - `$url`: Url of the event -//! - `$location`: Location of the event -//! - `$start`: Start time of the event -//! - `$end`: End time of the event -//! -//! # Icons Used -//! - `calendar` - -use chrono::{Duration, Local, Utc}; -use oauth2::{AuthUrl, ClientId, ClientSecret, Scope, TokenUrl}; -use reqwest::Url; - -use crate::util; -use crate::{subprocess::spawn_process, util::has_command}; - -mod auth; -mod caldav; - -use self::auth::{Authorize, AuthorizeUrl, OAuth2Flow, TokenStore, TokenStoreError}; -use self::caldav::Event; - -use super::prelude::*; - -use std::path::Path; -use std::sync::Arc; - -use caldav::Client; - -#[derive(Deserialize, Debug, SmartDefault, Clone)] -#[serde(deny_unknown_fields, default)] -pub struct BasicCredentials { - pub username: Option, - pub password: Option, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct BasicAuthConfig { - #[serde(flatten)] - pub credentials: BasicCredentials, - pub credentials_path: Option, -} - -#[derive(Deserialize, Debug, SmartDefault, Clone)] -#[serde(deny_unknown_fields, default)] -pub struct OAuth2Credentials { - pub client_id: Option, - pub client_secret: Option, -} - -#[derive(Deserialize, Debug, SmartDefault, Clone)] -#[serde(deny_unknown_fields, default)] -pub struct OAuth2Config { - #[serde(flatten)] - pub credentials: OAuth2Credentials, - pub credentials_path: Option, - pub auth_url: String, - pub token_url: String, - #[default("~/.config/i3status-rust/calendar.auth_token".into())] - pub auth_token: ShellString, - #[default(8080)] - pub redirect_port: u16, - pub scopes: Vec, -} - -#[derive(Deserialize, Default, Debug, Clone)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum AuthConfig { - #[default] - Unauthenticated, - Basic(BasicAuthConfig), - OAuth2(OAuth2Config), -} - -#[derive(Deserialize, Debug, SmartDefault, Clone)] -#[serde(deny_unknown_fields, default)] -pub struct SourceConfig { - pub url: String, - pub auth: AuthConfig, - pub calendars: Vec, -} - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub next_event_format: FormatConfig, - pub ongoing_event_format: FormatConfig, - pub no_events_format: FormatConfig, - pub redirect_format: FormatConfig, - #[default(60.into())] - pub fetch_interval: Seconds, - #[default(10.into())] - pub alternate_events_interval: Seconds, - #[default(48)] - pub events_within_hours: u32, - pub source: Vec, - #[default(300)] - pub warning_threshold: u32, - #[default("xdg-open".into())] - pub browser_cmd: ShellString, -} - -enum WidgetStatus { - AlternateEvents, - FetchSources, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let next_event_format = config - .next_event_format - .with_default(" $icon $start.datetime(f:'%a %H:%M') $summary ")?; - let ongoing_event_format = config - .ongoing_event_format - .with_default(" $icon $summary (ends at $end.datetime(f:'%H:%M')) ")?; - let no_events_format = config.no_events_format.with_default(" $icon ")?; - let redirect_format = config - .redirect_format - .with_default(" $icon Check your web browser ")?; - - api.set_default_actions(&[(MouseButton::Left, None, "open_link")])?; - - let source_config = match config.source.len() { - 0 => return Err(Error::new("A calendar source must be supplied")), - 1 => config - .source - .first() - .expect("There must be a first entry since the length is 1"), - _ => { - return Err(Error::new( - "Currently only one calendar source is supported", - )); - } - }; - - let warning_threshold = Duration::try_seconds(config.warning_threshold.into()) - .error("Invalid warning threshold configuration")?; - - let mut source = Source::new(source_config.clone()).await?; - - let mut timer = config.fetch_interval.timer(); - - let mut alternate_events_timer = config.alternate_events_interval.timer(); - - let mut actions = api.get_actions()?; - - let events_within = Duration::try_hours(config.events_within_hours.into()) - .error("Invalid events within hours configuration")?; - - let mut widget_status = WidgetStatus::FetchSources; - - let mut next_events = OverlappingEvents::default(); - - loop { - let mut widget = Widget::new().with_format(no_events_format.clone()); - widget.set_values(map! { - "icon" => Value::icon("calendar"), - }); - - if matches!(widget_status, WidgetStatus::FetchSources) { - for retries in 0..=1 { - match source.get_next_events(events_within).await { - Ok(events) => { - next_events.refresh(events); - break; - } - Err(err) => match err { - CalendarError::AuthRequired => { - let authorization = source - .client - .authorize() - .await - .error("Authorization failed")?; - match &authorization { - Authorize::AskUser(AuthorizeUrl { url, .. }) if retries == 0 => { - widget.set_format(redirect_format.clone()); - api.set_widget(widget.clone())?; - open_browser(config, url).await?; - source - .client - .ask_user(authorization) - .await - .error("Ask user failed")?; - } - _ => { - return Err(Error::new( - "Authorization failed. Check your configurations", - )); - } - } - } - e => { - return Err(Error { - message: None, - cause: Some(Arc::new(e)), - }); - } - }, - }; - } - } - - if let Some(event) = next_events.current().cloned() - && let Some(start_date) = event.start_at - && let Some(end_date) = event.end_at - { - let warn_datetime = start_date - warning_threshold; - if warn_datetime < Utc::now() && Utc::now() < start_date { - widget.state = State::Warning; - } - if start_date < Utc::now() && Utc::now() < end_date { - widget.set_format(ongoing_event_format.clone()); - } else { - widget.set_format(next_event_format.clone()); - } - widget.set_values(map! { - "icon" => Value::icon("calendar"), - [if let Some(summary) = event.summary] "summary" => Value::text(summary), - [if let Some(description) = event.description] "description" => Value::text(description), - [if let Some(location) = event.location] "location" => Value::text(location), - [if let Some(url) = event.url] "url" => Value::text(url), - "start" => Value::datetime(start_date, None), - "end" => Value::datetime(end_date, None), - }); - } - - api.set_widget(widget)?; - loop { - select! { - _ = timer.tick() => { - widget_status = WidgetStatus::FetchSources; - break - } - _ = alternate_events_timer.tick() => { - next_events.cycle_warning_or_ongoing(warning_threshold); - widget_status = WidgetStatus::AlternateEvents; - break - } - _ = api.wait_for_update_request() => break, - Some(action) = actions.recv() => match action.as_ref() { - "open_link" => { - if let Some(Event { url: Some(url), .. }) = next_events.current() - && let Ok(url) = Url::parse(url) { - open_browser(config, &url).await?; - } - } - _ => () - } - } - } - } -} - -struct Source { - pub client: caldav::Client, - pub config: SourceConfig, -} - -impl Source { - async fn new(config: SourceConfig) -> Result { - let auth = match &config.auth { - AuthConfig::Unauthenticated => auth::Auth::Unauthenticated, - AuthConfig::Basic(BasicAuthConfig { - credentials, - credentials_path, - }) => { - let credentials = if let Some(path) = credentials_path { - util::deserialize_toml_file(path.expand()?.to_string()) - .error("Failed to read basic credentials file")? - } else { - credentials.clone() - }; - let BasicCredentials { - username: Some(username), - password: Some(password), - } = credentials - else { - return Err(Error::new("Basic credentials are not configured")); - }; - auth::Auth::basic(username, password) - } - AuthConfig::OAuth2(oauth2) => { - let credentials = if let Some(path) = &oauth2.credentials_path { - util::deserialize_toml_file(path.expand()?.to_string()) - .error("Failed to read oauth2 credentials file")? - } else { - oauth2.credentials.clone() - }; - let OAuth2Credentials { - client_id: Some(client_id), - client_secret: Some(client_secret), - } = credentials - else { - return Err(Error::new("Oauth2 credentials are not configured")); - }; - let auth_url = - AuthUrl::new(oauth2.auth_url.clone()).error("Invalid authorization url")?; - let token_url = - TokenUrl::new(oauth2.token_url.clone()).error("Invalid token url")?; - - let flow = OAuth2Flow::new( - ClientId::new(client_id), - ClientSecret::new(client_secret), - auth_url, - token_url, - oauth2.redirect_port, - ); - let token_store = - TokenStore::new(Path::new(&oauth2.auth_token.expand()?.to_string())); - auth::Auth::oauth2(flow, token_store, oauth2.scopes.clone()) - } - }; - Ok(Self { - client: Client::new( - Url::parse(&config.url).error("Invalid CalDav server url")?, - auth, - ), - config, - }) - } - - async fn get_next_events( - &mut self, - within: Duration, - ) -> Result { - let calendars: Vec<_> = self - .client - .calendars() - .await? - .into_iter() - .filter(|c| self.config.calendars.is_empty() || self.config.calendars.contains(&c.name)) - .collect(); - let mut events: Vec = vec![]; - for calendar in calendars { - let calendar_events: Vec<_> = self - .client - .events( - &calendar, - Local::now() - .date_naive() - .and_hms_opt(0, 0, 0) - .expect("A valid time") - .and_local_timezone(Local) - .earliest() - .expect("A valid datetime") - .to_utc(), - Utc::now() + within, - ) - .await? - .into_iter() - .filter(|e| { - let not_started = e.start_at.is_some_and(|d| d > Utc::now()); - let is_ongoing = e.start_at.is_some_and(|d| d < Utc::now()) - && e.end_at.is_some_and(|d| d > Utc::now()); - not_started || is_ongoing - }) - .collect(); - events.extend(calendar_events); - } - - events.sort_by_key(|e| e.start_at); - let Some(next_event) = events.first().cloned() else { - return Ok(OverlappingEvents::default()); - }; - let overlapping_events = events - .into_iter() - .take_while(|e| e.start_at <= next_event.end_at) - .collect(); - Ok(OverlappingEvents::new(overlapping_events)) - } -} - -#[derive(Default)] -struct OverlappingEvents { - current: Option, - events: Vec, -} - -impl OverlappingEvents { - fn new(events: Vec) -> Self { - Self { - current: events.first().cloned(), - events, - } - } - - fn refresh(&mut self, other: OverlappingEvents) { - if self.current.is_none() { - self.current = other.events.first().cloned(); - } - self.events = other.events; - } - - fn current(&self) -> Option<&Event> { - self.current.as_ref() - } - - fn cycle_warning_or_ongoing(&mut self, warning_threshold: Duration) { - self.current = if let Some(current) = &self.current { - if self.events.iter().any(|e| e.uid == current.uid) { - let mut iter = self - .events - .iter() - .cycle() - .skip_while(|e| e.uid != current.uid); - iter.next(); - iter.find(|e| { - let is_ongoing = e.start_at.is_some_and(|d| d < Utc::now()) - && e.end_at.is_some_and(|d| d > Utc::now()); - let is_warning = e - .start_at - .is_some_and(|d| d - warning_threshold < Utc::now() && Utc::now() < d); - e.uid == current.uid || is_warning || is_ongoing - }) - .cloned() - } else { - self.events.first().cloned() - } - } else { - self.events.first().cloned() - }; - } -} - -async fn open_browser(config: &Config, url: &Url) -> Result<()> { - let cmd = config.browser_cmd.expand()?; - has_command(&cmd) - .await - .or_error(|| "Browser command not found")?; - spawn_process(&cmd, &[url.as_ref()]).error("Open browser failed") -} - -#[derive(thiserror::Error, Debug)] -pub enum CalendarError { - #[error(transparent)] - Http(#[from] reqwest::Error), - #[error(transparent)] - Deserialize(#[from] quick_xml::de::DeError), - #[error("Parsing error: {0}")] - Parsing(String), - #[error("Auth required")] - AuthRequired, - #[error(transparent)] - Io(#[from] std::io::Error), - #[error(transparent)] - Serialize(#[from] serde_json::Error), - #[error("Request token error: {0}")] - RequestToken(String), - #[error("Store token error: {0}")] - StoreToken(#[from] TokenStoreError), -} diff --git a/src/blocks/calendar/auth.rs b/src/blocks/calendar/auth.rs deleted file mode 100644 index 96bb3109bd..0000000000 --- a/src/blocks/calendar/auth.rs +++ /dev/null @@ -1,330 +0,0 @@ -use base64::Engine as _; -use oauth2::basic::{ - BasicErrorResponse, BasicRevocationErrorResponse, BasicTokenIntrospectionResponse, - BasicTokenResponse, -}; -use oauth2::{ - AuthUrl, AuthorizationCode, Client, ClientId, ClientSecret, CsrfToken, EndpointNotSet, - EndpointSet, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, RefreshToken, Scope, - StandardRevocableToken, TokenResponse as _, TokenUrl, -}; -use reqwest; -use reqwest::Url; -use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue}; -use std::path::{Path, PathBuf}; -use std::sync::LazyLock; -use tokio::fs::File; -use tokio::io::{AsyncBufReadExt as _, AsyncReadExt as _, AsyncWriteExt as _, BufReader}; -use tokio::net::TcpListener; - -use super::CalendarError; -use crate::{APP_USER_AGENT, REQWEST_TIMEOUT}; - -static REQWEST_CLIENT: LazyLock = LazyLock::new(|| { - reqwest::Client::builder() - .user_agent(APP_USER_AGENT) - .timeout(REQWEST_TIMEOUT) - // Following redirects opens the client up to SSRF vulnerabilities. - .redirect(reqwest::redirect::Policy::none()) - .build() - .unwrap() -}); - -type BasicClient< - HasAuthUrl = EndpointSet, - HasDeviceAuthUrl = EndpointNotSet, - HasIntrospectionUrl = EndpointNotSet, - HasRevocationUrl = EndpointNotSet, - HasTokenUrl = EndpointSet, -> = Client< - BasicErrorResponse, - BasicTokenResponse, - BasicTokenIntrospectionResponse, - StandardRevocableToken, - BasicRevocationErrorResponse, - HasAuthUrl, - HasDeviceAuthUrl, - HasIntrospectionUrl, - HasRevocationUrl, - HasTokenUrl, ->; - -pub enum Auth { - Unauthenticated, - Basic(Basic), - OAuth2(Box), -} - -impl Auth { - pub fn oauth2(flow: OAuth2Flow, token_store: TokenStore, scopes: Vec) -> Self { - Self::OAuth2(Box::new(OAuth2 { - flow, - token_store, - scopes, - })) - } - pub fn basic(username: String, password: String) -> Self { - Self::Basic(Basic { username, password }) - } - pub async fn headers(&mut self) -> HeaderMap { - match self { - Auth::Unauthenticated => HeaderMap::new(), - Auth::Basic(auth) => auth.headers().await, - Auth::OAuth2(auth) => auth.headers().await, - } - } - - pub async fn handle_error(&mut self, error: reqwest::Error) -> Result<(), CalendarError> { - match self { - Auth::Unauthenticated | Auth::Basic(_) => Err(CalendarError::Http(error)), - Auth::OAuth2(auth) => auth.handle_error(error).await, - } - } - - pub async fn authorize(&mut self) -> Result { - match self { - Auth::Unauthenticated | Auth::Basic(_) => Ok(Authorize::Completed), - Auth::OAuth2(auth) => Ok(Authorize::AskUser(auth.authorize().await?)), - } - } - pub async fn ask_user(&mut self, authorize_url: AuthorizeUrl) -> Result<(), CalendarError> { - match self { - Auth::Unauthenticated | Auth::Basic(_) => Ok(()), - Auth::OAuth2(auth) => auth.ask_user(authorize_url).await, - } - } -} - -pub struct Basic { - username: String, - password: String, -} - -impl Basic { - pub async fn headers(&mut self) -> HeaderMap { - let mut headers = HeaderMap::new(); - let header = - base64::prelude::BASE64_STANDARD.encode(format!("{}:{}", self.username, self.password)); - let mut header_value = HeaderValue::from_str(format!("Basic {header}").as_str()) - .expect("A valid basic header"); - header_value.set_sensitive(true); - headers.insert(AUTHORIZATION, header_value); - headers - } -} - -pub struct OAuth2 { - flow: OAuth2Flow, - token_store: TokenStore, - scopes: Vec, -} - -impl OAuth2 { - pub async fn headers(&mut self) -> HeaderMap { - let mut headers = HeaderMap::new(); - if let Some(token) = self.token_store.get().await { - let mut auth_value = - HeaderValue::from_str(format!("Bearer {}", token.access_token().secret()).as_str()) - .expect("A valid access token"); - auth_value.set_sensitive(true); - headers.insert(AUTHORIZATION, auth_value); - } - headers - } - - async fn handle_error(&mut self, error: reqwest::Error) -> Result<(), CalendarError> { - if let Some(status) = error.status() { - if status == 401 { - match self - .token_store - .get() - .await - .and_then(|t| t.refresh_token().cloned()) - { - Some(refresh_token) => { - let mut token = self.flow.refresh_token_exchange(&refresh_token).await?; - if token.refresh_token().is_none() { - token.set_refresh_token(Some(refresh_token)); - } - self.token_store.store(token).await?; - return Ok(()); - } - None => return Err(CalendarError::AuthRequired), - } - } - if status == 403 { - return Err(CalendarError::AuthRequired); - } - } - Err(CalendarError::Http(error)) - } - - async fn authorize(&mut self) -> Result { - Ok(self.flow.authorize_url(self.scopes.clone())) - } - - async fn ask_user(&mut self, authorize_url: AuthorizeUrl) -> Result<(), CalendarError> { - let token = self.flow.redirect(authorize_url).await?; - self.token_store.store(token).await?; - Ok(()) - } -} -pub struct OAuth2Flow { - client: BasicClient, - redirect_port: u16, -} - -impl OAuth2Flow { - pub fn new( - client_id: ClientId, - client_secret: ClientSecret, - auth_url: AuthUrl, - token_url: TokenUrl, - redirect_port: u16, - ) -> Self { - Self { - client: BasicClient::new(client_id) - .set_client_secret(client_secret) - .set_auth_uri(auth_url) - .set_token_uri(token_url) - .set_redirect_uri( - RedirectUrl::new(format!("http://localhost:{redirect_port}").to_string()) - .expect("A valid redirect URL"), - ), - redirect_port, - } - } - - pub fn authorize_url(&self, scopes: Vec) -> AuthorizeUrl { - let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256(); - let (authorize_url, csrf_token) = self - .client - .authorize_url(CsrfToken::new_random) - .add_scopes(scopes) - .set_pkce_challenge(pkce_code_challenge.clone()) - .url(); - AuthorizeUrl { - pkce_code_verifier, - url: authorize_url, - csrf_token, - } - } - - pub async fn refresh_token_exchange( - &self, - token: &RefreshToken, - ) -> Result { - self.client - .exchange_refresh_token(token) - .request_async(&*REQWEST_CLIENT) - .await - .map_err(|e| CalendarError::RequestToken(e.to_string())) - } - - pub async fn redirect( - &self, - authorize_url: AuthorizeUrl, - ) -> Result { - let client = self.client.clone(); - let redirect_port = self.redirect_port; - let listener = TcpListener::bind(format!("127.0.0.1:{redirect_port}")).await?; - let (mut stream, _) = listener.accept().await?; - let mut request_line = String::new(); - let mut reader = BufReader::new(&mut stream); - reader.read_line(&mut request_line).await?; - - let redirect_url = request_line - .split_whitespace() - .nth(1) - .ok_or(CalendarError::RequestToken("Invalid redirect url".into()))?; - let url = Url::parse(&("http://localhost".to_string() + redirect_url)) - .map_err(|e| CalendarError::RequestToken(e.to_string()))?; - - let (_, code_value) = - url.query_pairs() - .find(|(key, _)| key == "code") - .ok_or(CalendarError::RequestToken( - "code query param is missing".into(), - ))?; - let code = AuthorizationCode::new(code_value.into_owned()); - let (_, state_value) = url.query_pairs().find(|(key, _)| key == "state").ok_or( - CalendarError::RequestToken("state query param is missing".into()), - )?; - let state = CsrfToken::new(state_value.into_owned()); - if state.secret() != authorize_url.csrf_token.secret() { - return Err(CalendarError::RequestToken( - "Received state and csrf token are different".to_string(), - )); - } - - let message = "Now your i3status-rust calendar is authorized"; - let response = format!( - "HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}", - message.len(), - message - ); - stream.write_all(response.as_bytes()).await?; - - client - .exchange_code(code) - .set_pkce_verifier(authorize_url.pkce_code_verifier) - .request_async(&*REQWEST_CLIENT) - .await - .map_err(|e| CalendarError::RequestToken(e.to_string())) - } -} - -#[derive(Debug)] -pub enum Authorize { - Completed, - AskUser(AuthorizeUrl), -} - -#[derive(Debug)] -pub struct AuthorizeUrl { - pkce_code_verifier: PkceCodeVerifier, - pub url: Url, - csrf_token: CsrfToken, -} - -#[derive(Debug)] -pub struct TokenStore { - path: PathBuf, - token: Option, -} - -impl TokenStore { - pub fn new(path: &Path) -> Self { - Self { - path: path.into(), - token: None, - } - } - - pub async fn store(&mut self, token: BasicTokenResponse) -> Result<(), TokenStoreError> { - let mut file = File::create(&self.path).await?; - let value = serde_json::to_string(&token)?; - file.write_all(value.as_bytes()).await?; - self.token = Some(token); - Ok(()) - } - - pub async fn get(&mut self) -> Option { - if self.token.is_none() - && let Ok(mut file) = File::open(&self.path).await - { - let mut content = vec![]; - file.read_to_end(&mut content).await.ok()?; - self.token = serde_json::from_slice(&content).ok(); - } - self.token.clone() - } -} - -#[derive(thiserror::Error, Debug)] -pub enum TokenStoreError { - #[error(transparent)] - Io(#[from] std::io::Error), - #[error(transparent)] - Serde(#[from] serde_json::Error), -} diff --git a/src/blocks/calendar/caldav.rs b/src/blocks/calendar/caldav.rs deleted file mode 100644 index d232ce6962..0000000000 --- a/src/blocks/calendar/caldav.rs +++ /dev/null @@ -1,374 +0,0 @@ -use std::{str::FromStr as _, time::Duration, vec}; - -use chrono::{DateTime, Local, Utc}; -use icalendar::{Component as _, EventLike as _}; -use reqwest::{ - self, ClientBuilder, Method, Url, - header::{CONTENT_TYPE, HeaderMap, HeaderValue}, -}; -use serde::Deserialize; - -use super::{ - CalendarError, - auth::{Auth, Authorize}, -}; - -#[derive(Clone, Debug)] -pub struct Event { - pub uid: Option, - pub summary: Option, - pub description: Option, - pub location: Option, - pub url: Option, - pub start_at: Option>, - pub end_at: Option>, -} - -#[derive(Deserialize, Debug)] -pub struct Calendar { - pub url: Url, - pub name: String, -} - -pub struct Client { - url: Url, - client: reqwest::Client, - auth: Auth, -} - -impl Client { - pub fn new(url: Url, auth: Auth) -> Self { - let mut headers = HeaderMap::new(); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/xml")); - Self { - url, - client: ClientBuilder::new() - .timeout(Duration::from_secs(10)) - .default_headers(headers) - .build() - .expect("A valid http client"), - auth, - } - } - async fn propfind_request( - &mut self, - url: Url, - depth: usize, - body: String, - ) -> Result { - let request = self - .client - .request(Method::from_str("PROPFIND").expect("A valid method"), url) - .body(body.clone()) - .headers(self.auth.headers().await) - .header("Depth", depth) - .build() - .expect("A valid propfind request"); - self.call(request).await - } - - async fn report_request( - &mut self, - url: Url, - depth: usize, - body: String, - ) -> Result { - let request = self - .client - .request(Method::from_str("REPORT").expect("A valid method"), url) - .body(body) - .headers(self.auth.headers().await) - .header("Depth", depth) - .build() - .expect("A valid report request"); - self.call(request).await - } - - async fn call(&mut self, request: reqwest::Request) -> Result { - let mut retries = 0; - loop { - let result = self - .client - .execute(request.try_clone().expect("Request to be cloneable")) - .await?; - match result.error_for_status() { - Err(err) if retries == 0 => { - self.auth.handle_error(err).await?; - retries += 1; - } - Err(err) => return Err(CalendarError::Http(err)), - Ok(result) => return Ok(quick_xml::de::from_str(result.text().await?.as_str())?), - }; - } - } - - async fn user_principal_url(&mut self) -> Result { - let multi_status = self - .propfind_request(self.url.clone(), 1, CURRENT_USER_PRINCIPAL.into()) - .await?; - parse_href(multi_status, self.url.clone()) - } - - async fn home_set_url(&mut self, user_principal_url: Url) -> Result { - let multi_status = self - .propfind_request(user_principal_url, 0, CALENDAR_HOME_SET.into()) - .await?; - parse_href(multi_status, self.url.clone()) - } - - async fn calendars_query(&mut self, home_set_url: Url) -> Result, CalendarError> { - let multi_status = self - .propfind_request(home_set_url, 1, CALENDAR_REQUEST.into()) - .await?; - parse_calendars(multi_status, self.url.clone()) - } - - pub async fn calendars(&mut self) -> Result, CalendarError> { - let user_principal_url = self.user_principal_url().await?; - let home_set_url = self.home_set_url(user_principal_url).await?; - self.calendars_query(home_set_url).await - } - - pub async fn events( - &mut self, - calendar: &Calendar, - start: DateTime, - end: DateTime, - ) -> Result, CalendarError> { - let multi_status = self - .report_request(calendar.url.clone(), 1, calendar_events_request(start, end)) - .await?; - parse_events(multi_status) - } - - pub async fn authorize(&mut self) -> Result { - self.auth.authorize().await - } - - pub async fn ask_user(&mut self, authorize: Authorize) -> Result<(), CalendarError> { - match authorize { - Authorize::Completed => Ok(()), - Authorize::AskUser(authorize_url) => self.auth.ask_user(authorize_url).await, - } - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename = "multistatus")] -struct Multistatus { - #[serde(rename = "response", default)] - responses: Vec, -} - -#[derive(Debug, Deserialize)] -struct Response { - href: String, - #[serde(rename = "propstat", default)] - propstats: Vec, -} - -impl Response { - fn valid_props(self) -> Vec { - self.propstats - .into_iter() - .filter(|p| p.status.contains("200")) - .flat_map(|p| p.prop.values.into_iter()) - .collect() - } -} - -#[derive(Debug, Deserialize)] -struct Propstat { - status: String, - prop: Prop, -} - -#[derive(Debug, Deserialize)] -struct Prop { - #[serde(rename = "$value")] - pub values: Vec, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "kebab-case")] -enum PropValue { - CurrentUserPrincipal(HrefProperty), - CalendarHomeSet(HrefProperty), - SupportedCalendarComponentSet(SupportedCalendarComponentSet), - #[serde(rename = "displayname")] - DisplayName(String), - #[serde(rename = "resourcetype")] - ResourceType(ResourceTypes), - CalendarData(String), -} - -#[derive(Debug, Deserialize)] -pub struct HrefProperty { - href: String, -} - -#[derive(Debug, Deserialize)] -struct ResourceTypes { - #[serde(rename = "$value")] - pub values: Vec, -} - -impl ResourceTypes { - fn is_calendar(&self) -> bool { - self.values.contains(&ResourceType::Calendar) - } -} -#[derive(Debug, Deserialize, PartialEq)] -#[serde(rename_all = "kebab-case")] -enum ResourceType { - Calendar, - #[serde(other)] - Unsupported, -} - -#[derive(Debug, Deserialize)] -struct SupportedCalendarComponentSet { - #[serde(rename = "$value", default)] - pub values: Vec, -} -impl SupportedCalendarComponentSet { - fn supports_events(&self) -> bool { - self.values.iter().any(|v| v.name == "VEVENT") - } -} - -#[derive(Debug, Deserialize)] -struct Comp { - #[serde(rename = "@name", default)] - name: String, -} - -fn parse_href(multi_status: Multistatus, base_url: Url) -> Result { - let props = multi_status - .responses - .into_iter() - .flat_map(|r| r.valid_props().into_iter()) - .next(); - match props.ok_or_else(|| CalendarError::Parsing("Property not found".into()))? { - PropValue::CurrentUserPrincipal(href) | PropValue::CalendarHomeSet(href) => base_url - .join(&href.href) - .map_err(|e| CalendarError::Parsing(e.to_string())), - _ => Err(CalendarError::Parsing("Invalid property".into())), - } -} - -fn parse_calendars( - multi_status: Multistatus, - base_url: Url, -) -> Result, CalendarError> { - let mut result = vec![]; - for response in multi_status.responses { - let mut is_calendar = false; - let mut supports_events = false; - let mut name = None; - let href = response.href.clone(); - for prop in response.valid_props() { - match prop { - PropValue::SupportedCalendarComponentSet(comp) => { - supports_events = comp.supports_events(); - } - PropValue::DisplayName(display_name) => name = Some(display_name), - PropValue::ResourceType(ty) => is_calendar = ty.is_calendar(), - _ => {} - } - } - if is_calendar - && supports_events - && let Some(name) = name - { - result.push(Calendar { - name, - url: base_url - .join(&href) - .map_err(|_| CalendarError::Parsing("Malformed calendar url".into()))?, - }); - } - } - Ok(result) -} - -fn parse_events(multi_status: Multistatus) -> Result, CalendarError> { - let mut result = vec![]; - for response in multi_status.responses { - for prop in response.valid_props() { - if let PropValue::CalendarData(data) = prop { - let calendar = - icalendar::Calendar::from_str(&data).map_err(CalendarError::Parsing)?; - for component in calendar.components { - if let icalendar::CalendarComponent::Event(event) = component { - let start_at = event.get_start().and_then(|d| match d { - icalendar::DatePerhapsTime::DateTime(dt) => dt.try_into_utc(), - icalendar::DatePerhapsTime::Date(d) => d - .and_hms_opt(0, 0, 0) - .and_then(|d| d.and_local_timezone(Local).earliest()) - .map(|d| d.to_utc()), - }); - let end_at = event.get_end().and_then(|d| match d { - icalendar::DatePerhapsTime::DateTime(dt) => dt.try_into_utc(), - icalendar::DatePerhapsTime::Date(d) => d - .and_hms_opt(23, 59, 59) - .and_then(|d| d.and_local_timezone(Local).earliest()) - .map(|d| d.to_utc()), - }); - result.push(Event { - uid: event.get_uid().map(Into::into), - summary: event.get_summary().map(Into::into), - description: event.get_description().map(Into::into), - location: event.get_location().map(Into::into), - url: event.get_url().map(Into::into), - start_at, - end_at, - }); - } - } - } - } - } - Ok(result) -} - -static CURRENT_USER_PRINCIPAL: &str = r#" - - - - "#; - -static CALENDAR_HOME_SET: &str = r#" - - - - "#; - -static CALENDAR_REQUEST: &str = r#" - - - - - - "#; - -pub fn calendar_events_request(start: DateTime, end: DateTime) -> String { - const DATE_FORMAT: &str = "%Y%m%dT%H%M%SZ"; - let start = start.format(DATE_FORMAT); - let end = end.format(DATE_FORMAT); - format!( - r#" - - - - - - - - - - - - "# - ) -} diff --git a/src/blocks/cpu.rs b/src/blocks/cpu.rs deleted file mode 100644 index 907dad6961..0000000000 --- a/src/blocks/cpu.rs +++ /dev/null @@ -1,267 +0,0 @@ -//! CPU statistics -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $utilization "` -//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None` -//! `interval` | Update interval in seconds | `5` -//! `info_cpu` | Percentage of CPU usage, where state is set to info | `30.0` -//! `warning_cpu` | Percentage of CPU usage, where state is set to warning | `60.0` -//! `critical_cpu` | Percentage of CPU usage, where state is set to critical | `90.0` -//! -//! Placeholder | Value | Type | Unit -//! -----------------|----------------------------------------------------------------------|--------|--------------- -//! `icon` | An icon | Icon | - -//! `utilization` | Average CPU utilization | Number | % -//! `utilization` | Utilization of Nth logical CPU | Number | % -//! `barchart` | Utilization of all logical CPUs presented as a barchart | Text | - -//! `frequency` | Average CPU frequency (may be absent if CPU is not supported) | Number | Hz -//! `frequency` | Frequency of Nth logical CPU (may be absent if CPU is not supported) | Number | Hz -//! `max_frequency` | Max frequency of all logical CPUs | Number | Hz -//! `boost` | CPU turbo boost status (may be absent if CPU is not supported) | Text | - -//! -//! Action | Description | Default button -//! ----------------|-------------------------------------------|--------------- -//! `toggle_format` | Toggles between `format` and `format_alt` | Left -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "cpu" -//! interval = 1 -//! format = " $icon $barchart $utilization " -//! format_alt = " $icon $frequency{ $boost|} " -//! info_cpu = 20 -//! warning_cpu = 50 -//! critical_cpu = 90 -//! ``` -//! -//! # Icons Used -//! - `cpu` (as a progression) -//! - `cpu_boost_on` -//! - `cpu_boost_off` - -use std::str::FromStr as _; - -use tokio::fs::File; -use tokio::io::{AsyncBufReadExt as _, BufReader}; - -use super::prelude::*; -use crate::util::read_file; - -const CPU_BOOST_PATH: &str = "/sys/devices/system/cpu/cpufreq/boost"; -const CPU_NO_TURBO_PATH: &str = "/sys/devices/system/cpu/intel_pstate/no_turbo"; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - pub format_alt: Option, - #[default(5.into())] - pub interval: Seconds, - #[default(30.0)] - pub info_cpu: f64, - #[default(60.0)] - pub warning_cpu: f64, - #[default(90.0)] - pub critical_cpu: f64, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?; - - let mut format = config.format.with_default(" $icon $utilization ")?; - let mut format_alt = match &config.format_alt { - Some(f) => Some(f.with_default("")?), - None => None, - }; - - // Store previous /proc/stat state - let mut cputime = read_proc_stat().await?; - let cores = cputime.1.len(); - - if cores == 0 { - return Err(Error::new("/proc/stat reported zero cores")); - } - - let mut timer = config.interval.timer(); - - loop { - let freqs = read_frequencies().await?; - - // Compute utilizations - let new_cputime = read_proc_stat().await?; - let utilization_avg = new_cputime.0.utilization(cputime.0); - let mut utilizations = Vec::new(); - if new_cputime.1.len() != cores { - return Err(Error::new("new cputime length is incorrect")); - } - for i in 0..cores { - utilizations.push(new_cputime.1[i].utilization(cputime.1[i])); - } - cputime = new_cputime; - - // Create barchart indicating per-core utilization - let mut barchart = String::new(); - const BOXCHARS: &[char] = &['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']; - for utilization in &utilizations { - barchart.push(BOXCHARS[(7.5 * utilization) as usize]); - } - - // Read boost state on intel CPUs - let boost = boost_status().await.map(|status| match status { - true => "cpu_boost_on", - false => "cpu_boost_off", - }); - - let mut values = map!( - "icon" => Value::icon_progression("cpu", utilization_avg), - "barchart" => Value::text(barchart), - "utilization" => Value::percents(utilization_avg * 100.), - [if !freqs.is_empty()] "frequency" => Value::hertz(freqs.iter().sum::() / (freqs.len() as f64)), - [if !freqs.is_empty()] "max_frequency" => Value::hertz(freqs.iter().copied().max_by(f64::total_cmp).unwrap()), - ); - boost.map(|b| values.insert("boost".into(), Value::icon(b))); - for (i, freq) in freqs.iter().enumerate() { - values.insert(format!("frequency{}", i + 1).into(), Value::hertz(*freq)); - } - for (i, utilization) in utilizations.iter().enumerate() { - values.insert( - format!("utilization{}", i + 1).into(), - Value::percents(utilization * 100.), - ); - } - - let mut widget = Widget::new().with_format(format.clone()); - widget.set_values(values); - widget.state = match utilization_avg * 100. { - x if x > config.critical_cpu => State::Critical, - x if x > config.warning_cpu => State::Warning, - x if x > config.info_cpu => State::Info, - _ => State::Idle, - }; - api.set_widget(widget)?; - - loop { - select! { - _ = timer.tick() => break, - _ = api.wait_for_update_request() => break, - Some(action) = actions.recv() => match action.as_ref() { - "toggle_format" => { - if let Some(ref mut format_alt) = format_alt { - std::mem::swap(format_alt, &mut format); - break; - } - } - _ => (), - } - } - } - } -} - -// Read frequencies (read in MHz, store in Hz) -async fn read_frequencies() -> Result> { - let mut freqs = Vec::with_capacity(32); - - let file = File::open("/proc/cpuinfo") - .await - .error("failed to read /proc/cpuinfo")?; - let mut file = BufReader::new(file); - - let mut line = String::new(); - while file - .read_line(&mut line) - .await - .error("failed to read /proc/cpuinfo")? - != 0 - { - if line.starts_with("cpu MHz") { - let slice = line - .trim_end() - .trim_start_matches(|c: char| !c.is_ascii_digit()); - freqs.push(f64::from_str(slice).error("failed to parse /proc/cpuinfo")? * 1e6); - } - line.clear(); - } - - Ok(freqs) -} - -#[derive(Debug, Clone, Copy)] -struct CpuTime { - idle: u64, - non_idle: u64, -} - -impl CpuTime { - fn from_str(s: &str) -> Option { - let mut s = s.trim().split_ascii_whitespace(); - let user = u64::from_str(s.next()?).ok()?; - let nice = u64::from_str(s.next()?).ok()?; - let system = u64::from_str(s.next()?).ok()?; - let idle = u64::from_str(s.next()?).ok()?; - let iowait = u64::from_str(s.next()?).ok()?; - let irq = u64::from_str(s.next()?).ok()?; - let softirq = u64::from_str(s.next()?).ok()?; - - Some(Self { - idle: idle + iowait, - non_idle: user + nice + system + irq + softirq, - }) - } - - fn utilization(&self, old: Self) -> f64 { - let elapsed = (self.idle + self.non_idle).saturating_sub(old.idle + old.non_idle); - if elapsed == 0 { - 0.0 - } else { - ((self.non_idle - old.non_idle) as f64 / elapsed as f64).clamp(0., 1.) - } - } -} - -async fn read_proc_stat() -> Result<(CpuTime, Vec)> { - let mut utilizations = Vec::with_capacity(32); - let mut total = None; - - let file = File::open("/proc/stat") - .await - .error("failed to read /proc/stat")?; - let mut file = BufReader::new(file); - - let mut line = String::new(); - while file - .read_line(&mut line) - .await - .error("failed to read /proc/stat")? - != 0 - { - // Total time - let data = line.trim_start_matches(|c: char| !c.is_ascii_whitespace()); - if line.starts_with("cpu ") { - total = Some(CpuTime::from_str(data).error("failed to parse /proc/stat")?); - } else if line.starts_with("cpu") { - utilizations.push(CpuTime::from_str(data).error("failed to parse /proc/stat")?); - } - line.clear(); - } - - Ok((total.error("failed to parse /proc/stat")?, utilizations)) -} - -/// Read the cpu turbo boost status from kernel sys interface -/// or intel pstate interface -async fn boost_status() -> Option { - if let Ok(boost) = read_file(CPU_BOOST_PATH).await { - Some(boost.starts_with('1')) - } else if let Ok(no_turbo) = read_file(CPU_NO_TURBO_PATH).await { - Some(no_turbo.starts_with('0')) - } else { - None - } -} diff --git a/src/blocks/custom.rs b/src/blocks/custom.rs deleted file mode 100644 index 2341270913..0000000000 --- a/src/blocks/custom.rs +++ /dev/null @@ -1,297 +0,0 @@ -//! The output of a custom shell command -//! -//! For further customisation, use the `json` option and have the shell command output valid JSON in the schema below: -//! ```json -//! {"icon": "...", "state": "...", "text": "...", "short_text": "..."} -//! ``` -//! `icon` is optional (default "") -//! `state` is optional, it may be Idle, Info, Good, Warning, Critical (default Idle) -//! `short_text` is optional. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | \"{ $icon\|} $text.pango-str() \" -//! `command` | Shell command to execute & display | `None` -//! `persistent` | Run command in the background; update display for each output line of the command | `false` -//! `cycle` | Commands to execute and change when the button is clicked | `None` -//! `interval` | Update interval in seconds (or "once" to update only once) | `10` -//! `json` | Use JSON from command output to format the block. If the JSON is not valid, the block will error out. | `false` -//! `watch_files` | Watch files to trigger update on file modification. Supports path expansions e.g. `~`. | `None` -//! `hide_when_empty` | Hides the block when the command output (or json text field) is empty | `false` -//! `shell` | Specify the shell to use when running commands | `$SHELL` if set, otherwise fallback to `sh` -//! -//! Placeholder | Value | Type | Unit -//! -----------------|------------------------------------------------------------|--------|--------------- -//! `icon` | Value of icon field from JSON output when it's non-empty | Icon | - -//! `text` | Output of the script or text field from JSON output | Text | -//! `short_text` | short_text field from JSON output | Text | -//! -//! Action | Default button -//! --------|--------------- -//! `cycle` | Left -//! -//! # Examples -//! -//! Display temperature, update every 10 seconds: -//! -//! ```toml -//! [[block]] -//! block = "custom" -//! command = ''' cat /sys/class/thermal/thermal_zone0/temp | awk '{printf("%.1f\n",$1/1000)}' ''' -//! ``` -//! -//! Cycle between "ON" and "OFF", update every 1 second, run next cycle command when block is clicked: -//! -//! ```toml -//! [[block]] -//! block = "custom" -//! cycle = ["echo ON", "echo OFF"] -//! interval = 1 -//! [[block.click]] -//! button = "left" -//! action = "cycle" -//! ``` -//! -//! Use JSON output: -//! -//! ```toml -//! [[block]] -//! block = "custom" -//! command = "echo '{\"icon\":\"weather_thunder\",\"state\":\"Critical\", \"text\": \"Danger!\"}'" -//! json = true -//! ``` -//! -//! Display kernel, update the block only once: -//! -//! ```toml -//! [[block]] -//! block = "custom" -//! command = "uname -r" -//! interval = "once" -//! ``` -//! -//! Display the screen brightness on an intel machine and update this only when `pkill -SIGRTMIN+4 i3status-rs` is called: -//! -//! ```toml -//! [[block]] -//! block = "custom" -//! command = ''' cat /sys/class/backlight/intel_backlight/brightness | awk '{print $1}' ''' -//! signal = 4 -//! interval = "once" -//! ``` -//! -//! Update block when one or more specified files are modified: -//! -//! ```toml -//! [[block]] -//! block = "custom" -//! command = "cat custom_status" -//! watch_files = ["custom_status"] -//! interval = "once" -//! ``` -//! -//! # TODO: -//! - Use `shellexpand` - -use crate::formatting::Format; - -use super::prelude::*; -use inotify::{Inotify, WatchMask}; -use std::process::Stdio; -use tokio::io::{self, BufReader}; -use tokio::process::Command; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - pub command: Option, - pub persistent: bool, - pub cycle: Option>, - #[default(10.into())] - pub interval: Seconds, - pub json: bool, - pub hide_when_empty: bool, - pub shell: Option, - pub watch_files: Vec, -} - -async fn update_bar( - stdout: &str, - hide_when_empty: bool, - json: bool, - api: &CommonApi, - format: Format, -) -> Result<()> { - let mut widget = Widget::new().with_format(format); - - let text_empty; - - if json { - match serde_json::from_str::(stdout).error("Invalid JSON") { - Ok(input) => { - text_empty = input.text.is_empty(); - widget.set_values(map! { - "text" => Value::text(input.text), - [if !input.icon.is_empty()] "icon" => Value::icon(input.icon), - [if let Some(t) = input.short_text] "short_text" => Value::text(t) - }); - widget.state = input.state; - } - Err(error) => return api.set_error(error), - } - } else { - text_empty = stdout.is_empty(); - widget.set_values(map!("text" => Value::text(stdout.into()))); - } - - if text_empty && hide_when_empty { - api.hide() - } else { - api.set_widget(widget) - } -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - api.set_default_actions(&[(MouseButton::Left, None, "cycle")])?; - - let format = config.format.with_defaults( - "{ $icon|} $text.pango-str() ", - "{ $icon|} $short_text.pango-str() |", - )?; - - let mut timer = config.interval.timer(); - - type FileStream = Pin> + Send + Sync>>; - let mut file_updates: FileStream = match config.watch_files.as_slice() { - [] => Box::pin(futures::stream::pending()), - files => { - let notify = Inotify::init().error("Failed to start inotify")?; - let mut watches = notify.watches(); - for file in files { - let file = file.expand()?; - watches - .add( - &*file, - WatchMask::MODIFY - | WatchMask::CLOSE_WRITE - | WatchMask::DELETE - | WatchMask::MOVE, - ) - .error("Failed to add file watch")?; - } - Box::pin( - notify - .into_event_stream([0; 1024]) - .error("Failed to create event stream")?, - ) - } - }; - - let shell = config - .shell - .clone() - .or_else(|| std::env::var("SHELL").ok()) - .unwrap_or_else(|| "sh".to_string()); - - if config.persistent { - let mut process = Command::new(&shell) - .args([ - "-c", - config - .command - .as_deref() - .error("'command' must be specified when 'persistent' is set")?, - ]) - .stdout(Stdio::piped()) - .stdin(Stdio::null()) - .kill_on_drop(true) - .spawn() - .error("failed to run command")?; - - let stdout = process - .stdout - .take() - .expect("child did not have a handle to stdout"); - let mut reader = BufReader::new(stdout).lines(); - - tokio::spawn(async move { - let _ = process.wait().await; - }); - - loop { - let line = reader - .next_line() - .await - .error("error reading line from child process")? - .error("child process exited unexpectedly")?; - update_bar( - &line, - config.hide_when_empty, - config.json, - api, - format.clone(), - ) - .await?; - } - } else { - let mut actions = api.get_actions()?; - - let mut cycle = config - .cycle - .clone() - .or_else(|| config.command.clone().map(|cmd| vec![cmd])) - .error("either 'command' or 'cycle' must be specified")? - .into_iter() - .cycle(); - let mut cmd = cycle.next().unwrap(); - - loop { - // Run command - let output = Command::new(&shell) - .args(["-c", &cmd]) - .stdin(Stdio::null()) - .output() - .await - .error("failed to run command")?; - let stdout = std::str::from_utf8(&output.stdout) - .error("the output of command is invalid UTF-8")? - .trim(); - - update_bar( - stdout, - config.hide_when_empty, - config.json, - api, - format.clone(), - ) - .await?; - - loop { - select! { - _ = timer.tick() => break, - _ = file_updates.next() => break, - _ = api.wait_for_update_request() => break, - Some(action) = actions.recv() => match action.as_ref() { - "cycle" => { - cmd = cycle.next().unwrap(); - break; - } - _ => (), - } - } - } - } - } -} - -#[derive(Deserialize, Debug, Default)] -#[serde(default)] -struct Input { - icon: String, - state: State, - text: String, - short_text: Option, -} diff --git a/src/blocks/custom_dbus.rs b/src/blocks/custom_dbus.rs deleted file mode 100644 index 8b4d777f2b..0000000000 --- a/src/blocks/custom_dbus.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! A block controlled by the DBus -//! -//! This block creates a new DBus object in `rs.i3status` service. This object implements -//! `rs.i3status.custom` interface which allows you to set block's icon, text and state. -//! -//! Output of `busctl --user introspect rs.i3status / rs.i3status.custom`: -//! ```text -//! NAME TYPE SIGNATURE RESULT/VALUE FLAGS -//! rs.i3status.custom interface - - - -//! .SetIcon method s s - -//! .SetState method s s - -//! .SetText method ss s - -//! ``` -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. | \"{ $icon\|}{ $text.pango-str()\|} \" -//! -//! Placeholder | Value | Type | Unit -//! -------------|-------------------------------------------------------------------|--------|--------------- -//! `icon` | Value of icon set via `SetIcon` if the value is non-empty string. | Icon | - -//! `text` | Value of the first string from SetText | Text | - -//! `short_text` | Value of the second string from SetText | Text | - -//! -//! # Example -//! -//! Config: -//! ```toml -//! [[block]] -//! block = "custom_dbus" -//! path = "/my_path" -//! ``` -//! -//! Usage: -//! ```sh -//! # set full text to 'hello' and short text to 'hi' -//! busctl --user call rs.i3status /my_path rs.i3status.custom SetText ss hello hi -//! # set icon to 'music' -//! busctl --user call rs.i3status /my_path rs.i3status.custom SetIcon s music -//! # set state to 'good' -//! busctl --user call rs.i3status /my_path rs.i3status.custom SetState s good -//! ``` -//! -//! Because it's impossible to publish objects to the same name from different -//! processes, having multiple dbus blocks in different bars won't work. As a workaround, -//! you can set the env var `I3RS_DBUS_NAME` to set the interface a bar works on to -//! differentiate between different processes. For example, setting this to 'top', will allow you -//! to use `rs.i3status.top`. -//! -//! # TODO -//! - Send a signal on click? - -use super::prelude::*; -use std::env; -use zbus::fdo; - -// Share DBus connection between multiple block instances -static DBUS_CONNECTION: tokio::sync::OnceCell> = - tokio::sync::OnceCell::const_new(); - -const DBUS_NAME: &str = "rs.i3status"; - -#[derive(Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct Config { - #[serde(default)] - pub format: FormatConfig, - pub path: String, -} - -struct Block { - widget: Widget, - api: CommonApi, - icon: Option, - text: Option, - short_text: Option, -} - -fn block_values(block: &Block) -> HashMap, Value> { - map! { - [if let Some(icon) = &block.icon] "icon" => Value::icon(icon.to_string()), - [if let Some(text) = &block.text] "text" => Value::text(text.to_string()), - [if let Some(short_text) = &block.short_text] "short_text" => Value::text(short_text.to_string()), - } -} - -#[zbus::interface(name = "rs.i3status.custom")] -impl Block { - async fn set_icon(&mut self, icon: &str) -> fdo::Result<()> { - self.icon = if icon.is_empty() { - None - } else { - Some(icon.to_string()) - }; - self.widget.set_values(block_values(self)); - self.api.set_widget(self.widget.clone())?; - Ok(()) - } - - async fn set_text(&mut self, full: String, short: String) -> fdo::Result<()> { - self.text = Some(full); - self.short_text = Some(short); - self.widget.set_values(block_values(self)); - self.api.set_widget(self.widget.clone())?; - Ok(()) - } - - async fn set_state(&mut self, state: &str) -> fdo::Result<()> { - self.widget.state = match state { - "idle" => State::Idle, - "info" => State::Info, - "good" => State::Good, - "warning" => State::Warning, - "critical" => State::Critical, - _ => return Err(Error::new(format!("'{state}' is not a valid state")).into()), - }; - self.api.set_widget(self.widget.clone())?; - Ok(()) - } -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let widget = Widget::new().with_format(config.format.with_defaults( - "{ $icon|}{ $text.pango-str()|} ", - "{ $icon|} $short_text.pango-str() | ", - )?); - - let dbus_conn = DBUS_CONNECTION - .get_or_init(dbus_conn) - .await - .as_ref() - .map_err(Clone::clone)?; - dbus_conn - .object_server() - .at( - config.path.clone(), - Block { - widget, - api: api.clone(), - icon: None, - text: None, - short_text: None, - }, - ) - .await - .error("Failed to setup DBus server")?; - Ok(()) -} - -async fn dbus_conn() -> Result { - let dbus_interface_name = match env::var("I3RS_DBUS_NAME") { - Ok(v) => format!("{DBUS_NAME}.{v}"), - Err(_) => DBUS_NAME.to_string(), - }; - - let conn = new_dbus_connection().await?; - conn.request_name(dbus_interface_name) - .await - .error("Failed to request DBus name")?; - Ok(conn) -} diff --git a/src/blocks/disk_iostats.rs b/src/blocks/disk_iostats.rs deleted file mode 100644 index 56a9db6ac7..0000000000 --- a/src/blocks/disk_iostats.rs +++ /dev/null @@ -1,177 +0,0 @@ -//! Disk I/O statistics -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `device` | Block device name to monitor (as specified in `/dev/`) | If not set, device will be automatically selected every `interval` -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $speed_read.eng(prefix:K) $speed_write.eng(prefix:K) "` -//! `interval` | Update interval in seconds | `2` -//! `missing_format` | Same as `format` but for when the device is missing | `" × "` -//! -//! Placeholder | Value | Type | Unit -//! ------------|-------|--------|------- -//! `icon` | A static icon | Icon | - -//! `device` | The name of device | Text | - -//! `speed_read` | Read speed | Number | Bytes per second -//! `speed_write` | Write speed | Number | Bytes per second -//! -//! # Examples -//! -//! ```toml -//! [[block]] -//! block = "disk_iostats" -//! device = "sda" -//! format = " $icon $speed_write.eng(prefix:K) " -//! ``` -//! -//! # Icons Used -//! -//! - `disk_drive` - -use super::prelude::*; -use crate::util::read_file; -use libc::c_ulong; -use std::ops; -use std::path::Path; -use std::time::Instant; -use tokio::fs::read_dir; - -/// Path for block devices -const BLOCK_DEVICES_PATH: &str = "/sys/class/block"; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub device: Option, - #[default(2.into())] - pub interval: Seconds, - pub format: FormatConfig, - pub missing_format: FormatConfig, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config - .format - .with_default(" $icon $speed_read.eng(prefix:K) $speed_write.eng(prefix:K) ")?; - let missing_format = config.missing_format.with_default(" × ")?; - - let mut timer = config.interval.timer(); - let mut old_stats = None; - let mut stats_timer = Instant::now(); - - loop { - let mut device = config.device.clone(); - if device.is_none() { - device = find_device().await?; - } - match device { - None => { - api.set_widget(Widget::new().with_format(missing_format.clone()))?; - } - Some(device) => { - let mut widget = Widget::new(); - - widget.set_format(format.clone()); - - let new_stats = read_stats(&device).await?; - let sector_size = read_sector_size(&device).await?; - - let mut speed_read = 0.0; - let mut speed_write = 0.0; - if let Some(old_stats) = old_stats { - let diff = new_stats - old_stats; - let elapsed = stats_timer.elapsed().as_secs_f64(); - stats_timer = Instant::now(); - let size_read = diff.sectors_read as u64 * sector_size; - let size_written = diff.sectors_written as u64 * sector_size; - speed_read = size_read as f64 / elapsed; - speed_write = size_written as f64 / elapsed; - }; - old_stats = Some(new_stats); - - widget.set_values(map! { - "icon" => Value::icon("disk_drive"), - "speed_read" => Value::bytes(speed_read), - "speed_write" => Value::bytes(speed_write), - "device" => Value::text(device), - }); - - api.set_widget(widget)?; - } - } - - select! { - _ = timer.tick() => continue, - _ = api.wait_for_update_request() => continue, - } - } -} - -async fn find_device() -> Result> { - let mut sysfs_dir = read_dir(BLOCK_DEVICES_PATH) - .await - .error("Failed to open /sys/class/block directory")?; - while let Some(dir) = sysfs_dir - .next_entry() - .await - .error("Failed to read /sys/class/block directory")? - { - let path = dir.path(); - if path.join("device").exists() { - return Ok(Some( - dir.file_name() - .into_string() - .map_err(|_| Error::new("Invalid device filename"))?, - )); - } - } - - Ok(None) -} - -#[derive(Debug, Default, Clone, Copy)] -struct Stats { - sectors_read: c_ulong, - sectors_written: c_ulong, -} - -impl ops::Sub for Stats { - type Output = Self; - - fn sub(mut self, rhs: Self) -> Self::Output { - self.sectors_read = self.sectors_read.wrapping_sub(rhs.sectors_read); - self.sectors_written = self.sectors_written.wrapping_sub(rhs.sectors_written); - self - } -} - -async fn read_stats(device: &str) -> Result { - let raw = read_file(Path::new(BLOCK_DEVICES_PATH).join(device).join("stat")) - .await - .error("Failed to read stat file")?; - let fields: Vec<&str> = raw.split_whitespace().collect(); - Ok(Stats { - sectors_read: fields - .get(2) - .error("Missing sectors read field")? - .parse() - .error("Failed to parse sectors read")?, - sectors_written: fields - .get(6) - .error("Missing sectors written field")? - .parse() - .error("Failed to parse sectors written")?, - }) -} - -async fn read_sector_size(device: &str) -> Result { - let raw = read_file( - Path::new(BLOCK_DEVICES_PATH) - .join(device) - .join("queue/hw_sector_size"), - ) - .await - .error("Failed to read HW sector size")?; - raw.parse::().error("Failed to parse HW sector size") -} diff --git a/src/blocks/disk_space.rs b/src/blocks/disk_space.rs deleted file mode 100644 index 79bfebd27b..0000000000 --- a/src/blocks/disk_space.rs +++ /dev/null @@ -1,207 +0,0 @@ -//! Disk usage statistics -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `path` | Path to collect information from. Supports path expansions e.g. `~`. | `"/"` -//! `interval` | Update time in seconds | `20` -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $available "` -//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None` -//! `warning` | A value which will trigger warning block state | `20.0` -//! `alert` | A value which will trigger critical block state | `10.0` -//! `info_type` | Determines which information will affect the block state. Possible values are `"available"`, `"free"` and `"used"` | `"available"` -//! `alert_unit` | The unit of `alert` and `warning` options. If not set, percents are used. Possible values are `"B"`, `"KB"`, `"KiB"`, `"MB"`, `"MiB"`, `"GB"`, `"Gib"`, `"TB"` and `"TiB"` | `None` -//! -//! Placeholder | Value | Type | Unit -//! -------------|--------------------------------------------------------------------|--------|------- -//! `icon` | A static icon | Icon | - -//! `path` | The value of `path` option | Text | - -//! `percentage` | Free or used percentage. Depends on `info_type` | Number | % -//! `total` | Total disk space | Number | Bytes -//! `used` | Used disk space | Number | Bytes -//! `free` | Free disk space | Number | Bytes -//! `available` | Available disk space (free disk space minus reserved system space) | Number | Bytes -//! -//! Action | Description | Default button -//! ----------------|-------------------------------------------|--------------- -//! `toggle_format` | Toggles between `format` and `format_alt` | Left -//! -//! # Examples -//! -//! ```toml -//! [[block]] -//! block = "disk_space" -//! info_type = "available" -//! alert_unit = "GB" -//! alert = 10.0 -//! warning = 15.0 -//! format = " $icon $available " -//! format_alt = " $icon $available / $total " -//! ``` -//! -//! Update block on right click: -//! -//! ```toml -//! [[block]] -//! block = "disk_space" -//! [[block.click]] -//! button = "right" -//! update = true -//! ``` -//! -//! Show the block only if less than 10GB is available: -//! -//! ```toml -//! [[block]] -//! block = "disk_space" -//! format = " $free.eng(range:..10e9) |" -//! ``` -//! -//! # Icons Used -//! - `disk_drive` - -// make_log_macro!(debug, "disk_space"); - -use super::prelude::*; -use crate::formatting::prefix::Prefix; -use nix::sys::statvfs::statvfs; - -#[derive(Copy, Clone, Debug, Deserialize, SmartDefault)] -#[serde(rename_all = "lowercase")] -pub enum InfoType { - #[default] - Available, - Free, - Used, -} - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - #[default("/".into())] - pub path: ShellString, - pub info_type: InfoType, - pub format: FormatConfig, - pub format_alt: Option, - pub alert_unit: Option, - #[default(20.into())] - pub interval: Seconds, - #[default(20.0)] - pub warning: f64, - #[default(10.0)] - pub alert: f64, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?; - - let mut format = config.format.with_default(" $icon $available ")?; - let mut format_alt = match &config.format_alt { - Some(f) => Some(f.with_default("")?), - None => None, - }; - - let unit = match config.alert_unit.as_deref() { - // Decimal - Some("TB") => Some(Prefix::Tera), - Some("GB") => Some(Prefix::Giga), - Some("MB") => Some(Prefix::Mega), - Some("KB") => Some(Prefix::Kilo), - // Binary - Some("TiB") => Some(Prefix::Tebi), - Some("GiB") => Some(Prefix::Gibi), - Some("MiB") => Some(Prefix::Mebi), - Some("KiB") => Some(Prefix::Kibi), - // Byte - Some("B") => Some(Prefix::One), - // Unknown - Some(x) => return Err(Error::new(format!("Unknown unit: '{x}'"))), - None => None, - }; - - let path = config.path.expand()?; - - let mut timer = config.interval.timer(); - - loop { - let mut widget = Widget::new().with_format(format.clone()); - - let statvfs = statvfs(&*path).error("failed to retrieve statvfs")?; - - // Casting to be compatible with 32-bit systems - #[allow(clippy::unnecessary_cast)] - let (total, used, available, free) = { - let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64); - let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64)) - * (statvfs.fragment_size() as u64); - let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64); - let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64); - (total, used, available, free) - }; - - let result = match config.info_type { - InfoType::Available => available, - InfoType::Free => free, - InfoType::Used => used, - } as f64; - - let percentage = result / (total as f64) * 100.; - widget.set_values(map! { - "icon" => Value::icon("disk_drive"), - "path" => Value::text(path.to_string()), - "percentage" => Value::percents(percentage), - "total" => Value::bytes(total as f64), - "used" => Value::bytes(used as f64), - "available" => Value::bytes(available as f64), - "free" => Value::bytes(free as f64), - }); - - // Send percentage to alert check if we don't want absolute alerts - let alert_val_in_config_units = match unit { - Some(p) => p.apply(result), - None => percentage, - }; - - // Compute state - widget.state = match config.info_type { - InfoType::Used => { - if alert_val_in_config_units >= config.alert { - State::Critical - } else if alert_val_in_config_units >= config.warning { - State::Warning - } else { - State::Idle - } - } - InfoType::Free | InfoType::Available => { - if alert_val_in_config_units <= config.alert { - State::Critical - } else if alert_val_in_config_units <= config.warning { - State::Warning - } else { - State::Idle - } - } - }; - - api.set_widget(widget)?; - - loop { - select! { - _ = timer.tick() => break, - _ = api.wait_for_update_request() => break, - Some(action) = actions.recv() => match action.as_ref() { - "toggle_format" => { - if let Some(format_alt) = &mut format_alt { - std::mem::swap(format_alt, &mut format); - break; - } - } - _ => (), - } - } - } - } -} diff --git a/src/blocks/docker.rs b/src/blocks/docker.rs deleted file mode 100644 index 10c28a024b..0000000000 --- a/src/blocks/docker.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Local docker daemon status -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `interval` | Update interval, in seconds. | `5` -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $running.eng(w:1) "` -//! `socket_path` | The path to the docker socket. Supports path expansions e.g. `~`. | `"/var/run/docker.sock"` -//! -//! Key | Value | Type | Unit -//! ----------|--------------------------------|--------|----- -//! `icon` | A static icon | Icon | - -//! `total` | Total containers on the host | Number | - -//! `running` | Containers running on the host | Number | - -//! `stopped` | Containers stopped on the host | Number | - -//! `paused` | Containers paused on the host | Number | - -//! `images` | Total images on the host | Number | - -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "docker" -//! interval = 2 -//! format = " $icon $running/$total " -//! ``` -//! -//! # Icons Used -//! -//! - `docker` - -use super::prelude::*; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - #[default(5.into())] - pub interval: Seconds, - pub format: FormatConfig, - #[default("/var/run/docker.sock".into())] - pub socket_path: ShellString, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config.format.with_default(" $icon $running.eng(w:1) ")?; - let socket_path = config.socket_path.expand()?; - - let client = reqwest::Client::builder() - .unix_socket(&*socket_path) - .build() - .unwrap(); - - loop { - let status: Status = client - .get("http://api/info") - .send() - .await - .error("Failed to get response")? - .json() - .await - .error("Failed to deserialize JSON")?; - - let mut widget = Widget::new().with_format(format.clone()); - widget.set_values(map! { - "icon" => Value::icon("docker"), - "total" => Value::number(status.total), - "running" => Value::number(status.running), - "paused" => Value::number(status.paused), - "stopped" => Value::number(status.stopped), - "images" => Value::number(status.images), - }); - api.set_widget(widget)?; - - select! { - _ = sleep(config.interval.0) => (), - _ = api.wait_for_update_request() => (), - } - } -} - -#[derive(Deserialize, Debug)] -struct Status { - #[serde(rename = "Containers")] - total: i64, - #[serde(rename = "ContainersRunning")] - running: i64, - #[serde(rename = "ContainersStopped")] - stopped: i64, - #[serde(rename = "ContainersPaused")] - paused: i64, - #[serde(rename = "Images")] - images: i64, -} diff --git a/src/blocks/external_ip.rs b/src/blocks/external_ip.rs deleted file mode 100644 index f30c7957e1..0000000000 --- a/src/blocks/external_ip.rs +++ /dev/null @@ -1,219 +0,0 @@ -//! External IP address and various information about it -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $ip $country_flag "` -//! `interval` | Interval in seconds for automatic updates | `300` -//! `with_network_manager` | If 'true', listen for NetworkManager events and update the IP immediately if there was a change | `true` -//! `use_ipv4` | If 'true', use IPv4 for obtaining all info | `false` -//! -//! Key | Value | Type | Unit -//! -----|-------|------|------ -//! `ip` | The external IP address, as seen from a remote server | Text | - -//! `version` | IPv4 or IPv6 | Text | - -//! `city` | City name, such as "San Francisco" | Text | - -//! `region` | Region name, such as "California" | Text | - -//! `region_code` | Region code, such as "CA" for California | Text | - -//! `country` | Country code (2 letter, ISO 3166-1 alpha-2) | Text | - -//! `country_name` | Short country name | Text | - -//! `country_code` | Country code (2 letter, ISO 3166-1 alpha-2) | Text | - -//! `country_code_iso3` | Country code (3 letter, ISO 3166-1 alpha-3) | Text | - -//! `country_capital` | Capital of the country | Text | - -//! `country_tld` | Country specific TLD (top-level domain) | Text | - -//! `continent_code` | Continent code | Text | - -//! `in_eu` | Region code, such as "CA" | Flag | - -//! `postal` | ZIP / Postal code | Text | - -//! `latitude` | Latitude | Number | - (TODO: make degrees?) -//! `longitude` | Longitude | Number | - (TODO: make degrees?) -//! `timezone` | City | Text | - -//! `utc_offset` | UTC offset (with daylight saving time) as +HHMM or -HHMM (HH is hours, MM is minutes) | Text | - -//! `country_calling_code` | Country calling code (dial in code, comma separated) | Text | - -//! `currency` | Currency code (ISO 4217) | Text | - -//! `currency_name` | Currency name | Text | - -//! `languages` | Languages spoken (comma separated 2 or 3 letter ISO 639 code with optional hyphen separated country suffix) | Text | - -//! `country_area` | Area of the country (in sq km) | Number | - -//! `country_population` | Population of the country | Number | - -//! `timezone` | Time zone | Text | - -//! `org` | Organization | Text | - -//! `asn` | Autonomous system (AS) | Text | - -//! `country_flag` | Flag of the country | Text (glyph) | - -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "external_ip" -//! format = " $ip $country_code " -//! ``` -//! -//! # Notes -//! All the information comes from -//! Check their documentation here: -//! -//! The IP is queried, 1) When i3status-rs starts, 2) When a signal is received -//! on D-Bus about a network configuration change, 3) Every 5 minutes. This -//! periodic refresh exists to catch IP updates that don't trigger a notification, -//! for example due to a IP refresh at the router. -//! -//! Flags: They are not icons but unicode glyphs. You will need a font that -//! includes them. Tested with: - -use zbus::MatchRule; - -use super::prelude::*; -use crate::util::{country_flag_from_iso_code, new_system_dbus_connection}; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - #[default(300.into())] - pub interval: Seconds, - #[default(true)] - pub with_network_manager: bool, - #[default(false)] - pub use_ipv4: bool, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config.format.with_default(" $ip $country_flag ")?; - - type UpdatesStream = Pin>>; - let mut stream: UpdatesStream = if config.with_network_manager { - let dbus = new_system_dbus_connection().await?; - let proxy = zbus::fdo::DBusProxy::new(&dbus) - .await - .error("Failed to create DBusProxy")?; - proxy - .add_match_rule( - MatchRule::builder() - .msg_type(zbus::message::Type::Signal) - .path("/org/freedesktop/NetworkManager") - .and_then(|x| x.interface("org.freedesktop.DBus.Properties")) - .and_then(|x| x.member("PropertiesChanged")) - .unwrap() - .build(), - ) - .await - .error("Failed to add match")?; - proxy - .add_match_rule( - MatchRule::builder() - .msg_type(zbus::message::Type::Signal) - .path_namespace("/org/freedesktop/NetworkManager/ActiveConnection") - .and_then(|x| x.interface("org.freedesktop.DBus.Properties")) - .and_then(|x| x.member("PropertiesChanged")) - .unwrap() - .build(), - ) - .await - .error("Failed to add match")?; - proxy - .add_match_rule( - MatchRule::builder() - .msg_type(zbus::message::Type::Signal) - .path_namespace("/org/freedesktop/NetworkManager/IP4Config") - .and_then(|x| x.interface("org.freedesktop.DBus.Properties")) - .and_then(|x| x.member("PropertiesChanged")) - .unwrap() - .build(), - ) - .await - .error("Failed to add match")?; - let stream: zbus::MessageStream = dbus.into(); - Box::pin(stream.map(|_| ())) - } else { - Box::pin(futures::stream::empty()) - }; - - let client = if config.use_ipv4 { - &REQWEST_CLIENT_IPV4 - } else { - &REQWEST_CLIENT - }; - - loop { - let fetch_info = || api.find_ip_location(client, Duration::from_secs(0)); - let info = fetch_info.retry(ExponentialBuilder::default()).await?; - - let mut values = map! { - "ip" => Value::text(info.ip), - "city" => Value::text(info.city), - "latitude" => Value::number(info.latitude), - "longitude" => Value::number(info.longitude), - }; - - macro_rules! map_push_if_some { ($($key:ident: $type:ident),* $(,)?) => { - $({ - let key = stringify!($key); - if let Some(value) = info.$key { - values.insert(key.into(), Value::$type(value)); - } else if format.contains_key(key) { - return Err(Error::new(format!( - "The format string contains '{key}', but the {key} field is not provided by {} (an api key may be required)", - api.locator_name() - ))); - } - })* - } } - - map_push_if_some!( - version: text, - region: text, - region_code: text, - country: text, - country_name: text, - country_code_iso3: text, - country_capital: text, - country_tld: text, - continent_code: text, - postal: text, - timezone: text, - utc_offset: text, - country_calling_code: text, - currency: text, - currency_name: text, - languages: text, - country_area: number, - country_population: number, - asn: text, - org: text, - ); - - if let Some(country_code) = info.country_code { - values.insert( - "country_flag".into(), - Value::text(country_flag_from_iso_code(&country_code)), - ); - values.insert("country_code".into(), Value::text(country_code)); - } else if format.contains_key("country_code") || format.contains_key("country_flag") { - return Err(Error::new(format!( - "The format string contains 'country_code' or 'country_flag', but the country_code field is not provided by {}", - api.locator_name() - ))); - } - - if let Some(in_eu) = info.in_eu { - if in_eu { - values.insert("in_eu".into(), Value::flag()); - } - } else if format.contains_key("in_eu") { - return Err(Error::new(format!( - "The format string contains 'in_eu', but the in_eu field is not provided by {}", - api.locator_name() - ))); - } - - let mut widget = Widget::new().with_format(format.clone()); - widget.set_values(values); - api.set_widget(widget)?; - - select! { - _ = sleep(config.interval.0) => (), - _ = api.wait_for_update_request() => (), - _ = stream.next_debounced() => () - } - } -} diff --git a/src/blocks/focused_window.rs b/src/blocks/focused_window.rs deleted file mode 100644 index 5ed6e92ebe..0000000000 --- a/src/blocks/focused_window.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Currently focused window -//! -//! This block displays the title and/or the active marks (when used with `sway`/`i3`) of the currently -//! focused window. Supported WMs are: `sway`, `i3` and most wlroots-based compositors. See `driver` -//! option for more info. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | \" $title.str(max_w:21) \|\" -//! `driver` | Which driver to use. Available values: `sway_ipc` - for `i3` and `sway`, `wlr_toplevel_management` - for Wayland compositors that implement [wlr-foreign-toplevel-management-unstable-v1](https://gitlab.freedesktop.org/wlroots/wlr-protocols/-/blob/master/unstable/wlr-foreign-toplevel-management-unstable-v1.xml), `auto` - try to automatically guess which driver to use. | `"auto"` -//! -//! Placeholder | Value | Type | Unit -//! ----------------|-----------------------------------------------------------------------|------|----- -//! `title` | Window's title (may be absent) | Text | - -//! `marks` | Window's marks (present only with sway/i3) | Text | - -//! `visible_marks` | Window's marks that do not start with `_` (present only with sway/i3) | Text | - -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "focused_window" -//! [block.format] -//! full = " $title.str(max_w:15) |" -//! short = " $title.str(max_w:10) |" -//! ``` -//! -//! This example instead of hiding block when the window's title is empty displays "Missing" -//! -//! ```toml -//! [[block]] -//! block = "focused_window" -//! format = " $title.str(0,21) | Missing " -//! ``` - -mod sway_ipc; -mod wlr_toplevel_management; - -use sway_ipc::SwayIpc; -use wlr_toplevel_management::WlrToplevelManagement; - -use super::prelude::*; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - pub driver: Driver, -} - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(rename_all = "snake_case")] -pub enum Driver { - #[default] - Auto, - SwayIpc, - WlrToplevelManagement, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config.format.with_default(" $title.str(max_w:21) |")?; - - let mut backend: Box = match config.driver { - Driver::Auto => match SwayIpc::new().await { - Ok(swayipc) => Box::new(swayipc), - Err(_) => Box::new(WlrToplevelManagement::new().await?), - }, - Driver::SwayIpc => Box::new(SwayIpc::new().await?), - Driver::WlrToplevelManagement => Box::new(WlrToplevelManagement::new().await?), - }; - - loop { - let Info { title, marks } = backend.get_info().await?; - - let mut widget = Widget::new().with_format(format.clone()); - - if !title.is_empty() { - let join_marks = |mut s: String, m: &String| { - let _ = write!(s, "[{m}]"); // writing to String never fails - s - }; - - let marks_str = marks.iter().fold(String::new(), join_marks); - let visible_marks_str = marks - .iter() - .filter(|m| !m.starts_with('_')) - .fold(String::new(), join_marks); - - widget.set_values(map! { - "title" => Value::text(title), - "marks" => Value::text(marks_str), - "visible_marks" => Value::text(visible_marks_str), - }); - } - - api.set_widget(widget)?; - } -} - -#[async_trait] -trait Backend { - async fn get_info(&mut self) -> Result; -} - -#[derive(Clone, Default)] -struct Info { - title: String, - marks: Vec, -} diff --git a/src/blocks/focused_window/sway_ipc.rs b/src/blocks/focused_window/sway_ipc.rs deleted file mode 100644 index b7fa4177be..0000000000 --- a/src/blocks/focused_window/sway_ipc.rs +++ /dev/null @@ -1,72 +0,0 @@ -use super::{Backend, Info}; -use crate::blocks::prelude::*; -use swayipc_async::{Connection, Event, EventStream, EventType, WindowChange, WorkspaceChange}; - -pub(super) struct SwayIpc { - events: EventStream, - info: Info, -} - -impl SwayIpc { - pub(super) async fn new() -> Result { - Ok(Self { - events: Connection::new() - .await - .error("failed to open connection with swayipc")? - .subscribe(&[EventType::Window, EventType::Workspace]) - .await - .error("could not subscribe to window events")?, - info: default(), - }) - } -} - -#[async_trait] -impl Backend for SwayIpc { - async fn get_info(&mut self) -> Result { - loop { - let event = self - .events - .next() - .await - .error("swayipc channel closed")? - .error("bad event")?; - match event { - Event::Window(e) => match e.change { - WindowChange::Mark => { - self.info.marks = e.container.marks; - } - WindowChange::Focus => { - self.info.title.clear(); - if let Some(new_title) = &e.container.name { - self.info.title.push_str(new_title); - } - self.info.marks = e.container.marks; - } - WindowChange::Title => { - if e.container.focused { - self.info.title.clear(); - if let Some(new_title) = &e.container.name { - self.info.title.push_str(new_title); - } - } else { - continue; - } - } - WindowChange::Close => { - self.info.title.clear(); - self.info.marks.clear(); - } - _ => continue, - }, - Event::Workspace(e) if e.change == WorkspaceChange::Init => { - self.info.title.clear(); - self.info.marks.clear(); - } - _ => continue, - } - - return Ok(self.info.clone()); - } - } -} diff --git a/src/blocks/focused_window/wlr_toplevel_management.rs b/src/blocks/focused_window/wlr_toplevel_management.rs deleted file mode 100644 index 6d46789f89..0000000000 --- a/src/blocks/focused_window/wlr_toplevel_management.rs +++ /dev/null @@ -1,119 +0,0 @@ -use super::{Backend, Info}; -use crate::blocks::prelude::*; - -use wayrs_client::{Connection, EventCtx}; -use wayrs_protocols::wlr_foreign_toplevel_management_unstable_v1::*; - -pub(super) struct WlrToplevelManagement { - conn: Connection, - state: State, -} - -#[derive(Default)] -struct State { - error: Option, - new_title: Option, - toplevels: HashMap, - active_toplevel: Option, -} - -#[derive(Default)] -struct Toplevel { - title: Option, - is_active: bool, -} - -impl WlrToplevelManagement { - pub(super) async fn new() -> Result { - let mut conn = Connection::connect().error("failed to connect to wayland")?; - - conn.async_roundtrip() - .await - .error("wayland roundrip failed")?; - - let _: ZwlrForeignToplevelManagerV1 = conn - .bind_singleton_with_cb(1..=3, toplevel_manager_cb) - .error("unsupported compositor")?; - - Ok(Self { - conn, - state: default(), - }) - } -} - -#[async_trait] -impl Backend for WlrToplevelManagement { - async fn get_info(&mut self) -> Result { - loop { - self.conn.async_flush().await.error("wayland error")?; - self.conn.async_recv_events().await.error("wayland error")?; - self.conn.dispatch_events(&mut self.state); - - if let Some(err) = self.state.error.take() { - return Err(err); - } - - if let Some(title) = self.state.new_title.take() { - return Ok(Info { - title, - marks: default(), - }); - } - } - } -} - -fn toplevel_manager_cb(ctx: EventCtx) { - use zwlr_foreign_toplevel_manager_v1::Event; - match ctx.event { - Event::Toplevel(toplevel) => { - ctx.state.toplevels.insert(toplevel, default()); - ctx.conn.set_callback_for(toplevel, toplevel_cb); - } - Event::Finished => { - ctx.state.error = Some(Error::new("unexpected 'finished' event")); - ctx.conn.break_dispatch_loop(); - } - _ => (), - } -} - -fn toplevel_cb(ctx: EventCtx) { - use zwlr_foreign_toplevel_handle_v1::Event; - - let Some(toplevel) = ctx.state.toplevels.get_mut(&ctx.proxy) else { - return; - }; - - match ctx.event { - Event::Title(title) => { - toplevel.title = Some(String::from_utf8_lossy(title.as_bytes()).into()); - } - Event::State(state) => { - toplevel.is_active = state - .chunks_exact(4) - .map(|b| u32::from_ne_bytes(b.try_into().unwrap())) - .any(|s| s == zwlr_foreign_toplevel_handle_v1::State::Activated as u32); - } - Event::Closed => { - if ctx.state.active_toplevel == Some(ctx.proxy) { - ctx.state.active_toplevel = None; - ctx.state.new_title = Some(default()); - } - - ctx.proxy.destroy(ctx.conn); - ctx.state.toplevels.remove(&ctx.proxy); - } - Event::Done => { - if toplevel.is_active { - ctx.state.active_toplevel = Some(ctx.proxy); - ctx.state.new_title = Some(toplevel.title.clone().unwrap_or_default()); - } else if ctx.state.active_toplevel == Some(ctx.proxy) { - ctx.state.active_toplevel = None; - ctx.state.new_title = Some(default()); - } - } - _ => (), - } -} diff --git a/src/blocks/github.rs b/src/blocks/github.rs deleted file mode 100644 index 82445ac90c..0000000000 --- a/src/blocks/github.rs +++ /dev/null @@ -1,190 +0,0 @@ -//! The number of GitHub notifications -//! -//! This block shows the unread notification count for a GitHub account. A GitHub [personal access token](https://github.com/settings/tokens/new) with the "notifications" scope is required, and must be passed using the `I3RS_GITHUB_TOKEN` environment variable or `token` configuration option. Optionally the colour of the block is determined by the highest notification in the following lists from highest to lowest: `critical`,`warning`,`info`,`good` -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $total.eng(w:1) "` -//! `interval` | Update interval in seconds | `30` -//! `token` | A GitHub personal access token with the "notifications" scope | `None` -//! `hide_if_total_is_zero` | Hide this block if the total count of notifications is zero | `false` -//! `critical` | List of notification types that change the block to the critical colour | `None` -//! `warning` | List of notification types that change the block to the warning colour | `None` -//! `info` | List of notification types that change the block to the info colour | `None` -//! `good` | List of notification types that change the block to the good colour | `None` -//! -//! -//! All the placeholders are numbers without a unit. -//! -//! Placeholder | Value -//! -------------------|------ -//! `icon` | A static icon -//! `total` | The total number of notifications -//! `assign` | You were assigned to the issue -//! `author` | You created the thread -//! `comment` | You commented on the thread -//! `ci_activity` | A GitHub Actions workflow run that you triggered was completed -//! `invitation` | You accepted an invitation to contribute to the repository -//! `manual` | You subscribed to the thread (via an issue or pull request) -//! `mention` | You were specifically @mentioned in the content -//! `review_requested` | You, or a team you're a member of, were requested to review a pull request -//! `security_alert` | GitHub discovered a security vulnerability in your repository -//! `state_change` | You changed the thread state (for example, closing an issue or merging a pull request) -//! `subscribed` | You're watching the repository -//! `team_mention` | You were on a team that was mentioned -//! -//! # Examples -//! -//! ```toml -//! [[block]] -//! block = "github" -//! format = " $icon $total.eng(w:1)|$mention.eng(w:1) " -//! interval = 60 -//! token = "..." -//! ``` -//! -//! ```toml -//! [[block]] -//! block = "github" -//! token = "..." -//! format = " $icon $total.eng(w:1) " -//! info = ["total"] -//! warning = ["mention","review_requested"] -//! hide_if_total_is_zero = true -//! ``` -//! -//! # Icons Used -//! - `github` - -use super::prelude::*; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - #[default(60.into())] - pub interval: Seconds, - pub format: FormatConfig, - pub token: Option, - pub hide_if_total_is_zero: bool, - pub good: Option>, - pub info: Option>, - pub warning: Option>, - pub critical: Option>, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config.format.with_default(" $icon $total.eng(w:1) ")?; - - let mut interval = config.interval.timer(); - let token = config - .token - .clone() - .or_else(|| std::env::var("I3RS_GITHUB_TOKEN").ok()) - .error("Github token not found")?; - - loop { - let stats = get_stats(&token).await?; - - if stats.get("total").is_some_and(|x| *x > 0) || !config.hide_if_total_is_zero { - let mut widget = Widget::new().with_format(format.clone()); - - 'outer: for (list_opt, ret) in [ - (&config.critical, State::Critical), - (&config.warning, State::Warning), - (&config.info, State::Info), - (&config.good, State::Good), - ] { - if let Some(list) = list_opt { - for val in list { - if stats.get(val).is_some_and(|x| *x > 0) { - widget.state = ret; - break 'outer; - } - } - } - } - - let mut values: HashMap<_, _> = stats - .into_iter() - .map(|(k, v)| (k.into(), Value::number(v))) - .collect(); - values.insert("icon".into(), Value::icon("github")); - widget.set_values(values); - - api.set_widget(widget)?; - } else { - api.hide()?; - } - - select! { - _ = interval.tick() => (), - _ = api.wait_for_update_request() => (), - } - } -} - -#[derive(Deserialize, Debug)] -struct Notification { - reason: String, -} - -async fn get_stats(token: &str) -> Result> { - let mut stats = HashMap::new(); - let mut total = 0; - for page in 1..100 { - let fetch = || get_on_page(token, page); - let on_page = fetch.retry(ExponentialBuilder::default()).await?; - if on_page.is_empty() { - break; - } - total += on_page.len(); - for n in on_page { - stats.entry(n.reason).and_modify(|x| *x += 1).or_insert(1); - } - } - stats.insert("total".into(), total); - stats.entry("total".into()).or_insert(0); - stats.entry("assign".into()).or_insert(0); - stats.entry("author".into()).or_insert(0); - stats.entry("comment".into()).or_insert(0); - stats.entry("ci_activity".into()).or_insert(0); - stats.entry("invitation".into()).or_insert(0); - stats.entry("manual".into()).or_insert(0); - stats.entry("mention".into()).or_insert(0); - stats.entry("review_requested".into()).or_insert(0); - stats.entry("security_alert".into()).or_insert(0); - stats.entry("state_change".into()).or_insert(0); - stats.entry("subscribed".into()).or_insert(0); - stats.entry("team_mention".into()).or_insert(0); - Ok(stats) -} - -async fn get_on_page(token: &str, page: usize) -> Result> { - #[derive(Deserialize)] - #[serde(untagged)] - enum Response { - Notifications(Vec), - ErrorMessage { message: String }, - } - - // https://docs.github.com/en/rest/reference/activity#notifications - let request = REQWEST_CLIENT - .get(format!( - "https://api.github.com/notifications?per_page=100&page={page}", - )) - .header("Authorization", format!("token {token}")); - let response = request - .send() - .await - .error("Failed to send request")? - .json::() - .await - .error("Failed to get JSON")?; - - match response { - Response::Notifications(n) => Ok(n), - Response::ErrorMessage { message } => Err(Error::new(format!("API error: {message}"))), - } -} diff --git a/src/blocks/hueshift.rs b/src/blocks/hueshift.rs deleted file mode 100644 index 8482dee489..0000000000 --- a/src/blocks/hueshift.rs +++ /dev/null @@ -1,396 +0,0 @@ -//! Manage display temperature -//! -//! This block displays the current color temperature in Kelvin. When scrolling upon the block the color temperature is changed. -//! A left click on the block sets the color temperature to `click_temp` that is by default to `6500K`. -//! A right click completely resets the color temperature to its default value (`6500K`). -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $temperature "` -//! `step` | The step color temperature is in/decreased in Kelvin. | `100` -//! `hue_shifter` | Program used to control screen color. | Detect automatically -//! `max_temp` | Max color temperature in Kelvin. | `10000` -//! `min_temp` | Min color temperature in Kelvin. | `1000` -//! `click_temp` | Left click color temperature in Kelvin. | `6500` -//! -//! Placeholder | Value | Type | Unit -//! ----------------------|------------------------------|--------|--------------- -//! `temperature` | Current temperature | Number | - -//! -//! Action | Default button -//! -------------------|--------------- -//! `set_click_temp` | Left -//! `reset` | Right -//! `temperature_up` | Wheel Up -//! `temperature_down` | Wheel Down -//! -//! # Available Hue Shifters -//! -//! Name | Supports -//! ---------------------|--------- -//! `"redshift"` | X11 -//! `"sct"` | X11 -//! `"gammastep"` | X11 and Wayland -//! `"wl_gammarelay"` | Wayland -//! `"wl_gammarelay_rs"` | Wayland -//! `"wlsunset"` | Wayland -//! -//! Note that at the moment, only [`wl_gammarelay`](https://github.com/jeremija/wl-gammarelay) and -//! [`wl_gammarelay_rs`](https://github.com/MaxVerevkin/wl-gammarelay-rs) -//! subscribe to the events and update the bar when the temperature is modified externally. Also, -//! these are the only drivers at the moment that work under Wayland without flickering. -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "hueshift" -//! hue_shifter = "redshift" -//! step = 50 -//! click_temp = 3500 -//! ``` -//! -//! A hard limit is set for the `max_temp` to `10000K` and the same for the `min_temp` which is `1000K`. -//! The `step` has a hard limit as well, defined to `500K` to avoid too brutal changes. - -use super::prelude::*; -use crate::subprocess::{spawn_process, spawn_shell}; -use crate::util::has_command; -use futures::future::pending; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - // TODO: Document once this option becomes useful - #[default(5.into())] - pub interval: Seconds, - #[default(10_000)] - pub max_temp: u16, - #[default(1_000)] - pub min_temp: u16, - // TODO: Remove (this option is undocumented) - #[default(6_500)] - pub current_temp: u16, - pub hue_shifter: Option, - #[default(100)] - pub step: u16, - #[default(6_500)] - pub click_temp: u16, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[ - (MouseButton::Left, None, "set_click_temp"), - (MouseButton::Right, None, "reset"), - (MouseButton::WheelUp, None, "temperature_up"), - (MouseButton::WheelDown, None, "temperature_down"), - ])?; - - let format = config.format.with_default(" $icon $temperature ")?; - - // limit too big steps at 500K to avoid too brutal changes - let step = config.step.min(500); - let max_temp = config.max_temp.min(10_000); - let min_temp = config.min_temp.clamp(1_000, max_temp); - - let hue_shifter = match config.hue_shifter { - Some(driver) => driver, - None => { - if has_command("wl-gammarelay-rs").await? { - HueShifter::WlGammarelayRs - } else if has_command("wl-gammarelay").await? { - HueShifter::WlGammarelay - } else if has_command("redshift").await? { - HueShifter::Redshift - } else if has_command("sct").await? { - HueShifter::Sct - } else if has_command("gammastep").await? { - HueShifter::Gammastep - } else if has_command("wlsunset").await? { - HueShifter::Wlsunset - } else { - return Err(Error::new("Could not detect driver program")); - } - } - }; - - let mut driver: Box = match hue_shifter { - HueShifter::Redshift => Box::new(Redshift::new(config.interval)), - HueShifter::Sct => Box::new(Sct::new(config.interval)), - HueShifter::Gammastep => Box::new(Gammastep::new(config.interval)), - HueShifter::Wlsunset => Box::new(Wlsunset::new(config.interval)), - HueShifter::WlGammarelay => Box::new(WlGammarelayRs::new("wl-gammarelay").await?), - HueShifter::WlGammarelayRs => Box::new(WlGammarelayRs::new("wl-gammarelay-rs").await?), - }; - - let mut current_temp = driver.get().await?.unwrap_or(config.current_temp); - - loop { - let mut widget = Widget::new().with_format(format.clone()); - widget.set_values(map! { - "icon" => Value::icon("hueshift"), - "temperature" => Value::number(current_temp) - }); - api.set_widget(widget)?; - - select! { - update = driver.receive_update() => { - current_temp = update?; - } - _ = api.wait_for_update_request() => { - if let Some(val) = driver.get().await? { - current_temp = val; - } - } - Some(action) = actions.recv() => match action.as_ref() { - "set_click_temp" => { - current_temp = config.click_temp; - driver.update(current_temp).await?; - } - "reset" => { - if max_temp > 6500 { - current_temp = 6500; - driver.reset().await?; - } else { - current_temp = max_temp; - driver.update(current_temp).await?; - } - } - "temperature_up" => { - current_temp = (current_temp + step).min(max_temp); - driver.update(current_temp).await?; - } - "temperature_down" => { - current_temp = current_temp.saturating_sub(step).max(min_temp); - driver.update(current_temp).await?; - } - _ => (), - } - } - } -} - -#[derive(Deserialize, Debug, Clone, Copy)] -#[serde(rename_all = "snake_case")] -pub enum HueShifter { - Redshift, - Sct, - Gammastep, - Wlsunset, - WlGammarelay, - WlGammarelayRs, -} - -#[async_trait] -trait HueShiftDriver { - async fn get(&mut self) -> Result>; - async fn update(&mut self, temp: u16) -> Result<()>; - async fn reset(&mut self) -> Result<()>; - async fn receive_update(&mut self) -> Result; -} - -struct Redshift { - interval: Seconds, -} - -impl Redshift { - fn new(interval: Seconds) -> Self { - Self { interval } - } -} - -#[async_trait] -impl HueShiftDriver for Redshift { - async fn get(&mut self) -> Result> { - // TODO - Ok(None) - } - async fn update(&mut self, temp: u16) -> Result<()> { - spawn_process("redshift", &["-O", &temp.to_string(), "-P"]) - .error("Failed to set new color temperature using redshift.") - } - async fn reset(&mut self) -> Result<()> { - spawn_process("redshift", &["-x"]) - .error("Failed to set new color temperature using redshift.") - } - async fn receive_update(&mut self) -> Result { - sleep(self.interval.0).await; - // self.get().await - pending().await - } -} - -struct Sct { - interval: Seconds, -} - -impl Sct { - fn new(interval: Seconds) -> Self { - Self { interval } - } -} - -#[async_trait] -impl HueShiftDriver for Sct { - async fn get(&mut self) -> Result> { - // TODO - Ok(None) - } - async fn update(&mut self, temp: u16) -> Result<()> { - spawn_shell(&format!("sct {temp} >/dev/null 2>&1")) - .error("Failed to set new color temperature using sct.") - } - async fn reset(&mut self) -> Result<()> { - spawn_process("sct", &[]).error("Failed to set new color temperature using sct.") - } - async fn receive_update(&mut self) -> Result { - sleep(self.interval.0).await; - // self.get().await - pending().await - } -} - -struct Gammastep { - interval: Seconds, -} - -impl Gammastep { - fn new(interval: Seconds) -> Self { - Self { interval } - } -} - -#[async_trait] -impl HueShiftDriver for Gammastep { - async fn get(&mut self) -> Result> { - // TODO - Ok(None) - } - async fn update(&mut self, temp: u16) -> Result<()> { - spawn_shell(&format!("pkill gammastep; gammastep -O {temp} -P &",)) - .error("Failed to set new color temperature using gammastep.") - } - async fn reset(&mut self) -> Result<()> { - spawn_process("gammastep", &["-x"]) - .error("Failed to set new color temperature using gammastep.") - } - async fn receive_update(&mut self) -> Result { - sleep(self.interval.0).await; - // self.get().await - pending().await - } -} - -struct Wlsunset { - interval: Seconds, -} - -impl Wlsunset { - fn new(interval: Seconds) -> Self { - Self { interval } - } -} - -#[async_trait] -impl HueShiftDriver for Wlsunset { - async fn get(&mut self) -> Result> { - // TODO - Ok(None) - } - async fn update(&mut self, temp: u16) -> Result<()> { - // wlsunset does not have a oneshot option, so set both day and - // night temperature. wlsunset dose not allow for day and night - // temperatures to be the same, so increment the day temperature. - spawn_shell(&format!( - "pkill wlsunset; wlsunset -T {} -t {} &", - temp + 1, - temp - )) - .error("Failed to set new color temperature using wlsunset.") - } - async fn reset(&mut self) -> Result<()> { - // wlsunset does not have a reset option, so just kill the process. - // Trying to call wlsunset without any arguments uses the defaults: - // day temp: 6500K - // night temp: 4000K - // latitude/longitude: NaN - // ^ results in sun_condition == POLAR_NIGHT at time of testing - // With these defaults, this results in the the color temperature - // getting set to 4000K. - spawn_process("pkill", &["wlsunset"]) - .error("Failed to set new color temperature using wlsunset.") - } - async fn receive_update(&mut self) -> Result { - sleep(self.interval.0).await; - // self.get().await - pending().await - } -} - -struct WlGammarelayRs { - proxy: WlGammarelayRsBusProxy<'static>, - updates: zbus::proxy::PropertyStream<'static, u16>, -} - -impl WlGammarelayRs { - async fn new(cmd: &str) -> Result { - // Make sure the daemon is running - spawn_process(cmd, &[]).error("Failed to start wl-gammarelay daemon")?; - sleep(Duration::from_millis(100)).await; - - let conn = crate::util::new_dbus_connection().await?; - let proxy = WlGammarelayRsBusProxy::new(&conn) - .await - .error("Failed to create wl-gammarelay-rs DBus proxy")?; - let updates = proxy.receive_temperature_changed().await; - Ok(Self { proxy, updates }) - } -} - -#[async_trait] -impl HueShiftDriver for WlGammarelayRs { - async fn get(&mut self) -> Result> { - let value = self - .proxy - .temperature() - .await - .error("Failed to get temperature")?; - Ok(Some(value)) - } - async fn update(&mut self, temp: u16) -> Result<()> { - self.proxy - .set_temperature(temp) - .await - .error("Failed to set temperature") - } - async fn reset(&mut self) -> Result<()> { - self.update(6500).await - } - async fn receive_update(&mut self) -> Result { - let update = self.updates.next().await.error("No next update")?; - update.get().await.error("Failed to get temperature") - } -} - -#[zbus::proxy( - interface = "rs.wl.gammarelay", - default_service = "rs.wl-gammarelay", - default_path = "/" -)] -trait WlGammarelayRsBus { - /// Brightness property - #[zbus(property)] - fn brightness(&self) -> zbus::Result; - #[zbus(property)] - fn set_brightness(&self, value: f64) -> zbus::Result<()>; - - /// Temperature property - #[zbus(property)] - fn temperature(&self) -> zbus::Result; - #[zbus(property)] - fn set_temperature(&self, value: u16) -> zbus::Result<()>; -} diff --git a/src/blocks/kdeconnect.rs b/src/blocks/kdeconnect.rs deleted file mode 100644 index 79a1cb1a08..0000000000 --- a/src/blocks/kdeconnect.rs +++ /dev/null @@ -1,490 +0,0 @@ -//! [KDEConnect](https://community.kde.org/KDEConnect) indicator -//! -//! Display info from the currently connected device in KDEConnect, updated asynchronously. -//! -//! Block colours are updated based on the battery level, unless all bat_* thresholds are set to 0, -//! in which case the block colours will depend on the notification count instead. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `device_id` | Device ID as per the output of `kdeconnect --list-devices`. | Chooses the first found device, if any. -//! `format` | A string to customise the output of this block. See below for available placeholders. | \" $icon $name{ $bat_icon $bat_charge\|}{ $notif_icon\|} \" -//! `format_disconnected` | Same as `format` but when device is disconnected | `" $icon "` -//! `format_missing` | Same as `format` but when device does not exist | `" $icon x "` -//! `bat_info` | Min battery level below which state is set to info. | `60` -//! `bat_good` | Min battery level below which state is set to good. | `60` -//! `bat_warning` | Min battery level below which state is set to warning. | `30` -//! `bat_critical` | Min battery level below which state is set to critical. | `15` -//! -//! Placeholder | Value | Type | Unit -//! -------------------|--------------------------------------------------------------------------|--------|----- -//! `icon` | Icon based on connection's status | Icon | - -//! `bat_icon` | Battery level indicator (only when connected and if supported) | Icon | - -//! `bat_charge` | Battery charge level (only when connected and if supported) | Number | % -//! `network_icon` | Cell Network indicator (only when connected and if supported) | Icon | - -//! `network_type` | Cell Network type (only when connected and if supported) | Text | - -//! `network_strength` | Cell Network level (only when connected and if supported) | Number | % -//! `notif_icon` | Only when connected and there are notifications | Icon | - -//! `notif_count` | Number of notifications on your phone (only when connected and non-zero) | Number | - -//! `name` | Name of your device as reported by KDEConnect (if available) | Text | - -//! -//! # Example -//! -//! Do not show the name, do not set the "good" state. -//! -//! ```toml -//! [[block]] -//! block = "kdeconnect" -//! format = " $icon {$bat_icon $bat_charge |}{$notif_icon |}{$network_icon$network_strength $network_type |}" -//! bat_good = 101 -//! ``` -//! -//! # Icons Used -//! - `bat` (as a progression) -//! - `bat_charging` (as a progression) -//! - `net_cellular` (as a progression) -//! - `notification` -//! - `phone` -//! - `phone_disconnected` - -use super::prelude::*; - -mod battery; -mod connectivity_report; -use battery::BatteryDbusProxy; -use connectivity_report::ConnectivityDbusProxy; - -make_log_macro!(debug, "kdeconnect"); - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub device_id: Option, - pub format: FormatConfig, - pub disconnected_format: FormatConfig, - pub missing_format: FormatConfig, - #[default(60)] - pub bat_good: u8, - #[default(60)] - pub bat_info: u8, - #[default(30)] - pub bat_warning: u8, - #[default(15)] - pub bat_critical: u8, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config - .format - .with_default(" $icon $name {$bat_icon $bat_charge |}{$notif_icon |}")?; - let disconnected_format = config.disconnected_format.with_default(" $icon ")?; - let missing_format = config.missing_format.with_default(" $icon x ")?; - - let battery_state = ( - config.bat_good, - config.bat_info, - config.bat_warning, - config.bat_critical, - ) != (0, 0, 0, 0); - - let mut monitor = DeviceMonitor::new(config.device_id.clone()).await?; - - loop { - match monitor.get_device_info().await { - Some(info) => { - let mut widget = Widget::new(); - if info.connected { - widget.set_format(format.clone()); - } else { - widget.set_format(disconnected_format.clone()); - } - - let mut values = map! { - [if info.connected] "icon" => Value::icon("phone"), - [if !info.connected] "icon" => Value::icon("phone_disconnected"), - [if let Some(name) = info.name] "name" => Value::text(name), - [if info.notifications > 0] "notif_count" => Value::number(info.notifications), - [if info.notifications > 0] "notif_icon" => Value::icon("notification"), - [if let Some(bat) = info.bat_level] "bat_charge" => Value::percents(bat), - }; - - if let Some(bat_level) = info.bat_level { - values.insert( - "bat_icon".into(), - Value::icon_progression( - if info.charging { "bat_charging" } else { "bat" }, - bat_level as f64 / 100.0, - ), - ); - if battery_state { - widget.state = if info.charging { - State::Good - } else if bat_level <= config.bat_critical { - State::Critical - } else if bat_level <= config.bat_info { - State::Info - } else if bat_level > config.bat_good { - State::Good - } else { - State::Idle - }; - } - } - - if !battery_state { - widget.state = if info.notifications == 0 { - State::Idle - } else { - State::Info - }; - } - - if let Some(cellular_network_type) = info.cellular_network_type { - // network strength is 0..=4 from docs of - // kdeconnect/plugins/connectivity-report, and I - // got -1 for disabled SIM (undocumented) - let cell_network_percent = - (info.cellular_network_strength.clamp(0, 4) * 25) as f64; - values.insert( - "network_icon".into(), - Value::icon_progression( - "net_cellular", - (info.cellular_network_strength + 1).clamp(0, 5) as f64 / 5.0, - ), - ); - values.insert( - "network_strength".into(), - Value::percents(cell_network_percent), - ); - - if info.cellular_network_strength <= 0 { - widget.state = State::Critical; - values.insert("network_type".into(), Value::text("×".into())); - } else { - values.insert("network_type".into(), Value::text(cellular_network_type)); - } - } - - widget.set_values(values); - api.set_widget(widget)?; - } - None => { - let mut widget = Widget::new().with_format(missing_format.clone()); - widget.set_values(map! { "icon" => Value::icon("phone_disconnected") }); - api.set_widget(widget)?; - } - } - - monitor.wait_for_change().await?; - } -} - -struct DeviceMonitor { - device_id: Option, - daemon_proxy: DaemonDbusProxy<'static>, - device: Option, -} - -struct Device { - id: String, - device_proxy: DeviceDbusProxy<'static>, - battery_proxy: BatteryDbusProxy<'static>, - notifications_proxy: NotificationsDbusProxy<'static>, - connectivity_proxy: ConnectivityDbusProxy<'static>, - device_signals: zbus::proxy::SignalStream<'static>, - notifications_signals: zbus::proxy::SignalStream<'static>, - battery_refreshed: battery::refreshedStream, - connectivity_refreshed: connectivity_report::refreshedStream, -} - -struct DeviceInfo { - connected: bool, - name: Option, - notifications: usize, - charging: bool, - bat_level: Option, - cellular_network_type: Option, - cellular_network_strength: i32, -} - -impl DeviceMonitor { - async fn new(device_id: Option) -> Result { - let dbus_conn = new_dbus_connection().await?; - let daemon_proxy = DaemonDbusProxy::new(&dbus_conn) - .await - .error("Failed to create DaemonDbusProxy")?; - let device = Device::try_find(&daemon_proxy, device_id.as_deref()).await?; - Ok(Self { - device_id, - daemon_proxy, - device, - }) - } - - async fn wait_for_change(&mut self) -> Result<()> { - match &mut self.device { - None => { - let mut device_added = self - .daemon_proxy - .receive_device_added() - .await - .error("Couldn't create stream")?; - loop { - device_added - .next() - .await - .error("Stream ended unexpectedly")?; - if let Some(device) = - Device::try_find(&self.daemon_proxy, self.device_id.as_deref()).await? - { - self.device = Some(device); - return Ok(()); - } - } - } - Some(dev) => { - let mut device_removed = self - .daemon_proxy - .receive_device_removed() - .await - .error("Couldn't create stream")?; - loop { - select! { - rem = device_removed.next() => { - let rem = rem.error("stream ended unexpectedly")?; - let args = rem.args().error("dbus error")?; - if args.id() == &dev.id { - self.device = Device::try_find(&self.daemon_proxy, self.device_id.as_deref()).await?; - return Ok(()); - } - } - _ = dev.wait_for_change() => { - if !dev.connected().await { - debug!("device became unreachable, re-searching"); - if let Some(dev) = Device::try_find(&self.daemon_proxy, self.device_id.as_deref()).await? - && dev.connected().await { - debug!("selected {:?}", dev.id); - self.device = Some(dev); - } - } - return Ok(()) - } - } - } - } - } - } - - async fn get_device_info(&mut self) -> Option { - let device = self.device.as_ref()?; - let (bat_level, charging) = device.battery().await; - let (cellular_network_type, cellular_network_strength) = device.network().await; - Some(DeviceInfo { - connected: device.connected().await, - name: device.name().await, - notifications: device.notifications().await, - charging, - bat_level, - cellular_network_type, - cellular_network_strength, - }) - } -} - -impl Device { - /// Find a device which `device_id`. Reachable devices have precedence. - async fn try_find( - daemon_proxy: &DaemonDbusProxy<'_>, - device_id: Option<&str>, - ) -> Result> { - let Ok(mut devices) = daemon_proxy.devices().await else { - debug!("could not get the list of managed objects"); - return Ok(None); - }; - - debug!("all devices: {:?}", devices); - - if let Some(device_id) = device_id { - devices.retain(|id| id == device_id); - } - - let mut selected_device = None; - - for id in devices { - let device_proxy = DeviceDbusProxy::builder(daemon_proxy.inner().connection()) - .cache_properties(zbus::proxy::CacheProperties::No) - .path(format!("/modules/kdeconnect/devices/{id}")) - .unwrap() - .build() - .await - .error("Failed to create DeviceDbusProxy")?; - let reachable = device_proxy.is_reachable().await.unwrap_or(false); - selected_device = Some((id, device_proxy)); - if reachable { - break; - } - } - - let Some((device_id, device_proxy)) = selected_device else { - debug!("No device found"); - return Ok(None); - }; - - let device_path = format!("/modules/kdeconnect/devices/{device_id}"); - let battery_path = format!("{device_path}/battery"); - let notifications_path = format!("{device_path}/notifications"); - let connectivity_path = format!("{device_path}/connectivity_report"); - - let battery_proxy = BatteryDbusProxy::builder(daemon_proxy.inner().connection()) - .cache_properties(zbus::proxy::CacheProperties::No) - .path(battery_path) - .error("Failed to set battery path")? - .build() - .await - .error("Failed to create BatteryDbusProxy")?; - let notifications_proxy = - NotificationsDbusProxy::builder(daemon_proxy.inner().connection()) - .cache_properties(zbus::proxy::CacheProperties::No) - .path(notifications_path) - .error("Failed to set notifications path")? - .build() - .await - .error("Failed to create BatteryDbusProxy")?; - let connectivity_proxy = ConnectivityDbusProxy::builder(daemon_proxy.inner().connection()) - .cache_properties(zbus::proxy::CacheProperties::No) - .path(connectivity_path) - .error("Failed to set connectivity path")? - .build() - .await - .error("Failed to create ConnectivityDbusProxy")?; - - let device_signals = device_proxy - .inner() - .receive_all_signals() - .await - .error("Failed to receive signals")?; - let notifications_signals = notifications_proxy - .inner() - .receive_all_signals() - .await - .error("Failed to receive signals")?; - let battery_refreshed = battery_proxy - .receive_refreshed() - .await - .error("Failed to receive signals")?; - let connectivity_refreshed = connectivity_proxy - .receive_refreshed() - .await - .error("Failed to receive signals")?; - - Ok(Some(Self { - id: device_id, - device_proxy, - battery_proxy, - notifications_proxy, - connectivity_proxy, - device_signals, - notifications_signals, - battery_refreshed, - connectivity_refreshed, - })) - } - - async fn wait_for_change(&mut self) { - select! { - _ = self.device_signals.next() => (), - _ = self.notifications_signals.next() => (), - _ = self.battery_refreshed.next() => (), - _ = self.connectivity_refreshed.next() => (), - } - } - - async fn connected(&self) -> bool { - self.device_proxy.is_reachable().await.unwrap_or(false) - } - - async fn name(&self) -> Option { - self.device_proxy.name().await.ok() - } - - async fn battery(&self) -> (Option, bool) { - let (charge, is_charging) = tokio::join!( - self.battery_proxy.charge(), - self.battery_proxy.is_charging(), - ); - ( - charge.ok().map(|x| x.clamp(0, 100) as u8), - is_charging.unwrap_or(false), - ) - } - - async fn notifications(&self) -> usize { - self.notifications_proxy - .active_notifications() - .await - .map(|n| n.len()) - .unwrap_or(0) - } - - async fn network(&self) -> (Option, i32) { - let (ty, strength) = tokio::join!( - self.connectivity_proxy.cellular_network_type(), - self.connectivity_proxy.cellular_network_strength(), - ); - (ty.ok(), strength.unwrap_or(-1)) - } -} - -#[zbus::proxy( - interface = "org.kde.kdeconnect.daemon", - default_service = "org.kde.kdeconnect", - default_path = "/modules/kdeconnect" -)] -trait DaemonDbus { - #[zbus(name = "devices")] - fn devices(&self) -> zbus::Result>; - - #[zbus(signal, name = "deviceAdded")] - fn device_added(&self, id: String) -> zbus::Result<()>; - - #[zbus(signal, name = "deviceRemoved")] - fn device_removed(&self, id: String) -> zbus::Result<()>; -} - -#[zbus::proxy( - interface = "org.kde.kdeconnect.device", - default_service = "org.kde.kdeconnect" -)] -trait DeviceDbus { - #[zbus(property, name = "isReachable")] - fn is_reachable(&self) -> zbus::Result; - - #[zbus(signal, name = "reachableChanged")] - fn reachable_changed(&self, reachable: bool) -> zbus::Result<()>; - - #[zbus(property, name = "name")] - fn name(&self) -> zbus::Result; - - #[zbus(signal, name = "nameChanged")] - fn name_changed_(&self, name: &str) -> zbus::Result<()>; -} - -#[zbus::proxy( - interface = "org.kde.kdeconnect.device.notifications", - default_service = "org.kde.kdeconnect" -)] -trait NotificationsDbus { - #[zbus(name = "activeNotifications")] - fn active_notifications(&self) -> zbus::Result>; - - #[zbus(signal, name = "allNotificationsRemoved")] - fn all_notifications_removed(&self) -> zbus::Result<()>; - - #[zbus(signal, name = "notificationPosted")] - fn notification_posted(&self, id: &str) -> zbus::Result<()>; - - #[zbus(signal, name = "notificationRemoved")] - fn notification_removed(&self, id: &str) -> zbus::Result<()>; -} diff --git a/src/blocks/kdeconnect/battery.rs b/src/blocks/kdeconnect/battery.rs deleted file mode 100644 index c4168c5e76..0000000000 --- a/src/blocks/kdeconnect/battery.rs +++ /dev/null @@ -1,14 +0,0 @@ -#[zbus::proxy( - interface = "org.kde.kdeconnect.device.battery", - default_service = "org.kde.kdeconnect" -)] -pub(super) trait BatteryDbus { - #[zbus(signal, name = "refreshed")] - fn refreshed(&self, is_charging: bool, charge: i32) -> zbus::Result<()>; - - #[zbus(property, name = "charge")] - fn charge(&self) -> zbus::Result; - - #[zbus(property, name = "isCharging")] - fn is_charging(&self) -> zbus::Result; -} diff --git a/src/blocks/kdeconnect/connectivity_report.rs b/src/blocks/kdeconnect/connectivity_report.rs deleted file mode 100644 index 6178eed971..0000000000 --- a/src/blocks/kdeconnect/connectivity_report.rs +++ /dev/null @@ -1,14 +0,0 @@ -#[zbus::proxy( - interface = "org.kde.kdeconnect.device.connectivity_report", - default_service = "org.kde.kdeconnect" -)] -pub(super) trait ConnectivityDbus { - #[zbus(signal, name = "refreshed")] - fn refreshed(&self, network_type: String, network_strength: i32) -> zbus::Result<()>; - - #[zbus(property, name = "cellularNetworkStrength")] - fn cellular_network_strength(&self) -> zbus::Result; - - #[zbus(property, name = "cellularNetworkType")] - fn cellular_network_type(&self) -> zbus::Result; -} diff --git a/src/blocks/keyboard_layout.rs b/src/blocks/keyboard_layout.rs deleted file mode 100644 index f6606f704d..0000000000 --- a/src/blocks/keyboard_layout.rs +++ /dev/null @@ -1,187 +0,0 @@ -//! Keyboard layout indicator -//! -//! Six drivers are available: -//! - `xkbevent` which can read asynchronous updates from the x11 events -//! - `setxkbmap` (alias for `xkbevent`) *DEPRECATED* -//! - `xkbswitch` (alias for `xkbevent`) *DEPRECATED* -//! - `localebus` which can read asynchronous updates from the systemd `org.freedesktop.locale1` D-Bus path -//! - `kbddbus` which uses [kbdd](https://github.com/qnikst/kbdd) to monitor per-window layout changes via DBus -//! - `sway` which can read asynchronous updates from the sway IPC -//! -//! `setxkbmap` and `xkbswitch` are deprecated and will be removed in v0.35.0. -//! -//! Which of these methods is appropriate will depend on your system setup. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `driver` | One of `"xkbevent"`, `"setxkbmap"`, `"xkbswitch"`, `"localebus"`, `"kbddbus"` or `"sway"`, depending on your system. | `"xkbevent"` -//! `interval` *DEPRECATED* | Update interval, in seconds. Only used by the `"setxkbmap"` driver. | `60` -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $layout "` -//! `sway_kb_identifier` | Identifier of the device you want to monitor, as found in the output of `swaymsg -t get_inputs`. | Defaults to first input found -//! `mappings` | Map `layout (variant)` to custom short name. | `None` -//! -//! `interval` is deprecated and will be removed in v0.35.0. -//! -//! Key | Value | Type -//! ---------|-------|----- -//! `layout` | Keyboard layout name | String -//! `variant`| Keyboard variant name or `N/A` if not applicable | String -//! -//! # Examples -//! -//! Listen to D-Bus for changes: -//! -//! ```toml -//! [[block]] -//! block = "keyboard_layout" -//! driver = "localebus" -//! ``` -//! -//! Listen to kbdd for changes, the text is in the following format: -//! "English (US)" - {$layout ($variant)} -//! use block.mappings to override with shorter names as shown below. -//! Also use format = " $layout ($variant) " to see the full text to map, -//! or you can use: -//! dbus-monitor interface=ru.gentoo.kbdd -//! to see the exact variant spelling -//! -//! ```toml -//! [[block]] -//! block = "keyboard_layout" -//! driver = "kbddbus" -//! [block.mappings] -//! "English (US)" = "us" -//! "Bulgarian (new phonetic)" = "bg" -//! ``` -//! -//! Listen to sway for changes: -//! -//! ```toml -//! [[block]] -//! block = "keyboard_layout" -//! driver = "sway" -//! sway_kb_identifier = "1133:49706:Gaming_Keyboard_G110" -//! ``` -//! -//! Listen to sway for changes and override mappings: -//! ```toml -//! [[block]] -//! block = "keyboard_layout" -//! driver = "sway" -//! format = " $layout " -//! [block.mappings] -//! "English (Workman)" = "EN" -//! "Russian (N/A)" = "RU" -//! ``` -//! -//! Listen to xkb events for changes: -//! -//! ```toml -//! [[block]] -//! block = "keyboard_layout" -//! driver = "xkbevent" -//! ``` - -mod locale_bus; -use locale_bus::LocaleBus; - -mod kbdd_bus; -use kbdd_bus::KbddBus; - -mod sway; -use sway::Sway; - -mod xkb_event; -use xkb_event::XkbEvent; - -use super::prelude::*; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - pub driver: KeyboardLayoutDriver, - #[default(60.into())] - pub interval: Seconds, - pub sway_kb_identifier: Option, - pub mappings: Option>, -} - -#[derive(Deserialize, Debug, SmartDefault, Clone, Copy)] -#[serde(rename_all = "lowercase")] -pub enum KeyboardLayoutDriver { - #[default] - XkbEvent, - SetXkbMap, - XkbSwitch, - LocaleBus, - KbddBus, - Sway, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config.format.with_default(" $layout ")?; - - let mut backend: Box = match config.driver { - KeyboardLayoutDriver::LocaleBus => Box::new(LocaleBus::new().await?), - KeyboardLayoutDriver::KbddBus => Box::new(KbddBus::new().await?), - KeyboardLayoutDriver::Sway => Box::new(Sway::new(config.sway_kb_identifier.clone()).await?), - KeyboardLayoutDriver::XkbEvent - | KeyboardLayoutDriver::SetXkbMap - | KeyboardLayoutDriver::XkbSwitch => Box::new(XkbEvent::new().await?), - }; - - loop { - let Info { - mut layout, - variant, - } = backend.get_info().await?; - - let variant = variant.unwrap_or_else(|| "N/A".into()); - if let Some(mappings) = &config.mappings - && let Some(mapped) = mappings.get(&format!("{layout} ({variant})")) - { - layout.clone_from(mapped); - } - - let mut widget = Widget::new().with_format(format.clone()); - widget.set_values(map! { - "layout" => Value::text(layout), - "variant" => Value::text(variant), - }); - api.set_widget(widget)?; - - backend.wait_for_change().await?; - } -} - -#[async_trait] -trait Backend { - async fn get_info(&mut self) -> Result; - async fn wait_for_change(&mut self) -> Result<()>; -} - -#[derive(Clone)] -struct Info { - layout: String, - variant: Option, -} - -impl Info { - /// Parse "layout (variant)" string - fn from_layout_variant_str(s: &str) -> Self { - if let Some((layout, rest)) = s.split_once('(') { - Self { - layout: layout.trim_end().into(), - variant: Some(rest.trim_end_matches(')').into()), - } - } else { - Self { - layout: s.into(), - variant: None, - } - } - } -} diff --git a/src/blocks/keyboard_layout/kbdd_bus.rs b/src/blocks/keyboard_layout/kbdd_bus.rs deleted file mode 100644 index b605352afa..0000000000 --- a/src/blocks/keyboard_layout/kbdd_bus.rs +++ /dev/null @@ -1,67 +0,0 @@ -use super::*; - -pub(super) struct KbddBus { - stream: layoutNameChangedStream, - info: Info, -} - -impl KbddBus { - pub(super) async fn new() -> Result { - let conn = new_dbus_connection().await?; - let proxy = KbddBusInterfaceProxy::builder(&conn) - .cache_properties(zbus::proxy::CacheProperties::No) - .build() - .await - .error("Failed to create KbddBusInterfaceProxy")?; - let stream = proxy - .receive_layout_updated() - .await - .error("Failed to monitor kbdd interface")?; - let layout_index = proxy - .current_layout_index() - .await - .error("Failed to get current layout index from kbdd")?; - let current_layout = proxy - .current_layout(layout_index) - .await - .error("Failed to get current layout from kbdd")?; - let info = Info::from_layout_variant_str(¤t_layout); - Ok(Self { stream, info }) - } -} - -#[async_trait] -impl Backend for KbddBus { - async fn get_info(&mut self) -> Result { - Ok(self.info.clone()) - } - - async fn wait_for_change(&mut self) -> Result<()> { - let event = self - .stream - .next() - .await - .error("Failed to receive kbdd event from dbus")?; - let args = event - .args() - .error("Failed to get the args from kbdd message")?; - self.info = Info::from_layout_variant_str(args.layout()); - Ok(()) - } -} - -#[zbus::proxy( - interface = "ru.gentoo.kbdd", - default_service = "ru.gentoo.KbddService", - default_path = "/ru/gentoo/KbddService" -)] -trait KbddBusInterface { - #[zbus(signal, name = "layoutNameChanged")] - fn layout_updated(&self, layout: String) -> zbus::Result<()>; - - #[zbus(name = "getCurrentLayout")] - fn current_layout_index(&self) -> zbus::Result; - - #[zbus(name = "getLayoutName")] - fn current_layout(&self, layout_id: u32) -> zbus::Result; -} diff --git a/src/blocks/keyboard_layout/locale_bus.rs b/src/blocks/keyboard_layout/locale_bus.rs deleted file mode 100644 index c6c80dd98d..0000000000 --- a/src/blocks/keyboard_layout/locale_bus.rs +++ /dev/null @@ -1,57 +0,0 @@ -use super::*; - -pub(super) struct LocaleBus { - proxy: LocaleBusInterfaceProxy<'static>, - stream1: zbus::proxy::PropertyStream<'static, String>, - stream2: zbus::proxy::PropertyStream<'static, String>, -} - -impl LocaleBus { - pub(super) async fn new() -> Result { - let conn = new_system_dbus_connection().await?; - let proxy = LocaleBusInterfaceProxy::new(&conn) - .await - .error("Failed to create LocaleBusProxy")?; - let layout_updates = proxy.receive_layout_changed().await; - let variant_updates = proxy.receive_layout_changed().await; - Ok(Self { - proxy, - stream1: layout_updates, - stream2: variant_updates, - }) - } -} - -#[async_trait] -impl Backend for LocaleBus { - async fn get_info(&mut self) -> Result { - // zbus does internal caching - let layout = self.proxy.layout().await.error("Failed to get layout")?; - let variant = self.proxy.variant().await.error("Failed to get variant")?; - Ok(Info { - layout, - variant: Some(variant), - }) - } - - async fn wait_for_change(&mut self) -> Result<()> { - select! { - _ = self.stream1.next() => (), - _ = self.stream2.next() => (), - } - Ok(()) - } -} - -#[zbus::proxy( - interface = "org.freedesktop.locale1", - default_service = "org.freedesktop.locale1", - default_path = "/org/freedesktop/locale1" -)] -trait LocaleBusInterface { - #[zbus(property, name = "X11Layout")] - fn layout(&self) -> zbus::Result; - - #[zbus(property, name = "X11Variant")] - fn variant(&self) -> zbus::Result; -} diff --git a/src/blocks/keyboard_layout/sway.rs b/src/blocks/keyboard_layout/sway.rs deleted file mode 100644 index d610ac3a0d..0000000000 --- a/src/blocks/keyboard_layout/sway.rs +++ /dev/null @@ -1,69 +0,0 @@ -// use super::super::prelude::*; -use super::*; -use swayipc_async::{Connection, Event, EventType}; - -pub(super) struct Sway { - events: swayipc_async::EventStream, - cur_layout: String, - kbd: Option, -} - -impl Sway { - pub(super) async fn new(kbd: Option) -> Result { - let mut connection = Connection::new() - .await - .error("Failed to open swayipc connection")?; - let cur_layout = connection - .get_inputs() - .await - .error("failed to get current input")? - .iter() - .find_map(|i| { - if i.input_type == "keyboard" && kbd.as_deref().is_none_or(|id| id == i.identifier) - { - i.xkb_active_layout_name.clone() - } else { - None - } - }) - .error("Failed to get current input")?; - let events = connection - .subscribe(&[EventType::Input]) - .await - .error("Failed to subscribe to events")?; - Ok(Self { - events, - cur_layout, - kbd, - }) - } -} - -#[async_trait] -impl Backend for Sway { - async fn get_info(&mut self) -> Result { - Ok(Info::from_layout_variant_str(&self.cur_layout)) - } - - async fn wait_for_change(&mut self) -> Result<()> { - loop { - let event = self - .events - .next() - .await - .error("swayipc channel closed")? - .error("bad event")?; - if let Event::Input(event) = event - && self - .kbd - .as_deref() - .is_none_or(|id| id == event.input.identifier) - && let Some(new_layout) = event.input.xkb_active_layout_name - && new_layout != self.cur_layout - { - self.cur_layout = new_layout; - return Ok(()); - } - } - } -} diff --git a/src/blocks/keyboard_layout/xkb_event.rs b/src/blocks/keyboard_layout/xkb_event.rs deleted file mode 100644 index bbc7d97d34..0000000000 --- a/src/blocks/keyboard_layout/xkb_event.rs +++ /dev/null @@ -1,143 +0,0 @@ -use super::*; -use x11rb_async::{ - connection::{Connection as _, RequestConnection as _}, - protocol::{ - Event, - xkb::{ - self, ConnectionExt as _, EventType, ID, MapPart, NameDetail, SelectEventsAux, - UseExtensionReply, - }, - xproto::ConnectionExt as _, - }, - rust_connection::RustConnection, -}; - -const XCB_XKB_MINOR_VERSION: u16 = 0; -const XCB_XKB_MAJOR_VERSION: u16 = 1; - -pub(super) struct XkbEvent { - connection: RustConnection, -} - -fn parse_layout(buf: &[u8], index: usize) -> Result<&str> { - let colon_i = buf.iter().position(|c| *c == b':').unwrap_or(buf.len()); - let layout = buf[..colon_i] - .split(|&c| c == b'+') - .skip(1) // layout names start from index 1 - .nth(index) - .error("Index out of range")?; - std::str::from_utf8(layout).error("non utf8 layout") -} - -async fn get_layout(connection: &RustConnection) -> Result { - let xkb_state = connection - .xkb_get_state(ID::USE_CORE_KBD.into()) - .await - .error("xkb_get_state failed")? - .reply() - .await - .error("xkb_get_state reply failed")?; - let group: u8 = xkb_state.group.into(); - - let symbols_name = connection - .xkb_get_names( - ID::USE_CORE_KBD.into(), - NameDetail::GROUP_NAMES | NameDetail::SYMBOLS, - ) - .await - .error("xkb_get_names failed")? - .reply() - .await - .error("xkb_get_names reply failed")? - .value_list - .symbols_name - .error("symbols_name is empty")?; - - let name = connection - .get_atom_name(symbols_name) - .await - .error("get_atom_name failed")? - .reply() - .await - .error("get_atom_name reply failed")? - .name; - let layout = parse_layout(&name, group as _)?; - - Ok(layout.to_owned()) -} - -async fn prefetch_xkb_extension(connection: &RustConnection) -> Result { - connection - .prefetch_extension_information(xkb::X11_EXTENSION_NAME) - .await - .error("prefetch_extension_information failed")?; - - let reply = connection - .xkb_use_extension(XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION) - .await - .error("xkb_use_extension failed")? - .reply() - .await - .error("xkb_use_extension reply failed")?; - - Ok(reply) -} - -impl XkbEvent { - pub(super) async fn new() -> Result { - let (connection, _, drive) = RustConnection::connect(None) - .await - .error("Failed to open XCB connection")?; - - tokio::spawn(drive); - let reply = prefetch_xkb_extension(&connection) - .await - .error("Failed to prefetch xkb extension")?; - - if !reply.supported { - return Err(Error::new( - "This program requires the X11 server to support the XKB extension", - )); - } - - connection - .xkb_select_events( - ID::USE_CORE_KBD.into(), - EventType::default(), - EventType::STATE_NOTIFY, - MapPart::default(), - MapPart::default(), - &SelectEventsAux::new(), - ) - .await - .error("Failed to select events")?; - - Ok(XkbEvent { connection }) - } -} - -#[async_trait] -impl Backend for XkbEvent { - async fn get_info(&mut self) -> Result { - let cur_layout = get_layout(&self.connection) - .await - .error("Failed to get current layout")?; - Ok(Info::from_layout_variant_str(&cur_layout)) - } - - async fn wait_for_change(&mut self) -> Result<()> { - loop { - let event = self - .connection - .wait_for_event() - .await - .error("Failed to read the event")?; - - if let Event::XkbStateNotify(e) = event - && e.changed.contains(xkb::StatePart::GROUP_STATE) - { - return Ok(()); - } - } - } -} diff --git a/src/blocks/load.rs b/src/blocks/load.rs deleted file mode 100644 index 1604cf7038..0000000000 --- a/src/blocks/load.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! System load average -//! -//! # Configuration -//! -//! Key | Values | Default -//! -----------|---------------------------------------------------------------------------------------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $1m.eng(w:4) "` -//! `interval` | Update interval in seconds | `3` -//! `info` | Minimum load, where state is set to info | `0.3` -//! `warning` | Minimum load, where state is set to warning | `0.6` -//! `critical` | Minimum load, where state is set to critical | `0.9` -//! -//! Placeholder | Value | Type | Unit -//! -------------|------------------------|--------|----- -//! `icon` | A static icon | Icon | - -//! `1m` | 1 minute load average | Number | - -//! `5m` | 5 minute load average | Number | - -//! `15m` | 15 minute load average | Number | - -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "load" -//! format = " $icon 1min avg: $1m.eng(w:4) " -//! interval = 1 -//! ``` -//! -//! # Icons Used -//! - `cogs` - -use super::prelude::*; -use crate::util; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - #[default(3.into())] - pub interval: Seconds, - #[default(0.3)] - pub info: f64, - #[default(0.6)] - pub warning: f64, - #[default(0.9)] - pub critical: f64, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config.format.with_default(" $icon $1m.eng(w:4) ")?; - - // borrowed from https://docs.rs/cpuinfo/0.1.1/src/cpuinfo/count/logical.rs.html#4-6 - let logical_cores = util::read_file("/proc/cpuinfo") - .await - .error("Your system doesn't support /proc/cpuinfo")? - .lines() - .filter(|l| l.starts_with("processor")) - .count(); - - loop { - let loadavg = util::read_file("/proc/loadavg") - .await - .error("Your system does not support reading the load average from /proc/loadavg")?; - let mut values = loadavg.split(' '); - let m1: f64 = values - .next() - .and_then(|x| x.parse().ok()) - .error("bad /proc/loadavg file")?; - let m5: f64 = values - .next() - .and_then(|x| x.parse().ok()) - .error("bad /proc/loadavg file")?; - let m15: f64 = values - .next() - .and_then(|x| x.parse().ok()) - .error("bad /proc/loadavg file")?; - - let mut widget = Widget::new().with_format(format.clone()); - widget.state = match m1 / logical_cores as f64 { - x if x > config.critical => State::Critical, - x if x > config.warning => State::Warning, - x if x > config.info => State::Info, - _ => State::Idle, - }; - widget.set_values(map! { - "icon" => Value::icon("cogs"), - "1m" => Value::number(m1), - "5m" => Value::number(m5), - "15m" => Value::number(m15), - }); - api.set_widget(widget)?; - - select! { - _ = sleep(config.interval.0) => (), - _ = api.wait_for_update_request() => (), - } - } -} diff --git a/src/blocks/maildir.rs b/src/blocks/maildir.rs deleted file mode 100644 index 1c53eb9dc2..0000000000 --- a/src/blocks/maildir.rs +++ /dev/null @@ -1,110 +0,0 @@ -//! Unread mail. Only supports maildir format. -//! -//! Note that you need to enable `maildir` feature to use this block: -//! ```sh -//! cargo build --release --features maildir -//! ``` -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $status "` -//! `inboxes` | List of maildir inboxes to look for mails in. Supports path/glob expansions (e.g. `~` and `*`). | **Required** -//! `threshold_warning` | Number of unread mails where state is set to warning. | `1` -//! `threshold_critical` | Number of unread mails where state is set to critical. | `10` -//! `interval` | Update interval, in seconds. | `5` -//! `display_type` | Which part of the maildir to count: `"new"`, `"cur"`, or `"all"`. | `"new"` -//! -//! Placeholder | Value | Type | Unit -//! -------------|------------------------|--------|----- -//! `icon` | A static icon | Icon | - -//! `status` | Number of emails | Number | - -//! -//! # Examples -//! -//! ```toml -//! [[block]] -//! block = "maildir" -//! interval = 60 -//! inboxes = ["~/mail/local", "~/maildir/account1/*"] -//! threshold_warning = 1 -//! threshold_critical = 10 -//! display_type = "new" -//! ``` -//! -//! # Icons Used -//! - `mail` - -use super::prelude::*; -use maildir::Maildir; -use std::path::PathBuf; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - #[default(5.into())] - pub interval: Seconds, - pub inboxes: Vec, - #[default(1)] - pub threshold_warning: usize, - #[default(10)] - pub threshold_critical: usize, - #[default(MailType::New)] - pub display_type: MailType, -} - -fn expand_inbox(inbox: &str) -> Result> { - let expanded = shellexpand::full(inbox).error("Failed to expand inbox")?; - let paths = glob::glob(&expanded).error("Glob expansion failed")?; - Ok(paths.filter_map(|p| p.ok())) -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config.format.with_default(" $icon $status ")?; - - let mut inboxes = Vec::with_capacity(config.inboxes.len()); - for inbox in &config.inboxes { - inboxes.extend(expand_inbox(inbox)?.map(Maildir::from)); - } - - loop { - let mut newmails = 0; - for inbox in &inboxes { - // TODO: spawn_blocking? - newmails += match config.display_type { - MailType::New => inbox.count_new(), - MailType::Cur => inbox.count_cur(), - MailType::All => inbox.count_new() + inbox.count_cur(), - }; - } - - let mut widget = Widget::new().with_format(format.clone()); - widget.state = if newmails >= config.threshold_critical { - State::Critical - } else if newmails >= config.threshold_warning { - State::Warning - } else { - State::Idle - }; - widget.set_values(map!( - "icon" => Value::icon("mail"), - "status" => Value::number(newmails) - )); - api.set_widget(widget)?; - - select! { - _ = sleep(config.interval.0) => (), - _ = api.wait_for_update_request() => (), - } - } -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum MailType { - New, - Cur, - All, -} diff --git a/src/blocks/memory.rs b/src/blocks/memory.rs deleted file mode 100644 index 093ae2f087..0000000000 --- a/src/blocks/memory.rs +++ /dev/null @@ -1,385 +0,0 @@ -//! Memory and swap usage -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block when in "Memory" view. See below for available placeholders. | `" $icon $mem_used.eng(prefix:Mi)/$mem_total.eng(prefix:Mi)($mem_used_percents.eng(w:2)) "` -//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None` -//! `interval` | Update interval in seconds | `5` -//! `warning_mem` | Percentage of memory usage, where state is set to warning | `80.0` -//! `warning_swap` | Percentage of swap usage, where state is set to warning | `80.0` -//! `critical_mem` | Percentage of memory usage, where state is set to critical | `95.0` -//! `critical_swap` | Percentage of swap usage, where state is set to critical | `95.0` -//! -//! Placeholder | Value | Type | Unit -//! --------------------------|---------------------------------------------------------------------------------|--------|------- -//! `icon` | Memory icon | Icon | - -//! `icon_swap` | Swap icon | Icon | - -//! `mem_total` | Total physical ram available | Number | Bytes -//! `mem_free` | Free memory not yet used by the kernel or userspace (in general you should use mem_avail) | Number | Bytes -//! `mem_free_percents` | as above but as a percentage of total memory | Number | Percents -//! `mem_avail` | Kernel estimate of usable free memory which includes cached memory and buffers | Number | Bytes -//! `mem_avail_percents` | as above but as a percentage of total memory | Number | Percents -//! `mem_total_used` | mem_total - mem_free | Number | Bytes -//! `mem_total_used_percents` | as above but as a percentage of total memory | Number | Percents -//! `mem_used` | Memory used, excluding cached memory and buffers; same as htop's green bar | Number | Bytes -//! `mem_used_percents` | as above but as a percentage of total memory | Number | Percents -//! `buffers` | Buffers, similar to htop's blue bar | Number | Bytes -//! `buffers_percent` | as above but as a percentage of total memory | Number | Percents -//! `cached` | Cached memory (taking into account ZFS ARC cache), similar to htop's yellow bar | Number | Bytes -//! `cached_percent` | as above but as a percentage of total memory | Number | Percents -//! `swap_total` | Swap total | Number | Bytes -//! `swap_free` | Swap free | Number | Bytes -//! `swap_free_percents` | as above but as a percentage of total memory | Number | Percents -//! `swap_used` | Swap used | Number | Bytes -//! `swap_used_percents` | as above but as a percentage of total memory | Number | Percents -//! `zram_compressed` | Compressed zram memory usage | Number | Bytes -//! `zram_decompressed` | Decompressed zram memory usage | Number | Bytes -//! 'zram_comp_ratio' | Ratio of the decompressed/compressed zram memory | Number | - -//! `zswap_compressed` | Compressed zswap memory usage (>=Linux 5.19) | Number | Bytes -//! `zswap_decompressed` | Decompressed zswap memory usage (>=Linux 5.19) | Number | Bytes -//! `zswap_decompressed_percents` | as above but as a percentage of total zswap memory (>=Linux 5.19) | Number | Percents -//! 'zswap_comp_ratio' | Ratio of the decompressed/compressed zswap memory (>=Linux 5.19) | Number | - -//! -//! Action | Description | Default button -//! ----------------|-------------------------------------------|--------------- -//! `toggle_format` | Toggles between `format` and `format_alt` | Left -//! -//! # Examples -//! -//! ```toml -//! [[block]] -//! block = "memory" -//! format = " $icon $mem_used_percents.eng(w:1) " -//! format_alt = " $icon_swap $swap_free.eng(w:3,u:B,p:Mi)/$swap_total.eng(w:3,u:B,p:Mi)($swap_used_percents.eng(w:2)) " -//! interval = 30 -//! warning_mem = 70 -//! critical_mem = 90 -//! ``` -//! -//! Show swap and hide if it is zero: -//! -//! ```toml -//! [[block]] -//! block = "memory" -//! format = " $icon $swap_used.eng(range:1..) |" -//! ``` -//! -//! # Icons Used -//! - `memory_mem` -//! - `memory_swap` - -use std::cmp::min; -use std::str::FromStr as _; -use tokio::fs::{File, read_dir}; -use tokio::io::{AsyncBufReadExt as _, BufReader}; - -use super::prelude::*; -use crate::util::read_file; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - pub format_alt: Option, - #[default(5.into())] - pub interval: Seconds, - #[default(80.0)] - pub warning_mem: f64, - #[default(80.0)] - pub warning_swap: f64, - #[default(95.0)] - pub critical_mem: f64, - #[default(95.0)] - pub critical_swap: f64, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?; - - let mut format = config.format.with_default( - " $icon $mem_used.eng(prefix:Mi)/$mem_total.eng(prefix:Mi)($mem_used_percents.eng(w:2)) ", - )?; - let mut format_alt = match &config.format_alt { - Some(f) => Some(f.with_default("")?), - None => None, - }; - - let mut timer = config.interval.timer(); - - loop { - let mem_state = Memstate::new().await?; - - let mem_total = mem_state.mem_total as f64 * 1024.; - let mem_free = mem_state.mem_free as f64 * 1024.; - - // TODO: possibly remove this as it is confusing to have `mem_total_used` and `mem_used` - // htop and such only display equivalent of `mem_used` - let mem_total_used = mem_total - mem_free; - - // dev note: difference between avail and free: - // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 - // same logic as htop - let mem_avail = if mem_state.mem_available != 0 { - min(mem_state.mem_available, mem_state.mem_total) - } else { - mem_state.mem_free - } as f64 - * 1024.; - - // While zfs_arc_cache can be considered "available" memory, - // it can only free a maximum of (zfs_arc_cache - zfs_arc_min) amount. - // see https://github.com/htop-dev/htop/pull/1003 - let zfs_shrinkable_size = mem_state - .zfs_arc_cache - .saturating_sub(mem_state.zfs_arc_min) as f64; - let mem_avail = mem_avail + zfs_shrinkable_size; - - let pagecache = mem_state.pagecache as f64 * 1024.; - let reclaimable = mem_state.s_reclaimable as f64 * 1024.; - let shmem = mem_state.shmem as f64 * 1024.; - - // See https://lore.kernel.org/lkml/1455827801-13082-1-git-send-email-hannes@cmpxchg.org/ - let cached = pagecache + reclaimable - shmem + zfs_shrinkable_size; - - let buffers = mem_state.buffers as f64 * 1024.; - - // same logic as htop - let used_diff = mem_free + buffers + pagecache + reclaimable; - let mem_used = if mem_total >= used_diff { - mem_total - used_diff - } else { - mem_total - mem_free - }; - - // account for ZFS ARC cache - let mem_used = mem_used - zfs_shrinkable_size; - - let swap_total = mem_state.swap_total as f64 * 1024.; - let swap_free = mem_state.swap_free as f64 * 1024.; - let swap_cached = mem_state.swap_cached as f64 * 1024.; - let swap_used = swap_total - swap_free - swap_cached; - - // Zswap usage - let zswap_compressed = mem_state.zswap_compressed as f64 * 1024.; - let zswap_decompressed = mem_state.zswap_decompressed as f64 * 1024.; - - let zswap_comp_ratio = if zswap_compressed != 0.0 { - zswap_decompressed / zswap_compressed - } else { - 0.0 - }; - let zswap_decompressed_percents = if (swap_used + swap_cached) != 0.0 { - zswap_decompressed / (swap_used + swap_cached) * 100.0 - } else { - 0.0 - }; - - // Zram usage - let zram_compressed = mem_state.zram_compressed as f64; - let zram_decompressed = mem_state.zram_decompressed as f64; - - let zram_comp_ratio = if zram_compressed != 0.0 { - zram_decompressed / zram_compressed - } else { - 0.0 - }; - - let mut widget = Widget::new().with_format(format.clone()); - widget.set_values(map! { - "icon" => Value::icon("memory_mem"), - "icon_swap" => Value::icon("memory_swap"), - "mem_total" => Value::bytes(mem_total), - "mem_free" => Value::bytes(mem_free), - "mem_free_percents" => Value::percents(mem_free / mem_total * 100.), - "mem_total_used" => Value::bytes(mem_total_used), - "mem_total_used_percents" => Value::percents(mem_total_used / mem_total * 100.), - "mem_used" => Value::bytes(mem_used), - "mem_used_percents" => Value::percents(mem_used / mem_total * 100.), - "mem_avail" => Value::bytes(mem_avail), - "mem_avail_percents" => Value::percents(mem_avail / mem_total * 100.), - "swap_total" => Value::bytes(swap_total), - "swap_free" => Value::bytes(swap_free), - "swap_free_percents" => Value::percents(swap_free / swap_total * 100.), - "swap_used" => Value::bytes(swap_used), - "swap_used_percents" => Value::percents(swap_used / swap_total * 100.), - "buffers" => Value::bytes(buffers), - "buffers_percent" => Value::percents(buffers / mem_total * 100.), - "cached" => Value::bytes(cached), - "cached_percent" => Value::percents(cached / mem_total * 100.), - "zram_compressed" => Value::bytes(zram_compressed), - "zram_decompressed" => Value::bytes(zram_decompressed), - "zram_comp_ratio" => Value::number(zram_comp_ratio), - "zswap_compressed" => Value::bytes(zswap_compressed), - "zswap_decompressed" => Value::bytes(zswap_decompressed), - "zswap_decompressed_percents" => Value::percents(zswap_decompressed_percents), - "zswap_comp_ratio" => Value::number(zswap_comp_ratio), - }); - - let mem_state = match mem_used / mem_total * 100. { - x if x > config.critical_mem => State::Critical, - x if x > config.warning_mem => State::Warning, - _ => State::Idle, - }; - - let swap_state = match swap_used / swap_total * 100. { - x if x > config.critical_swap => State::Critical, - x if x > config.warning_swap => State::Warning, - _ => State::Idle, - }; - - widget.state = if mem_state == State::Critical || swap_state == State::Critical { - State::Critical - } else if mem_state == State::Warning || swap_state == State::Warning { - State::Warning - } else { - State::Idle - }; - - api.set_widget(widget)?; - - loop { - select! { - _ = timer.tick() => break, - _ = api.wait_for_update_request() => break, - Some(action) = actions.recv() => match action.as_ref() { - "toggle_format" => { - if let Some(ref mut format_alt) = format_alt { - std::mem::swap(format_alt, &mut format); - break; - } - } - _ => (), - } - } - } - } -} - -#[derive(Clone, Copy, Debug, Default)] -struct Memstate { - mem_total: u64, - mem_free: u64, - mem_available: u64, - buffers: u64, - pagecache: u64, - s_reclaimable: u64, - shmem: u64, - swap_total: u64, - swap_free: u64, - swap_cached: u64, - zram_compressed: u64, - zram_decompressed: u64, - zswap_compressed: u64, - zswap_decompressed: u64, - zfs_arc_cache: u64, - zfs_arc_min: u64, -} - -impl Memstate { - async fn new() -> Result { - // Reference: https://www.kernel.org/doc/Documentation/filesystems/proc.txt - let mut file = BufReader::new( - File::open("/proc/meminfo") - .await - .error("/proc/meminfo does not exist")?, - ); - - let mut mem_state = Memstate::default(); - let mut line = String::new(); - - while file - .read_line(&mut line) - .await - .error("failed to read /proc/meminfo")? - != 0 - { - let mut words = line.split_whitespace(); - - let name = match words.next() { - Some(name) => name, - None => { - line.clear(); - continue; - } - }; - let val = words - .next() - .and_then(|x| u64::from_str(x).ok()) - .error("failed to parse /proc/meminfo")?; - - match name { - "MemTotal:" => mem_state.mem_total = val, - "MemFree:" => mem_state.mem_free = val, - "MemAvailable:" => mem_state.mem_available = val, - "Buffers:" => mem_state.buffers = val, - "Cached:" => mem_state.pagecache = val, - "SReclaimable:" => mem_state.s_reclaimable = val, - "Shmem:" => mem_state.shmem = val, - "SwapTotal:" => mem_state.swap_total = val, - "SwapFree:" => mem_state.swap_free = val, - "SwapCached:" => mem_state.swap_cached = val, - "Zswap:" => mem_state.zswap_compressed = val, - "Zswapped:" => mem_state.zswap_decompressed = val, - _ => (), - } - - line.clear(); - } - - // For ZRAM - let mut entries = read_dir("/sys/block/") - .await - .error("Could not read /sys/block")?; - while let Some(entry) = entries - .next_entry() - .await - .error("Could not get next file /sys/block")? - { - let Ok(file_name) = entry.file_name().into_string() else { - continue; - }; - if !file_name.starts_with("zram") { - continue; - } - - let zram_file_path = entry.path().join("mm_stat"); - let Ok(file) = File::open(zram_file_path).await else { - continue; - }; - - let mut buf = BufReader::new(file); - let mut line = String::new(); - if buf.read_to_string(&mut line).await.is_err() { - continue; - } - - let mut values = line.split_whitespace().map(|s| s.parse::()); - if let Some(Ok(zram_swap_size)) = values.next() && let Some(Ok(zram_comp_size)) = values.next() - // zram initializes with small amount by default, return 0 then - && zram_swap_size >= 65_536 - { - mem_state.zram_decompressed += zram_swap_size; - mem_state.zram_compressed += zram_comp_size; - } - } - - // For ZFS - if let Ok(arcstats) = read_file("/proc/spl/kstat/zfs/arcstats").await { - let size_re = regex!(r"size\s+\d+\s+(\d+)"); - let size = &size_re - .captures(&arcstats) - .error("failed to find zfs_arc_cache size")?[1]; - mem_state.zfs_arc_cache = size.parse().error("failed to parse zfs_arc_cache size")?; - let c_min_re = regex!(r"c_min\s+\d+\s+(\d+)"); - let c_min = &c_min_re - .captures(&arcstats) - .error("failed to find zfs_arc_min size")?[1]; - mem_state.zfs_arc_min = c_min.parse().error("failed to parse zfs_arc_min size")?; - } - - Ok(mem_state) - } -} diff --git a/src/blocks/menu.rs b/src/blocks/menu.rs deleted file mode 100644 index 71dcf73f36..0000000000 --- a/src/blocks/menu.rs +++ /dev/null @@ -1,124 +0,0 @@ -//! A custom menu -//! -//! This block allows you to quickly run a custom shell command. Left-click on this block to -//! activate it, then scroll through configured items. Left-click on the item to run it and -//! optionally confirm your action by left-clicking again. Right-click any time to deactivate this -//! block. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `text` | Text that will be displayed when the block is inactive. | **Required** -//! `items` | A list of "items". See examples below. | **Required** -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "menu" -//! text = "\uf011" -//! [[block.items]] -//! display = " -> Sleep <-" -//! cmd = "systemctl suspend" -//! [[block.items]] -//! display = " -> Power Off <-" -//! cmd = "poweroff" -//! confirm_msg = "Are you sure you want to power off?" -//! [[block.items]] -//! display = " -> Reboot <-" -//! cmd = "reboot" -//! confirm_msg = "Are you sure you want to reboot?" -//! ``` - -use tokio::sync::mpsc::UnboundedReceiver; - -use super::{BlockAction, prelude::*}; -use crate::subprocess::spawn_shell; - -#[derive(Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct Config { - pub text: String, - pub items: Vec, -} - -#[derive(Deserialize, Debug, Clone)] -#[serde(deny_unknown_fields)] -pub struct Item { - pub display: String, - pub cmd: String, - #[serde(default)] - pub confirm_msg: Option, -} - -struct Block<'a> { - actions: UnboundedReceiver, - api: &'a CommonApi, - text: &'a str, - items: &'a [Item], -} - -impl Block<'_> { - async fn reset(&mut self) -> Result<()> { - self.set_text(self.text.to_owned()).await - } - - async fn set_text(&mut self, text: String) -> Result<()> { - self.api.set_widget(Widget::new().with_text(text)) - } - - async fn wait_for_click(&mut self, button: &str) -> Result<()> { - while self.actions.recv().await.error("channel closed")? != button {} - Ok(()) - } - - async fn run_menu(&mut self) -> Result> { - let mut index = 0; - loop { - self.set_text(self.items[index].display.clone()).await?; - match &*self.actions.recv().await.error("channel closed")? { - "_up" => index += 1, - "_down" => index += self.items.len() + 1, - "_left" => return Ok(Some(self.items[index].clone())), - "_right" => return Ok(None), - _ => (), - } - index %= self.items.len(); - } - } - - async fn confirm(&mut self, msg: String) -> Result { - self.set_text(msg).await?; - Ok(self.actions.recv().await.as_deref() == Some("_left")) - } -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - api.set_default_actions(&[ - (MouseButton::Left, None, "_left"), - (MouseButton::Right, None, "_right"), - (MouseButton::WheelUp, None, "_up"), - (MouseButton::WheelDown, None, "_down"), - ])?; - - let mut block = Block { - actions: api.get_actions()?, - api, - text: &config.text, - items: &config.items, - }; - - loop { - block.reset().await?; - block.wait_for_click("_left").await?; - if let Some(res) = block.run_menu().await? { - if let Some(msg) = res.confirm_msg - && !block.confirm(msg).await? - { - continue; - } - spawn_shell(&res.cmd).or_error(|| format!("Failed to run '{}'", res.cmd))?; - } - } -} diff --git a/src/blocks/music.rs b/src/blocks/music.rs deleted file mode 100644 index ae45184455..0000000000 --- a/src/blocks/music.rs +++ /dev/null @@ -1,702 +0,0 @@ -//! The current song title and artist -//! -//! Also provides buttons for play/pause, previous and next. -//! -//! Supports all music players that implement the [MediaPlayer2 Interface]. This includes: -//! -//! - Spotify -//! - VLC -//! - mpd (via [mpDris2](https://github.com/eonpatapon/mpDris2)) -//! -//! and many others. -//! -//! By default the block tracks all players available on the MPRIS bus. Right clicking on the block -//! will cycle it to the next player. You can pin the widget to a given player via the "player" -//! setting. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | \" $icon {$combo.str(max_w:25,rot_interval:0.5) $play \|}\" -//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None` -//! `player` | Name(s) of the music player(s) MPRIS interface. This can be either a music player name or an array of music player names. Run busctl \--user list \| grep \"org.mpris.MediaPlayer2.\" \| cut -d\' \' -f1 and the name is the part after "org.mpris.MediaPlayer2.". | `None` -//! `interface_name_exclude` | A list of regex patterns for player MPRIS interface names to ignore. | `["playerctld"]` -//! `separator` | String to insert between artist and title. | `" - "` -//! `seek_step_secs` | Positive number of seconds to seek forward/backward when scrolling on the bar. Does not need to be an integer. | `1` -//! `seek_forward_step_secs` | Positive number of seconds to seek forward when scrolling on the bar. Does not need to be an integer. | `seek_step_secs` -//! `seek_backward_step_secs` | Positive number of seconds to seek backward when scrolling on the bar. Does not need to be an integer. | `seek_step_secs` -//! `volume_step` | The percent volume level is increased/decreased for the selected audio device when scrolling. Capped automatically at 50. | `5` -//! -//! Note: All placeholders except `icon` can be absent. See the examples below to learn how to handle this. -//! -//! Placeholder | Value | Type -//! --------------|----------------|------ -//! `icon` | A static icon | Icon -//! `artist` | Current artist | Text -//! `title` | Current title | Text -//! `url` | Current song url | Text -//! `combo` | Resolves to "`$artist[sep]$title"`, `"$artist"`, `"$title"`, or `"$url"` depending on what information is available. `[sep]` is set by `separator` option. | Text -//! `player` | Name of the current player (taken from the last part of its MPRIS bus name) | Text -//! `avail` | Total number of players available to switch between | Number -//! `cur` | The current player index of the available players | Number -//! `play` | Play/Pause button | Clickable icon -//! `next` | Next button | Clickable icon -//! `prev` | Previous button | Clickable icon -//! `volume_icon` | Icon based on volume. Missing if unsupported. | Icon -//! `volume` | Current volume. Missing if muted or unsupported. | Number -//! -//! Widget | Placeholder -//! -----------------|------------- -//! `play_pause_btn` | `$play` -//! `next_btn` | `$next` -//! `prev_btn` | `$prev` -//! -//! Action | Default button -//! ----------------|------------------ -//! `play_pause` | Left on `play_pause_btn` -//! `next` | Left on `next_btn` -//! `prev` | Left on `prev_btn` -//! `next_player` | Right -//! `seek_forward` | Wheel Up -//! `seek_backward` | Wheel Down -//! `volume_up` | - -//! `volume_down` | - -//! `toggle_format` | Left -//! -//! # Examples -//! -//! Show the currently playing song on Spotify only, with play & next buttons and limit the width -//! to 20 characters: -//! -//! ```toml -//! [[block]] -//! block = "music" -//! format = " $icon {$combo.str(max_w:20) $play $next |}" -//! player = "spotify" -//! ``` -//! -//! Same thing for any compatible player, takes the first active on the bus, but ignores "mpd" or anything with "kdeconnect" in the name: -//! -//! ```toml -//! [[block]] -//! block = "music" -//! format = " $icon {$combo.str(max_w:20) $play $next |}" -//! interface_name_exclude = [".*kdeconnect.*", "mpd"] -//! ``` -//! -//! Same as above, but displays with rotating text -//! -//! ```toml -//! [[block]] -//! block = "music" -//! format = " $icon {$combo.str(max_w:20,rot_interval:0.5) $play $next |}" -//! interface_name_exclude = [".*kdeconnect.*", "mpd"] -//! ``` -//! -//! Click anywhere to play/pause, middle click to toggle format: -//! -//! ```toml -//! [[block]] -//! block = "music" -//! format = " format 1 " -//! format_alt = " format 2 " -//! [[block.click]] -//! button = "left" -//! action = "play_pause" -//! [[block.click]] -//! button = "middle" -//! widget = "." -//! action = "toggle_format" -//! ``` -//! -//! Scroll to change the player volume, use the forward and back buttons to seek: -//! -//! ```toml -//! [[block]] -//! block = "music" -//! format = " $icon $volume_icon $combo $play $next| " -//! seek_step_secs = 10 -//! [[block.click]] -//! button = "up" -//! action = "volume_up" -//! [[block.click]] -//! button = "down" -//! action = "volume_down" -//! [[block.click]] -//! button = "forward" -//! action = "seek_forward" -//! [[block.click]] -//! button = "back" -//! action = "seek_backward" -//! ``` -//! -//! # Icons Used -//! - `music` -//! - `music_next` -//! - `music_play` -//! - `music_prev` -//! - `volume_muted` -//! - `volume` (as a progression) -//! -//! [MediaPlayer2 Interface]: https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html - -use super::prelude::*; -use crate::wrappers::DisplaySlice; - -use regex::Regex; -use std::fmt; -use zbus::fdo::{DBusProxy, NameOwnerChanged, PropertiesChanged}; -use zbus::names::{OwnedBusName, OwnedUniqueName}; -use zbus::{MatchRule, MessageStream}; - -mod zbus_mpris; -mod zbus_playerctld; - -make_log_macro!(debug, "music"); - -const PLAY_PAUSE_BTN: &str = "play_pause_btn"; -const NEXT_BTN: &str = "next_btn"; -const PREV_BTN: &str = "prev_btn"; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - pub format_alt: Option, - pub player: PlayerName, - #[default(vec!["playerctld".into()])] - pub interface_name_exclude: Vec, - #[default(" - ".into())] - pub separator: String, - #[default(1.into())] - pub seek_step_secs: Seconds, - pub seek_forward_step_secs: Option>, - pub seek_backward_step_secs: Option>, - #[default(5.0)] - pub volume_step: f64, -} - -#[derive(Deserialize, Debug, Clone, SmartDefault)] -#[serde(untagged)] -pub enum PlayerName { - Single(String), - #[default] - Multiple(Vec), -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[ - (MouseButton::Left, Some(PLAY_PAUSE_BTN), "play_pause"), - (MouseButton::Left, Some(NEXT_BTN), "next"), - (MouseButton::Left, Some(PREV_BTN), "prev"), - (MouseButton::Right, None, "next_player"), - (MouseButton::WheelUp, None, "seek_forward"), - (MouseButton::WheelDown, None, "seek_backward"), - (MouseButton::Left, None, "toggle_format"), - ])?; - - let dbus_conn = new_dbus_connection().await?; - - let mut format = config - .format - .with_default(" $icon {$combo.str(max_w:25,rot_interval:0.5) $play |}")?; - let mut format_alt = match &config.format_alt { - Some(f) => Some(f.with_default("")?), - None => None, - }; - - let volume_step = config.volume_step.clamp(0.0, 50.0) / 100.0; - - let seek_forward_step = config - .seek_forward_step_secs - .unwrap_or(config.seek_step_secs) - .0 - .as_micros() as i64; - let seek_backward_step = -(config - .seek_backward_step_secs - .unwrap_or(config.seek_step_secs) - .0 - .as_micros() as i64); - - let new_btn = |icon: &str, instance: &'static str| -> Result { - Ok(Value::icon(icon.to_string()).with_instance(instance)) - }; - - let values = map! { - "icon" => Value::icon("music"), - "next" => new_btn("music_next", NEXT_BTN)?, - "prev" => new_btn("music_prev", PREV_BTN)?, - }; - - let preferred_players = match config.player.clone() { - PlayerName::Single(name) => vec![name], - PlayerName::Multiple(names) => names, - }; - let exclude_regex = config - .interface_name_exclude - .iter() - .map(|r| Regex::new(r)) - .collect::, _>>() - .error("Invalid regex")?; - - let playerctld_proxy = zbus_playerctld::PlayerctldProxy::new(&dbus_conn) - .await - .error("Failed to create PlayerctldProxy")?; - - let mut players = get_players(&dbus_conn, &preferred_players, &exclude_regex).await?; - let mut cur_player = None; - if let Ok(playerctld_players) = playerctld_proxy.player_names().await { - // If we can get the list of players from playerctld then we should - // take the first matching player (this is the most recently active player) - for playerctld_player in playerctld_players { - if let Some(pos) = players - .iter() - .position(|p| p.bus_name.as_str() == playerctld_player) - { - cur_player = Some(pos); - break; - } - } - } else { - // If we couldn't get the players from playerctld then fall back to walking over - // the players and select the first one found playing something, or the last one - // in the list (the most recently opened) - for (i, player) in players.iter().enumerate() { - cur_player = Some(i); - if player.status == Some(PlaybackStatus::Playing) { - break; - } - } - } - - let mut properties_stream = MessageStream::for_match_rule( - MatchRule::builder() - .msg_type(zbus::message::Type::Signal) - .interface("org.freedesktop.DBus.Properties") - .and_then(|x| x.member("PropertiesChanged")) - .and_then(|x| x.path("/org/mpris/MediaPlayer2")) - .unwrap() - .build(), - &dbus_conn, - None, - ) - .await - .error("Failed to add match rule")?; - - let mut name_owner_changed_stream = MessageStream::for_match_rule( - MatchRule::builder() - .msg_type(zbus::message::Type::Signal) - .interface("org.freedesktop.DBus") - .and_then(|x| x.member("NameOwnerChanged")) - .and_then(|x| x.arg0ns("org.mpris.MediaPlayer2")) - .unwrap() - .build(), - &dbus_conn, - None, - ) - .await - .error("Failed to add match rule")?; - - let mut active_player_change_end_stream = playerctld_proxy - .receive_active_player_change_end() - .await - .error("Failed to create ActivePlayerChangeEndStream")?; - - loop { - debug!("available players: {}", DisplaySlice(&players)); - - let avail = players.len(); - let player = cur_player.map(|c| players.get_mut(c).unwrap()); - match player { - Some(player) => { - let mut values = values.clone(); - values.insert("avail".into(), Value::number(avail)); - values.insert("cur".into(), Value::number(cur_player.unwrap() + 1)); - values.insert( - "player".into(), - Value::text( - extract_player_name(player.bus_name.as_str()) - .unwrap() - .into(), - ), - ); - let (state, play_icon) = match player.status { - Some(PlaybackStatus::Playing) => (State::Info, "music_pause"), - _ => (State::Idle, "music_play"), - }; - values.insert("play".into(), new_btn(play_icon, PLAY_PAUSE_BTN)?); - if let Some(url) = &player.metadata.url { - values.insert("url".into(), Value::text(url.clone())); - } - match ( - &player.metadata.title, - &player.metadata.artist, - &player.metadata.url, - ) { - (Some(t), None, _) => { - values.insert("combo".into(), Value::text(t.clone())); - values.insert("title".into(), Value::text(t.clone())); - } - (None, Some(a), _) => { - values.insert("combo".into(), Value::text(a.clone())); - values.insert("artist".into(), Value::text(a.clone())); - } - (Some(t), Some(a), _) => { - values.insert( - "combo".into(), - Value::text(format!("{t}{}{a}", config.separator)), - ); - values.insert("title".into(), Value::text(t.clone())); - values.insert("artist".into(), Value::text(a.clone())); - } - (None, None, Some(url)) => { - values.insert("combo".into(), Value::text(url.clone())); - } - _ => (), - } - if let Some(volume) = player.volume { - values.insert( - "volume_icon".into(), - Value::icon_progression("volume", volume), - ); - values.insert("volume".into(), Value::percents(volume * 100.0)); - } - let mut widget = Widget::new().with_format(format.clone()); - widget.set_values(values); - widget.state = state; - api.set_widget(widget)?; - } - None => { - let mut widget = Widget::new().with_format(format.clone()); - widget.set_values(map!("icon" => Value::icon("music"))); - api.set_widget(widget)?; - } - } - - loop { - select! { - Some(msg) = properties_stream.next() => { - let msg = msg.unwrap(); - let msg = PropertiesChanged::from_message(msg).unwrap(); - let args = msg.args().unwrap(); - let header = msg.message().header(); - let sender = header.sender().unwrap(); - if let Some((pos, player)) = players.iter_mut().enumerate().find(|p| &*p.1.owner == sender) { - let props = args.changed_properties; - if let Some(status) = props.get("PlaybackStatus") { - let status: &str = status.downcast_ref().unwrap(); - player.status = PlaybackStatus::from_str(status); - } - if let Some(metadata) = props.get("Metadata") { - player.metadata = - zbus_mpris::PlayerMetadata::try_from(metadata.try_to_owned().unwrap()).unwrap(); - } - if let Some(volume) = props.get("Volume") { - player.volume = Some(*volume.downcast_ref::<&f64>().unwrap()); - } - if player.status == Some(PlaybackStatus::Playing) - && ( - player.metadata.title.is_some() - || player.metadata.artist.is_some() - || player.metadata.url.is_some() - ) { - cur_player = Some(pos); - } - break; - } - } - Some(msg) = name_owner_changed_stream.next() => { - let msg = msg.unwrap(); - let msg = NameOwnerChanged::from_message(msg).unwrap(); - let args = msg.args().unwrap(); - match (args.old_owner.as_ref(), args.new_owner.as_ref()) { - (None, Some(new)) => { - debug!("new player {} owned by {new}", args.name); - if player_matches(args.name.as_str(), &preferred_players, &exclude_regex) { - match Player::new(&dbus_conn, args.name.to_owned().into(), new.to_owned().into()).await { - Ok(player) => players.push(player), - Err(e) => { - debug!("{e}"); - }, - } - } - } - (Some(old), None) => { - if let Some(pos) = players.iter().position(|p| &*p.owner == old) { - debug!("removed player {} owned by {old}", args.name); - players.remove(pos); - if let Some(cur) = cur_player { - if players.is_empty() { - cur_player = None; - } else if pos == cur { - cur_player = Some(0); - } else if pos < cur { - cur_player = Some(cur - 1); - } - } - } - } - _ => (), - } - break; - } - Some(msg) = active_player_change_end_stream.next() => { - let args = msg.args().unwrap(); - if let Some(pos) = players.iter().position(|p| p.bus_name == args.name){ - cur_player = Some(pos); - } - else{ - // We must have shifted to a player we wanted to skip (on the interface_name_exclude list). - // Let's shift again - if let Err(e) = playerctld_proxy.shift().await{ - debug!("{e}"); - } - } - break; - } - Some(action) = actions.recv() => { - if let Some(i) = cur_player { - let player = &players[i]; - match action.as_ref() { - "play_pause" => { - player.play_pause().await?; - } - "next" => { - player.next().await?; - } - "prev" => { - player.prev().await?; - } - "next_player" => { - cur_player = Some((i + 1) % players.len()); - if let Err(e) = playerctld_proxy.shift().await{ - debug!("{e}"); - } - break; - } - "seek_forward" => { - player.seek(seek_forward_step).await?; - } - "seek_backward" => { - player.seek(seek_backward_step).await?; - } - "volume_up" => { - player.set_volume(volume_step).await?; - } - "volume_down" => { - player.set_volume(-volume_step).await?; - } - "toggle_format" => { - if let Some(format_alt) = &mut format_alt { - std::mem::swap(format_alt, &mut format); - break; - } - } - _ => (), - } - } - } - } - } - } -} - -async fn get_players( - dbus_conn: &zbus::Connection, - preferred_players: &[String], - exclude_regex: &[Regex], -) -> Result> { - let proxy = DBusProxy::new(dbus_conn) - .await - .error("failed to create DBusProxy")?; - let names = proxy - .list_names() - .await - .error("failed to list dbus names")?; - let mut players = Vec::new(); - for name in names { - if player_matches(name.as_str(), preferred_players, exclude_regex) { - let owner = proxy.get_name_owner(name.as_ref()).await.unwrap(); - match Player::new(dbus_conn, name, owner).await { - Ok(player) => players.push(player), - Err(e) => { - debug!("{e}"); - } - } - } - } - Ok(players) -} - -#[derive(Debug)] -struct Player { - status: Option, - owner: OwnedUniqueName, - bus_name: OwnedBusName, - player_proxy: zbus_mpris::PlayerProxy<'static>, - metadata: zbus_mpris::PlayerMetadata, - volume: Option, -} - -impl Player { - async fn new( - dbus_conn: &zbus::Connection, - bus_name: OwnedBusName, - owner: OwnedUniqueName, - ) -> Result { - debug!("creating Player for {bus_name}"); - - let proxy = zbus_mpris::PlayerProxy::builder(dbus_conn) - .destination(bus_name.clone()) - .error("failed to set proxy destination")? - .build() - .await - .error("failed to open player proxy")?; - - // debug!("querying player info"); - // let (metadata, status, volume) = - // tokio::join!(proxy.metadata(), proxy.playback_status(), proxy.volume()); - debug!("querying player metadata"); - let metadata = proxy.metadata().await; - debug!("querying player status"); - let status = proxy.playback_status().await; - debug!("querying player volume"); - let volume = proxy.volume().await; - - let metadata = metadata.error("failed to obtain player metadata")?; - let status = status.error("failed to obtain player status")?; - - debug!("Player created"); - - Ok(Self { - status: PlaybackStatus::from_str(&status), - owner, - bus_name, - player_proxy: proxy, - metadata, - volume: volume.ok(), - }) - } - - async fn play_pause(&self) -> Result<()> { - self.player_proxy - .play_pause() - .await - .error("play_pause() failed") - } - - async fn prev(&self) -> Result<()> { - self.player_proxy.previous().await.error("prev() failed") - } - - async fn next(&self) -> Result<()> { - self.player_proxy.next().await.error("next() failed") - } - - async fn seek(&self, offset: i64) -> Result<()> { - match self.player_proxy.seek(offset).await { - Err(zbus::Error::MethodError(e, _, _)) - if e == "org.freedesktop.DBus.Error.NotSupported" => - { - // TODO show this error somehow - Ok(()) - } - other => dbg!(other).error("seek() failed"), - } - } - - async fn set_volume(&self, step_size: f64) -> Result<()> { - if let Some(volume) = self.volume { - self.player_proxy - .set_volume(volume + step_size) - .await - .error("set_volume() failed")?; - } - Ok(()) - } -} - -impl fmt::Display for Player { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - extract_player_name(&self.bus_name).unwrap().fmt(f) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum PlaybackStatus { - Playing, - Paused, - Stopped, -} - -impl PlaybackStatus { - fn from_str(s: &str) -> Option { - match s { - "Paused" => Some(Self::Paused), - "Playing" => Some(Self::Playing), - "Stopped" => Some(Self::Stopped), - _ => None, - } - } -} - -fn extract_player_name(full_name: &str) -> Option<&str> { - const NAME_PREFIX: &str = "org.mpris.MediaPlayer2."; - full_name - .starts_with(NAME_PREFIX) - .then(|| &full_name[NAME_PREFIX.len()..]) -} - -fn player_matches(full_name: &str, preferred_players: &[String], exclude_regex: &[Regex]) -> bool { - let name = match extract_player_name(full_name) { - Some(name) => name, - None => return false, - }; - - exclude_regex.iter().all(|r| !r.is_match(name)) - && (preferred_players.is_empty() - || preferred_players.iter().any(|p| name.starts_with(&**p))) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn extract_player_name_test() { - assert_eq!( - extract_player_name("org.mpris.MediaPlayer2.firefox.instance852"), - Some("firefox.instance852") - ); - assert_eq!( - extract_player_name("not.org.mpris.MediaPlayer2.firefox.instance852"), - None, - ); - assert_eq!( - extract_player_name("org.mpris.MediaPlayer3.firefox.instance852"), - None, - ); - } - - #[test] - fn player_matches_test() { - let exclude = vec![Regex::new("mpd").unwrap(), Regex::new("firefox.*").unwrap()]; - assert!(player_matches( - "org.mpris.MediaPlayer2.playerctld", - &[], - &exclude - )); - assert!(!player_matches( - "org.mpris.MediaPlayer2.playerctld", - &["spotify".into()], - &exclude - )); - assert!(!player_matches( - "org.mpris.MediaPlayer2.firefox.instance852", - &[], - &exclude - )); - } -} diff --git a/src/blocks/music/zbus_mpris.rs b/src/blocks/music/zbus_mpris.rs deleted file mode 100644 index 2cf11c3266..0000000000 --- a/src/blocks/music/zbus_mpris.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! # DBus interface proxies for: `org.mpris.MediaPlayer2.Player` -//! -//! This code was generated by `zbus-xmlgen` `1.0.0` from DBus introspection data. -//! Source: `11`. -//! -//! You may prefer to adapt it, instead of using it verbatim. -//! -//! More information can be found in the -//! [Writing a client proxy](https://zeenix.pages.freedesktop.org/zbus/client.html) -//! section of the zbus documentation. -//! -//! This DBus object implements -//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html), -//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used: -//! -//! * [`zbus::fdo::PropertiesProxy`] -//! * [`zbus::fdo::IntrospectableProxy`] -//! * [`zbus::fdo::PeerProxy`] -//! -//! …consequently `zbus-xmlgen` did not generate code for the above interfaces. - -use std::collections::HashMap; -use zbus::zvariant::{self, ObjectPath, OwnedValue, Value}; - -#[derive(Debug, Clone)] -pub struct PlayerMetadata { - pub title: Option, - pub artist: Option, - pub url: Option, -} - -impl TryFrom for PlayerMetadata { - type Error = as TryFrom>::Error; - - fn try_from(value: OwnedValue) -> Result { - let map = HashMap::::try_from(value)?; - - let val_to_string = |val: &Value| { - val.downcast_ref::<&str>() - .ok() - .and_then(|val| (!val.is_empty()).then(|| val.to_string())) - }; - - let title = map.get("xesam:title").and_then(|val| val_to_string(val)); - - let artists = map - .get("xesam:artist") - .and_then(|val| val.downcast_ref::<&zvariant::Array>().ok()) - .map(|val| val.inner()); - let artist = artists.and_then(|val| val.first()).and_then(val_to_string); - - let url = map.get("xesam:url").and_then(|val| val_to_string(val)); - - Ok(Self { title, artist, url }) - } -} - -#[zbus::proxy( - interface = "org.mpris.MediaPlayer2.Player", - default_path = "/org/mpris/MediaPlayer2" -)] -pub(super) trait Player { - /// Next method - fn next(&self) -> zbus::Result<()>; - - /// OpenUri method - fn open_uri(&self, uri: &str) -> zbus::Result<()>; - - /// Pause method - fn pause(&self) -> zbus::Result<()>; - - /// Play method - fn play(&self) -> zbus::Result<()>; - - /// PlayPause method - fn play_pause(&self) -> zbus::Result<()>; - - /// Previous method - fn previous(&self) -> zbus::Result<()>; - - /// Seek method - fn seek(&self, offset: i64) -> zbus::Result<()>; - - /// SetPosition method - fn set_position(&self, track_id: &ObjectPath<'_>, position: i64) -> zbus::Result<()>; - - /// Stop method - fn stop(&self) -> zbus::Result<()>; - - /// Seeked signal - #[zbus(signal)] - fn seeked(&self, position: i64) -> zbus::Result<()>; - - /// CanControl property - #[zbus(property)] - fn can_control(&self) -> zbus::Result; - - /// CanGoNext property - #[zbus(property)] - fn can_go_next(&self) -> zbus::Result; - - /// CanGoPrevious property - #[zbus(property)] - fn can_go_previous(&self) -> zbus::Result; - - /// CanPause property - #[zbus(property)] - fn can_pause(&self) -> zbus::Result; - - /// CanPlay property - #[zbus(property)] - fn can_play(&self) -> zbus::Result; - - /// CanSeek property - #[zbus(property)] - fn can_seek(&self) -> zbus::Result; - - /// MaximumRate property - #[zbus(property)] - fn maximum_rate(&self) -> zbus::Result; - - /// Metadata property - #[zbus(property)] - fn metadata(&self) -> zbus::Result; - - /// MinimumRate property - #[zbus(property)] - fn minimum_rate(&self) -> zbus::Result; - - /// PlaybackStatus property - #[zbus(property)] - fn playback_status(&self) -> zbus::Result; - - /// Position property - #[zbus(property)] - fn position(&self) -> zbus::Result; - - /// Rate property - #[zbus(property)] - fn rate(&self) -> zbus::Result; - #[zbus(property)] - fn set_rate(&self, value: f64) -> zbus::Result<()>; - - /// Volume property - #[zbus(property)] - fn volume(&self) -> zbus::Result; - #[zbus(property)] - fn set_volume(&self, value: f64) -> zbus::Result<()>; -} diff --git a/src/blocks/music/zbus_playerctld.rs b/src/blocks/music/zbus_playerctld.rs deleted file mode 100644 index 83214d4e30..0000000000 --- a/src/blocks/music/zbus_playerctld.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! # DBus interface proxies for: `com.github.altdesktop.playerctld` -//! -//! This code was generated by `zbus-xmlgen` `3.1.0` from DBus introspection data. -//! Source: `Service 'org.mpris.MediaPlayer2.playerctld' on session bus`. -//! -//! You may prefer to adapt it, instead of using it verbatim. -//! -//! More information can be found in the -//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html) -//! section of the zbus documentation. -//! -//! This DBus object implements -//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html), -//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used: -//! -//! * [`zbus::fdo::PropertiesProxy`] -//! * [`zbus::fdo::IntrospectableProxy`] -//! * [`zbus::fdo::PeerProxy`] -//! -//! …consequently `zbus-xmlgen` did not generate code for the above interfaces. - -#[zbus::proxy( - interface = "com.github.altdesktop.playerctld", - default_service = "org.mpris.MediaPlayer2.playerctld", - default_path = "/org/mpris/MediaPlayer2" -)] -pub(super) trait Playerctld { - /// Shift method - fn shift(&self) -> zbus::Result; - - /// Unshift method - fn unshift(&self) -> zbus::Result; - - /// ActivePlayerChangeBegin signal - #[zbus(signal)] - fn active_player_change_begin(&self, name: &str) -> zbus::Result<()>; - - /// ActivePlayerChangeEnd signal - #[zbus(signal)] - fn active_player_change_end(&self, name: &str) -> zbus::Result<()>; - - /// PlayerNames property - #[zbus(property)] - fn player_names(&self) -> zbus::Result>; -} diff --git a/src/blocks/net.rs b/src/blocks/net.rs deleted file mode 100644 index d641d53032..0000000000 --- a/src/blocks/net.rs +++ /dev/null @@ -1,220 +0,0 @@ -//! Network information -//! -//! This block uses `sysfs` and `netlink` and thus does not require any external dependencies. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `device` | Network interface to monitor (as specified in `/sys/class/net/`). Supports regex. | If not set, device will be automatically selected every `interval` -//! `interval` | Update interval in seconds | `2` -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon ^icon_net_down $speed_down.eng(prefix:K) ^icon_net_up $speed_up.eng(prefix:K) "` -//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None` -//! `inactive_format` | Same as `format` but for when the interface is inactive | `" $icon Down "` -//! `missing_format` | Same as `format` but for when the device is missing | `" × "` -//! -//! Action | Description | Default button -//! ----------------|-------------------------------------------|--------------- -//! `toggle_format` | Toggles between `format` and `format_alt` | Left -//! -//! Placeholder | Value | Type | Unit -//! ------------------|-----------------------------|--------|--------------- -//! `icon` | Icon based on device's type | Icon | - -//! `speed_down` | Download speed | Number | Bytes per second -//! `speed_up` | Upload speed | Number | Bytes per second -//! `graph_down` | Download speed graph | Text | - -//! `graph_up` | Upload speed graph | Text | - -//! `device` | The name of device | Text | - -//! `ssid` | Netfork SSID (WiFi only) | Text | - -//! `frequency` | WiFi frequency | Number | Hz -//! `signal_strength` | WiFi signal | Number | % -//! `bitrate` | WiFi connection bitrate | Number | Bits per second -//! `ip` | IPv4 address of the iface | Text | - -//! `ipv6` | IPv6 address of the iface | Text | - -//! `nameserver` | Nameserver | Text | - -//! -//! # Example -//! -//! Display WiFi info if available -//! -//! ```toml -//! [[block]] -//! block = "net" -//! format = " $icon {$signal_strength $ssid $frequency|Wired connection} via $device " -//! ``` -//! -//! Display exact device -//! -//! ```toml -//! [[block]] -//! block = "net" -//! device = "^wlo0$" -//! ``` -//! -//! # Icons Used -//! - `net_loopback` -//! - `net_vpn` -//! - `net_wired` -//! - `net_wireless` (as a progression) -//! - `net_up` -//! - `net_down` - -use super::prelude::*; -use crate::netlink::NetDevice; -use crate::util; -use itertools::Itertools as _; -use regex::Regex; -use std::time::Instant; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub device: Option, - #[default(2.into())] - pub interval: Seconds, - pub format: FormatConfig, - pub format_alt: Option, - pub inactive_format: FormatConfig, - pub missing_format: FormatConfig, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?; - - let mut format = config.format.with_default( - " $icon ^icon_net_down $speed_down.eng(prefix:K) ^icon_net_up $speed_up.eng(prefix:K) ", - )?; - let missing_format = config.missing_format.with_default(" × ")?; - let inactive_format = config.inactive_format.with_default(" $icon Down ")?; - let mut format_alt = match &config.format_alt { - Some(f) => Some(f.with_default("")?), - None => None, - }; - - let mut timer = config.interval.timer(); - - let device_re = config - .device - .as_deref() - .map(Regex::new) - .transpose() - .error("Failed to parse device regex")?; - - // Stats - let mut stats = None; - let mut stats_timer = Instant::now(); - let mut tx_hist = [0f64; 8]; - let mut rx_hist = [0f64; 8]; - - loop { - match NetDevice::new(device_re.as_ref()).await? { - None => { - api.set_widget(Widget::new().with_format(missing_format.clone()))?; - } - Some(device) => { - let mut widget = Widget::new(); - - if device.is_up() { - widget.set_format(format.clone()); - } else { - widget.set_format(inactive_format.clone()); - } - - let mut speed_down: f64 = 0.0; - let mut speed_up: f64 = 0.0; - - // Calculate speed - match (stats, device.iface.stats) { - // No previous stats available - (None, new_stats) => stats = new_stats, - // No new stats available - (Some(_), None) => stats = None, - // All stats available - (Some(old_stats), Some(new_stats)) => { - let diff = new_stats - old_stats; - let elapsed = stats_timer.elapsed().as_secs_f64(); - stats_timer = Instant::now(); - speed_down = diff.rx_bytes as f64 / elapsed; - speed_up = diff.tx_bytes as f64 / elapsed; - stats = Some(new_stats); - } - } - push_to_hist(&mut rx_hist, speed_down); - push_to_hist(&mut tx_hist, speed_up); - - let icon = if let Some(signal) = device.signal() { - Value::icon_progression(device.icon, signal / 100.0) - } else { - Value::icon(device.icon) - }; - - widget.set_values(map! { - "icon" => icon, - "speed_down" => Value::bytes(speed_down), - "speed_up" => Value::bytes(speed_up), - "graph_down" => Value::text(util::format_bar_graph(&rx_hist)), - "graph_up" => Value::text(util::format_bar_graph(&tx_hist)), - [if let Some(v) = device.ip] "ip" => Value::text(v.to_string()), - [if let Some(v) = device.ipv6] "ipv6" => Value::text(v.to_string()), - [if let Some(v) = device.ssid()] "ssid" => Value::text(v), - [if let Some(v) = device.frequency()] "frequency" => Value::hertz(v), - [if let Some(v) = device.bitrate()] "bitrate" => Value::bits(v), - [if let Some(v) = device.signal()] "signal_strength" => Value::percents(v), - [if !device.nameservers.is_empty()] "nameserver" => Value::text( - device - .nameservers - .into_iter() - .map(|s| s.to_string()) - .join(" "), - ), - "device" => Value::text(device.iface.name), - }); - - api.set_widget(widget)?; - } - } - - loop { - select! { - _ = timer.tick() => break, - _ = api.wait_for_update_request() => break, - Some(action) = actions.recv() => match action.as_ref() { - "toggle_format" => { - if let Some(format_alt) = &mut format_alt { - std::mem::swap(format_alt, &mut format); - break; - } - } - _ => () - } - } - } - } -} - -fn push_to_hist(hist: &mut [T], elem: T) { - hist[0] = elem; - hist.rotate_left(1); -} - -#[cfg(test)] -mod tests { - use super::push_to_hist; - - #[test] - fn test_push_to_hist() { - let mut hist = [0; 4]; - assert_eq!(&hist, &[0, 0, 0, 0]); - push_to_hist(&mut hist, 1); - assert_eq!(&hist, &[0, 0, 0, 1]); - push_to_hist(&mut hist, 3); - assert_eq!(&hist, &[0, 0, 1, 3]); - push_to_hist(&mut hist, 0); - assert_eq!(&hist, &[0, 1, 3, 0]); - push_to_hist(&mut hist, 10); - assert_eq!(&hist, &[1, 3, 0, 10]); - push_to_hist(&mut hist, 2); - assert_eq!(&hist, &[3, 0, 10, 2]); - } -} diff --git a/src/blocks/notify.rs b/src/blocks/notify.rs deleted file mode 100644 index b9f7513813..0000000000 --- a/src/blocks/notify.rs +++ /dev/null @@ -1,303 +0,0 @@ -//! Display and toggle the state of notifications daemon -//! -//! Left-clicking on this block will enable/disable notifications. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `driver` | Which notifications daemon is running. Available drivers are: `"dunst"` and `"swaync"` | `"dunst"` -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon "` -//! -//! Placeholder | Value | Type | Unit -//! ------------------------------------------|--------------------------------------------|--------|----- -//! `icon` | Icon based on notification's state | Icon | - -//! `notification_count`[^dunst_version_note] | The number of notification (omitted if 0) | Number | - -//! `paused` | Present only if notifications are disabled | Flag | - -//! -//! Action | Default button -//! ----------------|--------------- -//! `toggle_paused` | Left -//! `show` | - -//! -//! # Examples -//! -//! How to use `paused` flag -//! -//! ```toml -//! [[block]] -//! block = "notify" -//! format = " $icon {$paused{Off}|On} " -//! ``` -//! How to use `notification_count` -//! -//! ```toml -//! [[block]] -//! block = "notify" -//! format = " $icon {($notification_count.eng(w:1)) |}" -//! ``` -//! How to remap actions -//! -//! ```toml -//! [[block]] -//! block = "notify" -//! driver = "swaync" -//! [[block.click]] -//! button = "left" -//! action = "show" -//! [[block.click]] -//! button = "right" -//! action = "toggle_paused" -//! ``` -//! -//! # Icons Used -//! - `bell` -//! - `bell-slash` -//! -//! [^dunst_version_note]: when using `notification_count` with the `dunst` driver use dunst > 1.9.0 - -use super::prelude::*; -use tokio::{join, try_join}; -use zbus::proxy::PropertyStream; - -const ICON_ON: &str = "bell"; -const ICON_OFF: &str = "bell-slash"; - -#[derive(Deserialize, Debug, Default)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub driver: DriverType, - pub format: FormatConfig, -} - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(rename_all = "lowercase")] -pub enum DriverType { - #[default] - Dunst, - SwayNC, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Left, None, "toggle_paused")])?; - - let format = config.format.with_default(" $icon ")?; - - let mut driver: Box = match config.driver { - DriverType::Dunst => Box::new(DunstDriver::new().await?), - DriverType::SwayNC => Box::new(SwayNCDriver::new().await?), - }; - - loop { - let (is_paused, notification_count) = - try_join!(driver.is_paused(), driver.notification_count())?; - - let mut widget = Widget::new().with_format(format.clone()); - widget.set_values(map!( - "icon" => Value::icon(if is_paused { ICON_OFF } else { ICON_ON }), - [if notification_count != 0] "notification_count" => Value::number(notification_count), - [if is_paused] "paused" => Value::flag(), - )); - widget.state = if notification_count == 0 { - State::Idle - } else { - State::Info - }; - api.set_widget(widget)?; - - select! { - x = driver.wait_for_change() => x?, - Some(action) = actions.recv() => match action.as_ref() { - "toggle_paused" => { - driver.set_paused(!is_paused).await?; - } - "show" => { - driver.notification_show().await?; - } - _ => (), - } - } - } -} - -#[async_trait] -trait Driver { - async fn is_paused(&self) -> Result; - async fn set_paused(&self, paused: bool) -> Result<()>; - async fn notification_show(&self) -> Result<()>; - async fn notification_count(&self) -> Result; - async fn wait_for_change(&mut self) -> Result<()>; -} - -struct DunstDriver { - proxy: DunstDbusProxy<'static>, - paused_changes: PropertyStream<'static, bool>, - displayed_length_changes: PropertyStream<'static, u32>, - waiting_length_changes: PropertyStream<'static, u32>, -} - -impl DunstDriver { - async fn new() -> Result { - let dbus_conn = new_dbus_connection().await?; - let proxy = DunstDbusProxy::new(&dbus_conn) - .await - .error("Failed to create DunstDbusProxy")?; - Ok(Self { - paused_changes: proxy.receive_paused_changed().await, - displayed_length_changes: proxy.receive_displayed_length_changed().await, - waiting_length_changes: proxy.receive_waiting_length_changed().await, - proxy, - }) - } -} - -#[async_trait] -impl Driver for DunstDriver { - async fn is_paused(&self) -> Result { - self.proxy.paused().await.error("Failed to get 'paused'") - } - - async fn set_paused(&self, paused: bool) -> Result<()> { - self.proxy - .set_paused(paused) - .await - .error("Failed to set 'paused'") - } - - async fn notification_show(&self) -> Result<()> { - self.proxy - .notification_show() - .await - .error("Could not call 'NotificationShow'") - } - - async fn notification_count(&self) -> Result { - let (displayed_length, waiting_length) = - try_join!(self.proxy.displayed_length(), self.proxy.waiting_length()) - .error("Failed to get property")?; - - Ok(displayed_length + waiting_length) - } - - async fn wait_for_change(&mut self) -> Result<()> { - select! { - _ = self.paused_changes.next() => {} - _ = self.displayed_length_changes.next() => {} - _ = self.waiting_length_changes.next() => {} - } - Ok(()) - } -} - -#[zbus::proxy( - interface = "org.dunstproject.cmd0", - default_service = "org.freedesktop.Notifications", - default_path = "/org/freedesktop/Notifications" -)] - -trait DunstDbus { - #[zbus(property, name = "paused")] - fn paused(&self) -> zbus::Result; - #[zbus(property, name = "paused")] - fn set_paused(&self, value: bool) -> zbus::Result<()>; - fn notification_show(&self) -> zbus::Result<()>; - #[zbus(property, name = "displayedLength")] - fn displayed_length(&self) -> zbus::Result; - #[zbus(property, name = "waitingLength")] - fn waiting_length(&self) -> zbus::Result; -} -struct SwayNCDriver { - proxy: SwayNCDbusProxy<'static>, - changes: SubscribeStream, - changes_v2: SubscribeV2Stream, -} - -impl SwayNCDriver { - async fn new() -> Result { - let dbus_conn = new_dbus_connection().await?; - let proxy = SwayNCDbusProxy::new(&dbus_conn) - .await - .error("Failed to create SwayNCDbusProxy")?; - Ok(Self { - changes: proxy - .receive_subscribe() - .await - .error("Failed to create SubscribeStream")?, - changes_v2: proxy - .receive_subscribe_v2() - .await - .error("Failed to create SubscribeV2Stream")?, - proxy, - }) - } -} - -#[async_trait] -impl Driver for SwayNCDriver { - async fn is_paused(&self) -> Result { - let (is_dnd, is_inhibited) = join!(self.proxy.get_dnd(), self.proxy.is_inhibited()); - - is_dnd - .error("Failed to call 'GetDnd'") - .map(|is_dnd| is_dnd || is_inhibited.unwrap_or_default()) - } - - async fn set_paused(&self, paused: bool) -> Result<()> { - if paused { - self.proxy.set_dnd(paused).await - } else { - join!(self.proxy.set_dnd(paused), self.proxy.clear_inhibitors()).0 - } - .error("Failed to call 'SetDnd'") - } - - async fn notification_show(&self) -> Result<()> { - self.proxy - .toggle_visibility() - .await - .error("Failed to call 'ToggleVisibility'") - } - - async fn notification_count(&self) -> Result { - self.proxy - .notification_count() - .await - .error("Failed to call 'NotificationCount'") - } - - async fn wait_for_change(&mut self) -> Result<()> { - select! { - _ = self.changes.next() => (), - _ = self.changes_v2.next() => (), - } - Ok(()) - } -} - -#[zbus::proxy( - interface = "org.erikreider.swaync.cc", - default_service = "org.freedesktop.Notifications", - default_path = "/org/erikreider/swaync/cc" -)] -trait SwayNCDbus { - fn get_dnd(&self) -> zbus::Result; - fn set_dnd(&self, value: bool) -> zbus::Result<()>; - fn toggle_visibility(&self) -> zbus::Result<()>; - fn notification_count(&self) -> zbus::Result; - #[zbus(signal)] - fn subscribe(&self, count: u32, dnd: bool, cc_open: bool) -> zbus::Result<()>; - - // inhibitors were introduced in v0.8.0 - fn is_inhibited(&self) -> zbus::Result; - fn clear_inhibitors(&self) -> zbus::Result; - // subscribe_v2 replaced subscribe in v0.8.0 - #[zbus(signal)] - fn subscribe_v2( - &self, - count: u32, - dnd: bool, - cc_open: bool, - inhibited: bool, - ) -> zbus::Result<()>; -} diff --git a/src/blocks/notmuch.rs b/src/blocks/notmuch.rs deleted file mode 100644 index f7ce2ddc20..0000000000 --- a/src/blocks/notmuch.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! Count of notmuch messages -//! -//! This block queries a notmuch database and displays the count of messages. -//! -//! The simplest configuration will return the total count of messages in the notmuch database stored at $HOME/.mail -//! -//! Note that you need to enable `notmuch` feature to use this block: -//! ```sh -//! cargo build --release --features notmuch -//! ``` -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $count "` -//! `maildir` | Path to the directory containing the notmuch database. Supports path expansions e.g. `~`. | `~/.mail` -//! `query` | Query to run on the database. | `""` -//! `threshold_critical` | Mail count that triggers `critical` state. | `99999` -//! `threshold_warning` | Mail count that triggers `warning` state. | `99999` -//! `threshold_good` | Mail count that triggers `good` state. | `99999` -//! `threshold_info` | Mail count that triggers `info` state. | `99999` -//! `interval` | Update interval in seconds. | `10` -//! -//! Placeholder | Value | Type | Unit -//! ------------|--------------------------------------------|--------|----- -//! `icon` | A static icon | Icon | - -//! `count` | Number of messages for the query | Number | - -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "notmuch" -//! query = "tag:alert and not tag:trash" -//! threshold_warning = 1 -//! threshold_critical = 10 -//! [[block.click]] -//! button = "left" -//! update = true -//! ``` -//! -//! # Icons Used -//! - `mail` - -use super::prelude::*; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - #[default(10.into())] - pub interval: Seconds, - #[default("~/.mail".into())] - pub maildir: ShellString, - pub query: String, - #[default(u32::MAX)] - pub threshold_warning: u32, - #[default(u32::MAX)] - pub threshold_critical: u32, - #[default(u32::MAX)] - pub threshold_info: u32, - #[default(u32::MAX)] - pub threshold_good: u32, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config.format.with_default(" $icon $count ")?; - - let db = config.maildir.expand()?; - let mut timer = config.interval.timer(); - - loop { - // TODO: spawn_blocking? - let count = run_query(&db, &config.query).error("Failed to get count")?; - - let mut widget = Widget::new().with_format(format.clone()); - - widget.set_values(map! { - "icon" => Value::icon("mail"), - "count" => Value::number(count) - }); - - widget.state = if count >= config.threshold_critical { - State::Critical - } else if count >= config.threshold_warning { - State::Warning - } else if count >= config.threshold_good { - State::Good - } else if count >= config.threshold_info { - State::Info - } else { - State::Idle - }; - - api.set_widget(widget)?; - - tokio::select! { - _ = timer.tick() => (), - _ = api.wait_for_update_request() => (), - } - } -} - -fn run_query(db_path: &str, query_string: &str) -> std::result::Result { - let db = notmuch::Database::open_with_config( - Some(db_path), - notmuch::DatabaseMode::ReadOnly, - None::<&str>, - None, - )?; - let query = db.create_query(query_string)?; - query.count_messages() -} diff --git a/src/blocks/nvidia_gpu.rs b/src/blocks/nvidia_gpu.rs deleted file mode 100644 index 879a46cc04..0000000000 --- a/src/blocks/nvidia_gpu.rs +++ /dev/null @@ -1,265 +0,0 @@ -//! Display the stats of your NVidia GPU -//! -//! By default `show_temperature` shows the used memory. Clicking the left mouse on the -//! "temperature" part of the block will alternate it between showing used or total available -//! memory. -//! -//! Clicking the left mouse button on the "fan speed" part of the block will cause it to enter into -//! a fan speed setting mode. In this mode you can scroll the mouse wheel over the block to change -//! the fan speeds, and left click to exit the mode. -//! -//! Requires `nvidia-smi` for displaying info and `nvidia_settings` for setting fan speed. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `gpu_id` | GPU id in system. | `0` -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $utilization $memory $temperature "` -//! `interval` | Update interval in seconds. | `1` -//! `idle` | Maximum temperature, below which state is set to idle | `50` -//! `good` | Maximum temperature, below which state is set to good | `70` -//! `info` | Maximum temperature, below which state is set to info | `75` -//! `warning` | Maximum temperature, below which state is set to warning | `80` -//! -//! Placeholder | Type | Unit -//! --------------|--------|--------------- -//! `icon` | Icon | - -//! `name` | Text | - -//! `utilization` | Number | Percents -//! `memory` | Number | Bytes -//! `temperature` | Number | Degrees -//! `fan_speed` | Number | Percents -//! `clocks` | Number | Hertz -//! `power` | Number | Watts -//! -//! Widget | Placeholder -//! ----------|------------- -//! `mem_btn` | `$memory` -//! `fan_btn` | `$fan_speed` -//! -//! Action | Default button -//! ------------------------|---------------- -//! `toggle_mem_total` | Left on `mem_btn` -//! `toggle_fan_controlled` | Left on `fan_btn` -//! `fan_speed_up` | Wheel Up on `fan_btn` -//! `fan_speed_down` | Wheel Down on `fan_btn` -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "nvidia_gpu" -//! interval = 1 -//! format = " $icon GT 1030 $utilization $temperature $clocks " -//! ``` -//! -//! # Icons Used -//! - `gpu` -//! -//! # TODO -//! - Provide a `mappings` option similar to `keyboard_layout`'s to map GPU names to labels? - -use std::process::Stdio; -use std::str::FromStr; - -use tokio::io::{BufReader, Lines}; -use tokio::process::Command; - -const MEM_BTN: &str = "mem_btn"; -const FAN_BTN: &str = "fan_btn"; -const QUERY: &str = "--query-gpu=name,memory.total,utilization.gpu,memory.used,temperature.gpu,fan.speed,clocks.current.graphics,power.draw,"; -const FORMAT: &str = "--format=csv,noheader,nounits"; - -use super::prelude::*; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - #[default(1.into())] - pub interval: Seconds, - #[default(0)] - pub gpu_id: u64, - #[default(50)] - pub idle: u32, - #[default(70)] - pub good: u32, - #[default(75)] - pub info: u32, - #[default(80)] - pub warning: u32, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[ - (MouseButton::Left, Some(MEM_BTN), "toggle_mem_total"), - (MouseButton::Left, Some(FAN_BTN), "toggle_fan_controlled"), - (MouseButton::WheelUp, Some(FAN_BTN), "fan_speed_up"), - (MouseButton::WheelDown, Some(FAN_BTN), "fan_speed_down"), - ])?; - - let format = config - .format - .with_default(" $icon $utilization $memory $temperature ")?; - - // Run `nvidia-smi` command - let mut child = Command::new("nvidia-smi") - .args([ - "-l", - &config.interval.seconds().to_string(), - "-i", - &config.gpu_id.to_string(), - QUERY, - FORMAT, - ]) - .stdout(Stdio::piped()) - .kill_on_drop(true) - .spawn() - .error("Failed to execute nvidia-smi")?; - let mut reader = BufReader::new(child.stdout.take().unwrap()).lines(); - - // Read the initial info - let mut info = GpuInfo::from_reader(&mut reader).await?; - let mut show_mem_total = false; - let mut fan_controlled = false; - - loop { - let mut widget = Widget::new().with_format(format.clone()); - - widget.state = match info.temperature { - t if t <= config.idle => State::Idle, - t if t <= config.good => State::Good, - t if t <= config.info => State::Info, - t if t <= config.warning => State::Warning, - _ => State::Critical, - }; - - widget.set_values(map! { - "icon" => Value::icon("gpu"), - "name" => Value::text(info.name.clone()), - "utilization" => Value::percents(info.utilization), - "memory" => Value::bytes(if show_mem_total {info.mem_total} else {info.mem_used}).with_instance(MEM_BTN), - "temperature" => Value::degrees(info.temperature), - "fan_speed" => Value::percents(info.fan_speed).with_instance(FAN_BTN).underline(fan_controlled).italic(fan_controlled), - "clocks" => Value::hertz(info.clocks), - "power" => Value::watts(info.power_draw), - }); - - api.set_widget(widget)?; - - select! { - new_info = GpuInfo::from_reader(&mut reader) => { - info = new_info?; - } - code = child.wait() => { - let code = code.error("failed to check nvidia-smi exit code")?; - return Err(Error::new(format!("nvidia-smi exited with code {code}"))); - } - Some(action) = actions.recv() => match action.as_ref() { - "toggle_mem_total" => { - show_mem_total = !show_mem_total; - } - "toggle_fan_controlled" => { - fan_controlled = !fan_controlled; - set_fan_speed(config.gpu_id, fan_controlled.then_some(info.fan_speed)).await?; - } - "fan_speed_up" if fan_controlled && info.fan_speed < 100 => { - info.fan_speed += 1; - set_fan_speed(config.gpu_id, Some(info.fan_speed)).await?; - } - "fan_speed_down" if fan_controlled && info.fan_speed > 0 => { - info.fan_speed -= 1; - set_fan_speed(config.gpu_id, Some(info.fan_speed)).await?; - } - _ => (), - } - } - } -} - -#[derive(Debug)] -struct GpuInfo { - name: String, - mem_total: f64, // bytes - mem_used: f64, // bytes - utilization: f64, // percents - temperature: u32, // degrees - fan_speed: u32, // percents - clocks: f64, // hertz - power_draw: f64, // watts -} - -impl GpuInfo { - /// Read a line from provided reader and parse it - /// - /// # Cancel safety - /// - /// This method should be cancellation safe, because it has only one `.await` and it is on `next_line`, which is cancellation safe. - async fn from_reader(reader: &mut Lines) -> Result { - const ERR_MSG: &str = "failed to read from nvidia-smi"; - reader - .next_line() - .await - .error(ERR_MSG)? - .error(ERR_MSG)? - .parse::() - .error("failed to parse nvidia-smi output") - } -} - -impl FromStr for GpuInfo { - type Err = Error; - - fn from_str(s: &str) -> Result { - macro_rules! parse { - ($s:ident -> $($part:ident : $t:ident $(* $mul:expr)?),*) => {{ - let mut parts = $s.trim().split(", "); - let info = GpuInfo { - $( - $part: { - let $part = parts - .next() - .error(concat!("missing property: ", stringify!($part)))? - .parse::<$t>() - .unwrap_or_default(); - $(let $part = $part * $mul;)? - $part - }, - )* - }; - Ok(info) - }} - } - // `memory` and `clocks` are initially in MB and MHz, so we have to multiply them by 1_000_000 - parse!(s -> name: String, mem_total: f64 * 1e6, utilization: f64, mem_used: f64 * 1e6, temperature: u32, fan_speed: u32, clocks: f64 * 1e6, power_draw: f64) - } -} - -async fn set_fan_speed(id: u64, speed: Option) -> Result<()> { - const ERR_MSG: &str = "Failed to execute nvidia-settings"; - let mut cmd = Command::new("nvidia-settings"); - if let Some(speed) = speed { - cmd.args([ - "-a", - &format!("[gpu:{id}]/GPUFanControlState=1"), - "-a", - &format!("[fan:{id}]/GPUTargetFanSpeed={speed}"), - ]); - } else { - cmd.args(["-a", &format!("[gpu:{id}]/GPUFanControlState=0")]); - } - if cmd - .spawn() - .error(ERR_MSG)? - .wait() - .await - .error(ERR_MSG)? - .success() - { - Ok(()) - } else { - Err(Error::new(ERR_MSG)) - } -} diff --git a/src/blocks/packages.rs b/src/blocks/packages.rs deleted file mode 100644 index ee2d89dfed..0000000000 --- a/src/blocks/packages.rs +++ /dev/null @@ -1,378 +0,0 @@ -//! Pending updates for different package manager like apt, pacman, etc. -//! -//! Currently these package managers are available: -//! - `apt` for Debian/Ubuntu based system -//! - `pacman` for Arch based system -//! - `aur` for Arch based system -//! - `dnf` for Fedora based system -//! - `xbps` for Void Linux -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `interval` | Update interval in seconds. | `600` -//! `package_manager` | Package manager to check for updates | Automatically derived from format templates, but can be used to influence the `$total` value -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $total.eng(w:1) "` -//! `format_singular` | Same as `format`, but for when exactly one update is available. | `" $icon $total.eng(w:1) "` -//! `format_up_to_date` | Same as `format`, but for when no updates are available. | `" $icon $total.eng(w:1) "` -//! `warning_updates_regex` | Display block as warning if updates matching regex are available. | `None` -//! `critical_updates_regex` | Display block as critical if updates matching regex are available. | `None` -//! `ignore_updates_regex` | Doesn't include updates matching regex in the count. | `None` -//! `ignore_phased_updates` | Doesn't include potentially held back phased updates in the count. (For Debian/Ubuntu based system) | `false` -//! `aur_command` | AUR command to check available updates, which outputs in the same format as pacman. e.g. `yay -Qua` (For Arch based system) | Required if `$aur` are used -//! -//! Placeholder | Value | Type | Unit -//! -------------|----------------------------------------------------------------------------------|--------|----- -//! `icon` | A static icon | Icon | - -//! `apt` | Number of updates available in Debian/Ubuntu based system | Number | - -//! `pacman` | Number of updates available in Arch based system | Number | - -//! `aur` | Number of updates available in Arch based system | Number | - -//! `dnf` | Number of updates available in Fedora based system | Number | - -//! `xbps` | Number of updates available in Void Linux | Number | - -//! `total` | Number of updates available in all package manager listed | Number | - -//! -//! # Apt -//! -//! Behind the scenes this uses `apt`, and in order to run it without root privileges i3status-rust will create its own package database in `/tmp/i3rs-apt/` which may take up several MB or more. If you have a custom apt config then this block may not work as expected - in that case please open an issue. -//! -//! Tip: You can grab the list of available updates using `APT_CONFIG=/tmp/i3rs-apt/apt.conf apt list --upgradable` -//! -//! # Pacman -//! -//! Requires fakeroot to be installed (only required for pacman). -//! -//! Tip: You can grab the list of available updates using `fakeroot pacman -Qu --dbpath /tmp/checkup-db-i3statusrs-$USER/`. -//! If you have the `CHECKUPDATES_DB` env var set on your system then substitute that dir instead. -//! -//! Note: `pikaur` may hang the whole block if there is no internet connectivity [reference](https://github.com/actionless/pikaur/issues/595). In that case, try a different AUR helper. -//! -//! ### Pacman hook -//! -//! Tip: On Arch Linux you can setup a `pacman` hook to signal i3status-rs to update after packages -//! have been upgraded, so you won't have stale info in your pacman block. -//! -//! In the block configuration, set `signal = 1` (or other number if `1` is being used by some -//! other block): -//! -//! ```toml -//! [[block]] -//! block = "packages" -//! signal = 1 -//! ``` -//! -//! Create `/etc/pacman.d/hooks/i3status-rust.hook` with the below contents: -//! -//! ```ini -//! [Trigger] -//! Operation = Upgrade -//! Type = Package -//! Target = * -//! -//! [Action] -//! When = PostTransaction -//! Exec = /usr/bin/pkill -SIGRTMIN+1 i3status-rs -//! ``` -//! -//! # Example -//! -//! Apt only config -//! -//! ```toml -//! [[block]] -//! block = "packages" -//! interval = 1800 -//! package_manager = ["apt"] -//! format = " $icon $apt updates available" -//! format_singular = " $icon One update available " -//! format_up_to_date = " $icon system up to date " -//! [[block.click]] -//! # shows dmenu with cached available updates. Any dmenu alternative should also work. -//! button = "left" -//! cmd = "APT_CONFIG=/tmp/i3rs-apt/apt.conf apt list --upgradable | tail -n +2 | rofi -dmenu" -//! [[block.click]] -//! # Updates the block on right click -//! button = "right" -//! update = true -//! ``` -//! -//! Pacman only config: -//! -//! ```toml -//! [[block]] -//! block = "packages" -//! package_manager = ["pacman"] -//! interval = 600 -//! format = " $icon $pacman updates available " -//! format_singular = " $icon $pacman update available " -//! format_up_to_date = " $icon system up to date " -//! [[block.click]] -//! # pop-up a menu showing the available updates. Replace wofi with your favourite menu command. -//! button = "left" -//! cmd = "fakeroot pacman -Qu --dbpath /tmp/checkup-db-i3statusrs-$USER/ | wofi --show dmenu" -//! [[block.click]] -//! # Updates the block on right click -//! button = "right" -//! update = true -//! ``` -//! -//! Pacman and AUR helper config: -//! -//! ```toml -//! [[block]] -//! block = "packages" -//! package_manager = ["pacman", "aur"] -//! interval = 600 -//! error_interval = 300 -//! format = " $icon $pacman + $aur = $total updates available " -//! format_singular = " $icon $total update available " -//! format_up_to_date = " $icon system up to date " -//! # aur_command should output available updates to stdout (ie behave as echo -ne "update\n") -//! aur_command = "yay -Qua" -//! ``` -//! -//! -//! Dnf only config: -//! -//! ```toml -//! [[block]] -//! block = "packages" -//! package_manager = ["dnf"] -//! interval = 1800 -//! format = " $icon $dnf.eng(w:1) updates available " -//! format_singular = " $icon One update available " -//! format_up_to_date = " $icon system up to date " -//! [[block.click]] -//! # shows dmenu with cached available updates. Any dmenu alternative should also work. -//! button = "left" -//! cmd = "dnf list -q --upgrades | tail -n +2 | rofi -dmenu" -//! ``` -//! -//! -//! Xbps only config: -//! -//! ```toml -//! [[block]] -//! block = "packages" -//! package_manager = ["xbps"] -//! interval = 1800 -//! format = " $icon $xbps.eng(w:1) updates available " -//! format_singular = " $icon One update available " -//! format_up_to_date = " $icon system up to date " -//! [[block.click]] -//! # shows dmenu with available updates. Any dmenu alternative should also work. -//! button = "left" -//! cmd = "xbps-install -Mun | dmenu -l 10" -//! ``` -//! -//! Multiple package managers config: -//! -//! Update the list of pending updates every thirty minutes (1800 seconds): -//! -//! ```toml -//! [[block]] -//! block = "packages" -//! package_manager = ["apt", "pacman", "aur", "dnf", "xbps"] -//! interval = 1800 -//! format = " $icon $apt + $pacman + $aur + $dnf + $xbps = $total updates available " -//! format_singular = " $icon One update available " -//! format_up_to_date = " $icon system up to date " -//! # If a linux update is available, but no ZFS package, it won't be possible to -//! # actually perform a system upgrade, so we show a warning. -//! warning_updates_regex = "(linux|linux-lts|linux-zen)" -//! # If ZFS is available, we know that we can and should do an upgrade, so we show -//! # the status as critical. -//! critical_updates_regex = "(zfs|zfs-lts)" -//! ``` -//! -//! # Icons Used -//! -//! - `update` - -pub mod apt; -use apt::Apt; - -pub mod pacman; -use pacman::{Aur, Pacman}; - -pub mod dnf; -use dnf::Dnf; - -pub mod xbps; -use xbps::Xbps; - -use regex::Regex; - -use super::prelude::*; - -#[derive(Deserialize, Debug, SmartDefault, Clone)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - #[default(600.into())] - pub interval: Seconds, - pub package_manager: Vec, - pub format: FormatConfig, - pub format_singular: FormatConfig, - pub format_up_to_date: FormatConfig, - pub warning_updates_regex: Option, - pub critical_updates_regex: Option, - pub ignore_updates_regex: Option, - pub ignore_phased_updates: bool, - pub aur_command: Option, -} - -#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum PackageManager { - Apt, - Pacman, - Aur, - Dnf, - Xbps, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut config: Config = config.clone(); - - let format = config.format.with_default(" $icon $total.eng(w:1) ")?; - let format_singular = config - .format_singular - .with_default(" $icon $total.eng(w:1) ")?; - let format_up_to_date = config - .format_up_to_date - .with_default(" $icon $total.eng(w:1) ")?; - - // If user provide package manager in any of the formats then consider that also - macro_rules! any_format_contains { - ($name:expr) => { - format.contains_key($name) - || format_singular.contains_key($name) - || format_up_to_date.contains_key($name) - }; - } - - let apt = any_format_contains!("apt"); - let aur = any_format_contains!("aur"); - let pacman = any_format_contains!("pacman"); - let dnf = any_format_contains!("dnf"); - let xbps = any_format_contains!("xbps"); - - if !config.package_manager.contains(&PackageManager::Apt) && apt { - config.package_manager.push(PackageManager::Apt); - } - if !config.package_manager.contains(&PackageManager::Pacman) && pacman { - config.package_manager.push(PackageManager::Pacman); - } - if !config.package_manager.contains(&PackageManager::Aur) && aur { - config.package_manager.push(PackageManager::Aur); - } - if !config.package_manager.contains(&PackageManager::Dnf) && dnf { - config.package_manager.push(PackageManager::Dnf); - } - if !config.package_manager.contains(&PackageManager::Xbps) && xbps { - config.package_manager.push(PackageManager::Xbps); - } - - let warning_updates_regex = config - .warning_updates_regex - .as_deref() - .map(Regex::new) - .transpose() - .error("invalid warning updates regex")?; - let critical_updates_regex = config - .critical_updates_regex - .as_deref() - .map(Regex::new) - .transpose() - .error("invalid critical updates regex")?; - let ignore_updates_regex = config - .ignore_updates_regex - .as_deref() - .map(Regex::new) - .transpose() - .error("invalid ignore updates regex")?; - - let mut package_manager_vec: Vec> = Vec::new(); - - for &package_manager in config.package_manager.iter() { - package_manager_vec.push(match package_manager { - PackageManager::Apt => Box::new(Apt::new(config.ignore_phased_updates).await?), - PackageManager::Pacman => Box::new(Pacman::new().await?), - PackageManager::Aur => Box::new(Aur::new( - config.aur_command.clone().error("aur_command is not set")?, - )), - PackageManager::Dnf => Box::new(Dnf::new()), - PackageManager::Xbps => Box::new(Xbps::new()), - }); - } - - loop { - let mut package_manager_map: HashMap, Value> = HashMap::new(); - - let mut critical = false; - let mut warning = false; - let mut total_count = 0; - - // Iterate over the all package manager listed in Config - for package_manager in &package_manager_vec { - let mut updates = package_manager.get_updates_list().await?; - if let Some(regex) = ignore_updates_regex.clone() { - updates.retain(|u| !regex.is_match(u)); - } - - let updates_count = updates.len(); - - package_manager_map.insert(package_manager.name(), Value::number(updates_count)); - total_count += updates_count; - - warning |= warning_updates_regex - .as_ref() - .is_some_and(|regex| has_matching_update(&updates, regex)); - critical |= critical_updates_regex - .as_ref() - .is_some_and(|regex| has_matching_update(&updates, regex)); - } - - let mut widget = Widget::new(); - - package_manager_map.insert("icon".into(), Value::icon("update")); - package_manager_map.insert("total".into(), Value::number(total_count)); - - widget.set_format(match total_count { - 0 => format_up_to_date.clone(), - 1 => format_singular.clone(), - _ => format.clone(), - }); - widget.set_values(package_manager_map); - - widget.state = match total_count { - 0 => State::Idle, - _ => { - if critical { - State::Critical - } else if warning { - State::Warning - } else { - State::Info - } - } - }; - api.set_widget(widget)?; - - select! { - _ = sleep(config.interval.0) => (), - _ = api.wait_for_update_request() => (), - } - } -} - -#[async_trait] -pub trait Backend { - fn name(&self) -> Cow<'static, str>; - - async fn get_updates_list(&self) -> Result>; -} - -pub fn has_matching_update(updates: &[String], regex: &Regex) -> bool { - updates.iter().any(|line| regex.is_match(line)) -} diff --git a/src/blocks/packages/apt.rs b/src/blocks/packages/apt.rs deleted file mode 100644 index 5d9a5e7e7b..0000000000 --- a/src/blocks/packages/apt.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::env; -use std::process::Stdio; - -use tokio::fs::{File, create_dir_all}; -use tokio::process::Command; - -use super::*; - -#[derive(Default)] -pub struct Apt { - pub(super) config_file: String, - pub(super) ignore_phased_updates: bool, -} - -impl Apt { - pub async fn new(ignore_phased_updates: bool) -> Result { - let mut apt = Apt { - config_file: String::new(), - ignore_phased_updates, - }; - - apt.setup().await?; - - Ok(apt) - } - - async fn is_phased_update(&self, package_line: &str) -> Result { - let package_name_regex = regex!(r#"(.*)/.*"#); - let package_name = &package_name_regex - .captures(package_line) - .error("Couldn't find package name")?[1]; - - let output = String::from_utf8( - Command::new("apt-cache") - .args(["-c", &self.config_file, "policy", package_name]) - .output() - .await - .error("Problem running apt-cache command")? - .stdout, - ) - .error("Problem capturing apt-cache command output")?; - - let phased_regex = regex!(r".*\(phased (\d+)%\).*"); - Ok(match phased_regex.captures(&output) { - Some(matches) => &matches[1] != "100", - None => false, - }) - } - - async fn setup(&mut self) -> Result<()> { - let mut cache_dir = env::temp_dir(); - cache_dir.push("i3rs-apt"); - if !cache_dir.exists() { - create_dir_all(&cache_dir) - .await - .error("Failed to create temp dir")?; - } - - let apt_config = format!( - "Dir::State \"{}\";\n - Dir::State::lists \"lists\";\n - Dir::Cache \"{}\";\n - Dir::Cache::srcpkgcache \"srcpkgcache.bin\";\n - Dir::Cache::pkgcache \"pkgcache.bin\";", - cache_dir.display(), - cache_dir.display(), - ); - - let mut config_file = cache_dir; - config_file.push("apt.conf"); - let config_file = config_file.to_str().unwrap(); - - self.config_file = config_file.to_string(); - - let mut file = File::create(&config_file) - .await - .error("Failed to create config file")?; - file.write_all(apt_config.as_bytes()) - .await - .error("Failed to write to config file")?; - - Ok(()) - } -} - -#[async_trait] -impl Backend for Apt { - fn name(&self) -> Cow<'static, str> { - "apt".into() - } - - async fn get_updates_list(&self) -> Result> { - Command::new("apt") - .env("APT_CONFIG", &self.config_file) - .args(["update"]) - .stdout(Stdio::null()) - .stdin(Stdio::null()) - .spawn() - .error("Failed to run `apt update`")? - .wait() - .await - .error("Failed to run `apt update`")?; - let stdout = Command::new("apt") - .env("LANG", "C") - .env("APT_CONFIG", &self.config_file) - .args(["list", "--upgradable"]) - .output() - .await - .error("Problem running apt command")? - .stdout; - - let updates = String::from_utf8(stdout).error("apt produced non-UTF8 output")?; - let mut updates_list: Vec = Vec::new(); - - for update in updates.lines().filter(|line| line.contains("[upgradable")) { - if !self.ignore_phased_updates || !self.is_phased_update(update).await? { - updates_list.push(update.to_string()); - } - } - - Ok(updates_list) - } -} diff --git a/src/blocks/packages/dnf.rs b/src/blocks/packages/dnf.rs deleted file mode 100644 index 85a9f73089..0000000000 --- a/src/blocks/packages/dnf.rs +++ /dev/null @@ -1,37 +0,0 @@ -use tokio::process::Command; - -use super::super::packages::*; - -#[derive(Default)] -pub struct Dnf; - -impl Dnf { - pub fn new() -> Self { - Default::default() - } -} - -#[async_trait] -impl Backend for Dnf { - fn name(&self) -> Cow<'static, str> { - "dnf".into() - } - - async fn get_updates_list(&self) -> Result> { - let stdout = Command::new("sh") - .env("LC_LANG", "C") - .args(["-c", "dnf check-update -q --skip-broken"]) - .output() - .await - .error("Failed to run dnf check-update")? - .stdout; - let updates = String::from_utf8(stdout).error("dnf produced non-UTF8 output")?; - let updates: Vec = updates - .lines() - .filter(|line| line.len() > 1) - .map(|lines| lines.to_string()) - .collect(); - - Ok(updates) - } -} diff --git a/src/blocks/packages/pacman.rs b/src/blocks/packages/pacman.rs deleted file mode 100644 index fc4da0935f..0000000000 --- a/src/blocks/packages/pacman.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::env; -use std::path::PathBuf; -use std::process::Stdio; - -use tokio::fs::{create_dir_all, symlink}; -use tokio::process::Command; - -use super::*; -use crate::util::has_command; - -make_log_macro!(debug, "pacman"); - -pub static PACMAN_UPDATES_DB: LazyLock = LazyLock::new(|| { - let path = match env::var_os("CHECKUPDATES_DB") { - Some(val) => val.into(), - None => { - let mut path = env::temp_dir(); - let user = env::var("USER"); - path.push(format!( - "checkup-db-i3statusrs-{}", - user.as_deref().unwrap_or("no-user") - )); - path - } - }; - debug!("Using {} as updates DB path", path.display()); - path -}); - -pub static PACMAN_DB: LazyLock = LazyLock::new(|| { - let path = env::var_os("DBPath") - .map(Into::into) - .unwrap_or_else(|| PathBuf::from("/var/lib/pacman/")); - debug!("Using {} as pacman DB path", path.display()); - path -}); - -pub struct Pacman; - -pub struct Aur { - aur_command: String, -} - -impl Pacman { - pub async fn new() -> Result { - check_fakeroot_command_exists().await?; - - Ok(Self) - } -} - -impl Aur { - pub fn new(aur_command: String) -> Self { - Aur { aur_command } - } -} - -#[async_trait] -impl Backend for Pacman { - fn name(&self) -> Cow<'static, str> { - "pacman".into() - } - - async fn get_updates_list(&self) -> Result> { - // Create the determined `checkup-db` path recursively - create_dir_all(&*PACMAN_UPDATES_DB).await.or_error(|| { - format!( - "Failed to create checkup-db directory at '{}'", - PACMAN_UPDATES_DB.display() - ) - })?; - - // Create symlink to local cache in `checkup-db` if required - let local_cache = PACMAN_UPDATES_DB.join("local"); - if !local_cache.exists() { - symlink(PACMAN_DB.join("local"), local_cache) - .await - .error("Failed to created required symlink")?; - } - - // Update database - let status = Command::new("fakeroot") - .env("LC_ALL", "C") - .args([ - "--".as_ref(), - "pacman".as_ref(), - "-Sy".as_ref(), - "--dbpath".as_ref(), - PACMAN_UPDATES_DB.as_os_str(), - "--logfile".as_ref(), - "/dev/null".as_ref(), - ]) - .stdout(Stdio::null()) - .status() - .await - .error("Failed to run command")?; - if !status.success() { - debug!("{}", status); - return Err(Error::new("pacman -Sy exited with non zero exit status")); - } - - let stdout = Command::new("fakeroot") - .env("LC_ALL", "C") - .args([ - "--".as_ref(), - "pacman".as_ref(), - "-Qu".as_ref(), - "--dbpath".as_ref(), - PACMAN_UPDATES_DB.as_os_str(), - ]) - .output() - .await - .error("There was a problem running the pacman commands")? - .stdout; - - let updates = String::from_utf8(stdout).error("Pacman produced non-UTF8 output")?; - - let updates = updates - .lines() - .filter(|line| !line.contains("[ignored]")) - .map(|line| line.to_string()) - .collect(); - - Ok(updates) - } -} - -#[async_trait] -impl Backend for Aur { - fn name(&self) -> Cow<'static, str> { - "aur".into() - } - - async fn get_updates_list(&self) -> Result> { - let stdout = Command::new("sh") - .args(["-c", &self.aur_command]) - .output() - .await - .or_error(|| format!("aur command: {} failed", self.aur_command))? - .stdout; - let updates = String::from_utf8(stdout) - .error("There was a problem while converting the aur command output to a string")?; - - let updates = updates - .lines() - .filter(|line| !line.contains("[ignored]")) - .map(|line| line.to_string()) - .collect(); - - Ok(updates) - } -} - -async fn check_fakeroot_command_exists() -> Result<()> { - if !has_command("fakeroot").await? { - Err(Error::new("fakeroot not found")) - } else { - Ok(()) - } -} diff --git a/src/blocks/packages/xbps.rs b/src/blocks/packages/xbps.rs deleted file mode 100644 index 843110b5b5..0000000000 --- a/src/blocks/packages/xbps.rs +++ /dev/null @@ -1,38 +0,0 @@ -use tokio::process::Command; - -use super::*; - -#[derive(Default)] -pub struct Xbps; - -impl Xbps { - pub fn new() -> Self { - Default::default() - } -} - -#[async_trait] -impl Backend for Xbps { - fn name(&self) -> Cow<'static, str> { - "xbps".into() - } - - async fn get_updates_list(&self) -> Result> { - let stdout = Command::new("xbps-install") - .env("LC_LANG", "C") - .args(["-M", "-u", "-n"]) - .output() - .await - .error("Problem running xbps-install command")? - .stdout; - - let updates = String::from_utf8(stdout).expect("xbps-install produced non-UTF8 output"); - let updates_list: Vec = updates - .lines() - .filter(|line| line.len() > 1) - .map(|line| line.to_string()) - .collect(); - - Ok(updates_list) - } -} diff --git a/src/blocks/pomodoro.rs b/src/blocks/pomodoro.rs deleted file mode 100644 index 514fa82d71..0000000000 --- a/src/blocks/pomodoro.rs +++ /dev/null @@ -1,352 +0,0 @@ -//! A [pomodoro timer](https://en.wikipedia.org/wiki/Pomodoro_Technique) -//! -//! # Technique -//! -//! There are six steps in the original technique: -//! 1) Decide on the task to be done. -//! 2) Set the pomodoro timer (traditionally to 25 minutes). -//! 3) Work on the task. -//! 4) End work when the timer rings and put a checkmark on a piece of paper. -//! 5) If you have fewer than four checkmarks, take a short break (3–5 minutes) and then return to step 2. -//! 6) After four pomodoros, take a longer break (15–30 minutes), reset your checkmark count to zero, then go to step 1. -//! -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | The format used when in idle, prompt, or notify states | \" $icon{ $message\|} \" -//! `pomodoro_format` | The format used when the pomodoro is running or paused | \" $icon $status_icon{ $completed_pomodoros.tally()\|} $time_remaining.duration(hms:true) \" -//! `break_format` |The format used when the pomodoro is during the break | \" $icon $status_icon Break: $time_remaining.duration(hms:true) \" -//! `message` | Message when timer expires | `"Pomodoro over! Take a break!"` -//! `break_message` | Message when break is over | `"Break over! Time to work!"` -//! `notify_cmd` | A shell command to run as a notifier. `{msg}` will be substituted with either `message` or `break_message`. | `None` -//! `blocking_cmd` | Is `notify_cmd` blocking? If it is, then pomodoro block will wait until the command finishes before proceeding. Otherwise, you will have to click on the block in order to proceed. | `false` -//! -//! Placeholder | Value | Type | Supported by -//! ----------------------|-----------------------------------------------|----------|-------------- -//! `icon` | A static icon | Icon | All formats -//! `status_icon` | An icon that reflects the pomodoro state | Icon | `pomodoro_format`, `break_format` -//! `message` | Current message | Text | `format` -//! `time_remaining` | How much time is left (minutes) | Duration | `pomodoro_format`, `break_format` -//! `completed_pomodoros` | The number of completed pomodoros | Number | `pomodoro_format` -//! -//! # Example -//! -//! Use `swaynag` as a notifier: -//! -//! ```toml -//! [[block]] -//! block = "pomodoro" -//! notify_cmd = "swaynag -m '{msg}'" -//! blocking_cmd = true -//! ``` -//! -//! Use `notify-send` as a notifier: -//! -//! ```toml -//! [[block]] -//! block = "pomodoro" -//! notify_cmd = "notify-send '{msg}'" -//! blocking_cmd = false -//! ``` -//! -//! # Icons Used -//! - `pomodoro` -//! - `pomodoro_started` -//! - `pomodoro_stopped` -//! - `pomodoro_paused` -//! - `pomodoro_break` - -use num_traits::{Num, NumAssignOps, SaturatingSub}; -use tokio::sync::mpsc; - -use super::prelude::*; -use crate::{ - formatting::Format, - subprocess::{spawn_shell, spawn_shell_sync}, -}; -use std::time::Instant; - -make_log_macro!(debug, "pomodoro"); - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - pub pomodoro_format: FormatConfig, - pub break_format: FormatConfig, - #[default("Pomodoro over! Take a break!".into())] - pub message: String, - #[default("Break over! Time to work!".into())] - pub break_message: String, - pub notify_cmd: Option, - pub blocking_cmd: bool, -} - -enum PomodoroState { - Idle, - Prompt, - Notify, - Break, - PomodoroRunning, - PomodoroPaused, -} - -impl PomodoroState { - fn get_block_state(&self) -> State { - use PomodoroState::*; - match self { - Idle | PomodoroPaused => State::Idle, - Prompt => State::Warning, - Notify => State::Good, - Break | PomodoroRunning => State::Info, - } - } - - fn get_status_icon(&self) -> Option<&'static str> { - use PomodoroState::*; - match self { - Idle => Some("pomodoro_stopped"), - Break => Some("pomodoro_break"), - PomodoroRunning => Some("pomodoro_started"), - PomodoroPaused => Some("pomodoro_paused"), - _ => None, - } - } -} - -struct Block<'a> { - widget: Widget, - actions: mpsc::UnboundedReceiver, - api: &'a CommonApi, - config: &'a Config, - state: PomodoroState, - format: Format, - pomodoro_format: Format, - break_format: Format, -} - -impl Block<'_> { - async fn set_text(&mut self, additional_values: Values) -> Result<()> { - let mut values = map! { - "icon" => Value::icon("pomodoro"), - }; - values.extend(additional_values); - - if let Some(icon) = self.state.get_status_icon() { - values.insert("status_icon".into(), Value::icon(icon)); - } - self.widget.set_format(match self.state { - PomodoroState::Idle | PomodoroState::Prompt | PomodoroState::Notify => { - self.format.clone() - } - PomodoroState::Break => self.break_format.clone(), - PomodoroState::PomodoroRunning | PomodoroState::PomodoroPaused => { - self.pomodoro_format.clone() - } - }); - self.widget.state = self.state.get_block_state(); - debug!("{:?}", values); - self.widget.set_values(values); - self.api.set_widget(self.widget.clone()) - } - - async fn wait_for_click(&mut self, button: &str) -> Result<()> { - while self.actions.recv().await.error("channel closed")? != button {} - Ok(()) - } - - async fn read_params(&mut self) -> Result> { - self.state = PomodoroState::Prompt; - let task_len = match self.read_number(25, "Task length:").await? { - Some(task_len) => task_len, - None => return Ok(None), - }; - let break_len = match self.read_number(5, "Break length:").await? { - Some(break_len) => break_len, - None => return Ok(None), - }; - let pomodoros = match self.read_number(4, "Pomodoros:").await? { - Some(pomodoros) => pomodoros, - None => return Ok(None), - }; - Ok(Some(( - Duration::from_secs(task_len * 60), - Duration::from_secs(break_len * 60), - pomodoros, - ))) - } - - async fn read_number( - &mut self, - mut number: T, - msg: &str, - ) -> Result> { - loop { - self.set_text(map! {"message" => Value::text(format!("{msg} {number}"))}) - .await?; - match &*self.actions.recv().await.error("channel closed")? { - "_left" => break, - "_up" => number += T::one(), - "_down" => number = number.saturating_sub(&T::one()), - "_middle" | "_right" => return Ok(None), - _ => (), - } - } - Ok(Some(number)) - } - - async fn set_notification(&mut self, message: &str) -> Result<()> { - self.state = PomodoroState::Notify; - self.set_text(map! {"message" => Value::text(message.to_string())}) - .await?; - if let Some(cmd) = &self.config.notify_cmd { - let cmd = cmd.replace("{msg}", message); - if self.config.blocking_cmd { - spawn_shell_sync(&cmd) - .await - .error("failed to run notify_cmd")?; - } else { - spawn_shell(&cmd).error("failed to run notify_cmd")?; - self.wait_for_click("_left").await?; - } - } else { - self.wait_for_click("_left").await?; - } - Ok(()) - } - - async fn run_pomodoro( - &mut self, - task_len: Duration, - break_len: Duration, - pomodoros: usize, - ) -> Result<()> { - let interval: Seconds = 1.into(); - let mut update_timer = interval.timer(); - for pomodoro in 0..pomodoros { - let mut total_elapsed = Duration::ZERO; - 'pomodoro_run: loop { - // Task timer - self.state = PomodoroState::PomodoroRunning; - let timer = Instant::now(); - loop { - let elapsed = timer.elapsed(); - if total_elapsed + elapsed >= task_len { - break 'pomodoro_run; - } - let remaining_time = task_len - total_elapsed - elapsed; - let values = map! { - [if pomodoro != 0] "completed_pomodoros" => Value::number(pomodoro), - "time_remaining" => Value::duration(remaining_time), - }; - self.set_text(values.clone()).await?; - select! { - _ = update_timer.tick() => (), - Some(action) = self.actions.recv() => match action.as_ref() { - "_middle" | "_right" => return Ok(()), - "_left" => { - self.state = PomodoroState::PomodoroPaused; - self.set_text(values).await?; - total_elapsed += timer.elapsed(); - loop { - match self.actions.recv().await.as_deref() { - Some("_middle") | Some("_right") => return Ok(()), - Some("_left") => { - continue 'pomodoro_run; - }, - _ => () - - } - } - }, - _ => () - } - } - } - } - - // Show break message - self.set_notification(&self.config.message).await?; - - // No break after the last pomodoro - if pomodoro == pomodoros - 1 { - break; - } - - // Break timer - self.state = PomodoroState::Break; - let timer = Instant::now(); - loop { - let elapsed = timer.elapsed(); - if elapsed >= break_len { - break; - } - let remaining_time = break_len - elapsed; - self.set_text(map! { - "time_remaining" => Value::duration(remaining_time), - }) - .await?; - select! { - _ = update_timer.tick() => (), - Some(action) = self.actions.recv() => match action.as_ref() { - "_middle" | "_right" => return Ok(()), - _ => () - } - } - } - - // Show task message - self.set_notification(&self.config.break_message).await?; - } - - Ok(()) - } -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - api.set_default_actions(&[ - (MouseButton::Left, None, "_left"), - (MouseButton::Middle, None, "_middle"), - (MouseButton::Right, None, "_right"), - (MouseButton::WheelUp, None, "_up"), - (MouseButton::WheelDown, None, "_down"), - ])?; - - let format = config.format.clone().with_default(" $icon{ $message|} ")?; - - let pomodoro_format = config.pomodoro_format.clone().with_default( - " $icon $status_icon{ $completed_pomodoros.tally()|} $time_remaining.duration(hms:true) ", - )?; - - let break_format = config - .break_format - .clone() - .with_default(" $icon $status_icon Break: $time_remaining.duration(hms:true) ")?; - - let widget = Widget::new(); - - let mut block = Block { - widget, - actions: api.get_actions()?, - api, - config, - state: PomodoroState::Idle, - format, - pomodoro_format, - break_format, - }; - - loop { - // Send collaped block - block.state = PomodoroState::Idle; - block.set_text(Values::default()).await?; - - block.wait_for_click("_left").await?; - - if let Some((task_len, break_len, pomodoros)) = block.read_params().await? { - block.run_pomodoro(task_len, break_len, pomodoros).await?; - } - } -} diff --git a/src/blocks/prelude.rs b/src/blocks/prelude.rs deleted file mode 100644 index 9e3840bee1..0000000000 --- a/src/blocks/prelude.rs +++ /dev/null @@ -1,33 +0,0 @@ -pub use super::{BlockAction, CommonApi}; - -pub(crate) use crate::REQWEST_CLIENT; -pub(crate) use crate::REQWEST_CLIENT_IPV4; -pub use crate::click::MouseButton; -pub use crate::errors::*; -pub use crate::formatting::{Values, config::Config as FormatConfig, value::Value}; -pub use crate::util::{default, new_dbus_connection, new_system_dbus_connection}; -pub use crate::widget::{State, Widget}; -pub use crate::wrappers::{Seconds, ShellString}; - -pub use serde::Deserialize; - -pub use backon::{ExponentialBuilder, Retryable}; - -pub use std::borrow::Cow; -pub use std::collections::HashMap; -pub use std::fmt::Write; -pub use std::pin::Pin; -pub use std::sync::LazyLock; -pub use std::time::Duration; - -pub use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt}; -pub use tokio::select; -pub use tokio::time::sleep; - -pub use futures::{Stream, StreamExt}; - -pub use smart_default::SmartDefault; - -pub use async_trait::async_trait; - -pub use crate::util::StreamExtDebounced as _; diff --git a/src/blocks/privacy.rs b/src/blocks/privacy.rs deleted file mode 100644 index 12ea64d0ee..0000000000 --- a/src/blocks/privacy.rs +++ /dev/null @@ -1,250 +0,0 @@ -//! Privacy Monitor -//! -//! # Configuration -//! -//! Key | Values | Default| -//! -----------|--------|--------| -//! `driver` | The configuration of a driver (see below). | **Required** -//! `format` | Format string. | \"{ $icon_audio \|}{ $icon_audio_sink \|}{ $icon_video \|}{ $icon_webcam \|}{ $icon_unknown \|}\" | -//! `format_alt` | Format string. | \"{ $icon_audio $info_audio \|}{ $icon_audio_sink $info_audio_sink \|}{ $icon_video $info_video \|}{ $icon_webcam $info_webcam \|}{ $icon_unknown $info_unknown \|}\" | -//! -//! # pipewire Options (requires the pipewire feature to be enabled) -//! -//! Key | Values | Required | Default -//! ----|--------|----------|-------- -//! `name` | `pipewire` | Yes | None -//! `exclude_output` | An output node to ignore, example: `["HD Pro Webcam C920"]` | No | `[]` -//! `exclude_input` | An input node to ignore, example: `["openrgb"]` | No | `[]` -//! `display` | Which node field should be used as a display name, options: `name`, `description`, `nickname` | No | `name` -//! -//! # vl4 Options -//! -//! Key | Values | Required | Default -//! ----|--------|----------|-------- -//! `name` | `vl4` | Yes | None -//! `exclude_device` | A device to ignore, example: `["/dev/video5"]` | No | `[]` -//! `exclude_consumer` | Processes to ignore | No | `["pipewire", "wireplumber"]` -//! -//! # Available Format Keys -//! -//! Placeholder | Value | Type | Unit -//! -------------------------------------------------|------------------------------------------------|----------|----- -//! `icon_{audio,audio_sink,video,webcam,unknown}` | A static icon | Icon | - -//! `info_{audio,audio_sink,video,webcam,unknown}` | The mapping of which source are being consumed | Text | - -//! -//! You can use the suffixes noted above to get the following: -//! -//! Suffix | Description -//! -------------|------------ -//! `audio` | Captured audio (ex. Mic) -//! `audio_sink` | Audio captured from a sink (ex. openrgb) -//! `video` | Video capture (ex. screen capture) -//! `webcam` | Webcam capture -//! `unknown` | Anything else -//! -//! # Available Actions -//! -//! Action | Description | Default button -//! ----------------|-------------------------------------------|--------------- -//! `toggle_format` | Toggles between `format` and `format_alt` | Left -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "privacy" -//! [[block.driver]] -//! name = "v4l" -//! [[block.driver]] -//! name = "pipewire" -//! exclude_input = ["openrgb"] -//! display = "nickname" -//! ``` -//! -//! # Icons Used -//! - `microphone` -//! - `volume` -//! - `xrandr` -//! - `webcam` -//! - `unknown` - -use futures::future::{select_all, try_join_all}; - -use super::prelude::*; - -make_log_macro!(debug, "privacy"); - -#[cfg(feature = "pipewire")] -mod pipewire; -mod v4l; - -#[derive(Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct Config { - #[serde(default)] - pub format: FormatConfig, - #[serde(default)] - pub format_alt: FormatConfig, - pub driver: Vec, -} - -#[derive(Deserialize, Debug)] -#[serde(tag = "name", rename_all = "snake_case")] -pub enum PrivacyDriver { - #[cfg(feature = "pipewire")] - Pipewire(pipewire::Config), - V4l(v4l::Config), -} - -#[derive(Debug, Clone, Eq, Hash, PartialEq)] -enum Type { - Audio, - AudioSink, - Video, - Webcam, - Unknown, -} - -// {type: {source: {destination: count}} -type PrivacyInfo = HashMap; - -type PrivacyInfoInnerType = HashMap>; -#[derive(Default, Debug)] -struct PrivacyInfoInner(PrivacyInfoInnerType); - -impl std::ops::Deref for PrivacyInfoInner { - type Target = PrivacyInfoInnerType; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::DerefMut for PrivacyInfoInner { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl std::fmt::Display for PrivacyInfoInner { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{{ {} }}", - itertools::join( - self.iter().map(|(source, destinations)| { - format!( - "{} => [ {} ]", - source, - itertools::join( - destinations - .iter() - .map(|(destination, count)| if count == &1 { - destination.into() - } else { - format!("{destination} (x{count})") - }), - ", " - ) - ) - }), - ", ", - ) - ) - } -} - -#[async_trait] -trait PrivacyMonitor { - async fn get_info(&mut self) -> Result; - async fn wait_for_change(&mut self) -> Result<()>; -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?; - - let mut format = config.format.with_default( - "{ $icon_audio |}{ $icon_audio_sink |}{ $icon_video |}{ $icon_webcam |}{ $icon_unknown |}", - )?; - let mut format_alt = config.format_alt.with_default("{ $icon_audio $info_audio |}{ $icon_audio_sink $info_audio_sink |}{ $icon_video $info_video |}{ $icon_webcam $info_webcam |}{ $icon_unknown $info_unknown |}")?; - - let mut drivers: Vec> = Vec::new(); - - for driver in &config.driver { - drivers.push(match driver { - #[cfg(feature = "pipewire")] - PrivacyDriver::Pipewire(driver_config) => { - Box::new(pipewire::Monitor::new(driver_config).await?) - } - PrivacyDriver::V4l(driver_config) => { - Box::new(v4l::Monitor::new(driver_config, api.error_interval).await?) - } - }); - } - - loop { - let mut widget = Widget::new().with_format(format.clone()); - - let mut info = PrivacyInfo::default(); - //Merge driver info - for driver_info in try_join_all(drivers.iter_mut().map(|driver| driver.get_info())).await? { - for (type_, mapping) in driver_info { - let existing_mapping = info.entry(type_).or_default(); - for (source, dest) in mapping.0 { - existing_mapping.entry(source).or_default().extend(dest); - } - } - } - if !info.is_empty() { - widget.state = State::Warning; - } - - let mut values = Values::new(); - - if let Some(info_by_type) = info.get(&Type::Audio) { - map! { @extend values - "icon_audio" => Value::icon("microphone"), - "info_audio" => Value::text(info_by_type.to_string()) - } - } - if let Some(info_by_type) = info.get(&Type::AudioSink) { - map! { @extend values - "icon_audio_sink" => Value::icon("volume"), - "info_audio_sink" => Value::text(info_by_type.to_string()) - } - } - if let Some(info_by_type) = info.get(&Type::Video) { - map! { @extend values - "icon_video" => Value::icon("xrandr"), - "info_video" => Value::text(info_by_type.to_string()) - } - } - if let Some(info_by_type) = info.get(&Type::Webcam) { - map! { @extend values - "icon_webcam" => Value::icon("webcam"), - "info_webcam" => Value::text(info_by_type.to_string()) - } - } - if let Some(info_by_type) = info.get(&Type::Unknown) { - map! { @extend values - "icon_unknown" => Value::icon("unknown"), - "info_unknown" => Value::text(info_by_type.to_string()) - } - } - - widget.set_values(values); - - api.set_widget(widget)?; - - select! { - _ = api.wait_for_update_request() => (), - _ = select_all(drivers.iter_mut().map(|driver| driver.wait_for_change())) =>(), - Some(action) = actions.recv() => match action.as_ref() { - "toggle_format" => { - std::mem::swap(&mut format_alt, &mut format); - } - _ => (), - } - } - } -} diff --git a/src/blocks/privacy/pipewire.rs b/src/blocks/privacy/pipewire.rs deleted file mode 100644 index 94c49a6fa9..0000000000 --- a/src/blocks/privacy/pipewire.rs +++ /dev/null @@ -1,261 +0,0 @@ -use std::cell::Cell; -use std::collections::HashMap; -use std::rc::Rc; -use std::sync::{Arc, Mutex, Weak}; -use std::thread; - -use ::pipewire::{ - context::Context, keys, main_loop::MainLoop, properties::properties, spa::utils::dict::DictRef, - types::ObjectType, -}; -use itertools::Itertools as _; -use tokio::sync::Notify; - -use super::*; - -static CLIENT: LazyLock> = LazyLock::new(Client::new); - -#[derive(Debug)] -struct Node { - name: String, - nick: Option, - media_class: Option, - media_role: Option, - description: Option, -} - -impl Node { - fn new(global_id: u32, global_props: &DictRef) -> Self { - Self { - name: global_props - .get(&keys::NODE_NAME) - .map_or_else(|| format!("node_{global_id}"), |s| s.to_string()), - nick: global_props.get(&keys::NODE_NICK).map(|s| s.to_string()), - media_class: global_props.get(&keys::MEDIA_CLASS).map(|s| s.to_string()), - media_role: global_props.get(&keys::MEDIA_ROLE).map(|s| s.to_string()), - description: global_props - .get(&keys::NODE_DESCRIPTION) - .map(|s| s.to_string()), - } - } -} - -#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] -struct Link { - link_output_node: u32, - link_input_node: u32, -} - -impl Link { - fn new(global_props: &DictRef) -> Option { - if let Some(link_output_node) = global_props - .get(&keys::LINK_OUTPUT_NODE) - .and_then(|s| s.parse().ok()) - && let Some(link_input_node) = global_props - .get(&keys::LINK_INPUT_NODE) - .and_then(|s| s.parse().ok()) - { - Some(Self { - link_output_node, - link_input_node, - }) - } else { - None - } - } -} - -#[derive(Default)] -struct Data { - nodes: HashMap, - links: HashMap, -} - -#[derive(Default)] -struct Client { - event_listeners: Mutex>>, - data: Mutex, -} - -impl Client { - fn new() -> Result { - thread::Builder::new() - .name("privacy_pipewire".to_string()) - .spawn(Client::main_loop_thread) - .error("failed to spawn a thread")?; - - Ok(Client::default()) - } - - fn main_loop_thread() { - let client = CLIENT.as_ref().error("Could not get client").unwrap(); - - let proplist = properties! {*keys::APP_NAME => env!("CARGO_PKG_NAME")}; - - let main_loop = MainLoop::new(None).expect("Failed to create main loop"); - - let context = - Context::with_properties(&main_loop, proplist).expect("Failed to create context"); - let core = context.connect(None).expect("Failed to connect"); - let registry = core.get_registry().expect("Failed to get registry"); - - let updated = Rc::new(Cell::new(false)); - let updated_copy = updated.clone(); - let updated_copy2 = updated.clone(); - - // Register a callback to the `global` event on the registry, which notifies of any new global objects - // appearing on the remote. - // The callback will only get called as long as we keep the returned listener alive. - let _registry_listener = registry - .add_listener_local() - .global(move |global| { - let Some(global_props) = global.props else { - return; - }; - match &global.type_ { - ObjectType::Node => { - client - .data - .lock() - .unwrap() - .nodes - .insert(global.id, Node::new(global.id, global_props)); - updated_copy.set(true); - } - ObjectType::Link => { - let Some(link) = Link::new(global_props) else { - return; - }; - client.data.lock().unwrap().links.insert(global.id, link); - updated_copy.set(true); - } - _ => (), - } - }) - .global_remove(move |uid| { - let mut data = client.data.lock().unwrap(); - if data.nodes.remove(&uid).is_some() || data.links.remove(&uid).is_some() { - updated_copy2.set(true); - } - }) - .register(); - - loop { - main_loop.loop_().iterate(Duration::from_secs(60 * 60 * 24)); - if updated.get() { - updated.set(false); - client - .event_listeners - .lock() - .unwrap() - .retain(|notify| notify.upgrade().inspect(|x| x.notify_one()).is_some()); - } - } - } - - fn add_event_listener(&self, notify: &Arc) { - self.event_listeners - .lock() - .unwrap() - .push(Arc::downgrade(notify)); - } -} - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(rename_all = "lowercase", deny_unknown_fields, default)] -pub struct Config { - exclude_output: Vec, - exclude_input: Vec, - display: NodeDisplay, -} - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(rename_all = "snake_case")] -enum NodeDisplay { - #[default] - Name, - Description, - Nickname, -} - -impl NodeDisplay { - fn map_node(&self, node: &Node) -> String { - match self { - NodeDisplay::Name => node.name.clone(), - NodeDisplay::Description => node.description.clone().unwrap_or(node.name.clone()), - NodeDisplay::Nickname => node.nick.clone().unwrap_or(node.name.clone()), - } - } -} - -pub(super) struct Monitor<'a> { - config: &'a Config, - notify: Arc, -} - -impl<'a> Monitor<'a> { - pub(super) async fn new(config: &'a Config) -> Result { - let client = CLIENT.as_ref().error("Could not get client")?; - let notify = Arc::new(Notify::new()); - client.add_event_listener(¬ify); - Ok(Self { config, notify }) - } -} - -#[async_trait] -impl PrivacyMonitor for Monitor<'_> { - async fn get_info(&mut self) -> Result { - let client = CLIENT.as_ref().error("Could not get client")?; - let data = client.data.lock().unwrap(); - let mut mapping: PrivacyInfo = PrivacyInfo::new(); - - for node in data.nodes.values() { - debug! {"{:?}", node}; - } - - // The links must be sorted and then dedup'ed since you can multiple links between any given pair of nodes - for Link { - link_output_node, - link_input_node, - .. - } in data.links.values().sorted().dedup() - { - if let Some(output_node) = data.nodes.get(link_output_node) - && let Some(input_node) = data.nodes.get(link_input_node) - && input_node.media_class != Some("Audio/Sink".into()) - && !self.config.exclude_output.contains(&output_node.name) - && !self.config.exclude_input.contains(&input_node.name) - { - let type_ = if input_node.media_class == Some("Stream/Input/Video".into()) { - if output_node.media_role == Some("Camera".into()) { - Type::Webcam - } else { - Type::Video - } - } else if input_node.media_class == Some("Stream/Input/Audio".into()) { - if output_node.media_class == Some("Audio/Sink".into()) { - Type::AudioSink - } else { - Type::Audio - } - } else { - Type::Unknown - }; - *mapping - .entry(type_) - .or_default() - .entry(self.config.display.map_node(output_node)) - .or_default() - .entry(self.config.display.map_node(input_node)) - .or_default() += 1; - } - } - - Ok(mapping) - } - - async fn wait_for_change(&mut self) -> Result<()> { - self.notify.notified().await; - Ok(()) - } -} diff --git a/src/blocks/privacy/v4l.rs b/src/blocks/privacy/v4l.rs deleted file mode 100644 index 1b0e69701a..0000000000 --- a/src/blocks/privacy/v4l.rs +++ /dev/null @@ -1,149 +0,0 @@ -use inotify::{EventStream, Inotify, WatchDescriptor, WatchMask, Watches}; -use tokio::fs::{File, read_dir}; -use tokio::time::{Interval, interval}; - -use std::path::PathBuf; - -use super::*; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(rename_all = "lowercase", deny_unknown_fields, default)] -pub struct Config { - exclude_device: Vec, - #[default(vec!["pipewire".into(), "wireplumber".into()])] - exclude_consumer: Vec, -} - -pub(super) struct Monitor<'a> { - config: &'a Config, - devices: HashMap, - interval: Interval, - watches: Watches, - stream: EventStream<[u8; 1024]>, -} - -impl<'a> Monitor<'a> { - pub(super) async fn new(config: &'a Config, duration: Duration) -> Result { - let notify = Inotify::init().error("Failed to start inotify")?; - let watches = notify.watches(); - - let stream = notify - .into_event_stream([0; 1024]) - .error("Failed to create event stream")?; - - let mut s = Self { - config, - devices: HashMap::new(), - interval: interval(duration), - watches, - stream, - }; - s.update_devices().await?; - - Ok(s) - } - - async fn update_devices(&mut self) -> Result { - let mut changes = false; - let mut devices_to_remove: HashMap = self.devices.clone(); - let mut sysfs_paths = read_dir("/dev").await.error("Unable to read /dev")?; - while let Some(entry) = sysfs_paths - .next_entry() - .await - .error("Unable to get next device in /dev")? - { - if let Some(file_name) = entry.file_name().to_str() - && !file_name.starts_with("video") - { - continue; - } - - let sysfs_path = entry.path(); - - if self.config.exclude_device.contains(&sysfs_path) { - debug!("ignoring {:?}", sysfs_path); - continue; - } - - if self.devices.contains_key(&sysfs_path) { - devices_to_remove.remove(&sysfs_path); - } else { - debug!("adding watch {:?}", sysfs_path); - self.devices.insert( - sysfs_path.clone(), - self.watches - .add(&sysfs_path, WatchMask::OPEN | WatchMask::CLOSE) - .error("Failed to watch data location")?, - ); - changes = true; - } - } - for (sysfs_path, wd) in devices_to_remove { - debug!("removing watch {:?}", sysfs_path); - self.devices.remove(&sysfs_path); - self.watches - .remove(wd) - .error("Failed to unwatch data location")?; - changes = true; - } - - Ok(changes) - } -} - -#[async_trait] -impl PrivacyMonitor for Monitor<'_> { - async fn get_info(&mut self) -> Result { - let mut mapping: PrivacyInfo = PrivacyInfo::new(); - - let mut proc_paths = read_dir("/proc").await.error("Unable to read /proc")?; - while let Some(proc_path) = proc_paths - .next_entry() - .await - .error("Unable to get next device in /proc")? - { - let proc_path = proc_path.path(); - let fd_path = proc_path.join("fd"); - let Ok(mut fd_paths) = read_dir(fd_path).await else { - continue; - }; - while let Ok(Some(fd_path)) = fd_paths.next_entry().await { - let mut contents = String::new(); - if let Ok(link_path) = fd_path.path().read_link() - && self.devices.contains_key(&link_path) - && let Ok(mut file) = File::open(proc_path.join("comm")).await - && file.read_to_string(&mut contents).await.is_ok() - { - let reader = contents.trim_end().to_string(); - if self.config.exclude_consumer.contains(&reader) { - continue; - } - debug!("{} {:?}", reader, link_path); - *mapping - .entry(Type::Webcam) - .or_default() - .entry(link_path.to_string_lossy().to_string()) - .or_default() - .entry(reader) - .or_default() += 1; - debug!("{:?}", mapping); - } - } - } - Ok(mapping) - } - - async fn wait_for_change(&mut self) -> Result<()> { - loop { - select! { - _ = self.interval.tick() => { - if self.update_devices().await? { - break; - } - }, - _ = self.stream.next_debounced() => break - } - } - Ok(()) - } -} diff --git a/src/blocks/rofication.rs b/src/blocks/rofication.rs deleted file mode 100644 index 3666514640..0000000000 --- a/src/blocks/rofication.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! The number of pending notifications in rofication-daemon -//! -//! A different color is used if there are critical notifications. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `interval` | Refresh rate in seconds. | `1` -//! `format` | A string to customise the output of this block. See below for placeholders. | `" $icon $num.eng(w:1) "` -//! `socket_path` | Socket path for the rofication daemon. Supports path expansions e.g. `~`. | `"/tmp/rofi_notification_daemon"` -//! -//! Placeholder | Value | Type | Unit -//! -------------|-------|------|----- -//! `icon` | A static icon | Icon | - -//! `num` | Number of pending notifications | Number | - -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "rofication" -//! interval = 1 -//! socket_path = "/tmp/rofi_notification_daemon" -//! [[block.click]] -//! button = "left" -//! cmd = "rofication-gui" -//! ``` -//! -//! # Icons Used -//! - `bell` - -use super::prelude::*; -use tokio::net::UnixStream; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - #[default(1.into())] - pub interval: Seconds, - #[default("/tmp/rofi_notification_daemon".into())] - pub socket_path: ShellString, - pub format: FormatConfig, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config.format.with_default(" $icon $num.eng(w:1) ")?; - - let path = config.socket_path.expand()?; - let mut timer = config.interval.timer(); - - loop { - let (num, crit) = rofication_status(&path).await?; - - let mut widget = Widget::new().with_format(format.clone()); - - widget.set_values(map!( - "icon" => Value::icon("bell"), - "num" => Value::number(num) - )); - - widget.state = if crit > 0 { - State::Warning - } else if num > 0 { - State::Info - } else { - State::Idle - }; - - api.set_widget(widget)?; - - tokio::select! { - _ = timer.tick() => (), - _ = api.wait_for_update_request() => (), - } - } -} - -async fn rofication_status(socket_path: &str) -> Result<(usize, usize)> { - let mut stream = UnixStream::connect(socket_path) - .await - .error("Failed to connect to socket")?; - - // Request count - stream - .write_all(b"num:\n") - .await - .error("Failed to write to socket")?; - - let mut response = String::new(); - stream - .read_to_string(&mut response) - .await - .error("Failed to read from socket")?; - - // Response must be two integers: regular and critical, separated either by a comma or a \n - let (num, crit) = response - .split_once([',', '\n']) - .error("Incorrect response")?; - Ok(( - num.parse().error("Incorrect response")?, - crit.parse().error("Incorrect response")?, - )) -} diff --git a/src/blocks/scratchpad.rs b/src/blocks/scratchpad.rs deleted file mode 100644 index 93610542a7..0000000000 --- a/src/blocks/scratchpad.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! Scratchpad indicator -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block | ` $icon $count.eng(range:1..) |` -//! -//! Placeholder | Value | Type | Unit -//! ------------|--------------------------------------------|--------|----- -//! `icon` | A static icon | Icon | - -//! `count` | Number of windows in scratchpad | Number | - -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "scratchpad" -//! ``` -//! -//! # Icons Used -//! - `scratchpad` - -use swayipc_async::{Connection, Event as SwayEvent, EventType, Node, WindowChange}; - -use super::prelude::*; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, -} - -fn count_scratchpad_windows(node: &Node) -> usize { - node.find_as_ref(|n| n.name.as_deref() == Some("__i3_scratch")) - .map_or(0, |node| node.floating_nodes.len()) -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config - .format - .with_default(" $icon $count.eng(range:1..) |")?; - - let connection_for_events = Connection::new() - .await - .error("failed to open connection with swayipc")?; - - let mut connection_for_tree = Connection::new() - .await - .error("failed to open connection with swayipc")?; - - let mut events = connection_for_events - .subscribe(&[EventType::Window]) - .await - .error("could not subscribe to window events")?; - - loop { - let mut widget = Widget::new().with_format(format.clone()); - - let root_node = connection_for_tree - .get_tree() - .await - .error("could not get windows tree")?; - let count = count_scratchpad_windows(&root_node); - - widget.set_values(map! { - "icon" => Value::icon("scratchpad"), - "count" => Value::number(count), - }); - - api.set_widget(widget)?; - - loop { - let event = events - .next() - .await - .error("swayipc channel closed")? - .error("bad event")?; - - match event { - SwayEvent::Window(e) if e.change == WindowChange::Move => break, - _ => continue, - } - } - } -} diff --git a/src/blocks/service_status.rs b/src/blocks/service_status.rs deleted file mode 100644 index 521f3cb58c..0000000000 --- a/src/blocks/service_status.rs +++ /dev/null @@ -1,212 +0,0 @@ -//! Display the status of a service -//! -//! Right now only `systemd` is supported. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `driver` | Which init system is running the service. Available drivers are: `"systemd"` | `"systemd"` -//! `service` | The name of the service | **Required** -//! `user` | If true, monitor the status of a user service instead of a system service. | `false` -//! `active_format` | A string to customise the output of this block. See below for available placeholders. | `" $service active "` -//! `inactive_format` | A string to customise the output of this block. See below for available placeholders. | `" $service inactive "` -//! `active_state` | A valid [`State`] | [`State::Idle`] -//! `inactive_state` | A valid [`State`] | [`State::Critical`] -//! -//! Placeholder | Value | Type | Unit -//! ---------------|---------------------------|--------|----- -//! `service` | The name of the service | Text | - -//! -//! # Example -//! -//! Example using an icon: -//! -//! ```toml -//! [[block]] -//! block = "service_status" -//! service = "cups" -//! active_format = " ^icon_tea " -//! inactive_format = " no ^icon_tea " -//! ``` -//! -//! Example overriding the default `inactive_state`: -//! -//! ```toml -//! [[block]] -//! block = "service_status" -//! service = "shadow" -//! active_format = "" -//! inactive_format = " Integrity of password and group files failed " -//! inactive_state = "Warning" -//! ``` -//! - -use super::prelude::*; -use zbus::proxy::PropertyStream; - -#[derive(Deserialize, Debug, Default)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub driver: DriverType, - pub service: String, - pub user: bool, - pub active_format: FormatConfig, - pub inactive_format: FormatConfig, - pub active_state: Option, - pub inactive_state: Option, -} - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(rename_all = "snake_case")] -pub enum DriverType { - #[default] - Systemd, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let active_format = config.active_format.with_default(" $service active ")?; - let inactive_format = config.inactive_format.with_default(" $service inactive ")?; - - let active_state = config.active_state.unwrap_or(State::Idle); - let inactive_state = config.inactive_state.unwrap_or(State::Critical); - - let mut driver: Box = match config.driver { - DriverType::Systemd => { - Box::new(SystemdDriver::new(config.user, config.service.clone()).await?) - } - }; - - loop { - let service_active_state = driver.is_active().await?; - - let mut widget = Widget::new(); - - if service_active_state { - widget.state = active_state; - widget.set_format(active_format.clone()); - } else { - widget.state = inactive_state; - widget.set_format(inactive_format.clone()); - }; - - widget.set_values(map! { - "service" =>Value::text(config.service.clone()), - }); - - api.set_widget(widget)?; - - driver.wait_for_change().await?; - } -} - -#[async_trait] -trait Driver { - async fn is_active(&self) -> Result; - async fn wait_for_change(&mut self) -> Result<()>; -} - -struct SystemdDriver { - proxy: UnitProxy<'static>, - service_proxy: ServiceProxy<'static>, - active_state_changed: PropertyStream<'static, String>, -} - -impl SystemdDriver { - async fn new(user: bool, service: String) -> Result { - let dbus_conn = if user { - new_dbus_connection().await? - } else { - new_system_dbus_connection().await? - }; - - if !service.is_ascii() { - return Err(Error::new(format!( - "service name \"{service}\" must only contain ASCII characters" - ))); - } - let encoded_service = format!("{service}.service") - // For each byte... - .bytes() - .map(|b| { - if b.is_ascii_alphanumeric() { - // Just use the character as a string - char::from(b).to_string() - } else { - // Otherwise use the hex representation of the byte preceded by an underscore - format!("_{b:02x}") - } - }) - .collect::(); - - let path = format!("/org/freedesktop/systemd1/unit/{encoded_service}"); - - let proxy = UnitProxy::builder(&dbus_conn) - .path(path.clone()) - .error("Could not set path")? - .build() - .await - .error("Failed to create UnitProxy")?; - - let service_proxy = ServiceProxy::builder(&dbus_conn) - .path(path) - .error("Could not set path")? - .build() - .await - .error("Failed to create ServiceProxy")?; - - Ok(Self { - active_state_changed: proxy.receive_active_state_changed().await, - proxy, - service_proxy, - }) - } -} - -#[async_trait] -impl Driver for SystemdDriver { - async fn is_active(&self) -> Result { - let active_state = self - .proxy - .active_state() - .await - .error("Could not get active_state")?; - - Ok(match &*active_state { - "active" => true, - "activating" => { - let service_type = self - .service_proxy - .type_() - .await - .error("Could not get service type")?; - - service_type == "oneshot" - } - _ => false, - }) - } - - async fn wait_for_change(&mut self) -> Result<()> { - self.active_state_changed.next().await; - Ok(()) - } -} - -#[zbus::proxy( - interface = "org.freedesktop.systemd1.Unit", - default_service = "org.freedesktop.systemd1" -)] -trait Unit { - #[zbus(property)] - fn active_state(&self) -> zbus::Result; -} - -#[zbus::proxy( - interface = "org.freedesktop.systemd1.Service", - default_service = "org.freedesktop.systemd1" -)] -trait Service { - #[zbus(property, name = "Type")] - fn type_(&self) -> zbus::Result; -} diff --git a/src/blocks/sound.rs b/src/blocks/sound.rs deleted file mode 100644 index 6893d49465..0000000000 --- a/src/blocks/sound.rs +++ /dev/null @@ -1,357 +0,0 @@ -//! Volume level -//! -//! This block displays the volume level (according to PulseAudio or ALSA). Right click to toggle mute, scroll to adjust volume. -//! -//! Requires a PulseAudio installation or `alsa-utils` for ALSA. -//! -//! Note that if you are using PulseAudio commands (such as `pactl`) to control your volume, you should select the `"pulseaudio"` (or `"auto"`) driver to see volume changes that exceed 100%. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `driver` | `"auto"`, `"pulseaudio"`, `"alsa"`. | `"auto"` (Pulseaudio with ALSA fallback) -//! `format` | A string to customise the output of this block. See below for available placeholders. | \" $icon {$volume.eng(w:2) \|}\" -//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click. | `None` -//! `name` | PulseAudio device name, or the ALSA control name as found in the output of `amixer -D yourdevice scontrols`. | PulseAudio: `@DEFAULT_SINK@` / ALSA: `Master` -//! `device` | ALSA device name, usually in the form "hw:X" or "hw:X,Y" where `X` is the card number and `Y` is the device number as found in the output of `aplay -l`. | `default` -//! `device_kind` | PulseAudio device kind: `source` or `sink`. | `"sink"` -//! `natural_mapping` | When using the ALSA driver, display the "mapped volume" as given by `alsamixer`/`amixer -M`, which represents the volume level more naturally with respect for the human ear. | `false` -//! `step_width` | The percent volume level is increased/decreased for the selected audio device when scrolling. Capped automatically at 50. | `5` -//! `max_vol` | Max volume in percent that can be set via scrolling. Note it can still be set above this value if changed by another application. | `None` -//! `show_volume_when_muted` | Show the volume even if it is currently muted. | `false` -//! `headphones_indicator` | Change icon when headphones are plugged in (pulseaudio only) | `false` -//! `mappings` | Map `output_name` to a custom name. | `None` -//! `mappings_use_regex` | Let `mappings` match using regex instead of string equality. The replacement will be regex aware and can contain capture groups. | `true` -//! `active_port_mappings` | Map `active_port` to a custom name. The replacement will be regex aware and can contain capture groups. | `None` -//! -//! Placeholder | Value | Type | Unit -//! ---------------------|-----------------------------------|--------|--------------- -//! `icon` | Icon based on volume | Icon | - -//! `volume` | Current volume. Missing if muted. | Number | % -//! `output_name` | PulseAudio or ALSA device name | Text | - -//! `output_description` | PulseAudio device description, will fallback to `output_name` if no description is available and will be overwritten by mappings (mappings will still use `output_name`) | Text | - -//! `active_port` | Active port (same as information in Ports section of `pactl list cards`). Will be absent if not supported by `driver` or if mapped to `""` in `active_port_mappings`. | Text | - -//! -//! Action | Default button -//! ----------------|--------------- -//! `toggle_format` | Left -//! `toggle_mute` | Right -//! `volume_down` | Wheel Down -//! `volume_up` | Wheel Up -//! -//! # Examples -//! -//! Change the default scrolling step width to 3 percent: -//! -//! ```toml -//! [[block]] -//! block = "sound" -//! step_width = 3 -//! ``` -//! -//! Change the output name shown: -//! -//! ```toml -//! [[block]] -//! block = "sound" -//! format = " $icon $output_name{ $volume|} " -//! [block.mappings] -//! "alsa_output.usb-Harman_Multimedia_JBL_Pebbles_1.0.0-00.analog-stereo" = "Speakers" -//! "alsa_output.pci-0000_00_1b.0.analog-stereo" = "Headset" -//! ``` -//! -//! Since the default value for the `device_kind` key is `sink`, -//! to display ***microphone*** block you have to use the `source` value: -//! -//! ```toml -//! [[block]] -//! block = "sound" -//! driver = "pulseaudio" -//! device_kind = "source" -//! ``` -//! -//! Display warning in block if microphone if using the wrong port: -//! -//! ```toml -//! [[block]] -//! block = "sound" -//! driver = "pulseaudio" -//! device_kind = "source" -//! format = " $icon { $volume|} {$active_port |}" -//! [block.active_port_mappings] -//! "analog-input-rear-mic" = "" # Mapping to an empty string makes `$active_port` absent -//! "analog-input-front-mic" = "ERR!" -//! ``` -//! -//! # Icons Used -//! -//! - `microphone_muted` (as a progression) -//! - `microphone` (as a progression) -//! - `volume_muted` (as a progression) -//! - `volume` (as a progression) -//! - `headphones` - -mod alsa; -#[cfg(feature = "pulseaudio")] -mod pulseaudio; - -use super::prelude::*; -use crate::wrappers::SerdeRegex; -use indexmap::IndexMap; -use regex::Regex; - -make_log_macro!(debug, "sound"); - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub driver: SoundDriver, - pub name: Option, - pub device: Option, - pub device_kind: DeviceKind, - pub natural_mapping: bool, - #[default(5)] - pub step_width: u32, - pub format: FormatConfig, - pub format_alt: Option, - pub headphones_indicator: bool, - pub show_volume_when_muted: bool, - pub mappings: Option>, - #[default(true)] - pub mappings_use_regex: bool, - pub max_vol: Option, - pub active_port_mappings: IndexMap, -} - -enum Mappings<'a> { - Exact(&'a IndexMap), - Regex(Vec<(Regex, &'a str)>), -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[ - (MouseButton::Left, None, "toggle_format"), - (MouseButton::Right, None, "toggle_mute"), - (MouseButton::WheelUp, None, "volume_up"), - (MouseButton::WheelDown, None, "volume_down"), - ])?; - - let mut format = config.format.with_default(" $icon {$volume.eng(w:2)|} ")?; - let mut format_alt = match &config.format_alt { - Some(f) => Some(f.with_default("")?), - None => None, - }; - - let device_kind = config.device_kind; - let step_width = config.step_width.clamp(0, 50) as i32; - - let icon = |muted: bool, device: &dyn SoundDevice| -> &'static str { - if config.headphones_indicator && device_kind == DeviceKind::Sink { - let form_factor = device.form_factor(); - let active_port = device.active_port(); - debug!("form_factor = {form_factor:?} active_port = {active_port:?}"); - let headphones = match form_factor { - // form_factor's possible values are listed at: - // https://docs.rs/libpulse-binding/2.25.0/libpulse_binding/proplist/properties/constant.DEVICE_FORM_FACTOR.html - Some("headset") | Some("headphone") | Some("hands-free") | Some("portable") => true, - // Per discussion at - // https://github.com/greshake/i3status-rust/pull/1363#issuecomment-1046095869, - // some sinks may not have the form_factor property, so we should fall back to the - // active_port if that property is not present. - None => active_port.is_some_and(|p| p.to_lowercase().contains("headphones")), - // form_factor is present and is some non-headphone value - _ => false, - }; - if headphones { - return "headphones"; - } - } - if muted { - match device_kind { - DeviceKind::Source => "microphone_muted", - DeviceKind::Sink => "volume_muted", - } - } else { - match device_kind { - DeviceKind::Source => "microphone", - DeviceKind::Sink => "volume", - } - } - }; - - type DeviceType = Box; - let mut device: DeviceType = match config.driver { - SoundDriver::Alsa => Box::new(alsa::Device::new( - config.name.clone().unwrap_or_else(|| "Master".into()), - config.device.clone().unwrap_or_else(|| "default".into()), - config.natural_mapping, - )?), - #[cfg(feature = "pulseaudio")] - SoundDriver::PulseAudio => Box::new(pulseaudio::Device::new( - config.device_kind, - config.name.clone(), - )?), - #[cfg(feature = "pulseaudio")] - SoundDriver::Auto => { - if let Ok(pulse) = pulseaudio::Device::new(config.device_kind, config.name.clone()) { - Box::new(pulse) - } else { - Box::new(alsa::Device::new( - config.name.clone().unwrap_or_else(|| "Master".into()), - config.device.clone().unwrap_or_else(|| "default".into()), - config.natural_mapping, - )?) - } - } - #[cfg(not(feature = "pulseaudio"))] - SoundDriver::Auto => Box::new(alsa::Device::new( - config.name.clone().unwrap_or_else(|| "Master".into()), - config.device.clone().unwrap_or_else(|| "default".into()), - config.natural_mapping, - )?), - }; - - let mappings = match &config.mappings { - Some(m) => { - if config.mappings_use_regex { - Some(Mappings::Regex( - m.iter() - .map(|(key, val)| { - Ok(( - Regex::new(key) - .error("Failed to parse `{key}` in mappings as regex")?, - val.as_str(), - )) - }) - .collect::>()?, - )) - } else { - Some(Mappings::Exact(m)) - } - } - None => None, - }; - - loop { - device.get_info().await?; - let volume = device.volume(); - let muted = device.muted(); - let mut output_name = device.output_name(); - let mut active_port = device.active_port(); - match &mappings { - Some(Mappings::Regex(m)) => { - if let Some((regex, mapped)) = - m.iter().find(|(regex, _)| regex.is_match(&output_name)) - { - output_name = regex.replace(&output_name, *mapped).into_owned(); - } - } - Some(Mappings::Exact(m)) => { - if let Some(mapped) = m.get(&output_name) { - output_name.clone_from(mapped); - } - } - None => (), - } - if let Some(ap) = &active_port - && let Some((regex, mapped)) = config - .active_port_mappings - .iter() - .find(|(regex, _)| regex.0.is_match(ap)) - { - let mapped = regex.0.replace(ap, mapped); - if mapped.is_empty() { - active_port = None; - } else { - active_port = Some(mapped.into_owned()); - } - } - - let output_description = device - .output_description() - .unwrap_or_else(|| output_name.clone()); - - let mut values = map! { - "icon" => Value::icon_progression(icon(muted, &*device), volume as f64 / 100.0), - "volume" => Value::percents(volume), - "output_name" => Value::text(output_name), - "output_description" => Value::text(output_description), - [if let Some(ap) = active_port] "active_port" => Value::text(ap), - }; - - let mut widget = Widget::new().with_format(format.clone()); - - if muted { - widget.state = State::Warning; - if !config.show_volume_when_muted { - values.remove("volume"); - } - } - - widget.set_values(values); - api.set_widget(widget)?; - - loop { - select! { - val = device.wait_for_update() => { - val?; - break; - } - _ = api.wait_for_update_request() => break, - Some(action) = actions.recv() => match action.as_ref() { - "toggle_format" => { - if let Some(format_alt) = &mut format_alt { - std::mem::swap(format_alt, &mut format); - break; - } - } - "toggle_mute" => { - device.toggle().await?; - } - "volume_up" => { - device.set_volume(step_width, config.max_vol).await?; - } - "volume_down" => { - device.set_volume(-step_width, config.max_vol).await?; - } - _ => (), - } - } - } - } -} - -#[derive(Deserialize, Debug, SmartDefault, Clone, Copy)] -#[serde(rename_all = "lowercase")] -pub enum SoundDriver { - #[default] - Auto, - Alsa, - #[cfg(feature = "pulseaudio")] - PulseAudio, -} - -#[derive(Deserialize, Debug, SmartDefault, Clone, Copy, PartialEq, Eq, Hash)] -#[serde(rename_all = "lowercase")] -pub enum DeviceKind { - #[default] - Sink, - Source, -} - -#[async_trait::async_trait] -trait SoundDevice { - fn volume(&self) -> u32; - fn muted(&self) -> bool; - fn output_name(&self) -> String; - fn output_description(&self) -> Option; - fn active_port(&self) -> Option; - fn form_factor(&self) -> Option<&str>; - - async fn get_info(&mut self) -> Result<()>; - async fn set_volume(&mut self, step: i32, max_vol: Option) -> Result<()>; - async fn toggle(&mut self) -> Result<()>; - async fn wait_for_update(&mut self) -> Result<()>; -} diff --git a/src/blocks/sound/alsa.rs b/src/blocks/sound/alsa.rs deleted file mode 100644 index fd3467615f..0000000000 --- a/src/blocks/sound/alsa.rs +++ /dev/null @@ -1,147 +0,0 @@ -use std::cmp::{max, min}; -use std::process::Stdio; -use tokio::process::{ChildStdout, Command}; - -use super::super::prelude::*; -use super::SoundDevice; - -pub(super) struct Device { - name: String, - device: String, - natural_mapping: bool, - volume: u32, - muted: bool, - monitor: ChildStdout, -} - -impl Device { - pub(super) fn new(name: String, device: String, natural_mapping: bool) -> Result { - Ok(Device { - name, - device, - natural_mapping, - volume: 0, - muted: false, - monitor: Command::new("alsactl") - .arg("monitor") - .stdout(Stdio::piped()) - .spawn() - .error("Failed to start alsactl monitor")? - .stdout - .error("Failed to pipe alsactl monitor output")?, - }) - } -} - -#[async_trait::async_trait] -impl SoundDevice for Device { - fn volume(&self) -> u32 { - self.volume - } - - fn muted(&self) -> bool { - self.muted - } - - fn output_name(&self) -> String { - self.name.clone() - } - - fn output_description(&self) -> Option { - // TODO Does Alsa has something similar like descriptions in Pulse? - None - } - - fn active_port(&self) -> Option { - None - } - - fn form_factor(&self) -> Option<&str> { - None - } - - async fn get_info(&mut self) -> Result<()> { - let mut args = Vec::new(); - if self.natural_mapping { - args.push("-M"); - }; - args.extend(["-D", &self.device, "get", &self.name]); - - let output: String = Command::new("amixer") - .args(&args) - .output() - .await - .map(|o| std::str::from_utf8(&o.stdout).unwrap().trim().into()) - .error("could not run amixer to get sound info")?; - - let last_line = &output.lines().last().error("could not get sound info")?; - - const FILTER: &[char] = &['[', ']', '%']; - let mut last = last_line - .split_whitespace() - .filter(|x| x.starts_with('[') && !x.contains("dB")) - .map(|s| s.trim_matches(FILTER)); - - self.volume = last - .next() - .error("could not get volume")? - .parse::() - .error("could not parse volume to u32")?; - - self.muted = last.next().map(|muted| muted == "off").unwrap_or(false); - - Ok(()) - } - - async fn set_volume(&mut self, step: i32, max_vol: Option) -> Result<()> { - let new_vol = max(0, self.volume as i32 + step) as u32; - let capped_volume = if let Some(vol_cap) = max_vol { - min(new_vol, vol_cap) - } else { - new_vol - }; - let mut args = Vec::new(); - if self.natural_mapping { - args.push("-M"); - }; - let vol_str = format!("{capped_volume}%"); - args.extend(["-D", &self.device, "set", &self.name, &vol_str]); - - Command::new("amixer") - .args(&args) - .output() - .await - .error("failed to set volume")?; - - self.volume = capped_volume; - - Ok(()) - } - - async fn toggle(&mut self) -> Result<()> { - let mut args = Vec::new(); - if self.natural_mapping { - args.push("-M"); - }; - args.extend(["-D", &self.device, "set", &self.name, "toggle"]); - - Command::new("amixer") - .args(&args) - .output() - .await - .error("failed to toggle mute")?; - - self.muted = !self.muted; - - Ok(()) - } - - async fn wait_for_update(&mut self) -> Result<()> { - let mut buf = [0u8; 1024]; - self.monitor - .read(&mut buf) - .await - .error("Failed to read stdbuf output")?; - Ok(()) - } -} diff --git a/src/blocks/sound/pulseaudio.rs b/src/blocks/sound/pulseaudio.rs deleted file mode 100644 index 91b9717bab..0000000000 --- a/src/blocks/sound/pulseaudio.rs +++ /dev/null @@ -1,526 +0,0 @@ -use std::cmp::{max, min}; -use std::convert::{TryFrom, TryInto}; -use std::io; -use std::os::fd::{IntoRawFd as _, RawFd}; -use std::sync::{Arc, Mutex, Weak}; -use std::thread; - -use libc::c_void; -use libpulse_binding::callbacks::ListResult; -use libpulse_binding::context::{ - Context, FlagSet, State as PulseState, introspect::ServerInfo, introspect::SinkInfo, - introspect::SourceInfo, subscribe::Facility, subscribe::InterestMaskSet, -}; -use libpulse_binding::mainloop::api::MainloopApi; -use libpulse_binding::mainloop::standard::{IterateResult, Mainloop}; -use libpulse_binding::proplist::{Proplist, properties}; -use libpulse_binding::volume::{ChannelVolumes, Volume}; -use tokio::sync::Notify; - -use super::super::prelude::*; -use super::{DeviceKind, SoundDevice}; - -static CLIENT: LazyLock> = LazyLock::new(Client::new); -static EVENT_LISTENER: Mutex>> = Mutex::new(Vec::new()); -static DEVICES: LazyLock>> = LazyLock::new(default); - -// Default device names -pub(super) static DEFAULT_SOURCE: Mutex> = - Mutex::new(Cow::Borrowed("@DEFAULT_SOURCE@")); -pub(super) static DEFAULT_SINK: Mutex> = - Mutex::new(Cow::Borrowed("@DEFAULT_SINK@")); - -impl DeviceKind { - pub fn default_name(self) -> Cow<'static, str> { - match self { - Self::Sink => DEFAULT_SINK.lock().unwrap().clone(), - Self::Source => DEFAULT_SOURCE.lock().unwrap().clone(), - } - } -} - -pub(super) struct Device { - name: Option, - description: Option, - active_port: Option, - form_factor: Option, - device_kind: DeviceKind, - volume: Option, - volume_avg: u32, - muted: bool, - notify: Arc, -} - -struct Connection { - mainloop: Mainloop, - context: Context, -} - -struct Client { - send_req: std::sync::mpsc::Sender, - ml_waker: MainloopWaker, -} - -#[derive(Debug)] -struct VolInfo { - volume: ChannelVolumes, - mute: bool, - name: String, - description: Option, - active_port: Option, - form_factor: Option, -} - -impl TryFrom<&SourceInfo<'_>> for VolInfo { - type Error = (); - - fn try_from(source_info: &SourceInfo) -> std::result::Result { - match source_info.name.as_ref() { - None => Err(()), - Some(name) => Ok(VolInfo { - volume: source_info.volume, - mute: source_info.mute, - name: name.to_string(), - description: source_info.description.as_ref().map(|d| d.to_string()), - active_port: source_info - .active_port - .as_ref() - .and_then(|a| a.name.as_ref().map(|n| n.to_string())), - form_factor: source_info.proplist.get_str(properties::DEVICE_FORM_FACTOR), - }), - } - } -} - -impl TryFrom<&SinkInfo<'_>> for VolInfo { - type Error = (); - - fn try_from(sink_info: &SinkInfo) -> std::result::Result { - match sink_info.name.as_ref() { - None => Err(()), - Some(name) => Ok(VolInfo { - volume: sink_info.volume, - mute: sink_info.mute, - name: name.to_string(), - description: sink_info.description.as_ref().map(|d| d.to_string()), - active_port: sink_info - .active_port - .as_ref() - .and_then(|a| a.name.as_ref().map(|n| n.to_string())), - form_factor: sink_info.proplist.get_str(properties::DEVICE_FORM_FACTOR), - }), - } - } -} - -#[derive(Debug)] -enum ClientRequest { - GetDefaultDevice, - GetInfoByName(DeviceKind, String), - SetVolumeByName(DeviceKind, String, ChannelVolumes), - SetMuteByName(DeviceKind, String, bool), -} - -impl Connection { - fn new() -> Result { - let mut proplist = Proplist::new().unwrap(); - proplist - .set_str(properties::APPLICATION_NAME, env!("CARGO_PKG_NAME")) - .map_err(|_| Error::new("Could not set pulseaudio APPLICATION_NAME property"))?; - - let mainloop = Mainloop::new().error("Failed to create pulseaudio mainloop")?; - - let mut context = Context::new_with_proplist( - &mainloop, - concat!(env!("CARGO_PKG_NAME"), "_context"), - &proplist, - ) - .error("Failed to create new pulseaudio context")?; - - context - .connect(None, FlagSet::NOFLAGS, None) - .error("Failed to connect to pulseaudio context")?; - - let mut connection = Connection { mainloop, context }; - - // Wait for context to be ready - loop { - connection.iterate(false)?; - match connection.context.get_state() { - PulseState::Ready => { - break; - } - PulseState::Failed | PulseState::Terminated => { - return Err(Error::new("pulseaudio context state failed/terminated")); - } - _ => {} - } - } - - Ok(connection) - } - - fn iterate(&mut self, blocking: bool) -> Result<()> { - match self.mainloop.iterate(blocking) { - IterateResult::Quit(_) | IterateResult::Err(_) => { - Err(Error::new("failed to iterate pulseaudio state")) - } - IterateResult::Success(_) => Ok(()), - } - } - - /// Create connection in a new thread. - /// - /// If connection can't be created, Err is returned. - fn spawn(thread_name: &str, f: impl Fn(Self) -> bool + Send + 'static) -> Result<()> { - let (tx, rx) = std::sync::mpsc::sync_channel(0); - thread::Builder::new() - .name(thread_name.to_owned()) - .spawn(move || match Self::new() { - Ok(mut conn) => { - tx.send(Ok(())).unwrap(); - while f(conn) { - let mut try_i = 0usize; - loop { - try_i += 1; - let delay = - Duration::from_millis(if try_i <= 10 { 100 } else { 5_000 }); - eprintln!("reconnecting to pulseaudio in {delay:?}... (try {try_i})"); - thread::sleep(delay); - if let Ok(c) = Self::new() { - eprintln!("reconnected to pulseaudio"); - conn = c; - break; - } - } - } - } - Err(err) => { - tx.send(Err(err)).unwrap(); - } - }) - .error("failed to spawn a thread")?; - rx.recv().error("channel closed")? - } -} - -impl Client { - fn new() -> Result { - let (send_req, recv_req) = std::sync::mpsc::channel(); - let ml_waker = MainloopWaker::new().unwrap(); - - Connection::spawn("sound_pulseaudio", move |mut connection| { - ml_waker.attach(connection.mainloop.get_api()); - - let introspector = connection.context.introspect(); - connection - .context - .set_subscribe_callback(Some(Box::new(move |facility, _, index| match facility { - Some(Facility::Server) => { - introspector.get_server_info(Client::server_info_callback); - } - Some(Facility::Sink) => { - introspector.get_sink_info_by_index(index, Client::sink_info_callback); - } - Some(Facility::Source) => { - introspector.get_source_info_by_index(index, Client::source_info_callback); - } - _ => (), - }))); - - connection.context.subscribe( - InterestMaskSet::SERVER | InterestMaskSet::SINK | InterestMaskSet::SOURCE, - |_| (), - ); - - let mut introspector = connection.context.introspect(); - - loop { - loop { - connection.iterate(true).unwrap(); - match connection.context.get_state() { - PulseState::Ready => break, - PulseState::Failed => return true, - _ => (), - } - } - - loop { - use std::sync::mpsc::TryRecvError; - let req = match recv_req.try_recv() { - Ok(x) => x, - Err(TryRecvError::Empty) => break, - Err(TryRecvError::Disconnected) => return false, - }; - - use ClientRequest::*; - match req { - GetDefaultDevice => { - introspector.get_server_info(Client::server_info_callback); - } - GetInfoByName(DeviceKind::Sink, name) => { - introspector.get_sink_info_by_name(&name, Client::sink_info_callback); - } - GetInfoByName(DeviceKind::Source, name) => { - introspector - .get_source_info_by_name(&name, Client::source_info_callback); - } - SetVolumeByName(DeviceKind::Sink, name, volumes) => { - introspector.set_sink_volume_by_name(&name, &volumes, None); - } - SetVolumeByName(DeviceKind::Source, name, volumes) => { - introspector.set_source_volume_by_name(&name, &volumes, None); - } - SetMuteByName(DeviceKind::Sink, name, mute) => { - introspector.set_sink_mute_by_name(&name, mute, None); - } - SetMuteByName(DeviceKind::Source, name, mute) => { - introspector.set_source_mute_by_name(&name, mute, None); - } - }; - } - } - })?; - - Ok(Client { send_req, ml_waker }) - } - - fn send(request: ClientRequest) -> Result<()> { - match CLIENT.as_ref() { - Ok(client) => { - client.send_req.send(request).unwrap(); - client.ml_waker.wake().unwrap(); - Ok(()) - } - Err(err) => Err(Error::new(format!( - "pulseaudio connection failed with error: {err}", - ))), - } - } - - fn server_info_callback(server_info: &ServerInfo) { - if let Some(default_sink) = server_info.default_sink_name.as_ref() { - *DEFAULT_SINK.lock().unwrap() = default_sink.to_string().into(); - } - - if let Some(default_source) = server_info.default_source_name.as_ref() { - *DEFAULT_SOURCE.lock().unwrap() = default_source.to_string().into(); - } - - Client::send_update_event(); - } - - fn get_info_callback>(result: ListResult) -> Option { - match result { - ListResult::End | ListResult::Error => None, - ListResult::Item(info) => info.try_into().ok(), - } - } - - fn sink_info_callback(result: ListResult<&SinkInfo>) { - if let Some(vol_info) = Self::get_info_callback(result) { - DEVICES - .lock() - .unwrap() - .insert((DeviceKind::Sink, vol_info.name.to_string()), vol_info); - - Client::send_update_event(); - } - } - - fn source_info_callback(result: ListResult<&SourceInfo>) { - if let Some(vol_info) = Self::get_info_callback(result) { - DEVICES - .lock() - .unwrap() - .insert((DeviceKind::Source, vol_info.name.to_string()), vol_info); - - Client::send_update_event(); - } - } - - fn send_update_event() { - EVENT_LISTENER - .lock() - .unwrap() - .retain(|notify| notify.upgrade().inspect(|x| x.notify_one()).is_some()); - } -} - -impl Device { - pub(super) fn new(device_kind: DeviceKind, name: Option) -> Result { - let notify = Arc::new(Notify::new()); - EVENT_LISTENER.lock().unwrap().push(Arc::downgrade(¬ify)); - - Client::send(ClientRequest::GetDefaultDevice)?; - - let device = Device { - name, - description: None, - active_port: None, - form_factor: None, - device_kind, - volume: None, - volume_avg: 0, - muted: false, - notify, - }; - - Client::send(ClientRequest::GetInfoByName(device_kind, device.name()))?; - - Ok(device) - } - - fn name(&self) -> String { - self.name - .clone() - .unwrap_or_else(|| self.device_kind.default_name().into()) - } - - fn volume(&mut self, volume: ChannelVolumes) { - self.volume = Some(volume); - self.volume_avg = (volume.avg().0 as f32 / Volume::NORMAL.0 as f32 * 100.0).round() as u32; - } -} - -#[async_trait::async_trait] -impl SoundDevice for Device { - fn volume(&self) -> u32 { - self.volume_avg - } - - fn muted(&self) -> bool { - self.muted - } - - fn output_name(&self) -> String { - self.name() - } - - fn output_description(&self) -> Option { - self.description.clone() - } - - fn active_port(&self) -> Option { - self.active_port.clone() - } - - fn form_factor(&self) -> Option<&str> { - self.form_factor.as_deref() - } - - async fn get_info(&mut self) -> Result<()> { - let devices = DEVICES.lock().unwrap(); - - if let Some(info) = devices.get(&(self.device_kind, self.name())) { - self.volume(info.volume); - self.muted = info.mute; - self.description.clone_from(&info.description); - self.active_port.clone_from(&info.active_port); - self.form_factor.clone_from(&info.form_factor); - } - - Ok(()) - } - - async fn set_volume(&mut self, step: i32, max_vol: Option) -> Result<()> { - let mut volume = self.volume.error("Volume unknown")?; - - // apply step to volumes - let step = (step as f32 * Volume::NORMAL.0 as f32 / 100.0).round() as i32; - for vol in volume.get_mut().iter_mut() { - let uncapped_vol = max(0, vol.0 as i32 + step) as u32; - let capped_vol = if let Some(vol_cap) = max_vol { - min( - uncapped_vol, - (vol_cap as f32 * Volume::NORMAL.0 as f32 / 100.0).round() as u32, - ) - } else { - uncapped_vol - }; - vol.0 = min(capped_vol, Volume::MAX.0); - } - - // update volumes - self.volume(volume); - Client::send(ClientRequest::SetVolumeByName( - self.device_kind, - self.name(), - volume, - ))?; - - Ok(()) - } - - async fn toggle(&mut self) -> Result<()> { - self.muted = !self.muted; - - Client::send(ClientRequest::SetMuteByName( - self.device_kind, - self.name(), - self.muted, - ))?; - - Ok(()) - } - - async fn wait_for_update(&mut self) -> Result<()> { - self.notify.notified().await; - Ok(()) - } -} - -/// Thread safe [`Mainloop`] waker. -/// -/// Has the same purpose as [`Mainloop::wake`], but can be shared across threads. -#[derive(Debug, Clone, Copy)] -struct MainloopWaker { - // Note: these fds are never closed, but this is OK because there is only one instance of this struct. - pipe_tx: RawFd, - pipe_rx: RawFd, -} - -impl MainloopWaker { - /// Create new waker. - fn new() -> io::Result { - let (pipe_rx, pipe_tx) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; - Ok(Self { - pipe_tx: pipe_tx.into_raw_fd(), - pipe_rx: pipe_rx.into_raw_fd(), - }) - } - - /// Attach this waker to a [`Mainloop`]. - /// - /// A waker should be attached to _one_ mainloop. - fn attach(self, ml: &MainloopApi) { - extern "C" fn wake_cb( - _: *const MainloopApi, - _: *mut libpulse_binding::mainloop::events::io::IoEventInternal, - fd: RawFd, - _: libpulse_binding::mainloop::events::io::FlagSet, - _: *mut c_void, - ) { - nix::unistd::read(fd, &mut [0; 32]).unwrap(); - } - - (ml.io_new.unwrap())( - ml as *const _, - self.pipe_rx, - libpulse_binding::mainloop::events::io::FlagSet::INPUT, - Some(wake_cb), - std::ptr::null_mut(), - ); - } - - /// Interrupt blocking [`Mainloop::iterate`]. - fn wake(self) -> io::Result<()> { - let buf = [0u8]; - let res = unsafe { libc::write(self.pipe_tx, buf.as_ptr().cast(), 1) }; - if res == -1 { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } - } -} diff --git a/src/blocks/speedtest.rs b/src/blocks/speedtest.rs deleted file mode 100644 index e0a1fd3e2a..0000000000 --- a/src/blocks/speedtest.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! Ping, download, and upload speeds -//! -//! This block which requires [`speedtest-cli`](https://github.com/sivel/speedtest-cli). -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" ^icon_ping $ping ^icon_net_down $speed_down ^icon_net_up $speed_up "` -//! `interval` | Update interval in seconds | `1800` -//! -//! Placeholder | Value | Type | Unit -//! -------------|----------------|--------|--------------- -//! `ping` | Ping delay | Number | Seconds -//! `speed_down` | Download speed | Number | Bits per second -//! `speed_up` | Upload speed | Number | Bits per second -//! -//! # Example -//! -//! Show only ping (with an icon) -//! -//! ```toml -//! [[block]] -//! block = "speedtest" -//! interval = 1800 -//! format = " ^icon_ping $ping " -//! ``` -//! -//! Hide ping and display speed in bytes per second each using 4 characters (without icons) -//! -//! ```toml -//! [[block]] -//! block = "speedtest" -//! interval = 1800 -//! format = " $speed_down.eng(w:4,u:B) $speed_up(w:4,u:B) " -//! ``` -//! -//! # Icons Used -//! - `ping` -//! - `net_down` -//! - `net_up` - -use super::prelude::*; -use tokio::process::Command; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - #[default(1800.into())] - pub interval: Seconds, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config - .format - .with_default(" ^icon_ping $ping ^icon_net_down $speed_down ^icon_net_up $speed_up ")?; - - let mut command = Command::new("speedtest-cli"); - command.arg("--json"); - - loop { - let output = command - .output() - .await - .error("failed to run 'speedtest-cli'")? - .stdout; - let output = - std::str::from_utf8(&output).error("'speedtest-cli' produced non-UTF8 output")?; - let output: SpeedtestCliOutput = - serde_json::from_str(output).error("'speedtest-cli' produced wrong JSON")?; - - let mut widget = Widget::new().with_format(format.clone()); - widget.set_values(map! { - "ping" => Value::seconds(output.ping * 1e-3), - "speed_down" => Value::bits(output.download), - "speed_up" => Value::bits(output.upload), - }); - api.set_widget(widget)?; - - select! { - _ = sleep(config.interval.0) => (), - _ = api.wait_for_update_request() => (), - } - } -} - -#[derive(Deserialize, Debug, Clone, Copy)] -struct SpeedtestCliOutput { - /// Download speed in bits per second - download: f64, - /// Upload speed in bits per second - upload: f64, - /// Ping time in ms - ping: f64, -} diff --git a/src/blocks/taskwarrior.rs b/src/blocks/taskwarrior.rs deleted file mode 100644 index 952299a39b..0000000000 --- a/src/blocks/taskwarrior.rs +++ /dev/null @@ -1,190 +0,0 @@ -//! The number of tasks from the taskwarrior list -//! -//! Clicking the right mouse button on the icon cycles the view of the block through the user's filters. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `interval` | Update interval in seconds | `600` (10min) -//! `warning_threshold` | The threshold of pending (or started) tasks when the block turns into a warning state | `10` -//! `critical_threshold` | The threshold of pending (or started) tasks when the block turns into a critical state | `20` -//! `filters` | A list of tables describing filters (see bellow) | ```[{name = "pending", filter = "-COMPLETED -DELETED"}]``` -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $count.eng(w:1) "` -//! `format_singular` | Same as `format` but for when exactly one task is pending. | `" $icon $count.eng(w:1) "` -//! `format_everything_done` | Same as `format` but for when all tasks are completed. | `" $icon $count.eng(w:1) "` -//! `data_location`| Directory in which taskwarrior stores its data files. Supports path expansions e.g. `~`. | `"~/.task"` -//! -//! ## Filter configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `name` | The name of the filter | -//! `filter` | Specifies the criteria that must be met for a task to be counted towards this filter | -//! `config_override` | An array containing configuration overrides, useful for explicitly setting context or other configuration variables | `[]` -//! -//! # Placeholders -//! -//! Placeholder | Value | Type | Unit -//! --------------|---------------------------------------------|--------|----- -//! `icon` | A static icon | Icon | - -//! `count` | The number of tasks matching current filter | Number | - -//! `filter_name` | The name of current filter | Text | - -//! -//! # Actions -//! -//! Action | Default button -//! --------------|--------------- -//! `next_filter` | Right -//! -//! # Example -//! -//! In this example, block will be hidden if `count` is zero. -//! -//! ```toml -//! [[block]] -//! block = "taskwarrior" -//! interval = 60 -//! format = " $icon count.eng(w:1) tasks " -//! format_singular = " $icon 1 task " -//! format_everything_done = "" -//! warning_threshold = 10 -//! critical_threshold = 20 -//! [[block.filters]] -//! name = "today" -//! filter = "+PENDING +OVERDUE or +DUETODAY" -//! [[block.filters]] -//! name = "some-project" -//! filter = "project:some-project +PENDING" -//! config_override = ["rc.context:none"] -//! ``` -//! -//! # Icons Used -//! - `tasks` - -use super::prelude::*; -use inotify::{Inotify, WatchMask}; -use tokio::process::Command; - -#[derive(Deserialize, Debug)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub interval: Seconds, - pub warning_threshold: u32, - pub critical_threshold: u32, - pub filters: Vec, - pub format: FormatConfig, - pub format_singular: FormatConfig, - pub format_everything_done: FormatConfig, - pub data_location: ShellString, -} - -impl Default for Config { - fn default() -> Self { - Self { - interval: Seconds::new(600), - warning_threshold: 10, - critical_threshold: 20, - filters: vec![Filter { - name: "pending".into(), - filter: "-COMPLETED -DELETED".into(), - config_override: Default::default(), - }], - format: default(), - format_singular: default(), - format_everything_done: default(), - data_location: ShellString::new("~/.task"), - } - } -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Right, None, "next_filter")])?; - - let format = config.format.with_default(" $icon $count.eng(w:1) ")?; - let format_singular = config - .format_singular - .with_default(" $icon $count.eng(w:1) ")?; - let format_everything_done = config - .format_everything_done - .with_default(" $icon $count.eng(w:1) ")?; - - let mut filters = config.filters.iter().cycle(); - let mut filter = filters.next().error("`filters` is empty")?; - - let notify = Inotify::init().error("Failed to start inotify")?; - notify - .watches() - .add(&*config.data_location.expand()?, WatchMask::MODIFY) - .error("Failed to watch data location")?; - let mut updates = notify - .into_event_stream([0; 1024]) - .error("Failed to create event stream")?; - - loop { - let number_of_tasks = get_number_of_tasks(filter).await?; - - let mut widget = Widget::new(); - - widget.set_format(match number_of_tasks { - 0 => format_everything_done.clone(), - 1 => format_singular.clone(), - _ => format.clone(), - }); - - widget.set_values(map! { - "icon" => Value::icon("tasks"), - "count" => Value::number(number_of_tasks), - "filter_name" => Value::text(filter.name.clone()), - }); - - widget.state = match number_of_tasks { - x if x >= config.critical_threshold => State::Critical, - x if x >= config.warning_threshold => State::Warning, - _ => State::Idle, - }; - - api.set_widget(widget)?; - - select! { - _ = sleep(config.interval.0) =>(), - _ = updates.next() => (), - _ = api.wait_for_update_request() => (), - Some(action) = actions.recv() => match action.as_ref() { - "next_filter" => { - filter = filters.next().unwrap(); - } - _ => (), - } - } - } -} - -async fn get_number_of_tasks(filter: &Filter) -> Result { - let args_iter = filter.config_override.iter().map(String::as_str).chain([ - "rc.gc=off", - &filter.filter, - "count", - ]); - let output = Command::new("task") - .args(args_iter) - .output() - .await - .error("failed to run taskwarrior for getting the number of tasks")? - .stdout; - std::str::from_utf8(&output) - .error("failed to get the number of tasks from taskwarrior (invalid UTF-8)")? - .trim() - .parse::() - .error("could not parse the result of taskwarrior") -} - -#[derive(Deserialize, Debug, Default, Clone)] -#[serde(deny_unknown_fields)] -pub struct Filter { - pub name: String, - pub filter: String, - #[serde(default)] - pub config_override: Vec, -} diff --git a/src/blocks/tea_timer.rs b/src/blocks/tea_timer.rs deleted file mode 100644 index 73e65fae77..0000000000 --- a/src/blocks/tea_timer.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! Timer -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | \" $icon {$time.duration(hms:true) \|}\" -//! `increment` | The numbers of seconds to add each time the block is clicked. | 30 -//! `done_cmd` | A command to run in `sh` when timer finishes. | None -//! -//! Placeholder | Value | Type | Unit -//! -----------------------|----------------------------------------------------------------|----------|--------------- -//! `icon` | A static icon | Icon | - -//! `time` | The time remaining on the timer | Duration | - -//! `hours` *DEPRECATED* | The hours remaining on the timer | Text | h -//! `minutes` *DEPRECATED* | The minutes remaining on the timer | Text | mn -//! `seconds` *DEPRECATED* | The seconds remaining on the timer | Text | s -//! -//! `time`, `hours`, `minutes`, and `seconds` are unset when the timer is inactive. -//! -//! `hours`, `minutes`, and `seconds` have been deprecated in favor of `time`. -//! -//! Action | Default button -//! ------------|--------------- -//! `increment` | Left / Wheel Up -//! `decrement` | Wheel Down -//! `reset` | Right -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "tea_timer" -//! format = " $icon {$minutes:$seconds |}" -//! done_cmd = "notify-send 'Timer Finished'" -//! ``` -//! -//! # Icons Used -//! - `tea` - -use super::prelude::*; -use crate::subprocess::spawn_shell; - -use std::time::{Duration, Instant}; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - pub increment: Option, - pub done_cmd: Option, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[ - (MouseButton::Left, None, "increment"), - (MouseButton::WheelUp, None, "increment"), - (MouseButton::WheelDown, None, "decrement"), - (MouseButton::Right, None, "reset"), - ])?; - - let interval: Seconds = 1.into(); - let mut timer = interval.timer(); - - let format = config - .format - .with_default(" $icon {$time.duration(hms:true) |}")?; - - let increment = Duration::from_secs(config.increment.unwrap_or(30)); - let mut timer_end = Instant::now(); - - let mut timer_was_active = false; - - loop { - let mut widget = Widget::new().with_format(format.clone()); - - let remaining_time = timer_end - Instant::now(); - let is_timer_active = !remaining_time.is_zero(); - - if !is_timer_active - && timer_was_active - && let Some(cmd) = &config.done_cmd - { - spawn_shell(cmd).error("done_cmd error")?; - } - timer_was_active = is_timer_active; - - let mut values = map!( - "icon" => Value::icon("tea"), - ); - - if is_timer_active { - values.insert("time".into(), Value::duration(remaining_time)); - let mut seconds = remaining_time.as_secs(); - - if format.contains_key("hours") { - let hours = seconds / 3_600; - values.insert("hours".into(), Value::text(format!("{hours:02}"))); - seconds %= 3_600; - } - - if format.contains_key("minutes") { - let minutes = seconds / 60; - values.insert("minutes".into(), Value::text(format!("{minutes:02}"))); - seconds %= 60; - } - - values.insert("seconds".into(), Value::text(format!("{seconds:02}"))); - } - - widget.set_values(values); - - api.set_widget(widget)?; - - select! { - _ = timer.tick(), if is_timer_active => (), - _ = api.wait_for_update_request() => (), - Some(action) = actions.recv() => { - let now = Instant::now(); - match action.as_ref() { - "increment" if is_timer_active => timer_end += increment, - "increment" => timer_end = now + increment, - "decrement" if is_timer_active => timer_end -= increment, - "reset" => timer_end = now, - _ => (), - } - } - } - } -} diff --git a/src/blocks/temperature.rs b/src/blocks/temperature.rs deleted file mode 100644 index 676c0760ad..0000000000 --- a/src/blocks/temperature.rs +++ /dev/null @@ -1,213 +0,0 @@ -//! The system temperature -//! -//! This block displays the system temperature, based on `libsensors` library. -//! -//! This block has two modes: "collapsed", which uses only color as an indicator, and "expanded", -//! which shows the content of a `format` string. The average, minimum, and maximum temperatures -//! are computed using all sensors displayed by `sensors`, or optionally filtered by `chip` and -//! `inputs`. -//! -//! Requires `libsensors` and appropriate kernel modules for your hardware. -//! -//! Run `sensors` command to list available chips and inputs. -//! -//! Note that the colour of the block is always determined by the maximum temperature across all -//! sensors, not the average. You may need to keep this in mind if you have a misbehaving sensor. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders | `" $icon $average avg, $max max "` -//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None` -//! `interval` | Update interval in seconds | `5` -//! `scale` | Either `"celsius"` or `"fahrenheit"` | `"celsius"` -//! `good` | Maximum temperature to set state to good | `20` °C (`68` °F) -//! `idle` | Maximum temperature to set state to idle | `45` °C (`113` °F) -//! `info` | Maximum temperature to set state to info | `60` °C (`140` °F) -//! `warning` | Maximum temperature to set state to warning. Beyond this temperature, state is set to critical | `80` °C (`176` °F) -//! `chip` | Narrows the results to a given chip name. `*` may be used as a wildcard. | None -//! `inputs` | Narrows the results to individual inputs reported by each chip. | None -//! -//! Action | Description | Default button -//! ----------------|-------------------------------------------|--------------- -//! `toggle_format` | Toggles between `format` and `format_alt` | Left -//! -//! Placeholder | Value | Type | Unit -//! ------------|--------------------------------------|--------|-------- -//! `min` | Minimum temperature among all inputs | Number | Degrees -//! `average` | Average temperature among all inputs | Number | Degrees -//! `max` | Maximum temperature among all inputs | Number | Degrees -//! -//! Note that when block is collapsed, no placeholders are provided. -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "temperature" -//! format = " $icon $max max " -//! format_alt = " $icon $min min, $max max, $average avg " -//! interval = 10 -//! chip = "*-isa-*" -//! ``` -//! -//! # Icons Used -//! - `thermometer` - -use super::prelude::*; -use sensors::FeatureType::SENSORS_FEATURE_TEMP; -use sensors::Sensors; -use sensors::SubfeatureType::SENSORS_SUBFEATURE_TEMP_INPUT; - -const DEFAULT_GOOD: f64 = 20.0; -const DEFAULT_IDLE: f64 = 45.0; -const DEFAULT_INFO: f64 = 60.0; -const DEFAULT_WARN: f64 = 80.0; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - pub format_alt: Option, - #[default(5.into())] - pub interval: Seconds, - pub scale: TemperatureScale, - pub good: Option, - pub idle: Option, - pub info: Option, - pub warning: Option, - pub chip: Option, - pub inputs: Option>, -} - -#[derive(Deserialize, Debug, SmartDefault, Clone, Copy, PartialEq, Eq)] -#[serde(rename_all = "lowercase")] -pub enum TemperatureScale { - #[default] - Celsius, - Fahrenheit, -} - -impl TemperatureScale { - #[allow(clippy::wrong_self_convention)] - pub fn from_celsius(self, val: f64) -> f64 { - match self { - Self::Celsius => val, - Self::Fahrenheit => val * 1.8 + 32.0, - } - } -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?; - - let mut format = config - .format - .with_default(" $icon $average avg, $max max ")?; - let mut format_alt = match &config.format_alt { - Some(f) => Some(f.with_default("")?), - None => None, - }; - - let good = config - .good - .unwrap_or_else(|| config.scale.from_celsius(DEFAULT_GOOD)); - let idle = config - .idle - .unwrap_or_else(|| config.scale.from_celsius(DEFAULT_IDLE)); - let info = config - .info - .unwrap_or_else(|| config.scale.from_celsius(DEFAULT_INFO)); - let warn = config - .warning - .unwrap_or_else(|| config.scale.from_celsius(DEFAULT_WARN)); - - loop { - let chip = config.chip.clone(); - let inputs = config.inputs.clone(); - let config_scale = config.scale; - let temp = tokio::task::spawn_blocking(move || { - let mut vals = Vec::new(); - let sensors = Sensors::new(); - let chips = match &chip { - Some(chip) => sensors - .detected_chips(chip) - .error("Failed to create chip iterator")?, - None => sensors.into_iter(), - }; - for chip in chips { - for feat in chip { - if *feat.feature_type() != SENSORS_FEATURE_TEMP { - continue; - } - if let Some(inputs) = &inputs { - let label = feat.get_label().error("Failed to get input label")?; - if !inputs.contains(&label) { - continue; - } - } - for subfeat in feat { - if *subfeat.subfeature_type() == SENSORS_SUBFEATURE_TEMP_INPUT - && let Ok(value) = subfeat.get_value() - { - if (-100.0..=150.0).contains(&value) { - vals.push(config_scale.from_celsius(value)); - } else { - eprintln!("Temperature ({value}) outside of range ([-100, 150])"); - } - } - } - } - } - Ok(vals) - }) - .await - .error("Failed to join tokio task")??; - - let min_temp = temp - .iter() - .min_by(|a, b| a.partial_cmp(b).unwrap()) - .cloned() - .unwrap_or(0.0); - let max_temp = temp - .iter() - .max_by(|a, b| a.partial_cmp(b).unwrap()) - .cloned() - .unwrap_or(0.0); - let avg_temp = temp.iter().sum::() / temp.len() as f64; - - let mut widget = Widget::new().with_format(format.clone()); - - widget.state = match max_temp { - x if x <= good => State::Good, - x if x <= idle => State::Idle, - x if x <= info => State::Info, - x if x <= warn => State::Warning, - _ => State::Critical, - }; - - widget.set_values(map! { - "icon" => Value::icon_progression_bound("thermometer", max_temp, good, warn), - "average" => Value::degrees(avg_temp), - "min" => Value::degrees(min_temp), - "max" => Value::degrees(max_temp), - }); - - api.set_widget(widget)?; - - select! { - _ = sleep(config.interval.0) => (), - _ = api.wait_for_update_request() => (), - Some(action) = actions.recv() => match action.as_ref() { - "toggle_format" => { - if let Some(format_alt) = &mut format_alt { - std::mem::swap(format_alt, &mut format); - } - } - _ => (), - } - } - } -} diff --git a/src/blocks/time.rs b/src/blocks/time.rs deleted file mode 100644 index 4f0fca5da8..0000000000 --- a/src/blocks/time.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! The current time. -//! -//! # Configuration -//! -//! Key | Values | Default -//! -----------|--------|-------- -//! `format` | Format string. See [chrono docs](https://docs.rs/chrono/0.3.0/chrono/format/strftime/index.html#specifiers) for all options. | `" $icon $timestamp.datetime() "` -//! `interval` | Update interval in seconds | `10` -//! `timezone` | A timezone specifier (e.g. "Europe/Lisbon") | Local timezone -//! -//! Placeholder | Value | Type | Unit -//! --------------|---------------------------------------------|----------|----- -//! `icon` | A static icon | Icon | - -//! `timestamp` | The current time | Datetime | - -//! -//! Action | Default button -//! ----------------|--------------- -//! `next_timezone` | Left -//! `prev_timezone` | Right -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "time" -//! interval = 60 -//! [block.format] -//! full = " $icon $timestamp.datetime(f:'%a %Y-%m-%d %R %Z', l:fr_BE) " -//! short = " $icon $timestamp.datetime(f:%R) " -//! ``` -//! -//! # Non Gregorian calendars -//! -//! You can use calendars other than the Gregorian calendar by adding the calendar specifier in the locale string. When using -//! this feature you can't use chrono style format string, and you should use one of the options provided by -//! the `icu4x` crate: `short`, `medium`, `long`, `full`. -//! -//! ** Only available using feature `icu_calendar`. ** -//! -//! ## Example -//! -//! ```toml -//! [[block]] -//! block = "time" -//! interval = 60 -//! format = "$timestamp.datetime(locale:'fa_IR-u-ca-persian', f:'full')" -//! ``` -//! -//! # Icons Used -//! - `time` - -use chrono::{Timelike as _, Utc}; -use chrono_tz::Tz; - -use super::prelude::*; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - #[default(10.into())] - pub interval: Seconds, - pub timezone: Option, -} - -#[derive(Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum Timezone { - Timezone(Tz), - Timezones(Vec), -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[ - (MouseButton::Left, None, "next_timezone"), - (MouseButton::Right, None, "prev_timezone"), - ])?; - - let format = config - .format - .with_default(" $icon $timestamp.datetime() ")?; - - let timezones = match config.timezone.clone() { - Some(tzs) => match tzs { - Timezone::Timezone(tz) => vec![tz], - Timezone::Timezones(tzs) => tzs, - }, - None => Vec::new(), - }; - - let prev_step_length = timezones.len().saturating_sub(2); - - let mut timezone_iter = timezones.iter().cycle(); - - let mut timezone = timezone_iter.next(); - - let interval_seconds = config.interval.seconds().max(1); - - let mut timer = tokio::time::interval_at( - tokio::time::Instant::now() + Duration::from_secs(interval_seconds), - Duration::from_secs(interval_seconds), - ); - timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - - loop { - let mut widget = Widget::new().with_format(format.clone()); - let now = Utc::now(); - - widget.set_values(map! { - "icon" => Value::icon("time"), - "timestamp" => Value::datetime(now, timezone.copied()) - }); - - api.set_widget(widget)?; - - let phase = now.second() as u64 % interval_seconds; - if phase != 0 { - timer.reset_after(Duration::from_secs(interval_seconds - phase)); - } - - tokio::select! { - _ = timer.tick() => (), - _ = api.wait_for_update_request() => (), - Some(action) = actions.recv() => match action.as_ref() { - "next_timezone" => { - timezone = timezone_iter.next(); - }, - "prev_timezone" => { - timezone = timezone_iter.nth(prev_step_length); - }, - _ => (), - } - } - } -} diff --git a/src/blocks/toggle.rs b/src/blocks/toggle.rs deleted file mode 100644 index 5115aaace9..0000000000 --- a/src/blocks/toggle.rs +++ /dev/null @@ -1,150 +0,0 @@ -//! A Toggle block -//! -//! You can add commands to be executed to disable the toggle (`command_off`), and to enable it -//! (`command_on`). If these command exit with a non-zero status, the block will not be toggled and -//! the block state will be changed to `critical` to give a visual warning of the failure. You also need to -//! specify a command to determine the state of the toggle (`command_state`). When the command outputs -//! nothing, the toggle is disabled, otherwise enabled. By specifying the interval property you can -//! let the command_state be executed continuously. -//! -//! To run those commands, the shell form `$SHELL` environment variable is used. If such variable -//! is not presented, `sh` is used. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders | `" $icon "` -//! `command_on` | Shell command to enable the toggle | **Required** -//! `command_off` | Shell command to disable the toggle | **Required** -//! `command_state` | Shell command to determine the state. Empty output => No, otherwise => Yes. | **Required** -//! `icon_on` | Icon override for the toggle button while on | `"toggle_on"` -//! `icon_off` | Icon override for the toggle button while off | `"toggle_off"` -//! `interval` | Update interval in seconds. If not set, `command_state` will run only on click. | None -//! `state_on` | [`State`] (color) of this block while on | [idle][State::Idle] -//! `state_off` | [`State`] (color) of this block while off | [idle][State::Idle] -//! -//! Placeholder | Value | Type | Unit -//! --------------|---------------------------------------------|--------|----- -//! `icon` | Icon based on toggle's state | Icon | - -//! -//! Action | Default button -//! ---------|--------------- -//! `toggle` | Left -//! -//! # Examples -//! -//! This is what can be used to toggle an external monitor configuration: -//! -//! ```toml -//! [[block]] -//! block = "toggle" -//! format = " $icon 4k " -//! command_state = "xrandr | grep 'DP1 connected 38' | grep -v eDP1" -//! command_on = "~/.screenlayout/4kmon_default.sh" -//! command_off = "~/.screenlayout/builtin.sh" -//! interval = 5 -//! state_on = "good" -//! state_off = "warning" -//! ``` -//! -//! # Icons Used -//! - `toggle_off` -//! - `toggle_on` - -use super::prelude::*; -use std::env; -use tokio::process::Command; - -#[derive(Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct Config { - pub format: FormatConfig, - pub command_on: String, - pub command_off: String, - pub command_state: String, - #[serde(default)] - pub icon_on: Option, - #[serde(default)] - pub icon_off: Option, - #[serde(default)] - pub interval: Option, - pub state_on: Option, - pub state_off: Option, -} - -async fn sleep_opt(dur: Option) { - match dur { - Some(dur) => tokio::time::sleep(dur).await, - None => std::future::pending().await, - } -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Left, None, "toggle")])?; - - let interval = config.interval.map(Duration::from_secs); - let mut widget = Widget::new().with_format(config.format.with_default(" $icon ")?); - - let icon_on = config.icon_on.as_deref().unwrap_or("toggle_on"); - let icon_off = config.icon_off.as_deref().unwrap_or("toggle_off"); - - let shell = env::var("SHELL").unwrap_or_else(|_| "sh".to_string()); - - loop { - // Check state - let output = Command::new(&shell) - .args(["-c", &config.command_state]) - .output() - .await - .error("Failed to run command_state")?; - let is_on = !std::str::from_utf8(&output.stdout) - .error("The output of command_state is invalid UTF-8")? - .trim() - .is_empty(); - - widget.set_values(map!( - "icon" => Value::icon( - if is_on { icon_on.to_string() } else { icon_off.to_string() } - ) - )); - if widget.state != State::Critical { - widget.state = if is_on { - config.state_on.unwrap_or(State::Idle) - } else { - config.state_off.unwrap_or(State::Idle) - }; - } - api.set_widget(widget.clone())?; - - loop { - select! { - _ = sleep_opt(interval) => break, - _ = api.wait_for_update_request() => break, - Some(action) = actions.recv() => match action.as_ref() { - "toggle" => { - let cmd = if is_on { - &config.command_off - } else { - &config.command_on - }; - let output = Command::new(&shell) - .args(["-c", cmd]) - .output() - .await - .error("Failed to run command")?; - if output.status.success() { - // Temporary; it will immediately be updated by the outer loop - widget.state = State::Idle; - break; - } else { - widget.state = State::Critical; - } - } - _ => (), - } - } - } - } -} diff --git a/src/blocks/uptime.rs b/src/blocks/uptime.rs deleted file mode 100644 index 85c561bae1..0000000000 --- a/src/blocks/uptime.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! System's uptime -//! -//! This block displays system uptime in terms of two biggest units, so minutes and seconds, or -//! hours and minutes or days and hours or weeks and days. -//! -//! # Configuration -//! -//! Key | Values | Default -//! -----------|----------------------------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders | `" $icon $uptime "` -//! `interval` | Update interval in seconds | `60` -//! -//! Placeholder | Value | Type | Unit -//! --------------------|-------------------------|----------|----- -//! `icon` | A static icon | Icon | - -//! `text` *DEPRECATED* | Current uptime | Text | - -//! `uptime` | Current uptime | Duration | - -//! -//! `text` has been deprecated in favor of `uptime`. -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "uptime" -//! interval = 3600 # update every hour -//! ``` -//! -//! # Used Icons -//! - `uptime` - -use super::prelude::*; -use tokio::fs::read_to_string; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - #[default(60.into())] - pub interval: Seconds, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let format = config.format.with_default(" $icon $uptime ")?; - - loop { - let uptime = read_to_string("/proc/uptime") - .await - .error("Failed to read /proc/uptime")?; - let mut seconds: u64 = uptime - .split('.') - .next() - .and_then(|u| u.parse().ok()) - .error("/proc/uptime has invalid content")?; - - let uptime = Duration::from_secs(seconds); - - let weeks = seconds / 604_800; - seconds %= 604_800; - let days = seconds / 86_400; - seconds %= 86_400; - let hours = seconds / 3_600; - seconds %= 3_600; - let minutes = seconds / 60; - seconds %= 60; - - let text = if weeks > 0 { - format!("{weeks}w {days}d") - } else if days > 0 { - format!("{days}d {hours}h") - } else if hours > 0 { - format!("{hours}h {minutes}m") - } else { - format!("{minutes}m {seconds}s") - }; - - let mut widget = Widget::new().with_format(format.clone()); - widget.set_values(map! { - "icon" => Value::icon("uptime"), - "text" => Value::text(text), - "uptime" => Value::duration(uptime) - }); - api.set_widget(widget)?; - - select! { - _ = sleep(config.interval.0) => (), - _ = api.wait_for_update_request() => (), - } - } -} diff --git a/src/blocks/vpn.rs b/src/blocks/vpn.rs deleted file mode 100644 index f7f9bd1f26..0000000000 --- a/src/blocks/vpn.rs +++ /dev/null @@ -1,180 +0,0 @@ -//! Shows the current connection status for VPN networks -//! -//! This widget toggles the connection on left click. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `driver` | Which vpn should be used . Available drivers are: `"nordvpn"` and `"mullvad"` | `"nordvpn"` -//! `interval` | Update interval in seconds. | `10` -//! `format_connected` | A string to customise the output in case the network is connected. See below for available placeholders. | `" VPN: $icon "` -//! `format_disconnected` | A string to customise the output in case the network is disconnected. See below for available placeholders. | `" VPN: $icon "` -//! `state_connected` | The widgets state if the vpn network is connected. | `info` -//! `state_disconnected` | The widgets state if the vpn network is disconnected | `idle` -//! -//! Placeholder | Value | Type | Unit -//! ------------|-----------------------------------------------------------|--------|------ -//! `icon` | A static icon | Icon | - -//! `country` | Country currently connected to | Text | - -//! `flag` | Country specific flag (depends on a font supporting them) | Text | - -//! -//! Action | Default button | Description -//! ----------|----------------|----------------------------------- -//! `toggle` | Left | toggles the vpn network connection -//! -//! # Drivers -//! -//! ## nordvpn -//! Behind the scenes the nordvpn driver uses the `nordvpn` command line binary. In order for this to work -//! properly the binary should be executable without root privileges. -//! -//! ## Mullvad -//! Behind the scenes the mullvad driver uses the `mullvad` command line binary. In order for this to work properly the binary should be executable and mullvad daemon should be running. -//! -//! # Example -//! -//! Shows the current vpn network state: -//! -//! ```toml -//! [[block]] -//! block = "vpn" -//! driver = "nordvpn" -//! interval = 10 -//! format_connected = "VPN: $icon " -//! format_disconnected = "VPN: $icon " -//! state_connected = "good" -//! state_disconnected = "warning" -//! ``` -//! -//! Possible values for `state_connected` and `state_disconnected`: -//! -//! ```text -//! warning -//! critical -//! good -//! info -//! idle -//! ``` -//! -//! # Icons Used -//! -//! - `net_vpn` -//! - `net_wired` -//! - `net_down` -//! - country code flags (if supported by font) -//! -//! Flags: They are not icons but unicode glyphs. You will need a font that -//! includes them. Tested with: - -mod nordvpn; -use nordvpn::NordVpnDriver; -mod mullvad; -use mullvad::MullvadDriver; - -use super::prelude::*; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(rename_all = "snake_case")] -pub enum DriverType { - #[default] - Nordvpn, - Mullvad, -} - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub driver: DriverType, - #[default(10.into())] - pub interval: Seconds, - pub format_connected: FormatConfig, - pub format_disconnected: FormatConfig, - pub state_connected: State, - pub state_disconnected: State, -} - -enum Status { - Connected { - country: String, - country_flag: String, - }, - Disconnected, - Error, -} - -impl Status { - fn icon(&self) -> Cow<'static, str> { - match self { - Status::Connected { .. } => "net_vpn".into(), - Status::Disconnected => "net_wired".into(), - Status::Error => "net_down".into(), - } - } -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Left, None, "toggle")])?; - - let format_connected = config.format_connected.with_default(" VPN: $icon ")?; - let format_disconnected = config.format_disconnected.with_default(" VPN: $icon ")?; - - let driver: Box = match config.driver { - DriverType::Nordvpn => Box::new(NordVpnDriver::new().await), - DriverType::Mullvad => Box::new(MullvadDriver::new().await), - }; - - loop { - let status = driver.get_status().await?; - - let mut widget = Widget::new(); - - widget.state = match &status { - Status::Connected { - country, - country_flag, - } => { - widget.set_values(map!( - "icon" => Value::icon(status.icon()), - "country" => Value::text(country.to_string()), - "flag" => Value::text(country_flag.to_string()), - - )); - widget.set_format(format_connected.clone()); - config.state_connected - } - Status::Disconnected => { - widget.set_values(map!( - "icon" => Value::icon(status.icon()), - )); - widget.set_format(format_disconnected.clone()); - config.state_disconnected - } - Status::Error => { - widget.set_values(map!( - "icon" => Value::icon(status.icon()), - )); - widget.set_format(format_disconnected.clone()); - State::Critical - } - }; - - api.set_widget(widget)?; - - select! { - _ = sleep(config.interval.0) => (), - _ = api.wait_for_update_request() => (), - Some(action) = actions.recv() => match action.as_ref() { - "toggle" => driver.toggle_connection(&status).await?, - _ => (), - } - } - } -} - -#[async_trait] -trait Driver { - async fn get_status(&self) -> Result; - async fn toggle_connection(&self, status: &Status) -> Result<()>; -} diff --git a/src/blocks/vpn/mullvad.rs b/src/blocks/vpn/mullvad.rs deleted file mode 100644 index 7cf2685e57..0000000000 --- a/src/blocks/vpn/mullvad.rs +++ /dev/null @@ -1,85 +0,0 @@ -use regex::Regex; -use std::process::Stdio; -use tokio::process::Command; - -use crate::blocks::prelude::*; -use crate::util::country_flag_from_iso_code; - -use super::{Driver, Status}; - -pub struct MullvadDriver { - regex_country_code: Regex, -} - -impl MullvadDriver { - pub async fn new() -> MullvadDriver { - MullvadDriver { - regex_country_code: Regex::new("Connected to ([a-z]{2}).*, ([A-Z][a-z]*).*\n").unwrap(), - } - } - - async fn run_network_command(arg: &str) -> Result<()> { - let code = Command::new("mullvad") - .args([arg]) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .spawn() - .error(format!("Problem running mullvad command: {arg}"))? - .wait() - .await - .error(format!("Problem running mullvad command: {arg}"))?; - - if code.success() { - Ok(()) - } else { - Err(Error::new(format!( - "mullvad command failed with nonzero status: {code:?}" - ))) - } - } -} - -#[async_trait] -impl Driver for MullvadDriver { - async fn get_status(&self) -> Result { - let stdout = Command::new("mullvad") - .args(["status"]) - .output() - .await - .error("Problem running mullvad command")? - .stdout; - - let status = String::from_utf8(stdout).error("mullvad produced non-UTF8 output")?; - - if status.contains("Disconnected") { - return Ok(Status::Disconnected); - } else if status.contains("Connected") { - let (country_flag, country) = self - .regex_country_code - .captures_iter(&status) - .next() - .map(|capture| { - let country_code = capture[1].to_uppercase(); - let country = capture[2].to_owned(); - let country_flag = country_flag_from_iso_code(&country_code); - (country_flag, country) - }) - .unwrap_or_default(); - - return Ok(Status::Connected { - country, - country_flag, - }); - } - Ok(Status::Error) - } - - async fn toggle_connection(&self, status: &Status) -> Result<()> { - match status { - Status::Connected { .. } => Self::run_network_command("disconnect").await?, - Status::Disconnected => Self::run_network_command("connect").await?, - Status::Error => (), - } - Ok(()) - } -} diff --git a/src/blocks/vpn/nordvpn.rs b/src/blocks/vpn/nordvpn.rs deleted file mode 100644 index 5856b8c9b1..0000000000 --- a/src/blocks/vpn/nordvpn.rs +++ /dev/null @@ -1,95 +0,0 @@ -use regex::Regex; -use std::process::Stdio; -use tokio::process::Command; - -use crate::blocks::prelude::*; -use crate::util::country_flag_from_iso_code; - -use super::{Driver, Status}; - -pub struct NordVpnDriver { - regex_country_code: Regex, -} - -impl NordVpnDriver { - pub async fn new() -> NordVpnDriver { - NordVpnDriver { - regex_country_code: Regex::new("^.*Hostname:\\s+([a-z]{2}).*$").unwrap(), - } - } - - async fn run_network_command(arg: &str) -> Result<()> { - Command::new("nordvpn") - .args([arg]) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .spawn() - .error(format!("Problem running nordvpn command: {arg}"))? - .wait() - .await - .error(format!("Problem running nordvpn command: {arg}"))?; - Ok(()) - } - - async fn find_line(stdout: &str, needle: &str) -> Option { - stdout - .lines() - .find(|s| s.contains(needle)) - .map(|s| s.to_owned()) - } -} - -#[async_trait] -impl Driver for NordVpnDriver { - async fn get_status(&self) -> Result { - let stdout = Command::new("nordvpn") - .args(["status"]) - .output() - .await - .error("Problem running nordvpn command")? - .stdout; - - let stdout = String::from_utf8(stdout).error("nordvpn produced non-UTF8 output")?; - let line_status = Self::find_line(&stdout, "Status:").await; - let line_country = Self::find_line(&stdout, "Country:").await; - let line_country_flag = Self::find_line(&stdout, "Hostname:").await; - if line_status.is_none() { - return Ok(Status::Error); - } - let line_status = line_status.unwrap(); - - if line_status.ends_with("Disconnected") { - return Ok(Status::Disconnected); - } else if line_status.ends_with("Connected") { - let country = match line_country { - Some(country_line) => country_line.rsplit(": ").next().unwrap().to_string(), - None => String::default(), - }; - let country_flag = match line_country_flag { - Some(country_line_flag) => self - .regex_country_code - .captures_iter(&country_line_flag) - .last() - .map(|capture| capture[1].to_owned()) - .map(|code| code.to_uppercase()) - .map(|code| country_flag_from_iso_code(&code)) - .unwrap_or_default(), - None => String::default(), - }; - return Ok(Status::Connected { - country, - country_flag, - }); - } - Ok(Status::Error) - } - - async fn toggle_connection(&self, status: &Status) -> Result<()> { - match status { - Status::Connected { .. } => Self::run_network_command("disconnect").await?, - Status::Disconnected => Self::run_network_command("connect").await?, - Status::Error => (), - } - Ok(()) - } -} diff --git a/src/blocks/watson.rs b/src/blocks/watson.rs deleted file mode 100644 index 2b1942b098..0000000000 --- a/src/blocks/watson.rs +++ /dev/null @@ -1,232 +0,0 @@ -//! Watson statistics -//! -//! [Watson](http://tailordev.github.io/Watson/) is a simple CLI time tracking application. This block will show the name of your current active project, tags and optionally recorded time. Clicking the widget will toggle the `show_time` variable dynamically. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders | `" $text |"` -//! `show_time` | Whether to show recorded time. | `false` -//! `state_path` | Path to the Watson state file. Supports path expansions e.g. `~`. | `$XDG_CONFIG_HOME/watson/state` -//! `interval` | Update interval, in seconds. | `60` -//! -//! Placeholder | Value | Type | Unit -//! --------------|-------------------------|--------|----- -//! `text` | Current activity | Text | - -//! -//! Action | Description | Default button -//! -------------------|---------------------------------|--------------- -//! `toggle_show_time` | Toggle the value of `show_time` | Left -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "watson" -//! show_time = true -//! state_path = "~/.config/watson/state" -//! ``` -//! -//! # TODO -//! - Extend functionality: start / stop watson using this block - -use chrono::{DateTime, offset::Local}; -use dirs::config_dir; -use inotify::{Inotify, WatchMask}; -use serde::de::Deserializer; -use std::path::PathBuf; -use tokio::fs::read_to_string; - -use super::prelude::*; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - pub format: FormatConfig, - pub state_path: Option, - #[default(60.into())] - pub interval: Seconds, - pub show_time: bool, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Left, None, "toggle_show_time")])?; - - let format = config.format.with_default(" $text |")?; - - let mut show_time = config.show_time; - - let (state_dir, state_file, state_path) = match &config.state_path { - Some(p) => { - let mut p: PathBuf = (*p.expand()?).into(); - let path = p.clone(); - let file = p.file_name().error("Failed to parse state_dir")?.to_owned(); - p.pop(); - (p, file, path) - } - None => { - let mut path = config_dir().error("xdg config directory not found")?; - path.push("watson"); - let dir = path.clone(); - path.push("state"); - (dir, "state".into(), path) - } - }; - - let notify = Inotify::init().error("Failed to start inotify")?; - notify - .watches() - .add(&state_dir, WatchMask::CREATE | WatchMask::MOVED_TO) - .error("Failed to watch watson state directory")?; - let mut state_updates = notify - .into_event_stream([0; 1024]) - .error("Failed to create event stream")?; - - let mut timer = config.interval.timer(); - let mut prev_state = None; - - loop { - let state = read_to_string(&state_path) - .await - .error("Failed to read state file")?; - let state = serde_json::from_str(&state).unwrap_or(WatsonState::Idle {}); - - let mut widget = Widget::new().with_format(format.clone()); - - match state { - state @ WatsonState::Active { .. } => { - widget.state = State::Good; - widget.set_values(map!( - "text" => Value::text(state.format(show_time, "started", format_delta_past)) - )); - prev_state = Some(state); - } - WatsonState::Idle {} => { - if let Some(prev @ WatsonState::Active { .. }) = &prev_state { - // The previous state was active, which means that we just now stopped the time - // tracking. This means that we could show some statistics. - widget.state = State::Idle; - widget.set_values(map!( - "text" => Value::text(prev.format(true, "stopped", format_delta_after)) - )); - } else { - // File is empty which means that there is currently no active time tracking, - // and the previous state wasn't time tracking neither so we reset the - // contents. - widget.state = State::Idle; - widget.set_values(Values::default()); - } - prev_state = Some(state); - } - } - - api.set_widget(widget)?; - - loop { - select! { - _ = timer.tick() => break, - _ = api.wait_for_update_request() => break, - Some(update) = state_updates.next() => { - let update = update.error("Bad inotify update")?; - if update.name.is_some_and(|x| state_file == x) { - break; - } - } - Some(action) = actions.recv() => match action.as_ref() { - "toggle_show_time" => { - show_time = !show_time; - break; - } - _ => (), - } - } - } - } -} - -fn format_delta_past(delta: &chrono::Duration) -> String { - let spans = &[ - ("week", delta.num_weeks()), - ("day", delta.num_days()), - ("hour", delta.num_hours()), - ("minute", delta.num_minutes()), - ]; - - spans - .iter() - .filter(|&(_, n)| *n != 0) - .map(|&(label, n)| format!("{n} {label}{} ago", if n > 1 { "s" } else { "" })) - .next() - .unwrap_or_else(|| "now".into()) -} - -fn format_delta_after(delta: &chrono::Duration) -> String { - let spans = &[ - ("week", delta.num_weeks()), - ("day", delta.num_days()), - ("hour", delta.num_hours()), - ("minute", delta.num_minutes()), - ("second", delta.num_seconds()), - ]; - - spans - .iter() - .find(|&(_, n)| *n != 0) - .map(|&(label, n)| format!("after {n} {label}{}", if n > 1 { "s" } else { "" })) - .unwrap_or_else(|| "now".into()) -} - -#[derive(Deserialize, Clone, Debug)] -#[serde(untagged)] -enum WatsonState { - Active { - project: String, - #[serde(deserialize_with = "deserialize_local_timestamp")] - start: DateTime, - tags: Vec, - }, - // This matches an empty JSON object - Idle {}, -} - -impl WatsonState { - fn format(&self, show_time: bool, verb: &str, f: fn(&chrono::Duration) -> String) -> String { - if let WatsonState::Active { - project, - start, - tags, - } = self - { - let mut s = project.clone(); - if let [first, other @ ..] = &tags[..] { - s.push_str(" ["); - s.push_str(first); - for tag in other { - s.push(' '); - s.push_str(tag); - } - s.push(']'); - } - if show_time { - s.push(' '); - s.push_str(verb); - let delta = Local::now() - *start; - s.push(' '); - s.push_str(&f(&delta)); - } - s - } else { - panic!("WatsonState::Idle does not have a specified format") - } - } -} - -pub fn deserialize_local_timestamp<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - use chrono::TimeZone as _; - i64::deserialize(deserializer).map(|seconds| Local.timestamp_opt(seconds, 0).single().unwrap()) -} diff --git a/src/blocks/weather.rs b/src/blocks/weather.rs deleted file mode 100644 index 1735c0418b..0000000000 --- a/src/blocks/weather.rs +++ /dev/null @@ -1,673 +0,0 @@ -//! Current weather -//! -//! This block displays local weather and temperature information. In order to use this block, you -//! will need access to a supported weather API service. At the time of writing, OpenWeatherMap, -//! met.no, and the US National Weather Service are supported. -//! -//! Configuring this block requires configuring a weather service, which may require API keys and -//! other parameters. -//! -//! If using the `autolocate` feature, set the autolocate update interval such that you do not exceed ipapi.co's free daily limit of 1000 hits. Or use `autolocate_interval = "once"` to only run on initialization. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `service` | The configuration of a weather service (see below). | **Required** -//! `format` | A string to customise the output of this block. See below for available placeholders. Text may need to be escaped, refer to [Escaping Text](#escaping-text). | `" $icon $weather $temp "` -//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None` -//! `interval` | Update interval, in seconds. | `600` -//! `autolocate` | Gets your location using the ipapi.co IP location service (no API key required). If the API call fails then the block will fallback to service specific location config. | `false` -//! `autolocate_interval` | Update interval for `autolocate` in seconds or "once" | `interval` -//! -//! # OpenWeatherMap Options -//! -//! To use the service you will need a (free) API key. -//! -//! Key | Values | Required | Default -//! ----|--------|----------|-------- -//! `name` | `openweathermap`. | Yes | None -//! `api_key` | Your OpenWeatherMap API key. | Yes | None -//! `coordinates` | GPS latitude longitude coordinates as a tuple, example: `["39.2362","9.3317"]` | Yes* | None -//! `city_id` | OpenWeatherMap's ID for the city. (Deprecated) | Yes* | None -//! `place` | OpenWeatherMap 'By {city name},{state code},{country code}' search query. See [here](https://openweathermap.org/api/geocoding-api#direct_name). Consumes an additional API call | Yes* | None -//! `zip` | OpenWeatherMap 'By {zip code},{country code}' search query. See [here](https://openweathermap.org/api/geocoding-api#direct_zip). Consumes an additional API call | Yes* | None -//! `units` | Either `"metric"` or `"imperial"`. | No | `"metric"` -//! `lang` | Language code. See [here](https://openweathermap.org/current#multi). Currently only affects `weather_verbose` key. | No | `"en"` -//! `forecast_hours` | How many hours should be forecast (must be increments of 3 hours, max 120 hours) | No | 12 -//! -//! One of `coordinates`, `city_id`, `place`, or `zip` is required. If more than one are supplied, `coordinates` takes precedence over `city_id` which takes precedence over `place` which takes precedence over `zip`. -//! -//! The options `api_key`, `city_id`, `place`, `zip`, can be omitted from configuration, -//! in which case they must be provided in the environment variables -//! `OPENWEATHERMAP_API_KEY`, `OPENWEATHERMAP_CITY_ID`, `OPENWEATHERMAP_PLACE`, `OPENWEATHERMAP_ZIP`. -//! -//! Forecasts are only fetched if forecast_hours > 0 and the format has keys related to forecast. -//! -//! # met.no Options -//! -//! Key | Values | Required | Default -//! ----|--------|----------|-------- -//! `name` | `metno`. | Yes | None -//! `coordinates` | GPS latitude longitude coordinates as a tuple, example: `["39.2362","9.3317"]` | Required if `autolocate = false` | None -//! `lang` | Language code: `en`, `nn` or `nb` | No | `en` -//! `altitude` | Meters above sea level of the ground | No | Approximated by server -//! `forecast_hours` | How many hours should be forecast | No | 12 -//! -//! Met.no does not support location name, but if autolocate is enabled then autolocate's city value is used. -//! -//! # US National Weather Service Options -//! -//! Key | Values | Required | Default -//! ----|--------|----------|-------- -//! `name` | `nws`. | Yes | None -//! `coordinates` | GPS latitude longitude coordinates as a tuple, example: `["39.2362","9.3317"]` | Required if `autolocate = false` | None -//! `forecast_hours` | How many hours should be forecast | No | 12 -//! `units` | Either `"metric"` or `"imperial"`. | No | `"metric"` -//! -//! Forecasts gather statistics from each hour between now and the `forecast_hours` value, and -//! provide predicted weather at the set number of hours into the future. -//! -//! # Available Format Keys -//! -//! Key | Value | Type | Unit -//! ---------------------------------------------|-------------------------------------------------------------------------------|----------|----- -//! `location` | Location name (exact format depends on the service) | Text | - -//! `icon{,_ffin}` | Icon representing the weather | Icon | - -//! `weather{,_ffin}` | Textual brief description of the weather, e.g. "Raining" | Text | - -//! `weather_verbose{,_ffin}` | Textual verbose description of the weather, e.g. "overcast clouds" | Text | - -//! `temp{,_{favg,fmin,fmax,ffin}}` | Temperature | Number | degrees -//! `apparent{,_{favg,fmin,fmax,ffin}}` | Australian Apparent Temperature | Number | degrees -//! `humidity{,_{favg,fmin,fmax,ffin}}` | Humidity | Number | % -//! `wind{,_{favg,fmin,fmax,ffin}}` | Wind speed | Number | - -//! `wind_kmh{,_{favg,fmin,fmax,ffin}}` | Wind speed. The wind speed in km/h | Number | - -//! `direction{,_{favg,fmin,fmax,ffin}}` | Wind direction, e.g. "NE" | Text | - -//! `sunrise` | Time of sunrise | DateTime | - -//! `sunset` | Time of sunset | DateTime | - -//! -//! You can use the suffixes noted above to get the following: -//! -//! Suffix | Description -//! ----------|------------ -//! None | Current weather -//! `_favg` | Average forecast value -//! `_fmin` | Minimum forecast value -//! `_fmax` | Maximum forecast value -//! `_ffin` | Final forecast value -//! -//! Action | Description | Default button -//! ----------------|-------------------------------------------|--------------- -//! `toggle_format` | Toggles between `format` and `format_alt` | Left -//! -//! # Examples -//! -//! Show detailed weather in San Francisco through the OpenWeatherMap service: -//! -//! ```toml -//! [[block]] -//! block = "weather" -//! format = " $icon $weather ($location) $temp, $wind m/s $direction " -//! format_alt = " $icon_ffin Forecast (9 hour avg) {$temp_favg ({$temp_fmin}-{$temp_fmax})|Unavailable} " -//! [block.service] -//! name = "openweathermap" -//! api_key = "XXX" -//! city_id = "5398563" -//! units = "metric" -//! forecast_hours = 9 -//! ``` -//! -//! Show sunrise and sunset times in null island -//! -//! ```toml -//! [[block]] -//! block = "weather" -//! format = "up $sunrise.datetime(f:'%R') down $sunset.datetime(f:'%R')" -//! [block.service] -//! name = "metno" -//! coordinates = ["0", "0"] -//! ``` -//! -//! # Used Icons -//! -//! - `weather_sun` (when weather is reported as "Clear" during the day) -//! - `weather_moon` (when weather is reported as "Clear" at night) -//! - `weather_clouds` (when weather is reported as "Clouds" during the day) -//! - `weather_clouds_night` (when weather is reported as "Clouds" at night) -//! - `weather_fog` (when weather is reported as "Fog" or "Mist" during the day) -//! - `weather_fog_night` (when weather is reported as "Fog" or "Mist" at night) -//! - `weather_rain` (when weather is reported as "Rain" or "Drizzle" during the day) -//! - `weather_rain_night` (when weather is reported as "Rain" or "Drizzle" at night) -//! - `weather_snow` (when weather is reported as "Snow") -//! - `weather_thunder` (when weather is reported as "Thunderstorm" during the day) -//! - `weather_thunder_night` (when weather is reported as "Thunderstorm" at night) - -use chrono::{DateTime, Utc}; -use sunrise::{SolarDay, SolarEvent}; - -use crate::formatting::Format; -pub(super) use crate::geolocator::IPAddressInfo; - -use super::prelude::*; - -pub mod met_no; -pub mod nws; -pub mod open_weather_map; - -#[derive(Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct Config { - #[serde(default = "default_interval")] - pub interval: Seconds, - #[serde(default)] - pub format: FormatConfig, - pub format_alt: Option, - pub service: WeatherService, - #[serde(default)] - pub autolocate: bool, - pub autolocate_interval: Option, -} - -fn default_interval() -> Seconds { - Seconds::new(600) -} - -#[async_trait] -trait WeatherProvider { - async fn get_weather( - &self, - autolocated_location: Option<&IPAddressInfo>, - need_forecast: bool, - ) -> Result; -} - -#[derive(Deserialize, Debug)] -#[serde(tag = "name", rename_all = "lowercase")] -pub enum WeatherService { - OpenWeatherMap(open_weather_map::Config), - MetNo(met_no::Config), - Nws(nws::Config), -} - -#[derive(Clone, Copy, Default)] -enum WeatherIcon { - Clear { - is_night: bool, - }, - Clouds { - is_night: bool, - }, - Fog { - is_night: bool, - }, - Rain { - is_night: bool, - }, - Snow, - Thunder { - is_night: bool, - }, - #[default] - Default, -} - -impl WeatherIcon { - fn to_icon_str(self) -> &'static str { - match self { - Self::Clear { is_night: false } => "weather_sun", - Self::Clear { is_night: true } => "weather_moon", - Self::Clouds { is_night: false } => "weather_clouds", - Self::Clouds { is_night: true } => "weather_clouds_night", - Self::Fog { is_night: false } => "weather_fog", - Self::Fog { is_night: true } => "weather_fog_night", - Self::Rain { is_night: false } => "weather_rain", - Self::Rain { is_night: true } => "weather_rain_night", - Self::Snow => "weather_snow", - Self::Thunder { is_night: false } => "weather_thunder", - Self::Thunder { is_night: true } => "weather_thunder_night", - Self::Default => "weather_default", - } - } -} - -#[derive(Default)] -struct WeatherMoment { - icon: WeatherIcon, - weather: String, - weather_verbose: String, - temp: f64, - apparent: f64, - humidity: f64, - wind: f64, - wind_kmh: f64, - wind_direction: Option, -} - -struct ForecastAggregate { - temp: f64, - apparent: f64, - humidity: f64, - wind: f64, - wind_kmh: f64, - wind_direction: Option, -} - -struct ForecastAggregateSegment { - temp: Option, - apparent: Option, - humidity: Option, - wind: Option, - wind_kmh: Option, - wind_direction: Option, -} - -struct WeatherResult { - location: String, - current_weather: WeatherMoment, - forecast: Option, - sunrise: DateTime, - sunset: DateTime, -} - -impl WeatherResult { - fn into_values(self) -> Values { - let mut values = map! { - "location" => Value::text(self.location), - //current_weather - "icon" => Value::icon(self.current_weather.icon.to_icon_str()), - "temp" => Value::degrees(self.current_weather.temp), - "apparent" => Value::degrees(self.current_weather.apparent), - "humidity" => Value::percents(self.current_weather.humidity), - "weather" => Value::text(self.current_weather.weather), - "weather_verbose" => Value::text(self.current_weather.weather_verbose), - "wind" => Value::number(self.current_weather.wind), - "wind_kmh" => Value::number(self.current_weather.wind_kmh), - "direction" => Value::text(convert_wind_direction(self.current_weather.wind_direction).into()), - "sunrise" => Value::datetime(self.sunrise, None), - "sunset" => Value::datetime(self.sunset, None), - }; - - if let Some(forecast) = self.forecast { - macro_rules! map_forecasts { - ({$($suffix: literal => $src: expr),* $(,)?}) => { - map!{ @extend values - $( - concat!("temp_f", $suffix) => Value::degrees($src.temp), - concat!("apparent_f", $suffix) => Value::degrees($src.apparent), - concat!("humidity_f", $suffix) => Value::percents($src.humidity), - concat!("wind_f", $suffix) => Value::number($src.wind), - concat!("wind_kmh_f", $suffix) => Value::number($src.wind_kmh), - concat!("direction_f", $suffix) => Value::text(convert_wind_direction($src.wind_direction).into()), - )* - } - }; - } - map_forecasts!({ - "avg" => forecast.avg, - "min" => forecast.min, - "max" => forecast.max, - "fin" => forecast.fin, - }); - - map! { @extend values - "icon_ffin" => Value::icon(forecast.fin.icon.to_icon_str()), - "weather_ffin" => Value::text(forecast.fin.weather.clone()), - "weather_verbose_ffin" => Value::text(forecast.fin.weather_verbose.clone()), - } - } - - values - } -} - -struct Forecast { - avg: ForecastAggregate, - min: ForecastAggregate, - max: ForecastAggregate, - fin: WeatherMoment, -} - -impl Forecast { - fn new(data: &[ForecastAggregateSegment], fin: WeatherMoment) -> Self { - let mut temp_avg = 0.0; - let mut temp_count = 0.0; - let mut apparent_avg = 0.0; - let mut apparent_count = 0.0; - let mut humidity_avg = 0.0; - let mut humidity_count = 0.0; - let mut wind_north_avg = 0.0; - let mut wind_east_avg = 0.0; - let mut wind_kmh_north_avg = 0.0; - let mut wind_kmh_east_avg = 0.0; - let mut wind_count = 0.0; - let mut max = ForecastAggregate { - temp: f64::MIN, - apparent: f64::MIN, - humidity: f64::MIN, - wind: f64::MIN, - wind_kmh: f64::MIN, - wind_direction: None, - }; - let mut min = ForecastAggregate { - temp: f64::MAX, - apparent: f64::MAX, - humidity: f64::MAX, - wind: f64::MAX, - wind_kmh: f64::MAX, - wind_direction: None, - }; - for val in data { - if let Some(temp) = val.temp { - temp_avg += temp; - max.temp = max.temp.max(temp); - min.temp = min.temp.min(temp); - temp_count += 1.0; - } - if let Some(apparent) = val.apparent { - apparent_avg += apparent; - max.apparent = max.apparent.max(apparent); - min.apparent = min.apparent.min(apparent); - apparent_count += 1.0; - } - if let Some(humidity) = val.humidity { - humidity_avg += humidity; - max.humidity = max.humidity.max(humidity); - min.humidity = min.humidity.min(humidity); - humidity_count += 1.0; - } - - if let Some(wind) = val.wind - && let Some(wind_kmh) = val.wind_kmh - { - if let Some(degrees) = val.wind_direction { - let (sin, cos) = degrees.to_radians().sin_cos(); - wind_north_avg += wind * cos; - wind_east_avg += wind * sin; - wind_kmh_north_avg += wind_kmh * cos; - wind_kmh_east_avg += wind_kmh * sin; - wind_count += 1.0; - } - - if wind > max.wind { - max.wind_direction = val.wind_direction; - max.wind = wind; - max.wind_kmh = wind_kmh; - } - - if wind < min.wind { - min.wind_direction = val.wind_direction; - min.wind = wind; - min.wind_kmh = wind_kmh; - } - } - } - - temp_avg /= temp_count; - humidity_avg /= humidity_count; - apparent_avg /= apparent_count; - - // Calculate the wind results separately, discarding invalid wind values - let (wind_avg, wind_kmh_avg, wind_direction_avg) = if wind_count == 0.0 { - (0.0, 0.0, None) - } else { - ( - wind_east_avg.hypot(wind_north_avg) / wind_count, - wind_kmh_east_avg.hypot(wind_kmh_north_avg) / wind_count, - Some( - wind_east_avg - .atan2(wind_north_avg) - .to_degrees() - .rem_euclid(360.0), - ), - ) - }; - - let avg = ForecastAggregate { - temp: temp_avg, - apparent: apparent_avg, - humidity: humidity_avg, - wind: wind_avg, - wind_kmh: wind_kmh_avg, - wind_direction: wind_direction_avg, - }; - Self { avg, min, max, fin } - } -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?; - - let mut format = config.format.with_default(" $icon $weather $temp ")?; - let mut format_alt = match &config.format_alt { - Some(f) => Some(f.with_default("")?), - None => None, - }; - - let provider: Box = match &config.service { - WeatherService::MetNo(service_config) => Box::new(met_no::Service::new(service_config)?), - WeatherService::OpenWeatherMap(service_config) => { - Box::new(open_weather_map::Service::new(config.autolocate, service_config).await?) - } - WeatherService::Nws(service_config) => { - Box::new(nws::Service::new(config.autolocate, service_config).await?) - } - }; - - let autolocate_interval = config.autolocate_interval.unwrap_or(config.interval); - let need_forecast = need_forecast(&format, format_alt.as_ref()); - - let mut timer = config.interval.timer(); - - loop { - let location = if config.autolocate { - let fetch = || api.find_ip_location(&REQWEST_CLIENT, autolocate_interval.0); - Some(fetch.retry(ExponentialBuilder::default()).await?) - } else { - None - }; - - let fetch = || provider.get_weather(location.as_ref(), need_forecast); - let data = fetch.retry(ExponentialBuilder::default()).await?; - let data_values = data.into_values(); - - loop { - let mut widget = Widget::new().with_format(format.clone()); - widget.set_values(data_values.clone()); - api.set_widget(widget)?; - - select! { - _ = timer.tick() => break, - _ = api.wait_for_update_request() => break, - Some(action) = actions.recv() => match action.as_ref() { - "toggle_format" => { - if let Some(ref mut format_alt) = format_alt { - std::mem::swap(format_alt, &mut format); - } - } - _ => (), - } - } - } - } -} - -fn need_forecast(format: &Format, format_alt: Option<&Format>) -> bool { - fn has_forecast_key(format: &Format) -> bool { - macro_rules! format_suffix { - ($($suffix: literal),* $(,)?) => { - false - $( - || format.contains_key(concat!("temp_f", $suffix)) - || format.contains_key(concat!("apparent_f", $suffix)) - || format.contains_key(concat!("humidity_f", $suffix)) - || format.contains_key(concat!("wind_f", $suffix)) - || format.contains_key(concat!("wind_kmh_f", $suffix)) - || format.contains_key(concat!("direction_f", $suffix)) - )* - }; - } - - format_suffix!("avg", "min", "max", "fin") - || format.contains_key("icon_ffin") - || format.contains_key("weather_ffin") - || format.contains_key("weather_verbose_ffin") - } - has_forecast_key(format) || format_alt.is_some_and(has_forecast_key) -} - -fn calculate_sunrise_sunset( - lat: f64, - lon: f64, - altitude: Option, -) -> Result<(DateTime, DateTime)> { - let date = Utc::now().date_naive(); - let coordinates = sunrise::Coordinates::new(lat, lon).error("Invalid coordinates")?; - let solar_day = SolarDay::new(coordinates, date).with_altitude(altitude.unwrap_or_default()); - - Ok(( - solar_day.event_time(SolarEvent::Sunrise), - solar_day.event_time(SolarEvent::Sunset), - )) -} - -#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq, SmartDefault)] -#[serde(rename_all = "lowercase")] -enum UnitSystem { - #[default] - Metric, - Imperial, -} - -// Convert wind direction in azimuth degrees to abbreviation names -fn convert_wind_direction(direction_opt: Option) -> &'static str { - match direction_opt { - Some(direction) => match direction.round() as i64 { - 24..=68 => "NE", - 69..=113 => "E", - 114..=158 => "SE", - 159..=203 => "S", - 204..=248 => "SW", - 249..=293 => "W", - 294..=338 => "NW", - _ => "N", - }, - None => "-", - } -} - -/// Compute the Australian Apparent Temperature from metric units -fn australian_apparent_temp(temp: f64, humidity: f64, wind_speed: f64) -> f64 { - let exponent = 17.27 * temp / (237.7 + temp); - let water_vapor_pressure = humidity * 0.06105 * exponent.exp(); - temp + 0.33 * water_vapor_pressure - 0.7 * wind_speed - 4.0 -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_new_forecast_average_wind_speed() { - let mut degrees = 0.0; - while degrees < 360.0 { - let forecast = Forecast::new( - &[ - ForecastAggregateSegment { - temp: None, - apparent: None, - humidity: None, - wind: Some(1.0), - wind_kmh: Some(3.6), - wind_direction: Some(degrees), - }, - ForecastAggregateSegment { - temp: None, - apparent: None, - humidity: None, - wind: Some(2.0), - wind_kmh: Some(7.2), - wind_direction: Some(degrees), - }, - ], - WeatherMoment::default(), - ); - assert!((forecast.avg.wind - 1.5).abs() < 0.1); - assert!((forecast.avg.wind_kmh - 5.4).abs() < 0.1); - assert!((forecast.avg.wind_direction.unwrap() - degrees).abs() < 0.1); - - degrees += 15.0; - } - } - - #[test] - fn test_new_forecast_average_wind_degrees() { - let mut degrees = 0.0; - while degrees < 360.0 { - let low = degrees - 1.0; - let high = degrees + 1.0; - let forecast = Forecast::new( - &[ - ForecastAggregateSegment { - temp: None, - apparent: None, - humidity: None, - wind: Some(1.0), - wind_kmh: Some(3.6), - wind_direction: Some(low), - }, - ForecastAggregateSegment { - temp: None, - apparent: None, - humidity: None, - wind: Some(1.0), - wind_kmh: Some(3.6), - wind_direction: Some(high), - }, - ], - WeatherMoment::default(), - ); - // For winds of equal strength the direction should will be the - // average of the low and high degrees - assert!((forecast.avg.wind_direction.unwrap() - degrees).abs() < 0.1); - - degrees += 15.0; - } - } - - #[test] - fn test_new_forecast_average_wind_speed_and_degrees() { - let mut degrees = 0.0; - while degrees < 360.0 { - let low = degrees - 1.0; - let high = degrees + 1.0; - let forecast = Forecast::new( - &[ - ForecastAggregateSegment { - temp: None, - apparent: None, - humidity: None, - wind: Some(1.0), - wind_kmh: Some(3.6), - wind_direction: Some(low), - }, - ForecastAggregateSegment { - temp: None, - apparent: None, - humidity: None, - wind: Some(2.0), - wind_kmh: Some(7.2), - wind_direction: Some(high), - }, - ], - WeatherMoment::default(), - ); - // Wind degree will be higher than the centerpoint of the low - // and high winds since the high wind is stronger and will be - // less than high - // (low+high)/2 < average.degrees < high - assert!((low + high) / 2.0 < forecast.avg.wind_direction.unwrap()); - assert!(forecast.avg.wind_direction.unwrap() < high); - degrees += 15.0; - } - } -} diff --git a/src/blocks/weather/met_no.rs b/src/blocks/weather/met_no.rs deleted file mode 100644 index 2ff3d4d26d..0000000000 --- a/src/blocks/weather/met_no.rs +++ /dev/null @@ -1,295 +0,0 @@ -use super::*; - -type LegendsStore = HashMap; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(tag = "name", rename_all = "lowercase", deny_unknown_fields, default)] -pub struct Config { - coordinates: Option<(String, String)>, - altitude: Option, - #[serde(default)] - lang: ApiLanguage, - #[default(12)] - forecast_hours: usize, -} - -pub(super) struct Service<'a> { - config: &'a Config, - legend: &'static LegendsStore, -} - -impl<'a> Service<'a> { - pub(super) fn new(config: &'a Config) -> Result> { - Ok(Self { - config, - legend: LEGENDS.as_ref().error("Invalid legends file")?, - }) - } - - fn translate(&self, summary: &str) -> String { - self.legend - .get(summary) - .map(|res| match self.config.lang { - ApiLanguage::English => res.desc_en.as_str(), - ApiLanguage::NorwegianBokmaal => res.desc_nb.as_str(), - ApiLanguage::NorwegianNynorsk => res.desc_nn.as_str(), - }) - .unwrap_or(summary) - .into() - } -} - -#[derive(Deserialize)] -struct LegendsResult { - desc_en: String, - desc_nb: String, - desc_nn: String, -} - -#[derive(Deserialize, Debug, Clone, Default)] -pub(super) enum ApiLanguage { - #[serde(rename = "en")] - #[default] - English, - #[serde(rename = "nn")] - NorwegianNynorsk, - #[serde(rename = "nb")] - NorwegianBokmaal, -} - -#[derive(Deserialize, Debug)] -struct ForecastResponse { - properties: ForecastProperties, -} - -#[derive(Deserialize, Debug)] -struct ForecastProperties { - timeseries: Vec, -} - -#[derive(Deserialize, Debug)] -struct ForecastTimeStep { - data: ForecastData, - // time: String, -} - -impl ForecastTimeStep { - fn to_moment(&self, service: &Service) -> WeatherMoment { - let instant = &self.data.instant.details; - - let mut symbol_code_split = self - .data - .next_1_hours - .as_ref() - .unwrap() - .summary - .symbol_code - .split('_'); - - let summary = symbol_code_split.next().unwrap(); - - // Times of day can be day, night, and polartwilight - let is_night = symbol_code_split.next() == Some("night"); - - let translated = service.translate(summary); - - let temp = instant.air_temperature.unwrap_or_default(); - let humidity = instant.relative_humidity.unwrap_or_default(); - let wind_speed = instant.wind_speed.unwrap_or_default(); - - WeatherMoment { - temp, - apparent: australian_apparent_temp(temp, humidity, wind_speed), - humidity, - weather: translated.clone(), - weather_verbose: translated, - wind: wind_speed, - wind_kmh: wind_speed * 3.6, - wind_direction: instant.wind_from_direction, - icon: weather_to_icon(summary, is_night), - } - } - - fn to_aggregate(&self) -> ForecastAggregateSegment { - let instant = &self.data.instant.details; - - let apparent = if let Some(air_temperature) = instant.air_temperature - && let Some(relative_humidity) = instant.relative_humidity - && let Some(wind_speed) = instant.wind_speed - { - Some(australian_apparent_temp( - air_temperature, - relative_humidity, - wind_speed, - )) - } else { - None - }; - - ForecastAggregateSegment { - temp: instant.air_temperature, - apparent, - humidity: instant.relative_humidity, - wind: instant.wind_speed, - wind_kmh: instant.wind_speed.map(|t| t * 3.6), - wind_direction: instant.wind_from_direction, - } - } -} - -#[derive(Deserialize, Debug)] -struct ForecastData { - instant: ForecastModelInstant, - // next_12_hours: ForecastModelPeriod, - next_1_hours: Option, - // next_6_hours: ForecastModelPeriod, -} - -#[derive(Deserialize, Debug)] -struct ForecastModelInstant { - details: ForecastTimeInstant, -} - -#[derive(Deserialize, Debug)] -struct ForecastModelPeriod { - summary: ForecastSummary, -} - -#[derive(Deserialize, Debug)] -struct ForecastSummary { - symbol_code: String, -} - -#[derive(Deserialize, Debug, Default)] -struct ForecastTimeInstant { - air_temperature: Option, - wind_from_direction: Option, - wind_speed: Option, - relative_humidity: Option, -} - -static LEGENDS: LazyLock> = - LazyLock::new(|| serde_json::from_str(include_str!("met_no_legends.json")).ok()); - -const FORECAST_URL: &str = "https://api.met.no/weatherapi/locationforecast/2.0/compact"; - -#[async_trait] -impl WeatherProvider for Service<'_> { - async fn get_weather( - &self, - autolocated: Option<&IPAddressInfo>, - need_forecast: bool, - ) -> Result { - let (lat, lon) = autolocated - .as_ref() - .map(|loc| (loc.latitude.to_string(), loc.longitude.to_string())) - .or_else(|| self.config.coordinates.clone()) - .error("No location given")?; - - let altitude = if let Some(altitude) = &self.config.altitude { - Some(altitude.parse().error("Unable to convert string to f64")?) - } else { - None - }; - - let (sunrise, sunset) = calculate_sunrise_sunset( - lat.parse().error("Unable to convert string to f64")?, - lon.parse().error("Unable to convert string to f64")?, - altitude, - )?; - - let querystr: HashMap<&str, String> = map! { - "lat" => &lat, - "lon" => &lon, - [if let Some(alt) = &self.config.altitude] "altitude" => alt, - }; - - let data: ForecastResponse = REQWEST_CLIENT - .get(FORECAST_URL) - .query(&querystr) - .header(reqwest::header::CONTENT_TYPE, "application/json") - .send() - .await - .error("Forecast request failed")? - .json() - .await - .error("Forecast request failed")?; - - let forecast_hours = self.config.forecast_hours; - let location_name = autolocated.map_or("Unknown".to_string(), |c| c.city.clone()); - - let current_weather = data.properties.timeseries.first().unwrap().to_moment(self); - - if !need_forecast || forecast_hours == 0 { - return Ok(WeatherResult { - location: location_name, - current_weather, - forecast: None, - sunrise, - sunset, - }); - } - - if data.properties.timeseries.len() < forecast_hours { - return Err(Error::new(format!( - "Unable to fetch the specified number of forecast_hours specified {}, only {} hours available", - forecast_hours, - data.properties.timeseries.len() - )))?; - } - - let data_agg: Vec = data - .properties - .timeseries - .iter() - .take(forecast_hours) - .map(|f| f.to_aggregate()) - .collect(); - - let fin = data.properties.timeseries[forecast_hours - 1].to_moment(self); - - let forecast = Some(Forecast::new(&data_agg, fin)); - - Ok(WeatherResult { - location: location_name, - current_weather, - forecast, - sunset, - sunrise, - }) - } -} - -fn weather_to_icon(weather: &str, is_night: bool) -> WeatherIcon { - match weather { - "cloudy" | "partlycloudy" | "fair" => WeatherIcon::Clouds{is_night}, - "fog" => WeatherIcon::Fog{is_night}, - "clearsky" => WeatherIcon::Clear{is_night}, - "heavyrain" | "heavyrainshowers" | "lightrain" | "lightrainshowers" | "rain" - | "rainshowers" => WeatherIcon::Rain{is_night}, - "rainandthunder" - | "heavyrainandthunder" - | "rainshowersandthunder" - | "sleetandthunder" - | "sleetshowersandthunder" - | "snowandthunder" - | "snowshowersandthunder" - | "heavyrainshowersandthunder" - | "heavysleetandthunder" - | "heavysleetshowersandthunder" - | "heavysnowandthunder" - | "heavysnowshowersandthunder" - | "lightsleetandthunder" - | "lightrainandthunder" - | "lightsnowandthunder" - | "lightssleetshowersandthunder" // There's a typo in the api it will be fixed in the next version to the following entry - | "lightsleetshowersandthunder" - | "lightssnowshowersandthunder"// There's a typo in the api it will be fixed in the next version to the following entry - | "lightsnowshowersandthunder" - | "lightrainshowersandthunder" => WeatherIcon::Thunder{is_night}, - "heavysleet" | "heavysleetshowers" | "heavysnow" | "heavysnowshowers" | "lightsleet" - | "lightsleetshowers" | "lightsnow" | "lightsnowshowers" | "sleet" | "sleetshowers" - | "snow" | "snowshowers" => WeatherIcon::Snow, - _ => WeatherIcon::Default, - } -} diff --git a/src/blocks/weather/met_no_legends.json b/src/blocks/weather/met_no_legends.json deleted file mode 100644 index f8f6eb7af4..0000000000 --- a/src/blocks/weather/met_no_legends.json +++ /dev/null @@ -1 +0,0 @@ -{"clearsky":{"desc_en":"Clear sky","desc_nb":"Klarvær","desc_nn":"Klårvêr","old_id":"1","variants":["day","night","polartwilight"]},"cloudy":{"desc_en":"Cloudy","desc_nb":"Skyet","desc_nn":"Skya","old_id":"4","variants":null},"fair":{"desc_en":"Fair","desc_nb":"Lettskyet","desc_nn":"Lettskya","old_id":"2","variants":["day","night","polartwilight"]},"fog":{"desc_en":"Fog","desc_nb":"Tåke","desc_nn":"Skodde","old_id":"15","variants":null},"heavyrain":{"desc_en":"Heavy rain","desc_nb":"Kraftig regn","desc_nn":"Kraftig regn","old_id":"10","variants":null},"heavyrainandthunder":{"desc_en":"Heavy rain and thunder","desc_nb":"Kraftig regn og torden","desc_nn":"Kraftig regn og torevêr","old_id":"11","variants":null},"heavyrainshowers":{"desc_en":"Heavy rain showers","desc_nb":"Kraftige regnbyger","desc_nn":"Kraftige regnbyer","old_id":"41","variants":["day","night","polartwilight"]},"heavyrainshowersandthunder":{"desc_en":"Heavy rain showers and thunder","desc_nb":"Kraftige regnbyger og torden","desc_nn":"Kraftige regnbyer og torevêr","old_id":"25","variants":["day","night","polartwilight"]},"heavysleet":{"desc_en":"Heavy sleet","desc_nb":"Kraftig sludd","desc_nn":"Kraftig sludd","old_id":"48","variants":null},"heavysleetandthunder":{"desc_en":"Heavy sleet and thunder","desc_nb":"Kraftig sludd og torden","desc_nn":"Kraftig sludd og torevêr","old_id":"32","variants":null},"heavysleetshowers":{"desc_en":"Heavy sleet showers","desc_nb":"Kraftige sluddbyger","desc_nn":"Kraftige sluddbyer","old_id":"43","variants":["day","night","polartwilight"]},"heavysleetshowersandthunder":{"desc_en":"Heavy sleet showers and thunder","desc_nb":"Kraftige sluddbyger og torden","desc_nn":"Kraftige sluddbyer og torevêr","old_id":"27","variants":["day","night","polartwilight"]},"heavysnow":{"desc_en":"Heavy snow","desc_nb":"Kraftig snø","desc_nn":"Kraftig snø","old_id":"50","variants":null},"heavysnowandthunder":{"desc_en":"Heavy snow and thunder","desc_nb":"Kraftig snø og torden","desc_nn":"Kraftig snø og torevêr","old_id":"34","variants":null},"heavysnowshowers":{"desc_en":"Heavy snow showers","desc_nb":"Kraftige snøbyger","desc_nn":"Kraftige snøbyer","old_id":"45","variants":["day","night","polartwilight"]},"heavysnowshowersandthunder":{"desc_en":"Heavy snow showers and thunder","desc_nb":"Kraftige snøbyger og torden","desc_nn":"Kraftige snøbyer og torevêr","old_id":"29","variants":["day","night","polartwilight"]},"lightrain":{"desc_en":"Light rain","desc_nb":"Lett regn","desc_nn":"Lett regn","old_id":"46","variants":null},"lightrainandthunder":{"desc_en":"Light rain and thunder","desc_nb":"Lett regn og torden","desc_nn":"Lett regn og torevêr","old_id":"30","variants":null},"lightrainshowers":{"desc_en":"Light rain showers","desc_nb":"Lette regnbyger","desc_nn":"Lette regnbyer","old_id":"40","variants":["day","night","polartwilight"]},"lightrainshowersandthunder":{"desc_en":"Light rain showers and thunder","desc_nb":"Lette regnbyger og torden","desc_nn":"Lette regnbyer og torevêr","old_id":"24","variants":["day","night","polartwilight"]},"lightsleet":{"desc_en":"Light sleet","desc_nb":"Lett sludd","desc_nn":"Lett sludd","old_id":"47","variants":null},"lightsleetandthunder":{"desc_en":"Light sleet and thunder","desc_nb":"Lett sludd og torden","desc_nn":"Lett sludd og torevêr","old_id":"31","variants":null},"lightsleetshowers":{"desc_en":"Light sleet showers","desc_nb":"Lette sluddbyger","desc_nn":"Lette sluddbyer","old_id":"42","variants":["day","night","polartwilight"]},"lightsnow":{"desc_en":"Light snow","desc_nb":"Lett snø","desc_nn":"Lett snø","old_id":"49","variants":null},"lightsnowandthunder":{"desc_en":"Light snow and thunder","desc_nb":"Lett snø og torden","desc_nn":"Lett snø og torevêr","old_id":"33","variants":null},"lightsnowshowers":{"desc_en":"Light snow showers","desc_nb":"Lette snøbyger","desc_nn":"Lette snøbyer","old_id":"44","variants":["day","night","polartwilight"]},"lightssleetshowersandthunder":{"desc_en":"Light sleet showers and thunder","desc_nb":"Lette sluddbyger og torden","desc_nn":"Lette sluddbyer og torevêr","old_id":"26","variants":["day","night","polartwilight"]},"lightssnowshowersandthunder":{"desc_en":"Light snow showers and thunder","desc_nb":"Lette snøbyger og torden","desc_nn":"Lette snøbyer og torevêr","old_id":"28","variants":["day","night","polartwilight"]},"partlycloudy":{"desc_en":"Partly cloudy","desc_nb":"Delvis skyet","desc_nn":"Delvis skya","old_id":"3","variants":["day","night","polartwilight"]},"rain":{"desc_en":"Rain","desc_nb":"Regn","desc_nn":"Regn","old_id":"9","variants":null},"rainandthunder":{"desc_en":"Rain and thunder","desc_nb":"Regn og torden","desc_nn":"Regn og torevêr","old_id":"22","variants":null},"rainshowers":{"desc_en":"Rain showers","desc_nb":"Regnbyger","desc_nn":"Regnbyer","old_id":"5","variants":["day","night","polartwilight"]},"rainshowersandthunder":{"desc_en":"Rain showers and thunder","desc_nb":"Regnbyger og torden","desc_nn":"Regnbyer og torevêr","old_id":"6","variants":["day","night","polartwilight"]},"sleet":{"desc_en":"Sleet","desc_nb":"Sludd","desc_nn":"Sludd","old_id":"12","variants":null},"sleetandthunder":{"desc_en":"Sleet and thunder","desc_nb":"Sludd og torden","desc_nn":"Sludd og torevêr","old_id":"23","variants":null},"sleetshowers":{"desc_en":"Sleet showers","desc_nb":"Sluddbyger","desc_nn":"Sluddbyer","old_id":"7","variants":["day","night","polartwilight"]},"sleetshowersandthunder":{"desc_en":"Sleet showers and thunder","desc_nb":"Sluddbyger og torden","desc_nn":"Sluddbyer og torevêr","old_id":"20","variants":["day","night","polartwilight"]},"snow":{"desc_en":"Snow","desc_nb":"Snø","desc_nn":"Snø","old_id":"13","variants":null},"snowandthunder":{"desc_en":"Snow and thunder","desc_nb":"Snø og torden","desc_nn":"Snø og torevêr","old_id":"14","variants":null},"snowshowers":{"desc_en":"Snow showers","desc_nb":"Snøbyger","desc_nn":"Snøbyer","old_id":"8","variants":["day","night","polartwilight"]},"snowshowersandthunder":{"desc_en":"Snow showers and thunder","desc_nb":"Snøbyger og torden","desc_nn":"Snøbyer og torevêr","old_id":"21","variants":["day","night","polartwilight"]}} diff --git a/src/blocks/weather/nws.rs b/src/blocks/weather/nws.rs deleted file mode 100644 index e512803b74..0000000000 --- a/src/blocks/weather/nws.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! Support for using the US National Weather Service API. -//! -//! The API is documented [here](https://www.weather.gov/documentation/services-web-api). -//! There is a corresponding [OpenAPI document](https://api.weather.gov/openapi.json). The forecast -//! descriptions are translated into the set of supported icons as best as possible, and a more -//! complete summary forecast is available in the `weather_verbose` format key. The full NWS list -//! of icons and corresponding descriptions can be found [here](https://api.weather.gov/icons), -//! though these are slated for deprecation. -//! -//! All data is gathered using the hourly weather forecast service, after resolving from latitude & -//! longitude coordinates to a specific forecast office and grid point. -//! - -use super::*; -use serde::Deserialize; - -const API_URL: &str = "https://api.weather.gov/"; - -const MPH_TO_KPH: f64 = 1.609344; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(tag = "name", rename_all = "lowercase", deny_unknown_fields, default)] -pub struct Config { - coordinates: Option<(String, String)>, - #[default(12)] - forecast_hours: usize, - #[serde(default)] - units: UnitSystem, -} - -#[derive(Clone, Debug)] -struct LocationInfo { - query: String, - name: String, - lat: f64, - lon: f64, -} - -pub(super) struct Service<'a> { - config: &'a Config, - location: Option, -} - -impl<'a> Service<'a> { - pub(super) async fn new(autolocate: bool, config: &'a Config) -> Result> { - let location = if autolocate { - None - } else { - let coords = config.coordinates.as_ref().error("no location given")?; - Some( - Self::get_location_query( - coords.0.parse().error("Unable to convert string to f64")?, - coords.1.parse().error("Unable to convert string to f64")?, - config.units, - ) - .await?, - ) - }; - Ok(Self { config, location }) - } - - async fn get_location_query(lat: f64, lon: f64, units: UnitSystem) -> Result { - let points_url = format!("{API_URL}/points/{lat},{lon}"); - - let response: ApiPoints = REQWEST_CLIENT - .get(points_url) - .send() - .await - .error("Zone resolution request failed")? - .json() - .await - .error("Failed to parse zone resolution request")?; - let mut query = response.properties.forecast_hourly; - query.push_str(match units { - UnitSystem::Metric => "?units=si", - UnitSystem::Imperial => "?units=us", - }); - let location = response.properties.relative_location.properties; - let name = format!("{}, {}", location.city, location.state); - Ok(LocationInfo { - query, - name, - lat, - lon, - }) - } -} - -#[derive(Deserialize, Debug)] -struct ApiPoints { - properties: ApiPointsProperties, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct ApiPointsProperties { - forecast_hourly: String, - relative_location: ApiRelativeLocation, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct ApiRelativeLocation { - properties: ApiRelativeLocationProperties, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct ApiRelativeLocationProperties { - city: String, - state: String, -} - -#[derive(Deserialize, Debug)] -struct ApiForecastResponse { - properties: ApiForecastProperties, -} - -#[derive(Deserialize, Debug)] -struct ApiForecastProperties { - periods: Vec, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct ApiValue { - value: f64, - unit_code: String, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct ApiForecast { - is_daytime: bool, - temperature: ApiValue, - relative_humidity: ApiValue, - wind_speed: ApiValue, - wind_direction: String, - short_forecast: String, -} - -impl ApiForecast { - fn wind_direction(&self) -> Option { - let dir = match self.wind_direction.as_str() { - "N" => 0, - "NNE" => 1, - "NE" => 2, - "ENE" => 3, - "E" => 4, - "ESE" => 5, - "SE" => 6, - "SSE" => 7, - "S" => 8, - "SSW" => 9, - "SW" => 10, - "WSW" => 11, - "W" => 12, - "WNW" => 13, - "NW" => 14, - "NNW" => 15, - _ => return None, - }; - Some((dir as f64) * (360.0 / 16.0)) - } - - fn icon_to_word(icon: WeatherIcon) -> String { - match icon { - WeatherIcon::Clear { .. } => "Clear", - WeatherIcon::Clouds { .. } => "Clouds", - WeatherIcon::Fog { .. } => "Fog", - WeatherIcon::Thunder { .. } => "Thunder", - WeatherIcon::Rain { .. } => "Rain", - WeatherIcon::Snow => "Snow", - WeatherIcon::Default => "Unknown", - } - .to_string() - } - - fn wind_speed(&self) -> f64 { - if self.wind_speed.unit_code.ends_with("km_h-1") { - // m/s - self.wind_speed.value / 3.6 - } else { - // mph - self.wind_speed.value - } - } - - fn wind_kmh(&self) -> f64 { - if self.wind_speed.unit_code.ends_with("km_h-1") { - self.wind_speed.value - } else { - self.wind_speed.value * MPH_TO_KPH - } - } - - fn apparent_temp(&self) -> f64 { - let temp = if self.temperature.unit_code.ends_with("degC") { - self.temperature.value - } else { - (self.temperature.value - 32.0) * 5.0 / 9.0 - }; - let humidity = self.relative_humidity.value; - // wind_speed in m/s - let wind_speed = self.wind_kmh() / 3.6; - let apparent = australian_apparent_temp(temp, humidity, wind_speed); - if self.temperature.unit_code.ends_with("degC") { - apparent - } else { - (apparent * 9.0 / 5.0) + 32.0 - } - } - - fn to_moment(&self) -> WeatherMoment { - let icon = short_forecast_to_icon(&self.short_forecast, !self.is_daytime); - let weather = Self::icon_to_word(icon); - WeatherMoment { - icon, - weather, - weather_verbose: self.short_forecast.clone(), - temp: self.temperature.value, - apparent: self.apparent_temp(), - humidity: self.relative_humidity.value, - wind: self.wind_speed(), - wind_kmh: self.wind_kmh(), - wind_direction: self.wind_direction(), - } - } - - fn to_aggregate(&self) -> ForecastAggregateSegment { - ForecastAggregateSegment { - temp: Some(self.temperature.value), - apparent: Some(self.apparent_temp()), - humidity: Some(self.relative_humidity.value), - wind: Some(self.wind_speed()), - wind_kmh: Some(self.wind_kmh()), - wind_direction: self.wind_direction(), - } - } -} - -#[async_trait] -impl WeatherProvider for Service<'_> { - async fn get_weather( - &self, - autolocated: Option<&IPAddressInfo>, - need_forecast: bool, - ) -> Result { - let location = if let Some(coords) = autolocated { - Self::get_location_query(coords.latitude, coords.longitude, self.config.units).await? - } else { - self.location.clone().error("No location was provided")? - }; - - let (sunrise, sunset) = calculate_sunrise_sunset(location.lat, location.lon, None)?; - - let data: ApiForecastResponse = REQWEST_CLIENT - .get(location.query) - .header( - "Feature-Flags", - "forecast_wind_speed_qv,forecast_temperature_qv", - ) - .send() - .await - .error("weather request failed")? - .json() - .await - .error("parsing weather data failed")?; - - let data = data.properties.periods; - let current_weather = data.first().error("No current weather")?.to_moment(); - - if !need_forecast || self.config.forecast_hours == 0 { - return Ok(WeatherResult { - location: location.name, - current_weather, - forecast: None, - sunrise, - sunset, - }); - } - - let data_agg: Vec = data - .iter() - .take(self.config.forecast_hours) - .map(|f| f.to_aggregate()) - .collect(); - - let fin = data.last().error("no weather available")?.to_moment(); - - let forecast = Some(Forecast::new(&data_agg, fin)); - - Ok(WeatherResult { - location: location.name, - current_weather, - forecast, - sunrise, - sunset, - }) - } -} - -/// Try to turn the short forecast into an icon. -/// -/// The official API has an icon field, but it's been marked as deprecated. -/// Unfortunately, the short forecast cannot actually be fully enumerated, so -/// we're reduced to checking for the presence of specific strings. -fn short_forecast_to_icon(weather: &str, is_night: bool) -> WeatherIcon { - let weather = weather.to_lowercase(); - // snow, flurries, flurry, blizzard - if weather.contains("snow") || weather.contains("flurr") || weather.contains("blizzard") { - return WeatherIcon::Snow; - } - // thunderstorms - if weather.contains("thunder") { - return WeatherIcon::Thunder { is_night }; - } - // fog or mist - if weather.contains("fog") || weather.contains("mist") { - return WeatherIcon::Fog { is_night }; - } - // rain, rainy, shower, drizzle (drizzle might not be present) - if weather.contains("rain") || weather.contains("shower") || weather.contains("drizzle") { - return WeatherIcon::Rain { is_night }; - } - // cloudy, clouds, partly cloudy, overcast, etc. - if weather.contains("cloud") || weather.contains("overcast") { - return WeatherIcon::Clouds { is_night }; - } - // clear (night), sunny (day). "Mostly sunny" / "Mostly clear" fit here too - if weather.contains("clear") || weather.contains("sunny") { - return WeatherIcon::Clear { is_night }; - } - WeatherIcon::Default -} diff --git a/src/blocks/weather/open_weather_map.rs b/src/blocks/weather/open_weather_map.rs deleted file mode 100644 index 8e34778b59..0000000000 --- a/src/blocks/weather/open_weather_map.rs +++ /dev/null @@ -1,350 +0,0 @@ -use super::*; -use chrono::{DateTime, Utc}; -use serde::{Deserializer, de}; - -pub(super) const GEO_URL: &str = "https://api.openweathermap.org/geo/1.0"; -pub(super) const CURRENT_URL: &str = "https://api.openweathermap.org/data/2.5/weather"; -pub(super) const FORECAST_URL: &str = "https://api.openweathermap.org/data/2.5/forecast"; -pub(super) const API_KEY_ENV: &str = "OPENWEATHERMAP_API_KEY"; -pub(super) const CITY_ID_ENV: &str = "OPENWEATHERMAP_CITY_ID"; -pub(super) const PLACE_ENV: &str = "OPENWEATHERMAP_PLACE"; -pub(super) const ZIP_ENV: &str = "OPENWEATHERMAP_ZIP"; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(tag = "name", rename_all = "lowercase", deny_unknown_fields, default)] -pub struct Config { - #[serde(default = "getenv_openweathermap_api_key")] - api_key: Option, - #[serde(default = "getenv_openweathermap_city_id")] - city_id: Option, - #[serde(default = "getenv_openweathermap_place")] - place: Option, - #[serde(default = "getenv_openweathermap_zip")] - zip: Option, - coordinates: Option<(String, String)>, - #[serde(default)] - units: UnitSystem, - #[default("en")] - lang: String, - #[default(12)] - #[serde(deserialize_with = "deserialize_forecast_hours")] - forecast_hours: usize, -} - -pub fn deserialize_forecast_hours<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - usize::deserialize(deserializer).and_then(|hours| { - if hours % 3 != 0 && hours > 120 { - Err(de::Error::custom( - "'forecast_hours' is not divisible by 3 and must be <= 120", - )) - } else if hours % 3 != 0 { - Err(de::Error::custom("'forecast_hours' is not divisible by 3")) - } else if hours > 120 { - Err(de::Error::custom("'forecast_hours' must be <= 120")) - } else { - Ok(hours) - } - }) -} - -pub(super) struct Service<'a> { - api_key: &'a String, - units: &'a UnitSystem, - lang: &'a String, - location_query: Option, - forecast_hours: usize, -} - -impl<'a> Service<'a> { - pub(super) async fn new(autolocate: bool, config: &'a Config) -> Result> { - let api_key = config.api_key.as_ref().or_error(|| { - format!("missing key 'service.api_key' and environment variable {API_KEY_ENV}",) - })?; - Ok(Self { - api_key, - units: &config.units, - lang: &config.lang, - location_query: Service::get_location_query(autolocate, api_key, config).await?, - forecast_hours: config.forecast_hours, - }) - } - - async fn get_location_query( - autolocate: bool, - api_key: &String, - config: &Config, - ) -> Result> { - if autolocate { - return Ok(None); - } - - let mut location_query = config - .coordinates - .as_ref() - .map(|(lat, lon)| format!("lat={lat}&lon={lon}")) - .or_else(|| config.city_id.as_ref().map(|x| format!("id={x}"))); - - location_query = match location_query { - Some(x) => Some(x), - None => match config.place.as_ref() { - Some(place) => { - let url = format!("{GEO_URL}/direct?q={place}&appid={api_key}"); - - REQWEST_CLIENT - .get(url) - .send() - .await - .error("Geo request failed")? - .json::>() - .await - .error("Geo failed to parse json")? - .first() - .map(|city| format!("lat={}&lon={}", city.lat, city.lon)) - } - None => None, - }, - }; - - location_query = match location_query { - Some(x) => Some(x), - None => match config.zip.as_ref() { - Some(zip) => { - let url = format!("{GEO_URL}/zip?zip={zip}&appid={api_key}"); - let city: CityCoord = REQWEST_CLIENT - .get(url) - .send() - .await - .error("Geo request failed")? - .json() - .await - .error("Geo failed to parse json")?; - - Some(format!("lat={}&lon={}", city.lat, city.lon)) - } - None => None, - }, - }; - - Ok(location_query) - } -} - -fn getenv_openweathermap_api_key() -> Option { - std::env::var(API_KEY_ENV).ok() -} -fn getenv_openweathermap_city_id() -> Option { - std::env::var(CITY_ID_ENV).ok() -} -fn getenv_openweathermap_place() -> Option { - std::env::var(PLACE_ENV).ok() -} -fn getenv_openweathermap_zip() -> Option { - std::env::var(ZIP_ENV).ok() -} - -#[derive(Deserialize, Debug)] -struct ApiForecastResponse { - list: Vec, -} - -#[derive(Deserialize, Debug)] -struct ApiInstantResponse { - weather: Vec, - main: ApiMain, - wind: ApiWind, - dt: i64, -} - -impl ApiInstantResponse { - fn wind_kmh(&self, units: &UnitSystem) -> f64 { - self.wind.speed - * match units { - UnitSystem::Metric => 3.6, - UnitSystem::Imperial => 3.6 * 0.447, - } - } - - fn to_moment(&self, units: &UnitSystem, current_data: &ApiCurrentResponse) -> WeatherMoment { - let is_night = current_data.sys.sunrise >= self.dt || self.dt >= current_data.sys.sunset; - - WeatherMoment { - icon: weather_to_icon(self.weather[0].main.as_str(), is_night), - weather: self.weather[0].main.clone(), - weather_verbose: self.weather[0].description.clone(), - temp: self.main.temp, - apparent: self.main.feels_like, - humidity: self.main.humidity, - wind: self.wind.speed, - wind_kmh: self.wind_kmh(units), - wind_direction: self.wind.deg, - } - } - - fn to_aggregate(&self, units: &UnitSystem) -> ForecastAggregateSegment { - ForecastAggregateSegment { - temp: Some(self.main.temp), - apparent: Some(self.main.feels_like), - humidity: Some(self.main.humidity), - wind: Some(self.wind.speed), - wind_kmh: Some(self.wind_kmh(units)), - wind_direction: self.wind.deg, - } - } -} - -#[derive(Deserialize, Debug)] -struct ApiCurrentResponse { - #[serde(flatten)] - instant: ApiInstantResponse, - sys: ApiSys, - name: String, -} - -impl ApiCurrentResponse { - fn to_moment(&self, units: &UnitSystem) -> WeatherMoment { - self.instant.to_moment(units, self) - } -} - -#[derive(Deserialize, Debug)] -struct ApiWind { - speed: f64, - deg: Option, -} - -#[derive(Deserialize, Debug)] -struct ApiMain { - temp: f64, - feels_like: f64, - humidity: f64, -} - -#[derive(Deserialize, Debug)] -struct ApiSys { - sunrise: i64, - sunset: i64, -} - -#[derive(Deserialize, Debug)] -struct ApiWeather { - main: String, - description: String, -} - -#[derive(Deserialize, Debug)] -struct CityCoord { - lat: f64, - lon: f64, -} - -#[async_trait] -impl WeatherProvider for Service<'_> { - async fn get_weather( - &self, - autolocated: Option<&IPAddressInfo>, - need_forecast: bool, - ) -> Result { - let location_query = autolocated - .as_ref() - .map(|al| format!("lat={}&lon={}", al.latitude, al.longitude)) - .or_else(|| self.location_query.clone()) - .error("no location was provided")?; - - // Refer to https://openweathermap.org/current - let current_url = format!( - "{CURRENT_URL}?{location_query}&appid={api_key}&units={units}&lang={lang}", - api_key = self.api_key, - units = match self.units { - UnitSystem::Metric => "metric", - UnitSystem::Imperial => "imperial", - }, - lang = self.lang, - ); - - let current_data: ApiCurrentResponse = REQWEST_CLIENT - .get(current_url) - .send() - .await - .error("Current weather request failed")? - .json() - .await - .error("Current weather request failed")?; - - let current_weather = current_data.to_moment(self.units); - - let sunrise = DateTime::::from_timestamp(current_data.sys.sunrise, 0) - .error("Unable to convert timestamp to DateTime")?; - - let sunset = DateTime::::from_timestamp(current_data.sys.sunset, 0) - .error("Unable to convert timestamp to DateTime")?; - - if !need_forecast || self.forecast_hours == 0 { - return Ok(WeatherResult { - location: current_data.name, - current_weather, - forecast: None, - sunrise, - sunset, - }); - } - - // Refer to https://openweathermap.org/forecast5 - let forecast_url = format!( - "{FORECAST_URL}?{location_query}&appid={api_key}&units={units}&lang={lang}&cnt={cnt}", - api_key = self.api_key, - units = match self.units { - UnitSystem::Metric => "metric", - UnitSystem::Imperial => "imperial", - }, - lang = self.lang, - cnt = self.forecast_hours / 3, - ); - - let forecast_data: ApiForecastResponse = REQWEST_CLIENT - .get(forecast_url) - .send() - .await - .error("Forecast weather request failed")? - .json() - .await - .error("Forecast weather request failed")?; - - let data_agg: Vec = forecast_data - .list - .iter() - .take(self.forecast_hours) - .map(|f| f.to_aggregate(self.units)) - .collect(); - - let fin = forecast_data - .list - .last() - .error("no weather available")? - .to_moment(self.units, ¤t_data); - - let forecast = Some(Forecast::new(&data_agg, fin)); - - Ok(WeatherResult { - location: current_data.name, - current_weather, - forecast, - sunrise, - sunset, - }) - } -} - -fn weather_to_icon(weather: &str, is_night: bool) -> WeatherIcon { - match weather { - "Clear" => WeatherIcon::Clear { is_night }, - "Rain" | "Drizzle" => WeatherIcon::Rain { is_night }, - "Clouds" => WeatherIcon::Clouds { is_night }, - "Fog" | "Mist" => WeatherIcon::Fog { is_night }, - "Thunderstorm" => WeatherIcon::Thunder { is_night }, - "Snow" => WeatherIcon::Snow, - _ => WeatherIcon::Default, - } -} diff --git a/src/blocks/xrandr.rs b/src/blocks/xrandr.rs deleted file mode 100644 index 42f75ad18e..0000000000 --- a/src/blocks/xrandr.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! X11 screen information -//! -//! X11 screen information (name, brightness, resolution). With a click you can toggle through your active screens and with wheel up and down you can adjust the selected screens brightness. Regarding brightness control, xrandr changes the brightness of the display using gamma rather than changing the brightness in hardware, so if that is not desirable then consider using the `backlight` block instead. -//! -//! NOTE: Some users report issues (e.g. [here](https://github.com/greshake/i3status-rust/issues/274) and [here](https://github.com/greshake/i3status-rust/issues/668) when using this block. The cause is currently unknown, however setting a higher update interval may help. -//! -//! # Configuration -//! -//! Key | Values | Default -//! ----|--------|-------- -//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $display $brightness_icon $brightness "` -//! `step_width` | The steps brightness is in/decreased for the selected screen (When greater than 50 it gets limited to 50). | `5` -//! `interval` | Update interval in seconds. | `5` -//! `invert_icons` | Invert icons' ordering, useful if you have colorful emoji | `false` -//! -//! Placeholder | Value | Type | Unit -//! ------------------|------------------------------|--------|--------------- -//! `icon` | A static icon | Icon | - -//! `display` | The name of a monitor | Text | - -//! `brightness` | The brightness of a monitor | Number | % -//! `brightness_icon` | A static icon | Icon | - -//! `resolution` | The resolution of a monitor | Text | - -//! `res_icon` | A static icon | Icon | - -//! -//! Action | Default button -//! ------------------|--------------- -//! `cycle_outputs` | Left -//! `brightness_up` | Wheel Up -//! `brightness_down` | Wheel Down -//! -//! # Example -//! -//! ```toml -//! [[block]] -//! block = "xrandr" -//! format = " $icon $brightness $resolution " -//! ``` -//! -//! # Used Icons -//! - `xrandr` -//! - `backlight` -//! - `resolution` - -use super::prelude::*; -use crate::subprocess::spawn_shell; -use regex::RegexSet; -use tokio::process::Command; - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(deny_unknown_fields, default)] -pub struct Config { - #[default(5.into())] - pub interval: Seconds, - pub format: FormatConfig, - #[default(5)] - pub step_width: u32, - pub invert_icons: bool, -} - -pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { - let mut actions = api.get_actions()?; - api.set_default_actions(&[ - (MouseButton::Left, None, "cycle_outputs"), - (MouseButton::WheelUp, None, "brightness_up"), - (MouseButton::WheelDown, None, "brightness_down"), - ])?; - - let format = config - .format - .with_default(" $icon $display $brightness_icon $brightness ")?; - - let mut cur_indx = 0; - let mut timer = config.interval.timer(); - - loop { - let mut monitors = get_monitors().await?; - if cur_indx > monitors.len() { - cur_indx = 0; - } - - loop { - let mut widget = Widget::new().with_format(format.clone()); - - if let Some(mon) = monitors.get(cur_indx) { - let mut icon_value = (mon.brightness as f64) / 100.0; - if config.invert_icons { - icon_value = 1.0 - icon_value; - } - widget.set_values(map! { - "display" => Value::text(mon.name.clone()), - "brightness" => Value::percents(mon.brightness), - "brightness_icon" => Value::icon_progression("backlight", icon_value), - "resolution" => Value::text(mon.resolution.clone()), - "icon" => Value::icon("xrandr"), - "res_icon" => Value::icon("resolution"), - }); - } - api.set_widget(widget)?; - - select! { - _ = timer.tick() => break, - _ = api.wait_for_update_request() => break, - Some(action) = actions.recv() => match action.as_ref() { - "cycle_outputs" => { - cur_indx = (cur_indx + 1) % monitors.len(); - } - "brightness_up" => { - if let Some(monitor) = monitors.get_mut(cur_indx) { - let bright = (monitor.brightness + config.step_width).min(100); - monitor.set_brightness(bright); - } - } - "brightness_down" => { - if let Some(monitor) = monitors.get_mut(cur_indx) { - let bright = monitor.brightness.saturating_sub(config.step_width); - monitor.set_brightness(bright); - } - } - _ => (), - } - } - } - } -} - -struct Monitor { - name: String, - brightness: u32, - resolution: String, -} - -impl Monitor { - fn set_brightness(&mut self, brightness: u32) { - let _ = spawn_shell(&format!( - "xrandr --output {} --brightness {}", - self.name, - brightness as f64 / 100.0 - )); - self.brightness = brightness; - } -} - -async fn get_monitors() -> Result> { - let mut monitors = Vec::new(); - - let active_monitors = Command::new("xrandr") - .arg("--listactivemonitors") - .output() - .await - .error("Failed to collect active xrandr monitors")? - .stdout; - let active_monitors = - String::from_utf8(active_monitors).error("xrandr produced non-UTF8 output")?; - - let regex = active_monitors - .lines() - .filter_map(|line| line.split_ascii_whitespace().last()) - .map(|name| format!("{name} connected")) - .chain(Some("Brightness:".into())); - let regex = RegexSet::new(regex).error("Failed to create RegexSet")?; - - let monitors_info = Command::new("xrandr") - .arg("--verbose") - .output() - .await - .error("Failed to collect xrandr monitors info")? - .stdout; - let monitors_info = - String::from_utf8(monitors_info).error("xrandr produced non-UTF8 output")?; - - let mut it = monitors_info.lines().filter(|line| regex.is_match(line)); - - while let Some(line1) = it.next() - && let Some(line2) = it.next() - { - let mut tokens = line1.split_ascii_whitespace().peekable(); - let name = tokens.next().error("Failed to parse xrandr output")?.into(); - let _ = tokens.next(); - - // The output may be " connected " or " connected primary " - let _ = tokens.next_if_eq(&"primary"); - - let resolution = tokens - .next() - .and_then(|x| x.split('+').next()) - .error("Failed to parse xrandr output")? - .into(); - - let brightness = (line2 - .split(':') - .nth(1) - .error("Failed to parse xrandr output")? - .trim() - .parse::() - .error("Failed to parse xrandr output")? - * 100.0) as u32; - - monitors.push(Monitor { - name, - brightness, - resolution, - }); - } - - Ok(monitors) -} diff --git a/src/click.rs b/src/click.rs deleted file mode 100644 index a74faf6e4a..0000000000 --- a/src/click.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::fmt; - -use serde::Deserialize; -use serde::de::{self, Deserializer, Visitor}; - -use crate::errors::{ErrorContext as _, Result}; -use crate::protocol::i3bar_event::I3BarEvent; -use crate::subprocess::{spawn_shell, spawn_shell_sync}; -use crate::wrappers::SerdeRegex; - -/// Can be one of `left`, `middle`, `right`, `up`/`wheel_up`, `down`/`wheel_down`, `wheel_left`, `wheel_right`, `forward`, `back` or `double_left`. -/// -/// Note that in order for double clicks to be registered, you have to set `double_click_delay` to a -/// non-zero value. `200` might be a good choice. Note that enabling this functionality will -/// make left clicks less responsive and feel a bit laggy. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum MouseButton { - Left, - Middle, - Right, - WheelUp, - WheelDown, - WheelLeft, - WheelRight, - Forward, - Back, - DoubleLeft, -} - -#[derive(Debug, Clone)] -pub struct PostActions { - pub action: Option, - pub update: bool, -} - -#[derive(Deserialize, Debug, Clone, Default)] -pub struct ClickHandler(Vec); - -impl ClickHandler { - pub async fn handle(&self, event: &I3BarEvent) -> Result> { - let Some(entry) = self - .0 - .iter() - .filter(|e| e.button == event.button) - .find(|e| match &e.widget { - None => event.instance.is_none(), - Some(re) => re.0.is_match(event.instance.as_deref().unwrap_or("block")), - }) - else { - return Ok(None); - }; - - if let Some(cmd) = &entry.cmd { - if entry.sync { - spawn_shell_sync(cmd).await - } else { - spawn_shell(cmd) - } - .or_error(|| format!("'{:?}' button handler: Failed to run '{cmd}", event.button))?; - } - - Ok(Some(PostActions { - action: entry.action.clone(), - update: entry.update, - })) - } -} - -#[derive(Deserialize, Debug, Clone)] -#[serde(deny_unknown_fields)] -pub struct ClickConfigEntry { - /// Which button to handle - button: MouseButton, - /// To which part of the block this entry applies - #[serde(default)] - widget: Option, - /// Which command to run - #[serde(default)] - cmd: Option, - /// Which block action to trigger - #[serde(default)] - action: Option, - /// Whether to wait for command to exit or not (default is `false`) - #[serde(default)] - sync: bool, - /// Whether to update the block on click (default is `false`) - #[serde(default)] - update: bool, -} - -impl<'de> Deserialize<'de> for MouseButton { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct MouseButtonVisitor; - - impl Visitor<'_> for MouseButtonVisitor { - type Value = MouseButton; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("button as int or string") - } - - // ```toml - // button = "left" - // ``` - fn visit_str(self, name: &str) -> Result - where - E: de::Error, - { - use MouseButton::*; - Ok(match name { - "left" => Left, - "middle" => Middle, - "right" => Right, - "up" | "wheel_up" => WheelUp, - "down" | "wheel_down" => WheelDown, - "wheel_left" => WheelLeft, - "wheel_right" => WheelRight, - "forward" => Forward, - "back" => Back, - // Experimental - "double_left" => DoubleLeft, - other => return Err(E::custom(format!("unknown button '{other}'"))), - }) - } - - // ```toml - // button = 1 - // ``` - fn visit_i64(self, number: i64) -> Result - where - E: de::Error, - { - use MouseButton::*; - Ok(match number { - 1 => Left, - 2 => Middle, - 3 => Right, - 4 => WheelUp, - 5 => WheelDown, - 6 => WheelLeft, - 7 => WheelRight, - 8 => Back, - 9 => Forward, - other => return Err(E::custom(format!("unknown button '{other}'"))), - }) - } - fn visit_u64(self, number: u64) -> Result - where - E: de::Error, - { - self.visit_i64(number as i64) - } - } - - deserializer.deserialize_any(MouseButtonVisitor) - } -} diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index f6f9ba6612..0000000000 --- a/src/config.rs +++ /dev/null @@ -1,122 +0,0 @@ -use serde::{Deserialize, Deserializer}; -use smart_default::SmartDefault; -use std::collections::HashMap; -use std::sync::Arc; - -use crate::blocks::BlockConfig; -use crate::click::ClickHandler; -use crate::errors::*; -use crate::formatting::config::Config as FormatConfig; -use crate::geolocator::Geolocator; -use crate::icons::{Icon, Icons}; -use crate::themes::{Theme, ThemeOverrides, ThemeUserConfig}; - -#[derive(Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct Config { - #[serde(flatten)] - pub shared: SharedConfig, - - /// Set to `true` to invert mouse wheel direction - #[serde(default)] - pub invert_scrolling: bool, - - #[serde(default)] - pub geolocator: Arc, - - /// The maximum delay (ms) between two clicks that are considered as double click - #[serde(default)] - pub double_click_delay: u64, - - #[serde(default = "default_error_format")] - pub error_format: FormatConfig, - #[serde(default = "default_error_fullscreen")] - pub error_fullscreen_format: FormatConfig, - - #[serde(default)] - #[serde(rename = "block")] - pub blocks: Vec, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct SharedConfig { - #[serde(default)] - #[serde(deserialize_with = "deserialize_theme_config")] - pub theme: Arc, - #[serde(default)] - pub icons: Arc, - #[serde(default = "default_icons_format")] - pub icons_format: Arc, -} - -impl Default for SharedConfig { - fn default() -> Self { - Self { - theme: Default::default(), - icons: Default::default(), - icons_format: default_icons_format(), - } - } -} - -fn default_error_format() -> FormatConfig { - " {$short_error_message|X} ".parse().unwrap() -} - -fn default_error_fullscreen() -> FormatConfig { - " $full_error_message ".parse().unwrap() -} - -fn default_icons_format() -> Arc { - Arc::new("{icon}".into()) -} - -impl SharedConfig { - pub fn get_icon(&self, icon: &str, value: Option) -> Result { - if icon.is_empty() { - Ok(String::new()) - } else { - Ok(self.icons_format.replace( - "{icon}", - self.icons - .get(icon, value) - .or_error(|| format!("Icon '{icon}' not found"))?, - )) - } - } -} - -#[derive(Deserialize, Debug)] -pub struct BlockConfigEntry { - #[serde(flatten)] - pub common: CommonBlockConfig, - #[serde(flatten)] - pub config: BlockConfig, -} - -#[derive(Deserialize, Debug, SmartDefault)] -#[serde(default)] -pub struct CommonBlockConfig { - pub click: ClickHandler, - pub signal: Option, - pub icons_format: Option, - pub theme_overrides: Option, - pub icons_overrides: Option>, - pub merge_with_next: bool, - - #[default(5)] - pub error_interval: u64, - pub error_format: FormatConfig, - pub error_fullscreen_format: FormatConfig, - - pub if_command: Option, -} - -fn deserialize_theme_config<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let theme_config = ThemeUserConfig::deserialize(deserializer)?; - let theme = Theme::try_from(theme_config).serde_error()?; - Ok(Arc::new(theme)) -} diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index fb8ac737d2..0000000000 --- a/src/errors.rs +++ /dev/null @@ -1,112 +0,0 @@ -use std::borrow::Cow; -use std::fmt; -use std::sync::Arc; - -pub use std::error::Error as StdError; - -/// Result type returned from functions that can have our `Error`s. -pub type Result = std::result::Result; - -type ErrorMsg = Cow<'static, str>; - -/// Error type -#[derive(Debug, Clone)] -pub struct Error { - pub message: Option, - pub cause: Option>, -} - -impl Error { - pub fn new>(message: T) -> Self { - Self { - message: Some(message.into()), - cause: None, - } - } -} - -pub trait ErrorContext { - fn error>(self, message: M) -> Result; - fn or_error, F: FnOnce() -> M>(self, f: F) -> Result; -} - -impl ErrorContext for Result { - fn error>(self, message: M) -> Result { - self.map_err(|e| Error { - message: Some(message.into()), - cause: Some(Arc::new(e)), - }) - } - - fn or_error, F: FnOnce() -> M>(self, f: F) -> Result { - self.map_err(|e| Error { - message: Some(f().into()), - cause: Some(Arc::new(e)), - }) - } -} - -impl ErrorContext for Option { - fn error>(self, message: M) -> Result { - self.ok_or_else(|| Error { - message: Some(message.into()), - cause: None, - }) - } - - fn or_error, F: FnOnce() -> M>(self, f: F) -> Result { - self.ok_or_else(|| Error { - message: Some(f().into()), - cause: None, - }) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(self.message.as_deref().unwrap_or("Error"))?; - - if let Some(cause) = &self.cause { - write!(f, ". Cause: {cause}")?; - } - - Ok(()) - } -} - -impl From for zbus::fdo::Error { - fn from(err: Error) -> Self { - Self::Failed(err.to_string()) - } -} - -impl StdError for Error {} - -pub trait ToSerdeError { - fn serde_error(self) -> Result; -} - -impl ToSerdeError for Result -where - F: fmt::Display, -{ - fn serde_error(self) -> Result { - self.map_err(E::custom) - } -} - -pub struct BoxErrorWrapper(pub Box); - -impl fmt::Debug for BoxErrorWrapper { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.0, f) - } -} - -impl fmt::Display for BoxErrorWrapper { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl StdError for BoxErrorWrapper {} diff --git a/src/escape.rs b/src/escape.rs deleted file mode 100644 index 7155010b0f..0000000000 --- a/src/escape.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! Simple json escaping - -use std::fmt::Write; - -use unicode_segmentation::UnicodeSegmentation as _; - -pub trait CollectEscaped { - /// Write escaped version of `self` to `out` - fn collect_pango_escaped_into(self, out: &mut T); - - /// Write escaped version of `self` to a new buffer - #[inline] - fn collect_pango_escaped(self) -> T - where - Self: Sized, - { - let mut out = T::default(); - self.collect_pango_escaped_into(&mut out); - out - } -} - -impl CollectEscaped for I -where - I: Iterator, - R: AsRef, -{ - fn collect_pango_escaped_into(self, out: &mut T) { - for c in self { - let _ = match c.as_ref() { - "&" => out.write_str("&"), - "<" => out.write_str("<"), - ">" => out.write_str(">"), - "'" => out.write_str("'"), - x => out.write_str(x), - }; - } - } -} - -pub trait Escaped { - /// Write escaped version of `self` to `out` - fn pango_escaped_into(self, out: &mut T); - - /// Write escaped version of `self` to a new buffer - #[inline] - fn pango_escaped(self) -> T - where - Self: Sized, - { - let mut out = T::default(); - self.pango_escaped_into(&mut out); - out - } -} - -impl> Escaped for R { - fn pango_escaped_into(self, out: &mut T) { - self.as_ref() - .split_word_bounds() - .collect_pango_escaped_into(out); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn collect_pango() { - let orig = "&my 'text' "; - let escaped: String = orig.graphemes(true).collect_pango_escaped(); - assert_eq!(escaped, "&my 'text' <a̐>"); - } - #[test] - fn pango() { - let orig = "&my 'text' "; - let escaped: String = orig.pango_escaped(); - assert_eq!(escaped, "&my 'text' <a̐>"); - } -} diff --git a/src/formatting.rs b/src/formatting.rs deleted file mode 100644 index 8468b2f661..0000000000 --- a/src/formatting.rs +++ /dev/null @@ -1,235 +0,0 @@ -//! # Formatting system -//! Many blocks have a `format` configuration option, which allows to heavily customize the block's -//! appearance. In short, each block with `format` option provides a set of values, which are -//! displayed according to `format`. `format`'s value is just a text with embedded variables. -//! Similarly to PHP and shell, variable name must start with a `$`: -//! `this is a variable: -> $var <-`. -//! -//! Also, format strings can embed icons. For example, `^icon_ping` in `" ^icon_ping $ping "` gets -//! substituted with a "ping" icon from your icon set. For a complete list of icons, see -//! [this](https://github.com/greshake/i3status-rust/blob/master/doc/themes.md#available-icon-overrides). -//! -//! # Types -//! -//! The allowed types of variables are: -//! -//! Type | Default formatter -//! --------------------------|------------------ -//! Text | `str` -//! Number | `eng` -//! Datetime | `datetime` -//! Duration | `duration` -//! [Flag](#how-to-use-flags) | N/A -//! -//! # Formatters -//! -//! A formatter is something that converts a value into a text. Because there are many ways to do -//! this, a number of formatters is available. Formatter can be specified using the syntax similar -//! to method calls in many programming languages: `.()`. For example: -//! `$title.str(min_w:10, max_w:20)`. -//! -//! Note: for arguments that accept a boolean value, just specifying the argument will be treated as `arg:true`. -//! -//! ## `str` - Format text -//! -//! Argument | Description |Default value -//! -----------------------|---------------------------------------------------|------------- -//! `min_width` or `min_w` | if text is shorter it will be padded using spaces | `0` -//! `max_width` or `max_w` | if text is longer it will be truncated | Infinity -//! `width` or `w` | Text will be exactly this length by padding or truncating as needed | N/A -//! `rot_interval` | if text is longer than `max_width` it will be rotated every `rot_interval` seconds, if set | None -//! `rot_separator` | if text is longer than `max_width` it will be rotated with this seporator | \"\|\" -//! -//! Note: width just changes the values of both min_width and max_width to be the same. Use width -//! if you want the values to be the same, or the other two otherwise. Don't mix width with -//! min_width or max_width. -//! -//! ## `eng` - Format numbers using engineering notation -//! -//! Argument | Description |Default value -//! ----------------|--------------------------------------------------------------------------------------------------|------------- -//! `width` or `w` | the resulting text will be at least `width` characters long | `2` -//! `unit` or `u` | some values have a [unit](unit::Unit), and it is possible to convert them by setting this option | N/A -//! `hide_unit` | hide the unit symbol | `false` -//! `unit_space` | have a whitespace before unit symbol | `false` -//! `prefix` or `p` | specify this argument if you want to set the minimal [SI prefix](prefix::Prefix) | N/A -//! `hide_prefix` | hide the prefix symbol | `false` -//! `prefix_space` | have a whitespace before prefix symbol | `false` -//! `force_prefix` | force the prefix value instead of setting a "minimal prefix" | `false` -//! `pad_with` | the character that is used to pad the number to be `width` long | ` ` (a space) -//! `range` | a range of allowed values, in the format `..`, inclusive. Both start and end are optional. Can be used to, for example, hide the block when the value is not in a given range. | `..` -//! `show` | show this value. Can be used with `range` for conditional formatting | `true` -//! -//! ## `bar` - Display numbers as progress bars -//! -//! Argument | Description |Default value -//! -----------------------|---------------------------------------------------------------------------------|------------------------- -//! `width` or `w` | the width of the bar (in characters) | `5` (`1` for `vertical`) -//! `max_value` | which value is treated as "full". For example, for battery level `100` is full. | `100` -//! `vertical` or `v` | whether to render the bar vertically or not | `false` -//! -//! ## `tally` - Display numbers as tally marks -//! -//! Argument | Description |Default value -//! ---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------- -//! `style` or `s` | One of [`chinese_counting_rods`/`ccr`](https://en.wikipedia.org/wiki/Counting_rods), [`chinese_tally`/`ct`, `western_tally`/`wt`, `western_tally_ungrouped`/`wtu`](https://en.wikipedia.org/wiki/Tally_marks) | western_tally -//! -//! ## `pango-str` - Just display the text without pango markup escaping -//! -//! No arguments. -//! -//! ## `datetime` - Display datetime -//! -//! Argument | Description |Default value -//! -----------------------|-----------------------------------------------------------------------------------------------------------|------------- -//! `format` or `f` | [chrono docs](https://docs.rs/chrono/0.3.0/chrono/format/strftime/index.html#specifiers) for all options. | `'%a %d/%m %R'` -//! `locale` or `l` | Locale to apply when formatting the time | System locale -//! -//! -//! ## `duration`/`dur` - Format durations -//! -//! Argument | Description |Default value -//! -----------------|--------------------------------------------------------------------------------------------------|------------------------------------------------------ -//! `hms` | Should the format be hours:minutes:seconds.milliseconds | `false` -//! `max_unit` | The largest unit to display the duration with (see below for the list of all possible units) | hms ? `h` : `y` -//! `min_unit` | The smallest unit to display the duration with (see below for the list of all possible units) | `s` -//! `units` | The number of units to display | min(# of units between `max_unit` and `min_unit``, 2) -//! `round_up` | Round up to the nearest minimum displayed unit | `true` -//! `unit_space` | Should there be a space between the value and unit symbol (not allowed when `hms:true`) | `false` -//! `pad_with` | The character that is used to pad the numbers | hms ? `0` : ` ` (a space) -//! `leading_zeroes` | If fewer than `units` are non-zero should leading numbers that have a value of zero be shown | `true` -//! -//! Unit | Description -//! -----|------------ -//! y | years -//! w | weeks -//! d | days -//! h | hours -//! m | minutes -//! s | seconds -//! ms | milliseconds -//! -//! # Handling missing placeholders and incorrect types -//! -//! Some blocks allow missing placeholders, for example [bluetooth](crate::blocks::bluetooth)'s -//! "percentage" may be absent if the device is not supported. To handle such cases it is possible -//! to queue multiple formats together by using `|` symbol: `||`. -//! -//! In addition, formats can be recursive. To set a format inside of another format, place it -//! inside of `{}`. For example, in `Percentage: {$percentage|N/A}` the text "Percentage: " will be -//! always displayed, followed by the actual percentage or "N/A" in case percentage is not -//! available. This example does exactly the same thing as `Percentage: $percentage|Percentage: N/A` -//! -//! # How to use flags -//! -//! Some blocks provide flags, which can be used to change the format based on some criteria. For -//! example, [taskwarrior](crate::blocks::taskwarrior) defines `done` if the count is zero. In -//! general, flags are used in this way: -//! -//! ```text -//! $a{a is set}|$b$c{b and c are set}|${b|c}{b or c is set}|neither flag is set -//! ``` - -pub mod config; -pub mod formatter; -pub mod parse; -pub mod prefix; -pub mod scheduling; -pub mod template; -pub mod unit; -pub mod value; - -use std::borrow::Cow; -use std::collections::HashMap; - -use crate::config::SharedConfig; -use crate::errors::*; -use template::FormatTemplate; -use value::Value; - -pub type Values = HashMap, Value>; - -#[derive(Debug, thiserror::Error)] -pub enum FormatError { - #[error("Placeholder '{0}' not found")] - PlaceholderNotFound(String), - #[error("{} cannot be formatted with '{}' formatter", .ty, .fmt)] - IncompatibleFormatter { ty: &'static str, fmt: &'static str }, - #[error("Number {0} is out of range")] - NumberOutOfRange(f64), - #[error(transparent)] - Other(#[from] Error), -} - -#[derive(Debug, Clone)] -pub struct Format { - full: FormatTemplate, - short: FormatTemplate, - intervals: Vec, -} - -impl Format { - pub fn contains_key(&self, key: &str) -> bool { - self.full.contains_key(key) || self.short.contains_key(key) - } - - pub fn intervals(&self) -> Vec { - self.intervals.clone() - } - - pub fn render( - &self, - values: &Values, - config: &SharedConfig, - ) -> Result<(Vec, Vec)> { - let full = self - .full - .render(values, config) - .error("Failed to render full text")?; - let short = self - .short - .render(values, config) - .error("Failed to render short text")?; - Ok((full, short)) - } -} - -#[derive(Debug, Default, Clone)] -pub struct Fragment { - pub text: String, - pub metadata: Metadata, -} - -impl From for Fragment { - fn from(text: String) -> Self { - Self { - text, - metadata: Default::default(), - } - } -} - -impl Fragment { - pub fn formatted_text(&self) -> String { - match (self.metadata.italic, self.metadata.underline) { - (true, true) => format!("{}", self.text), - (false, true) => format!("{}", self.text), - (true, false) => format!("{}", self.text), - (false, false) => self.text.clone(), - } - } -} - -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -pub struct Metadata { - pub instance: Option<&'static str>, - pub underline: bool, - pub italic: bool, -} - -impl Metadata { - pub fn is_default(&self) -> bool { - *self == Default::default() - } -} diff --git a/src/formatting/config.rs b/src/formatting/config.rs deleted file mode 100644 index 90d0e47800..0000000000 --- a/src/formatting/config.rs +++ /dev/null @@ -1,165 +0,0 @@ -use super::{Format, template::FormatTemplate}; -use crate::errors::*; -use serde::de::{MapAccess, Visitor}; -use serde::{Deserialize, Deserializer, de}; -use std::fmt; -use std::str::FromStr; - -#[derive(Debug, Default, Clone)] -pub struct Config { - pub full: Option, - pub short: Option, -} - -impl Config { - pub fn with_default(&self, default_full: &str) -> Result { - self.with_defaults(default_full, "") - } - - pub fn with_defaults(&self, default_full: &str, default_short: &str) -> Result { - let full = match self.full.clone() { - Some(full) => full, - None => default_full.parse()?, - }; - - let short = match self.short.clone() { - Some(short) => short, - None => default_short.parse()?, - }; - - let mut intervals = Vec::new(); - full.init_intervals(&mut intervals); - short.init_intervals(&mut intervals); - - Ok(Format { - full, - short, - intervals, - }) - } - - pub fn with_default_config(&self, default_config: &Self) -> Format { - let full = self - .full - .clone() - .or_else(|| default_config.full.clone()) - .unwrap_or_default(); - let short = self - .short - .clone() - .or_else(|| default_config.short.clone()) - .unwrap_or_default(); - - let mut intervals = Vec::new(); - full.init_intervals(&mut intervals); - short.init_intervals(&mut intervals); - - Format { - full, - short, - intervals, - } - } - - pub fn with_default_format(&self, default_format: &Format) -> Format { - let full = self - .full - .clone() - .unwrap_or_else(|| default_format.full.clone()); - let short = self - .short - .clone() - .unwrap_or_else(|| default_format.short.clone()); - - let mut intervals = Vec::new(); - full.init_intervals(&mut intervals); - short.init_intervals(&mut intervals); - - Format { - full, - short, - intervals, - } - } -} - -impl FromStr for Config { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(Self { - full: Some(s.parse()?), - short: None, - }) - } -} - -impl<'de> Deserialize<'de> for Config { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(field_identifier, rename_all = "lowercase")] - enum Field { - Full, - Short, - } - - struct FormatTemplateVisitor; - - impl<'de> Visitor<'de> for FormatTemplateVisitor { - type Value = Config; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("format structure") - } - - /// Handle configs like: - /// - /// ```toml - /// format = "{layout}" - /// ``` - fn visit_str(self, full: &str) -> Result - where - E: de::Error, - { - full.parse().serde_error() - } - - /// Handle configs like: - /// - /// ```toml - /// [block.format] - /// full = "{layout}" - /// short = "{layout^2}" - /// ``` - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut full: Option = None; - let mut short: Option = None; - while let Some(key) = map.next_key()? { - match key { - Field::Full => { - if full.is_some() { - return Err(de::Error::duplicate_field("full")); - } - full = Some(map.next_value::()?.parse().serde_error()?); - } - Field::Short => { - if short.is_some() { - return Err(de::Error::duplicate_field("short")); - } - short = Some(map.next_value::()?.parse().serde_error()?); - } - } - } - Ok(Config { full, short }) - } - } - - deserializer.deserialize_any(FormatTemplateVisitor) - } -} diff --git a/src/formatting/formatter.rs b/src/formatting/formatter.rs deleted file mode 100644 index 1a12624acd..0000000000 --- a/src/formatting/formatter.rs +++ /dev/null @@ -1,66 +0,0 @@ -use unicode_segmentation::UnicodeSegmentation as _; - -use std::time::Duration; -use std::{borrow::Cow, fmt::Debug}; - -use super::FormatError; -use super::parse::Arg; -use super::value::ValueInner as Value; -use crate::config::SharedConfig; -use crate::errors::*; - -// A helper macro for testing formatters -#[cfg(test)] -#[macro_export] -macro_rules! new_fmt { - ($name:ident) => {{ - new_fmt!($name,) - }}; - ($name:ident, $($key:ident : $value:tt),* $(,)?) => { - new_formatter(stringify!($name), &[ - $( Arg { key: stringify!($key), val: Some(stringify!($value)) } ),* - ]) - }; -} - -mod bar; -pub use bar::BarFormatter; -mod tally; -pub use tally::TallyFormatter; -mod datetime; -pub use datetime::{DEFAULT_DATETIME_FORMATTER, DatetimeFormatter}; -mod duration; -pub use duration::{DEFAULT_DURATION_FORMATTER, DurationFormatter}; -mod eng; -pub use eng::{DEFAULT_NUMBER_FORMATTER, EngFormatter}; -mod flag; -pub use flag::{DEFAULT_FLAG_FORMATTER, FlagFormatter}; -mod pango; -pub use pango::PangoStrFormatter; -mod str; -pub use str::{DEFAULT_STRING_FORMATTER, StrFormatter}; - -type PadWith = Cow<'static, str>; - -const DEFAULT_NUMBER_PAD_WITH: PadWith = Cow::Borrowed(" "); - -pub trait Formatter: Debug + Send + Sync { - fn format(&self, val: &Value, config: &SharedConfig) -> Result; - - fn interval(&self) -> Option { - None - } -} - -pub fn new_formatter(name: &str, args: &[Arg]) -> Result> { - match name { - "bar" => Ok(Box::new(BarFormatter::from_args(args)?)), - "datetime" => Ok(Box::new(DatetimeFormatter::from_args(args)?)), - "dur" | "duration" => Ok(Box::new(DurationFormatter::from_args(args)?)), - "eng" => Ok(Box::new(EngFormatter::from_args(args)?)), - "pango-str" => Ok(Box::new(PangoStrFormatter::from_args(args)?)), - "str" => Ok(Box::new(StrFormatter::from_args(args)?)), - "tally" => Ok(Box::new(TallyFormatter::from_args(args)?)), - _ => Err(Error::new(format!("Unknown formatter: '{name}'"))), - } -} diff --git a/src/formatting/formatter/bar.rs b/src/formatting/formatter/bar.rs deleted file mode 100644 index bce5558b07..0000000000 --- a/src/formatting/formatter/bar.rs +++ /dev/null @@ -1,81 +0,0 @@ -use super::*; - -const DEFAULT_BAR_VERTICAL: bool = false; -const DEFAULT_BAR_WIDTH_HORIZONTAL: usize = 5; -const DEFAULT_BAR_WIDTH_VERTICAL: usize = 1; -const DEFAULT_BAR_MAX_VAL: f64 = 100.0; - -#[derive(Debug)] -pub struct BarFormatter { - width: usize, - max_value: f64, - vertical: bool, -} - -impl BarFormatter { - pub(super) fn from_args(args: &[Arg]) -> Result { - let mut vertical = DEFAULT_BAR_VERTICAL; - let mut width = None; - let mut max_value = DEFAULT_BAR_MAX_VAL; - for arg in args { - match arg.key { - "width" | "w" => { - width = Some(arg.parse_value()?); - } - "max_value" => { - max_value = arg.parse_value()?; - } - "vertical" | "v" => { - vertical = arg.parse_value()?; - } - other => { - return Err(Error::new(format!("Unknown argument for 'bar': '{other}'"))); - } - } - } - Ok(Self { - width: width.unwrap_or(match vertical { - false => DEFAULT_BAR_WIDTH_HORIZONTAL, - true => DEFAULT_BAR_WIDTH_VERTICAL, - }), - max_value, - vertical, - }) - } -} - -const HORIZONTAL_BAR_CHARS: [char; 9] = [ - ' ', '\u{258f}', '\u{258e}', '\u{258d}', '\u{258c}', '\u{258b}', '\u{258a}', '\u{2589}', - '\u{2588}', -]; - -const VERTICAL_BAR_CHARS: [char; 9] = [ - ' ', '\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}', - '\u{2588}', -]; - -impl Formatter for BarFormatter { - fn format(&self, val: &Value, _config: &SharedConfig) -> Result { - match val { - &Value::Number { mut val, .. } => { - val = (val / self.max_value).clamp(0., 1.); - if self.vertical { - let vert_char = VERTICAL_BAR_CHARS[(val * 8.) as usize]; - Ok((0..self.width).map(|_| vert_char).collect()) - } else { - let chars_to_fill = val * self.width as f64; - Ok((0..self.width) - .map(|i| { - HORIZONTAL_BAR_CHARS - [((chars_to_fill - i as f64).clamp(0., 1.) * 8.) as usize] - }) - .collect()) - } - } - other => Err(FormatError::IncompatibleFormatter { - ty: other.type_name(), - fmt: "bar", - }), - } - } -} diff --git a/src/formatting/formatter/datetime.rs b/src/formatting/formatter/datetime.rs deleted file mode 100644 index 77a3361631..0000000000 --- a/src/formatting/formatter/datetime.rs +++ /dev/null @@ -1,198 +0,0 @@ -use chrono::format::{Fixed, Item, StrftimeItems}; -use chrono::{DateTime, Local, Locale, TimeZone}; -use chrono_tz::{OffsetName as _, Tz}; - -use std::fmt::Display; -use std::sync::LazyLock; - -use super::*; - -make_log_macro!(error, "datetime"); - -const DEFAULT_DATETIME_FORMAT: &str = "%a %d/%m %R"; - -pub static DEFAULT_DATETIME_FORMATTER: LazyLock = - LazyLock::new(|| DatetimeFormatter::new(Some(DEFAULT_DATETIME_FORMAT), None).unwrap()); - -#[derive(Debug)] -pub enum DatetimeFormatter { - Chrono { - items: Vec>, - locale: Option, - }, - #[cfg(feature = "icu_calendar")] - Icu { - length: icu_datetime::options::length::Date, - locale: icu_locid::Locale, - }, -} - -impl DatetimeFormatter { - pub(super) fn from_args(args: &[Arg]) -> Result { - let mut format = None; - let mut locale = None; - for arg in args { - match arg.key { - "format" | "f" => { - format = Some(arg.val.error("format must be specified")?); - } - "locale" | "l" => { - locale = Some(arg.val.error("locale must be specified")?); - } - other => { - return Err(Error::new(format!( - "Unknown argument for 'datetime': '{other}'" - ))); - } - } - } - Self::new(format, locale) - } - - fn new(format: Option<&str>, locale: Option<&str>) -> Result { - let (items, locale) = match locale { - Some(locale) => { - #[cfg(feature = "icu_calendar")] - let Ok(locale) = locale.try_into() else { - use std::str::FromStr as _; - // try with icu4x - let locale = icu_locid::Locale::from_str(locale) - .ok() - .error("invalid locale")?; - let length = match format { - Some("full") => icu_datetime::options::length::Date::Full, - None | Some("long") => icu_datetime::options::length::Date::Long, - Some("medium") => icu_datetime::options::length::Date::Medium, - Some("short") => icu_datetime::options::length::Date::Short, - _ => return Err(Error::new("Unknown format option for icu based locale")), - }; - return Ok(Self::Icu { locale, length }); - }; - #[cfg(not(feature = "icu_calendar"))] - let locale = locale.try_into().ok().error("invalid locale")?; - ( - StrftimeItems::new_with_locale( - format.unwrap_or(DEFAULT_DATETIME_FORMAT), - locale, - ), - Some(locale), - ) - } - None => ( - StrftimeItems::new(format.unwrap_or(DEFAULT_DATETIME_FORMAT)), - None, - ), - }; - - Ok(Self::Chrono { - items: items.parse_to_owned().error(format!( - "Invalid format: \"{}\"", - format.unwrap_or(DEFAULT_DATETIME_FORMAT) - ))?, - locale, - }) - } -} - -pub(crate) trait TimezoneName { - fn timezone_name(datetime: &DateTime) -> Result> - where - Self: TimeZone; -} - -impl TimezoneName for Tz { - fn timezone_name(datetime: &DateTime) -> Result> { - Ok(Item::Literal( - datetime - .offset() - .abbreviation() - .error("Timezone name unknown")?, - )) - } -} - -impl TimezoneName for Local { - fn timezone_name(datetime: &DateTime) -> Result> { - let tz_name = iana_time_zone::get_timezone().error("Could not get local timezone")?; - let tz = tz_name - .parse::() - .error("Could not parse local timezone")?; - Tz::timezone_name(&datetime.with_timezone(&tz)).map(|x| x.to_owned()) - } -} - -fn borrow_item<'a>(item: &'a Item) -> Item<'a> { - match item { - Item::Literal(s) => Item::Literal(s), - Item::OwnedLiteral(s) => Item::Literal(s), - Item::Space(s) => Item::Space(s), - Item::OwnedSpace(s) => Item::Space(s), - Item::Numeric(n, p) => Item::Numeric(n.clone(), *p), - Item::Fixed(f) => Item::Fixed(f.clone()), - Item::Error => Item::Error, - } -} - -impl Formatter for DatetimeFormatter { - fn format(&self, val: &Value, _config: &SharedConfig) -> Result { - #[allow(clippy::unnecessary_wraps)] - fn for_generic_datetime( - this: &DatetimeFormatter, - datetime: DateTime, - ) -> Result - where - T: TimeZone + TimezoneName, - T::Offset: Display, - { - Ok(match this { - DatetimeFormatter::Chrono { items, locale } => { - let new_items = items.iter().map(|item| match item { - Item::Fixed(Fixed::TimezoneName) => match T::timezone_name(&datetime) { - Ok(name) => name, - Err(e) => { - error!("{e}"); - Item::Fixed(Fixed::TimezoneName) - } - }, - item => borrow_item(item), - }); - match *locale { - Some(locale) => datetime - .format_localized_with_items(new_items, locale) - .to_string(), - None => datetime.format_with_items(new_items).to_string(), - } - } - #[cfg(feature = "icu_calendar")] - DatetimeFormatter::Icu { locale, length } => { - use chrono::Datelike as _; - let date = icu_calendar::Date::try_new_iso_date( - datetime.year(), - datetime.month() as u8, - datetime.day() as u8, - ) - .ok() - .error("Current date should be a valid date")?; - let date = date.to_any(); - let dft = - icu_datetime::DateFormatter::try_new_with_length(&locale.into(), *length) - .ok() - .error("locale should be present in compiled data")?; - dft.format_to_string(&date) - .ok() - .error("formatting date using icu failed")? - } - }) - } - match val { - Value::Datetime(datetime, timezone) => match timezone { - Some(tz) => for_generic_datetime(self, datetime.with_timezone(tz)), - None => for_generic_datetime(self, datetime.with_timezone(&Local)), - }, - other => Err(FormatError::IncompatibleFormatter { - ty: other.type_name(), - fmt: "datetime", - }), - } - } -} diff --git a/src/formatting/formatter/duration.rs b/src/formatting/formatter/duration.rs deleted file mode 100644 index 2597973737..0000000000 --- a/src/formatting/formatter/duration.rs +++ /dev/null @@ -1,513 +0,0 @@ -use std::cmp::min; - -use super::*; - -const UNIT_COUNT: usize = 7; -const UNITS: [&str; UNIT_COUNT] = ["y", "w", "d", "h", "m", "s", "ms"]; -const UNIT_CONVERSION_RATES: [u128; UNIT_COUNT] = [ - 31_556_952_000, // Based on there being 365.2425 days/year - 604_800_000, - 86_400_000, - 3_600_000, - 60_000, - 1_000, - 1, -]; -const UNIT_PAD_WIDTHS: [usize; UNIT_COUNT] = [1, 2, 1, 2, 2, 2, 3]; - -pub const DEFAULT_DURATION_FORMATTER: DurationFormatter = DurationFormatter { - hms: false, - max_unit_index: 0, - min_unit_index: 5, - units: 2, - round_up: true, - unit_has_space: false, - pad_with: DEFAULT_NUMBER_PAD_WITH, - leading_zeroes: true, -}; - -#[derive(Debug, Default)] -pub struct DurationFormatter { - hms: bool, - max_unit_index: usize, - min_unit_index: usize, - units: usize, - round_up: bool, - unit_has_space: bool, - pad_with: PadWith, - leading_zeroes: bool, -} - -impl DurationFormatter { - pub(super) fn from_args(args: &[Arg]) -> Result { - let mut hms = false; - let mut max_unit = None; - let mut min_unit = "s"; - let mut units: Option = None; - let mut round_up = true; - let mut unit_has_space = false; - let mut pad_with = None; - let mut leading_zeroes = true; - for arg in args { - match arg.key { - "hms" => { - hms = arg.parse_value()?; - } - "max_unit" => { - max_unit = Some(arg.val.error("max_unit must be specified")?); - } - "min_unit" => { - min_unit = arg.val.error("min_unit must be specified")?; - } - "units" => { - units = Some(arg.parse_value()?); - } - "round_up" => { - round_up = arg.parse_value()?; - } - "unit_space" => { - unit_has_space = arg.parse_value()?; - } - "pad_with" => { - let pad_with_str = arg.val.error("pad_with must be specified")?; - if pad_with_str.graphemes(true).count() < 2 { - pad_with = Some(Cow::Owned(pad_with_str.into())); - } else { - return Err(Error::new( - "pad_with must be an empty string or a single character", - )); - }; - } - "leading_zeroes" => { - leading_zeroes = arg.parse_value()?; - } - - _ => return Err(Error::new(format!("Unexpected argument {:?}", arg.key))), - } - } - - if hms && unit_has_space { - return Err(Error::new( - "When hms is enabled unit_space should not be true", - )); - } - - let max_unit = max_unit.unwrap_or(if hms { "h" } else { "y" }); - let pad_with = pad_with.unwrap_or(if hms { - Cow::Borrowed("0") - } else { - DEFAULT_NUMBER_PAD_WITH - }); - - let max_unit_index = UNITS - .iter() - .position(|&x| x == max_unit) - .error("max_unit must be one of \"y\", \"w\", \"d\", \"h\", \"m\", \"s\", or \"ms\"")?; - - let min_unit_index = UNITS - .iter() - .position(|&x| x == min_unit) - .error("min_unit must be one of \"y\", \"w\", \"d\", \"h\", \"m\", \"s\", or \"ms\"")?; - - if hms && max_unit_index < 3 { - return Err(Error::new( - "When hms is enabled the max unit must be h,m,s,ms", - )); - } - - // UNITS are sorted largest to smallest - if min_unit_index < max_unit_index { - return Err(Error::new(format!( - "min_unit({min_unit}) must be smaller than or equal to max_unit({max_unit})", - ))); - } - - let units_upper_bound = min_unit_index - max_unit_index + 1; - let units = units.unwrap_or_else(|| min(units_upper_bound, 2)); - - if units > units_upper_bound { - return Err(Error::new(format!( - "there aren't {units} units between min_unit({min_unit}) and max_unit({max_unit})", - ))); - } - - Ok(Self { - hms, - max_unit_index, - min_unit_index, - units, - round_up, - unit_has_space, - pad_with, - leading_zeroes, - }) - } - - fn get_time_parts(&self, mut ms: u128) -> Vec<(usize, u128)> { - let mut should_push = false; - // A Vec of the unit index and value pairs - let mut v = Vec::with_capacity(self.units); - for (i, div) in UNIT_CONVERSION_RATES[self.max_unit_index..=self.min_unit_index] - .iter() - .enumerate() - { - // Offset i by the offset used to slice UNIT_CONVERSION_RATES - let index = i + self.max_unit_index; - let value = ms / div; - - // Only add the non-zero, unless we want to display the leading units of time with value of zero. - // For example we want to have a minimum unit of seconds but to always show two values we could have: - // " 0m 15s" - if !should_push { - should_push = value != 0 - || (self.leading_zeroes && index >= self.min_unit_index + 1 - self.units); - } - - if should_push { - v.push((index, value)); - // We have the right number of values/units - if v.len() == self.units { - break; - } - } - ms %= div; - } - - v - } -} - -impl Formatter for DurationFormatter { - fn format(&self, val: &Value, _config: &SharedConfig) -> Result { - match val { - Value::Duration(duration) => { - let mut v = self.get_time_parts(duration.as_millis()); - - if self.round_up { - // Get the index for which unit we should round up to - let i = v.last().map_or(self.min_unit_index, |&(i, _)| i); - v = self.get_time_parts(duration.as_millis() + UNIT_CONVERSION_RATES[i] - 1); - } - - let mut first_entry = true; - let mut result = String::new(); - for (i, value) in v { - // No separator before the first entry - if !first_entry { - if self.hms { - // Separator between s and ms should be a '.' - if i == 6 { - result.push('.'); - } else { - result.push(':'); - } - } else { - result.push(' '); - } - } else { - first_entry = false; - } - - // Pad the value - let value_str = value.to_string(); - for _ in value_str.len()..UNIT_PAD_WIDTHS[i] { - result.push_str(&self.pad_with); - } - result.push_str(&value_str); - - // No units in hms mode - if !self.hms { - if self.unit_has_space { - result.push(' '); - } - result.push_str(UNITS[i]); - } - } - - Ok(result) - } - other => Err(FormatError::IncompatibleFormatter { - ty: other.type_name(), - fmt: "duration", - }), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - macro_rules! dur { - ($($key:ident : $value:expr),*) => {{ - let mut ms = 0; - $( - let unit = stringify!($key); - ms += $value - * (UNIT_CONVERSION_RATES[UNITS - .iter() - .position(|&x| x == unit) - .expect("unit must be one of \"y\", \"w\", \"d\", \"h\", \"m\", \"s\", or \"ms\"")] - as u64); - )* - Value::Duration(std::time::Duration::from_millis(ms)) - }}; - } - - #[test] - fn dur_default_single_unit() { - let config = SharedConfig::default(); - let fmt = new_fmt!(dur).unwrap(); - - let result = fmt.format(&dur!(y:1), &config).unwrap(); - assert_eq!(result, "1y 0w"); - - let result = fmt.format(&dur!(w:1), &config).unwrap(); - assert_eq!(result, " 1w 0d"); - - let result = fmt.format(&dur!(d:1), &config).unwrap(); - assert_eq!(result, "1d 0h"); - - let result = fmt.format(&dur!(h:1), &config).unwrap(); - assert_eq!(result, " 1h 0m"); - - let result = fmt.format(&dur!(m:1), &config).unwrap(); - assert_eq!(result, " 1m 0s"); - - let result = fmt.format(&dur!(s:1), &config).unwrap(); - assert_eq!(result, " 0m 1s"); - - //This is rounded to 1s since min_unit is 's' and round_up is true - let result = fmt.format(&dur!(ms:1), &config).unwrap(); - assert_eq!(result, " 0m 1s"); - } - - #[test] - fn dur_default_consecutive_units() { - let config = SharedConfig::default(); - let fmt = new_fmt!(dur).unwrap(); - - let result = fmt.format(&dur!(y:1, w:2), &config).unwrap(); - assert_eq!(result, "1y 2w"); - - let result = fmt.format(&dur!(w:1, d:2), &config).unwrap(); - assert_eq!(result, " 1w 2d"); - - let result = fmt.format(&dur!(d:1, h:2), &config).unwrap(); - assert_eq!(result, "1d 2h"); - - let result = fmt.format(&dur!(h:1, m:2), &config).unwrap(); - assert_eq!(result, " 1h 2m"); - - let result = fmt.format(&dur!(m:1, s:2), &config).unwrap(); - assert_eq!(result, " 1m 2s"); - - //This is rounded to 2s since min_unit is 's' and round_up is true - let result = fmt.format(&dur!(s:1, ms:2), &config).unwrap(); - assert_eq!(result, " 0m 2s"); - } - - #[test] - fn dur_hms_no_ms() { - let config = SharedConfig::default(); - let fmt = new_fmt!(dur, hms:true, min_unit:s).unwrap(); - - let result = fmt.format(&dur!(d:1, h:2), &config).unwrap(); - assert_eq!(result, "26:00"); - - let result = fmt.format(&dur!(h:1, m:2), &config).unwrap(); - assert_eq!(result, "01:02"); - - let result = fmt.format(&dur!(m:1, s:2), &config).unwrap(); - assert_eq!(result, "01:02"); - - //This is rounded to 2s since min_unit is 's' and round_up is true - let result = fmt.format(&dur!(s:1, ms:2), &config).unwrap(); - assert_eq!(result, "00:02"); - } - - #[test] - fn dur_hms_with_ms() { - let config = SharedConfig::default(); - let fmt = new_fmt!(dur, hms:true, min_unit:ms).unwrap(); - - let result = fmt.format(&dur!(d:1, h:2), &config).unwrap(); - assert_eq!(result, "26:00"); - - let result = fmt.format(&dur!(h:1, m:2), &config).unwrap(); - assert_eq!(result, "01:02"); - - let result = fmt.format(&dur!(m:1, s:2), &config).unwrap(); - assert_eq!(result, "01:02"); - - let result = fmt.format(&dur!(s:1, ms:2), &config).unwrap(); - assert_eq!(result, "01.002"); - } - - #[test] - fn dur_round_up_true() { - let config = SharedConfig::default(); - let fmt = new_fmt!(dur, round_up:true).unwrap(); - - let result = fmt.format(&dur!(y:1, ms:1), &config).unwrap(); - assert_eq!(result, "1y 1w"); - - let result = fmt.format(&dur!(w:1, ms:1), &config).unwrap(); - assert_eq!(result, " 1w 1d"); - - let result = fmt.format(&dur!(d:1, ms:1), &config).unwrap(); - assert_eq!(result, "1d 1h"); - - let result = fmt.format(&dur!(h:1, ms:1), &config).unwrap(); - assert_eq!(result, " 1h 1m"); - - let result = fmt.format(&dur!(m:1, ms:1), &config).unwrap(); - assert_eq!(result, " 1m 1s"); - - //This is rounded to 2s since min_unit is 's' and round_up is true - let result = fmt.format(&dur!(s:1, ms:1), &config).unwrap(); - assert_eq!(result, " 0m 2s"); - } - - #[test] - fn dur_units() { - let config = SharedConfig::default(); - let val = dur!(y:1, w:2, d:3, h:4, m:5, s:6, ms:7); - - let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 1).unwrap(); - let result = fmt.format(&val, &config).unwrap(); - assert_eq!(result, "1y"); - - let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 2).unwrap(); - let result = fmt.format(&val, &config).unwrap(); - assert_eq!(result, "1y 2w"); - - let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 3).unwrap(); - let result = fmt.format(&val, &config).unwrap(); - assert_eq!(result, "1y 2w 3d"); - - let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 4).unwrap(); - let result = fmt.format(&val, &config).unwrap(); - assert_eq!(result, "1y 2w 3d 4h"); - - let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 5).unwrap(); - let result = fmt.format(&val, &config).unwrap(); - assert_eq!(result, "1y 2w 3d 4h 5m"); - - let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 6).unwrap(); - let result = fmt.format(&val, &config).unwrap(); - assert_eq!(result, "1y 2w 3d 4h 5m 6s"); - - let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 7).unwrap(); - let result = fmt.format(&val, &config).unwrap(); - assert_eq!(result, "1y 2w 3d 4h 5m 6s 7ms"); - } - - #[test] - fn dur_round_up_false() { - let config = SharedConfig::default(); - let fmt = new_fmt!(dur, round_up:false).unwrap(); - - let result = fmt.format(&dur!(y:1, ms:1), &config).unwrap(); - assert_eq!(result, "1y 0w"); - - let result = fmt.format(&dur!(w:1, ms:1), &config).unwrap(); - assert_eq!(result, " 1w 0d"); - - let result = fmt.format(&dur!(d:1, ms:1), &config).unwrap(); - assert_eq!(result, "1d 0h"); - - let result = fmt.format(&dur!(h:1, ms:1), &config).unwrap(); - assert_eq!(result, " 1h 0m"); - - let result = fmt.format(&dur!(m:1, ms:1), &config).unwrap(); - assert_eq!(result, " 1m 0s"); - - let result = fmt.format(&dur!(s:1, ms:1), &config).unwrap(); - assert_eq!(result, " 0m 1s"); - - let result = fmt.format(&dur!(ms:1), &config).unwrap(); - assert_eq!(result, " 0m 0s"); - } - - #[test] - fn dur_invalid_config_hms_and_unit_space() { - let fmt_err = new_fmt!(dur, hms:true, unit_space:true).unwrap_err(); - assert_eq!( - fmt_err.message, - Some("When hms is enabled unit_space should not be true".into()) - ); - } - - #[test] - fn dur_invalid_config_invalid_unit() { - let fmt_err = new_fmt!(dur, max_unit:does_not_exist).unwrap_err(); - assert_eq!( - fmt_err.message, - Some( - "max_unit must be one of \"y\", \"w\", \"d\", \"h\", \"m\", \"s\", or \"ms\"" - .into() - ) - ); - - let fmt_err = new_fmt!(dur, min_unit:does_not_exist).unwrap_err(); - assert_eq!( - fmt_err.message, - Some( - "min_unit must be one of \"y\", \"w\", \"d\", \"h\", \"m\", \"s\", or \"ms\"" - .into() - ) - ); - } - - #[test] - fn dur_invalid_config_hms_max_unit_too_large() { - let fmt_err = new_fmt!(dur, max_unit:d, hms:true).unwrap_err(); - assert_eq!( - fmt_err.message, - Some("When hms is enabled the max unit must be h,m,s,ms".into()) - ); - } - - #[test] - fn dur_invalid_config_min_larger_than_max() { - let fmt = new_fmt!(dur, max_unit:h, min_unit:h); - assert!(fmt.is_ok()); - - let fmt_err = new_fmt!(dur, max_unit:h, min_unit:d).unwrap_err(); - assert_eq!( - fmt_err.message, - Some("min_unit(d) must be smaller than or equal to max_unit(h)".into()) - ); - } - - #[test] - fn dur_invalid_config_too_many_units() { - let fmt = new_fmt!(dur, max_unit:y, min_unit:s, units:6); - assert!(fmt.is_ok()); - - let fmt_err = new_fmt!(dur, max_unit:y, min_unit:s, units:7).unwrap_err(); - assert_eq!( - fmt_err.message, - Some("there aren't 7 units between min_unit(s) and max_unit(y)".into()) - ); - - let fmt = new_fmt!(dur, max_unit:w, min_unit:s, units:5); - assert!(fmt.is_ok()); - - let fmt_err = new_fmt!(dur, max_unit:w, min_unit:s, units:6).unwrap_err(); - assert_eq!( - fmt_err.message, - Some("there aren't 6 units between min_unit(s) and max_unit(w)".into()) - ); - - let fmt = new_fmt!(dur, max_unit:y, min_unit:ms, units:7); - assert!(fmt.is_ok()); - - let fmt_err = new_fmt!(dur, max_unit:y, min_unit:ms, units:8).unwrap_err(); - assert_eq!( - fmt_err.message, - Some("there aren't 8 units between min_unit(ms) and max_unit(y)".into()) - ); - } -} diff --git a/src/formatting/formatter/eng.rs b/src/formatting/formatter/eng.rs deleted file mode 100644 index ba40c22bad..0000000000 --- a/src/formatting/formatter/eng.rs +++ /dev/null @@ -1,302 +0,0 @@ -use crate::formatting::prefix::Prefix; -use crate::formatting::unit::Unit; - -use std::borrow::Cow; -use std::ops::RangeInclusive; - -use super::*; - -const DEFAULT_NUMBER_WIDTH: usize = 2; - -pub const DEFAULT_NUMBER_FORMATTER: EngFormatter = EngFormatter { - show: true, - width: DEFAULT_NUMBER_WIDTH, - unit: None, - unit_has_space: false, - unit_hidden: false, - prefix: None, - prefix_has_space: false, - prefix_hidden: false, - prefix_forced: false, - pad_with: DEFAULT_NUMBER_PAD_WITH, - range: f64::NEG_INFINITY..=f64::INFINITY, -}; - -#[derive(Debug)] -pub struct EngFormatter { - show: bool, - width: usize, - unit: Option, - unit_has_space: bool, - unit_hidden: bool, - prefix: Option, - prefix_has_space: bool, - prefix_hidden: bool, - prefix_forced: bool, - pad_with: PadWith, - range: RangeInclusive, -} - -impl EngFormatter { - pub(super) fn from_args(args: &[Arg]) -> Result { - let mut result = DEFAULT_NUMBER_FORMATTER; - - for arg in args { - match arg.key { - "width" | "w" => { - result.width = arg.parse_value()?; - } - "unit" | "u" => { - result.unit = Some(arg.parse_value()?); - } - "hide_unit" => { - result.unit_hidden = arg.parse_value()?; - } - "unit_space" => { - result.unit_has_space = arg.parse_value()?; - } - "prefix" | "p" => { - result.prefix = Some(arg.parse_value()?); - } - "hide_prefix" => { - result.prefix_hidden = arg.parse_value()?; - } - "prefix_space" => { - result.prefix_has_space = arg.parse_value()?; - } - "force_prefix" => { - result.prefix_forced = arg.parse_value()?; - } - "pad_with" => { - let pad_with_str = arg.val.error("pad_with must be specified")?; - if pad_with_str.graphemes(true).count() < 2 { - result.pad_with = Cow::Owned(pad_with_str.into()); - } else { - return Err(Error::new( - "pad_with must be an empty string or a single character", - )); - } - } - "range" => { - let (start, end) = arg - .val - .error("range must be specified")? - .split_once("..") - .error("invalid range")?; - if !start.is_empty() { - result.range = start.parse::().error("invalid range start")? - ..=*result.range.end(); - } - if !end.is_empty() { - result.range = *result.range.start() - ..=end.parse::().error("invalid range end")?; - } - } - "show" => { - result.show = arg.parse_value()?; - } - other => { - return Err(Error::new(format!("Unknown argument for 'eng': '{other}'"))); - } - } - } - - Ok(result) - } -} - -impl Formatter for EngFormatter { - fn format(&self, val: &Value, _config: &SharedConfig) -> Result { - match val { - &Value::Number { mut val, mut unit } => { - if !self.range.contains(&val) { - return Err(FormatError::NumberOutOfRange(val)); - } - - if !self.show { - return Ok(String::new()); - } - - let is_negative = val.is_sign_negative(); - if is_negative { - val = -val; - } - - if let Some(new_unit) = self.unit { - val = unit.convert(val, new_unit)?; - unit = new_unit; - } - - let (min_prefix, max_prefix) = match (self.prefix, self.prefix_forced) { - (Some(prefix), true) => (prefix, prefix), - (Some(prefix), false) => (prefix, Prefix::max_available()), - (None, _) => (Prefix::min_available(), Prefix::max_available()), - }; - - let prefix = unit - .clamp_prefix(if min_prefix.is_binary() { - Prefix::eng_binary(val) - } else { - Prefix::eng(val) - }) - .clamp(min_prefix, max_prefix); - val = prefix.apply(val); - - let mut digits = (val.max(1.).log10().floor() + 1.0) as i32 + is_negative as i32; - - // handle rounding - if self.width as i32 - digits >= 1 { - let round_up_to = self.width as i32 - digits - 1; - let m = 10f64.powi(round_up_to); - val = (val * m).round() / m; - digits = (val.max(1.).log10().floor() + 1.0) as i32 + is_negative as i32; - } - - let sign = if is_negative { "-" } else { "" }; - let mut retval = match self.width as i32 - digits { - i32::MIN..=0 => format!("{sign}{}", val.round()), - 1 => format!("{}{sign}{}", self.pad_with, val.round() as i64), - rest => format!("{sign}{val:.*}", rest as usize - 1), - }; - - let display_prefix = - !self.prefix_hidden && prefix != Prefix::One && prefix != Prefix::OneButBinary; - let display_unit = !self.unit_hidden && unit != Unit::None; - - if display_prefix { - if self.prefix_has_space { - retval.push(' '); - } - retval.push_str(&prefix.to_string()); - } - if display_unit { - if self.unit_has_space || (self.prefix_has_space && !display_prefix) { - retval.push(' '); - } - retval.push_str(&unit.to_string()); - } - - Ok(retval) - } - other => Err(FormatError::IncompatibleFormatter { - ty: other.type_name(), - fmt: "eng", - }), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn eng_rounding_and_negatives() { - let fmt = new_fmt!(eng, w: 3).unwrap(); - let config = SharedConfig::default(); - - let result = fmt - .format( - &Value::Number { - val: -1.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, " -1"); - - let result = fmt - .format( - &Value::Number { - val: 9.9999, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, " 10"); - - let result = fmt - .format( - &Value::Number { - val: 999.9, - unit: Unit::Bytes, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "1.0KB"); - - let result = fmt - .format( - &Value::Number { - val: -9.99, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "-10"); - - let result = fmt - .format( - &Value::Number { - val: 9.94, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "9.9"); - - let result = fmt - .format( - &Value::Number { - val: 9.95, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, " 10"); - - let fmt = new_fmt!(eng, w: 5, p: 1).unwrap(); - let result = fmt - .format( - &Value::Number { - val: 321_600_000_000., - unit: Unit::Bytes, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "321.6GB"); - } - - #[test] - fn eng_prefixes() { - let config = SharedConfig::default(); - // 14.96 GiB - let val = Value::Number { - val: 14.96 * 1024. * 1024. * 1024., - unit: Unit::Bytes, - }; - - let fmt = new_fmt!(eng, w: 5, p: Mi).unwrap(); - let result = fmt.format(&val, &config).unwrap(); - assert_eq!(result, "14.96GiB"); - - let fmt = new_fmt!(eng, w: 4, p: Mi).unwrap(); - let result = fmt.format(&val, &config).unwrap(); - assert_eq!(result, "15.0GiB"); - - let fmt = new_fmt!(eng, w: 3, p: Mi).unwrap(); - let result = fmt.format(&val, &config).unwrap(); - assert_eq!(result, " 15GiB"); - - let fmt = new_fmt!(eng, w: 2, p: Mi).unwrap(); - let result = fmt.format(&val, &config).unwrap(); - assert_eq!(result, "15GiB"); - } -} diff --git a/src/formatting/formatter/flag.rs b/src/formatting/formatter/flag.rs deleted file mode 100644 index a59a9f20f6..0000000000 --- a/src/formatting/formatter/flag.rs +++ /dev/null @@ -1,17 +0,0 @@ -use super::*; - -pub const DEFAULT_FLAG_FORMATTER: FlagFormatter = FlagFormatter; - -#[derive(Debug)] -pub struct FlagFormatter; - -impl Formatter for FlagFormatter { - fn format(&self, val: &Value, _config: &SharedConfig) -> Result { - match val { - Value::Flag => Ok(String::new()), - _ => { - unreachable!() - } - } - } -} diff --git a/src/formatting/formatter/pango.rs b/src/formatting/formatter/pango.rs deleted file mode 100644 index 6a007f3093..0000000000 --- a/src/formatting/formatter/pango.rs +++ /dev/null @@ -1,29 +0,0 @@ -use super::*; - -#[derive(Debug)] -pub struct PangoStrFormatter; - -impl PangoStrFormatter { - pub(super) fn from_args(args: &[Arg]) -> Result { - if let Some(arg) = args.first() { - return Err(Error::new(format!( - "Unknown argument for 'pango-str': '{}'", - arg.key - ))); - } - Ok(Self) - } -} - -impl Formatter for PangoStrFormatter { - fn format(&self, val: &Value, config: &SharedConfig) -> Result { - match val { - Value::Text(x) => Ok(x.clone()), // No escaping - Value::Icon(icon, value) => config.get_icon(icon, *value).map_err(Into::into), - other => Err(FormatError::IncompatibleFormatter { - ty: other.type_name(), - fmt: "pango-str", - }), - } - } -} diff --git a/src/formatting/formatter/str.rs b/src/formatting/formatter/str.rs deleted file mode 100644 index c5e27ff01b..0000000000 --- a/src/formatting/formatter/str.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::iter::repeat_n; -use std::time::Instant; - -use crate::escape::CollectEscaped as _; - -use super::*; - -const DEFAULT_STR_MIN_WIDTH: usize = 0; -const DEFAULT_STR_MAX_WIDTH: usize = usize::MAX; -const DEFAULT_STR_ROT_INTERVAL: Option = None; -const DEFAULT_STR_ROT_SEP: Option = None; - -pub const DEFAULT_STRING_FORMATTER: StrFormatter = StrFormatter { - min_width: DEFAULT_STR_MIN_WIDTH, - max_width: DEFAULT_STR_MAX_WIDTH, - rot_interval_ms: None, - init_time: None, - rot_separator: None, -}; - -#[derive(Debug)] -pub struct StrFormatter { - min_width: usize, - max_width: usize, - rot_interval_ms: Option, - init_time: Option, - rot_separator: Option, -} - -impl StrFormatter { - pub(super) fn from_args(args: &[Arg]) -> Result { - let mut min_width = DEFAULT_STR_MIN_WIDTH; - let mut max_width = DEFAULT_STR_MAX_WIDTH; - let mut rot_interval = DEFAULT_STR_ROT_INTERVAL; - let mut rot_separator = DEFAULT_STR_ROT_SEP; - for arg in args { - match arg.key { - "min_width" | "min_w" => { - min_width = arg.parse_value()?; - } - "max_width" | "max_w" => { - max_width = arg.parse_value()?; - } - "width" | "w" => { - min_width = arg.parse_value()?; - max_width = min_width; - } - "rot_interval" => { - rot_interval = Some(arg.parse_value()?); - } - "rot_separator" => { - rot_separator = Some(arg.parse_value()?); - } - other => { - return Err(Error::new(format!("Unknown argument for 'str': '{other}'"))); - } - } - } - if max_width < min_width { - return Err(Error::new( - "Max width must be greater of equal to min width", - )); - } - if let Some(rot_interval) = rot_interval - && rot_interval < 0.1 - { - return Err(Error::new("Interval must be greater than 0.1")); - } - Ok(StrFormatter { - min_width, - max_width, - rot_interval_ms: rot_interval.map(|x| (x * 1e3) as u64), - init_time: Some(Instant::now()), - rot_separator, - }) - } -} - -impl Formatter for StrFormatter { - fn format(&self, val: &Value, config: &SharedConfig) -> Result { - match val { - Value::Text(text) => { - let text: Vec<&str> = text.graphemes(true).collect(); - let width = text.len(); - Ok(match (self.rot_interval_ms, self.init_time) { - (Some(rot_interval_ms), Some(init_time)) if width > self.max_width => { - let rot_separator: Vec<&str> = self - .rot_separator - .as_deref() - .unwrap_or("|") - .graphemes(true) - .collect(); - let width = width + rot_separator.len(); // Now we include `rot_separator` at the end - let step = (init_time.elapsed().as_millis() as u64 / rot_interval_ms) - as usize - % width; - let w1 = self.max_width.min(width - step); - text.iter() - .chain(rot_separator.iter()) - .skip(step) - .take(w1) - .chain(text.iter()) - .take(self.max_width) - .collect_pango_escaped() - } - _ => text - .iter() - .chain(repeat_n(&" ", self.min_width.saturating_sub(width))) - .take(self.max_width) - .collect_pango_escaped(), - }) - } - Value::Icon(icon, value) => config.get_icon(icon, *value).map_err(Into::into), - other => Err(FormatError::IncompatibleFormatter { - ty: other.type_name(), - fmt: "str", - }), - } - } - - fn interval(&self) -> Option { - self.rot_interval_ms.map(Duration::from_millis) - } -} diff --git a/src/formatting/formatter/tally.rs b/src/formatting/formatter/tally.rs deleted file mode 100644 index 5f2ca490e7..0000000000 --- a/src/formatting/formatter/tally.rs +++ /dev/null @@ -1,582 +0,0 @@ -use std::str::FromStr; - -use crate::formatting::unit::Unit; - -use super::*; - -#[derive(Debug)] -enum Style { - ChineseCountingRods, - ChineseTally, - WesternTally, - WesternTallyUngrouped, -} - -impl FromStr for Style { - type Err = Error; - - fn from_str(s: &str) -> Result { - match s { - "chinese_counting_rods" | "ccr" => Ok(Style::ChineseCountingRods), - "chinese_tally" | "ct" => Ok(Style::ChineseTally), - "western_tally" | "wt" => Ok(Style::WesternTally), - "western_tally_ungrouped" | "wtu" => Ok(Style::WesternTallyUngrouped), - x => Err(Error::new(format!("Unknown Style: '{x}'"))), - } - } -} - -#[derive(Debug)] -pub struct TallyFormatter { - style: Style, -} - -impl TallyFormatter { - pub(super) fn from_args(args: &[Arg]) -> Result { - let mut style = Style::WesternTally; - for arg in args { - match arg.key { - "style" | "s" => { - style = arg.parse_value()?; - } - other => { - return Err(Error::new(format!( - "Unknown argument for 'tally': '{other}'" - ))); - } - } - } - Ok(Self { style }) - } -} - -const HORIZONTAL_CHINESE_COUNTING_RODS_CHARS: [char; 10] = - ['〇', '𝍠', '𝍡', '𝍢', '𝍣', '𝍤', '𝍥', '𝍦', '𝍧', '𝍨']; - -const VERTICAL_CHINESE_COUNTING_RODS_CHARS: [char; 10] = - ['〇', '𝍩', '𝍪', '𝍫', '𝍬', '𝍭', '𝍮', '𝍯', '𝍰', '𝍱']; - -const CHINESE_TALLY_CHARS: [char; 5] = ['𝍲', '𝍳', '𝍴', '𝍵', '𝍶']; - -impl Formatter for TallyFormatter { - fn format(&self, val: &Value, _config: &SharedConfig) -> Result { - match val { - Value::Number { - val, - unit: Unit::None, - } => { - let is_negative = val.is_sign_negative(); - let mut val = val.abs().round() as u64; - let mut result = String::new(); - match self.style { - Style::ChineseCountingRods => { - if is_negative { - result.push('\u{20E5}'); - } - if val == 0 { - result.insert(0, '〇'); - } else { - let mut horizontal = true; - while val != 0 { - let digit = val % 10; - val /= 10; - let charset = if horizontal { - horizontal = false; - HORIZONTAL_CHINESE_COUNTING_RODS_CHARS - } else { - horizontal = true; - VERTICAL_CHINESE_COUNTING_RODS_CHARS - }; - result.insert(0, charset[digit as usize]); - } - } - } - Style::ChineseTally => { - if is_negative { - return Err(FormatError::Other(Error::new( - "Chinese Tally marks do not support negative numbers", - ))); - } - let (fives, rem) = (val / 5, val % 5); - for _ in 0..fives { - result.push(CHINESE_TALLY_CHARS[4]); - } - if rem != 0 { - result.push(CHINESE_TALLY_CHARS[rem as usize - 1]); - } - } - Style::WesternTally | Style::WesternTallyUngrouped => { - if is_negative { - return Err(FormatError::Other(Error::new( - "Western Tally marks do not support negative numbers", - ))); - } - if matches!(self.style, Style::WesternTally) { - let fives = val / 5; - val %= 5; - for _ in 0..fives { - result.push('𝍸'); - } - } - for _ in 0..val { - result.push('𝍷'); - } - } - } - Ok(result) - } - Value::Number { .. } => Err(FormatError::Other(Error::new( - "Tally can only format Numbers with Unit::None", - ))), - other => Err(FormatError::IncompatibleFormatter { - ty: other.type_name(), - fmt: "tally", - }), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn tally_chinese_counting_rods_negative() { - let fmt = new_fmt!(tally, style: chinese_counting_rods).unwrap(); - let config = SharedConfig::default(); - - let result = fmt - .format( - &Value::Number { - val: -0.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "〇\u{20E5}"); - - for (hundreds, hundreds_char) in HORIZONTAL_CHINESE_COUNTING_RODS_CHARS - .into_iter() - .enumerate() - { - for (tens, tens_char) in VERTICAL_CHINESE_COUNTING_RODS_CHARS.into_iter().enumerate() { - for (ones, ones_char) in HORIZONTAL_CHINESE_COUNTING_RODS_CHARS - .into_iter() - .enumerate() - { - let val = -((hundreds * 100 + tens * 10 + ones) as f64); - if val == 0.0 { - continue; - } - // Contcat characters, excluding leading 〇 - let expected = String::from_iter( - [hundreds_char, tens_char, ones_char, '\u{20E5}'] - .into_iter() - .skip_while(|c| *c == '〇'), - ); - - let result = fmt - .format( - &Value::Number { - val, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, expected); - } - } - } - } - - #[test] - fn tally_chinese_counting_rods_positive() { - let fmt = new_fmt!(tally, style: chinese_counting_rods).unwrap(); - let config = SharedConfig::default(); - - let result = fmt - .format( - &Value::Number { - val: 0.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "〇"); - - for (hundreds, hundreds_char) in HORIZONTAL_CHINESE_COUNTING_RODS_CHARS - .into_iter() - .enumerate() - { - for (tens, tens_char) in VERTICAL_CHINESE_COUNTING_RODS_CHARS.into_iter().enumerate() { - for (ones, ones_char) in HORIZONTAL_CHINESE_COUNTING_RODS_CHARS - .into_iter() - .enumerate() - { - let val = (hundreds * 100 + tens * 10 + ones) as f64; - if val == 0.0 { - continue; - } - // Contcat characters, excluding leading 〇 - let expected = String::from_iter( - [hundreds_char, tens_char, ones_char] - .into_iter() - .skip_while(|c| *c == '〇'), - ); - - let result = fmt - .format( - &Value::Number { - val, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, expected); - } - } - } - } - - #[test] - fn tally_chinese_tally_negative() { - let fmt = new_fmt!(tally, style: chinese_tally).unwrap(); - let config = SharedConfig::default(); - - let result = fmt.format( - &Value::Number { - val: -1.0, - unit: Unit::None, - }, - &config, - ); - assert!(result.is_err()); - } - - #[test] - fn tally_chinese_tally_positive() { - let fmt = new_fmt!(tally, style: chinese_tally).unwrap(); - let config = SharedConfig::default(); - - let result = fmt - .format( - &Value::Number { - val: 0.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, ""); - - let result = fmt - .format( - &Value::Number { - val: 1.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍲"); - - let result = fmt - .format( - &Value::Number { - val: 2.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍳"); - - let result = fmt - .format( - &Value::Number { - val: 3.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍴"); - - let result = fmt - .format( - &Value::Number { - val: 4.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍵"); - - let result = fmt - .format( - &Value::Number { - val: 5.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍶"); - - let result = fmt - .format( - &Value::Number { - val: 6.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍶𝍲"); - - let result = fmt - .format( - &Value::Number { - val: 7.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍶𝍳"); - - let result = fmt - .format( - &Value::Number { - val: 8.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍶𝍴"); - - let result = fmt - .format( - &Value::Number { - val: 9.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍶𝍵"); - - let result = fmt - .format( - &Value::Number { - val: 10.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍶𝍶"); - } - - #[test] - fn tally_western_tally_negative() { - let fmt = new_fmt!(tally, style: western_tally).unwrap(); - let config = SharedConfig::default(); - - let result = fmt.format( - &Value::Number { - val: -1.0, - unit: Unit::None, - }, - &config, - ); - assert!(result.is_err()); - } - - #[test] - fn tally_western_tally_positive() { - let fmt = new_fmt!(tally, style: western_tally).unwrap(); - let config = SharedConfig::default(); - - let result = fmt - .format( - &Value::Number { - val: 0.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, ""); - - let result = fmt - .format( - &Value::Number { - val: 1.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍷"); - - let result = fmt - .format( - &Value::Number { - val: 2.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍷𝍷"); - - let result = fmt - .format( - &Value::Number { - val: 3.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍷𝍷𝍷"); - - let result = fmt - .format( - &Value::Number { - val: 4.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍷𝍷𝍷𝍷"); - - let result = fmt - .format( - &Value::Number { - val: 5.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍸"); - - let result = fmt - .format( - &Value::Number { - val: 6.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍸𝍷"); - } - - #[test] - fn tally_western_tally_ungrouped_negative() { - let fmt = new_fmt!(tally, style: western_tally_ungrouped).unwrap(); - let config = SharedConfig::default(); - - let result = fmt.format( - &Value::Number { - val: -1.0, - unit: Unit::None, - }, - &config, - ); - assert!(result.is_err()); - } - - #[test] - fn tally_western_tally_ungrouped_positive() { - let fmt = new_fmt!(tally, style: western_tally_ungrouped).unwrap(); - let config = SharedConfig::default(); - - let result = fmt - .format( - &Value::Number { - val: 0.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, ""); - - let result = fmt - .format( - &Value::Number { - val: 1.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍷"); - - let result = fmt - .format( - &Value::Number { - val: 2.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍷𝍷"); - - let result = fmt - .format( - &Value::Number { - val: 3.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍷𝍷𝍷"); - - let result = fmt - .format( - &Value::Number { - val: 4.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍷𝍷𝍷𝍷"); - - let result = fmt - .format( - &Value::Number { - val: 5.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍷𝍷𝍷𝍷𝍷"); - - let result = fmt - .format( - &Value::Number { - val: 6.0, - unit: Unit::None, - }, - &config, - ) - .unwrap(); - assert_eq!(result, "𝍷𝍷𝍷𝍷𝍷𝍷"); - } -} diff --git a/src/formatting/parse.rs b/src/formatting/parse.rs deleted file mode 100644 index 0ce6213ef5..0000000000 --- a/src/formatting/parse.rs +++ /dev/null @@ -1,527 +0,0 @@ -use std::{any::TypeId, str::FromStr}; - -use nom::{ - IResult, Parser as _, - branch::alt, - bytes::complete::{escaped_transform, tag, take_while, take_while1}, - character::complete::{anychar, char}, - combinator::{cut, eof, map, not, opt}, - multi::{many0, separated_list0}, - sequence::{preceded, separated_pair, terminated, tuple}, -}; - -use crate::errors::*; - -#[derive(Debug, PartialEq, Eq)] -pub struct Arg<'a> { - pub key: &'a str, - pub val: Option<&'a str>, -} - -impl Arg<'_> { - pub fn parse_value(&self) -> Result - where - T: FromStr + 'static, - T::Err: StdError + Send + Sync + 'static, - { - if TypeId::of::() == TypeId::of::() && self.val.is_none() { - Ok("true".parse().expect("'true' is valid bool")) - } else { - self.val - .or_error(|| format!("missing value for argument '{}'", self.key))? - .parse() - .or_error(|| format!("invalid value for argument '{}'", self.key)) - } - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct Formatter<'a> { - pub name: &'a str, - pub args: Vec>, -} - -#[derive(Debug, PartialEq, Eq)] -pub struct Placeholder<'a> { - pub name: &'a str, - pub formatter: Option>, -} - -#[derive(Debug, PartialEq, Eq)] -pub enum Token<'a> { - Text(String), - Placeholder(Placeholder<'a>), - Icon(&'a str), - Recursive(FormatTemplate<'a>), -} - -#[derive(Debug, PartialEq, Eq)] -pub struct TokenList<'a>(pub Vec>); - -#[derive(Debug, PartialEq, Eq)] -pub struct FormatTemplate<'a>(pub Vec>); - -#[derive(Debug, PartialEq, Eq)] -enum PError<'a> { - Expected { - expected: char, - actual: Option, - }, - Other { - input: &'a str, - kind: nom::error::ErrorKind, - }, -} - -impl<'a> nom::error::ParseError<&'a str> for PError<'a> { - fn from_error_kind(input: &'a str, kind: nom::error::ErrorKind) -> Self { - Self::Other { input, kind } - } - - fn append(_: &'a str, _: nom::error::ErrorKind, other: Self) -> Self { - other - } - - fn from_char(input: &'a str, expected: char) -> Self { - let actual = input.chars().next(); - Self::Expected { expected, actual } - } - - fn or(self, other: Self) -> Self { - other - } -} - -fn spaces(i: &str) -> IResult<&str, &str, PError<'_>> { - take_while(|x: char| x.is_ascii_whitespace())(i) -} - -fn alphanum1(i: &str) -> IResult<&str, &str, PError<'_>> { - take_while1(|x: char| x.is_alphanumeric() || x == '_' || x == '-')(i) -} - -//val -//'val ue' -fn arg1(i: &str) -> IResult<&str, &str, PError<'_>> { - alt(( - take_while1(|x: char| x.is_alphanumeric() || x == '_' || x == '-' || x == '.' || x == '%'), - preceded( - char('\''), - cut(terminated(take_while(|x: char| x != '\''), char('\''))), - ), - ))(i) -} - -// `key:val` -// `abc` -fn parse_arg(i: &str) -> IResult<&str, Arg<'_>, PError<'_>> { - alt(( - map( - separated_pair(alphanum1, char(':'), cut(arg1)), - |(key, val)| Arg { - key, - val: Some(val), - }, - ), - map(alphanum1, |key| Arg { key, val: None }), - ))(i) -} - -// `(arg,key:val)` -// `( arg, key:val , abc)` -fn parse_args(i: &str) -> IResult<&str, Vec>, PError<'_>> { - let inner = separated_list0(preceded(spaces, char(',')), preceded(spaces, parse_arg)); - preceded( - char('('), - cut(terminated(inner, preceded(spaces, char(')')))), - )(i) -} - -// `.str(width:2)` -// `.eng(unit:bits,show)` -fn parse_formatter(i: &str) -> IResult<&str, Formatter<'_>, PError<'_>> { - preceded(char('.'), cut(tuple((alphanum1, opt(parse_args))))) - .map(|(name, args)| Formatter { - name, - args: args.unwrap_or_default(), - }) - .parse(i) -} - -// `$var` -// `$key.eng(unit:bits,show)` -fn parse_placeholder(i: &str) -> IResult<&str, Placeholder<'_>, PError<'_>> { - preceded(char('$'), cut(tuple((alphanum1, opt(parse_formatter))))) - .map(|(name, formatter)| Placeholder { name, formatter }) - .parse(i) -} - -// `just escaped \| text` -fn parse_string(i: &str) -> IResult<&str, String, PError<'_>> { - preceded( - not(eof), - escaped_transform( - take_while1(|x| x != '$' && x != '^' && x != '{' && x != '}' && x != '|' && x != '\\'), - '\\', - anychar, - ), - )(i) -} - -// `^icon_name` -fn parse_icon(i: &str) -> IResult<&str, &str, PError<'_>> { - preceded(char('^'), cut(preceded(tag("icon_"), alphanum1)))(i) -} - -// `{ a | b | c }` -fn parse_recursive_template(i: &str) -> IResult<&str, FormatTemplate<'_>, PError<'_>> { - preceded(char('{'), cut(terminated(parse_format_template, char('}'))))(i) -} - -fn parse_token_list(i: &str) -> IResult<&str, TokenList<'_>, PError<'_>> { - map( - many0(alt(( - map(parse_string, Token::Text), - map(parse_placeholder, Token::Placeholder), - map(parse_icon, Token::Icon), - map(parse_recursive_template, Token::Recursive), - ))), - TokenList, - )(i) -} - -fn parse_format_template(i: &str) -> IResult<&str, FormatTemplate<'_>, PError<'_>> { - map(separated_list0(char('|'), parse_token_list), FormatTemplate)(i) -} - -pub fn parse_full(i: &str) -> Result> { - match parse_format_template(i) { - Ok((rest, template)) => { - if rest.is_empty() { - Ok(template) - } else { - Err(Error::new(format!( - "unexpected '{}'", - rest.chars().next().unwrap() - ))) - } - } - Err(err) => Err(match err { - nom::Err::Incomplete(_) => unreachable!(), - nom::Err::Error(err) | nom::Err::Failure(err) => match err { - PError::Expected { expected, actual } => { - if let Some(actual) = actual { - Error::new(format!("expected '{expected}', got '{actual}'")) - } else { - Error::new(format!("expected '{expected}', got EOF")) - } - } - PError::Other { input, kind } => { - // TODO: improve? - Error::new(format!("{kind:?} error near '{input}'")) - } - }, - }), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn arg() { - assert_eq!( - parse_arg("key:val,"), - Ok(( - ",", - Arg { - key: "key", - val: Some("val") - } - )) - ); - assert_eq!( - parse_arg("key:'val ue',"), - Ok(( - ",", - Arg { - key: "key", - val: Some("val ue") - } - )) - ); - assert_eq!( - parse_arg("key:'',"), - Ok(( - ",", - Arg { - key: "key", - val: Some("") - } - )) - ); - assert_eq!( - parse_arg("key,"), - Ok(( - ",", - Arg { - key: "key", - val: None - } - )) - ); - assert_eq!( - parse_arg("key:,"), - Err(nom::Err::Failure(PError::Expected { - expected: '\'', - actual: Some(',') - })) - ); - } - - #[test] - fn args() { - assert_eq!( - parse_args("(key:val)"), - Ok(( - "", - vec![Arg { - key: "key", - val: Some("val") - }] - )) - ); - assert_eq!( - parse_args("( abc:d , key:val )"), - Ok(( - "", - vec![ - Arg { - key: "abc", - val: Some("d"), - }, - Arg { - key: "key", - val: Some("val") - } - ] - )) - ); - assert_eq!( - parse_args("(abc)"), - Ok(( - "", - vec![Arg { - key: "abc", - val: None - }] - )) - ); - assert_eq!( - parse_args("( key:, )"), - Err(nom::Err::Failure(PError::Expected { - expected: '\'', - actual: Some(',') - })) - ); - } - - #[test] - fn formatter() { - assert_eq!( - parse_formatter(".str(key:val)"), - Ok(( - "", - Formatter { - name: "str", - args: vec![Arg { - key: "key", - val: Some("val") - }] - } - )) - ); - assert_eq!( - parse_formatter(".eng(w:3 , show:true )"), - Ok(( - "", - Formatter { - name: "eng", - args: vec![ - Arg { - key: "w", - val: Some("3") - }, - Arg { - key: "show", - val: Some("true") - } - ] - } - )) - ); - assert_eq!( - parse_formatter(".eng(w:3 , show)"), - Ok(( - "", - Formatter { - name: "eng", - args: vec![ - Arg { - key: "w", - val: Some("3") - }, - Arg { - key: "show", - val: None - } - ] - } - )) - ); - } - - #[test] - fn placeholder() { - assert_eq!( - parse_placeholder("$key"), - Ok(( - "", - Placeholder { - name: "key", - formatter: None, - } - )) - ); - assert_eq!( - parse_placeholder("$var.str()"), - Ok(( - "", - Placeholder { - name: "var", - formatter: Some(Formatter { - name: "str", - args: vec![] - }), - } - )) - ); - assert_eq!( - parse_placeholder("$var.str(a:b, c:d)"), - Ok(( - "", - Placeholder { - name: "var", - formatter: Some(Formatter { - name: "str", - args: vec![ - Arg { - key: "a", - val: Some("b") - }, - Arg { - key: "c", - val: Some("d") - } - ] - }), - } - )) - ); - assert!(parse_placeholder("$key.").is_err()); - } - - #[test] - fn icon() { - assert_eq!(parse_icon("^icon_my_icon"), Ok(("", "my_icon"))); - assert_eq!(parse_icon("^icon_m"), Ok(("", "m"))); - assert!(parse_icon("^icon_").is_err()); - assert!(parse_icon("^2").is_err()); - } - - #[test] - fn token_list() { - assert_eq!( - parse_token_list(" abc \\$ $var.str(a:b)$x "), - Ok(( - "", - TokenList(vec![ - Token::Text(" abc $ ".into()), - Token::Placeholder(Placeholder { - name: "var", - formatter: Some(Formatter { - name: "str", - args: vec![Arg { - key: "a", - val: Some("b") - }] - }) - }), - Token::Placeholder(Placeholder { - name: "x", - formatter: None, - }), - Token::Text(" ".into()) - ]) - )) - ); - } - - #[test] - fn format_template() { - assert_eq!( - parse_format_template("simple"), - Ok(( - "", - FormatTemplate(vec![TokenList(vec![Token::Text("simple".into())]),]) - )) - ); - assert_eq!( - parse_format_template(" $x.str() | N/A "), - Ok(( - "", - FormatTemplate(vec![ - TokenList(vec![ - Token::Text(" ".into()), - Token::Placeholder(Placeholder { - name: "x", - formatter: Some(Formatter { - name: "str", - args: vec![] - }) - }), - Token::Text(" ".into()), - ]), - TokenList(vec![Token::Text(" N/A ".into())]), - ]) - )) - ); - } - - #[test] - fn full() { - assert_eq!( - parse_format_template(" ^icon_my_icon {$x.str()|N/A} "), - Ok(( - "", - FormatTemplate(vec![TokenList(vec![ - Token::Text(" ".into()), - Token::Icon("my_icon"), - Token::Text(" ".into()), - Token::Recursive(FormatTemplate(vec![ - TokenList(vec![Token::Placeholder(Placeholder { - name: "x", - formatter: Some(Formatter { - name: "str", - args: vec![] - }) - })]), - TokenList(vec![Token::Text("N/A".into())]), - ])), - Token::Text(" ".into()), - ]),]) - )) - ); - } -} diff --git a/src/formatting/prefix.rs b/src/formatting/prefix.rs deleted file mode 100644 index f9d0a27f55..0000000000 --- a/src/formatting/prefix.rs +++ /dev/null @@ -1,256 +0,0 @@ -use crate::errors::*; -use std::fmt; -use std::str::FromStr; - -/// SI prefix -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum Prefix { - /// `n` - Nano, - /// `u` - Micro, - /// `m` - Milli, - /// `1` - One, - /// `1i` - /// `1i` is a special prefix which means "one but binary". `1i` is to `1` as `Ki` is to `K`. - OneButBinary, - /// `K` - Kilo, - /// `Ki` - Kibi, - /// `M` - Mega, - /// `Mi` - Mebi, - /// `G` - Giga, - /// `Gi` - Gibi, - /// `T` - Tera, - /// `Ti` - Tebi, -} - -const MUL: [f64; 13] = [ - 1e-9, - 1e-6, - 1e-3, - 1.0, - 1.0, - 1e3, - 1024.0, - 1e6, - 1024.0 * 1024.0, - 1e9, - 1024.0 * 1024.0 * 1024.0, - 1e12, - 1024.0 * 1024.0 * 1024.0 * 1024.0, -]; - -impl Prefix { - pub fn min_available() -> Self { - Self::Nano - } - - pub fn max_available() -> Self { - Self::Tebi - } - - pub fn max(self, other: Self) -> Self { - if other > self { other } else { self } - } - - pub fn apply(self, value: f64) -> f64 { - value / MUL[self as usize] - } - - pub fn eng(mut number: f64) -> Self { - if number == 0.0 { - Self::One - } else { - number = number.abs(); - if number > 1.0 { - number = number.round(); - } else { - let round_up_to = -(number.log10().ceil() as i32); - let m = 10f64.powi(round_up_to); - number = (number * m).round() / m; - } - match number.log10().div_euclid(3.) as i32 { - i32::MIN..=-3 => Prefix::Nano, - -2 => Prefix::Micro, - -1 => Prefix::Milli, - 0 => Prefix::One, - 1 => Prefix::Kilo, - 2 => Prefix::Mega, - 3 => Prefix::Giga, - 4..=i32::MAX => Prefix::Tera, - } - } - } - - pub fn eng_binary(number: f64) -> Self { - if number == 0.0 { - Self::One - } else { - match number.abs().round().log2().div_euclid(10.) as i32 { - i32::MIN..=0 => Prefix::OneButBinary, - 1 => Prefix::Kibi, - 2 => Prefix::Mebi, - 3 => Prefix::Gibi, - 4..=i32::MAX => Prefix::Tebi, - } - } - } - - pub fn is_binary(&self) -> bool { - matches!( - self, - Self::OneButBinary | Self::Kibi | Self::Mebi | Self::Gibi | Self::Tebi - ) - } -} - -impl fmt::Display for Prefix { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match self { - Self::Nano => "n", - Self::Micro => "u", - Self::Milli => "m", - Self::One | Self::OneButBinary => "", - Self::Kilo => "K", - Self::Kibi => "Ki", - Self::Mega => "M", - Self::Mebi => "Mi", - Self::Giga => "G", - Self::Gibi => "Gi", - Self::Tera => "T", - Self::Tebi => "Ti", - }) - } -} - -impl FromStr for Prefix { - type Err = Error; - - fn from_str(s: &str) -> Result { - match s { - "n" => Ok(Prefix::Nano), - "u" => Ok(Prefix::Micro), - "m" => Ok(Prefix::Milli), - "1" => Ok(Prefix::One), - "1i" => Ok(Prefix::OneButBinary), - "K" => Ok(Prefix::Kilo), - "Ki" => Ok(Prefix::Kibi), - "M" => Ok(Prefix::Mega), - "Mi" => Ok(Prefix::Mebi), - "G" => Ok(Prefix::Giga), - "Gi" => Ok(Prefix::Gibi), - "T" => Ok(Prefix::Tera), - "Ti" => Ok(Prefix::Tebi), - x => Err(Error::new(format!("Unknown prefix: '{x}'"))), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn eng() { - assert_eq!(Prefix::eng(0.000_000_000_1), Prefix::Nano); - assert_eq!(Prefix::eng(0.000_000_001), Prefix::Nano); - assert_eq!(Prefix::eng(0.000_000_01), Prefix::Nano); - assert_eq!(Prefix::eng(0.000_000_1), Prefix::Nano); - assert_eq!(Prefix::eng(0.000_001), Prefix::Micro); - assert_eq!(Prefix::eng(0.000_01), Prefix::Micro); - assert_eq!(Prefix::eng(0.000_1), Prefix::Micro); - assert_eq!(Prefix::eng(0.001), Prefix::Milli); - assert_eq!(Prefix::eng(0.01), Prefix::Milli); - assert_eq!(Prefix::eng(0.1), Prefix::Milli); - assert_eq!(Prefix::eng(1.0), Prefix::One); - assert_eq!(Prefix::eng(10.0), Prefix::One); - assert_eq!(Prefix::eng(100.0), Prefix::One); - assert_eq!(Prefix::eng(1_000.0), Prefix::Kilo); - assert_eq!(Prefix::eng(10_000.0), Prefix::Kilo); - assert_eq!(Prefix::eng(100_000.0), Prefix::Kilo); - assert_eq!(Prefix::eng(1_000_000.0), Prefix::Mega); - assert_eq!(Prefix::eng(10_000_000.0), Prefix::Mega); - assert_eq!(Prefix::eng(100_000_000.0), Prefix::Mega); - assert_eq!(Prefix::eng(1_000_000_000.0), Prefix::Giga); - assert_eq!(Prefix::eng(10_000_000_000.0), Prefix::Giga); - assert_eq!(Prefix::eng(100_000_000_000.0), Prefix::Giga); - assert_eq!(Prefix::eng(1_000_000_000_000.0), Prefix::Tera); - assert_eq!(Prefix::eng(10_000_000_000_000.0), Prefix::Tera); - assert_eq!(Prefix::eng(100_000_000_000_000.0), Prefix::Tera); - assert_eq!(Prefix::eng(1_000_000_000_000_000.0), Prefix::Tera); - } - - #[test] - fn eng_round() { - assert_eq!(Prefix::eng(0.000_000_000_09), Prefix::Nano); - assert_eq!(Prefix::eng(0.000_000_000_9), Prefix::Nano); - assert_eq!(Prefix::eng(0.000_000_009), Prefix::Nano); - assert_eq!(Prefix::eng(0.000_000_09), Prefix::Nano); - assert_eq!(Prefix::eng(0.000_000_9), Prefix::Micro); - assert_eq!(Prefix::eng(0.000_009), Prefix::Micro); - assert_eq!(Prefix::eng(0.000_09), Prefix::Micro); - assert_eq!(Prefix::eng(0.000_9), Prefix::Milli); - assert_eq!(Prefix::eng(0.009), Prefix::Milli); - assert_eq!(Prefix::eng(0.09), Prefix::Milli); - assert_eq!(Prefix::eng(0.9), Prefix::One); - assert_eq!(Prefix::eng(9.9), Prefix::One); - assert_eq!(Prefix::eng(99.9), Prefix::One); - assert_eq!(Prefix::eng(999.9), Prefix::Kilo); - assert_eq!(Prefix::eng(9_999.9), Prefix::Kilo); - assert_eq!(Prefix::eng(99_999.9), Prefix::Kilo); - assert_eq!(Prefix::eng(999_999.9), Prefix::Mega); - assert_eq!(Prefix::eng(9_999_999.9), Prefix::Mega); - assert_eq!(Prefix::eng(99_999_999.9), Prefix::Mega); - assert_eq!(Prefix::eng(999_999_999.9), Prefix::Giga); - assert_eq!(Prefix::eng(9_999_999_999.9), Prefix::Giga); - assert_eq!(Prefix::eng(99_999_999_999.9), Prefix::Giga); - assert_eq!(Prefix::eng(999_999_999_999.9), Prefix::Tera); - assert_eq!(Prefix::eng(9_999_999_999_999.9), Prefix::Tera); - assert_eq!(Prefix::eng(99_999_999_999_999.9), Prefix::Tera); - assert_eq!(Prefix::eng(999_999_999_999_999.9), Prefix::Tera); - } - - #[test] - fn eng_binary() { - assert_eq!(Prefix::eng_binary(0.1), Prefix::OneButBinary); - assert_eq!(Prefix::eng_binary(1.0), Prefix::OneButBinary); - assert_eq!(Prefix::eng_binary((1 << 9) as f64), Prefix::OneButBinary); - assert_eq!(Prefix::eng_binary((1 << 10) as f64), Prefix::Kibi); - assert_eq!(Prefix::eng_binary((1 << 19) as f64), Prefix::Kibi); - assert_eq!(Prefix::eng_binary((1 << 29) as f64), Prefix::Mebi); - assert_eq!(Prefix::eng_binary((1 << 20) as f64), Prefix::Mebi); - assert_eq!(Prefix::eng_binary((1 << 30) as f64), Prefix::Gibi); - assert_eq!(Prefix::eng_binary((1_u64 << 39) as f64), Prefix::Gibi); - assert_eq!(Prefix::eng_binary((1_u64 << 40) as f64), Prefix::Tebi); - assert_eq!(Prefix::eng_binary((1_u64 << 49) as f64), Prefix::Tebi); - assert_eq!(Prefix::eng_binary((1_u64 << 50) as f64), Prefix::Tebi); - } - - #[test] - fn eng_binary_round() { - assert_eq!(Prefix::eng_binary(0.9), Prefix::OneButBinary); - assert_eq!( - Prefix::eng_binary((1 << 9) as f64 - 0.1), - Prefix::OneButBinary - ); - assert_eq!(Prefix::eng_binary((1 << 10) as f64 - 0.1), Prefix::Kibi); - assert_eq!(Prefix::eng_binary((1 << 19) as f64 - 0.1), Prefix::Kibi); - assert_eq!(Prefix::eng_binary((1 << 29) as f64 - 0.1), Prefix::Mebi); - assert_eq!(Prefix::eng_binary((1 << 20) as f64 - 0.1), Prefix::Mebi); - assert_eq!(Prefix::eng_binary((1 << 30) as f64 - 0.1), Prefix::Gibi); - assert_eq!(Prefix::eng_binary((1_u64 << 39) as f64 - 0.1), Prefix::Gibi); - assert_eq!(Prefix::eng_binary((1_u64 << 40) as f64 - 0.1), Prefix::Tebi); - assert_eq!(Prefix::eng_binary((1_u64 << 49) as f64 - 0.1), Prefix::Tebi); - assert_eq!(Prefix::eng_binary((1_u64 << 50) as f64 - 0.1), Prefix::Tebi); - } -} diff --git a/src/formatting/scheduling.rs b/src/formatting/scheduling.rs deleted file mode 100644 index 017fc26a24..0000000000 --- a/src/formatting/scheduling.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::BoxedStream; -use futures::stream::StreamExt as _; -use std::time::{Duration, Instant}; -use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}; - -pub fn manage_widgets_updates() -> (UnboundedSender<(usize, Vec)>, BoxedStream>) { - let (intervals_tx, intervals_rx) = unbounded_channel::<(usize, Vec)>(); - struct State { - time_anchor: Instant, - last_update: u64, - intervals_rx: UnboundedReceiver<(usize, Vec)>, - intervals: Vec<(usize, Vec)>, - } - let stream = futures::stream::unfold( - State { - time_anchor: Instant::now(), - last_update: 0, - intervals_rx, - intervals: Vec::new(), - }, - |mut state| async move { - loop { - if state.intervals.is_empty() { - let (id, new_intervals) = state.intervals_rx.recv().await?; - state.intervals.retain(|(i, _)| *i != id); - if !new_intervals.is_empty() { - state.intervals.push((id, new_intervals)); - } - continue; - } - - let time = state.time_anchor.elapsed().as_millis() as u64; - - let mut blocks = Vec::new(); - let mut delay = 100000; - for (id, intervals) in &state.intervals { - let block_delay = single_block_next_update(intervals, time, state.last_update); - if block_delay < delay { - delay = block_delay; - blocks.clear(); - } - if block_delay == delay { - blocks.push(*id); - } - } - - if delay == 0 { - state.last_update = time; - return Some((blocks, state)); - } - - if let Ok(Some((id, new_intervals))) = - tokio::time::timeout(Duration::from_millis(delay), state.intervals_rx.recv()) - .await - { - state.intervals.retain(|(i, _)| *i != id); - if !new_intervals.is_empty() { - state.intervals.push((id, new_intervals)); - } - } - } - }, - ) - .boxed(); - (intervals_tx, stream) -} - -fn single_block_next_update(intervals: &[u64], time: u64, last_update: u64) -> u64 { - fn next_update(time: u64, interval: u64) -> u64 { - time + interval - time % interval - } - let mut time_to_next = u64::MAX; - for &interval in intervals { - if next_update(last_update, interval) <= time { - return 0; - } - time_to_next = time_to_next.min(next_update(time, interval) - time); - } - time_to_next -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn single_block() { - // 0 100 200 300 400 500 600 700 800 900 1000 - // | | | | | | | | | | | - // 200 x x x x x x - // 300 x x x x - // 500 x x x - let intervals = &[200, 300, 500]; - assert_eq!(single_block_next_update(intervals, 0, 0), 200); - assert_eq!(single_block_next_update(intervals, 50, 0), 150); - assert_eq!(single_block_next_update(intervals, 210, 50), 0); - assert_eq!(single_block_next_update(intervals, 290, 210), 10); - assert_eq!(single_block_next_update(intervals, 300, 290), 0); - assert_eq!(single_block_next_update(intervals, 300, 300), 100); - assert_eq!(single_block_next_update(intervals, 800, 300), 0); - } -} diff --git a/src/formatting/template.rs b/src/formatting/template.rs deleted file mode 100644 index a4857672cc..0000000000 --- a/src/formatting/template.rs +++ /dev/null @@ -1,208 +0,0 @@ -use super::formatter::{Formatter, new_formatter}; -use super::{FormatError, Fragment, Values, parse}; -use crate::config::SharedConfig; -use crate::errors::*; - -use std::str::FromStr; -use std::sync::Arc; - -#[derive(Debug, Clone)] -pub struct FormatTemplate(Arc<[TokenList]>); - -impl Default for FormatTemplate { - fn default() -> Self { - Self(Arc::new([])) - } -} - -#[derive(Debug)] -pub struct TokenList(pub Vec); - -#[derive(Debug)] -pub enum Token { - Text(String), - Recursive(FormatTemplate), - Placeholder { - name: String, - formatter: Option>, - }, - Icon { - name: String, - }, -} - -impl FormatTemplate { - pub fn contains_key(&self, key: &str) -> bool { - self.0.iter().any(|token_list| { - token_list.0.iter().any(|token| match token { - Token::Placeholder { name, .. } => name == key, - Token::Recursive(rec) => rec.contains_key(key), - _ => false, - }) - }) - } - - pub fn render( - &self, - values: &Values, - config: &SharedConfig, - ) -> Result, FormatError> { - for (i, token_list) in self.0.iter().enumerate() { - match token_list.render(values, config) { - Ok(res) => return Ok(res), - Err( - FormatError::PlaceholderNotFound(_) - | FormatError::IncompatibleFormatter { .. } - | FormatError::NumberOutOfRange(_), - ) if i != self.0.len() - 1 => (), - Err(e) => return Err(e), - } - } - Ok(Vec::new()) - } - - pub fn init_intervals(&self, intervals: &mut Vec) { - for tl in self.0.iter() { - for t in &tl.0 { - match t { - Token::Recursive(r) => r.init_intervals(intervals), - Token::Placeholder { - formatter: Some(f), .. - } => { - if let Some(i) = f.interval() { - intervals.push(i.as_millis() as u64); - } - } - _ => (), - } - } - } - } -} - -impl TokenList { - pub fn render( - &self, - values: &Values, - config: &SharedConfig, - ) -> Result, FormatError> { - let mut retval = Vec::new(); - let mut cur = Fragment::default(); - for token in &self.0 { - match token { - Token::Text(text) => { - if cur.metadata.is_default() { - cur.text.push_str(text); - } else { - if !cur.text.is_empty() { - retval.push(cur); - } - cur = text.clone().into(); - } - } - Token::Recursive(rec) => { - if !cur.text.is_empty() { - retval.push(cur); - } - retval.extend(rec.render(values, config)?); - cur = retval.pop().unwrap_or_default(); - } - Token::Placeholder { name, formatter } => { - let value = values - .get(name.as_str()) - .ok_or_else(|| FormatError::PlaceholderNotFound(name.into()))?; - let formatter = formatter - .as_ref() - .map(Box::as_ref) - .unwrap_or_else(|| value.default_formatter()); - let formatted = formatter.format(&value.inner, config)?; - if value.metadata == cur.metadata { - cur.text.push_str(&formatted); - } else { - if !cur.text.is_empty() { - retval.push(cur); - } - cur = Fragment { - text: formatted, - metadata: value.metadata, - }; - } - } - Token::Icon { name } => { - let icon = config.get_icon(name, None)?; - if cur.metadata.is_default() { - cur.text.push_str(&icon); - } else { - if !cur.text.is_empty() { - retval.push(cur); - } - cur = icon.into(); - } - } - } - } - - if !cur.text.is_empty() { - retval.push(cur); - } - - Ok(retval) - } -} - -impl FromStr for FormatTemplate { - type Err = Error; - - fn from_str(s: &str) -> Result { - parse::parse_full(s) - .and_then(TryInto::try_into) - .error("Incorrect format template") - } -} - -impl TryFrom> for FormatTemplate { - type Error = Error; - - fn try_from(value: parse::FormatTemplate) -> Result { - value - .0 - .into_iter() - .map(TryInto::try_into) - .collect::>>() - .map(Self) - } -} - -impl TryFrom> for TokenList { - type Error = Error; - - fn try_from(value: parse::TokenList) -> Result { - value - .0 - .into_iter() - .map(TryInto::try_into) - .collect::>>() - .map(Self) - } -} - -impl TryFrom> for Token { - type Error = Error; - - fn try_from(value: parse::Token) -> Result { - Ok(match value { - parse::Token::Text(text) => Self::Text(text), - parse::Token::Placeholder(placeholder) => Self::Placeholder { - name: placeholder.name.to_owned(), - formatter: placeholder - .formatter - .map(|fmt| new_formatter(fmt.name, &fmt.args)) - .transpose()?, - }, - parse::Token::Icon(icon) => Self::Icon { - name: icon.to_owned(), - }, - parse::Token::Recursive(rec) => Self::Recursive(rec.try_into()?), - }) - } -} diff --git a/src/formatting/unit.rs b/src/formatting/unit.rs deleted file mode 100644 index 12eb66d1ba..0000000000 --- a/src/formatting/unit.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::fmt; -use std::str::FromStr; - -use super::prefix::Prefix; -use crate::errors::*; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Unit { - /// `B` - Bytes, - /// `b` - Bits, - /// `%` - Percents, - /// `deg` - Degrees, - /// `s` - Seconds, - /// `W` - Watts, - /// `Hz` - Hertz, - /// `` - None, -} - -impl fmt::Display for Unit { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match self { - Self::Bytes => "B", - Self::Bits => "b", - Self::Percents => "%", - Self::Degrees => "°", - Self::Seconds => "s", - Self::Watts => "W", - Self::Hertz => "Hz", - Self::None => "", - }) - } -} - -impl FromStr for Unit { - type Err = Error; - - fn from_str(s: &str) -> Result { - match s { - "B" => Ok(Unit::Bytes), - "b" => Ok(Unit::Bits), - "%" => Ok(Unit::Percents), - "deg" => Ok(Unit::Degrees), - "s" => Ok(Unit::Seconds), - "W" => Ok(Unit::Watts), - "Hz" => Ok(Unit::Hertz), - "" => Ok(Unit::None), - x => Err(Error::new(format!("Unknown unit: '{x}'"))), - } - } -} - -impl Unit { - pub fn convert(self, value: f64, unit: Self) -> Result { - match (self, unit) { - (x, y) if x == y => Ok(value), - (Self::Bytes, Self::Bits) => Ok(value * 8.), - (Self::Bits, Self::Bytes) => Ok(value / 8.), - _ => Err(Error::new(format!("Failed to convert '{self}' to '{unit}"))), - } - } - - pub fn clamp_prefix(self, prefix: Prefix) -> Prefix { - match self { - Self::Bytes | Self::Bits => prefix.max(Prefix::One), - Self::Percents | Self::Degrees | Self::None => Prefix::One, - _ => prefix, - } - } -} diff --git a/src/formatting/value.rs b/src/formatting/value.rs deleted file mode 100644 index b5e3d232cc..0000000000 --- a/src/formatting/value.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::borrow::Cow; -use std::time::Duration; - -use super::Metadata; -use super::formatter; -use super::unit::Unit; -use chrono::{DateTime, Utc}; -use chrono_tz::Tz; - -#[derive(Debug, Clone)] -pub struct Value { - pub inner: ValueInner, - pub metadata: Metadata, -} - -#[derive(Debug, Clone)] -pub enum ValueInner { - Text(String), - Icon(Cow<'static, str>, Option), - Number { val: f64, unit: Unit }, - Datetime(DateTime, Option), - Duration(Duration), - Flag, -} - -impl ValueInner { - pub fn type_name(&self) -> &'static str { - match self { - ValueInner::Text(..) => "Text", - ValueInner::Icon(..) => "Icon", - ValueInner::Number { .. } => "Number", - ValueInner::Datetime(..) => "Datetime", - ValueInner::Duration(..) => "Duration", - ValueInner::Flag => "Flag", - } - } -} - -pub trait IntoF64 { - fn into_f64(self) -> f64; -} - -macro_rules! impl_into_f64 { - ($($t:ty),+) => { - $( - impl IntoF64 for $t { - fn into_f64(self) -> f64 { - self as _ - } - } - )+ - } -} -impl_into_f64!(f64, f32, i64, u64, i32, u32, i16, u16, i8, u8, usize, isize); - -/// Constructors -impl Value { - pub fn new(val: ValueInner) -> Self { - Self { - inner: val, - metadata: Default::default(), - } - } - - pub fn flag() -> Self { - Self::new(ValueInner::Flag) - } - - pub fn datetime(datetime: DateTime, tz: Option) -> Self { - Self::new(ValueInner::Datetime(datetime, tz)) - } - - pub fn duration(duration: Duration) -> Self { - Self::new(ValueInner::Duration(duration)) - } - - pub fn icon(name: S) -> Self - where - S: Into>, - { - Self::new(ValueInner::Icon(name.into(), None)) - } - - pub fn icon_progression(name: S, value: f64) -> Self - where - S: Into>, - { - Self::new(ValueInner::Icon(name.into(), Some(value))) - } - pub fn icon_progression_bound(name: S, value: f64, low: f64, high: f64) -> Self - where - S: Into>, - { - Self::icon_progression(name, (value.clamp(low, high) - low) / (high - low)) - } - - pub fn text(text: String) -> Self { - Self::new(ValueInner::Text(text)) - } - - pub fn number_unit(val: impl IntoF64, unit: Unit) -> Self { - Self::new(ValueInner::Number { - val: val.into_f64(), - unit, - }) - } - - pub fn bytes(val: impl IntoF64) -> Self { - Self::number_unit(val, Unit::Bytes) - } - pub fn bits(val: impl IntoF64) -> Self { - Self::number_unit(val, Unit::Bits) - } - pub fn percents(val: impl IntoF64) -> Self { - Self::number_unit(val, Unit::Percents) - } - pub fn degrees(val: impl IntoF64) -> Self { - Self::number_unit(val, Unit::Degrees) - } - pub fn seconds(val: impl IntoF64) -> Self { - Self::number_unit(val, Unit::Seconds) - } - pub fn watts(val: impl IntoF64) -> Self { - Self::number_unit(val, Unit::Watts) - } - pub fn hertz(val: impl IntoF64) -> Self { - Self::number_unit(val, Unit::Hertz) - } - pub fn number(val: impl IntoF64) -> Self { - Self::number_unit(val, Unit::None) - } -} - -/// Set options -impl Value { - pub fn with_instance(mut self, instance: &'static str) -> Self { - self.metadata.instance = Some(instance); - self - } - - pub fn underline(mut self, val: bool) -> Self { - self.metadata.underline = val; - self - } - - pub fn italic(mut self, val: bool) -> Self { - self.metadata.italic = val; - self - } - - pub fn default_formatter(&self) -> &'static dyn formatter::Formatter { - match &self.inner { - ValueInner::Text(_) | ValueInner::Icon(..) => &formatter::DEFAULT_STRING_FORMATTER, - ValueInner::Number { .. } => &formatter::DEFAULT_NUMBER_FORMATTER, - ValueInner::Datetime { .. } => &*formatter::DEFAULT_DATETIME_FORMATTER, - ValueInner::Duration { .. } => &formatter::DEFAULT_DURATION_FORMATTER, - ValueInner::Flag => &formatter::DEFAULT_FLAG_FORMATTER, - } - } -} diff --git a/src/geolocator.rs b/src/geolocator.rs deleted file mode 100644 index 391e0533c5..0000000000 --- a/src/geolocator.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! Geolocation service -//! -//! This global module can be used to provide geolocation information -//! to blocks that support it. -//! -//! ipapi.co is the default geolocator service. -//! -//! # Configuration -//! -//! # ipapi.co Options -//! -//! Key | Values | Required | Default -//! ----|--------|----------|-------- -//! `geolocator` | `ipapi` | Yes | None -//! -//! # Ip2Location.io Options -//! -//! Key | Values | Required | Default -//! ----|--------|----------|-------- -//! `geolocator` | `ip2location` | Yes | None -//! `api_key` | Your Ip2Location.io API key. | No | None -//! -//! An api key is not required to get back basic information from ip2location.io. -//! However, to get more additional information, an api key is required. -//! See [pricing](https://www.ip2location.io/pricing) for more information. -//! -//! The `api_key` option can be omitted from configuration, in which case it -//! can be provided in the environment variable `IP2LOCATION_API_KEY` -//! -//! -//! # Examples -//! -//! Use the default geolocator service: -//! -//! ```toml -//! [geolocator] -//! geolocator = "ipapi" -//! ``` -//! -//! Use Ip2Location.io -//! -//! ```toml -//! [geolocator] -//! geolocator = "ip2location" -//! api_key = "XXX" -//! ``` - -use crate::errors::{Error, ErrorContext as _, Result, StdError}; -use std::borrow::Cow; -use std::fmt; -use std::sync::{Arc, Mutex}; -use std::time::{Duration, Instant}; - -use serde::Deserialize; -use smart_default::SmartDefault; - -mod ip2location; -mod ipapi; - -#[derive(Debug)] -struct AutolocateResult { - location: IPAddressInfo, - timestamp: Instant, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct IPAddressInfo { - // Required fields - pub ip: String, - pub latitude: f64, - pub longitude: f64, - pub city: String, - - // Optional fields - pub version: Option, - pub region: Option, - pub region_code: Option, - pub country: Option, - pub country_name: Option, - pub country_code: Option, - pub country_code_iso3: Option, - pub country_capital: Option, - pub country_tld: Option, - pub continent_code: Option, - pub in_eu: Option, - pub postal: Option, - pub timezone: Option, - pub utc_offset: Option, - pub country_calling_code: Option, - pub currency: Option, - pub currency_name: Option, - pub languages: Option, - pub country_area: Option, - pub country_population: Option, - pub asn: Option, - pub org: Option, -} - -#[derive(Default, Debug, Deserialize)] -#[serde(from = "GeolocatorBackend")] -pub struct Geolocator { - backend: GeolocatorBackend, - last_autolocate: Mutex>, -} - -impl Geolocator { - pub fn name(&self) -> Cow<'static, str> { - self.backend.name() - } - - /// No-op if last API call was made in the last `interval` seconds. - pub async fn find_ip_location( - &self, - client: &reqwest::Client, - interval: Duration, - ) -> Result { - { - let guard = self.last_autolocate.lock().unwrap(); - if let Some(cached) = &*guard - && cached.timestamp.elapsed() < interval - { - return Ok(cached.location.clone()); - } - } - - let location = self.backend.get_info(client).await?; - - { - let mut guard = self.last_autolocate.lock().unwrap(); - *guard = Some(AutolocateResult { - location: location.clone(), - timestamp: Instant::now(), - }); - } - - Ok(location) - } -} - -#[derive(Deserialize, Debug, SmartDefault, Clone)] -#[serde(tag = "geolocator", rename_all = "lowercase", deny_unknown_fields)] -pub enum GeolocatorBackend { - #[default] - Ipapi(ipapi::Config), - Ip2Location(ip2location::Config), -} - -impl GeolocatorBackend { - fn name(&self) -> Cow<'static, str> { - match self { - GeolocatorBackend::Ipapi(_) => ipapi::Ipapi.name(), - GeolocatorBackend::Ip2Location(_) => ip2location::Ip2Location.name(), - } - } - - async fn get_info(&self, client: &reqwest::Client) -> Result { - match self { - GeolocatorBackend::Ipapi(_) => ipapi::Ipapi.get_info(client).await, - GeolocatorBackend::Ip2Location(config) => { - ip2location::Ip2Location - .get_info(client, config.api_key.as_ref()) - .await - } - } - } -} - -impl From for Geolocator { - fn from(backend: GeolocatorBackend) -> Self { - Self { - backend, - last_autolocate: Mutex::new(None), - } - } -} diff --git a/src/geolocator/ip2location.rs b/src/geolocator/ip2location.rs deleted file mode 100644 index 4e0c36bd85..0000000000 --- a/src/geolocator/ip2location.rs +++ /dev/null @@ -1,235 +0,0 @@ -use super::*; - -const IP_API_URL: &str = "https://api.ip2location.io/"; -pub(super) const API_KEY_ENV: &str = "IP2LOCATION_API_KEY"; - -#[derive(Deserialize, Debug, Default, Clone)] -#[serde(deny_unknown_fields)] -pub struct Config { - #[serde(default = "getenv_api_key")] - pub api_key: Option, -} - -fn getenv_api_key() -> Option { - std::env::var(API_KEY_ENV).ok() -} - -#[derive(Deserialize)] -struct ApiResponse { - #[serde(flatten)] - data: Option, - #[serde(default)] - error: Option, -} - -#[derive(Deserialize)] -struct Ip2LocationAddressInfo { - // Provided without api key - ip: String, - country_code: String, - country_name: String, - region_name: String, - city_name: String, - latitude: f64, - longitude: f64, - zip_code: String, - time_zone: String, - asn: String, - // #[serde(rename = "as")] - // as_: String, - // is_proxy: bool, - - // Requires api key - // isp - // domain - // net_speed - // idd_code - // area_code - // weather_station_code - // weather_station_name - // mcc - // mnc - // mobile_brand - // elevation - // usage_type - // address_type - // ads_category - // ads_category_name - // district - continent: Option, - country: Option, - region: Option, - // city.name - // city.translation - time_zone_info: Option, - // geotargeting.metro - // fraud_score - // proxy.last_seen - // proxy.proxy_type - // proxy.threat - // proxy.provider - // proxy.is_vpn - // proxy.is_tor - // proxy.is_data_center - // proxy.is_public_proxy - // proxy.is_web_proxy - // proxy.is_web_crawler - // proxy.is_residential_proxy - // proxy.is_spammer - // proxy.is_scanner - // proxy.is_botnet - // proxy.is_consumer_privacy_network - // proxy.is_enterprise_private_network -} - -#[derive(Deserialize)] -struct Continent { - // name, - code: String, - // hemisphere, - // translation, -} - -#[derive(Deserialize)] -struct Country { - // name: String, - alpha3_code: String, - // numeric_code: String, - // demonym: String, - // flag: String, - capital: String, - total_area: f64, - population: f64, - currency: Currency, - language: Language, - tld: String, - // translation, -} - -#[derive(Deserialize)] -struct Currency { - name: String, - code: String, - // translation, -} -#[derive(Deserialize)] -struct Language { - // name: String, - code: String, -} -#[derive(Deserialize)] -struct Region { - // name: String, - code: String, - // translation, -} - -#[derive(Deserialize)] -struct TimeZoneInfo { - olson: String, - // current_time: String - // gmt_offset: String - // is_dst: String - // sunrise: String - // sunset: String -} - -impl From for IPAddressInfo { - fn from(val: Ip2LocationAddressInfo) -> Self { - let mut info = IPAddressInfo { - ip: val.ip, - city: val.city_name, - latitude: val.latitude, - longitude: val.longitude, - region: Some(val.region_name), - country: Some(val.country_code.clone()), - country_name: Some(val.country_name), - country_code: Some(val.country_code), - postal: Some(val.zip_code), - utc_offset: Some(val.time_zone), - asn: Some(val.asn), - ..Default::default() - }; - - if let Some(region) = val.region { - info.region_code = Some(region.code); - } - - if let Some(country) = val.country { - info.country_area = Some(country.total_area); - info.country_population = Some(country.population); - info.currency = Some(country.currency.code); - info.currency_name = Some(country.currency.name); - info.languages = Some(country.language.code); - info.country_tld = Some(country.tld); - info.country_capital = Some(country.capital); - info.country_code_iso3 = Some(country.alpha3_code); - } - - if let Some(continent) = val.continent { - info.continent_code = Some(continent.code); - } - - if let Some(time_zone_info) = val.time_zone_info { - info.timezone = Some(time_zone_info.olson); - } - - info - } -} - -#[derive(Deserialize, Default, Debug)] -struct ApiError { - error_code: u32, - error_message: String, -} - -impl fmt::Display for ApiError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_fmt(format_args!( - "Error {}: {}", - self.error_code, self.error_message - )) - } -} -impl StdError for ApiError {} - -pub struct Ip2Location; - -impl Ip2Location { - pub fn name(&self) -> Cow<'static, str> { - Cow::Borrowed("ip2location.io") - } - - pub async fn get_info( - &self, - client: &reqwest::Client, - api_key: Option<&String>, - ) -> Result { - let mut request_builder = client.get(IP_API_URL); - - if let Some(api_key) = api_key { - request_builder = request_builder.query(&[("key", api_key)]); - } - - let response: ApiResponse = request_builder - .send() - .await - .error("Failed during request for current location")? - .json() - .await - .error("Failed while parsing location API result")?; - - if let Some(error) = response.error { - Err(Error { - message: Some("ip2location.io error".into()), - cause: Some(Arc::new(error)), - }) - } else { - Ok(response - .data - .error("Failed while parsing location API result")? - .into()) - } - } -} diff --git a/src/geolocator/ipapi.rs b/src/geolocator/ipapi.rs deleted file mode 100644 index 7cf2268c74..0000000000 --- a/src/geolocator/ipapi.rs +++ /dev/null @@ -1,124 +0,0 @@ -use super::*; - -const IP_API_URL: &str = "https://ipapi.co/json"; - -// This config is here just to make sure that no other config is provided -#[derive(Deserialize, Debug, Default, Clone)] -#[serde(deny_unknown_fields)] -pub struct Config {} - -#[derive(Deserialize)] -struct ApiResponse { - #[serde(flatten)] - data: Option, - #[serde(default)] - error: bool, - #[serde(default)] - reason: ApiError, -} - -#[derive(Deserialize, Default)] -#[serde(default)] -struct IpApiAddressInfo { - ip: String, - version: String, - city: String, - region: String, - region_code: String, - country: String, - country_name: String, - country_code: String, - country_code_iso3: String, - country_capital: String, - country_tld: String, - continent_code: String, - in_eu: bool, - postal: Option, - latitude: f64, - longitude: f64, - timezone: String, - utc_offset: String, - country_calling_code: String, - currency: String, - currency_name: String, - languages: String, - country_area: f64, - country_population: f64, - asn: String, - org: String, -} - -impl From for IPAddressInfo { - fn from(val: IpApiAddressInfo) -> Self { - IPAddressInfo { - ip: val.ip, - version: Some(val.version), - city: val.city, - region: Some(val.region), - region_code: Some(val.region_code), - country: Some(val.country), - country_name: Some(val.country_name), - country_code: Some(val.country_code), - country_code_iso3: Some(val.country_code_iso3), - country_capital: Some(val.country_capital), - country_tld: Some(val.country_tld), - continent_code: Some(val.continent_code), - in_eu: Some(val.in_eu), - postal: val.postal, - latitude: val.latitude, - longitude: val.longitude, - timezone: Some(val.timezone), - utc_offset: Some(val.utc_offset), - country_calling_code: Some(val.country_calling_code), - currency: Some(val.currency), - currency_name: Some(val.currency_name), - languages: Some(val.languages), - country_area: Some(val.country_area), - country_population: Some(val.country_population), - asn: Some(val.asn), - org: Some(val.org), - } - } -} - -#[derive(Deserialize, Default, Debug)] -#[serde(transparent)] -struct ApiError(Option); - -impl fmt::Display for ApiError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.0.as_deref().unwrap_or("Unknown Error")) - } -} -impl StdError for ApiError {} - -pub struct Ipapi; - -impl Ipapi { - pub fn name(&self) -> Cow<'static, str> { - Cow::Borrowed("ipapi.co") - } - - pub async fn get_info(&self, client: &reqwest::Client) -> Result { - let response: ApiResponse = client - .get(IP_API_URL) - .send() - .await - .error("Failed during request for current location")? - .json() - .await - .error("Failed while parsing location API result")?; - - if response.error { - Err(Error { - message: Some("ipapi.co error".into()), - cause: Some(Arc::new(response.reason)), - }) - } else { - Ok(response - .data - .error("Failed while parsing location API result")? - .into()) - } - } -} diff --git a/src/i3status_rs/blocks.rs.html b/src/i3status_rs/blocks.rs.html new file mode 100644 index 0000000000..9f3411f4d3 --- /dev/null +++ b/src/i3status_rs/blocks.rs.html @@ -0,0 +1,285 @@ +blocks.rs - source

i3status_rs/
blocks.rs

1//! The collection of blocks
+2//!
+3//! Blocks are defined as a [TOML array of tables](https://github.com/toml-lang/toml/blob/main/toml.md#user-content-array-of-tables): `[[block]]`
+4//!
+5//! Key | Description | Default
+6//! ----|-------------|----------
+7//! `block` | Name of the i3status-rs block you want to use. See [modules](#modules) below for valid block names. | -
+8//! `signal` | Signal value that causes an update for this block with `0` corresponding to `-SIGRTMIN+0` and the largest value being `-SIGRTMAX` | None
+9//! `if_command` | Only display the block if the supplied command returns 0 on startup. | None
+10//! `merge_with_next` | If true this will group the block with the next one, so rendering such as alternating_tint will apply to the whole group | `false`
+11//! `icons_format` | Overrides global `icons_format` | None
+12//! `error_format` | Overrides global `error_format` | None
+13//! `error_fullscreen_format` | Overrides global `error_fullscreen_format` | None
+14//! `error_interval` | How long to wait until restarting the block after an error occurred. | `5`
+15//! `[block.theme_overrides]` | Same as the top-level config option, but for this block only. Refer to `Themes and Icons` below. | None
+16//! `[block.icons_overrides]` | Same as the top-level config option, but for this block only. Refer to `Themes and Icons` below. | None
+17//! `[[block.click]]` | Set or override click action for the block. See below for details. | Block default / None
+18//!
+19//! Per block click configuration `[[block.click]]`:
+20//!
+21//! Key | Description | Default
+22//! ----|-------------|----------
+23//! `button` | `left`, `middle`, `right`, `up`/`wheel_up`, `down`/`wheel_down`, `wheel_left`, `wheel_right`, `forward`, `back` or [`double_left`](MouseButton). | -
+24//! `widget` | To which part of the block this entry applies (accepts regex) | `"block"`
+25//! `cmd` | Command to run when the mouse button event is detected. | None
+26//! `action` | Which block action to trigger | None
+27//! `sync` | Whether to wait for command to exit or not. | `false`
+28//! `update` | Whether to update the block on click. | `false`
+29
+30mod prelude;
+31
+32use futures::future::FutureExt as _;
+33use futures::stream::FuturesUnordered;
+34use serde::de::{self, Deserialize};
+35use tokio::sync::{Notify, mpsc};
+36
+37use std::borrow::Cow;
+38use std::sync::Arc;
+39use std::time::Duration;
+40
+41use crate::click::MouseButton;
+42use crate::errors::*;
+43use crate::geolocator::{Geolocator, IPAddressInfo};
+44use crate::widget::Widget;
+45use crate::{BoxedFuture, Request, RequestCmd};
+46
+47macro_rules! define_blocks {
+48    {
+49        $(
+50            $(#[cfg(feature = $feat: literal)])?
+51            $(#[deprecated($($dep_k: ident = $dep_v: literal),+)])?
+52            $block: ident $(,)?
+53        )*
+54    } => {
+55        $(
+56            $(#[cfg(feature = $feat)])?
+57            $(#[cfg_attr(docsrs, doc(cfg(feature = $feat)))])?
+58            $(#[deprecated($($dep_k = $dep_v),+)])?
+59            pub mod $block;
+60        )*
+61
+62        #[derive(Debug)]
+63        pub enum BlockConfig {
+64            $(
+65                $(#[cfg(feature = $feat)])?
+66                #[allow(non_camel_case_types)]
+67                #[allow(deprecated)]
+68                $block($block::Config),
+69            )*
+70            Err(&'static str, Error),
+71        }
+72
+73        impl BlockConfig {
+74            pub fn name(&self) -> &'static str {
+75                match self {
+76                    $(
+77                        $(#[cfg(feature = $feat)])?
+78                        Self::$block { .. } => stringify!($block),
+79                    )*
+80                    Self::Err(name, _err) => name,
+81                }
+82            }
+83
+84            pub fn spawn(self, api: CommonApi, futures: &mut FuturesUnordered<BoxedFuture<()>>) {
+85                match self {
+86                    $(
+87                        $(#[cfg(feature = $feat)])?
+88                        #[allow(deprecated)]
+89                        Self::$block(config) => futures.push(async move {
+90                            while let Err(err) = $block::run(&config, &api).await {
+91                                if api.set_error(err).is_err() {
+92                                    return;
+93                                }
+94                                tokio::select! {
+95                                    _ = tokio::time::sleep(api.error_interval) => (),
+96                                    _ = api.wait_for_update_request() => (),
+97                                }
+98                            }
+99                        }.boxed_local()),
+100                    )*
+101                    Self::Err(_name, err) => {
+102                        let _ = api.set_error(Error {
+103                            message: Some("Configuration error".into()),
+104                            cause: Some(Arc::new(err)),
+105                        });
+106                    },
+107                }
+108            }
+109        }
+110
+111        impl<'de> Deserialize<'de> for BlockConfig {
+112            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+113            where
+114                D: de::Deserializer<'de>,
+115            {
+116                use de::Error as _;
+117
+118                let mut table = toml::Table::deserialize(deserializer)?;
+119                let block_name = table.remove("block").ok_or_else(|| D::Error::missing_field("block"))?;
+120                let block_name = block_name.as_str().ok_or_else(|| D::Error::custom("block must be a string"))?;
+121
+122                match block_name {
+123                    $(
+124                        $(#[cfg(feature = $feat)])?
+125                        #[allow(deprecated)]
+126                        stringify!($block) => match $block::Config::deserialize(table) {
+127                            Ok(config) => Ok(BlockConfig::$block(config)),
+128                            Err(err) => Ok(BlockConfig::Err(stringify!($block), crate::errors::Error::new(err.to_string()))),
+129                        }
+130                        $(
+131                            #[cfg(not(feature = $feat))]
+132                            stringify!($block) => Err(D::Error::custom(format!(
+133                                "block {} is behind a feature gate '{}' which must be enabled at compile time",
+134                                stringify!($block),
+135                                $feat,
+136                            ))),
+137                        )?
+138                    )*
+139                    other => Err(D::Error::custom(format!("unknown block '{other}'")))
+140                }
+141            }
+142        }
+143    };
+144}
+145
+146define_blocks!(
+147    amd_gpu,
+148    backlight,
+149    battery,
+150    bluetooth,
+151    calendar,
+152    cpu,
+153    custom,
+154    custom_dbus,
+155    disk_iostats,
+156    disk_space,
+157    docker,
+158    external_ip,
+159    focused_window,
+160    github,
+161    hueshift,
+162    kdeconnect,
+163    load,
+164    #[cfg(feature = "maildir")]
+165    maildir,
+166    menu,
+167    memory,
+168    music,
+169    net,
+170    notify,
+171    #[cfg(feature = "notmuch")]
+172    notmuch,
+173    nvidia_gpu,
+174    packages,
+175    pomodoro,
+176    privacy,
+177    rofication,
+178    service_status,
+179    scratchpad,
+180    sound,
+181    speedtest,
+182    keyboard_layout,
+183    taskwarrior,
+184    temperature,
+185    time,
+186    tea_timer,
+187    toggle,
+188    uptime,
+189    vpn,
+190    watson,
+191    weather,
+192    xrandr,
+193);
+194
+195/// An error which originates from a block
+196#[derive(Debug, thiserror::Error)]
+197#[error("In block {}: {}", .block_name, .error)]
+198pub struct BlockError {
+199    pub block_id: usize,
+200    pub block_name: &'static str,
+201    pub error: Error,
+202}
+203
+204pub type BlockAction = Cow<'static, str>;
+205
+206#[derive(Clone)]
+207pub struct CommonApi {
+208    pub(crate) id: usize,
+209    pub(crate) update_request: Arc<Notify>,
+210    pub(crate) request_sender: mpsc::UnboundedSender<Request>,
+211    pub(crate) error_interval: Duration,
+212    pub(crate) geolocator: Arc<Geolocator>,
+213}
+214
+215impl CommonApi {
+216    /// Sends the widget to be displayed.
+217    pub fn set_widget(&self, widget: Widget) -> Result<()> {
+218        self.request_sender
+219            .send(Request {
+220                block_id: self.id,
+221                cmd: RequestCmd::SetWidget(widget),
+222            })
+223            .error("Failed to send Request")
+224    }
+225
+226    /// Hides the block. Send new widget to make it visible again.
+227    pub fn hide(&self) -> Result<()> {
+228        self.request_sender
+229            .send(Request {
+230                block_id: self.id,
+231                cmd: RequestCmd::UnsetWidget,
+232            })
+233            .error("Failed to send Request")
+234    }
+235
+236    /// Sends the error to be displayed.
+237    pub fn set_error(&self, error: Error) -> Result<()> {
+238        self.request_sender
+239            .send(Request {
+240                block_id: self.id,
+241                cmd: RequestCmd::SetError(error),
+242            })
+243            .error("Failed to send Request")
+244    }
+245
+246    pub fn set_default_actions(
+247        &self,
+248        actions: &'static [(MouseButton, Option<&'static str>, &'static str)],
+249    ) -> Result<()> {
+250        self.request_sender
+251            .send(Request {
+252                block_id: self.id,
+253                cmd: RequestCmd::SetDefaultActions(actions),
+254            })
+255            .error("Failed to send Request")
+256    }
+257
+258    pub fn get_actions(&self) -> Result<mpsc::UnboundedReceiver<BlockAction>> {
+259        let (tx, rx) = mpsc::unbounded_channel();
+260        self.request_sender
+261            .send(Request {
+262                block_id: self.id,
+263                cmd: RequestCmd::SubscribeToActions(tx),
+264            })
+265            .error("Failed to send Request")?;
+266        Ok(rx)
+267    }
+268
+269    pub async fn wait_for_update_request(&self) {
+270        self.update_request.notified().await;
+271    }
+272
+273    fn locator_name(&self) -> Cow<'static, str> {
+274        self.geolocator.name()
+275    }
+276
+277    /// No-op if last API call was made in the last `interval` seconds.
+278    pub async fn find_ip_location(
+279        &self,
+280        client: &reqwest::Client,
+281        interval: Duration,
+282    ) -> Result<IPAddressInfo> {
+283        self.geolocator.find_ip_location(client, interval).await
+284    }
+285}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/amd_gpu.rs.html b/src/i3status_rs/blocks/amd_gpu.rs.html new file mode 100644 index 0000000000..c61f68a8c3 --- /dev/null +++ b/src/i3status_rs/blocks/amd_gpu.rs.html @@ -0,0 +1,191 @@ +amd_gpu.rs - source

i3status_rs/blocks/
amd_gpu.rs

1//! Display the stats of your AMD GPU
+2//!
+3//! # Configuration
+4//!
+5//! Key | Values | Default
+6//! ----|--------|--------
+7//! `device` | The device in `/sys/class/drm/` to read from. | Any AMD card
+8//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $utilization "`
+9//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None`
+10//! `interval` | Update interval in seconds | `5`
+11//!
+12//! Placeholder          | Value                               | Type   | Unit
+13//! ---------------------|-------------------------------------|--------|------------
+14//! `icon`               | A static icon                       | Icon   | -
+15//! `utilization`        | GPU utilization                     | Number | %
+16//! `vram_total`         | Total VRAM                          | Number | Bytes
+17//! `vram_used`          | Used VRAM                           | Number | Bytes
+18//! `vram_used_percents` | Used VRAM / Total VRAM              | Number | %
+19//!
+20//! Action          | Description                               | Default button
+21//! ----------------|-------------------------------------------|---------------
+22//! `toggle_format` | Toggles between `format` and `format_alt` | Left
+23//!
+24//! # Example
+25//!
+26//! ```toml
+27//! [[block]]
+28//! block = "amd_gpu"
+29//! format = " $icon $utilization "
+30//! format_alt = " $icon MEM: $vram_used_percents ($vram_used/$vram_total) "
+31//! interval = 1
+32//! ```
+33//!
+34//! # Icons Used
+35//! - `gpu`
+36
+37use std::path::PathBuf;
+38use std::str::FromStr;
+39
+40use tokio::fs::read_dir;
+41
+42use super::prelude::*;
+43use crate::util::read_file;
+44
+45#[derive(Deserialize, Debug, SmartDefault)]
+46#[serde(deny_unknown_fields, default)]
+47pub struct Config {
+48    pub device: Option<String>,
+49    pub format: FormatConfig,
+50    pub format_alt: Option<FormatConfig>,
+51    #[default(5.into())]
+52    pub interval: Seconds,
+53}
+54
+55pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+56    let mut actions = api.get_actions()?;
+57    api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
+58
+59    let mut format = config.format.with_default(" $icon $utilization ")?;
+60    let mut format_alt = match &config.format_alt {
+61        Some(f) => Some(f.with_default("")?),
+62        None => None,
+63    };
+64
+65    let device = match &config.device {
+66        Some(name) => Device::new(name)?,
+67        None => Device::default_card()
+68            .await
+69            .error("failed to get default GPU")?
+70            .error("no GPU found")?,
+71    };
+72
+73    loop {
+74        let mut widget = Widget::new().with_format(format.clone());
+75
+76        let info = device.read_info().await?;
+77
+78        widget.set_values(map! {
+79            "icon" => Value::icon("gpu"),
+80            "utilization" => Value::percents(info.utilization_percents),
+81            "vram_total" => Value::bytes(info.vram_total_bytes),
+82            "vram_used" => Value::bytes(info.vram_used_bytes),
+83            "vram_used_percents" => Value::percents(info.vram_used_bytes / info.vram_total_bytes * 100.0),
+84        });
+85
+86        widget.state = match info.utilization_percents {
+87            x if x > 90.0 => State::Critical,
+88            x if x > 60.0 => State::Warning,
+89            x if x > 30.0 => State::Info,
+90            _ => State::Idle,
+91        };
+92
+93        api.set_widget(widget)?;
+94
+95        loop {
+96            select! {
+97                _ = sleep(config.interval.0) => break,
+98                _ = api.wait_for_update_request() => break,
+99                Some(action) = actions.recv() => match action.as_ref() {
+100                    "toggle_format" => {
+101                        if let Some(ref mut format_alt) = format_alt {
+102                            std::mem::swap(format_alt, &mut format);
+103                            break;
+104                        }
+105                    }
+106                    _ => (),
+107                }
+108            }
+109        }
+110    }
+111}
+112
+113pub struct Device {
+114    path: PathBuf,
+115}
+116
+117struct GpuInfo {
+118    utilization_percents: f64,
+119    vram_total_bytes: f64,
+120    vram_used_bytes: f64,
+121}
+122
+123impl Device {
+124    fn new(name: &str) -> Result<Self, Error> {
+125        let path = PathBuf::from(format!("/sys/class/drm/{name}/device"));
+126
+127        if !path.exists() {
+128            Err(Error::new(format!("Device {name} not found")))
+129        } else {
+130            Ok(Self { path })
+131        }
+132    }
+133
+134    async fn default_card() -> std::io::Result<Option<Self>> {
+135        let mut dir = read_dir("/sys/class/drm").await?;
+136
+137        while let Some(entry) = dir.next_entry().await? {
+138            let name = entry.file_name();
+139            let Some(name) = name.to_str() else { continue };
+140            if !name.starts_with("card") {
+141                continue;
+142            }
+143
+144            let mut path = entry.path();
+145            path.push("device");
+146
+147            if let Ok(uevent) = read_file(path.join("uevent")).await
+148                && uevent.contains("PCI_ID=1002")
+149            {
+150                return Ok(Some(Self { path }));
+151            }
+152        }
+153
+154        Ok(None)
+155    }
+156
+157    async fn read_prop<T: FromStr>(&self, prop: &str) -> Option<T> {
+158        read_file(self.path.join(prop))
+159            .await
+160            .ok()
+161            .and_then(|x| x.parse().ok())
+162    }
+163
+164    async fn read_info(&self) -> Result<GpuInfo> {
+165        Ok(GpuInfo {
+166            utilization_percents: self
+167                .read_prop::<f64>("gpu_busy_percent")
+168                .await
+169                .error("Failed to read gpu_busy_percent")?,
+170            vram_total_bytes: self
+171                .read_prop::<f64>("mem_info_vram_total")
+172                .await
+173                .error("Failed to read mem_info_vram_total")?,
+174            vram_used_bytes: self
+175                .read_prop::<f64>("mem_info_vram_used")
+176                .await
+177                .error("Failed to read mem_info_vram_used")?,
+178        })
+179    }
+180}
+181
+182#[cfg(test)]
+183mod tests {
+184    use super::*;
+185
+186    #[test]
+187    fn test_non_existing_gpu_device() {
+188        let device = Device::new("/nope");
+189        assert!(device.is_err());
+190    }
+191}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/backlight.rs.html b/src/i3status_rs/blocks/backlight.rs.html new file mode 100644 index 0000000000..4c75ece010 --- /dev/null +++ b/src/i3status_rs/blocks/backlight.rs.html @@ -0,0 +1,241 @@ +backlight.rs - source

i3status_rs/blocks/
backlight.rs

1//! The brightness of a backlight device
+2//!
+3//! This block reads brightness information directly from the filesystem, so it works under both
+4//! X11 and Wayland. The block uses `inotify` to listen for changes in the device's brightness
+5//! directly, so there is no need to set an update interval. This block uses DBus to set brightness
+6//! level using the mouse wheel, but will [fallback to sysfs](#d-bus-fallback) if `systemd-logind` is not used.
+7//!
+8//! # Root scaling
+9//!
+10//! Some devices expose raw values that are best handled with nonlinear scaling. The human perception of lightness is close to the cube root of relative luminance, so settings for `root_scaling` between 2.4 and 3.0 are worth trying. For devices with few discrete steps this should be 1.0 (linear). More information: <https://en.wikipedia.org/wiki/Lightness>
+11//!
+12//! # Configuration
+13//!
+14//! Key | Values | Default
+15//! ----|--------|--------
+16//! `device` | A regex to match against `/sys/class/backlight` devices to read brightness information from (can match 1 or more devices). When there is no `device` specified, this block will display information for all devices found in the `/sys/class/backlight` directory. | Default device
+17//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $brightness "`
+18//! `missing_format` | A string to customise the output of this block. No placeholders available | `" no backlight devices "`
+19//! `step_width` | The brightness increment to use when scrolling, in percent | `5`
+20//! `minimum` | The minimum brightness that can be scrolled down to | `5`
+21//! `maximum` | The maximum brightness that can be scrolled up to | `100`
+22//! `cycle` | The brightnesses to cycle through on each click | `[minimum, maximum]`
+23//! `root_scaling` | Scaling exponent reciprocal (ie. root) | `1.0`
+24//! `invert_icons` | Invert icons' ordering, useful if you have colorful emoji | `false`
+25//! `ddcci_sleep_multiplier` | [See ddcutil documentation](https://www.ddcutil.com/performance_options/#option-sleep-multiplier) | `1.0`
+26//! `ddcci_max_tries_write_read` | The maximum number of times to attempt writing to  or reading from a ddcci monitor | `10`
+27//!
+28//! Placeholder  | Value                                     | Type   | Unit
+29//! -------------|-------------------------------------------|--------|---------------
+30//! `icon`       | Icon based on backlight's state           | Icon   | -
+31//! `brightness` | Current brightness                        | Number | %
+32//!
+33//! Action            | Default button
+34//! ------------------|---------------
+35//! `cycle`           | Left
+36//! `brightness_up`   | Wheel Up
+37//! `brightness_down` | Wheel Down
+38//!
+39//! # Example
+40//!
+41//! ```toml
+42//! [[block]]
+43//! block = "backlight"
+44//! device = "intel_backlight"
+45//! ```
+46//!
+47//! Hide missing backlight:
+48//!
+49//! ```toml
+50//! [[block]]
+51//! block = "backlight"
+52//! missing_format = ""
+53//! ```
+54//!
+55//! # calibright
+56//!
+57//! Additional display brightness calibration can be set in `$XDG_CONFIG_HOME/calibright/config.toml`
+58//! See <https://github.com/bim9262/calibright> for more details.
+59//! This block will override any global config set in `$XDG_CONFIG_HOME/calibright/config.toml`
+60//!
+61//! # D-Bus Fallback
+62//!
+63//! If you don't use `systemd-logind` i3status-rust will attempt to set the brightness
+64//! using sysfs. In order to do this you'll need to have write permission.
+65//! You can do this by writing a `udev` rule for your system.
+66//!
+67//! First, check that your user is a member of the "video" group using the
+68//! `groups` command. Then add a rule in the `/etc/udev/rules.d/` directory
+69//! containing the following, for example in `backlight.rules`:
+70//!
+71//! ```text
+72//! ACTION=="add", SUBSYSTEM=="backlight", GROUP="video", MODE="0664"
+73//! ```
+74//!
+75//! This will allow the video group to modify all backlight devices. You will
+76//! also need to restart for this rule to take effect.
+77//!
+78//! # Icons Used
+79//! - `backlight` (as a progression)
+80
+81use std::sync::Arc;
+82
+83use calibright::{CalibrightBuilder, CalibrightConfig, CalibrightError, DeviceConfig};
+84
+85use super::prelude::*;
+86
+87#[derive(Deserialize, Debug, SmartDefault)]
+88#[serde(deny_unknown_fields, default)]
+89pub struct Config {
+90    pub device: Option<String>,
+91    pub format: FormatConfig,
+92    pub missing_format: FormatConfig,
+93    #[default(5.0)]
+94    pub step_width: f64,
+95    #[default(5.0)]
+96    pub minimum: f64,
+97    #[default(100.0)]
+98    pub maximum: f64,
+99    pub cycle: Option<Vec<f64>>,
+100    pub invert_icons: bool,
+101    //Calibright config settings
+102    pub root_scaling: Option<f64>,
+103    pub ddcci_sleep_multiplier: Option<f64>,
+104    pub ddcci_max_tries_write_read: Option<u8>,
+105}
+106
+107pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+108    let mut actions = api.get_actions()?;
+109    api.set_default_actions(&[
+110        (MouseButton::Left, None, "cycle"),
+111        (MouseButton::WheelUp, None, "brightness_up"),
+112        (MouseButton::WheelDown, None, "brightness_down"),
+113    ])?;
+114
+115    let format = config.format.with_default(" $icon $brightness ")?;
+116    let missing_format = config
+117        .missing_format
+118        .with_default(" no backlight devices ")?;
+119
+120    let default_cycle = &[config.minimum, config.maximum];
+121    let mut cycle = config
+122        .cycle
+123        .as_deref()
+124        .unwrap_or(default_cycle)
+125        .iter()
+126        .map(|x| x / 100.0)
+127        .cycle();
+128
+129    let step_width = config.step_width / 100.0;
+130    let minimum = config.minimum / 100.0;
+131    let maximum = config.maximum / 100.0;
+132
+133    let mut calibright_defaults = DeviceConfig::default();
+134
+135    if let Some(root_scaling) = config.root_scaling {
+136        calibright_defaults.root_scaling = root_scaling;
+137    }
+138
+139    if let Some(ddcci_sleep_multiplier) = config.ddcci_sleep_multiplier {
+140        calibright_defaults.ddcci_sleep_multiplier = ddcci_sleep_multiplier;
+141    }
+142
+143    if let Some(ddcci_max_tries_write_read) = config.ddcci_max_tries_write_read {
+144        calibright_defaults.ddcci_max_tries_write_read = ddcci_max_tries_write_read;
+145    }
+146
+147    let calibright_config = CalibrightConfig::new_with_defaults(&calibright_defaults)
+148        .await
+149        .error("calibright config error")?;
+150
+151    let mut calibright = CalibrightBuilder::new()
+152        .with_device_regex(config.device.as_deref().unwrap_or("."))
+153        .with_config(calibright_config)
+154        .with_poll_interval(api.error_interval)
+155        .build()
+156        .await
+157        .error("Failed to init calibright")?;
+158
+159    // This is used to display the error, if there is one
+160    let mut block_error: Option<CalibrightError> = None;
+161
+162    let mut brightness = calibright
+163        .get_brightness()
+164        .await
+165        .map_err(|e| block_error = Some(e))
+166        .unwrap_or_default();
+167
+168    loop {
+169        match block_error {
+170            Some(CalibrightError::NoDevices) => {
+171                let widget = Widget::new()
+172                    .with_format(missing_format.clone())
+173                    .with_state(State::Critical);
+174                api.set_widget(widget)?;
+175            }
+176            Some(e) => {
+177                api.set_error(Error {
+178                    message: None,
+179                    cause: Some(Arc::new(e)),
+180                })?;
+181            }
+182            None => {
+183                let mut widget = Widget::new().with_format(format.clone());
+184                let mut icon_value = brightness;
+185                if config.invert_icons {
+186                    icon_value = 1.0 - icon_value;
+187                }
+188                widget.set_values(map! {
+189                    "icon" => Value::icon_progression("backlight", icon_value),
+190                    "brightness" => Value::percents((brightness * 100.0).round())
+191                });
+192                api.set_widget(widget)?;
+193            }
+194        }
+195
+196        loop {
+197            select! {
+198                // Calibright can recover from errors, just keep reading the next event.
+199                _ = calibright.next() => {
+200                    block_error = calibright
+201                        .get_brightness()
+202                        .await
+203                        .map(|new_brightness| {brightness = new_brightness;})
+204                        .err();
+205
+206                    break;
+207                },
+208                Some(action) = actions.recv() => match action.as_ref() {
+209                    "cycle" => {
+210                        if let Some(cycle_brightness) = cycle.next() {
+211                            brightness = cycle_brightness;
+212                            block_error = calibright
+213                                .set_brightness(brightness)
+214                                .await
+215                                .err();
+216                            break;
+217
+218                        }
+219                    }
+220                    "brightness_up" => {
+221                        brightness = (brightness + step_width).clamp(minimum, maximum);
+222                        block_error = calibright
+223                            .set_brightness(brightness)
+224                            .await
+225                            .err();
+226                        break;
+227                    }
+228                    "brightness_down" => {
+229                        brightness = (brightness - step_width).clamp(minimum, maximum);
+230                        block_error = calibright
+231                            .set_brightness(brightness)
+232                            .await
+233                            .err();
+234                        break;
+235                    }
+236                    _ => (),
+237                }
+238            }
+239        }
+240    }
+241}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/battery.rs.html b/src/i3status_rs/blocks/battery.rs.html new file mode 100644 index 0000000000..dad444bdce --- /dev/null +++ b/src/i3status_rs/blocks/battery.rs.html @@ -0,0 +1,309 @@ +battery.rs - source

i3status_rs/blocks/
battery.rs

1//! Information about the internal power supply
+2//!
+3//! This block can display the current battery state (Full, Charging or Discharging), percentage
+4//! charged and estimate time until (dis)charged for an internal power supply.
+5//!
+6//! # Configuration
+7//!
+8//! Key | Values | Default
+9//! ----|--------|--------
+10//! `device` | sysfs/UPower: The device in `/sys/class/power_supply/` to read from (can also be "DisplayDevice" for UPower, which is a single logical power source representing all physical power sources. This is for example useful if your system has multiple batteries, in which case the DisplayDevice behaves as if you had a single larger battery.). apc_ups: IPv4Address:port or hostname:port | sysfs: the first battery device found in /sys/class/power_supply, with "BATx" or "CMBx" entries taking precedence. apc_ups: "localhost:3551". upower: `DisplayDevice`
+11//! `driver` | One of `"sysfs"`, `"apc_ups"`, or `"upower"` | `"sysfs"`
+12//! `model` | If present, the contents of `/sys/class/power_supply/.../model_name` must match this value. Typical use is to select by model name on devices that change their path. | N/A
+13//! `interval` | Update interval, in seconds. Only relevant for driver = "sysfs" or "apc_ups". | `10`
+14//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $percentage "`
+15//! `full_format` | Same as `format` but for when the battery is full | `" $icon "`
+16//! `charging_format` | Same as `format` but for when the battery is charging | Links to `format`
+17//! `empty_format` | Same as `format` but for when the battery is empty | `" $icon "`
+18//! `not_charging_format` | Same as `format` but for when the battery is not charging. Defaults to the full battery icon as many batteries report this status when they are full. | `" $icon "`
+19//! `missing_format` | Same as `format` if the battery cannot be found. | `" $icon "`
+20//! `info` | Minimum battery level, where state is set to info | `60`
+21//! `good` | Minimum battery level, where state is set to good | `60`
+22//! `warning` | Minimum battery level, where state is set to warning | `30`
+23//! `critical` | Minimum battery level, where state is set to critical | `15`
+24//! `full_threshold` | Percentage above which the battery is considered full (`full_format` shown) | `95`
+25//! `empty_threshold` | Percentage below which the battery is considered empty | `7.5`
+26//!
+27//! Placeholder  | Value                                                                   | Type              | Unit
+28//! -------------|-------------------------------------------------------------------------|-------------------|-----
+29//! `icon`       | Icon based on battery's state                                           | Icon   | -
+30//! `percentage` | Battery level, in percent                                               | Number | Percents
+31//! `time_remaining`  | Time remaining until (dis)charge is complete. Presented only if battery's status is (dis)charging. | Duration | -
+32//! `time`       | Time remaining until (dis)charge is complete. Presented only if battery's status is (dis)charging. | String *DEPRECATED* | -
+33//! `power`      | Power consumption by the battery or from the power supply when charging | String or Float   | Watts
+34//!
+35//! `time` has been deprecated in favor of `time_remaining`.
+36//!
+37//! # Examples
+38//!
+39//! Basic usage:
+40//!
+41//! ```toml
+42//! [[block]]
+43//! block = "battery"
+44//! format = " $icon $percentage "
+45//! ```
+46//!
+47//! ```toml
+48//! [[block]]
+49//! block = "battery"
+50//! format = " $percentage {$time_remaining.dur(hms:true, min_unit:m) |}"
+51//! device = "DisplayDevice"
+52//! driver = "upower"
+53//! ```
+54//!
+55//! Hide missing battery:
+56//!
+57//! ```toml
+58//! [[block]]
+59//! block = "battery"
+60//! missing_format = ""
+61//! ```
+62//!
+63//! # Icons Used
+64//! - `bat` (as a progression)
+65//! - `bat_charging` (as a progression)
+66//! - `bat_not_available`
+67
+68use regex::Regex;
+69use std::convert::Infallible;
+70use std::str::FromStr;
+71
+72use super::prelude::*;
+73
+74mod apc_ups;
+75mod sysfs;
+76mod upower;
+77
+78// make_log_macro!(debug, "battery");
+79
+80#[derive(Deserialize, Debug, SmartDefault)]
+81#[serde(deny_unknown_fields, default)]
+82pub struct Config {
+83    pub device: Option<String>,
+84    pub driver: BatteryDriver,
+85    pub model: Option<String>,
+86    #[default(10.into())]
+87    pub interval: Seconds,
+88    pub format: FormatConfig,
+89    pub full_format: FormatConfig,
+90    pub charging_format: FormatConfig,
+91    pub empty_format: FormatConfig,
+92    pub not_charging_format: FormatConfig,
+93    pub missing_format: FormatConfig,
+94    #[default(60.0)]
+95    pub info: f64,
+96    #[default(60.0)]
+97    pub good: f64,
+98    #[default(30.0)]
+99    pub warning: f64,
+100    #[default(15.0)]
+101    pub critical: f64,
+102    #[default(95.0)]
+103    pub full_threshold: f64,
+104    #[default(7.5)]
+105    pub empty_threshold: f64,
+106}
+107
+108#[derive(Deserialize, Debug, SmartDefault)]
+109#[serde(rename_all = "snake_case")]
+110pub enum BatteryDriver {
+111    #[default]
+112    Sysfs,
+113    ApcUps,
+114    Upower,
+115}
+116
+117pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+118    let format = config.format.with_default(" $icon $percentage ")?;
+119    let format_full = config.full_format.with_default(" $icon ")?;
+120    let charging_format = config.charging_format.with_default_format(&format);
+121    let format_empty = config.empty_format.with_default(" $icon ")?;
+122    let format_not_charging = config.not_charging_format.with_default(" $icon ")?;
+123    let missing_format = config.missing_format.with_default(" $icon ")?;
+124
+125    let dev_name = DeviceName::new(config.device.clone())?;
+126    let mut device: Box<dyn BatteryDevice + Send + Sync> = match config.driver {
+127        BatteryDriver::Sysfs => Box::new(sysfs::Device::new(
+128            dev_name,
+129            config.model.clone(),
+130            config.interval,
+131        )),
+132        BatteryDriver::ApcUps => Box::new(apc_ups::Device::new(dev_name, config.interval).await?),
+133        BatteryDriver::Upower => {
+134            Box::new(upower::Device::new(dev_name, config.model.clone()).await?)
+135        }
+136    };
+137
+138    loop {
+139        let mut info = device.get_info().await?;
+140
+141        if let Some(info) = &mut info {
+142            if info.capacity >= config.full_threshold {
+143                info.status = BatteryStatus::Full;
+144            } else if info.capacity <= config.empty_threshold
+145                && info.status != BatteryStatus::Charging
+146            {
+147                info.status = BatteryStatus::Empty;
+148            }
+149        }
+150
+151        match info {
+152            Some(info) => {
+153                let mut widget = Widget::new();
+154
+155                widget.set_format(match info.status {
+156                    BatteryStatus::Empty => format_empty.clone(),
+157                    BatteryStatus::Full => format_full.clone(),
+158                    BatteryStatus::Charging => charging_format.clone(),
+159                    BatteryStatus::NotCharging => format_not_charging.clone(),
+160                    _ => format.clone(),
+161                });
+162
+163                let mut values = map!(
+164                    "percentage" => Value::percents(info.capacity)
+165                );
+166
+167                info.power
+168                    .map(|p| values.insert("power".into(), Value::watts(p)));
+169                info.time_remaining.inspect(|&t| {
+170                    map! { @extend values
+171                        "time" => Value::text(
+172                            format!(
+173                                "{}:{:02}",
+174                                (t / 3600.) as i32,
+175                                (t % 3600. / 60.) as i32
+176                            ),
+177                        ),
+178                        "time_remaining" =>  Value::duration(
+179                            Duration::from_secs(t as u64),
+180                        ),
+181                    }
+182                });
+183
+184                let (icon_name, icon_value, state) = match (info.status, info.capacity) {
+185                    (BatteryStatus::Empty, _) => ("bat", 0.0, State::Critical),
+186                    (BatteryStatus::Full | BatteryStatus::NotCharging, _) => {
+187                        ("bat", 1.0, State::Idle)
+188                    }
+189                    (status, capacity) => (
+190                        if status == BatteryStatus::Charging {
+191                            "bat_charging"
+192                        } else {
+193                            "bat"
+194                        },
+195                        capacity / 100.0,
+196                        if status == BatteryStatus::Charging {
+197                            State::Good
+198                        } else if capacity <= config.critical {
+199                            State::Critical
+200                        } else if capacity <= config.warning {
+201                            State::Warning
+202                        } else if capacity <= config.info {
+203                            State::Info
+204                        } else if capacity > config.good {
+205                            State::Good
+206                        } else {
+207                            State::Idle
+208                        },
+209                    ),
+210                };
+211
+212                values.insert(
+213                    "icon".into(),
+214                    Value::icon_progression(icon_name, icon_value),
+215                );
+216
+217                widget.set_values(values);
+218                widget.state = state;
+219                api.set_widget(widget)?;
+220            }
+221            None => {
+222                let mut widget = Widget::new()
+223                    .with_format(missing_format.clone())
+224                    .with_state(State::Critical);
+225                widget.set_values(map!("icon" => Value::icon("bat_not_available")));
+226                api.set_widget(widget)?;
+227            }
+228        }
+229
+230        select! {
+231            update = device.wait_for_change() => update?,
+232            _ = api.wait_for_update_request() => (),
+233        }
+234    }
+235}
+236
+237#[async_trait]
+238trait BatteryDevice {
+239    async fn get_info(&mut self) -> Result<Option<BatteryInfo>>;
+240    async fn wait_for_change(&mut self) -> Result<()>;
+241}
+242
+243/// `Option<Regex>`, but more intuitive
+244#[derive(Debug)]
+245enum DeviceName {
+246    Any,
+247    Regex(Regex),
+248}
+249
+250impl DeviceName {
+251    fn new(pat: Option<String>) -> Result<Self> {
+252        Ok(match pat {
+253            None => Self::Any,
+254            Some(pat) => Self::Regex(pat.parse().error("failed to parse regex")?),
+255        })
+256    }
+257
+258    fn matches(&self, name: &str) -> bool {
+259        match self {
+260            Self::Any => true,
+261            Self::Regex(pat) => pat.is_match(name),
+262        }
+263    }
+264
+265    fn exact(&self) -> Option<&str> {
+266        match self {
+267            Self::Any => None,
+268            Self::Regex(pat) => Some(pat.as_str()),
+269        }
+270    }
+271}
+272
+273#[derive(Debug, Clone, Copy)]
+274struct BatteryInfo {
+275    /// Current status, e.g. "charging", "discharging", etc.
+276    status: BatteryStatus,
+277    /// The capacity in percents
+278    capacity: f64,
+279    /// Power consumption in watts
+280    power: Option<f64>,
+281    /// Time in seconds
+282    time_remaining: Option<f64>,
+283}
+284
+285#[derive(Copy, Clone, Debug, Eq, PartialEq, SmartDefault)]
+286enum BatteryStatus {
+287    Charging,
+288    Discharging,
+289    Empty,
+290    Full,
+291    NotCharging,
+292    #[default]
+293    Unknown,
+294}
+295
+296impl FromStr for BatteryStatus {
+297    type Err = Infallible;
+298
+299    fn from_str(s: &str) -> Result<Self, Self::Err> {
+300        Ok(match s {
+301            "Charging" => Self::Charging,
+302            "Discharging" => Self::Discharging,
+303            "Empty" => Self::Empty,
+304            "Full" => Self::Full,
+305            "Not charging" => Self::NotCharging,
+306            _ => Self::Unknown,
+307        })
+308    }
+309}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/battery/apc_ups.rs.html b/src/i3status_rs/blocks/battery/apc_ups.rs.html new file mode 100644 index 0000000000..b768d6c77f --- /dev/null +++ b/src/i3status_rs/blocks/battery/apc_ups.rs.html @@ -0,0 +1,161 @@ +apc_ups.rs - source

i3status_rs/blocks/battery/
apc_ups.rs

1use bytes::Bytes;
+2use futures::SinkExt as _;
+3
+4use serde::de;
+5use tokio::net::TcpStream;
+6use tokio::time::Interval;
+7use tokio_util::codec::{Framed, LengthDelimitedCodec};
+8
+9use super::{BatteryDevice, BatteryInfo, BatteryStatus, DeviceName};
+10use crate::blocks::prelude::*;
+11
+12make_log_macro!(debug, "battery[apc_ups]");
+13
+14#[derive(Debug, SmartDefault)]
+15enum Value {
+16    String(String),
+17    // The value is a percentage (0-100)
+18    Percent(f64),
+19    Watts(f64),
+20    Seconds(f64),
+21    #[default]
+22    None,
+23}
+24
+25impl<'de> Deserialize<'de> for Value {
+26    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+27    where
+28        D: de::Deserializer<'de>,
+29    {
+30        let s = String::deserialize(deserializer)?;
+31        for unit in ["Percent", "Watts", "Seconds", "Minutes", "Hours"] {
+32            if let Some(stripped) = s.strip_suffix(unit) {
+33                let value = stripped.trim().parse::<f64>().map_err(de::Error::custom)?;
+34                return Ok(match unit {
+35                    "Percent" => Value::Percent(value),
+36                    "Watts" => Value::Watts(value),
+37                    "Seconds" => Value::Seconds(value),
+38                    "Minutes" => Value::Seconds(value * 60.0),
+39                    "Hours" => Value::Seconds(value * 3600.0),
+40                    _ => unreachable!(),
+41                });
+42            }
+43        }
+44        Ok(Value::String(s))
+45    }
+46}
+47
+48#[derive(Debug, Deserialize, Default)]
+49#[serde(rename_all = "UPPERCASE", default)]
+50struct Properties {
+51    status: Value,
+52    bcharge: Value,
+53    nompower: Value,
+54    loadpct: Value,
+55    timeleft: Value,
+56}
+57
+58pub(super) struct Device {
+59    addr: String,
+60    interval: Interval,
+61}
+62
+63impl Device {
+64    pub(super) async fn new(dev_name: DeviceName, interval: Seconds) -> Result<Self> {
+65        let addr = dev_name.exact().unwrap_or("localhost:3551");
+66        Ok(Self {
+67            addr: addr.to_string(),
+68            interval: interval.timer(),
+69        })
+70    }
+71
+72    async fn get_status(&mut self) -> Result<Properties> {
+73        let mut conn = Framed::new(
+74            TcpStream::connect(&self.addr)
+75                .await
+76                .error("Failed to connect to socket")?,
+77            LengthDelimitedCodec::builder()
+78                .length_field_type::<u16>()
+79                .new_codec(),
+80        );
+81
+82        conn.send(Bytes::from_static(b"status"))
+83            .await
+84            .error("Could not send message to socket")?;
+85        conn.close().await.error("Could not close socket sink")?;
+86
+87        let mut map = serde_json::Map::new();
+88
+89        while let Some(frame) = conn.next().await {
+90            let frame = frame.error("Failed to read from socket")?;
+91            if frame.is_empty() {
+92                continue;
+93            }
+94            let line = std::str::from_utf8(&frame).error("Failed to convert to UTF-8")?;
+95            let Some((key, value)) = line.split_once(':') else {
+96                debug!("Invalid field format: {line:?}");
+97                continue;
+98            };
+99            map.insert(
+100                key.trim().to_uppercase(),
+101                serde_json::Value::String(value.trim().to_string()),
+102            );
+103        }
+104
+105        serde_json::from_value(serde_json::Value::Object(map)).error("Failed to deserialize")
+106    }
+107}
+108
+109#[async_trait]
+110impl BatteryDevice for Device {
+111    async fn get_info(&mut self) -> Result<Option<BatteryInfo>> {
+112        let status_data = self
+113            .get_status()
+114            .await
+115            .map_err(|e| {
+116                debug!("{e}");
+117                e
+118            })
+119            .unwrap_or_default();
+120
+121        let Value::String(status_str) = status_data.status else {
+122            return Ok(None);
+123        };
+124
+125        let status = match &*status_str {
+126            "ONBATT" => BatteryStatus::Discharging,
+127            "ONLINE" => BatteryStatus::Charging,
+128            _ => BatteryStatus::Unknown,
+129        };
+130
+131        // Even if the connection is valid, in the first few seconds
+132        // after apcupsd starts BCHARGE may not be present
+133        let Value::Percent(capacity) = status_data.bcharge else {
+134            return Ok(None);
+135        };
+136
+137        let power = match (status_data.nompower, status_data.loadpct) {
+138            (Value::Watts(nominal_power), Value::Percent(load_percent)) => {
+139                Some(nominal_power * load_percent / 100.0)
+140            }
+141            _ => None,
+142        };
+143
+144        let time_remaining = match status_data.timeleft {
+145            Value::Seconds(time_left) => Some(time_left),
+146            _ => None,
+147        };
+148
+149        Ok(Some(BatteryInfo {
+150            status,
+151            capacity,
+152            power,
+153            time_remaining,
+154        }))
+155    }
+156
+157    async fn wait_for_change(&mut self) -> Result<()> {
+158        self.interval.tick().await;
+159        Ok(())
+160    }
+161}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/battery/sysfs.rs.html b/src/i3status_rs/blocks/battery/sysfs.rs.html new file mode 100644 index 0000000000..9885434eaa --- /dev/null +++ b/src/i3status_rs/blocks/battery/sysfs.rs.html @@ -0,0 +1,281 @@ +sysfs.rs - source

i3status_rs/blocks/battery/
sysfs.rs

1use std::convert::Infallible;
+2use std::path::{Path, PathBuf};
+3use std::str::FromStr;
+4
+5use tokio::fs::read_dir;
+6use tokio::time::Interval;
+7
+8use super::{BatteryDevice, BatteryInfo, BatteryStatus, DeviceName};
+9use crate::blocks::prelude::*;
+10use crate::util::read_file;
+11
+12make_log_macro!(debug, "battery");
+13
+14/// Path for the power supply devices
+15const POWER_SUPPLY_DEVICES_PATH: &str = "/sys/class/power_supply";
+16
+17#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+18enum CapacityLevel {
+19    Full,
+20    High,
+21    Normal,
+22    Low,
+23    Critical,
+24    Unknown,
+25}
+26
+27impl FromStr for CapacityLevel {
+28    type Err = Infallible;
+29
+30    fn from_str(s: &str) -> Result<Self, Self::Err> {
+31        Ok(match s {
+32            "Full" => Self::Full,
+33            "High" => Self::High,
+34            "Normal" => Self::Normal,
+35            "Low" => Self::Low,
+36            "Critical" => Self::Critical,
+37            _ => Self::Unknown,
+38        })
+39    }
+40}
+41
+42impl CapacityLevel {
+43    fn percentage(self) -> Option<f64> {
+44        match self {
+45            CapacityLevel::Full => Some(100.0),
+46            CapacityLevel::High => Some(75.0),
+47            CapacityLevel::Normal => Some(50.0),
+48            CapacityLevel::Low => Some(25.0),
+49            CapacityLevel::Critical => Some(5.0),
+50            CapacityLevel::Unknown => None,
+51        }
+52    }
+53}
+54
+55/// Represents a physical power supply device, as known to sysfs.
+56/// <https://www.kernel.org/doc/html/v5.15/power/power_supply_class.html>
+57pub(super) struct Device {
+58    dev_name: DeviceName,
+59    dev_path: Option<PathBuf>,
+60    dev_model: Option<String>,
+61    interval: Interval,
+62}
+63
+64impl Device {
+65    pub(super) fn new(dev_name: DeviceName, dev_model: Option<String>, interval: Seconds) -> Self {
+66        Self {
+67            dev_name,
+68            dev_path: None,
+69            dev_model,
+70            interval: interval.timer(),
+71        }
+72    }
+73
+74    /// Returns `self.dev_path` if it is still available. Otherwise, find any device that matches
+75    /// `self.dev_name`.
+76    async fn get_device_path(&mut self) -> Result<Option<&Path>> {
+77        if let Some(path) = &self.dev_path
+78            && Self::device_available(path).await
+79        {
+80            debug!("battery '{}' is still available", path.display());
+81            return Ok(self.dev_path.as_deref());
+82        }
+83
+84        let mut matching_battery = None;
+85
+86        let mut sysfs_dir = read_dir(POWER_SUPPLY_DEVICES_PATH)
+87            .await
+88            .error("failed to read /sys/class/power_supply directory")?;
+89        while let Some(dir) = sysfs_dir
+90            .next_entry()
+91            .await
+92            .error("failed to read /sys/class/power_supply directory")?
+93        {
+94            let name = dir.file_name();
+95            let name = name.to_str().error("non UTF-8 battery path")?;
+96
+97            let path = dir.path();
+98
+99            if !self.dev_name.matches(name)
+100                || Self::read_prop::<String>(&path, "type").await.as_deref() != Some("Battery")
+101                || !Self::device_available(&path).await
+102            {
+103                continue;
+104            }
+105
+106            let model_name = Self::read_prop::<String>(&path, "model_name").await;
+107            debug!(
+108                "battery '{}', model={:?}",
+109                path.display(),
+110                model_name.as_deref()
+111            );
+112            if let Some(dev_model) = &self.dev_model
+113                && model_name.as_deref() != Some(dev_model.as_str())
+114            {
+115                debug!("Skipping based on model.");
+116                continue;
+117            }
+118
+119            debug!(
+120                "Found matching battery: '{}' matches {:?}",
+121                path.display(),
+122                self.dev_name
+123            );
+124
+125            // Better to default to the system battery, rather than possibly a keyboard or mouse battery.
+126            // System batteries usually start with BAT or CMB.
+127            if name.starts_with("BAT") || name.starts_with("CMB") {
+128                return Ok(Some(self.dev_path.insert(path)));
+129            } else {
+130                matching_battery = Some(path);
+131            }
+132        }
+133
+134        Ok(match matching_battery {
+135            Some(path) => Some(self.dev_path.insert(path)),
+136            None => {
+137                debug!("No batteries found");
+138                None
+139            }
+140        })
+141    }
+142
+143    async fn read_prop<T: FromStr + Send + Sync>(path: &Path, prop: &str) -> Option<T> {
+144        read_file(path.join(prop))
+145            .await
+146            .ok()
+147            .and_then(|x| x.parse().ok())
+148    }
+149
+150    async fn device_available(path: &Path) -> bool {
+151        // If `scope` is `Device`, then this is HID, in which case we don't have to check the
+152        // `present` property, because the existence of the device directory implies that the device
+153        // is available
+154        Self::read_prop::<String>(path, "scope").await.as_deref() == Some("Device")
+155            || Self::read_prop::<u8>(path, "present").await == Some(1)
+156    }
+157}
+158
+159#[async_trait]
+160impl BatteryDevice for Device {
+161    async fn get_info(&mut self) -> Result<Option<BatteryInfo>> {
+162        // Check if the battery is available
+163        let path = match self.get_device_path().await? {
+164            Some(path) => path,
+165            None => return Ok(None),
+166        };
+167
+168        // Read all the necessary data
+169        let (
+170            status,
+171            capacity_level,
+172            capacity,
+173            charge_now,
+174            charge_full,
+175            energy_now,
+176            energy_full,
+177            power_now,
+178            current_now,
+179            voltage_now,
+180            time_to_empty,
+181            time_to_full,
+182        ) = tokio::join!(
+183            Self::read_prop::<BatteryStatus>(path, "status"),
+184            Self::read_prop::<CapacityLevel>(path, "capacity_level"),
+185            Self::read_prop::<f64>(path, "capacity"),
+186            Self::read_prop::<f64>(path, "charge_now"), // uAh
+187            Self::read_prop::<f64>(path, "charge_full"), // uAh
+188            Self::read_prop::<f64>(path, "energy_now"), // uWh
+189            Self::read_prop::<f64>(path, "energy_full"), // uWh
+190            Self::read_prop::<f64>(path, "power_now"),  // uW
+191            Self::read_prop::<f64>(path, "current_now"), // uA
+192            Self::read_prop::<f64>(path, "voltage_now"), // uV
+193            Self::read_prop::<f64>(path, "time_to_empty"), // seconds
+194            Self::read_prop::<f64>(path, "time_to_full"), // seconds
+195        );
+196
+197        if !Self::device_available(path).await {
+198            // Device became unavailable while we were reading data from it. The simplest thing we
+199            // can do now is to pretend it wasn't available to begin with.
+200            debug!("battery suddenly unavailable");
+201            return Ok(None);
+202        }
+203
+204        debug!("status = {:?}", status);
+205        debug!("capacity_level = {:?}", capacity_level);
+206        debug!("capacity = {:?}", capacity);
+207        debug!("charge_now = {:?}", charge_now);
+208        debug!("charge_full = {:?}", charge_full);
+209        debug!("energy_now = {:?}", energy_now);
+210        debug!("energy_full = {:?}", energy_full);
+211        debug!("power_now = {:?}", power_now);
+212        debug!("current_now = {:?}", current_now);
+213        debug!("voltage_now = {:?}", voltage_now);
+214        debug!("time_to_empty = {:?}", time_to_empty);
+215        debug!("time_to_full = {:?}", time_to_full);
+216
+217        let charge_now = charge_now.map(|c| c * 1e-6); // uAh -> Ah
+218        let charge_full = charge_full.map(|c| c * 1e-6); // uAh -> Ah
+219        let energy_now = energy_now.map(|e| e * 1e-6); // uWh -> Wh
+220        let energy_full = energy_full.map(|e| e * 1e-6); // uWh -> Wh
+221        let power_now = power_now.map(|e| e * 1e-6); // uW -> W
+222        let current_now = current_now.map(|e| (e * 1e-6).abs()); // uA -> A
+223        let voltage_now = voltage_now.map(|e| e * 1e-6); // uV -> V
+224
+225        let status = status.unwrap_or_default();
+226
+227        // Prefer `charge_now/charge_full` and `energy_now/energy_full` because `capacity` is
+228        // calculated using `_full_design`, which is not practical (#1410, #1906).
+229        let calc_capacity = |now, full| Some(now? / full? * 100.0);
+230        let capacity = calc_capacity(charge_now, charge_full)
+231            .or_else(|| calc_capacity(energy_now, energy_full))
+232            .or(capacity)
+233            .or_else(|| capacity_level.and_then(CapacityLevel::percentage))
+234            .error("Failed to get capacity")?;
+235
+236        // A * V = W
+237        let power = power_now
+238            .or_else(|| current_now.zip(voltage_now).map(|(c, v)| c * v))
+239            .filter(|&p| p != 0.0);
+240
+241        // Ah * V = Wh
+242        // Wh / W = h
+243        let time_remaining = match status {
+244            BatteryStatus::Charging =>
+245            {
+246                #[allow(clippy::unnecessary_lazy_evaluations)]
+247                time_to_full.or_else(|| match (energy_now, energy_full, power) {
+248                    (Some(en), Some(ef), Some(p)) => Some((ef - en) / p * 3600.0),
+249                    _ => match (charge_now, charge_full, voltage_now, power) {
+250                        (Some(cn), Some(cf), Some(v), Some(p)) => Some((cf - cn) * v / p * 3600.0),
+251                        _ => None,
+252                    },
+253                })
+254            }
+255            BatteryStatus::Discharging =>
+256            {
+257                #[allow(clippy::unnecessary_lazy_evaluations)]
+258                time_to_empty.or_else(|| match (energy_now, power) {
+259                    (Some(en), Some(p)) => Some(en / p * 3600.0),
+260                    _ => match (charge_now, voltage_now, power) {
+261                        (Some(cn), Some(v), Some(p)) => Some(cn * v / p * 3600.0),
+262                        _ => None,
+263                    },
+264                })
+265            }
+266            _ => None,
+267        };
+268
+269        Ok(Some(BatteryInfo {
+270            status,
+271            capacity,
+272            power,
+273            time_remaining,
+274        }))
+275    }
+276
+277    async fn wait_for_change(&mut self) -> Result<()> {
+278        self.interval.tick().await;
+279        Ok(())
+280    }
+281}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/battery/upower.rs.html b/src/i3status_rs/blocks/battery/upower.rs.html new file mode 100644 index 0000000000..23d060b628 --- /dev/null +++ b/src/i3status_rs/blocks/battery/upower.rs.html @@ -0,0 +1,264 @@ +upower.rs - source

i3status_rs/blocks/battery/
upower.rs

1use tokio::try_join;
+2use zbus::fdo::{PropertiesChangedStream, PropertiesProxy};
+3use zbus::{Connection, zvariant};
+4use zvariant::ObjectPath;
+5
+6use super::{BatteryDevice, BatteryInfo, BatteryStatus, DeviceName};
+7use crate::blocks::prelude::*;
+8use crate::util::new_system_dbus_connection;
+9
+10const DISPLAY_DEVICE_PATH: ObjectPath =
+11    ObjectPath::from_static_str_unchecked("/org/freedesktop/UPower/devices/DisplayDevice");
+12
+13struct DeviceConnection {
+14    device_proxy: DeviceProxy<'static>,
+15    changes: PropertiesChangedStream,
+16}
+17
+18impl DeviceConnection {
+19    async fn new(
+20        dbus_conn: &Connection,
+21        device: &DeviceName,
+22        expected_model: Option<&str>,
+23    ) -> Result<Option<Self>> {
+24        let device_proxy =
+25            if device.exact().is_none_or(|d| d == "DisplayDevice") && expected_model.is_none() {
+26                DeviceProxy::builder(dbus_conn)
+27                    .path(DISPLAY_DEVICE_PATH)
+28                    .unwrap()
+29                    .build()
+30                    .await
+31                    .error("Failed to create DeviceProxy")?
+32            } else {
+33                let mut res = None;
+34                for path in UPowerProxy::new(dbus_conn)
+35                    .await
+36                    .error("Failed to create UPowerProxy")?
+37                    .enumerate_devices()
+38                    .await
+39                    .error("Failed to retrieve UPower devices")?
+40                {
+41                    let proxy = DeviceProxy::builder(dbus_conn)
+42                        .path(path)
+43                        .unwrap()
+44                        .build()
+45                        .await
+46                        .error("Failed to create DeviceProxy")?;
+47
+48                    // Filter by model if needed
+49                    if let Some(expected_model) = &expected_model
+50                        && let Ok(device_model) = proxy.model().await
+51                        && !expected_model.eq(&device_model)
+52                    {
+53                        continue;
+54                    }
+55                    // Verify device type
+56                    // https://upower.freedesktop.org/docs/Device.html#Device:Type
+57                    // consider any peripheral, UPS and internal battery
+58                    let device_type = proxy.type_().await.error("Failed to get device's type")?;
+59                    if device_type == 1 {
+60                        continue;
+61                    }
+62                    let name = proxy
+63                        .native_path()
+64                        .await
+65                        .error("Failed to get device's native path")?;
+66                    if device.matches(&name) {
+67                        res = Some(proxy);
+68                        break;
+69                    }
+70                }
+71                match res {
+72                    Some(res) => res,
+73                    None => return Ok(None),
+74                }
+75            };
+76
+77        let changes = PropertiesProxy::builder(dbus_conn)
+78            .destination("org.freedesktop.UPower")
+79            .unwrap()
+80            .path(device_proxy.inner().path().to_owned())
+81            .unwrap()
+82            .build()
+83            .await
+84            .error("Failed to create PropertiesProxy")?
+85            .receive_properties_changed()
+86            .await
+87            .error("Failed to create PropertiesChangedStream")?;
+88
+89        Ok(Some(DeviceConnection {
+90            device_proxy,
+91            changes,
+92        }))
+93    }
+94}
+95
+96pub(super) struct Device {
+97    dbus_conn: Connection,
+98    device: DeviceName,
+99    dev_model: Option<String>,
+100    device_conn: Option<DeviceConnection>,
+101    device_added_stream: DeviceAddedStream,
+102    device_removed_stream: DeviceRemovedStream,
+103}
+104
+105impl Device {
+106    pub(super) async fn new(device: DeviceName, dev_model: Option<String>) -> Result<Self> {
+107        let dbus_conn = new_system_dbus_connection().await?;
+108
+109        let device_conn = DeviceConnection::new(&dbus_conn, &device, dev_model.as_deref()).await?;
+110
+111        let upower_proxy = UPowerProxy::new(&dbus_conn)
+112            .await
+113            .error("Could not create UPowerProxy")?;
+114
+115        let (device_added_stream, device_removed_stream) = try_join! {
+116            upower_proxy.receive_device_added(),
+117            upower_proxy.receive_device_removed()
+118        }
+119        .error("Could not create signal stream")?;
+120
+121        Ok(Self {
+122            dbus_conn,
+123            device,
+124            dev_model,
+125            device_conn,
+126            device_added_stream,
+127            device_removed_stream,
+128        })
+129    }
+130}
+131
+132#[async_trait]
+133impl BatteryDevice for Device {
+134    async fn get_info(&mut self) -> Result<Option<BatteryInfo>> {
+135        match &self.device_conn {
+136            None => Ok(None),
+137            Some(device_conn) => {
+138                match try_join! {
+139                    device_conn.device_proxy.percentage(),
+140                    device_conn.device_proxy.energy_rate(),
+141                    device_conn.device_proxy.state(),
+142                    device_conn.device_proxy.time_to_full(),
+143                    device_conn.device_proxy.time_to_empty(),
+144                } {
+145                    Err(_) => Ok(None),
+146                    Ok((capacity, power, state, time_to_full, time_to_empty)) => {
+147                        let status = match state {
+148                            1 => BatteryStatus::Charging,
+149                            2 | 6 => BatteryStatus::Discharging,
+150                            3 => BatteryStatus::Empty,
+151                            4 => BatteryStatus::Full,
+152                            5 => BatteryStatus::NotCharging,
+153                            _ => BatteryStatus::Unknown,
+154                        };
+155
+156                        let time_remaining = match status {
+157                            BatteryStatus::Charging => Some(time_to_full as f64),
+158                            BatteryStatus::Discharging => Some(time_to_empty as f64),
+159                            _ => None,
+160                        };
+161
+162                        Ok(Some(BatteryInfo {
+163                            status,
+164                            capacity,
+165                            power: Some(power),
+166                            time_remaining,
+167                        }))
+168                    }
+169                }
+170            }
+171        }
+172    }
+173
+174    async fn wait_for_change(&mut self) -> Result<()> {
+175        match &mut self.device_conn {
+176            Some(device_conn) => loop {
+177                select! {
+178                    _ = self.device_added_stream.next() => {},
+179                    _ = device_conn.changes.next() => {
+180                        break;
+181                    },
+182                    Some(msg) = self.device_removed_stream.next() => {
+183                        let args = msg.args().unwrap();
+184                        if args.device().as_ref() == device_conn.device_proxy.inner().path().as_ref() {
+185                            self.device_conn = None;
+186                            break;
+187                        }
+188                    },
+189                }
+190            },
+191            None => loop {
+192                select! {
+193                    _ = self.device_removed_stream.next() => {},
+194                    _ = self.device_added_stream.next() => {
+195                        if let Some(device_conn) =
+196                        DeviceConnection::new(&self.dbus_conn, &self.device, self.dev_model.as_deref()).await?
+197                        {
+198                            self.device_conn = Some(device_conn);
+199                            break;
+200                        }
+201                    },
+202                }
+203            },
+204        }
+205
+206        Ok(())
+207    }
+208}
+209
+210#[zbus::proxy(
+211    interface = "org.freedesktop.UPower.Device",
+212    default_service = "org.freedesktop.UPower"
+213)]
+214trait Device {
+215    #[zbus(property)]
+216    fn energy_rate(&self) -> zbus::Result<f64>;
+217
+218    #[zbus(property)]
+219    fn is_present(&self) -> zbus::Result<bool>;
+220
+221    #[zbus(property)]
+222    fn native_path(&self) -> zbus::Result<String>;
+223
+224    #[zbus(property)]
+225    fn model(&self) -> zbus::Result<String>;
+226
+227    #[zbus(property)]
+228    fn online(&self) -> zbus::Result<bool>;
+229
+230    #[zbus(property)]
+231    fn percentage(&self) -> zbus::Result<f64>;
+232
+233    #[zbus(property)]
+234    fn state(&self) -> zbus::Result<u32>;
+235
+236    #[zbus(property)]
+237    fn time_to_empty(&self) -> zbus::Result<i64>;
+238
+239    #[zbus(property)]
+240    fn time_to_full(&self) -> zbus::Result<i64>;
+241
+242    #[zbus(property, name = "Type")]
+243    fn type_(&self) -> zbus::Result<u32>;
+244}
+245
+246#[zbus::proxy(
+247    interface = "org.freedesktop.UPower",
+248    default_service = "org.freedesktop.UPower",
+249    default_path = "/org/freedesktop/UPower"
+250)]
+251trait UPower {
+252    fn enumerate_devices(&self) -> zbus::Result<Vec<zvariant::OwnedObjectPath>>;
+253
+254    fn get_display_device(&self) -> zbus::Result<zvariant::OwnedObjectPath>;
+255
+256    #[zbus(signal)]
+257    fn device_added(&self, device: zvariant::OwnedObjectPath) -> zbus::Result<()>;
+258
+259    #[zbus(signal)]
+260    fn device_removed(&self, device: zvariant::OwnedObjectPath) -> zbus::Result<()>;
+261
+262    #[zbus(property)]
+263    fn on_battery(&self) -> zbus::Result<bool>;
+264}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/bluetooth.rs.html b/src/i3status_rs/blocks/bluetooth.rs.html new file mode 100644 index 0000000000..aca39273bb --- /dev/null +++ b/src/i3status_rs/blocks/bluetooth.rs.html @@ -0,0 +1,436 @@ +bluetooth.rs - source

i3status_rs/blocks/
bluetooth.rs

1//! Monitor Bluetooth device
+2//!
+3//! This block displays the connectivity of a given Bluetooth device and the battery level if this
+4//! is supported. Relies on the Bluez D-Bus API.
+5//!
+6//! When the device can be identified as an audio headset, a keyboard, joystick, or mouse, use the
+7//! relevant icon. Otherwise, fall back on the generic Bluetooth symbol.
+8//!
+9//! Right-clicking the block will attempt to connect (or disconnect) the device.
+10//!
+11//! Note: battery level information is not reported for some devices. [Enabling experimental
+12//! features of `bluez`](https://wiki.archlinux.org/title/bluetooth#Enabling_experimental_features)
+13//! may fix it.
+14//!
+15//! # Configuration
+16//!
+17//! Key | Values | Default
+18//! ----|--------|--------
+19//! `mac` | MAC address of the Bluetooth device | **Required**
+20//! `adapter_mac` | MAC Address of the Bluetooth adapter (in case your device was connected to multiple currently available adapters) | `None`
+21//! `format` | A string to customise the output of this block. See below for available placeholders. | <code>\" $icon $name{ $percentage\|} \"</code>
+22//! `disconnected_format` | A string to customise the output of this block. See below for available placeholders. | <code>\" $icon{ $name\|} \"</code>
+23//! `battery_state` | A mapping from battery percentage to block's [state](State) (color). See example below. | 0..15 -> critical, 16..30 -> warning, 31..60 -> info, 61..100 -> good
+24//!
+25//! Placeholder    | Value                                                                 | Type   | Unit
+26//! ---------------|-----------------------------------------------------------------------|--------|------
+27//! `icon`         | Icon based on what type of device is connected                        | Icon   | -
+28//! `name`         | Device's name                                                         | Text   | -
+29//! `percentage`   | Device's battery level (may be absent if the device is not supported) | Number | %
+30//! `battery_icon` | Battery icon (may be absent if the device is not supported)           | Icon   | -
+31//! `available`    | Present if the device is available                                    | Flag   | -
+32//!
+33//! Action   | Default button
+34//! ---------|---------------
+35//! `toggle` | Right
+36//!
+37//! # Examples
+38//!
+39//! This example just shows the icon when device is connected.
+40//!
+41//! ```toml
+42//! [[block]]
+43//! block = "bluetooth"
+44//! mac = "00:18:09:92:1B:BA"
+45//! disconnected_format = ""
+46//! format = " $icon "
+47//! [block.battery_state]
+48//! "0..20" = "critical"
+49//! "21..70" = "warning"
+50//! "71..100" = "good"
+51//! ```
+52//!
+53//! # Icons Used
+54//! - `headphones` for bluetooth devices identifying as "audio-card", "audio-headset" or "audio-headphones"
+55//! - `joystick` for bluetooth devices identifying as "input-gaming"
+56//! - `keyboard` for bluetooth devices identifying as "input-keyboard"
+57//! - `mouse` for bluetooth devices identifying as "input-mouse"
+58//! - `bluetooth` for all other devices
+59
+60use zbus::fdo::{DBusProxy, ObjectManagerProxy, PropertiesProxy};
+61
+62use super::prelude::*;
+63use crate::wrappers::RangeMap;
+64
+65make_log_macro!(debug, "bluetooth");
+66
+67#[derive(Deserialize, Debug)]
+68#[serde(deny_unknown_fields)]
+69pub struct Config {
+70    pub mac: String,
+71    #[serde(default)]
+72    pub adapter_mac: Option<String>,
+73    #[serde(default)]
+74    pub format: FormatConfig,
+75    #[serde(default)]
+76    pub disconnected_format: FormatConfig,
+77    #[serde(default)]
+78    pub battery_state: Option<RangeMap<u8, State>>,
+79}
+80
+81pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+82    let mut actions = api.get_actions()?;
+83    api.set_default_actions(&[(MouseButton::Right, None, "toggle")])?;
+84
+85    let format = config.format.with_default(" $icon $name{ $percentage|} ")?;
+86    let disconnected_format = config
+87        .disconnected_format
+88        .with_default(" $icon{ $name|} ")?;
+89
+90    let mut monitor = DeviceMonitor::new(config.mac.clone(), config.adapter_mac.clone()).await?;
+91
+92    let battery_states = config.battery_state.clone().unwrap_or_else(|| {
+93        vec![
+94            (0..=15, State::Critical),
+95            (16..=30, State::Warning),
+96            (31..=60, State::Info),
+97            (61..=100, State::Good),
+98        ]
+99        .into()
+100    });
+101
+102    loop {
+103        match monitor.get_device_info().await {
+104            // Available
+105            Some(device) => {
+106                debug!("Device available, info: {device:?}");
+107
+108                let mut widget = Widget::new();
+109
+110                let values = map! {
+111                    "icon" => Value::icon(device.icon),
+112                    "name" => Value::text(device.name),
+113                    "available" => Value::flag(),
+114                    [if let Some(p) = device.battery_percentage] "percentage" => Value::percents(p),
+115                    [if let Some(p) = device.battery_percentage]
+116                        "battery_icon" => Value::icon_progression("bat", p as f64 / 100.0),
+117                };
+118
+119                if device.connected {
+120                    widget.set_format(format.clone());
+121                    widget.state = battery_states
+122                        .get(&device.battery_percentage.unwrap_or(100))
+123                        .copied()
+124                        .unwrap_or(State::Good);
+125                } else {
+126                    widget.set_format(disconnected_format.clone());
+127                    widget.state = State::Idle;
+128                }
+129
+130                widget.set_values(values);
+131                api.set_widget(widget)?;
+132            }
+133            // Unavailable
+134            None => {
+135                debug!("Showing device as unavailable");
+136                let mut widget = Widget::new().with_format(disconnected_format.clone());
+137                widget.set_values(map!("icon" => Value::icon("bluetooth")));
+138                api.set_widget(widget)?;
+139            }
+140        }
+141
+142        loop {
+143            select! {
+144                res = monitor.wait_for_change() => {
+145                    res?;
+146                    break;
+147                },
+148                Some(action) = actions.recv() => match action.as_ref() {
+149                    "toggle" => {
+150                        if let Some(dev) = &monitor.device
+151                            && let Ok(connected) = dev.device.connected().await {
+152                                if connected {
+153                                    let _ = dev.device.disconnect().await;
+154                                } else {
+155                                    let _ = dev.device.connect().await;
+156                                }
+157                                break;
+158                            }
+159                    }
+160                    _ => (),
+161                }
+162            }
+163        }
+164    }
+165}
+166
+167struct DeviceMonitor {
+168    mac: String,
+169    adapter_mac: Option<String>,
+170    manager_proxy: ObjectManagerProxy<'static>,
+171    device: Option<Device>,
+172}
+173
+174struct Device {
+175    props: PropertiesProxy<'static>,
+176    device: Device1Proxy<'static>,
+177    battery: Battery1Proxy<'static>,
+178}
+179
+180#[derive(Debug)]
+181struct DeviceInfo {
+182    connected: bool,
+183    icon: &'static str,
+184    name: String,
+185    battery_percentage: Option<u8>,
+186}
+187
+188impl DeviceMonitor {
+189    async fn new(mac: String, adapter_mac: Option<String>) -> Result<Self> {
+190        let dbus_conn = new_system_dbus_connection().await?;
+191        let manager_proxy = ObjectManagerProxy::builder(&dbus_conn)
+192            .destination("org.bluez")
+193            .and_then(|x| x.path("/"))
+194            .unwrap()
+195            .build()
+196            .await
+197            .error("Failed to create ObjectManagerProxy")?;
+198        let device = Device::try_find(&manager_proxy, &mac, adapter_mac.as_deref()).await?;
+199        Ok(Self {
+200            mac,
+201            adapter_mac,
+202            manager_proxy,
+203            device,
+204        })
+205    }
+206
+207    async fn wait_for_change(&mut self) -> Result<()> {
+208        match &mut self.device {
+209            None => {
+210                let mut interface_added = self
+211                    .manager_proxy
+212                    .receive_interfaces_added()
+213                    .await
+214                    .error("Failed to monitor interfaces")?;
+215                loop {
+216                    interface_added
+217                        .next()
+218                        .await
+219                        .error("Stream ended unexpectedly")?;
+220                    if let Some(device) = Device::try_find(
+221                        &self.manager_proxy,
+222                        &self.mac,
+223                        self.adapter_mac.as_deref(),
+224                    )
+225                    .await?
+226                    {
+227                        self.device = Some(device);
+228                        debug!("Device has been added");
+229                        return Ok(());
+230                    }
+231                }
+232            }
+233            Some(device) => {
+234                let mut updates = device
+235                    .props
+236                    .receive_properties_changed()
+237                    .await
+238                    .error("Failed to receive updates")?;
+239
+240                let mut interface_added = self
+241                    .manager_proxy
+242                    .receive_interfaces_added()
+243                    .await
+244                    .error("Failed to monitor interfaces")?;
+245
+246                let mut interface_removed = self
+247                    .manager_proxy
+248                    .receive_interfaces_removed()
+249                    .await
+250                    .error("Failed to monitor interfaces")?;
+251
+252                let mut bluez_owner_changed =
+253                    DBusProxy::new(self.manager_proxy.inner().connection())
+254                        .await
+255                        .error("Failed to create DBusProxy")?
+256                        .receive_name_owner_changed_with_args(&[(0, "org.bluez")])
+257                        .await
+258                        .unwrap();
+259
+260                loop {
+261                    select! {
+262                        _ = updates.next_debounced() => {
+263                            debug!("Got update for device");
+264                            return Ok(());
+265                        }
+266                        Some(event) = interface_added.next() => {
+267                            let args = event.args().error("Failed to get the args")?;
+268                            if args.object_path() == device.device.inner().path() {
+269                                debug!("Interfaces added: {:?}", args.interfaces_and_properties().keys());
+270                                return Ok(());
+271                            }
+272                        }
+273                        Some(event) = interface_removed.next() => {
+274                            let args = event.args().error("Failed to get the args")?;
+275                            if args.object_path() == device.device.inner().path() {
+276                                self.device = None;
+277                                debug!("Device is no longer available");
+278                                return Ok(());
+279                            }
+280                        }
+281                        Some(event) = bluez_owner_changed.next() => {
+282                            let args = event.args().error("Failed to get the args")?;
+283                            if args.new_owner.is_none() {
+284                                self.device = None;
+285                                debug!("org.bluez disappeared");
+286                                return Ok(());
+287                            }
+288                        }
+289                    }
+290                }
+291            }
+292        }
+293    }
+294
+295    async fn get_device_info(&mut self) -> Option<DeviceInfo> {
+296        let device = self.device.as_ref()?;
+297
+298        let Ok((connected, name)) =
+299            tokio::try_join!(device.device.connected(), device.device.name(),)
+300        else {
+301            debug!("failed to fetch device info, assuming device or bluez disappeared");
+302            self.device = None;
+303            return None;
+304        };
+305
+306        //icon can be null, so ignore errors when fetching it
+307        let icon: &str = match device.device.icon().await.ok().as_deref() {
+308            Some("audio-card" | "audio-headset" | "audio-headphones") => "headphones",
+309            Some("input-gaming") => "joystick",
+310            Some("input-keyboard") => "keyboard",
+311            Some("input-mouse") => "mouse",
+312            _ => "bluetooth",
+313        };
+314
+315        Some(DeviceInfo {
+316            connected,
+317            icon,
+318            name,
+319            battery_percentage: device.battery.percentage().await.ok(),
+320        })
+321    }
+322}
+323
+324impl Device {
+325    async fn try_find(
+326        manager_proxy: &ObjectManagerProxy<'_>,
+327        mac: &str,
+328        adapter_mac: Option<&str>,
+329    ) -> Result<Option<Self>> {
+330        let Ok(devices) = manager_proxy.get_managed_objects().await else {
+331            debug!("could not get the list of managed objects");
+332            return Ok(None);
+333        };
+334
+335        debug!("all managed devices: {:?}", devices);
+336
+337        let root_object: Option<String> = match adapter_mac {
+338            Some(adapter_mac) => {
+339                let mut adapter_path = None;
+340                for (path, interfaces) in &devices {
+341                    let adapter_interface = match interfaces.get("org.bluez.Adapter1") {
+342                        Some(i) => i,
+343                        None => continue, // Not an adapter
+344                    };
+345                    let addr: &str = adapter_interface
+346                        .get("Address")
+347                        .and_then(|a| a.downcast_ref().ok())
+348                        .unwrap();
+349                    if addr == adapter_mac {
+350                        adapter_path = Some(path);
+351                        break;
+352                    }
+353                }
+354                match adapter_path {
+355                    Some(path) => Some(format!("{}/", path.as_str())),
+356                    None => return Ok(None),
+357                }
+358            }
+359            None => None,
+360        };
+361
+362        debug!("root object: {:?}", root_object);
+363
+364        for (path, interfaces) in devices {
+365            if let Some(root) = &root_object
+366                && !path.starts_with(root)
+367            {
+368                continue;
+369            }
+370
+371            let Some(device_interface) = interfaces.get("org.bluez.Device1") else {
+372                // Not a device
+373                continue;
+374            };
+375
+376            let addr: &str = device_interface
+377                .get("Address")
+378                .and_then(|a| a.downcast_ref().ok())
+379                .unwrap();
+380            if addr != mac {
+381                continue;
+382            }
+383
+384            debug!("Found device with path {:?}", path);
+385
+386            return Ok(Some(Self {
+387                props: PropertiesProxy::builder(manager_proxy.inner().connection())
+388                    .destination("org.bluez")
+389                    .and_then(|x| x.path(path.clone()))
+390                    .unwrap()
+391                    .build()
+392                    .await
+393                    .error("Failed to create PropertiesProxy")?,
+394                device: Device1Proxy::builder(manager_proxy.inner().connection())
+395                    // No caching because https://github.com/greshake/i3status-rust/issues/1565#issuecomment-1379308681
+396                    .cache_properties(zbus::proxy::CacheProperties::No)
+397                    .path(path.clone())
+398                    .unwrap()
+399                    .build()
+400                    .await
+401                    .error("Failed to create Device1Proxy")?,
+402                battery: Battery1Proxy::builder(manager_proxy.inner().connection())
+403                    .cache_properties(zbus::proxy::CacheProperties::No)
+404                    .path(path)
+405                    .unwrap()
+406                    .build()
+407                    .await
+408                    .error("Failed to create Battery1Proxy")?,
+409            }));
+410        }
+411
+412        debug!("No device found");
+413        Ok(None)
+414    }
+415}
+416
+417#[zbus::proxy(interface = "org.bluez.Device1", default_service = "org.bluez")]
+418trait Device1 {
+419    fn connect(&self) -> zbus::Result<()>;
+420    fn disconnect(&self) -> zbus::Result<()>;
+421
+422    #[zbus(property)]
+423    fn connected(&self) -> zbus::Result<bool>;
+424
+425    #[zbus(property)]
+426    fn name(&self) -> zbus::Result<String>;
+427
+428    #[zbus(property)]
+429    fn icon(&self) -> zbus::Result<String>;
+430}
+431
+432#[zbus::proxy(interface = "org.bluez.Battery1", default_service = "org.bluez")]
+433trait Battery1 {
+434    #[zbus(property)]
+435    fn percentage(&self) -> zbus::Result<u8>;
+436}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/calendar.rs.html b/src/i3status_rs/blocks/calendar.rs.html new file mode 100644 index 0000000000..a6cee62144 --- /dev/null +++ b/src/i3status_rs/blocks/calendar.rs.html @@ -0,0 +1,610 @@ +calendar.rs - source

i3status_rs/blocks/
calendar.rs

1//! Calendar
+2//!
+3//! This block displays upcoming calendar events retrieved from a CalDav ICalendar server.
+4//!
+5//! # Configuration
+6//!
+7//! Key | Values | Default
+8//! ----|--------|--------
+9//! `next_event_format` | A string to customize the output of this block when there is a next event in the calendar. See below for available placeholders. | <code>\" $icon $start.datetime(f:'%a %H:%M') $summary \"</code>
+10//! `ongoing_event_format` | A string to customize the output of this block when an event is ongoing. | <code>\" $icon $summary (ends at $end.datetime(f:'%H:%M')) \"</code>
+11//! `no_events_format` | A string to customize the output of this block when there are no events | <code>\" $icon \"</code>
+12//! `redirect_format` | A string to customize the output of this block when the authorization is asked | <code>\" $icon Check your web browser \"</code>
+13//! `fetch_interval` | Fetch events interval in seconds | `60`
+14//! `alternate_events_interval` | Alternate overlapping events interval in seconds | `10`
+15//! `events_within_hours` | Number of hours to look for events in the future | `48`
+16//! `source` | Array of sources to pull calendars from | `[]`
+17//! `warning_threshold` | Warning threshold in seconds for the upcoming event | `300`
+18//! `browser_cmd` | Command to open event details in a browser. The block passes the HTML link as an argument | `"xdg-open"`
+19//!
+20//! # Source Configuration
+21//!
+22//! Key | Values | Default
+23//! ----|--------|--------
+24//! `url` | CalDav calendar server URL | N/A
+25//! `auth` | Authentication configuration (unauthenticated, basic, or oauth2) | `unauthenticated`
+26//! `calendars` | List of calendar names to monitor. If empty, all calendars will be fetched. | `[]`
+27//!
+28//! Note: Currently only one source is supported
+29//!
+30//! Action          | Description                               | Default button
+31//! ----------------|-------------------------------------------|---------------
+32//! `open_link` | Opens the HTML link of the event | Left
+33//!
+34//! # Examples
+35//!
+36//! ## Unauthenticated
+37//!
+38//! ```toml
+39//! [[block]]
+40//! block = "calendar"
+41//! next_event_format = " $icon $start.datetime(f:'%a %H:%M') $summary "
+42//! ongoing_event_format = " $icon $summary (ends at $end.datetime(f:'%H:%M')) "
+43//! no_events_format = " $icon no events "
+44//! fetch_interval = 30
+45//! alternate_events_interval = 10
+46//! events_within_hours = 48
+47//! warning_threshold = 600
+48//! browser_cmd = "firefox"
+49//! [[block.source]]
+50//! url = "https://caldav.example.com/calendar/"
+51//! calendars = ["user/calendar"]
+52//! [block.source.auth]
+53//! type = "unauthenticated"
+54//! ```
+55//!
+56//! ## Basic Authentication
+57//!
+58//! ```toml
+59//! [[block]]
+60//! block = "calendar"
+61//! next_event_format = " $icon $start.datetime(f:'%a %H:%M') $summary "
+62//! ongoing_event_format = " $icon $summary (ends at $end.datetime(f:'%H:%M')) "
+63//! no_events_format = " $icon no events "
+64//! fetch_interval = 30
+65//! alternate_events_interval = 10
+66//! events_within_hours = 48
+67//! warning_threshold = 600
+68//! browser_cmd = "firefox"
+69//! [[block.source]]
+70//! url = "https://caldav.example.com/calendar/"
+71//! calendars = [ "Holidays" ]
+72//! [block.source.auth]
+73//! type = "basic"
+74//! username = "your_username"
+75//! password = "your_password"
+76//! ```
+77//!
+78//! Note: You can also configure the `username` and `password` in a separate TOML file.
+79//!
+80//! `~/.config/i3status-rust/example_credentials.toml`
+81//! ```toml
+82//! username = "my-username"
+83//! password = "my-password"
+84//! ```
+85//!
+86//! Source auth configuration with `credentials_path`:
+87//!
+88//! ```toml
+89//! [block.source.auth]
+90//! type = "basic"
+91//! credentials_path = "~/.config/i3status-rust/example_credentials.toml"
+92//! ```
+93//!
+94//! ## OAuth2 Authentication (Google Calendar)
+95//!
+96//! To access the CalDav API of Google, follow these steps to enable the API and obtain the `client_id` and `client_secret`:
+97//! 1. **Go to the Google Cloud Console**: Navigate to the [Google Cloud Console](https://console.cloud.google.com/).
+98//! 2. **Create a New Project**: If you don't already have a project, click on the project dropdown and select "New Project". Give your project a name and click "Create".
+99//! 3. **Enable the CalDAV API**: In the project dashboard, go to the "APIs & Services" > "Library". Search for "CalDAV API" and click on it, then click "Enable".
+100//! 4. **Set Up OAuth Consent Screen**: Go to "APIs & Services" > "OAuth consent screen". Fill out the required information and save.
+101//! 5. **Create Credentials**:
+102//!    - Navigate to "APIs & Services" > "Credentials".
+103//!    - Click "Create Credentials" and select "OAuth 2.0 Client IDs".
+104//!    - Configure the consent screen if you haven't already.
+105//!    - Set the application type to "Web application".
+106//!    - Add your authorized redirect URIs. For example, `http://localhost:8080`.
+107//!    - Click "Create" and note down the `client_id` and `client_secret`.
+108//! 6. **Download the Credentials**: Click on the download icon next to your OAuth 2.0 Client ID to download the JSON file containing your client ID and client secret. Use these values in your configuration.
+109//!
+110//! ```toml
+111//! [[block]]
+112//! block = "calendar"
+113//! next_event_format = " $icon $start.datetime(f:'%a %H:%M') $summary "
+114//! ongoing_event_format = " $icon $summary (ends at $end.datetime(f:'%H:%M')) "
+115//! no_events_format = " $icon no events "
+116//! fetch_interval = 30
+117//! alternate_events_interval = 10
+118//! events_within_hours = 48
+119//! warning_threshold = 600
+120//! browser_cmd = "firefox"
+121//! [[block.source]]
+122//! url = "https://apidata.googleusercontent.com/caldav/v2/"
+123//! calendars = ["primary"]
+124//! [block.source.auth]
+125//! type = "oauth2"
+126//! client_id = "your_client_id"
+127//! client_secret = "your_client_secret"
+128//! auth_url = "https://accounts.google.com/o/oauth2/auth"
+129//! token_url = "https://oauth2.googleapis.com/token"
+130//! auth_token = "~/.config/i3status-rust/calendar.auth_token"
+131//! redirect_port = 8080
+132//! scopes = ["https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/calendar.events"]
+133//! ```
+134//!
+135//! Note: You can also configure the `client_id` and `client_secret` in a separate TOML file.
+136//!
+137//! `~/.config/i3status-rust/google_credentials.toml`
+138//! ```toml
+139//! client_id = "my-client_id"
+140//! client_secret = "my-client_secret"
+141//! ```
+142//!
+143//! Source auth configuration with `credentials_path`:
+144//!
+145//! ```toml
+146//! [block.source.auth]
+147//! type = "oauth2"
+148//! credentials_path = "~/.config/i3status-rust/google_credentials.toml"
+149//! auth_url = "https://accounts.google.com/o/oauth2/auth"
+150//! token_url = "https://oauth2.googleapis.com/token"
+151//! auth_token = "~/.config/i3status-rust/calendar.auth_token"
+152//! redirect_port = 8080
+153//! scopes = ["https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/calendar.events"]
+154//! ```
+155//!
+156//! # Format Configuration
+157//!
+158//! The format configuration is a string that can include placeholders to be replaced with dynamic content.
+159//! Placeholders can be:
+160//! - `$summary`: Summary of the event
+161//! - `$description`: Description of the event
+162//! - `$url`: Url of the event
+163//! - `$location`: Location of the event
+164//! - `$start`: Start time of the event
+165//! - `$end`: End time of the event
+166//!
+167//! # Icons Used
+168//! - `calendar`
+169
+170use chrono::{Duration, Local, Utc};
+171use oauth2::{AuthUrl, ClientId, ClientSecret, Scope, TokenUrl};
+172use reqwest::Url;
+173
+174use crate::util;
+175use crate::{subprocess::spawn_process, util::has_command};
+176
+177mod auth;
+178mod caldav;
+179
+180use self::auth::{Authorize, AuthorizeUrl, OAuth2Flow, TokenStore, TokenStoreError};
+181use self::caldav::Event;
+182
+183use super::prelude::*;
+184
+185use std::path::Path;
+186use std::sync::Arc;
+187
+188use caldav::Client;
+189
+190#[derive(Deserialize, Debug, SmartDefault, Clone)]
+191#[serde(deny_unknown_fields, default)]
+192pub struct BasicCredentials {
+193    pub username: Option<String>,
+194    pub password: Option<String>,
+195}
+196
+197#[derive(Deserialize, Debug, Clone)]
+198pub struct BasicAuthConfig {
+199    #[serde(flatten)]
+200    pub credentials: BasicCredentials,
+201    pub credentials_path: Option<ShellString>,
+202}
+203
+204#[derive(Deserialize, Debug, SmartDefault, Clone)]
+205#[serde(deny_unknown_fields, default)]
+206pub struct OAuth2Credentials {
+207    pub client_id: Option<String>,
+208    pub client_secret: Option<String>,
+209}
+210
+211#[derive(Deserialize, Debug, SmartDefault, Clone)]
+212#[serde(deny_unknown_fields, default)]
+213pub struct OAuth2Config {
+214    #[serde(flatten)]
+215    pub credentials: OAuth2Credentials,
+216    pub credentials_path: Option<ShellString>,
+217    pub auth_url: String,
+218    pub token_url: String,
+219    #[default("~/.config/i3status-rust/calendar.auth_token".into())]
+220    pub auth_token: ShellString,
+221    #[default(8080)]
+222    pub redirect_port: u16,
+223    pub scopes: Vec<Scope>,
+224}
+225
+226#[derive(Deserialize, Default, Debug, Clone)]
+227#[serde(tag = "type", rename_all = "lowercase")]
+228pub enum AuthConfig {
+229    #[default]
+230    Unauthenticated,
+231    Basic(BasicAuthConfig),
+232    OAuth2(OAuth2Config),
+233}
+234
+235#[derive(Deserialize, Debug, SmartDefault, Clone)]
+236#[serde(deny_unknown_fields, default)]
+237pub struct SourceConfig {
+238    pub url: String,
+239    pub auth: AuthConfig,
+240    pub calendars: Vec<String>,
+241}
+242
+243#[derive(Deserialize, Debug, SmartDefault)]
+244#[serde(deny_unknown_fields, default)]
+245pub struct Config {
+246    pub next_event_format: FormatConfig,
+247    pub ongoing_event_format: FormatConfig,
+248    pub no_events_format: FormatConfig,
+249    pub redirect_format: FormatConfig,
+250    #[default(60.into())]
+251    pub fetch_interval: Seconds,
+252    #[default(10.into())]
+253    pub alternate_events_interval: Seconds,
+254    #[default(48)]
+255    pub events_within_hours: u32,
+256    pub source: Vec<SourceConfig>,
+257    #[default(300)]
+258    pub warning_threshold: u32,
+259    #[default("xdg-open".into())]
+260    pub browser_cmd: ShellString,
+261}
+262
+263enum WidgetStatus {
+264    AlternateEvents,
+265    FetchSources,
+266}
+267
+268pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+269    let next_event_format = config
+270        .next_event_format
+271        .with_default(" $icon $start.datetime(f:'%a %H:%M') $summary ")?;
+272    let ongoing_event_format = config
+273        .ongoing_event_format
+274        .with_default(" $icon $summary (ends at $end.datetime(f:'%H:%M')) ")?;
+275    let no_events_format = config.no_events_format.with_default(" $icon ")?;
+276    let redirect_format = config
+277        .redirect_format
+278        .with_default(" $icon Check your web browser ")?;
+279
+280    api.set_default_actions(&[(MouseButton::Left, None, "open_link")])?;
+281
+282    let source_config = match config.source.len() {
+283        0 => return Err(Error::new("A calendar source must be supplied")),
+284        1 => config
+285            .source
+286            .first()
+287            .expect("There must be a first entry since the length is 1"),
+288        _ => {
+289            return Err(Error::new(
+290                "Currently only one calendar source is supported",
+291            ));
+292        }
+293    };
+294
+295    let warning_threshold = Duration::try_seconds(config.warning_threshold.into())
+296        .error("Invalid warning threshold configuration")?;
+297
+298    let mut source = Source::new(source_config.clone()).await?;
+299
+300    let mut timer = config.fetch_interval.timer();
+301
+302    let mut alternate_events_timer = config.alternate_events_interval.timer();
+303
+304    let mut actions = api.get_actions()?;
+305
+306    let events_within = Duration::try_hours(config.events_within_hours.into())
+307        .error("Invalid events within hours configuration")?;
+308
+309    let mut widget_status = WidgetStatus::FetchSources;
+310
+311    let mut next_events = OverlappingEvents::default();
+312
+313    loop {
+314        let mut widget = Widget::new().with_format(no_events_format.clone());
+315        widget.set_values(map! {
+316            "icon" => Value::icon("calendar"),
+317        });
+318
+319        if matches!(widget_status, WidgetStatus::FetchSources) {
+320            for retries in 0..=1 {
+321                match source.get_next_events(events_within).await {
+322                    Ok(events) => {
+323                        next_events.refresh(events);
+324                        break;
+325                    }
+326                    Err(err) => match err {
+327                        CalendarError::AuthRequired => {
+328                            let authorization = source
+329                                .client
+330                                .authorize()
+331                                .await
+332                                .error("Authorization failed")?;
+333                            match &authorization {
+334                                Authorize::AskUser(AuthorizeUrl { url, .. }) if retries == 0 => {
+335                                    widget.set_format(redirect_format.clone());
+336                                    api.set_widget(widget.clone())?;
+337                                    open_browser(config, url).await?;
+338                                    source
+339                                        .client
+340                                        .ask_user(authorization)
+341                                        .await
+342                                        .error("Ask user failed")?;
+343                                }
+344                                _ => {
+345                                    return Err(Error::new(
+346                                        "Authorization failed. Check your configurations",
+347                                    ));
+348                                }
+349                            }
+350                        }
+351                        e => {
+352                            return Err(Error {
+353                                message: None,
+354                                cause: Some(Arc::new(e)),
+355                            });
+356                        }
+357                    },
+358                };
+359            }
+360        }
+361
+362        if let Some(event) = next_events.current().cloned()
+363            && let Some(start_date) = event.start_at
+364            && let Some(end_date) = event.end_at
+365        {
+366            let warn_datetime = start_date - warning_threshold;
+367            if warn_datetime < Utc::now() && Utc::now() < start_date {
+368                widget.state = State::Warning;
+369            }
+370            if start_date < Utc::now() && Utc::now() < end_date {
+371                widget.set_format(ongoing_event_format.clone());
+372            } else {
+373                widget.set_format(next_event_format.clone());
+374            }
+375            widget.set_values(map! {
+376                  "icon" => Value::icon("calendar"),
+377                   [if let Some(summary) = event.summary] "summary" => Value::text(summary),
+378                   [if let Some(description) = event.description] "description" => Value::text(description),
+379                   [if let Some(location) = event.location] "location" => Value::text(location),
+380                   [if let Some(url) = event.url] "url" => Value::text(url),
+381                   "start" => Value::datetime(start_date, None),
+382                   "end" => Value::datetime(end_date, None),
+383                });
+384        }
+385
+386        api.set_widget(widget)?;
+387        loop {
+388            select! {
+389                _ = timer.tick() => {
+390                  widget_status = WidgetStatus::FetchSources;
+391                  break
+392                }
+393                _ = alternate_events_timer.tick() => {
+394                  next_events.cycle_warning_or_ongoing(warning_threshold);
+395                  widget_status = WidgetStatus::AlternateEvents;
+396                  break
+397                }
+398                _ = api.wait_for_update_request() => break,
+399                Some(action) = actions.recv() => match action.as_ref() {
+400                      "open_link" => {
+401                          if let Some(Event { url: Some(url), .. }) = next_events.current()
+402                              && let Ok(url) = Url::parse(url) {
+403                                  open_browser(config, &url).await?;
+404                              }
+405                      }
+406                      _ => ()
+407                }
+408            }
+409        }
+410    }
+411}
+412
+413struct Source {
+414    pub client: caldav::Client,
+415    pub config: SourceConfig,
+416}
+417
+418impl Source {
+419    async fn new(config: SourceConfig) -> Result<Self> {
+420        let auth = match &config.auth {
+421            AuthConfig::Unauthenticated => auth::Auth::Unauthenticated,
+422            AuthConfig::Basic(BasicAuthConfig {
+423                credentials,
+424                credentials_path,
+425            }) => {
+426                let credentials = if let Some(path) = credentials_path {
+427                    util::deserialize_toml_file(path.expand()?.to_string())
+428                        .error("Failed to read basic credentials file")?
+429                } else {
+430                    credentials.clone()
+431                };
+432                let BasicCredentials {
+433                    username: Some(username),
+434                    password: Some(password),
+435                } = credentials
+436                else {
+437                    return Err(Error::new("Basic credentials are not configured"));
+438                };
+439                auth::Auth::basic(username, password)
+440            }
+441            AuthConfig::OAuth2(oauth2) => {
+442                let credentials = if let Some(path) = &oauth2.credentials_path {
+443                    util::deserialize_toml_file(path.expand()?.to_string())
+444                        .error("Failed to read oauth2 credentials file")?
+445                } else {
+446                    oauth2.credentials.clone()
+447                };
+448                let OAuth2Credentials {
+449                    client_id: Some(client_id),
+450                    client_secret: Some(client_secret),
+451                } = credentials
+452                else {
+453                    return Err(Error::new("Oauth2 credentials are not configured"));
+454                };
+455                let auth_url =
+456                    AuthUrl::new(oauth2.auth_url.clone()).error("Invalid authorization url")?;
+457                let token_url =
+458                    TokenUrl::new(oauth2.token_url.clone()).error("Invalid token url")?;
+459
+460                let flow = OAuth2Flow::new(
+461                    ClientId::new(client_id),
+462                    ClientSecret::new(client_secret),
+463                    auth_url,
+464                    token_url,
+465                    oauth2.redirect_port,
+466                );
+467                let token_store =
+468                    TokenStore::new(Path::new(&oauth2.auth_token.expand()?.to_string()));
+469                auth::Auth::oauth2(flow, token_store, oauth2.scopes.clone())
+470            }
+471        };
+472        Ok(Self {
+473            client: Client::new(
+474                Url::parse(&config.url).error("Invalid CalDav server url")?,
+475                auth,
+476            ),
+477            config,
+478        })
+479    }
+480
+481    async fn get_next_events(
+482        &mut self,
+483        within: Duration,
+484    ) -> Result<OverlappingEvents, CalendarError> {
+485        let calendars: Vec<_> = self
+486            .client
+487            .calendars()
+488            .await?
+489            .into_iter()
+490            .filter(|c| self.config.calendars.is_empty() || self.config.calendars.contains(&c.name))
+491            .collect();
+492        let mut events: Vec<Event> = vec![];
+493        for calendar in calendars {
+494            let calendar_events: Vec<_> = self
+495                .client
+496                .events(
+497                    &calendar,
+498                    Local::now()
+499                        .date_naive()
+500                        .and_hms_opt(0, 0, 0)
+501                        .expect("A valid time")
+502                        .and_local_timezone(Local)
+503                        .earliest()
+504                        .expect("A valid datetime")
+505                        .to_utc(),
+506                    Utc::now() + within,
+507                )
+508                .await?
+509                .into_iter()
+510                .filter(|e| {
+511                    let not_started = e.start_at.is_some_and(|d| d > Utc::now());
+512                    let is_ongoing = e.start_at.is_some_and(|d| d < Utc::now())
+513                        && e.end_at.is_some_and(|d| d > Utc::now());
+514                    not_started || is_ongoing
+515                })
+516                .collect();
+517            events.extend(calendar_events);
+518        }
+519
+520        events.sort_by_key(|e| e.start_at);
+521        let Some(next_event) = events.first().cloned() else {
+522            return Ok(OverlappingEvents::default());
+523        };
+524        let overlapping_events = events
+525            .into_iter()
+526            .take_while(|e| e.start_at <= next_event.end_at)
+527            .collect();
+528        Ok(OverlappingEvents::new(overlapping_events))
+529    }
+530}
+531
+532#[derive(Default)]
+533struct OverlappingEvents {
+534    current: Option<Event>,
+535    events: Vec<Event>,
+536}
+537
+538impl OverlappingEvents {
+539    fn new(events: Vec<Event>) -> Self {
+540        Self {
+541            current: events.first().cloned(),
+542            events,
+543        }
+544    }
+545
+546    fn refresh(&mut self, other: OverlappingEvents) {
+547        if self.current.is_none() {
+548            self.current = other.events.first().cloned();
+549        }
+550        self.events = other.events;
+551    }
+552
+553    fn current(&self) -> Option<&Event> {
+554        self.current.as_ref()
+555    }
+556
+557    fn cycle_warning_or_ongoing(&mut self, warning_threshold: Duration) {
+558        self.current = if let Some(current) = &self.current {
+559            if self.events.iter().any(|e| e.uid == current.uid) {
+560                let mut iter = self
+561                    .events
+562                    .iter()
+563                    .cycle()
+564                    .skip_while(|e| e.uid != current.uid);
+565                iter.next();
+566                iter.find(|e| {
+567                    let is_ongoing = e.start_at.is_some_and(|d| d < Utc::now())
+568                        && e.end_at.is_some_and(|d| d > Utc::now());
+569                    let is_warning = e
+570                        .start_at
+571                        .is_some_and(|d| d - warning_threshold < Utc::now() && Utc::now() < d);
+572                    e.uid == current.uid || is_warning || is_ongoing
+573                })
+574                .cloned()
+575            } else {
+576                self.events.first().cloned()
+577            }
+578        } else {
+579            self.events.first().cloned()
+580        };
+581    }
+582}
+583
+584async fn open_browser(config: &Config, url: &Url) -> Result<()> {
+585    let cmd = config.browser_cmd.expand()?;
+586    has_command(&cmd)
+587        .await
+588        .or_error(|| "Browser command not found")?;
+589    spawn_process(&cmd, &[url.as_ref()]).error("Open browser failed")
+590}
+591
+592#[derive(thiserror::Error, Debug)]
+593pub enum CalendarError {
+594    #[error(transparent)]
+595    Http(#[from] reqwest::Error),
+596    #[error(transparent)]
+597    Deserialize(#[from] quick_xml::de::DeError),
+598    #[error("Parsing error: {0}")]
+599    Parsing(String),
+600    #[error("Auth required")]
+601    AuthRequired,
+602    #[error(transparent)]
+603    Io(#[from] std::io::Error),
+604    #[error(transparent)]
+605    Serialize(#[from] serde_json::Error),
+606    #[error("Request token error: {0}")]
+607    RequestToken(String),
+608    #[error("Store token error: {0}")]
+609    StoreToken(#[from] TokenStoreError),
+610}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/calendar/auth.rs.html b/src/i3status_rs/blocks/calendar/auth.rs.html new file mode 100644 index 0000000000..5d8a4f5a1c --- /dev/null +++ b/src/i3status_rs/blocks/calendar/auth.rs.html @@ -0,0 +1,330 @@ +auth.rs - source

i3status_rs/blocks/calendar/
auth.rs

1use base64::Engine as _;
+2use oauth2::basic::{
+3    BasicErrorResponse, BasicRevocationErrorResponse, BasicTokenIntrospectionResponse,
+4    BasicTokenResponse,
+5};
+6use oauth2::{
+7    AuthUrl, AuthorizationCode, Client, ClientId, ClientSecret, CsrfToken, EndpointNotSet,
+8    EndpointSet, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, RefreshToken, Scope,
+9    StandardRevocableToken, TokenResponse as _, TokenUrl,
+10};
+11use reqwest;
+12use reqwest::Url;
+13use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue};
+14use std::path::{Path, PathBuf};
+15use std::sync::LazyLock;
+16use tokio::fs::File;
+17use tokio::io::{AsyncBufReadExt as _, AsyncReadExt as _, AsyncWriteExt as _, BufReader};
+18use tokio::net::TcpListener;
+19
+20use super::CalendarError;
+21use crate::{APP_USER_AGENT, REQWEST_TIMEOUT};
+22
+23static REQWEST_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
+24    reqwest::Client::builder()
+25        .user_agent(APP_USER_AGENT)
+26        .timeout(REQWEST_TIMEOUT)
+27        // Following redirects opens the client up to SSRF vulnerabilities.
+28        .redirect(reqwest::redirect::Policy::none())
+29        .build()
+30        .unwrap()
+31});
+32
+33type BasicClient<
+34    HasAuthUrl = EndpointSet,
+35    HasDeviceAuthUrl = EndpointNotSet,
+36    HasIntrospectionUrl = EndpointNotSet,
+37    HasRevocationUrl = EndpointNotSet,
+38    HasTokenUrl = EndpointSet,
+39> = Client<
+40    BasicErrorResponse,
+41    BasicTokenResponse,
+42    BasicTokenIntrospectionResponse,
+43    StandardRevocableToken,
+44    BasicRevocationErrorResponse,
+45    HasAuthUrl,
+46    HasDeviceAuthUrl,
+47    HasIntrospectionUrl,
+48    HasRevocationUrl,
+49    HasTokenUrl,
+50>;
+51
+52pub enum Auth {
+53    Unauthenticated,
+54    Basic(Basic),
+55    OAuth2(Box<OAuth2>),
+56}
+57
+58impl Auth {
+59    pub fn oauth2(flow: OAuth2Flow, token_store: TokenStore, scopes: Vec<Scope>) -> Self {
+60        Self::OAuth2(Box::new(OAuth2 {
+61            flow,
+62            token_store,
+63            scopes,
+64        }))
+65    }
+66    pub fn basic(username: String, password: String) -> Self {
+67        Self::Basic(Basic { username, password })
+68    }
+69    pub async fn headers(&mut self) -> HeaderMap {
+70        match self {
+71            Auth::Unauthenticated => HeaderMap::new(),
+72            Auth::Basic(auth) => auth.headers().await,
+73            Auth::OAuth2(auth) => auth.headers().await,
+74        }
+75    }
+76
+77    pub async fn handle_error(&mut self, error: reqwest::Error) -> Result<(), CalendarError> {
+78        match self {
+79            Auth::Unauthenticated | Auth::Basic(_) => Err(CalendarError::Http(error)),
+80            Auth::OAuth2(auth) => auth.handle_error(error).await,
+81        }
+82    }
+83
+84    pub async fn authorize(&mut self) -> Result<Authorize, CalendarError> {
+85        match self {
+86            Auth::Unauthenticated | Auth::Basic(_) => Ok(Authorize::Completed),
+87            Auth::OAuth2(auth) => Ok(Authorize::AskUser(auth.authorize().await?)),
+88        }
+89    }
+90    pub async fn ask_user(&mut self, authorize_url: AuthorizeUrl) -> Result<(), CalendarError> {
+91        match self {
+92            Auth::Unauthenticated | Auth::Basic(_) => Ok(()),
+93            Auth::OAuth2(auth) => auth.ask_user(authorize_url).await,
+94        }
+95    }
+96}
+97
+98pub struct Basic {
+99    username: String,
+100    password: String,
+101}
+102
+103impl Basic {
+104    pub async fn headers(&mut self) -> HeaderMap {
+105        let mut headers = HeaderMap::new();
+106        let header =
+107            base64::prelude::BASE64_STANDARD.encode(format!("{}:{}", self.username, self.password));
+108        let mut header_value = HeaderValue::from_str(format!("Basic {header}").as_str())
+109            .expect("A valid basic header");
+110        header_value.set_sensitive(true);
+111        headers.insert(AUTHORIZATION, header_value);
+112        headers
+113    }
+114}
+115
+116pub struct OAuth2 {
+117    flow: OAuth2Flow,
+118    token_store: TokenStore,
+119    scopes: Vec<Scope>,
+120}
+121
+122impl OAuth2 {
+123    pub async fn headers(&mut self) -> HeaderMap {
+124        let mut headers = HeaderMap::new();
+125        if let Some(token) = self.token_store.get().await {
+126            let mut auth_value =
+127                HeaderValue::from_str(format!("Bearer {}", token.access_token().secret()).as_str())
+128                    .expect("A valid access token");
+129            auth_value.set_sensitive(true);
+130            headers.insert(AUTHORIZATION, auth_value);
+131        }
+132        headers
+133    }
+134
+135    async fn handle_error(&mut self, error: reqwest::Error) -> Result<(), CalendarError> {
+136        if let Some(status) = error.status() {
+137            if status == 401 {
+138                match self
+139                    .token_store
+140                    .get()
+141                    .await
+142                    .and_then(|t| t.refresh_token().cloned())
+143                {
+144                    Some(refresh_token) => {
+145                        let mut token = self.flow.refresh_token_exchange(&refresh_token).await?;
+146                        if token.refresh_token().is_none() {
+147                            token.set_refresh_token(Some(refresh_token));
+148                        }
+149                        self.token_store.store(token).await?;
+150                        return Ok(());
+151                    }
+152                    None => return Err(CalendarError::AuthRequired),
+153                }
+154            }
+155            if status == 403 {
+156                return Err(CalendarError::AuthRequired);
+157            }
+158        }
+159        Err(CalendarError::Http(error))
+160    }
+161
+162    async fn authorize(&mut self) -> Result<AuthorizeUrl, CalendarError> {
+163        Ok(self.flow.authorize_url(self.scopes.clone()))
+164    }
+165
+166    async fn ask_user(&mut self, authorize_url: AuthorizeUrl) -> Result<(), CalendarError> {
+167        let token = self.flow.redirect(authorize_url).await?;
+168        self.token_store.store(token).await?;
+169        Ok(())
+170    }
+171}
+172pub struct OAuth2Flow {
+173    client: BasicClient,
+174    redirect_port: u16,
+175}
+176
+177impl OAuth2Flow {
+178    pub fn new(
+179        client_id: ClientId,
+180        client_secret: ClientSecret,
+181        auth_url: AuthUrl,
+182        token_url: TokenUrl,
+183        redirect_port: u16,
+184    ) -> Self {
+185        Self {
+186            client: BasicClient::new(client_id)
+187                .set_client_secret(client_secret)
+188                .set_auth_uri(auth_url)
+189                .set_token_uri(token_url)
+190                .set_redirect_uri(
+191                    RedirectUrl::new(format!("http://localhost:{redirect_port}").to_string())
+192                        .expect("A valid redirect URL"),
+193                ),
+194            redirect_port,
+195        }
+196    }
+197
+198    pub fn authorize_url(&self, scopes: Vec<Scope>) -> AuthorizeUrl {
+199        let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256();
+200        let (authorize_url, csrf_token) = self
+201            .client
+202            .authorize_url(CsrfToken::new_random)
+203            .add_scopes(scopes)
+204            .set_pkce_challenge(pkce_code_challenge.clone())
+205            .url();
+206        AuthorizeUrl {
+207            pkce_code_verifier,
+208            url: authorize_url,
+209            csrf_token,
+210        }
+211    }
+212
+213    pub async fn refresh_token_exchange(
+214        &self,
+215        token: &RefreshToken,
+216    ) -> Result<BasicTokenResponse, CalendarError> {
+217        self.client
+218            .exchange_refresh_token(token)
+219            .request_async(&*REQWEST_CLIENT)
+220            .await
+221            .map_err(|e| CalendarError::RequestToken(e.to_string()))
+222    }
+223
+224    pub async fn redirect(
+225        &self,
+226        authorize_url: AuthorizeUrl,
+227    ) -> Result<BasicTokenResponse, CalendarError> {
+228        let client = self.client.clone();
+229        let redirect_port = self.redirect_port;
+230        let listener = TcpListener::bind(format!("127.0.0.1:{redirect_port}")).await?;
+231        let (mut stream, _) = listener.accept().await?;
+232        let mut request_line = String::new();
+233        let mut reader = BufReader::new(&mut stream);
+234        reader.read_line(&mut request_line).await?;
+235
+236        let redirect_url = request_line
+237            .split_whitespace()
+238            .nth(1)
+239            .ok_or(CalendarError::RequestToken("Invalid redirect url".into()))?;
+240        let url = Url::parse(&("http://localhost".to_string() + redirect_url))
+241            .map_err(|e| CalendarError::RequestToken(e.to_string()))?;
+242
+243        let (_, code_value) =
+244            url.query_pairs()
+245                .find(|(key, _)| key == "code")
+246                .ok_or(CalendarError::RequestToken(
+247                    "code query param is missing".into(),
+248                ))?;
+249        let code = AuthorizationCode::new(code_value.into_owned());
+250        let (_, state_value) = url.query_pairs().find(|(key, _)| key == "state").ok_or(
+251            CalendarError::RequestToken("state query param is missing".into()),
+252        )?;
+253        let state = CsrfToken::new(state_value.into_owned());
+254        if state.secret() != authorize_url.csrf_token.secret() {
+255            return Err(CalendarError::RequestToken(
+256                "Received state and csrf token are different".to_string(),
+257            ));
+258        }
+259
+260        let message = "Now your i3status-rust calendar is authorized";
+261        let response = format!(
+262            "HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
+263            message.len(),
+264            message
+265        );
+266        stream.write_all(response.as_bytes()).await?;
+267
+268        client
+269            .exchange_code(code)
+270            .set_pkce_verifier(authorize_url.pkce_code_verifier)
+271            .request_async(&*REQWEST_CLIENT)
+272            .await
+273            .map_err(|e| CalendarError::RequestToken(e.to_string()))
+274    }
+275}
+276
+277#[derive(Debug)]
+278pub enum Authorize {
+279    Completed,
+280    AskUser(AuthorizeUrl),
+281}
+282
+283#[derive(Debug)]
+284pub struct AuthorizeUrl {
+285    pkce_code_verifier: PkceCodeVerifier,
+286    pub url: Url,
+287    csrf_token: CsrfToken,
+288}
+289
+290#[derive(Debug)]
+291pub struct TokenStore {
+292    path: PathBuf,
+293    token: Option<BasicTokenResponse>,
+294}
+295
+296impl TokenStore {
+297    pub fn new(path: &Path) -> Self {
+298        Self {
+299            path: path.into(),
+300            token: None,
+301        }
+302    }
+303
+304    pub async fn store(&mut self, token: BasicTokenResponse) -> Result<(), TokenStoreError> {
+305        let mut file = File::create(&self.path).await?;
+306        let value = serde_json::to_string(&token)?;
+307        file.write_all(value.as_bytes()).await?;
+308        self.token = Some(token);
+309        Ok(())
+310    }
+311
+312    pub async fn get(&mut self) -> Option<BasicTokenResponse> {
+313        if self.token.is_none()
+314            && let Ok(mut file) = File::open(&self.path).await
+315        {
+316            let mut content = vec![];
+317            file.read_to_end(&mut content).await.ok()?;
+318            self.token = serde_json::from_slice(&content).ok();
+319        }
+320        self.token.clone()
+321    }
+322}
+323
+324#[derive(thiserror::Error, Debug)]
+325pub enum TokenStoreError {
+326    #[error(transparent)]
+327    Io(#[from] std::io::Error),
+328    #[error(transparent)]
+329    Serde(#[from] serde_json::Error),
+330}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/calendar/caldav.rs.html b/src/i3status_rs/blocks/calendar/caldav.rs.html new file mode 100644 index 0000000000..700af050cd --- /dev/null +++ b/src/i3status_rs/blocks/calendar/caldav.rs.html @@ -0,0 +1,374 @@ +caldav.rs - source

i3status_rs/blocks/calendar/
caldav.rs

1use std::{str::FromStr as _, time::Duration, vec};
+2
+3use chrono::{DateTime, Local, Utc};
+4use icalendar::{Component as _, EventLike as _};
+5use reqwest::{
+6    self, ClientBuilder, Method, Url,
+7    header::{CONTENT_TYPE, HeaderMap, HeaderValue},
+8};
+9use serde::Deserialize;
+10
+11use super::{
+12    CalendarError,
+13    auth::{Auth, Authorize},
+14};
+15
+16#[derive(Clone, Debug)]
+17pub struct Event {
+18    pub uid: Option<String>,
+19    pub summary: Option<String>,
+20    pub description: Option<String>,
+21    pub location: Option<String>,
+22    pub url: Option<String>,
+23    pub start_at: Option<DateTime<Utc>>,
+24    pub end_at: Option<DateTime<Utc>>,
+25}
+26
+27#[derive(Deserialize, Debug)]
+28pub struct Calendar {
+29    pub url: Url,
+30    pub name: String,
+31}
+32
+33pub struct Client {
+34    url: Url,
+35    client: reqwest::Client,
+36    auth: Auth,
+37}
+38
+39impl Client {
+40    pub fn new(url: Url, auth: Auth) -> Self {
+41        let mut headers = HeaderMap::new();
+42        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/xml"));
+43        Self {
+44            url,
+45            client: ClientBuilder::new()
+46                .timeout(Duration::from_secs(10))
+47                .default_headers(headers)
+48                .build()
+49                .expect("A valid http client"),
+50            auth,
+51        }
+52    }
+53    async fn propfind_request(
+54        &mut self,
+55        url: Url,
+56        depth: usize,
+57        body: String,
+58    ) -> Result<Multistatus, CalendarError> {
+59        let request = self
+60            .client
+61            .request(Method::from_str("PROPFIND").expect("A valid method"), url)
+62            .body(body.clone())
+63            .headers(self.auth.headers().await)
+64            .header("Depth", depth)
+65            .build()
+66            .expect("A valid propfind request");
+67        self.call(request).await
+68    }
+69
+70    async fn report_request(
+71        &mut self,
+72        url: Url,
+73        depth: usize,
+74        body: String,
+75    ) -> Result<Multistatus, CalendarError> {
+76        let request = self
+77            .client
+78            .request(Method::from_str("REPORT").expect("A valid method"), url)
+79            .body(body)
+80            .headers(self.auth.headers().await)
+81            .header("Depth", depth)
+82            .build()
+83            .expect("A valid report request");
+84        self.call(request).await
+85    }
+86
+87    async fn call(&mut self, request: reqwest::Request) -> Result<Multistatus, CalendarError> {
+88        let mut retries = 0;
+89        loop {
+90            let result = self
+91                .client
+92                .execute(request.try_clone().expect("Request to be cloneable"))
+93                .await?;
+94            match result.error_for_status() {
+95                Err(err) if retries == 0 => {
+96                    self.auth.handle_error(err).await?;
+97                    retries += 1;
+98                }
+99                Err(err) => return Err(CalendarError::Http(err)),
+100                Ok(result) => return Ok(quick_xml::de::from_str(result.text().await?.as_str())?),
+101            };
+102        }
+103    }
+104
+105    async fn user_principal_url(&mut self) -> Result<Url, CalendarError> {
+106        let multi_status = self
+107            .propfind_request(self.url.clone(), 1, CURRENT_USER_PRINCIPAL.into())
+108            .await?;
+109        parse_href(multi_status, self.url.clone())
+110    }
+111
+112    async fn home_set_url(&mut self, user_principal_url: Url) -> Result<Url, CalendarError> {
+113        let multi_status = self
+114            .propfind_request(user_principal_url, 0, CALENDAR_HOME_SET.into())
+115            .await?;
+116        parse_href(multi_status, self.url.clone())
+117    }
+118
+119    async fn calendars_query(&mut self, home_set_url: Url) -> Result<Vec<Calendar>, CalendarError> {
+120        let multi_status = self
+121            .propfind_request(home_set_url, 1, CALENDAR_REQUEST.into())
+122            .await?;
+123        parse_calendars(multi_status, self.url.clone())
+124    }
+125
+126    pub async fn calendars(&mut self) -> Result<Vec<Calendar>, CalendarError> {
+127        let user_principal_url = self.user_principal_url().await?;
+128        let home_set_url = self.home_set_url(user_principal_url).await?;
+129        self.calendars_query(home_set_url).await
+130    }
+131
+132    pub async fn events(
+133        &mut self,
+134        calendar: &Calendar,
+135        start: DateTime<Utc>,
+136        end: DateTime<Utc>,
+137    ) -> Result<Vec<Event>, CalendarError> {
+138        let multi_status = self
+139            .report_request(calendar.url.clone(), 1, calendar_events_request(start, end))
+140            .await?;
+141        parse_events(multi_status)
+142    }
+143
+144    pub async fn authorize(&mut self) -> Result<Authorize, CalendarError> {
+145        self.auth.authorize().await
+146    }
+147
+148    pub async fn ask_user(&mut self, authorize: Authorize) -> Result<(), CalendarError> {
+149        match authorize {
+150            Authorize::Completed => Ok(()),
+151            Authorize::AskUser(authorize_url) => self.auth.ask_user(authorize_url).await,
+152        }
+153    }
+154}
+155
+156#[derive(Debug, Deserialize)]
+157#[serde(rename = "multistatus")]
+158struct Multistatus {
+159    #[serde(rename = "response", default)]
+160    responses: Vec<Response>,
+161}
+162
+163#[derive(Debug, Deserialize)]
+164struct Response {
+165    href: String,
+166    #[serde(rename = "propstat", default)]
+167    propstats: Vec<Propstat>,
+168}
+169
+170impl Response {
+171    fn valid_props(self) -> Vec<PropValue> {
+172        self.propstats
+173            .into_iter()
+174            .filter(|p| p.status.contains("200"))
+175            .flat_map(|p| p.prop.values.into_iter())
+176            .collect()
+177    }
+178}
+179
+180#[derive(Debug, Deserialize)]
+181struct Propstat {
+182    status: String,
+183    prop: Prop,
+184}
+185
+186#[derive(Debug, Deserialize)]
+187struct Prop {
+188    #[serde(rename = "$value")]
+189    pub values: Vec<PropValue>,
+190}
+191
+192#[derive(Debug, Deserialize)]
+193#[serde(rename_all = "kebab-case")]
+194enum PropValue {
+195    CurrentUserPrincipal(HrefProperty),
+196    CalendarHomeSet(HrefProperty),
+197    SupportedCalendarComponentSet(SupportedCalendarComponentSet),
+198    #[serde(rename = "displayname")]
+199    DisplayName(String),
+200    #[serde(rename = "resourcetype")]
+201    ResourceType(ResourceTypes),
+202    CalendarData(String),
+203}
+204
+205#[derive(Debug, Deserialize)]
+206pub struct HrefProperty {
+207    href: String,
+208}
+209
+210#[derive(Debug, Deserialize)]
+211struct ResourceTypes {
+212    #[serde(rename = "$value")]
+213    pub values: Vec<ResourceType>,
+214}
+215
+216impl ResourceTypes {
+217    fn is_calendar(&self) -> bool {
+218        self.values.contains(&ResourceType::Calendar)
+219    }
+220}
+221#[derive(Debug, Deserialize, PartialEq)]
+222#[serde(rename_all = "kebab-case")]
+223enum ResourceType {
+224    Calendar,
+225    #[serde(other)]
+226    Unsupported,
+227}
+228
+229#[derive(Debug, Deserialize)]
+230struct SupportedCalendarComponentSet {
+231    #[serde(rename = "$value", default)]
+232    pub values: Vec<Comp>,
+233}
+234impl SupportedCalendarComponentSet {
+235    fn supports_events(&self) -> bool {
+236        self.values.iter().any(|v| v.name == "VEVENT")
+237    }
+238}
+239
+240#[derive(Debug, Deserialize)]
+241struct Comp {
+242    #[serde(rename = "@name", default)]
+243    name: String,
+244}
+245
+246fn parse_href(multi_status: Multistatus, base_url: Url) -> Result<Url, CalendarError> {
+247    let props = multi_status
+248        .responses
+249        .into_iter()
+250        .flat_map(|r| r.valid_props().into_iter())
+251        .next();
+252    match props.ok_or_else(|| CalendarError::Parsing("Property not found".into()))? {
+253        PropValue::CurrentUserPrincipal(href) | PropValue::CalendarHomeSet(href) => base_url
+254            .join(&href.href)
+255            .map_err(|e| CalendarError::Parsing(e.to_string())),
+256        _ => Err(CalendarError::Parsing("Invalid property".into())),
+257    }
+258}
+259
+260fn parse_calendars(
+261    multi_status: Multistatus,
+262    base_url: Url,
+263) -> Result<Vec<Calendar>, CalendarError> {
+264    let mut result = vec![];
+265    for response in multi_status.responses {
+266        let mut is_calendar = false;
+267        let mut supports_events = false;
+268        let mut name = None;
+269        let href = response.href.clone();
+270        for prop in response.valid_props() {
+271            match prop {
+272                PropValue::SupportedCalendarComponentSet(comp) => {
+273                    supports_events = comp.supports_events();
+274                }
+275                PropValue::DisplayName(display_name) => name = Some(display_name),
+276                PropValue::ResourceType(ty) => is_calendar = ty.is_calendar(),
+277                _ => {}
+278            }
+279        }
+280        if is_calendar
+281            && supports_events
+282            && let Some(name) = name
+283        {
+284            result.push(Calendar {
+285                name,
+286                url: base_url
+287                    .join(&href)
+288                    .map_err(|_| CalendarError::Parsing("Malformed calendar url".into()))?,
+289            });
+290        }
+291    }
+292    Ok(result)
+293}
+294
+295fn parse_events(multi_status: Multistatus) -> Result<Vec<Event>, CalendarError> {
+296    let mut result = vec![];
+297    for response in multi_status.responses {
+298        for prop in response.valid_props() {
+299            if let PropValue::CalendarData(data) = prop {
+300                let calendar =
+301                    icalendar::Calendar::from_str(&data).map_err(CalendarError::Parsing)?;
+302                for component in calendar.components {
+303                    if let icalendar::CalendarComponent::Event(event) = component {
+304                        let start_at = event.get_start().and_then(|d| match d {
+305                            icalendar::DatePerhapsTime::DateTime(dt) => dt.try_into_utc(),
+306                            icalendar::DatePerhapsTime::Date(d) => d
+307                                .and_hms_opt(0, 0, 0)
+308                                .and_then(|d| d.and_local_timezone(Local).earliest())
+309                                .map(|d| d.to_utc()),
+310                        });
+311                        let end_at = event.get_end().and_then(|d| match d {
+312                            icalendar::DatePerhapsTime::DateTime(dt) => dt.try_into_utc(),
+313                            icalendar::DatePerhapsTime::Date(d) => d
+314                                .and_hms_opt(23, 59, 59)
+315                                .and_then(|d| d.and_local_timezone(Local).earliest())
+316                                .map(|d| d.to_utc()),
+317                        });
+318                        result.push(Event {
+319                            uid: event.get_uid().map(Into::into),
+320                            summary: event.get_summary().map(Into::into),
+321                            description: event.get_description().map(Into::into),
+322                            location: event.get_location().map(Into::into),
+323                            url: event.get_url().map(Into::into),
+324                            start_at,
+325                            end_at,
+326                        });
+327                    }
+328                }
+329            }
+330        }
+331    }
+332    Ok(result)
+333}
+334
+335static CURRENT_USER_PRINCIPAL: &str = r#"<d:propfind xmlns:d="DAV:">
+336          <d:prop>
+337            <d:current-user-principal />
+338          </d:prop>
+339        </d:propfind>"#;
+340
+341static CALENDAR_HOME_SET: &str = r#"<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav" >
+342            <d:prop>
+343                <c:calendar-home-set />
+344            </d:prop>
+345        </d:propfind>"#;
+346
+347static CALENDAR_REQUEST: &str = r#"<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav" >
+348            <d:prop>
+349                <d:displayname />
+350                <d:resourcetype />
+351                <c:supported-calendar-component-set />
+352            </d:prop>
+353        </d:propfind>"#;
+354
+355pub fn calendar_events_request(start: DateTime<Utc>, end: DateTime<Utc>) -> String {
+356    const DATE_FORMAT: &str = "%Y%m%dT%H%M%SZ";
+357    let start = start.format(DATE_FORMAT);
+358    let end = end.format(DATE_FORMAT);
+359    format!(
+360        r#"<?xml version="1.0" encoding="UTF-8"?>
+361        <c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
+362        <d:prop>
+363            <c:calendar-data/>
+364        </d:prop>
+365        <c:filter>
+366            <c:comp-filter name="VCALENDAR">
+367                <c:comp-filter name="VEVENT">
+368                    <c:time-range start="{start}" end="{end}" />
+369                </c:comp-filter>
+370            </c:comp-filter>
+371        </c:filter>
+372        </c:calendar-query>"#
+373    )
+374}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/cpu.rs.html b/src/i3status_rs/blocks/cpu.rs.html new file mode 100644 index 0000000000..10dba20890 --- /dev/null +++ b/src/i3status_rs/blocks/cpu.rs.html @@ -0,0 +1,267 @@ +cpu.rs - source

i3status_rs/blocks/
cpu.rs

1//! CPU statistics
+2//!
+3//! # Configuration
+4//!
+5//! Key | Values | Default
+6//! ----|--------|--------
+7//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $utilization "`
+8//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None`
+9//! `interval` | Update interval in seconds | `5`
+10//! `info_cpu` | Percentage of CPU usage, where state is set to info | `30.0`
+11//! `warning_cpu` | Percentage of CPU usage, where state is set to warning | `60.0`
+12//! `critical_cpu` | Percentage of CPU usage, where state is set to critical | `90.0`
+13//!
+14//! Placeholder      | Value                                                                | Type   | Unit
+15//! -----------------|----------------------------------------------------------------------|--------|---------------
+16//! `icon`           | An icon                                                              | Icon   | -
+17//! `utilization`    | Average CPU utilization                                              | Number | %
+18//! `utilization<N>` | Utilization of Nth logical CPU                                       | Number | %
+19//! `barchart`       | Utilization of all logical CPUs presented as a barchart              | Text   | -
+20//! `frequency`      | Average CPU frequency (may be absent if CPU is not supported)        | Number | Hz
+21//! `frequency<N>`   | Frequency of Nth logical CPU (may be absent if CPU is not supported) | Number | Hz
+22//! `max_frequency`  | Max frequency of all logical CPUs                                    | Number | Hz
+23//! `boost`          | CPU turbo boost status (may be absent if CPU is not supported)       | Text   | -
+24//!
+25//! Action          | Description                               | Default button
+26//! ----------------|-------------------------------------------|---------------
+27//! `toggle_format` | Toggles between `format` and `format_alt` | Left
+28//!
+29//! # Example
+30//!
+31//! ```toml
+32//! [[block]]
+33//! block = "cpu"
+34//! interval = 1
+35//! format = " $icon $barchart $utilization "
+36//! format_alt = " $icon $frequency{ $boost|} "
+37//! info_cpu = 20
+38//! warning_cpu = 50
+39//! critical_cpu = 90
+40//! ```
+41//!
+42//! # Icons Used
+43//! - `cpu` (as a progression)
+44//! - `cpu_boost_on`
+45//! - `cpu_boost_off`
+46
+47use std::str::FromStr as _;
+48
+49use tokio::fs::File;
+50use tokio::io::{AsyncBufReadExt as _, BufReader};
+51
+52use super::prelude::*;
+53use crate::util::read_file;
+54
+55const CPU_BOOST_PATH: &str = "/sys/devices/system/cpu/cpufreq/boost";
+56const CPU_NO_TURBO_PATH: &str = "/sys/devices/system/cpu/intel_pstate/no_turbo";
+57
+58#[derive(Deserialize, Debug, SmartDefault)]
+59#[serde(deny_unknown_fields, default)]
+60pub struct Config {
+61    pub format: FormatConfig,
+62    pub format_alt: Option<FormatConfig>,
+63    #[default(5.into())]
+64    pub interval: Seconds,
+65    #[default(30.0)]
+66    pub info_cpu: f64,
+67    #[default(60.0)]
+68    pub warning_cpu: f64,
+69    #[default(90.0)]
+70    pub critical_cpu: f64,
+71}
+72
+73pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+74    let mut actions = api.get_actions()?;
+75    api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
+76
+77    let mut format = config.format.with_default(" $icon $utilization ")?;
+78    let mut format_alt = match &config.format_alt {
+79        Some(f) => Some(f.with_default("")?),
+80        None => None,
+81    };
+82
+83    // Store previous /proc/stat state
+84    let mut cputime = read_proc_stat().await?;
+85    let cores = cputime.1.len();
+86
+87    if cores == 0 {
+88        return Err(Error::new("/proc/stat reported zero cores"));
+89    }
+90
+91    let mut timer = config.interval.timer();
+92
+93    loop {
+94        let freqs = read_frequencies().await?;
+95
+96        // Compute utilizations
+97        let new_cputime = read_proc_stat().await?;
+98        let utilization_avg = new_cputime.0.utilization(cputime.0);
+99        let mut utilizations = Vec::new();
+100        if new_cputime.1.len() != cores {
+101            return Err(Error::new("new cputime length is incorrect"));
+102        }
+103        for i in 0..cores {
+104            utilizations.push(new_cputime.1[i].utilization(cputime.1[i]));
+105        }
+106        cputime = new_cputime;
+107
+108        // Create barchart indicating per-core utilization
+109        let mut barchart = String::new();
+110        const BOXCHARS: &[char] = &['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
+111        for utilization in &utilizations {
+112            barchart.push(BOXCHARS[(7.5 * utilization) as usize]);
+113        }
+114
+115        // Read boost state on intel CPUs
+116        let boost = boost_status().await.map(|status| match status {
+117            true => "cpu_boost_on",
+118            false => "cpu_boost_off",
+119        });
+120
+121        let mut values = map!(
+122            "icon" => Value::icon_progression("cpu", utilization_avg),
+123            "barchart" => Value::text(barchart),
+124            "utilization" => Value::percents(utilization_avg * 100.),
+125            [if !freqs.is_empty()] "frequency" => Value::hertz(freqs.iter().sum::<f64>() / (freqs.len() as f64)),
+126            [if !freqs.is_empty()] "max_frequency" => Value::hertz(freqs.iter().copied().max_by(f64::total_cmp).unwrap()),
+127        );
+128        boost.map(|b| values.insert("boost".into(), Value::icon(b)));
+129        for (i, freq) in freqs.iter().enumerate() {
+130            values.insert(format!("frequency{}", i + 1).into(), Value::hertz(*freq));
+131        }
+132        for (i, utilization) in utilizations.iter().enumerate() {
+133            values.insert(
+134                format!("utilization{}", i + 1).into(),
+135                Value::percents(utilization * 100.),
+136            );
+137        }
+138
+139        let mut widget = Widget::new().with_format(format.clone());
+140        widget.set_values(values);
+141        widget.state = match utilization_avg * 100. {
+142            x if x > config.critical_cpu => State::Critical,
+143            x if x > config.warning_cpu => State::Warning,
+144            x if x > config.info_cpu => State::Info,
+145            _ => State::Idle,
+146        };
+147        api.set_widget(widget)?;
+148
+149        loop {
+150            select! {
+151                _ = timer.tick() => break,
+152                _ = api.wait_for_update_request() => break,
+153                Some(action) = actions.recv() => match action.as_ref() {
+154                    "toggle_format" => {
+155                        if let Some(ref mut format_alt) = format_alt {
+156                            std::mem::swap(format_alt, &mut format);
+157                            break;
+158                        }
+159                    }
+160                    _ => (),
+161                }
+162            }
+163        }
+164    }
+165}
+166
+167// Read frequencies (read in MHz, store in Hz)
+168async fn read_frequencies() -> Result<Vec<f64>> {
+169    let mut freqs = Vec::with_capacity(32);
+170
+171    let file = File::open("/proc/cpuinfo")
+172        .await
+173        .error("failed to read /proc/cpuinfo")?;
+174    let mut file = BufReader::new(file);
+175
+176    let mut line = String::new();
+177    while file
+178        .read_line(&mut line)
+179        .await
+180        .error("failed to read /proc/cpuinfo")?
+181        != 0
+182    {
+183        if line.starts_with("cpu MHz") {
+184            let slice = line
+185                .trim_end()
+186                .trim_start_matches(|c: char| !c.is_ascii_digit());
+187            freqs.push(f64::from_str(slice).error("failed to parse /proc/cpuinfo")? * 1e6);
+188        }
+189        line.clear();
+190    }
+191
+192    Ok(freqs)
+193}
+194
+195#[derive(Debug, Clone, Copy)]
+196struct CpuTime {
+197    idle: u64,
+198    non_idle: u64,
+199}
+200
+201impl CpuTime {
+202    fn from_str(s: &str) -> Option<Self> {
+203        let mut s = s.trim().split_ascii_whitespace();
+204        let user = u64::from_str(s.next()?).ok()?;
+205        let nice = u64::from_str(s.next()?).ok()?;
+206        let system = u64::from_str(s.next()?).ok()?;
+207        let idle = u64::from_str(s.next()?).ok()?;
+208        let iowait = u64::from_str(s.next()?).ok()?;
+209        let irq = u64::from_str(s.next()?).ok()?;
+210        let softirq = u64::from_str(s.next()?).ok()?;
+211
+212        Some(Self {
+213            idle: idle + iowait,
+214            non_idle: user + nice + system + irq + softirq,
+215        })
+216    }
+217
+218    fn utilization(&self, old: Self) -> f64 {
+219        let elapsed = (self.idle + self.non_idle).saturating_sub(old.idle + old.non_idle);
+220        if elapsed == 0 {
+221            0.0
+222        } else {
+223            ((self.non_idle - old.non_idle) as f64 / elapsed as f64).clamp(0., 1.)
+224        }
+225    }
+226}
+227
+228async fn read_proc_stat() -> Result<(CpuTime, Vec<CpuTime>)> {
+229    let mut utilizations = Vec::with_capacity(32);
+230    let mut total = None;
+231
+232    let file = File::open("/proc/stat")
+233        .await
+234        .error("failed to read /proc/stat")?;
+235    let mut file = BufReader::new(file);
+236
+237    let mut line = String::new();
+238    while file
+239        .read_line(&mut line)
+240        .await
+241        .error("failed to read /proc/stat")?
+242        != 0
+243    {
+244        // Total time
+245        let data = line.trim_start_matches(|c: char| !c.is_ascii_whitespace());
+246        if line.starts_with("cpu ") {
+247            total = Some(CpuTime::from_str(data).error("failed to parse /proc/stat")?);
+248        } else if line.starts_with("cpu") {
+249            utilizations.push(CpuTime::from_str(data).error("failed to parse /proc/stat")?);
+250        }
+251        line.clear();
+252    }
+253
+254    Ok((total.error("failed to parse /proc/stat")?, utilizations))
+255}
+256
+257/// Read the cpu turbo boost status from kernel sys interface
+258/// or intel pstate interface
+259async fn boost_status() -> Option<bool> {
+260    if let Ok(boost) = read_file(CPU_BOOST_PATH).await {
+261        Some(boost.starts_with('1'))
+262    } else if let Ok(no_turbo) = read_file(CPU_NO_TURBO_PATH).await {
+263        Some(no_turbo.starts_with('0'))
+264    } else {
+265        None
+266    }
+267}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/custom.rs.html b/src/i3status_rs/blocks/custom.rs.html new file mode 100644 index 0000000000..c5aa0ee3bd --- /dev/null +++ b/src/i3status_rs/blocks/custom.rs.html @@ -0,0 +1,297 @@ +custom.rs - source

i3status_rs/blocks/
custom.rs

1//! The output of a custom shell command
+2//!
+3//! For further customisation, use the `json` option and have the shell command output valid JSON in the schema below:
+4//! ```json
+5//! {"icon": "...", "state": "...", "text": "...", "short_text": "..."}
+6//! ```
+7//! `icon` is optional (default "")
+8//! `state` is optional, it may be Idle, Info, Good, Warning, Critical (default Idle)
+9//! `short_text` is optional.
+10//!
+11//! # Configuration
+12//!
+13//! Key | Values | Default
+14//! ----|--------|--------
+15//! `format` | A string to customise the output of this block. See below for available placeholders. | <code>\"{ $icon\|} $text.pango-str() \"</code>
+16//! `command` | Shell command to execute & display | `None`
+17//! `persistent` | Run command in the background; update display for each output line of the command | `false`
+18//! `cycle` | Commands to execute and change when the button is clicked | `None`
+19//! `interval` | Update interval in seconds (or "once" to update only once) | `10`
+20//! `json` | Use JSON from command output to format the block. If the JSON is not valid, the block will error out. | `false`
+21//! `watch_files` | Watch files to trigger update on file modification. Supports path expansions e.g. `~`. | `None`
+22//! `hide_when_empty` | Hides the block when the command output (or json text field) is empty | `false`
+23//! `shell` | Specify the shell to use when running commands | `$SHELL` if set, otherwise fallback to `sh`
+24//!
+25//! Placeholder      | Value                                                      | Type   | Unit
+26//! -----------------|------------------------------------------------------------|--------|---------------
+27//! `icon`           | Value of icon field from JSON output when it's non-empty   | Icon   | -
+28//! `text`           | Output of the script or text field from JSON output        | Text   |
+29//! `short_text`     | short_text field from JSON output                          | Text   |
+30//!
+31//! Action  | Default button
+32//! --------|---------------
+33//! `cycle` | Left
+34//!
+35//! # Examples
+36//!
+37//! Display temperature, update every 10 seconds:
+38//!
+39//! ```toml
+40//! [[block]]
+41//! block = "custom"
+42//! command = ''' cat /sys/class/thermal/thermal_zone0/temp | awk '{printf("%.1f\n",$1/1000)}' '''
+43//! ```
+44//!
+45//! Cycle between "ON" and "OFF", update every 1 second, run next cycle command when block is clicked:
+46//!
+47//! ```toml
+48//! [[block]]
+49//! block = "custom"
+50//! cycle = ["echo ON", "echo OFF"]
+51//! interval = 1
+52//! [[block.click]]
+53//! button = "left"
+54//! action = "cycle"
+55//! ```
+56//!
+57//! Use JSON output:
+58//!
+59//! ```toml
+60//! [[block]]
+61//! block = "custom"
+62//! command = "echo '{\"icon\":\"weather_thunder\",\"state\":\"Critical\", \"text\": \"Danger!\"}'"
+63//! json = true
+64//! ```
+65//!
+66//! Display kernel, update the block only once:
+67//!
+68//! ```toml
+69//! [[block]]
+70//! block = "custom"
+71//! command = "uname -r"
+72//! interval = "once"
+73//! ```
+74//!
+75//! Display the screen brightness on an intel machine and update this only when `pkill -SIGRTMIN+4 i3status-rs` is called:
+76//!
+77//! ```toml
+78//! [[block]]
+79//! block = "custom"
+80//! command = ''' cat /sys/class/backlight/intel_backlight/brightness | awk '{print $1}' '''
+81//! signal = 4
+82//! interval = "once"
+83//! ```
+84//!
+85//! Update block when one or more specified files are modified:
+86//!
+87//! ```toml
+88//! [[block]]
+89//! block = "custom"
+90//! command = "cat custom_status"
+91//! watch_files = ["custom_status"]
+92//! interval = "once"
+93//! ```
+94//!
+95//! # TODO:
+96//! - Use `shellexpand`
+97
+98use crate::formatting::Format;
+99
+100use super::prelude::*;
+101use inotify::{Inotify, WatchMask};
+102use std::process::Stdio;
+103use tokio::io::{self, BufReader};
+104use tokio::process::Command;
+105
+106#[derive(Deserialize, Debug, SmartDefault)]
+107#[serde(deny_unknown_fields, default)]
+108pub struct Config {
+109    pub format: FormatConfig,
+110    pub command: Option<String>,
+111    pub persistent: bool,
+112    pub cycle: Option<Vec<String>>,
+113    #[default(10.into())]
+114    pub interval: Seconds,
+115    pub json: bool,
+116    pub hide_when_empty: bool,
+117    pub shell: Option<String>,
+118    pub watch_files: Vec<ShellString>,
+119}
+120
+121async fn update_bar(
+122    stdout: &str,
+123    hide_when_empty: bool,
+124    json: bool,
+125    api: &CommonApi,
+126    format: Format,
+127) -> Result<()> {
+128    let mut widget = Widget::new().with_format(format);
+129
+130    let text_empty;
+131
+132    if json {
+133        match serde_json::from_str::<Input>(stdout).error("Invalid JSON") {
+134            Ok(input) => {
+135                text_empty = input.text.is_empty();
+136                widget.set_values(map! {
+137                    "text" => Value::text(input.text),
+138                    [if !input.icon.is_empty()] "icon" => Value::icon(input.icon),
+139                    [if let Some(t) = input.short_text] "short_text" => Value::text(t)
+140                });
+141                widget.state = input.state;
+142            }
+143            Err(error) => return api.set_error(error),
+144        }
+145    } else {
+146        text_empty = stdout.is_empty();
+147        widget.set_values(map!("text" => Value::text(stdout.into())));
+148    }
+149
+150    if text_empty && hide_when_empty {
+151        api.hide()
+152    } else {
+153        api.set_widget(widget)
+154    }
+155}
+156
+157pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+158    api.set_default_actions(&[(MouseButton::Left, None, "cycle")])?;
+159
+160    let format = config.format.with_defaults(
+161        "{ $icon|} $text.pango-str() ",
+162        "{ $icon|} $short_text.pango-str() |",
+163    )?;
+164
+165    let mut timer = config.interval.timer();
+166
+167    type FileStream = Pin<Box<dyn Stream<Item = io::Result<inotify::EventOwned>> + Send + Sync>>;
+168    let mut file_updates: FileStream = match config.watch_files.as_slice() {
+169        [] => Box::pin(futures::stream::pending()),
+170        files => {
+171            let notify = Inotify::init().error("Failed to start inotify")?;
+172            let mut watches = notify.watches();
+173            for file in files {
+174                let file = file.expand()?;
+175                watches
+176                    .add(
+177                        &*file,
+178                        WatchMask::MODIFY
+179                            | WatchMask::CLOSE_WRITE
+180                            | WatchMask::DELETE
+181                            | WatchMask::MOVE,
+182                    )
+183                    .error("Failed to add file watch")?;
+184            }
+185            Box::pin(
+186                notify
+187                    .into_event_stream([0; 1024])
+188                    .error("Failed to create event stream")?,
+189            )
+190        }
+191    };
+192
+193    let shell = config
+194        .shell
+195        .clone()
+196        .or_else(|| std::env::var("SHELL").ok())
+197        .unwrap_or_else(|| "sh".to_string());
+198
+199    if config.persistent {
+200        let mut process = Command::new(&shell)
+201            .args([
+202                "-c",
+203                config
+204                    .command
+205                    .as_deref()
+206                    .error("'command' must be specified when 'persistent' is set")?,
+207            ])
+208            .stdout(Stdio::piped())
+209            .stdin(Stdio::null())
+210            .kill_on_drop(true)
+211            .spawn()
+212            .error("failed to run command")?;
+213
+214        let stdout = process
+215            .stdout
+216            .take()
+217            .expect("child did not have a handle to stdout");
+218        let mut reader = BufReader::new(stdout).lines();
+219
+220        tokio::spawn(async move {
+221            let _ = process.wait().await;
+222        });
+223
+224        loop {
+225            let line = reader
+226                .next_line()
+227                .await
+228                .error("error reading line from child process")?
+229                .error("child process exited unexpectedly")?;
+230            update_bar(
+231                &line,
+232                config.hide_when_empty,
+233                config.json,
+234                api,
+235                format.clone(),
+236            )
+237            .await?;
+238        }
+239    } else {
+240        let mut actions = api.get_actions()?;
+241
+242        let mut cycle = config
+243            .cycle
+244            .clone()
+245            .or_else(|| config.command.clone().map(|cmd| vec![cmd]))
+246            .error("either 'command' or 'cycle' must be specified")?
+247            .into_iter()
+248            .cycle();
+249        let mut cmd = cycle.next().unwrap();
+250
+251        loop {
+252            // Run command
+253            let output = Command::new(&shell)
+254                .args(["-c", &cmd])
+255                .stdin(Stdio::null())
+256                .output()
+257                .await
+258                .error("failed to run command")?;
+259            let stdout = std::str::from_utf8(&output.stdout)
+260                .error("the output of command is invalid UTF-8")?
+261                .trim();
+262
+263            update_bar(
+264                stdout,
+265                config.hide_when_empty,
+266                config.json,
+267                api,
+268                format.clone(),
+269            )
+270            .await?;
+271
+272            loop {
+273                select! {
+274                    _ = timer.tick() => break,
+275                    _ = file_updates.next() => break,
+276                    _ = api.wait_for_update_request() => break,
+277                    Some(action) = actions.recv() => match action.as_ref() {
+278                        "cycle" => {
+279                            cmd = cycle.next().unwrap();
+280                            break;
+281                        }
+282                        _ => (),
+283                    }
+284                }
+285            }
+286        }
+287    }
+288}
+289
+290#[derive(Deserialize, Debug, Default)]
+291#[serde(default)]
+292struct Input {
+293    icon: String,
+294    state: State,
+295    text: String,
+296    short_text: Option<String>,
+297}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/custom_dbus.rs.html b/src/i3status_rs/blocks/custom_dbus.rs.html new file mode 100644 index 0000000000..269a38a9f2 --- /dev/null +++ b/src/i3status_rs/blocks/custom_dbus.rs.html @@ -0,0 +1,163 @@ +custom_dbus.rs - source

i3status_rs/blocks/
custom_dbus.rs

1//! A block controlled by the DBus
+2//!
+3//! This block creates a new DBus object in `rs.i3status` service. This object implements
+4//! `rs.i3status.custom` interface which allows you to set block's icon, text and state.
+5//!
+6//! Output of `busctl --user introspect rs.i3status /<path> rs.i3status.custom`:
+7//! ```text
+8//! NAME                                TYPE      SIGNATURE RESULT/VALUE FLAGS
+9//! rs.i3status.custom                  interface -         -            -
+10//! .SetIcon                            method    s         s            -
+11//! .SetState                           method    s         s            -
+12//! .SetText                            method    ss        s            -
+13//! ```
+14//!
+15//! # Configuration
+16//!
+17//! Key | Values | Default
+18//! ----|--------|--------
+19//! `format` | A string to customise the output of this block. | <code>\"{ $icon\|}{ $text.pango-str()\|} \"</code>
+20//!
+21//! Placeholder  | Value                                  | Type   | Unit
+22//! -------------|-------------------------------------------------------------------|--------|---------------
+23//! `icon`       | Value of icon set via `SetIcon` if the value is non-empty string. | Icon   | -
+24//! `text`       | Value of the first string from SetText                            | Text   | -
+25//! `short_text` | Value of the second string from SetText                           | Text   | -
+26//!
+27//! # Example
+28//!
+29//! Config:
+30//! ```toml
+31//! [[block]]
+32//! block = "custom_dbus"
+33//! path = "/my_path"
+34//! ```
+35//!
+36//! Usage:
+37//! ```sh
+38//! # set full text to 'hello' and short text to 'hi'
+39//! busctl --user call rs.i3status /my_path rs.i3status.custom SetText ss hello hi
+40//! # set icon to 'music'
+41//! busctl --user call rs.i3status /my_path rs.i3status.custom SetIcon s music
+42//! # set state to 'good'
+43//! busctl --user call rs.i3status /my_path rs.i3status.custom SetState s good
+44//! ```
+45//!
+46//! Because it's impossible to publish objects to the same name from different
+47//! processes, having multiple dbus blocks in different bars won't work. As a workaround,
+48//! you can set the env var `I3RS_DBUS_NAME` to set the interface a bar works on to
+49//! differentiate between different processes. For example, setting this to 'top', will allow you
+50//! to use `rs.i3status.top`.
+51//!
+52//! # TODO
+53//! - Send a signal on click?
+54
+55use super::prelude::*;
+56use std::env;
+57use zbus::fdo;
+58
+59// Share DBus connection between multiple block instances
+60static DBUS_CONNECTION: tokio::sync::OnceCell<Result<zbus::Connection>> =
+61    tokio::sync::OnceCell::const_new();
+62
+63const DBUS_NAME: &str = "rs.i3status";
+64
+65#[derive(Deserialize, Debug)]
+66#[serde(deny_unknown_fields)]
+67pub struct Config {
+68    #[serde(default)]
+69    pub format: FormatConfig,
+70    pub path: String,
+71}
+72
+73struct Block {
+74    widget: Widget,
+75    api: CommonApi,
+76    icon: Option<String>,
+77    text: Option<String>,
+78    short_text: Option<String>,
+79}
+80
+81fn block_values(block: &Block) -> HashMap<Cow<'static, str>, Value> {
+82    map! {
+83        [if let Some(icon) = &block.icon] "icon" => Value::icon(icon.to_string()),
+84        [if let Some(text) = &block.text] "text" => Value::text(text.to_string()),
+85        [if let Some(short_text) = &block.short_text] "short_text" => Value::text(short_text.to_string()),
+86    }
+87}
+88
+89#[zbus::interface(name = "rs.i3status.custom")]
+90impl Block {
+91    async fn set_icon(&mut self, icon: &str) -> fdo::Result<()> {
+92        self.icon = if icon.is_empty() {
+93            None
+94        } else {
+95            Some(icon.to_string())
+96        };
+97        self.widget.set_values(block_values(self));
+98        self.api.set_widget(self.widget.clone())?;
+99        Ok(())
+100    }
+101
+102    async fn set_text(&mut self, full: String, short: String) -> fdo::Result<()> {
+103        self.text = Some(full);
+104        self.short_text = Some(short);
+105        self.widget.set_values(block_values(self));
+106        self.api.set_widget(self.widget.clone())?;
+107        Ok(())
+108    }
+109
+110    async fn set_state(&mut self, state: &str) -> fdo::Result<()> {
+111        self.widget.state = match state {
+112            "idle" => State::Idle,
+113            "info" => State::Info,
+114            "good" => State::Good,
+115            "warning" => State::Warning,
+116            "critical" => State::Critical,
+117            _ => return Err(Error::new(format!("'{state}' is not a valid state")).into()),
+118        };
+119        self.api.set_widget(self.widget.clone())?;
+120        Ok(())
+121    }
+122}
+123
+124pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+125    let widget = Widget::new().with_format(config.format.with_defaults(
+126        "{ $icon|}{ $text.pango-str()|} ",
+127        "{ $icon|} $short_text.pango-str() | ",
+128    )?);
+129
+130    let dbus_conn = DBUS_CONNECTION
+131        .get_or_init(dbus_conn)
+132        .await
+133        .as_ref()
+134        .map_err(Clone::clone)?;
+135    dbus_conn
+136        .object_server()
+137        .at(
+138            config.path.clone(),
+139            Block {
+140                widget,
+141                api: api.clone(),
+142                icon: None,
+143                text: None,
+144                short_text: None,
+145            },
+146        )
+147        .await
+148        .error("Failed to setup DBus server")?;
+149    Ok(())
+150}
+151
+152async fn dbus_conn() -> Result<zbus::Connection> {
+153    let dbus_interface_name = match env::var("I3RS_DBUS_NAME") {
+154        Ok(v) => format!("{DBUS_NAME}.{v}"),
+155        Err(_) => DBUS_NAME.to_string(),
+156    };
+157
+158    let conn = new_dbus_connection().await?;
+159    conn.request_name(dbus_interface_name)
+160        .await
+161        .error("Failed to request DBus name")?;
+162    Ok(conn)
+163}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/disk_iostats.rs.html b/src/i3status_rs/blocks/disk_iostats.rs.html new file mode 100644 index 0000000000..b6ca19bf2d --- /dev/null +++ b/src/i3status_rs/blocks/disk_iostats.rs.html @@ -0,0 +1,177 @@ +disk_iostats.rs - source

i3status_rs/blocks/
disk_iostats.rs

1//! Disk I/O statistics
+2//!
+3//! # Configuration
+4//!
+5//! Key | Values | Default
+6//! ----|--------|--------
+7//! `device` | Block device name to monitor (as specified in `/dev/`) | If not set, device will be automatically selected every `interval`
+8//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $speed_read.eng(prefix:K) $speed_write.eng(prefix:K) "`
+9//! `interval` | Update interval in seconds | `2`
+10//! `missing_format` | Same as `format` but for when the device is missing | `" × "`
+11//!
+12//! Placeholder | Value | Type   | Unit
+13//! ------------|-------|--------|-------
+14//! `icon` | A static icon | Icon | -
+15//! `device` | The name of device | Text | -
+16//! `speed_read` | Read speed | Number | Bytes per second
+17//! `speed_write` | Write speed | Number | Bytes per second
+18//!
+19//! # Examples
+20//!
+21//! ```toml
+22//! [[block]]
+23//! block = "disk_iostats"
+24//! device = "sda"
+25//! format = " $icon $speed_write.eng(prefix:K) "
+26//! ```
+27//!
+28//! # Icons Used
+29//!
+30//! - `disk_drive`
+31
+32use super::prelude::*;
+33use crate::util::read_file;
+34use libc::c_ulong;
+35use std::ops;
+36use std::path::Path;
+37use std::time::Instant;
+38use tokio::fs::read_dir;
+39
+40/// Path for block devices
+41const BLOCK_DEVICES_PATH: &str = "/sys/class/block";
+42
+43#[derive(Deserialize, Debug, SmartDefault)]
+44#[serde(deny_unknown_fields, default)]
+45pub struct Config {
+46    pub device: Option<String>,
+47    #[default(2.into())]
+48    pub interval: Seconds,
+49    pub format: FormatConfig,
+50    pub missing_format: FormatConfig,
+51}
+52
+53pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+54    let format = config
+55        .format
+56        .with_default(" $icon $speed_read.eng(prefix:K) $speed_write.eng(prefix:K) ")?;
+57    let missing_format = config.missing_format.with_default(" × ")?;
+58
+59    let mut timer = config.interval.timer();
+60    let mut old_stats = None;
+61    let mut stats_timer = Instant::now();
+62
+63    loop {
+64        let mut device = config.device.clone();
+65        if device.is_none() {
+66            device = find_device().await?;
+67        }
+68        match device {
+69            None => {
+70                api.set_widget(Widget::new().with_format(missing_format.clone()))?;
+71            }
+72            Some(device) => {
+73                let mut widget = Widget::new();
+74
+75                widget.set_format(format.clone());
+76
+77                let new_stats = read_stats(&device).await?;
+78                let sector_size = read_sector_size(&device).await?;
+79
+80                let mut speed_read = 0.0;
+81                let mut speed_write = 0.0;
+82                if let Some(old_stats) = old_stats {
+83                    let diff = new_stats - old_stats;
+84                    let elapsed = stats_timer.elapsed().as_secs_f64();
+85                    stats_timer = Instant::now();
+86                    let size_read = diff.sectors_read as u64 * sector_size;
+87                    let size_written = diff.sectors_written as u64 * sector_size;
+88                    speed_read = size_read as f64 / elapsed;
+89                    speed_write = size_written as f64 / elapsed;
+90                };
+91                old_stats = Some(new_stats);
+92
+93                widget.set_values(map! {
+94                    "icon" => Value::icon("disk_drive"),
+95                    "speed_read" => Value::bytes(speed_read),
+96                    "speed_write" => Value::bytes(speed_write),
+97                    "device" => Value::text(device),
+98                });
+99
+100                api.set_widget(widget)?;
+101            }
+102        }
+103
+104        select! {
+105            _ = timer.tick() => continue,
+106            _ = api.wait_for_update_request() => continue,
+107        }
+108    }
+109}
+110
+111async fn find_device() -> Result<Option<String>> {
+112    let mut sysfs_dir = read_dir(BLOCK_DEVICES_PATH)
+113        .await
+114        .error("Failed to open /sys/class/block directory")?;
+115    while let Some(dir) = sysfs_dir
+116        .next_entry()
+117        .await
+118        .error("Failed to read /sys/class/block directory")?
+119    {
+120        let path = dir.path();
+121        if path.join("device").exists() {
+122            return Ok(Some(
+123                dir.file_name()
+124                    .into_string()
+125                    .map_err(|_| Error::new("Invalid device filename"))?,
+126            ));
+127        }
+128    }
+129
+130    Ok(None)
+131}
+132
+133#[derive(Debug, Default, Clone, Copy)]
+134struct Stats {
+135    sectors_read: c_ulong,
+136    sectors_written: c_ulong,
+137}
+138
+139impl ops::Sub for Stats {
+140    type Output = Self;
+141
+142    fn sub(mut self, rhs: Self) -> Self::Output {
+143        self.sectors_read = self.sectors_read.wrapping_sub(rhs.sectors_read);
+144        self.sectors_written = self.sectors_written.wrapping_sub(rhs.sectors_written);
+145        self
+146    }
+147}
+148
+149async fn read_stats(device: &str) -> Result<Stats> {
+150    let raw = read_file(Path::new(BLOCK_DEVICES_PATH).join(device).join("stat"))
+151        .await
+152        .error("Failed to read stat file")?;
+153    let fields: Vec<&str> = raw.split_whitespace().collect();
+154    Ok(Stats {
+155        sectors_read: fields
+156            .get(2)
+157            .error("Missing sectors read field")?
+158            .parse()
+159            .error("Failed to parse sectors read")?,
+160        sectors_written: fields
+161            .get(6)
+162            .error("Missing sectors written field")?
+163            .parse()
+164            .error("Failed to parse sectors written")?,
+165    })
+166}
+167
+168async fn read_sector_size(device: &str) -> Result<u64> {
+169    let raw = read_file(
+170        Path::new(BLOCK_DEVICES_PATH)
+171            .join(device)
+172            .join("queue/hw_sector_size"),
+173    )
+174    .await
+175    .error("Failed to read HW sector size")?;
+176    raw.parse::<u64>().error("Failed to parse HW sector size")
+177}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/disk_space.rs.html b/src/i3status_rs/blocks/disk_space.rs.html new file mode 100644 index 0000000000..876e72a464 --- /dev/null +++ b/src/i3status_rs/blocks/disk_space.rs.html @@ -0,0 +1,207 @@ +disk_space.rs - source

i3status_rs/blocks/
disk_space.rs

1//! Disk usage statistics
+2//!
+3//! # Configuration
+4//!
+5//! Key | Values | Default
+6//! ----|--------|--------
+7//! `path` | Path to collect information from. Supports path expansions e.g. `~`. | `"/"`
+8//! `interval` | Update time in seconds | `20`
+9//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $available "`
+10//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None`
+11//! `warning` | A value which will trigger warning block state | `20.0`
+12//! `alert` | A value which will trigger critical block state | `10.0`
+13//! `info_type` | Determines which information will affect the block state. Possible values are `"available"`, `"free"` and `"used"` | `"available"`
+14//! `alert_unit` | The unit of `alert` and `warning` options. If not set, percents are used. Possible values are `"B"`, `"KB"`, `"KiB"`, `"MB"`, `"MiB"`, `"GB"`, `"Gib"`, `"TB"` and `"TiB"` | `None`
+15//!
+16//! Placeholder  | Value                                                              | Type   | Unit
+17//! -------------|--------------------------------------------------------------------|--------|-------
+18//! `icon`       | A static icon                                                      | Icon   | -
+19//! `path`       | The value of `path` option                                         | Text   | -
+20//! `percentage` | Free or used percentage. Depends on `info_type`                    | Number | %
+21//! `total`      | Total disk space                                                   | Number | Bytes
+22//! `used`       | Used disk space                                                    | Number | Bytes
+23//! `free`       | Free disk space                                                    | Number | Bytes
+24//! `available`  | Available disk space (free disk space minus reserved system space) | Number | Bytes
+25//!
+26//! Action          | Description                               | Default button
+27//! ----------------|-------------------------------------------|---------------
+28//! `toggle_format` | Toggles between `format` and `format_alt` | Left
+29//!
+30//! # Examples
+31//!
+32//! ```toml
+33//! [[block]]
+34//! block = "disk_space"
+35//! info_type = "available"
+36//! alert_unit = "GB"
+37//! alert = 10.0
+38//! warning = 15.0
+39//! format = " $icon $available "
+40//! format_alt = " $icon $available / $total "
+41//! ```
+42//!
+43//! Update block on right click:
+44//!
+45//! ```toml
+46//! [[block]]
+47//! block = "disk_space"
+48//! [[block.click]]
+49//! button = "right"
+50//! update = true
+51//! ```
+52//!
+53//! Show the block only if less than 10GB is available:
+54//!
+55//! ```toml
+56//! [[block]]
+57//! block = "disk_space"
+58//! format = " $free.eng(range:..10e9) |"
+59//! ```
+60//!
+61//! # Icons Used
+62//! - `disk_drive`
+63
+64// make_log_macro!(debug, "disk_space");
+65
+66use super::prelude::*;
+67use crate::formatting::prefix::Prefix;
+68use nix::sys::statvfs::statvfs;
+69
+70#[derive(Copy, Clone, Debug, Deserialize, SmartDefault)]
+71#[serde(rename_all = "lowercase")]
+72pub enum InfoType {
+73    #[default]
+74    Available,
+75    Free,
+76    Used,
+77}
+78
+79#[derive(Deserialize, Debug, SmartDefault)]
+80#[serde(deny_unknown_fields, default)]
+81pub struct Config {
+82    #[default("/".into())]
+83    pub path: ShellString,
+84    pub info_type: InfoType,
+85    pub format: FormatConfig,
+86    pub format_alt: Option<FormatConfig>,
+87    pub alert_unit: Option<String>,
+88    #[default(20.into())]
+89    pub interval: Seconds,
+90    #[default(20.0)]
+91    pub warning: f64,
+92    #[default(10.0)]
+93    pub alert: f64,
+94}
+95
+96pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+97    let mut actions = api.get_actions()?;
+98    api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
+99
+100    let mut format = config.format.with_default(" $icon $available ")?;
+101    let mut format_alt = match &config.format_alt {
+102        Some(f) => Some(f.with_default("")?),
+103        None => None,
+104    };
+105
+106    let unit = match config.alert_unit.as_deref() {
+107        // Decimal
+108        Some("TB") => Some(Prefix::Tera),
+109        Some("GB") => Some(Prefix::Giga),
+110        Some("MB") => Some(Prefix::Mega),
+111        Some("KB") => Some(Prefix::Kilo),
+112        // Binary
+113        Some("TiB") => Some(Prefix::Tebi),
+114        Some("GiB") => Some(Prefix::Gibi),
+115        Some("MiB") => Some(Prefix::Mebi),
+116        Some("KiB") => Some(Prefix::Kibi),
+117        // Byte
+118        Some("B") => Some(Prefix::One),
+119        // Unknown
+120        Some(x) => return Err(Error::new(format!("Unknown unit: '{x}'"))),
+121        None => None,
+122    };
+123
+124    let path = config.path.expand()?;
+125
+126    let mut timer = config.interval.timer();
+127
+128    loop {
+129        let mut widget = Widget::new().with_format(format.clone());
+130
+131        let statvfs = statvfs(&*path).error("failed to retrieve statvfs")?;
+132
+133        // Casting to be compatible with 32-bit systems
+134        #[allow(clippy::unnecessary_cast)]
+135        let (total, used, available, free) = {
+136            let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64);
+137            let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64))
+138                * (statvfs.fragment_size() as u64);
+139            let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64);
+140            let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64);
+141            (total, used, available, free)
+142        };
+143
+144        let result = match config.info_type {
+145            InfoType::Available => available,
+146            InfoType::Free => free,
+147            InfoType::Used => used,
+148        } as f64;
+149
+150        let percentage = result / (total as f64) * 100.;
+151        widget.set_values(map! {
+152            "icon" => Value::icon("disk_drive"),
+153            "path" => Value::text(path.to_string()),
+154            "percentage" => Value::percents(percentage),
+155            "total" => Value::bytes(total as f64),
+156            "used" => Value::bytes(used as f64),
+157            "available" => Value::bytes(available as f64),
+158            "free" => Value::bytes(free as f64),
+159        });
+160
+161        // Send percentage to alert check if we don't want absolute alerts
+162        let alert_val_in_config_units = match unit {
+163            Some(p) => p.apply(result),
+164            None => percentage,
+165        };
+166
+167        // Compute state
+168        widget.state = match config.info_type {
+169            InfoType::Used => {
+170                if alert_val_in_config_units >= config.alert {
+171                    State::Critical
+172                } else if alert_val_in_config_units >= config.warning {
+173                    State::Warning
+174                } else {
+175                    State::Idle
+176                }
+177            }
+178            InfoType::Free | InfoType::Available => {
+179                if alert_val_in_config_units <= config.alert {
+180                    State::Critical
+181                } else if alert_val_in_config_units <= config.warning {
+182                    State::Warning
+183                } else {
+184                    State::Idle
+185                }
+186            }
+187        };
+188
+189        api.set_widget(widget)?;
+190
+191        loop {
+192            select! {
+193                _ = timer.tick() => break,
+194                _ = api.wait_for_update_request() => break,
+195                Some(action) = actions.recv() => match action.as_ref() {
+196                    "toggle_format" => {
+197                        if let Some(format_alt) = &mut format_alt {
+198                            std::mem::swap(format_alt, &mut format);
+199                            break;
+200                        }
+201                    }
+202                    _ => (),
+203                }
+204            }
+205        }
+206    }
+207}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/docker.rs.html b/src/i3status_rs/blocks/docker.rs.html new file mode 100644 index 0000000000..9b40eac203 --- /dev/null +++ b/src/i3status_rs/blocks/docker.rs.html @@ -0,0 +1,94 @@ +docker.rs - source

i3status_rs/blocks/
docker.rs

1//! Local docker daemon status
+2//!
+3//! # Configuration
+4//!
+5//! Key | Values | Default
+6//! ----|--------|--------
+7//! `interval` | Update interval, in seconds. | `5`
+8//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $running.eng(w:1) "`
+9//! `socket_path` | The path to the docker socket. Supports path expansions e.g. `~`. | `"/var/run/docker.sock"`
+10//!
+11//! Key       | Value                          | Type   | Unit
+12//! ----------|--------------------------------|--------|-----
+13//! `icon`    | A static icon                  | Icon   | -
+14//! `total`   | Total containers on the host   | Number | -
+15//! `running` | Containers running on the host | Number | -
+16//! `stopped` | Containers stopped on the host | Number | -
+17//! `paused`  | Containers paused on the host  | Number | -
+18//! `images`  | Total images on the host       | Number | -
+19//!
+20//! # Example
+21//!
+22//! ```toml
+23//! [[block]]
+24//! block = "docker"
+25//! interval = 2
+26//! format = " $icon $running/$total "
+27//! ```
+28//!
+29//! # Icons Used
+30//!
+31//! - `docker`
+32
+33use super::prelude::*;
+34
+35#[derive(Deserialize, Debug, SmartDefault)]
+36#[serde(deny_unknown_fields, default)]
+37pub struct Config {
+38    #[default(5.into())]
+39    pub interval: Seconds,
+40    pub format: FormatConfig,
+41    #[default("/var/run/docker.sock".into())]
+42    pub socket_path: ShellString,
+43}
+44
+45pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+46    let format = config.format.with_default(" $icon $running.eng(w:1) ")?;
+47    let socket_path = config.socket_path.expand()?;
+48
+49    let client = reqwest::Client::builder()
+50        .unix_socket(&*socket_path)
+51        .build()
+52        .unwrap();
+53
+54    loop {
+55        let status: Status = client
+56            .get("http://api/info")
+57            .send()
+58            .await
+59            .error("Failed to get response")?
+60            .json()
+61            .await
+62            .error("Failed to deserialize JSON")?;
+63
+64        let mut widget = Widget::new().with_format(format.clone());
+65        widget.set_values(map! {
+66            "icon" => Value::icon("docker"),
+67            "total" =>   Value::number(status.total),
+68            "running" => Value::number(status.running),
+69            "paused" =>  Value::number(status.paused),
+70            "stopped" => Value::number(status.stopped),
+71            "images" =>  Value::number(status.images),
+72        });
+73        api.set_widget(widget)?;
+74
+75        select! {
+76            _ = sleep(config.interval.0) => (),
+77            _ = api.wait_for_update_request() => (),
+78        }
+79    }
+80}
+81
+82#[derive(Deserialize, Debug)]
+83struct Status {
+84    #[serde(rename = "Containers")]
+85    total: i64,
+86    #[serde(rename = "ContainersRunning")]
+87    running: i64,
+88    #[serde(rename = "ContainersStopped")]
+89    stopped: i64,
+90    #[serde(rename = "ContainersPaused")]
+91    paused: i64,
+92    #[serde(rename = "Images")]
+93    images: i64,
+94}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/external_ip.rs.html b/src/i3status_rs/blocks/external_ip.rs.html new file mode 100644 index 0000000000..a52bf82b1d --- /dev/null +++ b/src/i3status_rs/blocks/external_ip.rs.html @@ -0,0 +1,219 @@ +external_ip.rs - source

i3status_rs/blocks/
external_ip.rs

1//! External IP address and various information about it
+2//!
+3//! # Configuration
+4//!
+5//! Key | Values | Default
+6//! ----|--------|--------
+7//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $ip $country_flag "`
+8//! `interval` | Interval in seconds for automatic updates | `300`
+9//! `with_network_manager` | If 'true', listen for NetworkManager events and update the IP immediately if there was a change | `true`
+10//! `use_ipv4` | If 'true', use IPv4 for obtaining all info | `false`
+11//!
+12//!  Key | Value | Type | Unit
+13//! -----|-------|------|------
+14//! `ip` | The external IP address, as seen from a remote server | Text | -
+15//! `version` | IPv4 or IPv6 | Text | -
+16//! `city` | City name, such as "San Francisco" | Text | -
+17//! `region` | Region name, such as "California" | Text | -
+18//! `region_code` | Region code, such as "CA" for California | Text | -
+19//! `country` | Country code (2 letter, ISO 3166-1 alpha-2) | Text | -
+20//! `country_name` | Short country name | Text | -
+21//! `country_code` | Country code (2 letter, ISO 3166-1 alpha-2) | Text | -
+22//! `country_code_iso3` | Country code (3 letter, ISO 3166-1 alpha-3) | Text | -
+23//! `country_capital` | Capital of the country | Text | -
+24//! `country_tld` | Country specific TLD (top-level domain) | Text | -
+25//! `continent_code` | Continent code | Text | -
+26//! `in_eu` | Region code, such as "CA" | Flag | -
+27//! `postal` | ZIP / Postal code | Text | -
+28//! `latitude` | Latitude | Number | - (TODO: make degrees?)
+29//! `longitude` | Longitude | Number | - (TODO: make degrees?)
+30//! `timezone` | City | Text | -
+31//! `utc_offset` | UTC offset (with daylight saving time) as +HHMM or -HHMM (HH is hours, MM is minutes) | Text | -
+32//! `country_calling_code` | Country calling code (dial in code, comma separated) | Text | -
+33//! `currency` | Currency code (ISO 4217) | Text | -
+34//! `currency_name` | Currency name | Text | -
+35//! `languages` | Languages spoken (comma separated 2 or 3 letter ISO 639 code with optional hyphen separated country suffix) | Text | -
+36//! `country_area` | Area of the country (in sq km) | Number | -
+37//! `country_population` | Population of the country | Number | -
+38//! `timezone` | Time zone | Text | -
+39//! `org` | Organization | Text | -
+40//! `asn` | Autonomous system (AS) | Text | -
+41//! `country_flag` | Flag of the country | Text (glyph) | -
+42//!
+43//! # Example
+44//!
+45//! ```toml
+46//! [[block]]
+47//! block = "external_ip"
+48//! format = " $ip $country_code "
+49//! ```
+50//!
+51//! # Notes
+52//! All the information comes from <https://ipapi.co/json/>
+53//! Check their documentation here: <https://ipapi.co/api/#complete-location5>
+54//!
+55//! The IP is queried, 1) When i3status-rs starts, 2) When a signal is received
+56//! on D-Bus about a network configuration change, 3) Every 5 minutes. This
+57//! periodic refresh exists to catch IP updates that don't trigger a notification,
+58//! for example due to a IP refresh at the router.
+59//!
+60//! Flags: They are not icons but unicode glyphs. You will need a font that
+61//! includes them. Tested with: <https://www.babelstone.co.uk/Fonts/Flags.html>
+62
+63use zbus::MatchRule;
+64
+65use super::prelude::*;
+66use crate::util::{country_flag_from_iso_code, new_system_dbus_connection};
+67
+68#[derive(Deserialize, Debug, SmartDefault)]
+69#[serde(deny_unknown_fields, default)]
+70pub struct Config {
+71    pub format: FormatConfig,
+72    #[default(300.into())]
+73    pub interval: Seconds,
+74    #[default(true)]
+75    pub with_network_manager: bool,
+76    #[default(false)]
+77    pub use_ipv4: bool,
+78}
+79
+80pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+81    let format = config.format.with_default(" $ip $country_flag ")?;
+82
+83    type UpdatesStream = Pin<Box<dyn Stream<Item = ()>>>;
+84    let mut stream: UpdatesStream = if config.with_network_manager {
+85        let dbus = new_system_dbus_connection().await?;
+86        let proxy = zbus::fdo::DBusProxy::new(&dbus)
+87            .await
+88            .error("Failed to create DBusProxy")?;
+89        proxy
+90            .add_match_rule(
+91                MatchRule::builder()
+92                    .msg_type(zbus::message::Type::Signal)
+93                    .path("/org/freedesktop/NetworkManager")
+94                    .and_then(|x| x.interface("org.freedesktop.DBus.Properties"))
+95                    .and_then(|x| x.member("PropertiesChanged"))
+96                    .unwrap()
+97                    .build(),
+98            )
+99            .await
+100            .error("Failed to add match")?;
+101        proxy
+102            .add_match_rule(
+103                MatchRule::builder()
+104                    .msg_type(zbus::message::Type::Signal)
+105                    .path_namespace("/org/freedesktop/NetworkManager/ActiveConnection")
+106                    .and_then(|x| x.interface("org.freedesktop.DBus.Properties"))
+107                    .and_then(|x| x.member("PropertiesChanged"))
+108                    .unwrap()
+109                    .build(),
+110            )
+111            .await
+112            .error("Failed to add match")?;
+113        proxy
+114            .add_match_rule(
+115                MatchRule::builder()
+116                    .msg_type(zbus::message::Type::Signal)
+117                    .path_namespace("/org/freedesktop/NetworkManager/IP4Config")
+118                    .and_then(|x| x.interface("org.freedesktop.DBus.Properties"))
+119                    .and_then(|x| x.member("PropertiesChanged"))
+120                    .unwrap()
+121                    .build(),
+122            )
+123            .await
+124            .error("Failed to add match")?;
+125        let stream: zbus::MessageStream = dbus.into();
+126        Box::pin(stream.map(|_| ()))
+127    } else {
+128        Box::pin(futures::stream::empty())
+129    };
+130
+131    let client = if config.use_ipv4 {
+132        &REQWEST_CLIENT_IPV4
+133    } else {
+134        &REQWEST_CLIENT
+135    };
+136
+137    loop {
+138        let fetch_info = || api.find_ip_location(client, Duration::from_secs(0));
+139        let info = fetch_info.retry(ExponentialBuilder::default()).await?;
+140
+141        let mut values = map! {
+142            "ip" => Value::text(info.ip),
+143            "city" => Value::text(info.city),
+144            "latitude" => Value::number(info.latitude),
+145            "longitude" => Value::number(info.longitude),
+146        };
+147
+148        macro_rules! map_push_if_some { ($($key:ident: $type:ident),* $(,)?) => {
+149            $({
+150                let key = stringify!($key);
+151                if let Some(value) = info.$key {
+152                    values.insert(key.into(), Value::$type(value));
+153                } else if format.contains_key(key) {
+154                    return Err(Error::new(format!(
+155                        "The format string contains '{key}', but the {key} field is not provided by {} (an api key may be required)",
+156                        api.locator_name()
+157                    )));
+158                }
+159            })*
+160        } }
+161
+162        map_push_if_some!(
+163            version: text,
+164            region: text,
+165            region_code: text,
+166            country: text,
+167            country_name: text,
+168            country_code_iso3: text,
+169            country_capital: text,
+170            country_tld: text,
+171            continent_code: text,
+172            postal: text,
+173            timezone: text,
+174            utc_offset: text,
+175            country_calling_code: text,
+176            currency: text,
+177            currency_name: text,
+178            languages: text,
+179            country_area: number,
+180            country_population: number,
+181            asn: text,
+182            org: text,
+183        );
+184
+185        if let Some(country_code) = info.country_code {
+186            values.insert(
+187                "country_flag".into(),
+188                Value::text(country_flag_from_iso_code(&country_code)),
+189            );
+190            values.insert("country_code".into(), Value::text(country_code));
+191        } else if format.contains_key("country_code") || format.contains_key("country_flag") {
+192            return Err(Error::new(format!(
+193                "The format string contains 'country_code' or 'country_flag', but the country_code field is not provided by {}",
+194                api.locator_name()
+195            )));
+196        }
+197
+198        if let Some(in_eu) = info.in_eu {
+199            if in_eu {
+200                values.insert("in_eu".into(), Value::flag());
+201            }
+202        } else if format.contains_key("in_eu") {
+203            return Err(Error::new(format!(
+204                "The format string contains 'in_eu', but the in_eu field is not provided by {}",
+205                api.locator_name()
+206            )));
+207        }
+208
+209        let mut widget = Widget::new().with_format(format.clone());
+210        widget.set_values(values);
+211        api.set_widget(widget)?;
+212
+213        select! {
+214            _ = sleep(config.interval.0) => (),
+215            _ = api.wait_for_update_request() => (),
+216            _ = stream.next_debounced() => ()
+217        }
+218    }
+219}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/focused_window.rs.html b/src/i3status_rs/blocks/focused_window.rs.html new file mode 100644 index 0000000000..8ffacb8d26 --- /dev/null +++ b/src/i3status_rs/blocks/focused_window.rs.html @@ -0,0 +1,111 @@ +focused_window.rs - source

i3status_rs/blocks/
focused_window.rs

1//! Currently focused window
+2//!
+3//! This block displays the title and/or the active marks (when used with `sway`/`i3`) of the currently
+4//! focused window. Supported WMs are: `sway`, `i3` and most wlroots-based compositors. See `driver`
+5//! option for more info.
+6//!
+7//! # Configuration
+8//!
+9//! Key | Values | Default
+10//! ----|--------|--------
+11//! `format` | A string to customise the output of this block. See below for available placeholders. | <code>\" $title.str(max_w:21) \|\"</code>
+12//! `driver` | Which driver to use. Available values: `sway_ipc` - for `i3` and `sway`, `wlr_toplevel_management` - for Wayland compositors that implement [wlr-foreign-toplevel-management-unstable-v1](https://gitlab.freedesktop.org/wlroots/wlr-protocols/-/blob/master/unstable/wlr-foreign-toplevel-management-unstable-v1.xml), `auto` - try to automatically guess which driver to use. | `"auto"`
+13//!
+14//! Placeholder     | Value                                                                 | Type | Unit
+15//! ----------------|-----------------------------------------------------------------------|------|-----
+16//! `title`         | Window's title (may be absent)                                        | Text | -
+17//! `marks`         | Window's marks (present only with sway/i3)                            | Text | -
+18//! `visible_marks` | Window's marks that do not start with `_` (present only with sway/i3) | Text | -
+19//!
+20//! # Example
+21//!
+22//! ```toml
+23//! [[block]]
+24//! block = "focused_window"
+25//! [block.format]
+26//! full = " $title.str(max_w:15) |"
+27//! short = " $title.str(max_w:10) |"
+28//! ```
+29//!
+30//! This example instead of hiding block when the window's title is empty displays "Missing"
+31//!
+32//! ```toml
+33//! [[block]]
+34//! block = "focused_window"
+35//! format = " $title.str(0,21) | Missing "
+36//! ```
+37
+38mod sway_ipc;
+39mod wlr_toplevel_management;
+40
+41use sway_ipc::SwayIpc;
+42use wlr_toplevel_management::WlrToplevelManagement;
+43
+44use super::prelude::*;
+45
+46#[derive(Deserialize, Debug, SmartDefault)]
+47#[serde(deny_unknown_fields, default)]
+48pub struct Config {
+49    pub format: FormatConfig,
+50    pub driver: Driver,
+51}
+52
+53#[derive(Deserialize, Debug, SmartDefault)]
+54#[serde(rename_all = "snake_case")]
+55pub enum Driver {
+56    #[default]
+57    Auto,
+58    SwayIpc,
+59    WlrToplevelManagement,
+60}
+61
+62pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+63    let format = config.format.with_default(" $title.str(max_w:21) |")?;
+64
+65    let mut backend: Box<dyn Backend> = match config.driver {
+66        Driver::Auto => match SwayIpc::new().await {
+67            Ok(swayipc) => Box::new(swayipc),
+68            Err(_) => Box::new(WlrToplevelManagement::new().await?),
+69        },
+70        Driver::SwayIpc => Box::new(SwayIpc::new().await?),
+71        Driver::WlrToplevelManagement => Box::new(WlrToplevelManagement::new().await?),
+72    };
+73
+74    loop {
+75        let Info { title, marks } = backend.get_info().await?;
+76
+77        let mut widget = Widget::new().with_format(format.clone());
+78
+79        if !title.is_empty() {
+80            let join_marks = |mut s: String, m: &String| {
+81                let _ = write!(s, "[{m}]"); // writing to String never fails
+82                s
+83            };
+84
+85            let marks_str = marks.iter().fold(String::new(), join_marks);
+86            let visible_marks_str = marks
+87                .iter()
+88                .filter(|m| !m.starts_with('_'))
+89                .fold(String::new(), join_marks);
+90
+91            widget.set_values(map! {
+92                "title" => Value::text(title),
+93                "marks" => Value::text(marks_str),
+94                "visible_marks" => Value::text(visible_marks_str),
+95            });
+96        }
+97
+98        api.set_widget(widget)?;
+99    }
+100}
+101
+102#[async_trait]
+103trait Backend {
+104    async fn get_info(&mut self) -> Result<Info>;
+105}
+106
+107#[derive(Clone, Default)]
+108struct Info {
+109    title: String,
+110    marks: Vec<String>,
+111}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/focused_window/sway_ipc.rs.html b/src/i3status_rs/blocks/focused_window/sway_ipc.rs.html new file mode 100644 index 0000000000..fbb369c21d --- /dev/null +++ b/src/i3status_rs/blocks/focused_window/sway_ipc.rs.html @@ -0,0 +1,72 @@ +sway_ipc.rs - source

i3status_rs/blocks/focused_window/
sway_ipc.rs

1use super::{Backend, Info};
+2use crate::blocks::prelude::*;
+3use swayipc_async::{Connection, Event, EventStream, EventType, WindowChange, WorkspaceChange};
+4
+5pub(super) struct SwayIpc {
+6    events: EventStream,
+7    info: Info,
+8}
+9
+10impl SwayIpc {
+11    pub(super) async fn new() -> Result<Self> {
+12        Ok(Self {
+13            events: Connection::new()
+14                .await
+15                .error("failed to open connection with swayipc")?
+16                .subscribe(&[EventType::Window, EventType::Workspace])
+17                .await
+18                .error("could not subscribe to window events")?,
+19            info: default(),
+20        })
+21    }
+22}
+23
+24#[async_trait]
+25impl Backend for SwayIpc {
+26    async fn get_info(&mut self) -> Result<Info> {
+27        loop {
+28            let event = self
+29                .events
+30                .next()
+31                .await
+32                .error("swayipc channel closed")?
+33                .error("bad event")?;
+34            match event {
+35                Event::Window(e) => match e.change {
+36                    WindowChange::Mark => {
+37                        self.info.marks = e.container.marks;
+38                    }
+39                    WindowChange::Focus => {
+40                        self.info.title.clear();
+41                        if let Some(new_title) = &e.container.name {
+42                            self.info.title.push_str(new_title);
+43                        }
+44                        self.info.marks = e.container.marks;
+45                    }
+46                    WindowChange::Title => {
+47                        if e.container.focused {
+48                            self.info.title.clear();
+49                            if let Some(new_title) = &e.container.name {
+50                                self.info.title.push_str(new_title);
+51                            }
+52                        } else {
+53                            continue;
+54                        }
+55                    }
+56                    WindowChange::Close => {
+57                        self.info.title.clear();
+58                        self.info.marks.clear();
+59                    }
+60                    _ => continue,
+61                },
+62                Event::Workspace(e) if e.change == WorkspaceChange::Init => {
+63                    self.info.title.clear();
+64                    self.info.marks.clear();
+65                }
+66                _ => continue,
+67            }
+68
+69            return Ok(self.info.clone());
+70        }
+71    }
+72}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/focused_window/wlr_toplevel_management.rs.html b/src/i3status_rs/blocks/focused_window/wlr_toplevel_management.rs.html new file mode 100644 index 0000000000..70640a3815 --- /dev/null +++ b/src/i3status_rs/blocks/focused_window/wlr_toplevel_management.rs.html @@ -0,0 +1,119 @@ +wlr_toplevel_management.rs - source

i3status_rs/blocks/focused_window/
wlr_toplevel_management.rs

1use super::{Backend, Info};
+2use crate::blocks::prelude::*;
+3
+4use wayrs_client::{Connection, EventCtx};
+5use wayrs_protocols::wlr_foreign_toplevel_management_unstable_v1::*;
+6
+7pub(super) struct WlrToplevelManagement {
+8    conn: Connection<State>,
+9    state: State,
+10}
+11
+12#[derive(Default)]
+13struct State {
+14    error: Option<Error>,
+15    new_title: Option<String>,
+16    toplevels: HashMap<ZwlrForeignToplevelHandleV1, Toplevel>,
+17    active_toplevel: Option<ZwlrForeignToplevelHandleV1>,
+18}
+19
+20#[derive(Default)]
+21struct Toplevel {
+22    title: Option<String>,
+23    is_active: bool,
+24}
+25
+26impl WlrToplevelManagement {
+27    pub(super) async fn new() -> Result<Self> {
+28        let mut conn = Connection::connect().error("failed to connect to wayland")?;
+29
+30        conn.async_roundtrip()
+31            .await
+32            .error("wayland roundrip failed")?;
+33
+34        let _: ZwlrForeignToplevelManagerV1 = conn
+35            .bind_singleton_with_cb(1..=3, toplevel_manager_cb)
+36            .error("unsupported compositor")?;
+37
+38        Ok(Self {
+39            conn,
+40            state: default(),
+41        })
+42    }
+43}
+44
+45#[async_trait]
+46impl Backend for WlrToplevelManagement {
+47    async fn get_info(&mut self) -> Result<Info> {
+48        loop {
+49            self.conn.async_flush().await.error("wayland error")?;
+50            self.conn.async_recv_events().await.error("wayland error")?;
+51            self.conn.dispatch_events(&mut self.state);
+52
+53            if let Some(err) = self.state.error.take() {
+54                return Err(err);
+55            }
+56
+57            if let Some(title) = self.state.new_title.take() {
+58                return Ok(Info {
+59                    title,
+60                    marks: default(),
+61                });
+62            }
+63        }
+64    }
+65}
+66
+67fn toplevel_manager_cb(ctx: EventCtx<State, ZwlrForeignToplevelManagerV1>) {
+68    use zwlr_foreign_toplevel_manager_v1::Event;
+69    match ctx.event {
+70        Event::Toplevel(toplevel) => {
+71            ctx.state.toplevels.insert(toplevel, default());
+72            ctx.conn.set_callback_for(toplevel, toplevel_cb);
+73        }
+74        Event::Finished => {
+75            ctx.state.error = Some(Error::new("unexpected 'finished' event"));
+76            ctx.conn.break_dispatch_loop();
+77        }
+78        _ => (),
+79    }
+80}
+81
+82fn toplevel_cb(ctx: EventCtx<State, ZwlrForeignToplevelHandleV1>) {
+83    use zwlr_foreign_toplevel_handle_v1::Event;
+84
+85    let Some(toplevel) = ctx.state.toplevels.get_mut(&ctx.proxy) else {
+86        return;
+87    };
+88
+89    match ctx.event {
+90        Event::Title(title) => {
+91            toplevel.title = Some(String::from_utf8_lossy(title.as_bytes()).into());
+92        }
+93        Event::State(state) => {
+94            toplevel.is_active = state
+95                .chunks_exact(4)
+96                .map(|b| u32::from_ne_bytes(b.try_into().unwrap()))
+97                .any(|s| s == zwlr_foreign_toplevel_handle_v1::State::Activated as u32);
+98        }
+99        Event::Closed => {
+100            if ctx.state.active_toplevel == Some(ctx.proxy) {
+101                ctx.state.active_toplevel = None;
+102                ctx.state.new_title = Some(default());
+103            }
+104
+105            ctx.proxy.destroy(ctx.conn);
+106            ctx.state.toplevels.remove(&ctx.proxy);
+107        }
+108        Event::Done => {
+109            if toplevel.is_active {
+110                ctx.state.active_toplevel = Some(ctx.proxy);
+111                ctx.state.new_title = Some(toplevel.title.clone().unwrap_or_default());
+112            } else if ctx.state.active_toplevel == Some(ctx.proxy) {
+113                ctx.state.active_toplevel = None;
+114                ctx.state.new_title = Some(default());
+115            }
+116        }
+117        _ => (),
+118    }
+119}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/github.rs.html b/src/i3status_rs/blocks/github.rs.html new file mode 100644 index 0000000000..9e858c51b8 --- /dev/null +++ b/src/i3status_rs/blocks/github.rs.html @@ -0,0 +1,190 @@ +github.rs - source

i3status_rs/blocks/
github.rs

1//! The number of GitHub notifications
+2//!
+3//! This block shows the unread notification count for a GitHub account. A GitHub [personal access token](https://github.com/settings/tokens/new) with the "notifications" scope is required, and must be passed using the `I3RS_GITHUB_TOKEN` environment variable or `token` configuration option. Optionally the colour of the block is determined by the highest notification in the following lists from highest to lowest: `critical`,`warning`,`info`,`good`
+4//!
+5//! # Configuration
+6//!
+7//! Key | Values | Default
+8//! ----|--------|--------
+9//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $total.eng(w:1) "`
+10//! `interval` | Update interval in seconds | `30`
+11//! `token` | A GitHub personal access token with the "notifications" scope | `None`
+12//! `hide_if_total_is_zero` | Hide this block if the total count of notifications is zero | `false`
+13//! `critical` | List of notification types that change the block to the critical colour | `None`
+14//! `warning` | List of notification types that change the block to the warning colour | `None`
+15//! `info` | List of notification types that change the block to the info colour | `None`
+16//! `good` | List of notification types that change the block to the good colour | `None`
+17//!
+18//!
+19//! All the placeholders are numbers without a unit.
+20//!
+21//! Placeholder        | Value
+22//! -------------------|------
+23//! `icon`             | A static icon
+24//! `total`            | The total number of notifications
+25//! `assign`           | You were assigned to the issue
+26//! `author`           | You created the thread
+27//! `comment`          | You commented on the thread
+28//! `ci_activity`      | A GitHub Actions workflow run that you triggered was completed
+29//! `invitation`       | You accepted an invitation to contribute to the repository
+30//! `manual`           | You subscribed to the thread (via an issue or pull request)
+31//! `mention`          | You were specifically @mentioned in the content
+32//! `review_requested` | You, or a team you're a member of, were requested to review a pull request
+33//! `security_alert`   | GitHub discovered a security vulnerability in your repository
+34//! `state_change`     | You changed the thread state (for example, closing an issue or merging a pull request)
+35//! `subscribed`       | You're watching the repository
+36//! `team_mention`     | You were on a team that was mentioned
+37//!
+38//! # Examples
+39//!
+40//! ```toml
+41//! [[block]]
+42//! block = "github"
+43//! format = " $icon $total.eng(w:1)|$mention.eng(w:1) "
+44//! interval = 60
+45//! token = "..."
+46//! ```
+47//!
+48//! ```toml
+49//! [[block]]
+50//! block = "github"
+51//! token = "..."
+52//! format = " $icon $total.eng(w:1) "
+53//! info = ["total"]
+54//! warning = ["mention","review_requested"]
+55//! hide_if_total_is_zero = true
+56//! ```
+57//!
+58//! # Icons Used
+59//! - `github`
+60
+61use super::prelude::*;
+62
+63#[derive(Deserialize, Debug, SmartDefault)]
+64#[serde(deny_unknown_fields, default)]
+65pub struct Config {
+66    #[default(60.into())]
+67    pub interval: Seconds,
+68    pub format: FormatConfig,
+69    pub token: Option<String>,
+70    pub hide_if_total_is_zero: bool,
+71    pub good: Option<Vec<String>>,
+72    pub info: Option<Vec<String>>,
+73    pub warning: Option<Vec<String>>,
+74    pub critical: Option<Vec<String>>,
+75}
+76
+77pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+78    let format = config.format.with_default(" $icon $total.eng(w:1) ")?;
+79
+80    let mut interval = config.interval.timer();
+81    let token = config
+82        .token
+83        .clone()
+84        .or_else(|| std::env::var("I3RS_GITHUB_TOKEN").ok())
+85        .error("Github token not found")?;
+86
+87    loop {
+88        let stats = get_stats(&token).await?;
+89
+90        if stats.get("total").is_some_and(|x| *x > 0) || !config.hide_if_total_is_zero {
+91            let mut widget = Widget::new().with_format(format.clone());
+92
+93            'outer: for (list_opt, ret) in [
+94                (&config.critical, State::Critical),
+95                (&config.warning, State::Warning),
+96                (&config.info, State::Info),
+97                (&config.good, State::Good),
+98            ] {
+99                if let Some(list) = list_opt {
+100                    for val in list {
+101                        if stats.get(val).is_some_and(|x| *x > 0) {
+102                            widget.state = ret;
+103                            break 'outer;
+104                        }
+105                    }
+106                }
+107            }
+108
+109            let mut values: HashMap<_, _> = stats
+110                .into_iter()
+111                .map(|(k, v)| (k.into(), Value::number(v)))
+112                .collect();
+113            values.insert("icon".into(), Value::icon("github"));
+114            widget.set_values(values);
+115
+116            api.set_widget(widget)?;
+117        } else {
+118            api.hide()?;
+119        }
+120
+121        select! {
+122            _ = interval.tick() => (),
+123            _ = api.wait_for_update_request() => (),
+124        }
+125    }
+126}
+127
+128#[derive(Deserialize, Debug)]
+129struct Notification {
+130    reason: String,
+131}
+132
+133async fn get_stats(token: &str) -> Result<HashMap<String, usize>> {
+134    let mut stats = HashMap::new();
+135    let mut total = 0;
+136    for page in 1..100 {
+137        let fetch = || get_on_page(token, page);
+138        let on_page = fetch.retry(ExponentialBuilder::default()).await?;
+139        if on_page.is_empty() {
+140            break;
+141        }
+142        total += on_page.len();
+143        for n in on_page {
+144            stats.entry(n.reason).and_modify(|x| *x += 1).or_insert(1);
+145        }
+146    }
+147    stats.insert("total".into(), total);
+148    stats.entry("total".into()).or_insert(0);
+149    stats.entry("assign".into()).or_insert(0);
+150    stats.entry("author".into()).or_insert(0);
+151    stats.entry("comment".into()).or_insert(0);
+152    stats.entry("ci_activity".into()).or_insert(0);
+153    stats.entry("invitation".into()).or_insert(0);
+154    stats.entry("manual".into()).or_insert(0);
+155    stats.entry("mention".into()).or_insert(0);
+156    stats.entry("review_requested".into()).or_insert(0);
+157    stats.entry("security_alert".into()).or_insert(0);
+158    stats.entry("state_change".into()).or_insert(0);
+159    stats.entry("subscribed".into()).or_insert(0);
+160    stats.entry("team_mention".into()).or_insert(0);
+161    Ok(stats)
+162}
+163
+164async fn get_on_page(token: &str, page: usize) -> Result<Vec<Notification>> {
+165    #[derive(Deserialize)]
+166    #[serde(untagged)]
+167    enum Response {
+168        Notifications(Vec<Notification>),
+169        ErrorMessage { message: String },
+170    }
+171
+172    // https://docs.github.com/en/rest/reference/activity#notifications
+173    let request = REQWEST_CLIENT
+174        .get(format!(
+175            "https://api.github.com/notifications?per_page=100&page={page}",
+176        ))
+177        .header("Authorization", format!("token {token}"));
+178    let response = request
+179        .send()
+180        .await
+181        .error("Failed to send request")?
+182        .json::<Response>()
+183        .await
+184        .error("Failed to get JSON")?;
+185
+186    match response {
+187        Response::Notifications(n) => Ok(n),
+188        Response::ErrorMessage { message } => Err(Error::new(format!("API error: {message}"))),
+189    }
+190}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/hueshift.rs.html b/src/i3status_rs/blocks/hueshift.rs.html new file mode 100644 index 0000000000..923143b069 --- /dev/null +++ b/src/i3status_rs/blocks/hueshift.rs.html @@ -0,0 +1,396 @@ +hueshift.rs - source

i3status_rs/blocks/
hueshift.rs

1//! Manage display temperature
+2//!
+3//! This block displays the current color temperature in Kelvin. When scrolling upon the block the color temperature is changed.
+4//! A left click on the block sets the color temperature to `click_temp` that is by default to `6500K`.
+5//! A right click completely resets the color temperature to its default value (`6500K`).
+6//!
+7//! # Configuration
+8//!
+9//! Key | Values | Default
+10//! ----|--------|--------
+11//! `format`      | A string to customise the output of this block. See below for available placeholders. | `" $temperature "`
+12//! `step`        | The step color temperature is in/decreased in Kelvin. | `100`
+13//! `hue_shifter` | Program used to control screen color. | Detect automatically
+14//! `max_temp`    | Max color temperature in Kelvin. | `10000`
+15//! `min_temp`    | Min color temperature in Kelvin. | `1000`
+16//! `click_temp`  | Left click color temperature in Kelvin. | `6500`
+17//!
+18//! Placeholder           | Value                        | Type   | Unit
+19//! ----------------------|------------------------------|--------|---------------
+20//! `temperature`         | Current temperature          | Number | -
+21//!
+22//! Action             | Default button
+23//! -------------------|---------------
+24//! `set_click_temp`   | Left
+25//! `reset`            | Right
+26//! `temperature_up`   | Wheel Up
+27//! `temperature_down` | Wheel Down
+28//!
+29//! # Available Hue Shifters
+30//!
+31//! Name                 | Supports
+32//! ---------------------|---------
+33//! `"redshift"`         | X11
+34//! `"sct"`              | X11
+35//! `"gammastep"`        | X11 and Wayland
+36//! `"wl_gammarelay"`    | Wayland
+37//! `"wl_gammarelay_rs"` | Wayland
+38//! `"wlsunset"`         | Wayland
+39//!
+40//! Note that at the moment, only [`wl_gammarelay`](https://github.com/jeremija/wl-gammarelay) and
+41//! [`wl_gammarelay_rs`](https://github.com/MaxVerevkin/wl-gammarelay-rs)
+42//! subscribe to the events and update the bar when the temperature is modified externally. Also,
+43//! these are the only drivers at the moment that work under Wayland without flickering.
+44//!
+45//! # Example
+46//!
+47//! ```toml
+48//! [[block]]
+49//! block = "hueshift"
+50//! hue_shifter = "redshift"
+51//! step = 50
+52//! click_temp = 3500
+53//! ```
+54//!
+55//! A hard limit is set for the `max_temp` to `10000K` and the same for the `min_temp` which is `1000K`.
+56//! The `step` has a hard limit as well, defined to `500K` to avoid too brutal changes.
+57
+58use super::prelude::*;
+59use crate::subprocess::{spawn_process, spawn_shell};
+60use crate::util::has_command;
+61use futures::future::pending;
+62
+63#[derive(Deserialize, Debug, SmartDefault)]
+64#[serde(deny_unknown_fields, default)]
+65pub struct Config {
+66    pub format: FormatConfig,
+67    // TODO: Document once this option becomes useful
+68    #[default(5.into())]
+69    pub interval: Seconds,
+70    #[default(10_000)]
+71    pub max_temp: u16,
+72    #[default(1_000)]
+73    pub min_temp: u16,
+74    // TODO: Remove (this option is undocumented)
+75    #[default(6_500)]
+76    pub current_temp: u16,
+77    pub hue_shifter: Option<HueShifter>,
+78    #[default(100)]
+79    pub step: u16,
+80    #[default(6_500)]
+81    pub click_temp: u16,
+82}
+83
+84pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+85    let mut actions = api.get_actions()?;
+86    api.set_default_actions(&[
+87        (MouseButton::Left, None, "set_click_temp"),
+88        (MouseButton::Right, None, "reset"),
+89        (MouseButton::WheelUp, None, "temperature_up"),
+90        (MouseButton::WheelDown, None, "temperature_down"),
+91    ])?;
+92
+93    let format = config.format.with_default(" $icon $temperature ")?;
+94
+95    // limit too big steps at 500K to avoid too brutal changes
+96    let step = config.step.min(500);
+97    let max_temp = config.max_temp.min(10_000);
+98    let min_temp = config.min_temp.clamp(1_000, max_temp);
+99
+100    let hue_shifter = match config.hue_shifter {
+101        Some(driver) => driver,
+102        None => {
+103            if has_command("wl-gammarelay-rs").await? {
+104                HueShifter::WlGammarelayRs
+105            } else if has_command("wl-gammarelay").await? {
+106                HueShifter::WlGammarelay
+107            } else if has_command("redshift").await? {
+108                HueShifter::Redshift
+109            } else if has_command("sct").await? {
+110                HueShifter::Sct
+111            } else if has_command("gammastep").await? {
+112                HueShifter::Gammastep
+113            } else if has_command("wlsunset").await? {
+114                HueShifter::Wlsunset
+115            } else {
+116                return Err(Error::new("Could not detect driver program"));
+117            }
+118        }
+119    };
+120
+121    let mut driver: Box<dyn HueShiftDriver> = match hue_shifter {
+122        HueShifter::Redshift => Box::new(Redshift::new(config.interval)),
+123        HueShifter::Sct => Box::new(Sct::new(config.interval)),
+124        HueShifter::Gammastep => Box::new(Gammastep::new(config.interval)),
+125        HueShifter::Wlsunset => Box::new(Wlsunset::new(config.interval)),
+126        HueShifter::WlGammarelay => Box::new(WlGammarelayRs::new("wl-gammarelay").await?),
+127        HueShifter::WlGammarelayRs => Box::new(WlGammarelayRs::new("wl-gammarelay-rs").await?),
+128    };
+129
+130    let mut current_temp = driver.get().await?.unwrap_or(config.current_temp);
+131
+132    loop {
+133        let mut widget = Widget::new().with_format(format.clone());
+134        widget.set_values(map! {
+135            "icon" => Value::icon("hueshift"),
+136            "temperature" => Value::number(current_temp)
+137        });
+138        api.set_widget(widget)?;
+139
+140        select! {
+141            update = driver.receive_update() => {
+142                current_temp = update?;
+143            }
+144            _ = api.wait_for_update_request() => {
+145                if let Some(val) = driver.get().await? {
+146                    current_temp = val;
+147                }
+148            }
+149            Some(action) = actions.recv() => match action.as_ref() {
+150                "set_click_temp" => {
+151                    current_temp = config.click_temp;
+152                    driver.update(current_temp).await?;
+153                }
+154                "reset" => {
+155                    if max_temp > 6500 {
+156                        current_temp = 6500;
+157                        driver.reset().await?;
+158                    } else {
+159                        current_temp = max_temp;
+160                        driver.update(current_temp).await?;
+161                    }
+162                }
+163                "temperature_up" => {
+164                    current_temp = (current_temp + step).min(max_temp);
+165                    driver.update(current_temp).await?;
+166                }
+167                "temperature_down" => {
+168                    current_temp = current_temp.saturating_sub(step).max(min_temp);
+169                    driver.update(current_temp).await?;
+170                }
+171                _ => (),
+172            }
+173        }
+174    }
+175}
+176
+177#[derive(Deserialize, Debug, Clone, Copy)]
+178#[serde(rename_all = "snake_case")]
+179pub enum HueShifter {
+180    Redshift,
+181    Sct,
+182    Gammastep,
+183    Wlsunset,
+184    WlGammarelay,
+185    WlGammarelayRs,
+186}
+187
+188#[async_trait]
+189trait HueShiftDriver {
+190    async fn get(&mut self) -> Result<Option<u16>>;
+191    async fn update(&mut self, temp: u16) -> Result<()>;
+192    async fn reset(&mut self) -> Result<()>;
+193    async fn receive_update(&mut self) -> Result<u16>;
+194}
+195
+196struct Redshift {
+197    interval: Seconds,
+198}
+199
+200impl Redshift {
+201    fn new(interval: Seconds) -> Self {
+202        Self { interval }
+203    }
+204}
+205
+206#[async_trait]
+207impl HueShiftDriver for Redshift {
+208    async fn get(&mut self) -> Result<Option<u16>> {
+209        // TODO
+210        Ok(None)
+211    }
+212    async fn update(&mut self, temp: u16) -> Result<()> {
+213        spawn_process("redshift", &["-O", &temp.to_string(), "-P"])
+214            .error("Failed to set new color temperature using redshift.")
+215    }
+216    async fn reset(&mut self) -> Result<()> {
+217        spawn_process("redshift", &["-x"])
+218            .error("Failed to set new color temperature using redshift.")
+219    }
+220    async fn receive_update(&mut self) -> Result<u16> {
+221        sleep(self.interval.0).await;
+222        // self.get().await
+223        pending().await
+224    }
+225}
+226
+227struct Sct {
+228    interval: Seconds,
+229}
+230
+231impl Sct {
+232    fn new(interval: Seconds) -> Self {
+233        Self { interval }
+234    }
+235}
+236
+237#[async_trait]
+238impl HueShiftDriver for Sct {
+239    async fn get(&mut self) -> Result<Option<u16>> {
+240        // TODO
+241        Ok(None)
+242    }
+243    async fn update(&mut self, temp: u16) -> Result<()> {
+244        spawn_shell(&format!("sct {temp} >/dev/null 2>&1"))
+245            .error("Failed to set new color temperature using sct.")
+246    }
+247    async fn reset(&mut self) -> Result<()> {
+248        spawn_process("sct", &[]).error("Failed to set new color temperature using sct.")
+249    }
+250    async fn receive_update(&mut self) -> Result<u16> {
+251        sleep(self.interval.0).await;
+252        // self.get().await
+253        pending().await
+254    }
+255}
+256
+257struct Gammastep {
+258    interval: Seconds,
+259}
+260
+261impl Gammastep {
+262    fn new(interval: Seconds) -> Self {
+263        Self { interval }
+264    }
+265}
+266
+267#[async_trait]
+268impl HueShiftDriver for Gammastep {
+269    async fn get(&mut self) -> Result<Option<u16>> {
+270        // TODO
+271        Ok(None)
+272    }
+273    async fn update(&mut self, temp: u16) -> Result<()> {
+274        spawn_shell(&format!("pkill gammastep; gammastep -O {temp} -P &",))
+275            .error("Failed to set new color temperature using gammastep.")
+276    }
+277    async fn reset(&mut self) -> Result<()> {
+278        spawn_process("gammastep", &["-x"])
+279            .error("Failed to set new color temperature using gammastep.")
+280    }
+281    async fn receive_update(&mut self) -> Result<u16> {
+282        sleep(self.interval.0).await;
+283        // self.get().await
+284        pending().await
+285    }
+286}
+287
+288struct Wlsunset {
+289    interval: Seconds,
+290}
+291
+292impl Wlsunset {
+293    fn new(interval: Seconds) -> Self {
+294        Self { interval }
+295    }
+296}
+297
+298#[async_trait]
+299impl HueShiftDriver for Wlsunset {
+300    async fn get(&mut self) -> Result<Option<u16>> {
+301        // TODO
+302        Ok(None)
+303    }
+304    async fn update(&mut self, temp: u16) -> Result<()> {
+305        // wlsunset does not have a oneshot option, so set both day and
+306        // night temperature. wlsunset dose not allow for day and night
+307        // temperatures to be the same, so increment the day temperature.
+308        spawn_shell(&format!(
+309            "pkill wlsunset; wlsunset -T {} -t {} &",
+310            temp + 1,
+311            temp
+312        ))
+313        .error("Failed to set new color temperature using wlsunset.")
+314    }
+315    async fn reset(&mut self) -> Result<()> {
+316        // wlsunset does not have a reset option, so just kill the process.
+317        // Trying to call wlsunset without any arguments uses the defaults:
+318        // day temp: 6500K
+319        // night temp: 4000K
+320        // latitude/longitude: NaN
+321        //     ^ results in sun_condition == POLAR_NIGHT at time of testing
+322        // With these defaults, this results in the the color temperature
+323        // getting set to 4000K.
+324        spawn_process("pkill", &["wlsunset"])
+325            .error("Failed to set new color temperature using wlsunset.")
+326    }
+327    async fn receive_update(&mut self) -> Result<u16> {
+328        sleep(self.interval.0).await;
+329        // self.get().await
+330        pending().await
+331    }
+332}
+333
+334struct WlGammarelayRs {
+335    proxy: WlGammarelayRsBusProxy<'static>,
+336    updates: zbus::proxy::PropertyStream<'static, u16>,
+337}
+338
+339impl WlGammarelayRs {
+340    async fn new(cmd: &str) -> Result<Self> {
+341        // Make sure the daemon is running
+342        spawn_process(cmd, &[]).error("Failed to start wl-gammarelay daemon")?;
+343        sleep(Duration::from_millis(100)).await;
+344
+345        let conn = crate::util::new_dbus_connection().await?;
+346        let proxy = WlGammarelayRsBusProxy::new(&conn)
+347            .await
+348            .error("Failed to create wl-gammarelay-rs DBus proxy")?;
+349        let updates = proxy.receive_temperature_changed().await;
+350        Ok(Self { proxy, updates })
+351    }
+352}
+353
+354#[async_trait]
+355impl HueShiftDriver for WlGammarelayRs {
+356    async fn get(&mut self) -> Result<Option<u16>> {
+357        let value = self
+358            .proxy
+359            .temperature()
+360            .await
+361            .error("Failed to get temperature")?;
+362        Ok(Some(value))
+363    }
+364    async fn update(&mut self, temp: u16) -> Result<()> {
+365        self.proxy
+366            .set_temperature(temp)
+367            .await
+368            .error("Failed to set temperature")
+369    }
+370    async fn reset(&mut self) -> Result<()> {
+371        self.update(6500).await
+372    }
+373    async fn receive_update(&mut self) -> Result<u16> {
+374        let update = self.updates.next().await.error("No next update")?;
+375        update.get().await.error("Failed to get temperature")
+376    }
+377}
+378
+379#[zbus::proxy(
+380    interface = "rs.wl.gammarelay",
+381    default_service = "rs.wl-gammarelay",
+382    default_path = "/"
+383)]
+384trait WlGammarelayRsBus {
+385    /// Brightness property
+386    #[zbus(property)]
+387    fn brightness(&self) -> zbus::Result<f64>;
+388    #[zbus(property)]
+389    fn set_brightness(&self, value: f64) -> zbus::Result<()>;
+390
+391    /// Temperature property
+392    #[zbus(property)]
+393    fn temperature(&self) -> zbus::Result<u16>;
+394    #[zbus(property)]
+395    fn set_temperature(&self, value: u16) -> zbus::Result<()>;
+396}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/kdeconnect.rs.html b/src/i3status_rs/blocks/kdeconnect.rs.html new file mode 100644 index 0000000000..5b01aae050 --- /dev/null +++ b/src/i3status_rs/blocks/kdeconnect.rs.html @@ -0,0 +1,490 @@ +kdeconnect.rs - source

i3status_rs/blocks/
kdeconnect.rs

1//! [KDEConnect](https://community.kde.org/KDEConnect) indicator
+2//!
+3//! Display info from the currently connected device in KDEConnect, updated asynchronously.
+4//!
+5//! Block colours are updated based on the battery level, unless all bat_* thresholds are set to 0,
+6//! in which case the block colours will depend on the notification count instead.
+7//!
+8//! # Configuration
+9//!
+10//! Key | Values | Default
+11//! ----|--------|--------
+12//! `device_id` | Device ID as per the output of `kdeconnect --list-devices`. | Chooses the first found device, if any.
+13//! `format` | A string to customise the output of this block. See below for available placeholders. | <code>\" $icon $name{ $bat_icon $bat_charge\|}{ $notif_icon\|} \"</code>
+14//! `format_disconnected` | Same as `format` but when device is disconnected | `" $icon "`
+15//! `format_missing` | Same as `format` but when device does not exist | `" $icon x "`
+16//! `bat_info` | Min battery level below which state is set to info. | `60`
+17//! `bat_good` | Min battery level below which state is set to good. | `60`
+18//! `bat_warning` | Min battery level below which state is set to warning. | `30`
+19//! `bat_critical` | Min battery level below which state is set to critical. | `15`
+20//!
+21//! Placeholder        | Value                                                                    | Type   | Unit
+22//! -------------------|--------------------------------------------------------------------------|--------|-----
+23//! `icon`             | Icon based on connection's status                                        | Icon   | -
+24//! `bat_icon`         | Battery level indicator (only when connected and if supported)           | Icon   | -
+25//! `bat_charge`       | Battery charge level (only when connected and if supported)              | Number | %
+26//! `network_icon`     | Cell Network indicator (only when connected and if supported)            | Icon   | -
+27//! `network_type`     | Cell Network type (only when connected and if supported)                 | Text   | -
+28//! `network_strength` | Cell Network level (only when connected and if supported)                | Number | %
+29//! `notif_icon`       | Only when connected and there are notifications                          | Icon   | -
+30//! `notif_count`      | Number of notifications on your phone (only when connected and non-zero) | Number | -
+31//! `name`             | Name of your device as reported by KDEConnect (if available)             | Text   | -
+32//!
+33//! # Example
+34//!
+35//! Do not show the name, do not set the "good" state.
+36//!
+37//! ```toml
+38//! [[block]]
+39//! block = "kdeconnect"
+40//! format = " $icon {$bat_icon $bat_charge |}{$notif_icon |}{$network_icon$network_strength $network_type |}"
+41//! bat_good = 101
+42//! ```
+43//!
+44//! # Icons Used
+45//! - `bat` (as a progression)
+46//! - `bat_charging` (as a progression)
+47//! - `net_cellular` (as a progression)
+48//! - `notification`
+49//! - `phone`
+50//! - `phone_disconnected`
+51
+52use super::prelude::*;
+53
+54mod battery;
+55mod connectivity_report;
+56use battery::BatteryDbusProxy;
+57use connectivity_report::ConnectivityDbusProxy;
+58
+59make_log_macro!(debug, "kdeconnect");
+60
+61#[derive(Deserialize, Debug, SmartDefault)]
+62#[serde(deny_unknown_fields, default)]
+63pub struct Config {
+64    pub device_id: Option<String>,
+65    pub format: FormatConfig,
+66    pub disconnected_format: FormatConfig,
+67    pub missing_format: FormatConfig,
+68    #[default(60)]
+69    pub bat_good: u8,
+70    #[default(60)]
+71    pub bat_info: u8,
+72    #[default(30)]
+73    pub bat_warning: u8,
+74    #[default(15)]
+75    pub bat_critical: u8,
+76}
+77
+78pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+79    let format = config
+80        .format
+81        .with_default(" $icon $name {$bat_icon $bat_charge |}{$notif_icon |}")?;
+82    let disconnected_format = config.disconnected_format.with_default(" $icon ")?;
+83    let missing_format = config.missing_format.with_default(" $icon x ")?;
+84
+85    let battery_state = (
+86        config.bat_good,
+87        config.bat_info,
+88        config.bat_warning,
+89        config.bat_critical,
+90    ) != (0, 0, 0, 0);
+91
+92    let mut monitor = DeviceMonitor::new(config.device_id.clone()).await?;
+93
+94    loop {
+95        match monitor.get_device_info().await {
+96            Some(info) => {
+97                let mut widget = Widget::new();
+98                if info.connected {
+99                    widget.set_format(format.clone());
+100                } else {
+101                    widget.set_format(disconnected_format.clone());
+102                }
+103
+104                let mut values = map! {
+105                    [if info.connected] "icon" => Value::icon("phone"),
+106                    [if !info.connected] "icon" => Value::icon("phone_disconnected"),
+107                    [if let Some(name) = info.name] "name" => Value::text(name),
+108                    [if info.notifications > 0] "notif_count" => Value::number(info.notifications),
+109                    [if info.notifications > 0] "notif_icon" => Value::icon("notification"),
+110                    [if let Some(bat) = info.bat_level] "bat_charge" => Value::percents(bat),
+111                };
+112
+113                if let Some(bat_level) = info.bat_level {
+114                    values.insert(
+115                        "bat_icon".into(),
+116                        Value::icon_progression(
+117                            if info.charging { "bat_charging" } else { "bat" },
+118                            bat_level as f64 / 100.0,
+119                        ),
+120                    );
+121                    if battery_state {
+122                        widget.state = if info.charging {
+123                            State::Good
+124                        } else if bat_level <= config.bat_critical {
+125                            State::Critical
+126                        } else if bat_level <= config.bat_info {
+127                            State::Info
+128                        } else if bat_level > config.bat_good {
+129                            State::Good
+130                        } else {
+131                            State::Idle
+132                        };
+133                    }
+134                }
+135
+136                if !battery_state {
+137                    widget.state = if info.notifications == 0 {
+138                        State::Idle
+139                    } else {
+140                        State::Info
+141                    };
+142                }
+143
+144                if let Some(cellular_network_type) = info.cellular_network_type {
+145                    // network strength is 0..=4 from docs of
+146                    // kdeconnect/plugins/connectivity-report, and I
+147                    // got -1 for disabled SIM (undocumented)
+148                    let cell_network_percent =
+149                        (info.cellular_network_strength.clamp(0, 4) * 25) as f64;
+150                    values.insert(
+151                        "network_icon".into(),
+152                        Value::icon_progression(
+153                            "net_cellular",
+154                            (info.cellular_network_strength + 1).clamp(0, 5) as f64 / 5.0,
+155                        ),
+156                    );
+157                    values.insert(
+158                        "network_strength".into(),
+159                        Value::percents(cell_network_percent),
+160                    );
+161
+162                    if info.cellular_network_strength <= 0 {
+163                        widget.state = State::Critical;
+164                        values.insert("network_type".into(), Value::text("×".into()));
+165                    } else {
+166                        values.insert("network_type".into(), Value::text(cellular_network_type));
+167                    }
+168                }
+169
+170                widget.set_values(values);
+171                api.set_widget(widget)?;
+172            }
+173            None => {
+174                let mut widget = Widget::new().with_format(missing_format.clone());
+175                widget.set_values(map! { "icon" => Value::icon("phone_disconnected") });
+176                api.set_widget(widget)?;
+177            }
+178        }
+179
+180        monitor.wait_for_change().await?;
+181    }
+182}
+183
+184struct DeviceMonitor {
+185    device_id: Option<String>,
+186    daemon_proxy: DaemonDbusProxy<'static>,
+187    device: Option<Device>,
+188}
+189
+190struct Device {
+191    id: String,
+192    device_proxy: DeviceDbusProxy<'static>,
+193    battery_proxy: BatteryDbusProxy<'static>,
+194    notifications_proxy: NotificationsDbusProxy<'static>,
+195    connectivity_proxy: ConnectivityDbusProxy<'static>,
+196    device_signals: zbus::proxy::SignalStream<'static>,
+197    notifications_signals: zbus::proxy::SignalStream<'static>,
+198    battery_refreshed: battery::refreshedStream,
+199    connectivity_refreshed: connectivity_report::refreshedStream,
+200}
+201
+202struct DeviceInfo {
+203    connected: bool,
+204    name: Option<String>,
+205    notifications: usize,
+206    charging: bool,
+207    bat_level: Option<u8>,
+208    cellular_network_type: Option<String>,
+209    cellular_network_strength: i32,
+210}
+211
+212impl DeviceMonitor {
+213    async fn new(device_id: Option<String>) -> Result<Self> {
+214        let dbus_conn = new_dbus_connection().await?;
+215        let daemon_proxy = DaemonDbusProxy::new(&dbus_conn)
+216            .await
+217            .error("Failed to create DaemonDbusProxy")?;
+218        let device = Device::try_find(&daemon_proxy, device_id.as_deref()).await?;
+219        Ok(Self {
+220            device_id,
+221            daemon_proxy,
+222            device,
+223        })
+224    }
+225
+226    async fn wait_for_change(&mut self) -> Result<()> {
+227        match &mut self.device {
+228            None => {
+229                let mut device_added = self
+230                    .daemon_proxy
+231                    .receive_device_added()
+232                    .await
+233                    .error("Couldn't create stream")?;
+234                loop {
+235                    device_added
+236                        .next()
+237                        .await
+238                        .error("Stream ended unexpectedly")?;
+239                    if let Some(device) =
+240                        Device::try_find(&self.daemon_proxy, self.device_id.as_deref()).await?
+241                    {
+242                        self.device = Some(device);
+243                        return Ok(());
+244                    }
+245                }
+246            }
+247            Some(dev) => {
+248                let mut device_removed = self
+249                    .daemon_proxy
+250                    .receive_device_removed()
+251                    .await
+252                    .error("Couldn't create stream")?;
+253                loop {
+254                    select! {
+255                        rem = device_removed.next() => {
+256                            let rem = rem.error("stream ended unexpectedly")?;
+257                            let args = rem.args().error("dbus error")?;
+258                            if args.id() == &dev.id {
+259                                self.device = Device::try_find(&self.daemon_proxy, self.device_id.as_deref()).await?;
+260                                return Ok(());
+261                            }
+262                        }
+263                        _ = dev.wait_for_change() => {
+264                            if !dev.connected().await {
+265                                debug!("device became unreachable, re-searching");
+266                                if let Some(dev) = Device::try_find(&self.daemon_proxy, self.device_id.as_deref()).await?
+267                                    && dev.connected().await {
+268                                        debug!("selected {:?}", dev.id);
+269                                        self.device = Some(dev);
+270                                    }
+271                            }
+272                            return Ok(())
+273                        }
+274                    }
+275                }
+276            }
+277        }
+278    }
+279
+280    async fn get_device_info(&mut self) -> Option<DeviceInfo> {
+281        let device = self.device.as_ref()?;
+282        let (bat_level, charging) = device.battery().await;
+283        let (cellular_network_type, cellular_network_strength) = device.network().await;
+284        Some(DeviceInfo {
+285            connected: device.connected().await,
+286            name: device.name().await,
+287            notifications: device.notifications().await,
+288            charging,
+289            bat_level,
+290            cellular_network_type,
+291            cellular_network_strength,
+292        })
+293    }
+294}
+295
+296impl Device {
+297    /// Find a device which `device_id`. Reachable devices have precedence.
+298    async fn try_find(
+299        daemon_proxy: &DaemonDbusProxy<'_>,
+300        device_id: Option<&str>,
+301    ) -> Result<Option<Self>> {
+302        let Ok(mut devices) = daemon_proxy.devices().await else {
+303            debug!("could not get the list of managed objects");
+304            return Ok(None);
+305        };
+306
+307        debug!("all devices: {:?}", devices);
+308
+309        if let Some(device_id) = device_id {
+310            devices.retain(|id| id == device_id);
+311        }
+312
+313        let mut selected_device = None;
+314
+315        for id in devices {
+316            let device_proxy = DeviceDbusProxy::builder(daemon_proxy.inner().connection())
+317                .cache_properties(zbus::proxy::CacheProperties::No)
+318                .path(format!("/modules/kdeconnect/devices/{id}"))
+319                .unwrap()
+320                .build()
+321                .await
+322                .error("Failed to create DeviceDbusProxy")?;
+323            let reachable = device_proxy.is_reachable().await.unwrap_or(false);
+324            selected_device = Some((id, device_proxy));
+325            if reachable {
+326                break;
+327            }
+328        }
+329
+330        let Some((device_id, device_proxy)) = selected_device else {
+331            debug!("No device found");
+332            return Ok(None);
+333        };
+334
+335        let device_path = format!("/modules/kdeconnect/devices/{device_id}");
+336        let battery_path = format!("{device_path}/battery");
+337        let notifications_path = format!("{device_path}/notifications");
+338        let connectivity_path = format!("{device_path}/connectivity_report");
+339
+340        let battery_proxy = BatteryDbusProxy::builder(daemon_proxy.inner().connection())
+341            .cache_properties(zbus::proxy::CacheProperties::No)
+342            .path(battery_path)
+343            .error("Failed to set battery path")?
+344            .build()
+345            .await
+346            .error("Failed to create BatteryDbusProxy")?;
+347        let notifications_proxy =
+348            NotificationsDbusProxy::builder(daemon_proxy.inner().connection())
+349                .cache_properties(zbus::proxy::CacheProperties::No)
+350                .path(notifications_path)
+351                .error("Failed to set notifications path")?
+352                .build()
+353                .await
+354                .error("Failed to create BatteryDbusProxy")?;
+355        let connectivity_proxy = ConnectivityDbusProxy::builder(daemon_proxy.inner().connection())
+356            .cache_properties(zbus::proxy::CacheProperties::No)
+357            .path(connectivity_path)
+358            .error("Failed to set connectivity path")?
+359            .build()
+360            .await
+361            .error("Failed to create ConnectivityDbusProxy")?;
+362
+363        let device_signals = device_proxy
+364            .inner()
+365            .receive_all_signals()
+366            .await
+367            .error("Failed to receive signals")?;
+368        let notifications_signals = notifications_proxy
+369            .inner()
+370            .receive_all_signals()
+371            .await
+372            .error("Failed to receive signals")?;
+373        let battery_refreshed = battery_proxy
+374            .receive_refreshed()
+375            .await
+376            .error("Failed to receive signals")?;
+377        let connectivity_refreshed = connectivity_proxy
+378            .receive_refreshed()
+379            .await
+380            .error("Failed to receive signals")?;
+381
+382        Ok(Some(Self {
+383            id: device_id,
+384            device_proxy,
+385            battery_proxy,
+386            notifications_proxy,
+387            connectivity_proxy,
+388            device_signals,
+389            notifications_signals,
+390            battery_refreshed,
+391            connectivity_refreshed,
+392        }))
+393    }
+394
+395    async fn wait_for_change(&mut self) {
+396        select! {
+397            _ = self.device_signals.next() => (),
+398            _ = self.notifications_signals.next() => (),
+399            _ = self.battery_refreshed.next() => (),
+400            _ = self.connectivity_refreshed.next() => (),
+401        }
+402    }
+403
+404    async fn connected(&self) -> bool {
+405        self.device_proxy.is_reachable().await.unwrap_or(false)
+406    }
+407
+408    async fn name(&self) -> Option<String> {
+409        self.device_proxy.name().await.ok()
+410    }
+411
+412    async fn battery(&self) -> (Option<u8>, bool) {
+413        let (charge, is_charging) = tokio::join!(
+414            self.battery_proxy.charge(),
+415            self.battery_proxy.is_charging(),
+416        );
+417        (
+418            charge.ok().map(|x| x.clamp(0, 100) as u8),
+419            is_charging.unwrap_or(false),
+420        )
+421    }
+422
+423    async fn notifications(&self) -> usize {
+424        self.notifications_proxy
+425            .active_notifications()
+426            .await
+427            .map(|n| n.len())
+428            .unwrap_or(0)
+429    }
+430
+431    async fn network(&self) -> (Option<String>, i32) {
+432        let (ty, strength) = tokio::join!(
+433            self.connectivity_proxy.cellular_network_type(),
+434            self.connectivity_proxy.cellular_network_strength(),
+435        );
+436        (ty.ok(), strength.unwrap_or(-1))
+437    }
+438}
+439
+440#[zbus::proxy(
+441    interface = "org.kde.kdeconnect.daemon",
+442    default_service = "org.kde.kdeconnect",
+443    default_path = "/modules/kdeconnect"
+444)]
+445trait DaemonDbus {
+446    #[zbus(name = "devices")]
+447    fn devices(&self) -> zbus::Result<Vec<String>>;
+448
+449    #[zbus(signal, name = "deviceAdded")]
+450    fn device_added(&self, id: String) -> zbus::Result<()>;
+451
+452    #[zbus(signal, name = "deviceRemoved")]
+453    fn device_removed(&self, id: String) -> zbus::Result<()>;
+454}
+455
+456#[zbus::proxy(
+457    interface = "org.kde.kdeconnect.device",
+458    default_service = "org.kde.kdeconnect"
+459)]
+460trait DeviceDbus {
+461    #[zbus(property, name = "isReachable")]
+462    fn is_reachable(&self) -> zbus::Result<bool>;
+463
+464    #[zbus(signal, name = "reachableChanged")]
+465    fn reachable_changed(&self, reachable: bool) -> zbus::Result<()>;
+466
+467    #[zbus(property, name = "name")]
+468    fn name(&self) -> zbus::Result<String>;
+469
+470    #[zbus(signal, name = "nameChanged")]
+471    fn name_changed_(&self, name: &str) -> zbus::Result<()>;
+472}
+473
+474#[zbus::proxy(
+475    interface = "org.kde.kdeconnect.device.notifications",
+476    default_service = "org.kde.kdeconnect"
+477)]
+478trait NotificationsDbus {
+479    #[zbus(name = "activeNotifications")]
+480    fn active_notifications(&self) -> zbus::Result<Vec<String>>;
+481
+482    #[zbus(signal, name = "allNotificationsRemoved")]
+483    fn all_notifications_removed(&self) -> zbus::Result<()>;
+484
+485    #[zbus(signal, name = "notificationPosted")]
+486    fn notification_posted(&self, id: &str) -> zbus::Result<()>;
+487
+488    #[zbus(signal, name = "notificationRemoved")]
+489    fn notification_removed(&self, id: &str) -> zbus::Result<()>;
+490}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/kdeconnect/battery.rs.html b/src/i3status_rs/blocks/kdeconnect/battery.rs.html new file mode 100644 index 0000000000..c05bbcf75f --- /dev/null +++ b/src/i3status_rs/blocks/kdeconnect/battery.rs.html @@ -0,0 +1,14 @@ +battery.rs - source

i3status_rs/blocks/kdeconnect/
battery.rs

1#[zbus::proxy(
+2    interface = "org.kde.kdeconnect.device.battery",
+3    default_service = "org.kde.kdeconnect"
+4)]
+5pub(super) trait BatteryDbus {
+6    #[zbus(signal, name = "refreshed")]
+7    fn refreshed(&self, is_charging: bool, charge: i32) -> zbus::Result<()>;
+8
+9    #[zbus(property, name = "charge")]
+10    fn charge(&self) -> zbus::Result<i32>;
+11
+12    #[zbus(property, name = "isCharging")]
+13    fn is_charging(&self) -> zbus::Result<bool>;
+14}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/kdeconnect/connectivity_report.rs.html b/src/i3status_rs/blocks/kdeconnect/connectivity_report.rs.html new file mode 100644 index 0000000000..ff64578b2e --- /dev/null +++ b/src/i3status_rs/blocks/kdeconnect/connectivity_report.rs.html @@ -0,0 +1,14 @@ +connectivity_report.rs - source

i3status_rs/blocks/kdeconnect/
connectivity_report.rs

1#[zbus::proxy(
+2    interface = "org.kde.kdeconnect.device.connectivity_report",
+3    default_service = "org.kde.kdeconnect"
+4)]
+5pub(super) trait ConnectivityDbus {
+6    #[zbus(signal, name = "refreshed")]
+7    fn refreshed(&self, network_type: String, network_strength: i32) -> zbus::Result<()>;
+8
+9    #[zbus(property, name = "cellularNetworkStrength")]
+10    fn cellular_network_strength(&self) -> zbus::Result<i32>;
+11
+12    #[zbus(property, name = "cellularNetworkType")]
+13    fn cellular_network_type(&self) -> zbus::Result<String>;
+14}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/keyboard_layout.rs.html b/src/i3status_rs/blocks/keyboard_layout.rs.html new file mode 100644 index 0000000000..6cc86fd56d --- /dev/null +++ b/src/i3status_rs/blocks/keyboard_layout.rs.html @@ -0,0 +1,187 @@ +keyboard_layout.rs - source

i3status_rs/blocks/
keyboard_layout.rs

1//! Keyboard layout indicator
+2//!
+3//! Six drivers are available:
+4//! - `xkbevent` which can read asynchronous updates from the x11 events
+5//! - `setxkbmap` (alias for `xkbevent`) *DEPRECATED*
+6//! - `xkbswitch` (alias for `xkbevent`) *DEPRECATED*
+7//! - `localebus` which can read asynchronous updates from the systemd `org.freedesktop.locale1` D-Bus path
+8//! - `kbddbus` which uses [kbdd](https://github.com/qnikst/kbdd) to monitor per-window layout changes via DBus
+9//! - `sway` which can read asynchronous updates from the sway IPC
+10//!
+11//! `setxkbmap` and `xkbswitch` are deprecated and will be removed in v0.35.0.
+12//!
+13//! Which of these methods is appropriate will depend on your system setup.
+14//!
+15//! # Configuration
+16//!
+17//! Key | Values | Default
+18//! ----|--------|--------
+19//! `driver` | One of `"xkbevent"`, `"setxkbmap"`, `"xkbswitch"`, `"localebus"`, `"kbddbus"` or `"sway"`, depending on your system. | `"xkbevent"`
+20//! `interval` *DEPRECATED* | Update interval, in seconds. Only used by the `"setxkbmap"` driver. | `60`
+21//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $layout "`
+22//! `sway_kb_identifier` | Identifier of the device you want to monitor, as found in the output of `swaymsg -t get_inputs`. | Defaults to first input found
+23//! `mappings` | Map `layout (variant)` to custom short name. | `None`
+24//!
+25//! `interval` is deprecated and will be removed in v0.35.0.
+26//!
+27//!  Key     | Value | Type
+28//! ---------|-------|-----
+29//! `layout` | Keyboard layout name | String
+30//! `variant`| Keyboard variant name or `N/A` if not applicable | String
+31//!
+32//! # Examples
+33//!
+34//! Listen to D-Bus for changes:
+35//!
+36//! ```toml
+37//! [[block]]
+38//! block = "keyboard_layout"
+39//! driver = "localebus"
+40//! ```
+41//!
+42//! Listen to kbdd for changes, the text is in the following format:
+43//! "English (US)" - {$layout ($variant)}
+44//! use block.mappings to override with shorter names as shown below.
+45//! Also use format = " $layout ($variant) " to see the full text to map,
+46//! or you can use:
+47//! dbus-monitor interface=ru.gentoo.kbdd
+48//! to see the exact variant spelling
+49//!
+50//! ```toml
+51//! [[block]]
+52//! block = "keyboard_layout"
+53//! driver = "kbddbus"
+54//! [block.mappings]
+55//! "English (US)" = "us"
+56//! "Bulgarian (new phonetic)" = "bg"
+57//! ```
+58//!
+59//! Listen to sway for changes:
+60//!
+61//! ```toml
+62//! [[block]]
+63//! block = "keyboard_layout"
+64//! driver = "sway"
+65//! sway_kb_identifier = "1133:49706:Gaming_Keyboard_G110"
+66//! ```
+67//!
+68//! Listen to sway for changes and override mappings:
+69//! ```toml
+70//! [[block]]
+71//! block = "keyboard_layout"
+72//! driver = "sway"
+73//! format = " $layout "
+74//! [block.mappings]
+75//! "English (Workman)" = "EN"
+76//! "Russian (N/A)" = "RU"
+77//! ```
+78//!
+79//! Listen to xkb events for changes:
+80//!
+81//! ```toml
+82//! [[block]]
+83//! block = "keyboard_layout"
+84//! driver = "xkbevent"
+85//! ```
+86
+87mod locale_bus;
+88use locale_bus::LocaleBus;
+89
+90mod kbdd_bus;
+91use kbdd_bus::KbddBus;
+92
+93mod sway;
+94use sway::Sway;
+95
+96mod xkb_event;
+97use xkb_event::XkbEvent;
+98
+99use super::prelude::*;
+100
+101#[derive(Deserialize, Debug, SmartDefault)]
+102#[serde(deny_unknown_fields, default)]
+103pub struct Config {
+104    pub format: FormatConfig,
+105    pub driver: KeyboardLayoutDriver,
+106    #[default(60.into())]
+107    pub interval: Seconds,
+108    pub sway_kb_identifier: Option<String>,
+109    pub mappings: Option<HashMap<String, String>>,
+110}
+111
+112#[derive(Deserialize, Debug, SmartDefault, Clone, Copy)]
+113#[serde(rename_all = "lowercase")]
+114pub enum KeyboardLayoutDriver {
+115    #[default]
+116    XkbEvent,
+117    SetXkbMap,
+118    XkbSwitch,
+119    LocaleBus,
+120    KbddBus,
+121    Sway,
+122}
+123
+124pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+125    let format = config.format.with_default(" $layout ")?;
+126
+127    let mut backend: Box<dyn Backend> = match config.driver {
+128        KeyboardLayoutDriver::LocaleBus => Box::new(LocaleBus::new().await?),
+129        KeyboardLayoutDriver::KbddBus => Box::new(KbddBus::new().await?),
+130        KeyboardLayoutDriver::Sway => Box::new(Sway::new(config.sway_kb_identifier.clone()).await?),
+131        KeyboardLayoutDriver::XkbEvent
+132        | KeyboardLayoutDriver::SetXkbMap
+133        | KeyboardLayoutDriver::XkbSwitch => Box::new(XkbEvent::new().await?),
+134    };
+135
+136    loop {
+137        let Info {
+138            mut layout,
+139            variant,
+140        } = backend.get_info().await?;
+141
+142        let variant = variant.unwrap_or_else(|| "N/A".into());
+143        if let Some(mappings) = &config.mappings
+144            && let Some(mapped) = mappings.get(&format!("{layout} ({variant})"))
+145        {
+146            layout.clone_from(mapped);
+147        }
+148
+149        let mut widget = Widget::new().with_format(format.clone());
+150        widget.set_values(map! {
+151            "layout" => Value::text(layout),
+152            "variant" => Value::text(variant),
+153        });
+154        api.set_widget(widget)?;
+155
+156        backend.wait_for_change().await?;
+157    }
+158}
+159
+160#[async_trait]
+161trait Backend {
+162    async fn get_info(&mut self) -> Result<Info>;
+163    async fn wait_for_change(&mut self) -> Result<()>;
+164}
+165
+166#[derive(Clone)]
+167struct Info {
+168    layout: String,
+169    variant: Option<String>,
+170}
+171
+172impl Info {
+173    /// Parse "layout (variant)" string
+174    fn from_layout_variant_str(s: &str) -> Self {
+175        if let Some((layout, rest)) = s.split_once('(') {
+176            Self {
+177                layout: layout.trim_end().into(),
+178                variant: Some(rest.trim_end_matches(')').into()),
+179            }
+180        } else {
+181            Self {
+182                layout: s.into(),
+183                variant: None,
+184            }
+185        }
+186    }
+187}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/keyboard_layout/kbdd_bus.rs.html b/src/i3status_rs/blocks/keyboard_layout/kbdd_bus.rs.html new file mode 100644 index 0000000000..74cbc65dcd --- /dev/null +++ b/src/i3status_rs/blocks/keyboard_layout/kbdd_bus.rs.html @@ -0,0 +1,67 @@ +kbdd_bus.rs - source

i3status_rs/blocks/keyboard_layout/
kbdd_bus.rs

1use super::*;
+2
+3pub(super) struct KbddBus {
+4    stream: layoutNameChangedStream,
+5    info: Info,
+6}
+7
+8impl KbddBus {
+9    pub(super) async fn new() -> Result<Self> {
+10        let conn = new_dbus_connection().await?;
+11        let proxy = KbddBusInterfaceProxy::builder(&conn)
+12            .cache_properties(zbus::proxy::CacheProperties::No)
+13            .build()
+14            .await
+15            .error("Failed to create KbddBusInterfaceProxy")?;
+16        let stream = proxy
+17            .receive_layout_updated()
+18            .await
+19            .error("Failed to monitor kbdd interface")?;
+20        let layout_index = proxy
+21            .current_layout_index()
+22            .await
+23            .error("Failed to get current layout index from kbdd")?;
+24        let current_layout = proxy
+25            .current_layout(layout_index)
+26            .await
+27            .error("Failed to get current layout from kbdd")?;
+28        let info = Info::from_layout_variant_str(&current_layout);
+29        Ok(Self { stream, info })
+30    }
+31}
+32
+33#[async_trait]
+34impl Backend for KbddBus {
+35    async fn get_info(&mut self) -> Result<Info> {
+36        Ok(self.info.clone())
+37    }
+38
+39    async fn wait_for_change(&mut self) -> Result<()> {
+40        let event = self
+41            .stream
+42            .next()
+43            .await
+44            .error("Failed to receive kbdd event from dbus")?;
+45        let args = event
+46            .args()
+47            .error("Failed to get the args from kbdd message")?;
+48        self.info = Info::from_layout_variant_str(args.layout());
+49        Ok(())
+50    }
+51}
+52
+53#[zbus::proxy(
+54    interface = "ru.gentoo.kbdd",
+55    default_service = "ru.gentoo.KbddService",
+56    default_path = "/ru/gentoo/KbddService"
+57)]
+58trait KbddBusInterface {
+59    #[zbus(signal, name = "layoutNameChanged")]
+60    fn layout_updated(&self, layout: String) -> zbus::Result<()>;
+61
+62    #[zbus(name = "getCurrentLayout")]
+63    fn current_layout_index(&self) -> zbus::Result<u32>;
+64
+65    #[zbus(name = "getLayoutName")]
+66    fn current_layout(&self, layout_id: u32) -> zbus::Result<String>;
+67}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/keyboard_layout/locale_bus.rs.html b/src/i3status_rs/blocks/keyboard_layout/locale_bus.rs.html new file mode 100644 index 0000000000..9d91ee0310 --- /dev/null +++ b/src/i3status_rs/blocks/keyboard_layout/locale_bus.rs.html @@ -0,0 +1,57 @@ +locale_bus.rs - source

i3status_rs/blocks/keyboard_layout/
locale_bus.rs

1use super::*;
+2
+3pub(super) struct LocaleBus {
+4    proxy: LocaleBusInterfaceProxy<'static>,
+5    stream1: zbus::proxy::PropertyStream<'static, String>,
+6    stream2: zbus::proxy::PropertyStream<'static, String>,
+7}
+8
+9impl LocaleBus {
+10    pub(super) async fn new() -> Result<Self> {
+11        let conn = new_system_dbus_connection().await?;
+12        let proxy = LocaleBusInterfaceProxy::new(&conn)
+13            .await
+14            .error("Failed to create LocaleBusProxy")?;
+15        let layout_updates = proxy.receive_layout_changed().await;
+16        let variant_updates = proxy.receive_layout_changed().await;
+17        Ok(Self {
+18            proxy,
+19            stream1: layout_updates,
+20            stream2: variant_updates,
+21        })
+22    }
+23}
+24
+25#[async_trait]
+26impl Backend for LocaleBus {
+27    async fn get_info(&mut self) -> Result<Info> {
+28        // zbus does internal caching
+29        let layout = self.proxy.layout().await.error("Failed to get layout")?;
+30        let variant = self.proxy.variant().await.error("Failed to get variant")?;
+31        Ok(Info {
+32            layout,
+33            variant: Some(variant),
+34        })
+35    }
+36
+37    async fn wait_for_change(&mut self) -> Result<()> {
+38        select! {
+39            _ = self.stream1.next() => (),
+40            _ = self.stream2.next() => (),
+41        }
+42        Ok(())
+43    }
+44}
+45
+46#[zbus::proxy(
+47    interface = "org.freedesktop.locale1",
+48    default_service = "org.freedesktop.locale1",
+49    default_path = "/org/freedesktop/locale1"
+50)]
+51trait LocaleBusInterface {
+52    #[zbus(property, name = "X11Layout")]
+53    fn layout(&self) -> zbus::Result<String>;
+54
+55    #[zbus(property, name = "X11Variant")]
+56    fn variant(&self) -> zbus::Result<String>;
+57}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/keyboard_layout/sway.rs.html b/src/i3status_rs/blocks/keyboard_layout/sway.rs.html new file mode 100644 index 0000000000..501f35c6a6 --- /dev/null +++ b/src/i3status_rs/blocks/keyboard_layout/sway.rs.html @@ -0,0 +1,69 @@ +sway.rs - source

i3status_rs/blocks/keyboard_layout/
sway.rs

1// use super::super::prelude::*;
+2use super::*;
+3use swayipc_async::{Connection, Event, EventType};
+4
+5pub(super) struct Sway {
+6    events: swayipc_async::EventStream,
+7    cur_layout: String,
+8    kbd: Option<String>,
+9}
+10
+11impl Sway {
+12    pub(super) async fn new(kbd: Option<String>) -> Result<Self> {
+13        let mut connection = Connection::new()
+14            .await
+15            .error("Failed to open swayipc connection")?;
+16        let cur_layout = connection
+17            .get_inputs()
+18            .await
+19            .error("failed to get current input")?
+20            .iter()
+21            .find_map(|i| {
+22                if i.input_type == "keyboard" && kbd.as_deref().is_none_or(|id| id == i.identifier)
+23                {
+24                    i.xkb_active_layout_name.clone()
+25                } else {
+26                    None
+27                }
+28            })
+29            .error("Failed to get current input")?;
+30        let events = connection
+31            .subscribe(&[EventType::Input])
+32            .await
+33            .error("Failed to subscribe to events")?;
+34        Ok(Self {
+35            events,
+36            cur_layout,
+37            kbd,
+38        })
+39    }
+40}
+41
+42#[async_trait]
+43impl Backend for Sway {
+44    async fn get_info(&mut self) -> Result<Info> {
+45        Ok(Info::from_layout_variant_str(&self.cur_layout))
+46    }
+47
+48    async fn wait_for_change(&mut self) -> Result<()> {
+49        loop {
+50            let event = self
+51                .events
+52                .next()
+53                .await
+54                .error("swayipc channel closed")?
+55                .error("bad event")?;
+56            if let Event::Input(event) = event
+57                && self
+58                    .kbd
+59                    .as_deref()
+60                    .is_none_or(|id| id == event.input.identifier)
+61                && let Some(new_layout) = event.input.xkb_active_layout_name
+62                && new_layout != self.cur_layout
+63            {
+64                self.cur_layout = new_layout;
+65                return Ok(());
+66            }
+67        }
+68    }
+69}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/keyboard_layout/xkb_event.rs.html b/src/i3status_rs/blocks/keyboard_layout/xkb_event.rs.html new file mode 100644 index 0000000000..20e95184a5 --- /dev/null +++ b/src/i3status_rs/blocks/keyboard_layout/xkb_event.rs.html @@ -0,0 +1,143 @@ +xkb_event.rs - source

i3status_rs/blocks/keyboard_layout/
xkb_event.rs

1use super::*;
+2use x11rb_async::{
+3    connection::{Connection as _, RequestConnection as _},
+4    protocol::{
+5        Event,
+6        xkb::{
+7            self, ConnectionExt as _, EventType, ID, MapPart, NameDetail, SelectEventsAux,
+8            UseExtensionReply,
+9        },
+10        xproto::ConnectionExt as _,
+11    },
+12    rust_connection::RustConnection,
+13};
+14
+15const XCB_XKB_MINOR_VERSION: u16 = 0;
+16const XCB_XKB_MAJOR_VERSION: u16 = 1;
+17
+18pub(super) struct XkbEvent {
+19    connection: RustConnection,
+20}
+21
+22fn parse_layout(buf: &[u8], index: usize) -> Result<&str> {
+23    let colon_i = buf.iter().position(|c| *c == b':').unwrap_or(buf.len());
+24    let layout = buf[..colon_i]
+25        .split(|&c| c == b'+')
+26        .skip(1) // layout names start from index 1
+27        .nth(index)
+28        .error("Index out of range")?;
+29    std::str::from_utf8(layout).error("non utf8 layout")
+30}
+31
+32async fn get_layout(connection: &RustConnection) -> Result<String> {
+33    let xkb_state = connection
+34        .xkb_get_state(ID::USE_CORE_KBD.into())
+35        .await
+36        .error("xkb_get_state failed")?
+37        .reply()
+38        .await
+39        .error("xkb_get_state reply failed")?;
+40    let group: u8 = xkb_state.group.into();
+41
+42    let symbols_name = connection
+43        .xkb_get_names(
+44            ID::USE_CORE_KBD.into(),
+45            NameDetail::GROUP_NAMES | NameDetail::SYMBOLS,
+46        )
+47        .await
+48        .error("xkb_get_names failed")?
+49        .reply()
+50        .await
+51        .error("xkb_get_names reply failed")?
+52        .value_list
+53        .symbols_name
+54        .error("symbols_name is empty")?;
+55
+56    let name = connection
+57        .get_atom_name(symbols_name)
+58        .await
+59        .error("get_atom_name failed")?
+60        .reply()
+61        .await
+62        .error("get_atom_name reply failed")?
+63        .name;
+64    let layout = parse_layout(&name, group as _)?;
+65
+66    Ok(layout.to_owned())
+67}
+68
+69async fn prefetch_xkb_extension(connection: &RustConnection) -> Result<UseExtensionReply> {
+70    connection
+71        .prefetch_extension_information(xkb::X11_EXTENSION_NAME)
+72        .await
+73        .error("prefetch_extension_information failed")?;
+74
+75    let reply = connection
+76        .xkb_use_extension(XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION)
+77        .await
+78        .error("xkb_use_extension failed")?
+79        .reply()
+80        .await
+81        .error("xkb_use_extension reply failed")?;
+82
+83    Ok(reply)
+84}
+85
+86impl XkbEvent {
+87    pub(super) async fn new() -> Result<Self> {
+88        let (connection, _, drive) = RustConnection::connect(None)
+89            .await
+90            .error("Failed to open XCB connection")?;
+91
+92        tokio::spawn(drive);
+93        let reply = prefetch_xkb_extension(&connection)
+94            .await
+95            .error("Failed to prefetch xkb extension")?;
+96
+97        if !reply.supported {
+98            return Err(Error::new(
+99                "This program requires the X11 server to support the XKB extension",
+100            ));
+101        }
+102
+103        connection
+104            .xkb_select_events(
+105                ID::USE_CORE_KBD.into(),
+106                EventType::default(),
+107                EventType::STATE_NOTIFY,
+108                MapPart::default(),
+109                MapPart::default(),
+110                &SelectEventsAux::new(),
+111            )
+112            .await
+113            .error("Failed to select events")?;
+114
+115        Ok(XkbEvent { connection })
+116    }
+117}
+118
+119#[async_trait]
+120impl Backend for XkbEvent {
+121    async fn get_info(&mut self) -> Result<Info> {
+122        let cur_layout = get_layout(&self.connection)
+123            .await
+124            .error("Failed to get current layout")?;
+125        Ok(Info::from_layout_variant_str(&cur_layout))
+126    }
+127
+128    async fn wait_for_change(&mut self) -> Result<()> {
+129        loop {
+130            let event = self
+131                .connection
+132                .wait_for_event()
+133                .await
+134                .error("Failed to read the event")?;
+135
+136            if let Event::XkbStateNotify(e) = event
+137                && e.changed.contains(xkb::StatePart::GROUP_STATE)
+138            {
+139                return Ok(());
+140            }
+141        }
+142    }
+143}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/load.rs.html b/src/i3status_rs/blocks/load.rs.html new file mode 100644 index 0000000000..2f0ed8b964 --- /dev/null +++ b/src/i3status_rs/blocks/load.rs.html @@ -0,0 +1,98 @@ +load.rs - source

i3status_rs/blocks/
load.rs

1//! System load average
+2//!
+3//! # Configuration
+4//!
+5//! Key        | Values                                                                                | Default
+6//! -----------|---------------------------------------------------------------------------------------|--------
+7//! `format`   | A string to customise the output of this block. See below for available placeholders. | `" $icon $1m.eng(w:4) "`
+8//! `interval` | Update interval in seconds                                                            | `3`
+9//! `info`     | Minimum load, where state is set to info                                              | `0.3`
+10//! `warning`  | Minimum load, where state is set to warning                                           | `0.6`
+11//! `critical` | Minimum load, where state is set to critical                                          | `0.9`
+12//!
+13//! Placeholder  | Value                  | Type   | Unit
+14//! -------------|------------------------|--------|-----
+15//! `icon`       | A static icon          | Icon   | -
+16//! `1m`         | 1 minute load average  | Number | -
+17//! `5m`         | 5 minute load average  | Number | -
+18//! `15m`        | 15 minute load average | Number | -
+19//!
+20//! # Example
+21//!
+22//! ```toml
+23//! [[block]]
+24//! block = "load"
+25//! format = " $icon 1min avg: $1m.eng(w:4) "
+26//! interval = 1
+27//! ```
+28//!
+29//! # Icons Used
+30//! - `cogs`
+31
+32use super::prelude::*;
+33use crate::util;
+34
+35#[derive(Deserialize, Debug, SmartDefault)]
+36#[serde(deny_unknown_fields, default)]
+37pub struct Config {
+38    pub format: FormatConfig,
+39    #[default(3.into())]
+40    pub interval: Seconds,
+41    #[default(0.3)]
+42    pub info: f64,
+43    #[default(0.6)]
+44    pub warning: f64,
+45    #[default(0.9)]
+46    pub critical: f64,
+47}
+48
+49pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+50    let format = config.format.with_default(" $icon $1m.eng(w:4) ")?;
+51
+52    // borrowed from https://docs.rs/cpuinfo/0.1.1/src/cpuinfo/count/logical.rs.html#4-6
+53    let logical_cores = util::read_file("/proc/cpuinfo")
+54        .await
+55        .error("Your system doesn't support /proc/cpuinfo")?
+56        .lines()
+57        .filter(|l| l.starts_with("processor"))
+58        .count();
+59
+60    loop {
+61        let loadavg = util::read_file("/proc/loadavg")
+62            .await
+63            .error("Your system does not support reading the load average from /proc/loadavg")?;
+64        let mut values = loadavg.split(' ');
+65        let m1: f64 = values
+66            .next()
+67            .and_then(|x| x.parse().ok())
+68            .error("bad /proc/loadavg file")?;
+69        let m5: f64 = values
+70            .next()
+71            .and_then(|x| x.parse().ok())
+72            .error("bad /proc/loadavg file")?;
+73        let m15: f64 = values
+74            .next()
+75            .and_then(|x| x.parse().ok())
+76            .error("bad /proc/loadavg file")?;
+77
+78        let mut widget = Widget::new().with_format(format.clone());
+79        widget.state = match m1 / logical_cores as f64 {
+80            x if x > config.critical => State::Critical,
+81            x if x > config.warning => State::Warning,
+82            x if x > config.info => State::Info,
+83            _ => State::Idle,
+84        };
+85        widget.set_values(map! {
+86            "icon" => Value::icon("cogs"),
+87            "1m" => Value::number(m1),
+88            "5m" => Value::number(m5),
+89            "15m" => Value::number(m15),
+90        });
+91        api.set_widget(widget)?;
+92
+93        select! {
+94            _ = sleep(config.interval.0) => (),
+95            _ = api.wait_for_update_request() => (),
+96        }
+97    }
+98}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/maildir.rs.html b/src/i3status_rs/blocks/maildir.rs.html new file mode 100644 index 0000000000..3624b6cf2c --- /dev/null +++ b/src/i3status_rs/blocks/maildir.rs.html @@ -0,0 +1,110 @@ +maildir.rs - source

i3status_rs/blocks/
maildir.rs

1//! Unread mail. Only supports maildir format.
+2//!
+3//! Note that you need to enable `maildir` feature to use this block:
+4//! ```sh
+5//! cargo build --release --features maildir
+6//! ```
+7//!
+8//! # Configuration
+9//!
+10//! Key | Values | Default
+11//! ----|--------|--------
+12//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $status "`
+13//! `inboxes` | List of maildir inboxes to look for mails in. Supports path/glob expansions (e.g. `~` and `*`). | **Required**
+14//! `threshold_warning` | Number of unread mails where state is set to warning. | `1`
+15//! `threshold_critical` | Number of unread mails where state is set to critical. | `10`
+16//! `interval` | Update interval, in seconds. | `5`
+17//! `display_type` | Which part of the maildir to count: `"new"`, `"cur"`, or `"all"`. | `"new"`
+18//!
+19//! Placeholder  | Value                  | Type   | Unit
+20//! -------------|------------------------|--------|-----
+21//! `icon`       | A static icon          | Icon   | -
+22//! `status`     | Number of emails       | Number | -
+23//!
+24//! # Examples
+25//!
+26//! ```toml
+27//! [[block]]
+28//! block = "maildir"
+29//! interval = 60
+30//! inboxes = ["~/mail/local", "~/maildir/account1/*"]
+31//! threshold_warning = 1
+32//! threshold_critical = 10
+33//! display_type = "new"
+34//! ```
+35//!
+36//! # Icons Used
+37//! - `mail`
+38
+39use super::prelude::*;
+40use maildir::Maildir;
+41use std::path::PathBuf;
+42
+43#[derive(Deserialize, Debug, SmartDefault)]
+44#[serde(deny_unknown_fields, default)]
+45pub struct Config {
+46    pub format: FormatConfig,
+47    #[default(5.into())]
+48    pub interval: Seconds,
+49    pub inboxes: Vec<String>,
+50    #[default(1)]
+51    pub threshold_warning: usize,
+52    #[default(10)]
+53    pub threshold_critical: usize,
+54    #[default(MailType::New)]
+55    pub display_type: MailType,
+56}
+57
+58fn expand_inbox(inbox: &str) -> Result<impl Iterator<Item = PathBuf>> {
+59    let expanded = shellexpand::full(inbox).error("Failed to expand inbox")?;
+60    let paths = glob::glob(&expanded).error("Glob expansion failed")?;
+61    Ok(paths.filter_map(|p| p.ok()))
+62}
+63
+64pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+65    let format = config.format.with_default(" $icon $status ")?;
+66
+67    let mut inboxes = Vec::with_capacity(config.inboxes.len());
+68    for inbox in &config.inboxes {
+69        inboxes.extend(expand_inbox(inbox)?.map(Maildir::from));
+70    }
+71
+72    loop {
+73        let mut newmails = 0;
+74        for inbox in &inboxes {
+75            // TODO: spawn_blocking?
+76            newmails += match config.display_type {
+77                MailType::New => inbox.count_new(),
+78                MailType::Cur => inbox.count_cur(),
+79                MailType::All => inbox.count_new() + inbox.count_cur(),
+80            };
+81        }
+82
+83        let mut widget = Widget::new().with_format(format.clone());
+84        widget.state = if newmails >= config.threshold_critical {
+85            State::Critical
+86        } else if newmails >= config.threshold_warning {
+87            State::Warning
+88        } else {
+89            State::Idle
+90        };
+91        widget.set_values(map!(
+92            "icon" => Value::icon("mail"),
+93            "status" => Value::number(newmails)
+94        ));
+95        api.set_widget(widget)?;
+96
+97        select! {
+98            _ = sleep(config.interval.0) => (),
+99            _ = api.wait_for_update_request() => (),
+100        }
+101    }
+102}
+103
+104#[derive(Clone, Debug, Deserialize)]
+105#[serde(rename_all = "lowercase")]
+106pub enum MailType {
+107    New,
+108    Cur,
+109    All,
+110}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/memory.rs.html b/src/i3status_rs/blocks/memory.rs.html new file mode 100644 index 0000000000..2af57e9efe --- /dev/null +++ b/src/i3status_rs/blocks/memory.rs.html @@ -0,0 +1,385 @@ +memory.rs - source

i3status_rs/blocks/
memory.rs

1//! Memory and swap usage
+2//!
+3//! # Configuration
+4//!
+5//! Key | Values | Default
+6//! ----|--------|--------
+7//! `format` | A string to customise the output of this block when in "Memory" view. See below for available placeholders. | `" $icon $mem_used.eng(prefix:Mi)/$mem_total.eng(prefix:Mi)($mem_used_percents.eng(w:2)) "`
+8//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None`
+9//! `interval` | Update interval in seconds | `5`
+10//! `warning_mem` | Percentage of memory usage, where state is set to warning | `80.0`
+11//! `warning_swap` | Percentage of swap usage, where state is set to warning | `80.0`
+12//! `critical_mem` | Percentage of memory usage, where state is set to critical | `95.0`
+13//! `critical_swap` | Percentage of swap usage, where state is set to critical | `95.0`
+14//!
+15//! Placeholder               | Value                                                                           | Type   | Unit
+16//! --------------------------|---------------------------------------------------------------------------------|--------|-------
+17//! `icon`                    | Memory icon                                                                     | Icon   | -
+18//! `icon_swap`               | Swap icon                                                                       | Icon   | -
+19//! `mem_total`               | Total physical ram available                                                    | Number | Bytes
+20//! `mem_free`                | Free memory not yet used by the kernel or userspace (in general you should use mem_avail) | Number | Bytes
+21//! `mem_free_percents`       | as above but as a percentage of total memory                                    | Number | Percents
+22//! `mem_avail`               | Kernel estimate of usable free memory which includes cached memory and buffers  | Number | Bytes
+23//! `mem_avail_percents`      | as above but as a percentage of total memory                                    | Number | Percents
+24//! `mem_total_used`          | mem_total - mem_free                                                            | Number | Bytes
+25//! `mem_total_used_percents` | as above but as a percentage of total memory                                    | Number | Percents
+26//! `mem_used`                | Memory used, excluding cached memory and buffers; same as htop's green bar      | Number | Bytes
+27//! `mem_used_percents`       | as above but as a percentage of total memory                                    | Number | Percents
+28//! `buffers`                 | Buffers, similar to htop's blue bar                                             | Number | Bytes
+29//! `buffers_percent`         | as above but as a percentage of total memory                                    | Number | Percents
+30//! `cached`                  | Cached memory (taking into account ZFS ARC cache), similar to htop's yellow bar | Number | Bytes
+31//! `cached_percent`          | as above but as a percentage of total memory                                    | Number | Percents
+32//! `swap_total`              | Swap total                                                                      | Number | Bytes
+33//! `swap_free`               | Swap free                                                                       | Number | Bytes
+34//! `swap_free_percents`      | as above but as a percentage of total memory                                    | Number | Percents
+35//! `swap_used`               | Swap used                                                                       | Number | Bytes
+36//! `swap_used_percents`      | as above but as a percentage of total memory                                    | Number | Percents
+37//! `zram_compressed`         | Compressed zram memory usage                                                    | Number | Bytes
+38//! `zram_decompressed`       | Decompressed zram memory usage                                                  | Number | Bytes
+39//! 'zram_comp_ratio'         | Ratio of the decompressed/compressed zram memory                                | Number | -
+40//! `zswap_compressed`        | Compressed zswap memory usage (>=Linux 5.19)                                    | Number | Bytes
+41//! `zswap_decompressed`      | Decompressed zswap memory usage (>=Linux 5.19)                                  | Number | Bytes
+42//! `zswap_decompressed_percents` | as above but as a percentage of total zswap memory  (>=Linux 5.19)          | Number | Percents
+43//! 'zswap_comp_ratio'        | Ratio of the decompressed/compressed zswap memory (>=Linux 5.19)                | Number | -
+44//!
+45//! Action          | Description                               | Default button
+46//! ----------------|-------------------------------------------|---------------
+47//! `toggle_format` | Toggles between `format` and `format_alt` | Left
+48//!
+49//! # Examples
+50//!
+51//! ```toml
+52//! [[block]]
+53//! block = "memory"
+54//! format = " $icon $mem_used_percents.eng(w:1) "
+55//! format_alt = " $icon_swap $swap_free.eng(w:3,u:B,p:Mi)/$swap_total.eng(w:3,u:B,p:Mi)($swap_used_percents.eng(w:2)) "
+56//! interval = 30
+57//! warning_mem = 70
+58//! critical_mem = 90
+59//! ```
+60//!
+61//! Show swap and hide if it is zero:
+62//!
+63//! ```toml
+64//! [[block]]
+65//! block = "memory"
+66//! format = " $icon $swap_used.eng(range:1..) |"
+67//! ```
+68//!
+69//! # Icons Used
+70//! - `memory_mem`
+71//! - `memory_swap`
+72
+73use std::cmp::min;
+74use std::str::FromStr as _;
+75use tokio::fs::{File, read_dir};
+76use tokio::io::{AsyncBufReadExt as _, BufReader};
+77
+78use super::prelude::*;
+79use crate::util::read_file;
+80
+81#[derive(Deserialize, Debug, SmartDefault)]
+82#[serde(deny_unknown_fields, default)]
+83pub struct Config {
+84    pub format: FormatConfig,
+85    pub format_alt: Option<FormatConfig>,
+86    #[default(5.into())]
+87    pub interval: Seconds,
+88    #[default(80.0)]
+89    pub warning_mem: f64,
+90    #[default(80.0)]
+91    pub warning_swap: f64,
+92    #[default(95.0)]
+93    pub critical_mem: f64,
+94    #[default(95.0)]
+95    pub critical_swap: f64,
+96}
+97
+98pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+99    let mut actions = api.get_actions()?;
+100    api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
+101
+102    let mut format = config.format.with_default(
+103        " $icon $mem_used.eng(prefix:Mi)/$mem_total.eng(prefix:Mi)($mem_used_percents.eng(w:2)) ",
+104    )?;
+105    let mut format_alt = match &config.format_alt {
+106        Some(f) => Some(f.with_default("")?),
+107        None => None,
+108    };
+109
+110    let mut timer = config.interval.timer();
+111
+112    loop {
+113        let mem_state = Memstate::new().await?;
+114
+115        let mem_total = mem_state.mem_total as f64 * 1024.;
+116        let mem_free = mem_state.mem_free as f64 * 1024.;
+117
+118        // TODO: possibly remove this as it is confusing to have `mem_total_used` and `mem_used`
+119        // htop and such only display equivalent of `mem_used`
+120        let mem_total_used = mem_total - mem_free;
+121
+122        // dev note: difference between avail and free:
+123        // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
+124        // same logic as htop
+125        let mem_avail = if mem_state.mem_available != 0 {
+126            min(mem_state.mem_available, mem_state.mem_total)
+127        } else {
+128            mem_state.mem_free
+129        } as f64
+130            * 1024.;
+131
+132        // While zfs_arc_cache can be considered "available" memory,
+133        // it can only free a maximum of (zfs_arc_cache - zfs_arc_min) amount.
+134        // see https://github.com/htop-dev/htop/pull/1003
+135        let zfs_shrinkable_size = mem_state
+136            .zfs_arc_cache
+137            .saturating_sub(mem_state.zfs_arc_min) as f64;
+138        let mem_avail = mem_avail + zfs_shrinkable_size;
+139
+140        let pagecache = mem_state.pagecache as f64 * 1024.;
+141        let reclaimable = mem_state.s_reclaimable as f64 * 1024.;
+142        let shmem = mem_state.shmem as f64 * 1024.;
+143
+144        // See https://lore.kernel.org/lkml/1455827801-13082-1-git-send-email-hannes@cmpxchg.org/
+145        let cached = pagecache + reclaimable - shmem + zfs_shrinkable_size;
+146
+147        let buffers = mem_state.buffers as f64 * 1024.;
+148
+149        // same logic as htop
+150        let used_diff = mem_free + buffers + pagecache + reclaimable;
+151        let mem_used = if mem_total >= used_diff {
+152            mem_total - used_diff
+153        } else {
+154            mem_total - mem_free
+155        };
+156
+157        // account for ZFS ARC cache
+158        let mem_used = mem_used - zfs_shrinkable_size;
+159
+160        let swap_total = mem_state.swap_total as f64 * 1024.;
+161        let swap_free = mem_state.swap_free as f64 * 1024.;
+162        let swap_cached = mem_state.swap_cached as f64 * 1024.;
+163        let swap_used = swap_total - swap_free - swap_cached;
+164
+165        // Zswap usage
+166        let zswap_compressed = mem_state.zswap_compressed as f64 * 1024.;
+167        let zswap_decompressed = mem_state.zswap_decompressed as f64 * 1024.;
+168
+169        let zswap_comp_ratio = if zswap_compressed != 0.0 {
+170            zswap_decompressed / zswap_compressed
+171        } else {
+172            0.0
+173        };
+174        let zswap_decompressed_percents = if (swap_used + swap_cached) != 0.0 {
+175            zswap_decompressed / (swap_used + swap_cached) * 100.0
+176        } else {
+177            0.0
+178        };
+179
+180        // Zram usage
+181        let zram_compressed = mem_state.zram_compressed as f64;
+182        let zram_decompressed = mem_state.zram_decompressed as f64;
+183
+184        let zram_comp_ratio = if zram_compressed != 0.0 {
+185            zram_decompressed / zram_compressed
+186        } else {
+187            0.0
+188        };
+189
+190        let mut widget = Widget::new().with_format(format.clone());
+191        widget.set_values(map! {
+192            "icon" => Value::icon("memory_mem"),
+193            "icon_swap" => Value::icon("memory_swap"),
+194            "mem_total" => Value::bytes(mem_total),
+195            "mem_free" => Value::bytes(mem_free),
+196            "mem_free_percents" => Value::percents(mem_free / mem_total * 100.),
+197            "mem_total_used" => Value::bytes(mem_total_used),
+198            "mem_total_used_percents" => Value::percents(mem_total_used / mem_total * 100.),
+199            "mem_used" => Value::bytes(mem_used),
+200            "mem_used_percents" => Value::percents(mem_used / mem_total * 100.),
+201            "mem_avail" => Value::bytes(mem_avail),
+202            "mem_avail_percents" => Value::percents(mem_avail / mem_total * 100.),
+203            "swap_total" => Value::bytes(swap_total),
+204            "swap_free" => Value::bytes(swap_free),
+205            "swap_free_percents" => Value::percents(swap_free / swap_total * 100.),
+206            "swap_used" => Value::bytes(swap_used),
+207            "swap_used_percents" => Value::percents(swap_used / swap_total * 100.),
+208            "buffers" => Value::bytes(buffers),
+209            "buffers_percent" => Value::percents(buffers / mem_total * 100.),
+210            "cached" => Value::bytes(cached),
+211            "cached_percent" => Value::percents(cached / mem_total * 100.),
+212            "zram_compressed" => Value::bytes(zram_compressed),
+213            "zram_decompressed" => Value::bytes(zram_decompressed),
+214            "zram_comp_ratio" => Value::number(zram_comp_ratio),
+215            "zswap_compressed" => Value::bytes(zswap_compressed),
+216            "zswap_decompressed" => Value::bytes(zswap_decompressed),
+217            "zswap_decompressed_percents" => Value::percents(zswap_decompressed_percents),
+218            "zswap_comp_ratio" => Value::number(zswap_comp_ratio),
+219        });
+220
+221        let mem_state = match mem_used / mem_total * 100. {
+222            x if x > config.critical_mem => State::Critical,
+223            x if x > config.warning_mem => State::Warning,
+224            _ => State::Idle,
+225        };
+226
+227        let swap_state = match swap_used / swap_total * 100. {
+228            x if x > config.critical_swap => State::Critical,
+229            x if x > config.warning_swap => State::Warning,
+230            _ => State::Idle,
+231        };
+232
+233        widget.state = if mem_state == State::Critical || swap_state == State::Critical {
+234            State::Critical
+235        } else if mem_state == State::Warning || swap_state == State::Warning {
+236            State::Warning
+237        } else {
+238            State::Idle
+239        };
+240
+241        api.set_widget(widget)?;
+242
+243        loop {
+244            select! {
+245                _ = timer.tick() => break,
+246                _ = api.wait_for_update_request() => break,
+247                Some(action) = actions.recv() => match action.as_ref() {
+248                    "toggle_format" => {
+249                        if let Some(ref mut format_alt) = format_alt {
+250                            std::mem::swap(format_alt, &mut format);
+251                            break;
+252                        }
+253                    }
+254                    _ => (),
+255                }
+256            }
+257        }
+258    }
+259}
+260
+261#[derive(Clone, Copy, Debug, Default)]
+262struct Memstate {
+263    mem_total: u64,
+264    mem_free: u64,
+265    mem_available: u64,
+266    buffers: u64,
+267    pagecache: u64,
+268    s_reclaimable: u64,
+269    shmem: u64,
+270    swap_total: u64,
+271    swap_free: u64,
+272    swap_cached: u64,
+273    zram_compressed: u64,
+274    zram_decompressed: u64,
+275    zswap_compressed: u64,
+276    zswap_decompressed: u64,
+277    zfs_arc_cache: u64,
+278    zfs_arc_min: u64,
+279}
+280
+281impl Memstate {
+282    async fn new() -> Result<Self> {
+283        // Reference: https://www.kernel.org/doc/Documentation/filesystems/proc.txt
+284        let mut file = BufReader::new(
+285            File::open("/proc/meminfo")
+286                .await
+287                .error("/proc/meminfo does not exist")?,
+288        );
+289
+290        let mut mem_state = Memstate::default();
+291        let mut line = String::new();
+292
+293        while file
+294            .read_line(&mut line)
+295            .await
+296            .error("failed to read /proc/meminfo")?
+297            != 0
+298        {
+299            let mut words = line.split_whitespace();
+300
+301            let name = match words.next() {
+302                Some(name) => name,
+303                None => {
+304                    line.clear();
+305                    continue;
+306                }
+307            };
+308            let val = words
+309                .next()
+310                .and_then(|x| u64::from_str(x).ok())
+311                .error("failed to parse /proc/meminfo")?;
+312
+313            match name {
+314                "MemTotal:" => mem_state.mem_total = val,
+315                "MemFree:" => mem_state.mem_free = val,
+316                "MemAvailable:" => mem_state.mem_available = val,
+317                "Buffers:" => mem_state.buffers = val,
+318                "Cached:" => mem_state.pagecache = val,
+319                "SReclaimable:" => mem_state.s_reclaimable = val,
+320                "Shmem:" => mem_state.shmem = val,
+321                "SwapTotal:" => mem_state.swap_total = val,
+322                "SwapFree:" => mem_state.swap_free = val,
+323                "SwapCached:" => mem_state.swap_cached = val,
+324                "Zswap:" => mem_state.zswap_compressed = val,
+325                "Zswapped:" => mem_state.zswap_decompressed = val,
+326                _ => (),
+327            }
+328
+329            line.clear();
+330        }
+331
+332        // For ZRAM
+333        let mut entries = read_dir("/sys/block/")
+334            .await
+335            .error("Could not read /sys/block")?;
+336        while let Some(entry) = entries
+337            .next_entry()
+338            .await
+339            .error("Could not get next file /sys/block")?
+340        {
+341            let Ok(file_name) = entry.file_name().into_string() else {
+342                continue;
+343            };
+344            if !file_name.starts_with("zram") {
+345                continue;
+346            }
+347
+348            let zram_file_path = entry.path().join("mm_stat");
+349            let Ok(file) = File::open(zram_file_path).await else {
+350                continue;
+351            };
+352
+353            let mut buf = BufReader::new(file);
+354            let mut line = String::new();
+355            if buf.read_to_string(&mut line).await.is_err() {
+356                continue;
+357            }
+358
+359            let mut values = line.split_whitespace().map(|s| s.parse::<u64>());
+360            if let Some(Ok(zram_swap_size)) = values.next() && let Some(Ok(zram_comp_size)) = values.next()
+361                // zram initializes with small amount by default, return 0 then
+362                && zram_swap_size >= 65_536
+363            {
+364                mem_state.zram_decompressed += zram_swap_size;
+365                mem_state.zram_compressed += zram_comp_size;
+366            }
+367        }
+368
+369        // For ZFS
+370        if let Ok(arcstats) = read_file("/proc/spl/kstat/zfs/arcstats").await {
+371            let size_re = regex!(r"size\s+\d+\s+(\d+)");
+372            let size = &size_re
+373                .captures(&arcstats)
+374                .error("failed to find zfs_arc_cache size")?[1];
+375            mem_state.zfs_arc_cache = size.parse().error("failed to parse zfs_arc_cache size")?;
+376            let c_min_re = regex!(r"c_min\s+\d+\s+(\d+)");
+377            let c_min = &c_min_re
+378                .captures(&arcstats)
+379                .error("failed to find zfs_arc_min size")?[1];
+380            mem_state.zfs_arc_min = c_min.parse().error("failed to parse zfs_arc_min size")?;
+381        }
+382
+383        Ok(mem_state)
+384    }
+385}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/menu.rs.html b/src/i3status_rs/blocks/menu.rs.html new file mode 100644 index 0000000000..ab7e675f2b --- /dev/null +++ b/src/i3status_rs/blocks/menu.rs.html @@ -0,0 +1,124 @@ +menu.rs - source

i3status_rs/blocks/
menu.rs

1//! A custom menu
+2//!
+3//! This block allows you to quickly run a custom shell command. Left-click on this block to
+4//! activate it, then scroll through configured items. Left-click on the item to run it and
+5//! optionally confirm your action by left-clicking again. Right-click any time to deactivate this
+6//! block.
+7//!
+8//! # Configuration
+9//!
+10//! Key | Values | Default
+11//! ----|--------|--------
+12//! `text` | Text that will be displayed when the block is inactive. | **Required**
+13//! `items` | A list of "items". See examples below. | **Required**
+14//!
+15//! # Example
+16//!
+17//! ```toml
+18//! [[block]]
+19//! block = "menu"
+20//! text = "\uf011"
+21//! [[block.items]]
+22//! display = " -&gt;   Sleep   &lt;-"
+23//! cmd = "systemctl suspend"
+24//! [[block.items]]
+25//! display = " -&gt; Power Off &lt;-"
+26//! cmd = "poweroff"
+27//! confirm_msg = "Are you sure you want to power off?"
+28//! [[block.items]]
+29//! display = " -&gt;  Reboot   &lt;-"
+30//! cmd = "reboot"
+31//! confirm_msg = "Are you sure you want to reboot?"
+32//! ```
+33
+34use tokio::sync::mpsc::UnboundedReceiver;
+35
+36use super::{BlockAction, prelude::*};
+37use crate::subprocess::spawn_shell;
+38
+39#[derive(Deserialize, Debug)]
+40#[serde(deny_unknown_fields)]
+41pub struct Config {
+42    pub text: String,
+43    pub items: Vec<Item>,
+44}
+45
+46#[derive(Deserialize, Debug, Clone)]
+47#[serde(deny_unknown_fields)]
+48pub struct Item {
+49    pub display: String,
+50    pub cmd: String,
+51    #[serde(default)]
+52    pub confirm_msg: Option<String>,
+53}
+54
+55struct Block<'a> {
+56    actions: UnboundedReceiver<BlockAction>,
+57    api: &'a CommonApi,
+58    text: &'a str,
+59    items: &'a [Item],
+60}
+61
+62impl Block<'_> {
+63    async fn reset(&mut self) -> Result<()> {
+64        self.set_text(self.text.to_owned()).await
+65    }
+66
+67    async fn set_text(&mut self, text: String) -> Result<()> {
+68        self.api.set_widget(Widget::new().with_text(text))
+69    }
+70
+71    async fn wait_for_click(&mut self, button: &str) -> Result<()> {
+72        while self.actions.recv().await.error("channel closed")? != button {}
+73        Ok(())
+74    }
+75
+76    async fn run_menu(&mut self) -> Result<Option<Item>> {
+77        let mut index = 0;
+78        loop {
+79            self.set_text(self.items[index].display.clone()).await?;
+80            match &*self.actions.recv().await.error("channel closed")? {
+81                "_up" => index += 1,
+82                "_down" => index += self.items.len() + 1,
+83                "_left" => return Ok(Some(self.items[index].clone())),
+84                "_right" => return Ok(None),
+85                _ => (),
+86            }
+87            index %= self.items.len();
+88        }
+89    }
+90
+91    async fn confirm(&mut self, msg: String) -> Result<bool> {
+92        self.set_text(msg).await?;
+93        Ok(self.actions.recv().await.as_deref() == Some("_left"))
+94    }
+95}
+96
+97pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+98    api.set_default_actions(&[
+99        (MouseButton::Left, None, "_left"),
+100        (MouseButton::Right, None, "_right"),
+101        (MouseButton::WheelUp, None, "_up"),
+102        (MouseButton::WheelDown, None, "_down"),
+103    ])?;
+104
+105    let mut block = Block {
+106        actions: api.get_actions()?,
+107        api,
+108        text: &config.text,
+109        items: &config.items,
+110    };
+111
+112    loop {
+113        block.reset().await?;
+114        block.wait_for_click("_left").await?;
+115        if let Some(res) = block.run_menu().await? {
+116            if let Some(msg) = res.confirm_msg
+117                && !block.confirm(msg).await?
+118            {
+119                continue;
+120            }
+121            spawn_shell(&res.cmd).or_error(|| format!("Failed to run '{}'", res.cmd))?;
+122        }
+123    }
+124}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/music.rs.html b/src/i3status_rs/blocks/music.rs.html new file mode 100644 index 0000000000..7955f1d6da --- /dev/null +++ b/src/i3status_rs/blocks/music.rs.html @@ -0,0 +1,702 @@ +music.rs - source

i3status_rs/blocks/
music.rs

1//! The current song title and artist
+2//!
+3//! Also provides buttons for play/pause, previous and next.
+4//!
+5//! Supports all music players that implement the [MediaPlayer2 Interface]. This includes:
+6//!
+7//! - Spotify
+8//! - VLC
+9//! - mpd (via [mpDris2](https://github.com/eonpatapon/mpDris2))
+10//!
+11//! and many others.
+12//!
+13//! By default the block tracks all players available on the MPRIS bus. Right clicking on the block
+14//! will cycle it to the next player. You can pin the widget to a given player via the "player"
+15//! setting.
+16//!
+17//! # Configuration
+18//!
+19//! Key | Values | Default
+20//! ----|--------|--------
+21//! `format` | A string to customise the output of this block. See below for available placeholders. | <code>\" $icon {$combo.str(max_w:25,rot_interval:0.5) $play \|}\"</code>
+22//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None`
+23//! `player` | Name(s) of the music player(s) MPRIS interface. This can be either a music player name or an array of music player names. Run <code>busctl \--user list \| grep \"org.mpris.MediaPlayer2.\" \| cut -d\' \' -f1</code> and the name is the part after "org.mpris.MediaPlayer2.". | `None`
+24//! `interface_name_exclude` | A list of regex patterns for player MPRIS interface names to ignore. | `["playerctld"]`
+25//! `separator` | String to insert between artist and title. | `" - "`
+26//! `seek_step_secs` | Positive number of seconds to seek forward/backward when scrolling on the bar. Does not need to be an integer. | `1`
+27//! `seek_forward_step_secs` | Positive number of seconds to seek forward when scrolling on the bar. Does not need to be an integer. | `seek_step_secs`
+28//! `seek_backward_step_secs` | Positive number of seconds to seek backward when scrolling on the bar. Does not need to be an integer. | `seek_step_secs`
+29//! `volume_step` | The percent volume level is increased/decreased for the selected audio device when scrolling. Capped automatically at 50. | `5`
+30//!
+31//! Note: All placeholders except `icon` can be absent. See the examples below to learn how to handle this.
+32//!
+33//! Placeholder   | Value          | Type
+34//! --------------|----------------|------
+35//! `icon`        | A static icon  | Icon
+36//! `artist`      | Current artist | Text
+37//! `title`       | Current title  | Text
+38//! `url`         | Current song url | Text
+39//! `combo`       | Resolves to "`$artist[sep]$title"`, `"$artist"`, `"$title"`, or `"$url"` depending on what information is available. `[sep]` is set by `separator` option. | Text
+40//! `player`      | Name of the current player (taken from the last part of its MPRIS bus name) | Text
+41//! `avail`       | Total number of players available to switch between | Number
+42//! `cur`         | The current player index of the available players | Number
+43//! `play`        | Play/Pause button | Clickable icon
+44//! `next`        | Next button | Clickable icon
+45//! `prev`        | Previous button | Clickable icon
+46//! `volume_icon` | Icon based on volume. Missing if unsupported.    | Icon
+47//! `volume`      | Current volume. Missing if muted or unsupported. | Number
+48//!
+49//! Widget           | Placeholder
+50//! -----------------|-------------
+51//! `play_pause_btn` | `$play`
+52//! `next_btn`       | `$next`
+53//! `prev_btn`       | `$prev`
+54//!
+55//! Action          | Default button
+56//! ----------------|------------------
+57//! `play_pause`    | Left on `play_pause_btn`
+58//! `next`          | Left on `next_btn`
+59//! `prev`          | Left on `prev_btn`
+60//! `next_player`   | Right
+61//! `seek_forward`  | Wheel Up
+62//! `seek_backward` | Wheel Down
+63//! `volume_up`     | -
+64//! `volume_down`   | -
+65//! `toggle_format` | Left
+66//!
+67//! # Examples
+68//!
+69//! Show the currently playing song on Spotify only, with play & next buttons and limit the width
+70//! to 20 characters:
+71//!
+72//! ```toml
+73//! [[block]]
+74//! block = "music"
+75//! format = " $icon {$combo.str(max_w:20) $play $next |}"
+76//! player = "spotify"
+77//! ```
+78//!
+79//! Same thing for any compatible player, takes the first active on the bus, but ignores "mpd" or anything with "kdeconnect" in the name:
+80//!
+81//! ```toml
+82//! [[block]]
+83//! block = "music"
+84//! format = " $icon {$combo.str(max_w:20) $play $next |}"
+85//! interface_name_exclude = [".*kdeconnect.*", "mpd"]
+86//! ```
+87//!
+88//! Same as above, but displays with rotating text
+89//!
+90//! ```toml
+91//! [[block]]
+92//! block = "music"
+93//! format = " $icon {$combo.str(max_w:20,rot_interval:0.5) $play $next |}"
+94//! interface_name_exclude = [".*kdeconnect.*", "mpd"]
+95//! ```
+96//!
+97//! Click anywhere to play/pause, middle click to toggle format:
+98//!
+99//! ```toml
+100//! [[block]]
+101//! block = "music"
+102//! format = " format 1 "
+103//! format_alt = " format 2 "
+104//! [[block.click]]
+105//! button = "left"
+106//! action = "play_pause"
+107//! [[block.click]]
+108//! button = "middle"
+109//! widget = "."
+110//! action = "toggle_format"
+111//! ```
+112//!
+113//! Scroll to change the player volume, use the forward and back buttons to seek:
+114//!
+115//! ```toml
+116//! [[block]]
+117//! block = "music"
+118//! format = " $icon $volume_icon $combo $play $next| "
+119//! seek_step_secs = 10
+120//! [[block.click]]
+121//! button = "up"
+122//! action = "volume_up"
+123//! [[block.click]]
+124//! button = "down"
+125//! action = "volume_down"
+126//! [[block.click]]
+127//! button = "forward"
+128//! action = "seek_forward"
+129//! [[block.click]]
+130//! button = "back"
+131//! action = "seek_backward"
+132//! ```
+133//!
+134//! # Icons Used
+135//! - `music`
+136//! - `music_next`
+137//! - `music_play`
+138//! - `music_prev`
+139//! - `volume_muted`
+140//! - `volume` (as a progression)
+141//!
+142//! [MediaPlayer2 Interface]: https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html
+143
+144use super::prelude::*;
+145use crate::wrappers::DisplaySlice;
+146
+147use regex::Regex;
+148use std::fmt;
+149use zbus::fdo::{DBusProxy, NameOwnerChanged, PropertiesChanged};
+150use zbus::names::{OwnedBusName, OwnedUniqueName};
+151use zbus::{MatchRule, MessageStream};
+152
+153mod zbus_mpris;
+154mod zbus_playerctld;
+155
+156make_log_macro!(debug, "music");
+157
+158const PLAY_PAUSE_BTN: &str = "play_pause_btn";
+159const NEXT_BTN: &str = "next_btn";
+160const PREV_BTN: &str = "prev_btn";
+161
+162#[derive(Deserialize, Debug, SmartDefault)]
+163#[serde(deny_unknown_fields, default)]
+164pub struct Config {
+165    pub format: FormatConfig,
+166    pub format_alt: Option<FormatConfig>,
+167    pub player: PlayerName,
+168    #[default(vec!["playerctld".into()])]
+169    pub interface_name_exclude: Vec<String>,
+170    #[default(" - ".into())]
+171    pub separator: String,
+172    #[default(1.into())]
+173    pub seek_step_secs: Seconds<false>,
+174    pub seek_forward_step_secs: Option<Seconds<false>>,
+175    pub seek_backward_step_secs: Option<Seconds<false>>,
+176    #[default(5.0)]
+177    pub volume_step: f64,
+178}
+179
+180#[derive(Deserialize, Debug, Clone, SmartDefault)]
+181#[serde(untagged)]
+182pub enum PlayerName {
+183    Single(String),
+184    #[default]
+185    Multiple(Vec<String>),
+186}
+187
+188pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+189    let mut actions = api.get_actions()?;
+190    api.set_default_actions(&[
+191        (MouseButton::Left, Some(PLAY_PAUSE_BTN), "play_pause"),
+192        (MouseButton::Left, Some(NEXT_BTN), "next"),
+193        (MouseButton::Left, Some(PREV_BTN), "prev"),
+194        (MouseButton::Right, None, "next_player"),
+195        (MouseButton::WheelUp, None, "seek_forward"),
+196        (MouseButton::WheelDown, None, "seek_backward"),
+197        (MouseButton::Left, None, "toggle_format"),
+198    ])?;
+199
+200    let dbus_conn = new_dbus_connection().await?;
+201
+202    let mut format = config
+203        .format
+204        .with_default(" $icon {$combo.str(max_w:25,rot_interval:0.5) $play |}")?;
+205    let mut format_alt = match &config.format_alt {
+206        Some(f) => Some(f.with_default("")?),
+207        None => None,
+208    };
+209
+210    let volume_step = config.volume_step.clamp(0.0, 50.0) / 100.0;
+211
+212    let seek_forward_step = config
+213        .seek_forward_step_secs
+214        .unwrap_or(config.seek_step_secs)
+215        .0
+216        .as_micros() as i64;
+217    let seek_backward_step = -(config
+218        .seek_backward_step_secs
+219        .unwrap_or(config.seek_step_secs)
+220        .0
+221        .as_micros() as i64);
+222
+223    let new_btn = |icon: &str, instance: &'static str| -> Result<Value> {
+224        Ok(Value::icon(icon.to_string()).with_instance(instance))
+225    };
+226
+227    let values = map! {
+228        "icon" => Value::icon("music"),
+229        "next" => new_btn("music_next", NEXT_BTN)?,
+230        "prev" => new_btn("music_prev", PREV_BTN)?,
+231    };
+232
+233    let preferred_players = match config.player.clone() {
+234        PlayerName::Single(name) => vec![name],
+235        PlayerName::Multiple(names) => names,
+236    };
+237    let exclude_regex = config
+238        .interface_name_exclude
+239        .iter()
+240        .map(|r| Regex::new(r))
+241        .collect::<Result<Vec<_>, _>>()
+242        .error("Invalid regex")?;
+243
+244    let playerctld_proxy = zbus_playerctld::PlayerctldProxy::new(&dbus_conn)
+245        .await
+246        .error("Failed to create PlayerctldProxy")?;
+247
+248    let mut players = get_players(&dbus_conn, &preferred_players, &exclude_regex).await?;
+249    let mut cur_player = None;
+250    if let Ok(playerctld_players) = playerctld_proxy.player_names().await {
+251        // If we can get the list of players from playerctld then we should
+252        // take the first matching player (this is the most recently active player)
+253        for playerctld_player in playerctld_players {
+254            if let Some(pos) = players
+255                .iter()
+256                .position(|p| p.bus_name.as_str() == playerctld_player)
+257            {
+258                cur_player = Some(pos);
+259                break;
+260            }
+261        }
+262    } else {
+263        // If we couldn't get the players from playerctld then fall back to walking over
+264        // the players and select the first one found playing something, or the last one
+265        // in the list (the most recently opened)
+266        for (i, player) in players.iter().enumerate() {
+267            cur_player = Some(i);
+268            if player.status == Some(PlaybackStatus::Playing) {
+269                break;
+270            }
+271        }
+272    }
+273
+274    let mut properties_stream = MessageStream::for_match_rule(
+275        MatchRule::builder()
+276            .msg_type(zbus::message::Type::Signal)
+277            .interface("org.freedesktop.DBus.Properties")
+278            .and_then(|x| x.member("PropertiesChanged"))
+279            .and_then(|x| x.path("/org/mpris/MediaPlayer2"))
+280            .unwrap()
+281            .build(),
+282        &dbus_conn,
+283        None,
+284    )
+285    .await
+286    .error("Failed to add match rule")?;
+287
+288    let mut name_owner_changed_stream = MessageStream::for_match_rule(
+289        MatchRule::builder()
+290            .msg_type(zbus::message::Type::Signal)
+291            .interface("org.freedesktop.DBus")
+292            .and_then(|x| x.member("NameOwnerChanged"))
+293            .and_then(|x| x.arg0ns("org.mpris.MediaPlayer2"))
+294            .unwrap()
+295            .build(),
+296        &dbus_conn,
+297        None,
+298    )
+299    .await
+300    .error("Failed to add match rule")?;
+301
+302    let mut active_player_change_end_stream = playerctld_proxy
+303        .receive_active_player_change_end()
+304        .await
+305        .error("Failed to create ActivePlayerChangeEndStream")?;
+306
+307    loop {
+308        debug!("available players: {}", DisplaySlice(&players));
+309
+310        let avail = players.len();
+311        let player = cur_player.map(|c| players.get_mut(c).unwrap());
+312        match player {
+313            Some(player) => {
+314                let mut values = values.clone();
+315                values.insert("avail".into(), Value::number(avail));
+316                values.insert("cur".into(), Value::number(cur_player.unwrap() + 1));
+317                values.insert(
+318                    "player".into(),
+319                    Value::text(
+320                        extract_player_name(player.bus_name.as_str())
+321                            .unwrap()
+322                            .into(),
+323                    ),
+324                );
+325                let (state, play_icon) = match player.status {
+326                    Some(PlaybackStatus::Playing) => (State::Info, "music_pause"),
+327                    _ => (State::Idle, "music_play"),
+328                };
+329                values.insert("play".into(), new_btn(play_icon, PLAY_PAUSE_BTN)?);
+330                if let Some(url) = &player.metadata.url {
+331                    values.insert("url".into(), Value::text(url.clone()));
+332                }
+333                match (
+334                    &player.metadata.title,
+335                    &player.metadata.artist,
+336                    &player.metadata.url,
+337                ) {
+338                    (Some(t), None, _) => {
+339                        values.insert("combo".into(), Value::text(t.clone()));
+340                        values.insert("title".into(), Value::text(t.clone()));
+341                    }
+342                    (None, Some(a), _) => {
+343                        values.insert("combo".into(), Value::text(a.clone()));
+344                        values.insert("artist".into(), Value::text(a.clone()));
+345                    }
+346                    (Some(t), Some(a), _) => {
+347                        values.insert(
+348                            "combo".into(),
+349                            Value::text(format!("{t}{}{a}", config.separator)),
+350                        );
+351                        values.insert("title".into(), Value::text(t.clone()));
+352                        values.insert("artist".into(), Value::text(a.clone()));
+353                    }
+354                    (None, None, Some(url)) => {
+355                        values.insert("combo".into(), Value::text(url.clone()));
+356                    }
+357                    _ => (),
+358                }
+359                if let Some(volume) = player.volume {
+360                    values.insert(
+361                        "volume_icon".into(),
+362                        Value::icon_progression("volume", volume),
+363                    );
+364                    values.insert("volume".into(), Value::percents(volume * 100.0));
+365                }
+366                let mut widget = Widget::new().with_format(format.clone());
+367                widget.set_values(values);
+368                widget.state = state;
+369                api.set_widget(widget)?;
+370            }
+371            None => {
+372                let mut widget = Widget::new().with_format(format.clone());
+373                widget.set_values(map!("icon" => Value::icon("music")));
+374                api.set_widget(widget)?;
+375            }
+376        }
+377
+378        loop {
+379            select! {
+380                Some(msg) = properties_stream.next() => {
+381                    let msg = msg.unwrap();
+382                    let msg = PropertiesChanged::from_message(msg).unwrap();
+383                    let args = msg.args().unwrap();
+384                    let header = msg.message().header();
+385                    let sender = header.sender().unwrap();
+386                    if let Some((pos, player)) = players.iter_mut().enumerate().find(|p| &*p.1.owner == sender) {
+387                        let props = args.changed_properties;
+388                        if let Some(status) = props.get("PlaybackStatus") {
+389                            let status: &str = status.downcast_ref().unwrap();
+390                            player.status = PlaybackStatus::from_str(status);
+391                        }
+392                        if let Some(metadata) = props.get("Metadata") {
+393                            player.metadata =
+394                                zbus_mpris::PlayerMetadata::try_from(metadata.try_to_owned().unwrap()).unwrap();
+395                        }
+396                        if let Some(volume) = props.get("Volume") {
+397                            player.volume = Some(*volume.downcast_ref::<&f64>().unwrap());
+398                        }
+399                        if player.status == Some(PlaybackStatus::Playing)
+400                        && (
+401                            player.metadata.title.is_some()
+402                            || player.metadata.artist.is_some()
+403                            || player.metadata.url.is_some()
+404                        ) {
+405                            cur_player = Some(pos);
+406                        }
+407                        break;
+408                    }
+409                }
+410                Some(msg) = name_owner_changed_stream.next() => {
+411                    let msg = msg.unwrap();
+412                    let msg = NameOwnerChanged::from_message(msg).unwrap();
+413                    let args = msg.args().unwrap();
+414                    match (args.old_owner.as_ref(), args.new_owner.as_ref()) {
+415                        (None, Some(new)) => {
+416                            debug!("new player {} owned by {new}", args.name);
+417                            if player_matches(args.name.as_str(), &preferred_players, &exclude_regex) {
+418                                match Player::new(&dbus_conn, args.name.to_owned().into(), new.to_owned().into()).await {
+419                                    Ok(player) => players.push(player),
+420                                    Err(e) => {
+421                                        debug!("{e}");
+422                                    },
+423                                }
+424                            }
+425                        }
+426                        (Some(old), None) => {
+427                            if let Some(pos) = players.iter().position(|p| &*p.owner == old) {
+428                                debug!("removed player {} owned by {old}", args.name);
+429                                players.remove(pos);
+430                                if let Some(cur) = cur_player {
+431                                    if players.is_empty() {
+432                                        cur_player = None;
+433                                    } else if pos == cur {
+434                                        cur_player = Some(0);
+435                                    } else if pos < cur {
+436                                        cur_player = Some(cur - 1);
+437                                    }
+438                                }
+439                            }
+440                        }
+441                        _ => (),
+442                    }
+443                    break;
+444                }
+445                Some(msg) = active_player_change_end_stream.next() => {
+446                    let args = msg.args().unwrap();
+447                    if let Some(pos) = players.iter().position(|p| p.bus_name == args.name){
+448                        cur_player = Some(pos);
+449                    }
+450                    else{
+451                        // We must have shifted to a player we wanted to skip (on the interface_name_exclude list).
+452                        // Let's shift again
+453                        if let Err(e) = playerctld_proxy.shift().await{
+454                            debug!("{e}");
+455                        }
+456                    }
+457                    break;
+458                }
+459                Some(action) = actions.recv() => {
+460                    if let Some(i) = cur_player {
+461                        let player = &players[i];
+462                        match action.as_ref() {
+463                            "play_pause" => {
+464                                player.play_pause().await?;
+465                            }
+466                            "next" => {
+467                                player.next().await?;
+468                            }
+469                            "prev" => {
+470                                player.prev().await?;
+471                            }
+472                            "next_player" => {
+473                                cur_player = Some((i + 1) % players.len());
+474                                if let Err(e) = playerctld_proxy.shift().await{
+475                                    debug!("{e}");
+476                                }
+477                                break;
+478                            }
+479                            "seek_forward" => {
+480                                player.seek(seek_forward_step).await?;
+481                            }
+482                            "seek_backward" => {
+483                                player.seek(seek_backward_step).await?;
+484                            }
+485                            "volume_up" => {
+486                                player.set_volume(volume_step).await?;
+487                            }
+488                            "volume_down" => {
+489                                player.set_volume(-volume_step).await?;
+490                            }
+491                            "toggle_format" => {
+492                                if let Some(format_alt) = &mut format_alt {
+493                                    std::mem::swap(format_alt, &mut format);
+494                                    break;
+495                                }
+496                            }
+497                            _ => (),
+498                        }
+499                    }
+500                }
+501            }
+502        }
+503    }
+504}
+505
+506async fn get_players(
+507    dbus_conn: &zbus::Connection,
+508    preferred_players: &[String],
+509    exclude_regex: &[Regex],
+510) -> Result<Vec<Player>> {
+511    let proxy = DBusProxy::new(dbus_conn)
+512        .await
+513        .error("failed to create DBusProxy")?;
+514    let names = proxy
+515        .list_names()
+516        .await
+517        .error("failed to list dbus names")?;
+518    let mut players = Vec::new();
+519    for name in names {
+520        if player_matches(name.as_str(), preferred_players, exclude_regex) {
+521            let owner = proxy.get_name_owner(name.as_ref()).await.unwrap();
+522            match Player::new(dbus_conn, name, owner).await {
+523                Ok(player) => players.push(player),
+524                Err(e) => {
+525                    debug!("{e}");
+526                }
+527            }
+528        }
+529    }
+530    Ok(players)
+531}
+532
+533#[derive(Debug)]
+534struct Player {
+535    status: Option<PlaybackStatus>,
+536    owner: OwnedUniqueName,
+537    bus_name: OwnedBusName,
+538    player_proxy: zbus_mpris::PlayerProxy<'static>,
+539    metadata: zbus_mpris::PlayerMetadata,
+540    volume: Option<f64>,
+541}
+542
+543impl Player {
+544    async fn new(
+545        dbus_conn: &zbus::Connection,
+546        bus_name: OwnedBusName,
+547        owner: OwnedUniqueName,
+548    ) -> Result<Player> {
+549        debug!("creating Player for {bus_name}");
+550
+551        let proxy = zbus_mpris::PlayerProxy::builder(dbus_conn)
+552            .destination(bus_name.clone())
+553            .error("failed to set proxy destination")?
+554            .build()
+555            .await
+556            .error("failed to open player proxy")?;
+557
+558        // debug!("querying player info");
+559        // let (metadata, status, volume) =
+560        //     tokio::join!(proxy.metadata(), proxy.playback_status(), proxy.volume());
+561        debug!("querying player metadata");
+562        let metadata = proxy.metadata().await;
+563        debug!("querying player status");
+564        let status = proxy.playback_status().await;
+565        debug!("querying player volume");
+566        let volume = proxy.volume().await;
+567
+568        let metadata = metadata.error("failed to obtain player metadata")?;
+569        let status = status.error("failed to obtain player status")?;
+570
+571        debug!("Player created");
+572
+573        Ok(Self {
+574            status: PlaybackStatus::from_str(&status),
+575            owner,
+576            bus_name,
+577            player_proxy: proxy,
+578            metadata,
+579            volume: volume.ok(),
+580        })
+581    }
+582
+583    async fn play_pause(&self) -> Result<()> {
+584        self.player_proxy
+585            .play_pause()
+586            .await
+587            .error("play_pause() failed")
+588    }
+589
+590    async fn prev(&self) -> Result<()> {
+591        self.player_proxy.previous().await.error("prev() failed")
+592    }
+593
+594    async fn next(&self) -> Result<()> {
+595        self.player_proxy.next().await.error("next() failed")
+596    }
+597
+598    async fn seek(&self, offset: i64) -> Result<()> {
+599        match self.player_proxy.seek(offset).await {
+600            Err(zbus::Error::MethodError(e, _, _))
+601                if e == "org.freedesktop.DBus.Error.NotSupported" =>
+602            {
+603                // TODO show this error somehow
+604                Ok(())
+605            }
+606            other => dbg!(other).error("seek() failed"),
+607        }
+608    }
+609
+610    async fn set_volume(&self, step_size: f64) -> Result<()> {
+611        if let Some(volume) = self.volume {
+612            self.player_proxy
+613                .set_volume(volume + step_size)
+614                .await
+615                .error("set_volume() failed")?;
+616        }
+617        Ok(())
+618    }
+619}
+620
+621impl fmt::Display for Player {
+622    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+623        extract_player_name(&self.bus_name).unwrap().fmt(f)
+624    }
+625}
+626
+627#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+628enum PlaybackStatus {
+629    Playing,
+630    Paused,
+631    Stopped,
+632}
+633
+634impl PlaybackStatus {
+635    fn from_str(s: &str) -> Option<Self> {
+636        match s {
+637            "Paused" => Some(Self::Paused),
+638            "Playing" => Some(Self::Playing),
+639            "Stopped" => Some(Self::Stopped),
+640            _ => None,
+641        }
+642    }
+643}
+644
+645fn extract_player_name(full_name: &str) -> Option<&str> {
+646    const NAME_PREFIX: &str = "org.mpris.MediaPlayer2.";
+647    full_name
+648        .starts_with(NAME_PREFIX)
+649        .then(|| &full_name[NAME_PREFIX.len()..])
+650}
+651
+652fn player_matches(full_name: &str, preferred_players: &[String], exclude_regex: &[Regex]) -> bool {
+653    let name = match extract_player_name(full_name) {
+654        Some(name) => name,
+655        None => return false,
+656    };
+657
+658    exclude_regex.iter().all(|r| !r.is_match(name))
+659        && (preferred_players.is_empty()
+660            || preferred_players.iter().any(|p| name.starts_with(&**p)))
+661}
+662
+663#[cfg(test)]
+664mod tests {
+665    use super::*;
+666
+667    #[test]
+668    fn extract_player_name_test() {
+669        assert_eq!(
+670            extract_player_name("org.mpris.MediaPlayer2.firefox.instance852"),
+671            Some("firefox.instance852")
+672        );
+673        assert_eq!(
+674            extract_player_name("not.org.mpris.MediaPlayer2.firefox.instance852"),
+675            None,
+676        );
+677        assert_eq!(
+678            extract_player_name("org.mpris.MediaPlayer3.firefox.instance852"),
+679            None,
+680        );
+681    }
+682
+683    #[test]
+684    fn player_matches_test() {
+685        let exclude = vec![Regex::new("mpd").unwrap(), Regex::new("firefox.*").unwrap()];
+686        assert!(player_matches(
+687            "org.mpris.MediaPlayer2.playerctld",
+688            &[],
+689            &exclude
+690        ));
+691        assert!(!player_matches(
+692            "org.mpris.MediaPlayer2.playerctld",
+693            &["spotify".into()],
+694            &exclude
+695        ));
+696        assert!(!player_matches(
+697            "org.mpris.MediaPlayer2.firefox.instance852",
+698            &[],
+699            &exclude
+700        ));
+701    }
+702}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/music/zbus_mpris.rs.html b/src/i3status_rs/blocks/music/zbus_mpris.rs.html new file mode 100644 index 0000000000..25d8744f6e --- /dev/null +++ b/src/i3status_rs/blocks/music/zbus_mpris.rs.html @@ -0,0 +1,149 @@ +zbus_mpris.rs - source

i3status_rs/blocks/music/
zbus_mpris.rs

1//! # DBus interface proxies for: `org.mpris.MediaPlayer2.Player`
+2//!
+3//! This code was generated by `zbus-xmlgen` `1.0.0` from DBus introspection data.
+4//! Source: `11`.
+5//!
+6//! You may prefer to adapt it, instead of using it verbatim.
+7//!
+8//! More information can be found in the
+9//! [Writing a client proxy](https://zeenix.pages.freedesktop.org/zbus/client.html)
+10//! section of the zbus documentation.
+11//!
+12//! This DBus object implements
+13//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html),
+14//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used:
+15//!
+16//! * [`zbus::fdo::PropertiesProxy`]
+17//! * [`zbus::fdo::IntrospectableProxy`]
+18//! * [`zbus::fdo::PeerProxy`]
+19//!
+20//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
+21
+22use std::collections::HashMap;
+23use zbus::zvariant::{self, ObjectPath, OwnedValue, Value};
+24
+25#[derive(Debug, Clone)]
+26pub struct PlayerMetadata {
+27    pub title: Option<String>,
+28    pub artist: Option<String>,
+29    pub url: Option<String>,
+30}
+31
+32impl TryFrom<OwnedValue> for PlayerMetadata {
+33    type Error = <HashMap<String, OwnedValue> as TryFrom<OwnedValue>>::Error;
+34
+35    fn try_from(value: OwnedValue) -> Result<Self, Self::Error> {
+36        let map = HashMap::<String, OwnedValue>::try_from(value)?;
+37
+38        let val_to_string = |val: &Value| {
+39            val.downcast_ref::<&str>()
+40                .ok()
+41                .and_then(|val| (!val.is_empty()).then(|| val.to_string()))
+42        };
+43
+44        let title = map.get("xesam:title").and_then(|val| val_to_string(val));
+45
+46        let artists = map
+47            .get("xesam:artist")
+48            .and_then(|val| val.downcast_ref::<&zvariant::Array>().ok())
+49            .map(|val| val.inner());
+50        let artist = artists.and_then(|val| val.first()).and_then(val_to_string);
+51
+52        let url = map.get("xesam:url").and_then(|val| val_to_string(val));
+53
+54        Ok(Self { title, artist, url })
+55    }
+56}
+57
+58#[zbus::proxy(
+59    interface = "org.mpris.MediaPlayer2.Player",
+60    default_path = "/org/mpris/MediaPlayer2"
+61)]
+62pub(super) trait Player {
+63    /// Next method
+64    fn next(&self) -> zbus::Result<()>;
+65
+66    /// OpenUri method
+67    fn open_uri(&self, uri: &str) -> zbus::Result<()>;
+68
+69    /// Pause method
+70    fn pause(&self) -> zbus::Result<()>;
+71
+72    /// Play method
+73    fn play(&self) -> zbus::Result<()>;
+74
+75    /// PlayPause method
+76    fn play_pause(&self) -> zbus::Result<()>;
+77
+78    /// Previous method
+79    fn previous(&self) -> zbus::Result<()>;
+80
+81    /// Seek method
+82    fn seek(&self, offset: i64) -> zbus::Result<()>;
+83
+84    /// SetPosition method
+85    fn set_position(&self, track_id: &ObjectPath<'_>, position: i64) -> zbus::Result<()>;
+86
+87    /// Stop method
+88    fn stop(&self) -> zbus::Result<()>;
+89
+90    /// Seeked signal
+91    #[zbus(signal)]
+92    fn seeked(&self, position: i64) -> zbus::Result<()>;
+93
+94    /// CanControl property
+95    #[zbus(property)]
+96    fn can_control(&self) -> zbus::Result<bool>;
+97
+98    /// CanGoNext property
+99    #[zbus(property)]
+100    fn can_go_next(&self) -> zbus::Result<bool>;
+101
+102    /// CanGoPrevious property
+103    #[zbus(property)]
+104    fn can_go_previous(&self) -> zbus::Result<bool>;
+105
+106    /// CanPause property
+107    #[zbus(property)]
+108    fn can_pause(&self) -> zbus::Result<bool>;
+109
+110    /// CanPlay property
+111    #[zbus(property)]
+112    fn can_play(&self) -> zbus::Result<bool>;
+113
+114    /// CanSeek property
+115    #[zbus(property)]
+116    fn can_seek(&self) -> zbus::Result<bool>;
+117
+118    /// MaximumRate property
+119    #[zbus(property)]
+120    fn maximum_rate(&self) -> zbus::Result<f64>;
+121
+122    /// Metadata property
+123    #[zbus(property)]
+124    fn metadata(&self) -> zbus::Result<PlayerMetadata>;
+125
+126    /// MinimumRate property
+127    #[zbus(property)]
+128    fn minimum_rate(&self) -> zbus::Result<f64>;
+129
+130    /// PlaybackStatus property
+131    #[zbus(property)]
+132    fn playback_status(&self) -> zbus::Result<String>;
+133
+134    /// Position property
+135    #[zbus(property)]
+136    fn position(&self) -> zbus::Result<i64>;
+137
+138    /// Rate property
+139    #[zbus(property)]
+140    fn rate(&self) -> zbus::Result<f64>;
+141    #[zbus(property)]
+142    fn set_rate(&self, value: f64) -> zbus::Result<()>;
+143
+144    /// Volume property
+145    #[zbus(property)]
+146    fn volume(&self) -> zbus::Result<f64>;
+147    #[zbus(property)]
+148    fn set_volume(&self, value: f64) -> zbus::Result<()>;
+149}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/music/zbus_playerctld.rs.html b/src/i3status_rs/blocks/music/zbus_playerctld.rs.html new file mode 100644 index 0000000000..ebbfb353f7 --- /dev/null +++ b/src/i3status_rs/blocks/music/zbus_playerctld.rs.html @@ -0,0 +1,45 @@ +zbus_playerctld.rs - source

i3status_rs/blocks/music/
zbus_playerctld.rs

1//! # DBus interface proxies for: `com.github.altdesktop.playerctld`
+2//!
+3//! This code was generated by `zbus-xmlgen` `3.1.0` from DBus introspection data.
+4//! Source: `Service 'org.mpris.MediaPlayer2.playerctld' on session bus`.
+5//!
+6//! You may prefer to adapt it, instead of using it verbatim.
+7//!
+8//! More information can be found in the
+9//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html)
+10//! section of the zbus documentation.
+11//!
+12//! This DBus object implements
+13//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html),
+14//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used:
+15//!
+16//! * [`zbus::fdo::PropertiesProxy`]
+17//! * [`zbus::fdo::IntrospectableProxy`]
+18//! * [`zbus::fdo::PeerProxy`]
+19//!
+20//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
+21
+22#[zbus::proxy(
+23    interface = "com.github.altdesktop.playerctld",
+24    default_service = "org.mpris.MediaPlayer2.playerctld",
+25    default_path = "/org/mpris/MediaPlayer2"
+26)]
+27pub(super) trait Playerctld {
+28    /// Shift method
+29    fn shift(&self) -> zbus::Result<String>;
+30
+31    /// Unshift method
+32    fn unshift(&self) -> zbus::Result<String>;
+33
+34    /// ActivePlayerChangeBegin signal
+35    #[zbus(signal)]
+36    fn active_player_change_begin(&self, name: &str) -> zbus::Result<()>;
+37
+38    /// ActivePlayerChangeEnd signal
+39    #[zbus(signal)]
+40    fn active_player_change_end(&self, name: &str) -> zbus::Result<()>;
+41
+42    /// PlayerNames property
+43    #[zbus(property)]
+44    fn player_names(&self) -> zbus::Result<Vec<String>>;
+45}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/net.rs.html b/src/i3status_rs/blocks/net.rs.html new file mode 100644 index 0000000000..2289c8acf4 --- /dev/null +++ b/src/i3status_rs/blocks/net.rs.html @@ -0,0 +1,220 @@ +net.rs - source

i3status_rs/blocks/
net.rs

1//! Network information
+2//!
+3//! This block uses `sysfs` and `netlink` and thus does not require any external dependencies.
+4//!
+5//! # Configuration
+6//!
+7//! Key | Values | Default
+8//! ----|--------|--------
+9//! `device` | Network interface to monitor (as specified in `/sys/class/net/`). Supports regex. | If not set, device will be automatically selected every `interval`
+10//! `interval` | Update interval in seconds | `2`
+11//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon ^icon_net_down $speed_down.eng(prefix:K) ^icon_net_up $speed_up.eng(prefix:K) "`
+12//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None`
+13//! `inactive_format` | Same as `format` but for when the interface is inactive | `" $icon Down "`
+14//! `missing_format` | Same as `format` but for when the device is missing | `" × "`
+15//!
+16//! Action          | Description                               | Default button
+17//! ----------------|-------------------------------------------|---------------
+18//! `toggle_format` | Toggles between `format` and `format_alt` | Left
+19//!
+20//! Placeholder       | Value                       | Type   | Unit
+21//! ------------------|-----------------------------|--------|---------------
+22//! `icon`            | Icon based on device's type | Icon   | -
+23//! `speed_down`      | Download speed              | Number | Bytes per second
+24//! `speed_up`        | Upload speed                | Number | Bytes per second
+25//! `graph_down`      | Download speed graph        | Text   | -
+26//! `graph_up`        | Upload speed graph          | Text   | -
+27//! `device`          | The name of device          | Text   | -
+28//! `ssid`            | Netfork SSID (WiFi only)    | Text   | -
+29//! `frequency`       | WiFi frequency              | Number | Hz
+30//! `signal_strength` | WiFi signal                 | Number | %
+31//! `bitrate`         | WiFi connection bitrate     | Number | Bits per second
+32//! `ip`              | IPv4 address of the iface   | Text   | -
+33//! `ipv6`            | IPv6 address of the iface   | Text   | -
+34//! `nameserver`      | Nameserver                  | Text   | -
+35//!
+36//! # Example
+37//!
+38//! Display WiFi info if available
+39//!
+40//! ```toml
+41//! [[block]]
+42//! block = "net"
+43//! format = " $icon {$signal_strength $ssid $frequency|Wired connection} via $device "
+44//! ```
+45//!
+46//! Display exact device
+47//!
+48//! ```toml
+49//! [[block]]
+50//! block = "net"
+51//! device = "^wlo0$"
+52//! ```
+53//!
+54//! # Icons Used
+55//! - `net_loopback`
+56//! - `net_vpn`
+57//! - `net_wired`
+58//! - `net_wireless` (as a progression)
+59//! - `net_up`
+60//! - `net_down`
+61
+62use super::prelude::*;
+63use crate::netlink::NetDevice;
+64use crate::util;
+65use itertools::Itertools as _;
+66use regex::Regex;
+67use std::time::Instant;
+68
+69#[derive(Deserialize, Debug, SmartDefault)]
+70#[serde(deny_unknown_fields, default)]
+71pub struct Config {
+72    pub device: Option<String>,
+73    #[default(2.into())]
+74    pub interval: Seconds,
+75    pub format: FormatConfig,
+76    pub format_alt: Option<FormatConfig>,
+77    pub inactive_format: FormatConfig,
+78    pub missing_format: FormatConfig,
+79}
+80
+81pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+82    let mut actions = api.get_actions()?;
+83    api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
+84
+85    let mut format = config.format.with_default(
+86        " $icon ^icon_net_down $speed_down.eng(prefix:K) ^icon_net_up $speed_up.eng(prefix:K) ",
+87    )?;
+88    let missing_format = config.missing_format.with_default(" × ")?;
+89    let inactive_format = config.inactive_format.with_default(" $icon Down ")?;
+90    let mut format_alt = match &config.format_alt {
+91        Some(f) => Some(f.with_default("")?),
+92        None => None,
+93    };
+94
+95    let mut timer = config.interval.timer();
+96
+97    let device_re = config
+98        .device
+99        .as_deref()
+100        .map(Regex::new)
+101        .transpose()
+102        .error("Failed to parse device regex")?;
+103
+104    // Stats
+105    let mut stats = None;
+106    let mut stats_timer = Instant::now();
+107    let mut tx_hist = [0f64; 8];
+108    let mut rx_hist = [0f64; 8];
+109
+110    loop {
+111        match NetDevice::new(device_re.as_ref()).await? {
+112            None => {
+113                api.set_widget(Widget::new().with_format(missing_format.clone()))?;
+114            }
+115            Some(device) => {
+116                let mut widget = Widget::new();
+117
+118                if device.is_up() {
+119                    widget.set_format(format.clone());
+120                } else {
+121                    widget.set_format(inactive_format.clone());
+122                }
+123
+124                let mut speed_down: f64 = 0.0;
+125                let mut speed_up: f64 = 0.0;
+126
+127                // Calculate speed
+128                match (stats, device.iface.stats) {
+129                    // No previous stats available
+130                    (None, new_stats) => stats = new_stats,
+131                    // No new stats available
+132                    (Some(_), None) => stats = None,
+133                    // All stats available
+134                    (Some(old_stats), Some(new_stats)) => {
+135                        let diff = new_stats - old_stats;
+136                        let elapsed = stats_timer.elapsed().as_secs_f64();
+137                        stats_timer = Instant::now();
+138                        speed_down = diff.rx_bytes as f64 / elapsed;
+139                        speed_up = diff.tx_bytes as f64 / elapsed;
+140                        stats = Some(new_stats);
+141                    }
+142                }
+143                push_to_hist(&mut rx_hist, speed_down);
+144                push_to_hist(&mut tx_hist, speed_up);
+145
+146                let icon = if let Some(signal) = device.signal() {
+147                    Value::icon_progression(device.icon, signal / 100.0)
+148                } else {
+149                    Value::icon(device.icon)
+150                };
+151
+152                widget.set_values(map! {
+153                    "icon" => icon,
+154                    "speed_down" => Value::bytes(speed_down),
+155                    "speed_up" => Value::bytes(speed_up),
+156                    "graph_down" => Value::text(util::format_bar_graph(&rx_hist)),
+157                    "graph_up" => Value::text(util::format_bar_graph(&tx_hist)),
+158                    [if let Some(v) = device.ip] "ip" => Value::text(v.to_string()),
+159                    [if let Some(v) = device.ipv6] "ipv6" => Value::text(v.to_string()),
+160                    [if let Some(v) = device.ssid()] "ssid" => Value::text(v),
+161                    [if let Some(v) = device.frequency()] "frequency" => Value::hertz(v),
+162                    [if let Some(v) = device.bitrate()] "bitrate" => Value::bits(v),
+163                    [if let Some(v) = device.signal()] "signal_strength" => Value::percents(v),
+164                    [if !device.nameservers.is_empty()] "nameserver" => Value::text(
+165                                                                            device
+166                                                                                .nameservers
+167                                                                                .into_iter()
+168                                                                                .map(|s| s.to_string())
+169                                                                                .join(" "),
+170                                                                        ),
+171                    "device" => Value::text(device.iface.name),
+172                });
+173
+174                api.set_widget(widget)?;
+175            }
+176        }
+177
+178        loop {
+179            select! {
+180                _ = timer.tick() => break,
+181                _ = api.wait_for_update_request() => break,
+182                Some(action) = actions.recv() => match action.as_ref() {
+183                    "toggle_format" => {
+184                        if let Some(format_alt) = &mut format_alt {
+185                            std::mem::swap(format_alt, &mut format);
+186                            break;
+187                        }
+188                    }
+189                    _ => ()
+190                }
+191            }
+192        }
+193    }
+194}
+195
+196fn push_to_hist<T>(hist: &mut [T], elem: T) {
+197    hist[0] = elem;
+198    hist.rotate_left(1);
+199}
+200
+201#[cfg(test)]
+202mod tests {
+203    use super::push_to_hist;
+204
+205    #[test]
+206    fn test_push_to_hist() {
+207        let mut hist = [0; 4];
+208        assert_eq!(&hist, &[0, 0, 0, 0]);
+209        push_to_hist(&mut hist, 1);
+210        assert_eq!(&hist, &[0, 0, 0, 1]);
+211        push_to_hist(&mut hist, 3);
+212        assert_eq!(&hist, &[0, 0, 1, 3]);
+213        push_to_hist(&mut hist, 0);
+214        assert_eq!(&hist, &[0, 1, 3, 0]);
+215        push_to_hist(&mut hist, 10);
+216        assert_eq!(&hist, &[1, 3, 0, 10]);
+217        push_to_hist(&mut hist, 2);
+218        assert_eq!(&hist, &[3, 0, 10, 2]);
+219    }
+220}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/notify.rs.html b/src/i3status_rs/blocks/notify.rs.html new file mode 100644 index 0000000000..ba6de79caf --- /dev/null +++ b/src/i3status_rs/blocks/notify.rs.html @@ -0,0 +1,303 @@ +notify.rs - source

i3status_rs/blocks/
notify.rs

1//! Display and toggle the state of notifications daemon
+2//!
+3//! Left-clicking on this block will enable/disable notifications.
+4//!
+5//! # Configuration
+6//!
+7//! Key | Values | Default
+8//! ----|--------|--------
+9//! `driver` | Which notifications daemon is running. Available drivers are: `"dunst"` and `"swaync"` | `"dunst"`
+10//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon "`
+11//!
+12//! Placeholder                               | Value                                      | Type   | Unit
+13//! ------------------------------------------|--------------------------------------------|--------|-----
+14//! `icon`                                    | Icon based on notification's state         | Icon   | -
+15//! `notification_count`[^dunst_version_note] | The number of notification (omitted if 0)  | Number | -
+16//! `paused`                                  | Present only if notifications are disabled | Flag   | -
+17//!
+18//! Action          | Default button
+19//! ----------------|---------------
+20//! `toggle_paused` | Left
+21//! `show`          | -
+22//!
+23//! # Examples
+24//!
+25//! How to use `paused` flag
+26//!
+27//! ```toml
+28//! [[block]]
+29//! block = "notify"
+30//! format = " $icon {$paused{Off}|On} "
+31//! ```
+32//! How to use `notification_count`
+33//!
+34//! ```toml
+35//! [[block]]
+36//! block = "notify"
+37//! format = " $icon {($notification_count.eng(w:1)) |}"
+38//! ```
+39//! How to remap actions
+40//!
+41//! ```toml
+42//! [[block]]
+43//! block = "notify"
+44//! driver = "swaync"
+45//! [[block.click]]
+46//! button = "left"
+47//! action = "show"
+48//! [[block.click]]
+49//! button = "right"
+50//! action = "toggle_paused"
+51//! ```
+52//!
+53//! # Icons Used
+54//! - `bell`
+55//! - `bell-slash`
+56//!
+57//! [^dunst_version_note]: when using `notification_count` with the `dunst` driver use dunst > 1.9.0
+58
+59use super::prelude::*;
+60use tokio::{join, try_join};
+61use zbus::proxy::PropertyStream;
+62
+63const ICON_ON: &str = "bell";
+64const ICON_OFF: &str = "bell-slash";
+65
+66#[derive(Deserialize, Debug, Default)]
+67#[serde(deny_unknown_fields, default)]
+68pub struct Config {
+69    pub driver: DriverType,
+70    pub format: FormatConfig,
+71}
+72
+73#[derive(Deserialize, Debug, SmartDefault)]
+74#[serde(rename_all = "lowercase")]
+75pub enum DriverType {
+76    #[default]
+77    Dunst,
+78    SwayNC,
+79}
+80
+81pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+82    let mut actions = api.get_actions()?;
+83    api.set_default_actions(&[(MouseButton::Left, None, "toggle_paused")])?;
+84
+85    let format = config.format.with_default(" $icon ")?;
+86
+87    let mut driver: Box<dyn Driver> = match config.driver {
+88        DriverType::Dunst => Box::new(DunstDriver::new().await?),
+89        DriverType::SwayNC => Box::new(SwayNCDriver::new().await?),
+90    };
+91
+92    loop {
+93        let (is_paused, notification_count) =
+94            try_join!(driver.is_paused(), driver.notification_count())?;
+95
+96        let mut widget = Widget::new().with_format(format.clone());
+97        widget.set_values(map!(
+98            "icon" => Value::icon(if is_paused { ICON_OFF } else { ICON_ON }),
+99            [if notification_count != 0] "notification_count" => Value::number(notification_count),
+100            [if is_paused] "paused" => Value::flag(),
+101        ));
+102        widget.state = if notification_count == 0 {
+103            State::Idle
+104        } else {
+105            State::Info
+106        };
+107        api.set_widget(widget)?;
+108
+109        select! {
+110            x = driver.wait_for_change() => x?,
+111            Some(action) = actions.recv() => match action.as_ref() {
+112                "toggle_paused" => {
+113                    driver.set_paused(!is_paused).await?;
+114                }
+115                "show" => {
+116                    driver.notification_show().await?;
+117                }
+118                _ => (),
+119            }
+120        }
+121    }
+122}
+123
+124#[async_trait]
+125trait Driver {
+126    async fn is_paused(&self) -> Result<bool>;
+127    async fn set_paused(&self, paused: bool) -> Result<()>;
+128    async fn notification_show(&self) -> Result<()>;
+129    async fn notification_count(&self) -> Result<u32>;
+130    async fn wait_for_change(&mut self) -> Result<()>;
+131}
+132
+133struct DunstDriver {
+134    proxy: DunstDbusProxy<'static>,
+135    paused_changes: PropertyStream<'static, bool>,
+136    displayed_length_changes: PropertyStream<'static, u32>,
+137    waiting_length_changes: PropertyStream<'static, u32>,
+138}
+139
+140impl DunstDriver {
+141    async fn new() -> Result<Self> {
+142        let dbus_conn = new_dbus_connection().await?;
+143        let proxy = DunstDbusProxy::new(&dbus_conn)
+144            .await
+145            .error("Failed to create DunstDbusProxy")?;
+146        Ok(Self {
+147            paused_changes: proxy.receive_paused_changed().await,
+148            displayed_length_changes: proxy.receive_displayed_length_changed().await,
+149            waiting_length_changes: proxy.receive_waiting_length_changed().await,
+150            proxy,
+151        })
+152    }
+153}
+154
+155#[async_trait]
+156impl Driver for DunstDriver {
+157    async fn is_paused(&self) -> Result<bool> {
+158        self.proxy.paused().await.error("Failed to get 'paused'")
+159    }
+160
+161    async fn set_paused(&self, paused: bool) -> Result<()> {
+162        self.proxy
+163            .set_paused(paused)
+164            .await
+165            .error("Failed to set 'paused'")
+166    }
+167
+168    async fn notification_show(&self) -> Result<()> {
+169        self.proxy
+170            .notification_show()
+171            .await
+172            .error("Could not call 'NotificationShow'")
+173    }
+174
+175    async fn notification_count(&self) -> Result<u32> {
+176        let (displayed_length, waiting_length) =
+177            try_join!(self.proxy.displayed_length(), self.proxy.waiting_length())
+178                .error("Failed to get property")?;
+179
+180        Ok(displayed_length + waiting_length)
+181    }
+182
+183    async fn wait_for_change(&mut self) -> Result<()> {
+184        select! {
+185            _ = self.paused_changes.next() => {}
+186            _ = self.displayed_length_changes.next() => {}
+187            _ = self.waiting_length_changes.next() => {}
+188        }
+189        Ok(())
+190    }
+191}
+192
+193#[zbus::proxy(
+194    interface = "org.dunstproject.cmd0",
+195    default_service = "org.freedesktop.Notifications",
+196    default_path = "/org/freedesktop/Notifications"
+197)]
+198
+199trait DunstDbus {
+200    #[zbus(property, name = "paused")]
+201    fn paused(&self) -> zbus::Result<bool>;
+202    #[zbus(property, name = "paused")]
+203    fn set_paused(&self, value: bool) -> zbus::Result<()>;
+204    fn notification_show(&self) -> zbus::Result<()>;
+205    #[zbus(property, name = "displayedLength")]
+206    fn displayed_length(&self) -> zbus::Result<u32>;
+207    #[zbus(property, name = "waitingLength")]
+208    fn waiting_length(&self) -> zbus::Result<u32>;
+209}
+210struct SwayNCDriver {
+211    proxy: SwayNCDbusProxy<'static>,
+212    changes: SubscribeStream,
+213    changes_v2: SubscribeV2Stream,
+214}
+215
+216impl SwayNCDriver {
+217    async fn new() -> Result<Self> {
+218        let dbus_conn = new_dbus_connection().await?;
+219        let proxy = SwayNCDbusProxy::new(&dbus_conn)
+220            .await
+221            .error("Failed to create SwayNCDbusProxy")?;
+222        Ok(Self {
+223            changes: proxy
+224                .receive_subscribe()
+225                .await
+226                .error("Failed to create SubscribeStream")?,
+227            changes_v2: proxy
+228                .receive_subscribe_v2()
+229                .await
+230                .error("Failed to create SubscribeV2Stream")?,
+231            proxy,
+232        })
+233    }
+234}
+235
+236#[async_trait]
+237impl Driver for SwayNCDriver {
+238    async fn is_paused(&self) -> Result<bool> {
+239        let (is_dnd, is_inhibited) = join!(self.proxy.get_dnd(), self.proxy.is_inhibited());
+240
+241        is_dnd
+242            .error("Failed to call 'GetDnd'")
+243            .map(|is_dnd| is_dnd || is_inhibited.unwrap_or_default())
+244    }
+245
+246    async fn set_paused(&self, paused: bool) -> Result<()> {
+247        if paused {
+248            self.proxy.set_dnd(paused).await
+249        } else {
+250            join!(self.proxy.set_dnd(paused), self.proxy.clear_inhibitors()).0
+251        }
+252        .error("Failed to call 'SetDnd'")
+253    }
+254
+255    async fn notification_show(&self) -> Result<()> {
+256        self.proxy
+257            .toggle_visibility()
+258            .await
+259            .error("Failed to call 'ToggleVisibility'")
+260    }
+261
+262    async fn notification_count(&self) -> Result<u32> {
+263        self.proxy
+264            .notification_count()
+265            .await
+266            .error("Failed to call 'NotificationCount'")
+267    }
+268
+269    async fn wait_for_change(&mut self) -> Result<()> {
+270        select! {
+271            _ = self.changes.next() => (),
+272            _ = self.changes_v2.next() => (),
+273        }
+274        Ok(())
+275    }
+276}
+277
+278#[zbus::proxy(
+279    interface = "org.erikreider.swaync.cc",
+280    default_service = "org.freedesktop.Notifications",
+281    default_path = "/org/erikreider/swaync/cc"
+282)]
+283trait SwayNCDbus {
+284    fn get_dnd(&self) -> zbus::Result<bool>;
+285    fn set_dnd(&self, value: bool) -> zbus::Result<()>;
+286    fn toggle_visibility(&self) -> zbus::Result<()>;
+287    fn notification_count(&self) -> zbus::Result<u32>;
+288    #[zbus(signal)]
+289    fn subscribe(&self, count: u32, dnd: bool, cc_open: bool) -> zbus::Result<()>;
+290
+291    // inhibitors were introduced in v0.8.0
+292    fn is_inhibited(&self) -> zbus::Result<bool>;
+293    fn clear_inhibitors(&self) -> zbus::Result<bool>;
+294    // subscribe_v2 replaced subscribe in v0.8.0
+295    #[zbus(signal)]
+296    fn subscribe_v2(
+297        &self,
+298        count: u32,
+299        dnd: bool,
+300        cc_open: bool,
+301        inhibited: bool,
+302    ) -> zbus::Result<()>;
+303}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/notmuch.rs.html b/src/i3status_rs/blocks/notmuch.rs.html new file mode 100644 index 0000000000..b8ae9a0ec3 --- /dev/null +++ b/src/i3status_rs/blocks/notmuch.rs.html @@ -0,0 +1,114 @@ +notmuch.rs - source

i3status_rs/blocks/
notmuch.rs

1//! Count of notmuch messages
+2//!
+3//! This block queries a notmuch database and displays the count of messages.
+4//!
+5//! The simplest configuration will return the total count of messages in the notmuch database stored at $HOME/.mail
+6//!
+7//! Note that you need to enable `notmuch` feature to use this block:
+8//! ```sh
+9//! cargo build --release --features notmuch
+10//! ```
+11//!
+12//! # Configuration
+13//!
+14//! Key | Values | Default
+15//! ----|--------|--------
+16//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $count "`
+17//! `maildir` | Path to the directory containing the notmuch database. Supports path expansions e.g. `~`. | `~/.mail`
+18//! `query` | Query to run on the database. | `""`
+19//! `threshold_critical` | Mail count that triggers `critical` state. | `99999`
+20//! `threshold_warning` | Mail count that triggers `warning` state. | `99999`
+21//! `threshold_good` | Mail count that triggers `good` state. | `99999`
+22//! `threshold_info` | Mail count that triggers `info` state. | `99999`
+23//! `interval` | Update interval in seconds. | `10`
+24//!
+25//! Placeholder | Value                                      | Type   | Unit
+26//! ------------|--------------------------------------------|--------|-----
+27//! `icon`      | A static icon                              | Icon   | -
+28//! `count`     | Number of messages for the query           | Number | -
+29//!
+30//! # Example
+31//!
+32//! ```toml
+33//! [[block]]
+34//! block = "notmuch"
+35//! query = "tag:alert and not tag:trash"
+36//! threshold_warning = 1
+37//! threshold_critical = 10
+38//! [[block.click]]
+39//! button = "left"
+40//! update = true
+41//! ```
+42//!
+43//! # Icons Used
+44//! - `mail`
+45
+46use super::prelude::*;
+47
+48#[derive(Deserialize, Debug, SmartDefault)]
+49#[serde(deny_unknown_fields, default)]
+50pub struct Config {
+51    pub format: FormatConfig,
+52    #[default(10.into())]
+53    pub interval: Seconds,
+54    #[default("~/.mail".into())]
+55    pub maildir: ShellString,
+56    pub query: String,
+57    #[default(u32::MAX)]
+58    pub threshold_warning: u32,
+59    #[default(u32::MAX)]
+60    pub threshold_critical: u32,
+61    #[default(u32::MAX)]
+62    pub threshold_info: u32,
+63    #[default(u32::MAX)]
+64    pub threshold_good: u32,
+65}
+66
+67pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+68    let format = config.format.with_default(" $icon $count ")?;
+69
+70    let db = config.maildir.expand()?;
+71    let mut timer = config.interval.timer();
+72
+73    loop {
+74        // TODO: spawn_blocking?
+75        let count = run_query(&db, &config.query).error("Failed to get count")?;
+76
+77        let mut widget = Widget::new().with_format(format.clone());
+78
+79        widget.set_values(map! {
+80            "icon" => Value::icon("mail"),
+81            "count" => Value::number(count)
+82        });
+83
+84        widget.state = if count >= config.threshold_critical {
+85            State::Critical
+86        } else if count >= config.threshold_warning {
+87            State::Warning
+88        } else if count >= config.threshold_good {
+89            State::Good
+90        } else if count >= config.threshold_info {
+91            State::Info
+92        } else {
+93            State::Idle
+94        };
+95
+96        api.set_widget(widget)?;
+97
+98        tokio::select! {
+99            _ = timer.tick() => (),
+100            _ = api.wait_for_update_request() => (),
+101        }
+102    }
+103}
+104
+105fn run_query(db_path: &str, query_string: &str) -> std::result::Result<u32, notmuch::Error> {
+106    let db = notmuch::Database::open_with_config(
+107        Some(db_path),
+108        notmuch::DatabaseMode::ReadOnly,
+109        None::<&str>,
+110        None,
+111    )?;
+112    let query = db.create_query(query_string)?;
+113    query.count_messages()
+114}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/nvidia_gpu.rs.html b/src/i3status_rs/blocks/nvidia_gpu.rs.html new file mode 100644 index 0000000000..f5a615a0fc --- /dev/null +++ b/src/i3status_rs/blocks/nvidia_gpu.rs.html @@ -0,0 +1,265 @@ +nvidia_gpu.rs - source

i3status_rs/blocks/
nvidia_gpu.rs

1//! Display the stats of your NVidia GPU
+2//!
+3//! By default `show_temperature` shows the used memory. Clicking the left mouse on the
+4//! "temperature" part of the block will alternate it between showing used or total available
+5//! memory.
+6//!
+7//! Clicking the left mouse button on the "fan speed" part of the block will cause it to enter into
+8//! a fan speed setting mode. In this mode you can scroll the mouse wheel over the block to change
+9//! the fan speeds, and left click to exit the mode.
+10//!
+11//! Requires `nvidia-smi` for displaying info and `nvidia_settings` for setting fan speed.
+12//!
+13//! # Configuration
+14//!
+15//! Key | Values | Default
+16//! ----|--------|--------
+17//! `gpu_id` | GPU id in system. | `0`
+18//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $utilization $memory $temperature "`
+19//! `interval` | Update interval in seconds. | `1`
+20//! `idle` | Maximum temperature, below which state is set to idle | `50`
+21//! `good` | Maximum temperature, below which state is set to good | `70`
+22//! `info` | Maximum temperature, below which state is set to info | `75`
+23//! `warning` | Maximum temperature, below which state is set to warning | `80`
+24//!
+25//! Placeholder   | Type   | Unit
+26//! --------------|--------|---------------
+27//! `icon`        | Icon   | -
+28//! `name`        | Text   | -
+29//! `utilization` | Number | Percents
+30//! `memory`      | Number | Bytes
+31//! `temperature` | Number | Degrees
+32//! `fan_speed`   | Number | Percents
+33//! `clocks`      | Number | Hertz
+34//! `power`       | Number | Watts
+35//!
+36//! Widget    | Placeholder
+37//! ----------|-------------
+38//! `mem_btn` | `$memory`
+39//! `fan_btn` | `$fan_speed`
+40//!
+41//! Action                  | Default button
+42//! ------------------------|----------------
+43//! `toggle_mem_total`      | Left on `mem_btn`
+44//! `toggle_fan_controlled` | Left on `fan_btn`
+45//! `fan_speed_up`          | Wheel Up on `fan_btn`
+46//! `fan_speed_down`        | Wheel Down on `fan_btn`
+47//!
+48//! # Example
+49//!
+50//! ```toml
+51//! [[block]]
+52//! block = "nvidia_gpu"
+53//! interval = 1
+54//! format = " $icon GT 1030 $utilization $temperature $clocks "
+55//! ```
+56//!
+57//! # Icons Used
+58//! - `gpu`
+59//!
+60//! # TODO
+61//! - Provide a `mappings` option similar to `keyboard_layout`'s  to map GPU names to labels?
+62
+63use std::process::Stdio;
+64use std::str::FromStr;
+65
+66use tokio::io::{BufReader, Lines};
+67use tokio::process::Command;
+68
+69const MEM_BTN: &str = "mem_btn";
+70const FAN_BTN: &str = "fan_btn";
+71const QUERY: &str = "--query-gpu=name,memory.total,utilization.gpu,memory.used,temperature.gpu,fan.speed,clocks.current.graphics,power.draw,";
+72const FORMAT: &str = "--format=csv,noheader,nounits";
+73
+74use super::prelude::*;
+75
+76#[derive(Deserialize, Debug, SmartDefault)]
+77#[serde(deny_unknown_fields, default)]
+78pub struct Config {
+79    pub format: FormatConfig,
+80    #[default(1.into())]
+81    pub interval: Seconds,
+82    #[default(0)]
+83    pub gpu_id: u64,
+84    #[default(50)]
+85    pub idle: u32,
+86    #[default(70)]
+87    pub good: u32,
+88    #[default(75)]
+89    pub info: u32,
+90    #[default(80)]
+91    pub warning: u32,
+92}
+93
+94pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+95    let mut actions = api.get_actions()?;
+96    api.set_default_actions(&[
+97        (MouseButton::Left, Some(MEM_BTN), "toggle_mem_total"),
+98        (MouseButton::Left, Some(FAN_BTN), "toggle_fan_controlled"),
+99        (MouseButton::WheelUp, Some(FAN_BTN), "fan_speed_up"),
+100        (MouseButton::WheelDown, Some(FAN_BTN), "fan_speed_down"),
+101    ])?;
+102
+103    let format = config
+104        .format
+105        .with_default(" $icon $utilization $memory $temperature ")?;
+106
+107    // Run `nvidia-smi` command
+108    let mut child = Command::new("nvidia-smi")
+109        .args([
+110            "-l",
+111            &config.interval.seconds().to_string(),
+112            "-i",
+113            &config.gpu_id.to_string(),
+114            QUERY,
+115            FORMAT,
+116        ])
+117        .stdout(Stdio::piped())
+118        .kill_on_drop(true)
+119        .spawn()
+120        .error("Failed to execute nvidia-smi")?;
+121    let mut reader = BufReader::new(child.stdout.take().unwrap()).lines();
+122
+123    // Read the initial info
+124    let mut info = GpuInfo::from_reader(&mut reader).await?;
+125    let mut show_mem_total = false;
+126    let mut fan_controlled = false;
+127
+128    loop {
+129        let mut widget = Widget::new().with_format(format.clone());
+130
+131        widget.state = match info.temperature {
+132            t if t <= config.idle => State::Idle,
+133            t if t <= config.good => State::Good,
+134            t if t <= config.info => State::Info,
+135            t if t <= config.warning => State::Warning,
+136            _ => State::Critical,
+137        };
+138
+139        widget.set_values(map! {
+140            "icon" => Value::icon("gpu"),
+141            "name" => Value::text(info.name.clone()),
+142            "utilization" => Value::percents(info.utilization),
+143            "memory" => Value::bytes(if show_mem_total {info.mem_total} else {info.mem_used}).with_instance(MEM_BTN),
+144            "temperature" => Value::degrees(info.temperature),
+145            "fan_speed" => Value::percents(info.fan_speed).with_instance(FAN_BTN).underline(fan_controlled).italic(fan_controlled),
+146            "clocks" => Value::hertz(info.clocks),
+147            "power" => Value::watts(info.power_draw),
+148        });
+149
+150        api.set_widget(widget)?;
+151
+152        select! {
+153            new_info = GpuInfo::from_reader(&mut reader) => {
+154                info = new_info?;
+155            }
+156            code = child.wait() => {
+157                let code = code.error("failed to check nvidia-smi exit code")?;
+158                return Err(Error::new(format!("nvidia-smi exited with code {code}")));
+159            }
+160            Some(action) = actions.recv() => match action.as_ref() {
+161                "toggle_mem_total" => {
+162                    show_mem_total = !show_mem_total;
+163                }
+164                "toggle_fan_controlled" => {
+165                    fan_controlled = !fan_controlled;
+166                    set_fan_speed(config.gpu_id, fan_controlled.then_some(info.fan_speed)).await?;
+167                }
+168                "fan_speed_up" if fan_controlled && info.fan_speed < 100 => {
+169                    info.fan_speed += 1;
+170                    set_fan_speed(config.gpu_id, Some(info.fan_speed)).await?;
+171                }
+172                "fan_speed_down" if fan_controlled && info.fan_speed > 0 => {
+173                    info.fan_speed -= 1;
+174                    set_fan_speed(config.gpu_id, Some(info.fan_speed)).await?;
+175                }
+176                _ => (),
+177            }
+178        }
+179    }
+180}
+181
+182#[derive(Debug)]
+183struct GpuInfo {
+184    name: String,
+185    mem_total: f64,   // bytes
+186    mem_used: f64,    // bytes
+187    utilization: f64, // percents
+188    temperature: u32, // degrees
+189    fan_speed: u32,   // percents
+190    clocks: f64,      // hertz
+191    power_draw: f64,  // watts
+192}
+193
+194impl GpuInfo {
+195    /// Read a line from provided reader and parse it
+196    ///
+197    /// # Cancel safety
+198    ///
+199    /// This method should be cancellation safe, because it has only one `.await` and it is on `next_line`, which is cancellation safe.
+200    async fn from_reader<B: AsyncBufRead + Unpin>(reader: &mut Lines<B>) -> Result<Self> {
+201        const ERR_MSG: &str = "failed to read from nvidia-smi";
+202        reader
+203            .next_line()
+204            .await
+205            .error(ERR_MSG)?
+206            .error(ERR_MSG)?
+207            .parse::<GpuInfo>()
+208            .error("failed to parse nvidia-smi output")
+209    }
+210}
+211
+212impl FromStr for GpuInfo {
+213    type Err = Error;
+214
+215    fn from_str(s: &str) -> Result<Self, Self::Err> {
+216        macro_rules! parse {
+217            ($s:ident -> $($part:ident : $t:ident $(* $mul:expr)?),*) => {{
+218                let mut parts = $s.trim().split(", ");
+219                let info = GpuInfo {
+220                    $(
+221                    $part: {
+222                        let $part = parts
+223                            .next()
+224                            .error(concat!("missing property: ", stringify!($part)))?
+225                            .parse::<$t>()
+226                            .unwrap_or_default();
+227                        $(let $part = $part * $mul;)?
+228                        $part
+229                    },
+230                    )*
+231                };
+232                Ok(info)
+233            }}
+234        }
+235        // `memory` and `clocks` are initially in MB and MHz, so we have to multiply them by 1_000_000
+236        parse!(s -> name: String, mem_total: f64 * 1e6, utilization: f64, mem_used: f64 * 1e6, temperature: u32, fan_speed: u32, clocks: f64 * 1e6, power_draw: f64)
+237    }
+238}
+239
+240async fn set_fan_speed(id: u64, speed: Option<u32>) -> Result<()> {
+241    const ERR_MSG: &str = "Failed to execute nvidia-settings";
+242    let mut cmd = Command::new("nvidia-settings");
+243    if let Some(speed) = speed {
+244        cmd.args([
+245            "-a",
+246            &format!("[gpu:{id}]/GPUFanControlState=1"),
+247            "-a",
+248            &format!("[fan:{id}]/GPUTargetFanSpeed={speed}"),
+249        ]);
+250    } else {
+251        cmd.args(["-a", &format!("[gpu:{id}]/GPUFanControlState=0")]);
+252    }
+253    if cmd
+254        .spawn()
+255        .error(ERR_MSG)?
+256        .wait()
+257        .await
+258        .error(ERR_MSG)?
+259        .success()
+260    {
+261        Ok(())
+262    } else {
+263        Err(Error::new(ERR_MSG))
+264    }
+265}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/packages.rs.html b/src/i3status_rs/blocks/packages.rs.html new file mode 100644 index 0000000000..ccc4fc6623 --- /dev/null +++ b/src/i3status_rs/blocks/packages.rs.html @@ -0,0 +1,378 @@ +packages.rs - source

i3status_rs/blocks/
packages.rs

1//! Pending updates for different package manager like apt, pacman, etc.
+2//!
+3//! Currently these package managers are available:
+4//! - `apt` for Debian/Ubuntu based system
+5//! - `pacman` for Arch based system
+6//! - `aur` for Arch based system
+7//! - `dnf` for Fedora based system
+8//! - `xbps` for Void Linux
+9//!
+10//! # Configuration
+11//!
+12//! Key | Values | Default
+13//! ----|--------|--------
+14//! `interval` | Update interval in seconds. | `600`
+15//! `package_manager` | Package manager to check for updates | Automatically derived from format templates, but can be used to influence the `$total` value
+16//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $total.eng(w:1) "`
+17//! `format_singular` | Same as `format`, but for when exactly one update is available. | `" $icon $total.eng(w:1) "`
+18//! `format_up_to_date` | Same as `format`, but for when no updates are available. | `" $icon $total.eng(w:1) "`
+19//! `warning_updates_regex` | Display block as warning if updates matching regex are available. | `None`
+20//! `critical_updates_regex` | Display block as critical if updates matching regex are available. | `None`
+21//! `ignore_updates_regex` | Doesn't include updates matching regex in the count. | `None`
+22//! `ignore_phased_updates` | Doesn't include potentially held back phased updates in the count. (For Debian/Ubuntu based system) | `false`
+23//! `aur_command` | AUR command to check available updates, which outputs in the same format as pacman. e.g. `yay -Qua` (For Arch based system) | Required if `$aur` are used
+24//!
+25//!  Placeholder | Value                                                                            | Type   | Unit
+26//! -------------|----------------------------------------------------------------------------------|--------|-----
+27//! `icon`       | A static icon                                                                    | Icon   | -
+28//! `apt`        | Number of updates available in Debian/Ubuntu based system                        | Number | -
+29//! `pacman`     | Number of updates available in Arch based system                                 | Number | -
+30//! `aur`        | Number of updates available in Arch based system                                 | Number | -
+31//! `dnf`        | Number of updates available in Fedora based system                               | Number | -
+32//! `xbps`       | Number of updates available in Void Linux                                        | Number | -
+33//! `total`      | Number of updates available in all package manager listed                        | Number | -
+34//!
+35//! # Apt
+36//!
+37//! Behind the scenes this uses `apt`, and in order to run it without root privileges i3status-rust will create its own package database in `/tmp/i3rs-apt/` which may take up several MB or more. If you have a custom apt config then this block may not work as expected - in that case please open an issue.
+38//!
+39//! Tip: You can grab the list of available updates using `APT_CONFIG=/tmp/i3rs-apt/apt.conf apt list --upgradable`
+40//!
+41//! # Pacman
+42//!
+43//! Requires fakeroot to be installed (only required for pacman).
+44//!
+45//! Tip: You can grab the list of available updates using `fakeroot pacman -Qu --dbpath /tmp/checkup-db-i3statusrs-$USER/`.
+46//! If you have the `CHECKUPDATES_DB` env var set on your system then substitute that dir instead.
+47//!
+48//! Note: `pikaur` may hang the whole block if there is no internet connectivity [reference](https://github.com/actionless/pikaur/issues/595). In that case, try a different AUR helper.
+49//!
+50//! ### Pacman hook
+51//!
+52//! Tip: On Arch Linux you can setup a `pacman` hook to signal i3status-rs to update after packages
+53//! have been upgraded, so you won't have stale info in your pacman block.
+54//!
+55//! In the block configuration, set `signal = 1` (or other number if `1` is being used by some
+56//! other block):
+57//!
+58//! ```toml
+59//! [[block]]
+60//! block = "packages"
+61//! signal = 1
+62//! ```
+63//!
+64//! Create `/etc/pacman.d/hooks/i3status-rust.hook` with the below contents:
+65//!
+66//! ```ini
+67//! [Trigger]
+68//! Operation = Upgrade
+69//! Type = Package
+70//! Target = *
+71//!
+72//! [Action]
+73//! When = PostTransaction
+74//! Exec = /usr/bin/pkill -SIGRTMIN+1 i3status-rs
+75//! ```
+76//!
+77//! # Example
+78//!
+79//! Apt only config
+80//!
+81//! ```toml
+82//! [[block]]
+83//! block = "packages"
+84//! interval = 1800
+85//! package_manager = ["apt"]
+86//! format = " $icon $apt updates available"
+87//! format_singular = " $icon One update available "
+88//! format_up_to_date = " $icon system up to date "
+89//! [[block.click]]
+90//! # shows dmenu with cached available updates. Any dmenu alternative should also work.
+91//! button = "left"
+92//! cmd = "APT_CONFIG=/tmp/i3rs-apt/apt.conf apt list --upgradable | tail -n +2 | rofi -dmenu"
+93//! [[block.click]]
+94//! # Updates the block on right click
+95//! button = "right"
+96//! update = true
+97//! ```
+98//!
+99//! Pacman only config:
+100//!
+101//! ```toml
+102//! [[block]]
+103//! block = "packages"
+104//! package_manager = ["pacman"]
+105//! interval = 600
+106//! format = " $icon $pacman updates available "
+107//! format_singular = " $icon $pacman update available "
+108//! format_up_to_date = " $icon system up to date "
+109//! [[block.click]]
+110//! # pop-up a menu showing the available updates. Replace wofi with your favourite menu command.
+111//! button = "left"
+112//! cmd = "fakeroot pacman -Qu --dbpath /tmp/checkup-db-i3statusrs-$USER/ | wofi --show dmenu"
+113//! [[block.click]]
+114//! # Updates the block on right click
+115//! button = "right"
+116//! update = true
+117//! ```
+118//!
+119//! Pacman and AUR helper config:
+120//!
+121//! ```toml
+122//! [[block]]
+123//! block = "packages"
+124//! package_manager = ["pacman", "aur"]
+125//! interval = 600
+126//! error_interval = 300
+127//! format = " $icon $pacman + $aur = $total updates available "
+128//! format_singular = " $icon $total update available "
+129//! format_up_to_date = " $icon system up to date "
+130//! # aur_command should output available updates to stdout (ie behave as echo -ne "update\n")
+131//! aur_command = "yay -Qua"
+132//! ```
+133//!
+134//!
+135//! Dnf only config:
+136//!
+137//! ```toml
+138//! [[block]]
+139//! block = "packages"
+140//! package_manager = ["dnf"]
+141//! interval = 1800
+142//! format = " $icon $dnf.eng(w:1) updates available "
+143//! format_singular = " $icon One update available "
+144//! format_up_to_date = " $icon system up to date "
+145//! [[block.click]]
+146//! # shows dmenu with cached available updates. Any dmenu alternative should also work.
+147//! button = "left"
+148//! cmd = "dnf list -q --upgrades | tail -n +2 | rofi -dmenu"
+149//! ```
+150//!
+151//!
+152//! Xbps only config:
+153//!
+154//! ```toml
+155//! [[block]]
+156//! block = "packages"
+157//! package_manager = ["xbps"]
+158//! interval = 1800
+159//! format = " $icon $xbps.eng(w:1) updates available "
+160//! format_singular = " $icon One update available "
+161//! format_up_to_date = " $icon system up to date "
+162//! [[block.click]]
+163//! # shows dmenu with available updates. Any dmenu alternative should also work.
+164//! button = "left"
+165//! cmd = "xbps-install -Mun | dmenu -l 10"
+166//! ```
+167//!
+168//! Multiple package managers config:
+169//!
+170//! Update the list of pending updates every thirty minutes (1800 seconds):
+171//!
+172//! ```toml
+173//! [[block]]
+174//! block = "packages"
+175//! package_manager = ["apt", "pacman", "aur", "dnf", "xbps"]
+176//! interval = 1800
+177//! format = " $icon $apt + $pacman + $aur + $dnf + $xbps = $total updates available "
+178//! format_singular = " $icon One update available "
+179//! format_up_to_date = " $icon system up to date "
+180//! # If a linux update is available, but no ZFS package, it won't be possible to
+181//! # actually perform a system upgrade, so we show a warning.
+182//! warning_updates_regex = "(linux|linux-lts|linux-zen)"
+183//! # If ZFS is available, we know that we can and should do an upgrade, so we show
+184//! # the status as critical.
+185//! critical_updates_regex = "(zfs|zfs-lts)"
+186//! ```
+187//!
+188//! # Icons Used
+189//!
+190//! - `update`
+191
+192pub mod apt;
+193use apt::Apt;
+194
+195pub mod pacman;
+196use pacman::{Aur, Pacman};
+197
+198pub mod dnf;
+199use dnf::Dnf;
+200
+201pub mod xbps;
+202use xbps::Xbps;
+203
+204use regex::Regex;
+205
+206use super::prelude::*;
+207
+208#[derive(Deserialize, Debug, SmartDefault, Clone)]
+209#[serde(deny_unknown_fields, default)]
+210pub struct Config {
+211    #[default(600.into())]
+212    pub interval: Seconds,
+213    pub package_manager: Vec<PackageManager>,
+214    pub format: FormatConfig,
+215    pub format_singular: FormatConfig,
+216    pub format_up_to_date: FormatConfig,
+217    pub warning_updates_regex: Option<String>,
+218    pub critical_updates_regex: Option<String>,
+219    pub ignore_updates_regex: Option<String>,
+220    pub ignore_phased_updates: bool,
+221    pub aur_command: Option<String>,
+222}
+223
+224#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
+225#[serde(rename_all = "lowercase")]
+226pub enum PackageManager {
+227    Apt,
+228    Pacman,
+229    Aur,
+230    Dnf,
+231    Xbps,
+232}
+233
+234pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+235    let mut config: Config = config.clone();
+236
+237    let format = config.format.with_default(" $icon $total.eng(w:1) ")?;
+238    let format_singular = config
+239        .format_singular
+240        .with_default(" $icon $total.eng(w:1) ")?;
+241    let format_up_to_date = config
+242        .format_up_to_date
+243        .with_default(" $icon $total.eng(w:1) ")?;
+244
+245    // If user provide package manager in any of the formats then consider that also
+246    macro_rules! any_format_contains {
+247        ($name:expr) => {
+248            format.contains_key($name)
+249                || format_singular.contains_key($name)
+250                || format_up_to_date.contains_key($name)
+251        };
+252    }
+253
+254    let apt = any_format_contains!("apt");
+255    let aur = any_format_contains!("aur");
+256    let pacman = any_format_contains!("pacman");
+257    let dnf = any_format_contains!("dnf");
+258    let xbps = any_format_contains!("xbps");
+259
+260    if !config.package_manager.contains(&PackageManager::Apt) && apt {
+261        config.package_manager.push(PackageManager::Apt);
+262    }
+263    if !config.package_manager.contains(&PackageManager::Pacman) && pacman {
+264        config.package_manager.push(PackageManager::Pacman);
+265    }
+266    if !config.package_manager.contains(&PackageManager::Aur) && aur {
+267        config.package_manager.push(PackageManager::Aur);
+268    }
+269    if !config.package_manager.contains(&PackageManager::Dnf) && dnf {
+270        config.package_manager.push(PackageManager::Dnf);
+271    }
+272    if !config.package_manager.contains(&PackageManager::Xbps) && xbps {
+273        config.package_manager.push(PackageManager::Xbps);
+274    }
+275
+276    let warning_updates_regex = config
+277        .warning_updates_regex
+278        .as_deref()
+279        .map(Regex::new)
+280        .transpose()
+281        .error("invalid warning updates regex")?;
+282    let critical_updates_regex = config
+283        .critical_updates_regex
+284        .as_deref()
+285        .map(Regex::new)
+286        .transpose()
+287        .error("invalid critical updates regex")?;
+288    let ignore_updates_regex = config
+289        .ignore_updates_regex
+290        .as_deref()
+291        .map(Regex::new)
+292        .transpose()
+293        .error("invalid ignore updates regex")?;
+294
+295    let mut package_manager_vec: Vec<Box<dyn Backend>> = Vec::new();
+296
+297    for &package_manager in config.package_manager.iter() {
+298        package_manager_vec.push(match package_manager {
+299            PackageManager::Apt => Box::new(Apt::new(config.ignore_phased_updates).await?),
+300            PackageManager::Pacman => Box::new(Pacman::new().await?),
+301            PackageManager::Aur => Box::new(Aur::new(
+302                config.aur_command.clone().error("aur_command is not set")?,
+303            )),
+304            PackageManager::Dnf => Box::new(Dnf::new()),
+305            PackageManager::Xbps => Box::new(Xbps::new()),
+306        });
+307    }
+308
+309    loop {
+310        let mut package_manager_map: HashMap<Cow<'static, str>, Value> = HashMap::new();
+311
+312        let mut critical = false;
+313        let mut warning = false;
+314        let mut total_count = 0;
+315
+316        // Iterate over the all package manager listed in Config
+317        for package_manager in &package_manager_vec {
+318            let mut updates = package_manager.get_updates_list().await?;
+319            if let Some(regex) = ignore_updates_regex.clone() {
+320                updates.retain(|u| !regex.is_match(u));
+321            }
+322
+323            let updates_count = updates.len();
+324
+325            package_manager_map.insert(package_manager.name(), Value::number(updates_count));
+326            total_count += updates_count;
+327
+328            warning |= warning_updates_regex
+329                .as_ref()
+330                .is_some_and(|regex| has_matching_update(&updates, regex));
+331            critical |= critical_updates_regex
+332                .as_ref()
+333                .is_some_and(|regex| has_matching_update(&updates, regex));
+334        }
+335
+336        let mut widget = Widget::new();
+337
+338        package_manager_map.insert("icon".into(), Value::icon("update"));
+339        package_manager_map.insert("total".into(), Value::number(total_count));
+340
+341        widget.set_format(match total_count {
+342            0 => format_up_to_date.clone(),
+343            1 => format_singular.clone(),
+344            _ => format.clone(),
+345        });
+346        widget.set_values(package_manager_map);
+347
+348        widget.state = match total_count {
+349            0 => State::Idle,
+350            _ => {
+351                if critical {
+352                    State::Critical
+353                } else if warning {
+354                    State::Warning
+355                } else {
+356                    State::Info
+357                }
+358            }
+359        };
+360        api.set_widget(widget)?;
+361
+362        select! {
+363            _ = sleep(config.interval.0) => (),
+364            _ = api.wait_for_update_request() => (),
+365        }
+366    }
+367}
+368
+369#[async_trait]
+370pub trait Backend {
+371    fn name(&self) -> Cow<'static, str>;
+372
+373    async fn get_updates_list(&self) -> Result<Vec<String>>;
+374}
+375
+376pub fn has_matching_update(updates: &[String], regex: &Regex) -> bool {
+377    updates.iter().any(|line| regex.is_match(line))
+378}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/packages/apt.rs.html b/src/i3status_rs/blocks/packages/apt.rs.html new file mode 100644 index 0000000000..fde5f4ad4a --- /dev/null +++ b/src/i3status_rs/blocks/packages/apt.rs.html @@ -0,0 +1,123 @@ +apt.rs - source

i3status_rs/blocks/packages/
apt.rs

1use std::env;
+2use std::process::Stdio;
+3
+4use tokio::fs::{File, create_dir_all};
+5use tokio::process::Command;
+6
+7use super::*;
+8
+9#[derive(Default)]
+10pub struct Apt {
+11    pub(super) config_file: String,
+12    pub(super) ignore_phased_updates: bool,
+13}
+14
+15impl Apt {
+16    pub async fn new(ignore_phased_updates: bool) -> Result<Self> {
+17        let mut apt = Apt {
+18            config_file: String::new(),
+19            ignore_phased_updates,
+20        };
+21
+22        apt.setup().await?;
+23
+24        Ok(apt)
+25    }
+26
+27    async fn is_phased_update(&self, package_line: &str) -> Result<bool> {
+28        let package_name_regex = regex!(r#"(.*)/.*"#);
+29        let package_name = &package_name_regex
+30            .captures(package_line)
+31            .error("Couldn't find package name")?[1];
+32
+33        let output = String::from_utf8(
+34            Command::new("apt-cache")
+35                .args(["-c", &self.config_file, "policy", package_name])
+36                .output()
+37                .await
+38                .error("Problem running apt-cache command")?
+39                .stdout,
+40        )
+41        .error("Problem capturing apt-cache command output")?;
+42
+43        let phased_regex = regex!(r".*\(phased (\d+)%\).*");
+44        Ok(match phased_regex.captures(&output) {
+45            Some(matches) => &matches[1] != "100",
+46            None => false,
+47        })
+48    }
+49
+50    async fn setup(&mut self) -> Result<()> {
+51        let mut cache_dir = env::temp_dir();
+52        cache_dir.push("i3rs-apt");
+53        if !cache_dir.exists() {
+54            create_dir_all(&cache_dir)
+55                .await
+56                .error("Failed to create temp dir")?;
+57        }
+58
+59        let apt_config = format!(
+60            "Dir::State \"{}\";\n
+61         Dir::State::lists \"lists\";\n
+62         Dir::Cache \"{}\";\n
+63         Dir::Cache::srcpkgcache \"srcpkgcache.bin\";\n
+64         Dir::Cache::pkgcache \"pkgcache.bin\";",
+65            cache_dir.display(),
+66            cache_dir.display(),
+67        );
+68
+69        let mut config_file = cache_dir;
+70        config_file.push("apt.conf");
+71        let config_file = config_file.to_str().unwrap();
+72
+73        self.config_file = config_file.to_string();
+74
+75        let mut file = File::create(&config_file)
+76            .await
+77            .error("Failed to create config file")?;
+78        file.write_all(apt_config.as_bytes())
+79            .await
+80            .error("Failed to write to config file")?;
+81
+82        Ok(())
+83    }
+84}
+85
+86#[async_trait]
+87impl Backend for Apt {
+88    fn name(&self) -> Cow<'static, str> {
+89        "apt".into()
+90    }
+91
+92    async fn get_updates_list(&self) -> Result<Vec<String>> {
+93        Command::new("apt")
+94            .env("APT_CONFIG", &self.config_file)
+95            .args(["update"])
+96            .stdout(Stdio::null())
+97            .stdin(Stdio::null())
+98            .spawn()
+99            .error("Failed to run `apt update`")?
+100            .wait()
+101            .await
+102            .error("Failed to run `apt update`")?;
+103        let stdout = Command::new("apt")
+104            .env("LANG", "C")
+105            .env("APT_CONFIG", &self.config_file)
+106            .args(["list", "--upgradable"])
+107            .output()
+108            .await
+109            .error("Problem running apt command")?
+110            .stdout;
+111
+112        let updates = String::from_utf8(stdout).error("apt produced non-UTF8 output")?;
+113        let mut updates_list: Vec<String> = Vec::new();
+114
+115        for update in updates.lines().filter(|line| line.contains("[upgradable")) {
+116            if !self.ignore_phased_updates || !self.is_phased_update(update).await? {
+117                updates_list.push(update.to_string());
+118            }
+119        }
+120
+121        Ok(updates_list)
+122    }
+123}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/packages/dnf.rs.html b/src/i3status_rs/blocks/packages/dnf.rs.html new file mode 100644 index 0000000000..421fa564e8 --- /dev/null +++ b/src/i3status_rs/blocks/packages/dnf.rs.html @@ -0,0 +1,37 @@ +dnf.rs - source

i3status_rs/blocks/packages/
dnf.rs

1use tokio::process::Command;
+2
+3use super::super::packages::*;
+4
+5#[derive(Default)]
+6pub struct Dnf;
+7
+8impl Dnf {
+9    pub fn new() -> Self {
+10        Default::default()
+11    }
+12}
+13
+14#[async_trait]
+15impl Backend for Dnf {
+16    fn name(&self) -> Cow<'static, str> {
+17        "dnf".into()
+18    }
+19
+20    async fn get_updates_list(&self) -> Result<Vec<String>> {
+21        let stdout = Command::new("sh")
+22            .env("LC_LANG", "C")
+23            .args(["-c", "dnf check-update -q --skip-broken"])
+24            .output()
+25            .await
+26            .error("Failed to run dnf check-update")?
+27            .stdout;
+28        let updates = String::from_utf8(stdout).error("dnf produced non-UTF8 output")?;
+29        let updates: Vec<String> = updates
+30            .lines()
+31            .filter(|line| line.len() > 1)
+32            .map(|lines| lines.to_string())
+33            .collect();
+34
+35        Ok(updates)
+36    }
+37}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/packages/pacman.rs.html b/src/i3status_rs/blocks/packages/pacman.rs.html new file mode 100644 index 0000000000..57cedfafac --- /dev/null +++ b/src/i3status_rs/blocks/packages/pacman.rs.html @@ -0,0 +1,160 @@ +pacman.rs - source

i3status_rs/blocks/packages/
pacman.rs

1use std::env;
+2use std::path::PathBuf;
+3use std::process::Stdio;
+4
+5use tokio::fs::{create_dir_all, symlink};
+6use tokio::process::Command;
+7
+8use super::*;
+9use crate::util::has_command;
+10
+11make_log_macro!(debug, "pacman");
+12
+13pub static PACMAN_UPDATES_DB: LazyLock<PathBuf> = LazyLock::new(|| {
+14    let path = match env::var_os("CHECKUPDATES_DB") {
+15        Some(val) => val.into(),
+16        None => {
+17            let mut path = env::temp_dir();
+18            let user = env::var("USER");
+19            path.push(format!(
+20                "checkup-db-i3statusrs-{}",
+21                user.as_deref().unwrap_or("no-user")
+22            ));
+23            path
+24        }
+25    };
+26    debug!("Using {} as updates DB path", path.display());
+27    path
+28});
+29
+30pub static PACMAN_DB: LazyLock<PathBuf> = LazyLock::new(|| {
+31    let path = env::var_os("DBPath")
+32        .map(Into::into)
+33        .unwrap_or_else(|| PathBuf::from("/var/lib/pacman/"));
+34    debug!("Using {} as pacman DB path", path.display());
+35    path
+36});
+37
+38pub struct Pacman;
+39
+40pub struct Aur {
+41    aur_command: String,
+42}
+43
+44impl Pacman {
+45    pub async fn new() -> Result<Self> {
+46        check_fakeroot_command_exists().await?;
+47
+48        Ok(Self)
+49    }
+50}
+51
+52impl Aur {
+53    pub fn new(aur_command: String) -> Self {
+54        Aur { aur_command }
+55    }
+56}
+57
+58#[async_trait]
+59impl Backend for Pacman {
+60    fn name(&self) -> Cow<'static, str> {
+61        "pacman".into()
+62    }
+63
+64    async fn get_updates_list(&self) -> Result<Vec<String>> {
+65        // Create the determined `checkup-db` path recursively
+66        create_dir_all(&*PACMAN_UPDATES_DB).await.or_error(|| {
+67            format!(
+68                "Failed to create checkup-db directory at '{}'",
+69                PACMAN_UPDATES_DB.display()
+70            )
+71        })?;
+72
+73        // Create symlink to local cache in `checkup-db` if required
+74        let local_cache = PACMAN_UPDATES_DB.join("local");
+75        if !local_cache.exists() {
+76            symlink(PACMAN_DB.join("local"), local_cache)
+77                .await
+78                .error("Failed to created required symlink")?;
+79        }
+80
+81        // Update database
+82        let status = Command::new("fakeroot")
+83            .env("LC_ALL", "C")
+84            .args([
+85                "--".as_ref(),
+86                "pacman".as_ref(),
+87                "-Sy".as_ref(),
+88                "--dbpath".as_ref(),
+89                PACMAN_UPDATES_DB.as_os_str(),
+90                "--logfile".as_ref(),
+91                "/dev/null".as_ref(),
+92            ])
+93            .stdout(Stdio::null())
+94            .status()
+95            .await
+96            .error("Failed to run command")?;
+97        if !status.success() {
+98            debug!("{}", status);
+99            return Err(Error::new("pacman -Sy exited with non zero exit status"));
+100        }
+101
+102        let stdout = Command::new("fakeroot")
+103            .env("LC_ALL", "C")
+104            .args([
+105                "--".as_ref(),
+106                "pacman".as_ref(),
+107                "-Qu".as_ref(),
+108                "--dbpath".as_ref(),
+109                PACMAN_UPDATES_DB.as_os_str(),
+110            ])
+111            .output()
+112            .await
+113            .error("There was a problem running the pacman commands")?
+114            .stdout;
+115
+116        let updates = String::from_utf8(stdout).error("Pacman produced non-UTF8 output")?;
+117
+118        let updates = updates
+119            .lines()
+120            .filter(|line| !line.contains("[ignored]"))
+121            .map(|line| line.to_string())
+122            .collect();
+123
+124        Ok(updates)
+125    }
+126}
+127
+128#[async_trait]
+129impl Backend for Aur {
+130    fn name(&self) -> Cow<'static, str> {
+131        "aur".into()
+132    }
+133
+134    async fn get_updates_list(&self) -> Result<Vec<String>> {
+135        let stdout = Command::new("sh")
+136            .args(["-c", &self.aur_command])
+137            .output()
+138            .await
+139            .or_error(|| format!("aur command: {} failed", self.aur_command))?
+140            .stdout;
+141        let updates = String::from_utf8(stdout)
+142            .error("There was a problem while converting the aur command output to a string")?;
+143
+144        let updates = updates
+145            .lines()
+146            .filter(|line| !line.contains("[ignored]"))
+147            .map(|line| line.to_string())
+148            .collect();
+149
+150        Ok(updates)
+151    }
+152}
+153
+154async fn check_fakeroot_command_exists() -> Result<()> {
+155    if !has_command("fakeroot").await? {
+156        Err(Error::new("fakeroot not found"))
+157    } else {
+158        Ok(())
+159    }
+160}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/packages/xbps.rs.html b/src/i3status_rs/blocks/packages/xbps.rs.html new file mode 100644 index 0000000000..a60605f81c --- /dev/null +++ b/src/i3status_rs/blocks/packages/xbps.rs.html @@ -0,0 +1,38 @@ +xbps.rs - source

i3status_rs/blocks/packages/
xbps.rs

1use tokio::process::Command;
+2
+3use super::*;
+4
+5#[derive(Default)]
+6pub struct Xbps;
+7
+8impl Xbps {
+9    pub fn new() -> Self {
+10        Default::default()
+11    }
+12}
+13
+14#[async_trait]
+15impl Backend for Xbps {
+16    fn name(&self) -> Cow<'static, str> {
+17        "xbps".into()
+18    }
+19
+20    async fn get_updates_list(&self) -> Result<Vec<String>> {
+21        let stdout = Command::new("xbps-install")
+22            .env("LC_LANG", "C")
+23            .args(["-M", "-u", "-n"])
+24            .output()
+25            .await
+26            .error("Problem running xbps-install command")?
+27            .stdout;
+28
+29        let updates = String::from_utf8(stdout).expect("xbps-install produced non-UTF8 output");
+30        let updates_list: Vec<String> = updates
+31            .lines()
+32            .filter(|line| line.len() > 1)
+33            .map(|line| line.to_string())
+34            .collect();
+35
+36        Ok(updates_list)
+37    }
+38}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/pomodoro.rs.html b/src/i3status_rs/blocks/pomodoro.rs.html new file mode 100644 index 0000000000..8f6985e3a3 --- /dev/null +++ b/src/i3status_rs/blocks/pomodoro.rs.html @@ -0,0 +1,352 @@ +pomodoro.rs - source

i3status_rs/blocks/
pomodoro.rs

1//! A [pomodoro timer](https://en.wikipedia.org/wiki/Pomodoro_Technique)
+2//!
+3//! # Technique
+4//!
+5//! There are six steps in the original technique:
+6//! 1) Decide on the task to be done.
+7//! 2) Set the pomodoro timer (traditionally to 25 minutes).
+8//! 3) Work on the task.
+9//! 4) End work when the timer rings and put a checkmark on a piece of paper.
+10//! 5) If you have fewer than four checkmarks, take a short break (3–5 minutes) and then return to step 2.
+11//! 6) After four pomodoros, take a longer break (15–30 minutes), reset your checkmark count to zero, then go to step 1.
+12//!
+13//!
+14//! # Configuration
+15//!
+16//! Key | Values | Default
+17//! ----|--------|--------
+18//! `format` | The format used when in idle, prompt, or notify states | <code>\" $icon{ $message\|} \"</code>
+19//! `pomodoro_format` | The format used when the pomodoro is running or paused | <code>\" $icon $status_icon{ $completed_pomodoros.tally()\|} $time_remaining.duration(hms:true) \"</code>
+20//! `break_format` |The format used when the pomodoro is during the break | <code>\" $icon $status_icon Break: $time_remaining.duration(hms:true) \"</code>
+21//! `message` | Message when timer expires | `"Pomodoro over! Take a break!"`
+22//! `break_message` | Message when break is over | `"Break over! Time to work!"`
+23//! `notify_cmd` | A shell command to run as a notifier. `{msg}` will be substituted with either `message` or `break_message`. | `None`
+24//! `blocking_cmd` | Is `notify_cmd` blocking? If it is, then pomodoro block will wait until the command finishes before proceeding. Otherwise, you will have to click on the block in order to proceed. | `false`
+25//!
+26//! Placeholder           | Value                                         | Type     | Supported by
+27//! ----------------------|-----------------------------------------------|----------|--------------
+28//! `icon`                | A static icon                                 | Icon     | All formats
+29//! `status_icon`         | An icon that reflects the pomodoro state      | Icon     | `pomodoro_format`, `break_format`
+30//! `message`             | Current message                               | Text     | `format`
+31//! `time_remaining`      | How much time is left (minutes)               | Duration | `pomodoro_format`, `break_format`
+32//! `completed_pomodoros` | The number of completed pomodoros             | Number   | `pomodoro_format`
+33//!
+34//! # Example
+35//!
+36//! Use `swaynag` as a notifier:
+37//!
+38//! ```toml
+39//! [[block]]
+40//! block = "pomodoro"
+41//! notify_cmd = "swaynag -m '{msg}'"
+42//! blocking_cmd = true
+43//! ```
+44//!
+45//! Use `notify-send` as a notifier:
+46//!
+47//! ```toml
+48//! [[block]]
+49//! block = "pomodoro"
+50//! notify_cmd = "notify-send '{msg}'"
+51//! blocking_cmd = false
+52//! ```
+53//!
+54//! # Icons Used
+55//! - `pomodoro`
+56//! - `pomodoro_started`
+57//! - `pomodoro_stopped`
+58//! - `pomodoro_paused`
+59//! - `pomodoro_break`
+60
+61use num_traits::{Num, NumAssignOps, SaturatingSub};
+62use tokio::sync::mpsc;
+63
+64use super::prelude::*;
+65use crate::{
+66    formatting::Format,
+67    subprocess::{spawn_shell, spawn_shell_sync},
+68};
+69use std::time::Instant;
+70
+71make_log_macro!(debug, "pomodoro");
+72
+73#[derive(Deserialize, Debug, SmartDefault)]
+74#[serde(deny_unknown_fields, default)]
+75pub struct Config {
+76    pub format: FormatConfig,
+77    pub pomodoro_format: FormatConfig,
+78    pub break_format: FormatConfig,
+79    #[default("Pomodoro over! Take a break!".into())]
+80    pub message: String,
+81    #[default("Break over! Time to work!".into())]
+82    pub break_message: String,
+83    pub notify_cmd: Option<String>,
+84    pub blocking_cmd: bool,
+85}
+86
+87enum PomodoroState {
+88    Idle,
+89    Prompt,
+90    Notify,
+91    Break,
+92    PomodoroRunning,
+93    PomodoroPaused,
+94}
+95
+96impl PomodoroState {
+97    fn get_block_state(&self) -> State {
+98        use PomodoroState::*;
+99        match self {
+100            Idle | PomodoroPaused => State::Idle,
+101            Prompt => State::Warning,
+102            Notify => State::Good,
+103            Break | PomodoroRunning => State::Info,
+104        }
+105    }
+106
+107    fn get_status_icon(&self) -> Option<&'static str> {
+108        use PomodoroState::*;
+109        match self {
+110            Idle => Some("pomodoro_stopped"),
+111            Break => Some("pomodoro_break"),
+112            PomodoroRunning => Some("pomodoro_started"),
+113            PomodoroPaused => Some("pomodoro_paused"),
+114            _ => None,
+115        }
+116    }
+117}
+118
+119struct Block<'a> {
+120    widget: Widget,
+121    actions: mpsc::UnboundedReceiver<BlockAction>,
+122    api: &'a CommonApi,
+123    config: &'a Config,
+124    state: PomodoroState,
+125    format: Format,
+126    pomodoro_format: Format,
+127    break_format: Format,
+128}
+129
+130impl Block<'_> {
+131    async fn set_text(&mut self, additional_values: Values) -> Result<()> {
+132        let mut values = map! {
+133            "icon" => Value::icon("pomodoro"),
+134        };
+135        values.extend(additional_values);
+136
+137        if let Some(icon) = self.state.get_status_icon() {
+138            values.insert("status_icon".into(), Value::icon(icon));
+139        }
+140        self.widget.set_format(match self.state {
+141            PomodoroState::Idle | PomodoroState::Prompt | PomodoroState::Notify => {
+142                self.format.clone()
+143            }
+144            PomodoroState::Break => self.break_format.clone(),
+145            PomodoroState::PomodoroRunning | PomodoroState::PomodoroPaused => {
+146                self.pomodoro_format.clone()
+147            }
+148        });
+149        self.widget.state = self.state.get_block_state();
+150        debug!("{:?}", values);
+151        self.widget.set_values(values);
+152        self.api.set_widget(self.widget.clone())
+153    }
+154
+155    async fn wait_for_click(&mut self, button: &str) -> Result<()> {
+156        while self.actions.recv().await.error("channel closed")? != button {}
+157        Ok(())
+158    }
+159
+160    async fn read_params(&mut self) -> Result<Option<(Duration, Duration, usize)>> {
+161        self.state = PomodoroState::Prompt;
+162        let task_len = match self.read_number(25, "Task length:").await? {
+163            Some(task_len) => task_len,
+164            None => return Ok(None),
+165        };
+166        let break_len = match self.read_number(5, "Break length:").await? {
+167            Some(break_len) => break_len,
+168            None => return Ok(None),
+169        };
+170        let pomodoros = match self.read_number(4, "Pomodoros:").await? {
+171            Some(pomodoros) => pomodoros,
+172            None => return Ok(None),
+173        };
+174        Ok(Some((
+175            Duration::from_secs(task_len * 60),
+176            Duration::from_secs(break_len * 60),
+177            pomodoros,
+178        )))
+179    }
+180
+181    async fn read_number<T: Num + NumAssignOps + SaturatingSub + std::fmt::Display>(
+182        &mut self,
+183        mut number: T,
+184        msg: &str,
+185    ) -> Result<Option<T>> {
+186        loop {
+187            self.set_text(map! {"message" => Value::text(format!("{msg} {number}"))})
+188                .await?;
+189            match &*self.actions.recv().await.error("channel closed")? {
+190                "_left" => break,
+191                "_up" => number += T::one(),
+192                "_down" => number = number.saturating_sub(&T::one()),
+193                "_middle" | "_right" => return Ok(None),
+194                _ => (),
+195            }
+196        }
+197        Ok(Some(number))
+198    }
+199
+200    async fn set_notification(&mut self, message: &str) -> Result<()> {
+201        self.state = PomodoroState::Notify;
+202        self.set_text(map! {"message" => Value::text(message.to_string())})
+203            .await?;
+204        if let Some(cmd) = &self.config.notify_cmd {
+205            let cmd = cmd.replace("{msg}", message);
+206            if self.config.blocking_cmd {
+207                spawn_shell_sync(&cmd)
+208                    .await
+209                    .error("failed to run notify_cmd")?;
+210            } else {
+211                spawn_shell(&cmd).error("failed to run notify_cmd")?;
+212                self.wait_for_click("_left").await?;
+213            }
+214        } else {
+215            self.wait_for_click("_left").await?;
+216        }
+217        Ok(())
+218    }
+219
+220    async fn run_pomodoro(
+221        &mut self,
+222        task_len: Duration,
+223        break_len: Duration,
+224        pomodoros: usize,
+225    ) -> Result<()> {
+226        let interval: Seconds = 1.into();
+227        let mut update_timer = interval.timer();
+228        for pomodoro in 0..pomodoros {
+229            let mut total_elapsed = Duration::ZERO;
+230            'pomodoro_run: loop {
+231                // Task timer
+232                self.state = PomodoroState::PomodoroRunning;
+233                let timer = Instant::now();
+234                loop {
+235                    let elapsed = timer.elapsed();
+236                    if total_elapsed + elapsed >= task_len {
+237                        break 'pomodoro_run;
+238                    }
+239                    let remaining_time = task_len - total_elapsed - elapsed;
+240                    let values = map! {
+241                        [if pomodoro != 0] "completed_pomodoros" => Value::number(pomodoro),
+242                        "time_remaining" => Value::duration(remaining_time),
+243                    };
+244                    self.set_text(values.clone()).await?;
+245                    select! {
+246                        _ = update_timer.tick() => (),
+247                        Some(action) = self.actions.recv() => match action.as_ref() {
+248                            "_middle" | "_right" => return Ok(()),
+249                            "_left" => {
+250                                self.state = PomodoroState::PomodoroPaused;
+251                                self.set_text(values).await?;
+252                                total_elapsed += timer.elapsed();
+253                                loop {
+254                                    match self.actions.recv().await.as_deref() {
+255                                        Some("_middle") | Some("_right") => return Ok(()),
+256                                        Some("_left") =>  {
+257                                            continue 'pomodoro_run;
+258                                        },
+259                                        _ => ()
+260
+261                                    }
+262                                }
+263                            },
+264                            _ => ()
+265                        }
+266                    }
+267                }
+268            }
+269
+270            // Show break message
+271            self.set_notification(&self.config.message).await?;
+272
+273            // No break after the last pomodoro
+274            if pomodoro == pomodoros - 1 {
+275                break;
+276            }
+277
+278            // Break timer
+279            self.state = PomodoroState::Break;
+280            let timer = Instant::now();
+281            loop {
+282                let elapsed = timer.elapsed();
+283                if elapsed >= break_len {
+284                    break;
+285                }
+286                let remaining_time = break_len - elapsed;
+287                self.set_text(map! {
+288                    "time_remaining" => Value::duration(remaining_time),
+289                })
+290                .await?;
+291                select! {
+292                    _ = update_timer.tick() => (),
+293                    Some(action) = self.actions.recv() => match action.as_ref() {
+294                        "_middle" | "_right" => return Ok(()),
+295                        _ => ()
+296                    }
+297                }
+298            }
+299
+300            // Show task message
+301            self.set_notification(&self.config.break_message).await?;
+302        }
+303
+304        Ok(())
+305    }
+306}
+307
+308pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+309    api.set_default_actions(&[
+310        (MouseButton::Left, None, "_left"),
+311        (MouseButton::Middle, None, "_middle"),
+312        (MouseButton::Right, None, "_right"),
+313        (MouseButton::WheelUp, None, "_up"),
+314        (MouseButton::WheelDown, None, "_down"),
+315    ])?;
+316
+317    let format = config.format.clone().with_default(" $icon{ $message|} ")?;
+318
+319    let pomodoro_format = config.pomodoro_format.clone().with_default(
+320        " $icon $status_icon{ $completed_pomodoros.tally()|} $time_remaining.duration(hms:true) ",
+321    )?;
+322
+323    let break_format = config
+324        .break_format
+325        .clone()
+326        .with_default(" $icon $status_icon Break: $time_remaining.duration(hms:true) ")?;
+327
+328    let widget = Widget::new();
+329
+330    let mut block = Block {
+331        widget,
+332        actions: api.get_actions()?,
+333        api,
+334        config,
+335        state: PomodoroState::Idle,
+336        format,
+337        pomodoro_format,
+338        break_format,
+339    };
+340
+341    loop {
+342        // Send collaped block
+343        block.state = PomodoroState::Idle;
+344        block.set_text(Values::default()).await?;
+345
+346        block.wait_for_click("_left").await?;
+347
+348        if let Some((task_len, break_len, pomodoros)) = block.read_params().await? {
+349            block.run_pomodoro(task_len, break_len, pomodoros).await?;
+350        }
+351    }
+352}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/prelude.rs.html b/src/i3status_rs/blocks/prelude.rs.html new file mode 100644 index 0000000000..8d1b758325 --- /dev/null +++ b/src/i3status_rs/blocks/prelude.rs.html @@ -0,0 +1,33 @@ +prelude.rs - source

i3status_rs/blocks/
prelude.rs

1pub use super::{BlockAction, CommonApi};
+2
+3pub(crate) use crate::REQWEST_CLIENT;
+4pub(crate) use crate::REQWEST_CLIENT_IPV4;
+5pub use crate::click::MouseButton;
+6pub use crate::errors::*;
+7pub use crate::formatting::{Values, config::Config as FormatConfig, value::Value};
+8pub use crate::util::{default, new_dbus_connection, new_system_dbus_connection};
+9pub use crate::widget::{State, Widget};
+10pub use crate::wrappers::{Seconds, ShellString};
+11
+12pub use serde::Deserialize;
+13
+14pub use backon::{ExponentialBuilder, Retryable};
+15
+16pub use std::borrow::Cow;
+17pub use std::collections::HashMap;
+18pub use std::fmt::Write;
+19pub use std::pin::Pin;
+20pub use std::sync::LazyLock;
+21pub use std::time::Duration;
+22
+23pub use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt};
+24pub use tokio::select;
+25pub use tokio::time::sleep;
+26
+27pub use futures::{Stream, StreamExt};
+28
+29pub use smart_default::SmartDefault;
+30
+31pub use async_trait::async_trait;
+32
+33pub use crate::util::StreamExtDebounced as _;
\ No newline at end of file diff --git a/src/i3status_rs/blocks/privacy.rs.html b/src/i3status_rs/blocks/privacy.rs.html new file mode 100644 index 0000000000..4f5fe185be --- /dev/null +++ b/src/i3status_rs/blocks/privacy.rs.html @@ -0,0 +1,250 @@ +privacy.rs - source

i3status_rs/blocks/
privacy.rs

1//! Privacy Monitor
+2//!
+3//! # Configuration
+4//!
+5//! Key        | Values | Default|
+6//! -----------|--------|--------|
+7//! `driver` | The configuration of a driver (see below). | **Required**
+8//! `format`   | Format string. | <code>\"{ $icon_audio \|}{ $icon_audio_sink \|}{ $icon_video \|}{ $icon_webcam \|}{ $icon_unknown \|}\"</code> |
+9//! `format_alt`   | Format string. | <code>\"{ $icon_audio $info_audio \|}{ $icon_audio_sink $info_audio_sink \|}{ $icon_video $info_video \|}{ $icon_webcam $info_webcam \|}{ $icon_unknown $info_unknown \|}\"</code> |
+10//!
+11//! # pipewire Options (requires the pipewire feature to be enabled)
+12//!
+13//! Key | Values | Required | Default
+14//! ----|--------|----------|--------
+15//! `name` | `pipewire` | Yes | None
+16//! `exclude_output` | An output node to ignore, example: `["HD Pro Webcam C920"]` | No | `[]`
+17//! `exclude_input` | An input node to ignore, example: `["openrgb"]` | No | `[]`
+18//! `display`   | Which node field should be used as a display name, options: `name`, `description`, `nickname` | No | `name`
+19//!
+20//! # vl4 Options
+21//!
+22//! Key | Values | Required | Default
+23//! ----|--------|----------|--------
+24//! `name` | `vl4` | Yes | None
+25//! `exclude_device` | A device to ignore, example: `["/dev/video5"]` | No | `[]`
+26//! `exclude_consumer` | Processes to ignore | No | `["pipewire", "wireplumber"]`
+27//!
+28//! # Available Format Keys
+29//!
+30//! Placeholder                                      | Value                                          | Type     | Unit
+31//! -------------------------------------------------|------------------------------------------------|----------|-----
+32//! `icon_{audio,audio_sink,video,webcam,unknown}`   | A static icon                                  | Icon     | -
+33//! `info_{audio,audio_sink,video,webcam,unknown}`   | The mapping of which source are being consumed | Text     | -
+34//!
+35//! You can use the suffixes noted above to get the following:
+36//!
+37//! Suffix       | Description
+38//! -------------|------------
+39//! `audio`      | Captured audio (ex. Mic)
+40//! `audio_sink` | Audio captured from a sink (ex. openrgb)
+41//! `video`      | Video capture (ex. screen capture)
+42//! `webcam`     | Webcam capture
+43//! `unknown`    | Anything else
+44//!
+45//! # Available Actions
+46//!
+47//! Action          | Description                               | Default button
+48//! ----------------|-------------------------------------------|---------------
+49//! `toggle_format` | Toggles between `format` and `format_alt` | Left
+50//!
+51//! # Example
+52//!
+53//! ```toml
+54//! [[block]]
+55//! block = "privacy"
+56//! [[block.driver]]
+57//! name = "v4l"
+58//! [[block.driver]]
+59//! name = "pipewire"
+60//! exclude_input = ["openrgb"]
+61//! display = "nickname"
+62//! ```
+63//!
+64//! # Icons Used
+65//! - `microphone`
+66//! - `volume`
+67//! - `xrandr`
+68//! - `webcam`
+69//! - `unknown`
+70
+71use futures::future::{select_all, try_join_all};
+72
+73use super::prelude::*;
+74
+75make_log_macro!(debug, "privacy");
+76
+77#[cfg(feature = "pipewire")]
+78mod pipewire;
+79mod v4l;
+80
+81#[derive(Deserialize, Debug)]
+82#[serde(deny_unknown_fields)]
+83pub struct Config {
+84    #[serde(default)]
+85    pub format: FormatConfig,
+86    #[serde(default)]
+87    pub format_alt: FormatConfig,
+88    pub driver: Vec<PrivacyDriver>,
+89}
+90
+91#[derive(Deserialize, Debug)]
+92#[serde(tag = "name", rename_all = "snake_case")]
+93pub enum PrivacyDriver {
+94    #[cfg(feature = "pipewire")]
+95    Pipewire(pipewire::Config),
+96    V4l(v4l::Config),
+97}
+98
+99#[derive(Debug, Clone, Eq, Hash, PartialEq)]
+100enum Type {
+101    Audio,
+102    AudioSink,
+103    Video,
+104    Webcam,
+105    Unknown,
+106}
+107
+108// {type: {source: {destination: count}}
+109type PrivacyInfo = HashMap<Type, PrivacyInfoInner>;
+110
+111type PrivacyInfoInnerType = HashMap<String, HashMap<String, usize>>;
+112#[derive(Default, Debug)]
+113struct PrivacyInfoInner(PrivacyInfoInnerType);
+114
+115impl std::ops::Deref for PrivacyInfoInner {
+116    type Target = PrivacyInfoInnerType;
+117    fn deref(&self) -> &Self::Target {
+118        &self.0
+119    }
+120}
+121
+122impl std::ops::DerefMut for PrivacyInfoInner {
+123    fn deref_mut(&mut self) -> &mut Self::Target {
+124        &mut self.0
+125    }
+126}
+127
+128impl std::fmt::Display for PrivacyInfoInner {
+129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+130        write!(
+131            f,
+132            "{{ {} }}",
+133            itertools::join(
+134                self.iter().map(|(source, destinations)| {
+135                    format!(
+136                        "{} => [ {} ]",
+137                        source,
+138                        itertools::join(
+139                            destinations
+140                                .iter()
+141                                .map(|(destination, count)| if count == &1 {
+142                                    destination.into()
+143                                } else {
+144                                    format!("{destination} (x{count})")
+145                                }),
+146                            ", "
+147                        )
+148                    )
+149                }),
+150                ", ",
+151            )
+152        )
+153    }
+154}
+155
+156#[async_trait]
+157trait PrivacyMonitor {
+158    async fn get_info(&mut self) -> Result<PrivacyInfo>;
+159    async fn wait_for_change(&mut self) -> Result<()>;
+160}
+161
+162pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+163    let mut actions = api.get_actions()?;
+164    api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
+165
+166    let mut format = config.format.with_default(
+167        "{ $icon_audio |}{ $icon_audio_sink |}{ $icon_video |}{ $icon_webcam |}{ $icon_unknown |}",
+168    )?;
+169    let mut format_alt = config.format_alt.with_default("{ $icon_audio $info_audio |}{ $icon_audio_sink $info_audio_sink |}{ $icon_video $info_video |}{ $icon_webcam $info_webcam |}{ $icon_unknown $info_unknown |}")?;
+170
+171    let mut drivers: Vec<Box<dyn PrivacyMonitor + Send + Sync>> = Vec::new();
+172
+173    for driver in &config.driver {
+174        drivers.push(match driver {
+175            #[cfg(feature = "pipewire")]
+176            PrivacyDriver::Pipewire(driver_config) => {
+177                Box::new(pipewire::Monitor::new(driver_config).await?)
+178            }
+179            PrivacyDriver::V4l(driver_config) => {
+180                Box::new(v4l::Monitor::new(driver_config, api.error_interval).await?)
+181            }
+182        });
+183    }
+184
+185    loop {
+186        let mut widget = Widget::new().with_format(format.clone());
+187
+188        let mut info = PrivacyInfo::default();
+189        //Merge driver info
+190        for driver_info in try_join_all(drivers.iter_mut().map(|driver| driver.get_info())).await? {
+191            for (type_, mapping) in driver_info {
+192                let existing_mapping = info.entry(type_).or_default();
+193                for (source, dest) in mapping.0 {
+194                    existing_mapping.entry(source).or_default().extend(dest);
+195                }
+196            }
+197        }
+198        if !info.is_empty() {
+199            widget.state = State::Warning;
+200        }
+201
+202        let mut values = Values::new();
+203
+204        if let Some(info_by_type) = info.get(&Type::Audio) {
+205            map! { @extend values
+206                "icon_audio" => Value::icon("microphone"),
+207                "info_audio" => Value::text(info_by_type.to_string())
+208            }
+209        }
+210        if let Some(info_by_type) = info.get(&Type::AudioSink) {
+211            map! { @extend values
+212                "icon_audio_sink" => Value::icon("volume"),
+213                "info_audio_sink" => Value::text(info_by_type.to_string())
+214            }
+215        }
+216        if let Some(info_by_type) = info.get(&Type::Video) {
+217            map! { @extend values
+218                "icon_video" => Value::icon("xrandr"),
+219                "info_video" => Value::text(info_by_type.to_string())
+220            }
+221        }
+222        if let Some(info_by_type) = info.get(&Type::Webcam) {
+223            map! { @extend values
+224                "icon_webcam" => Value::icon("webcam"),
+225                "info_webcam" => Value::text(info_by_type.to_string())
+226            }
+227        }
+228        if let Some(info_by_type) = info.get(&Type::Unknown) {
+229            map! { @extend values
+230                "icon_unknown" => Value::icon("unknown"),
+231                "info_unknown" => Value::text(info_by_type.to_string())
+232            }
+233        }
+234
+235        widget.set_values(values);
+236
+237        api.set_widget(widget)?;
+238
+239        select! {
+240            _ = api.wait_for_update_request() => (),
+241            _ = select_all(drivers.iter_mut().map(|driver| driver.wait_for_change())) =>(),
+242            Some(action) = actions.recv() => match action.as_ref() {
+243                "toggle_format" => {
+244                    std::mem::swap(&mut format_alt, &mut format);
+245                }
+246                _ => (),
+247            }
+248        }
+249    }
+250}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/privacy/pipewire.rs.html b/src/i3status_rs/blocks/privacy/pipewire.rs.html new file mode 100644 index 0000000000..8075ce1ad9 --- /dev/null +++ b/src/i3status_rs/blocks/privacy/pipewire.rs.html @@ -0,0 +1,261 @@ +pipewire.rs - source

i3status_rs/blocks/privacy/
pipewire.rs

1use std::cell::Cell;
+2use std::collections::HashMap;
+3use std::rc::Rc;
+4use std::sync::{Arc, Mutex, Weak};
+5use std::thread;
+6
+7use ::pipewire::{
+8    context::Context, keys, main_loop::MainLoop, properties::properties, spa::utils::dict::DictRef,
+9    types::ObjectType,
+10};
+11use itertools::Itertools as _;
+12use tokio::sync::Notify;
+13
+14use super::*;
+15
+16static CLIENT: LazyLock<Result<Client>> = LazyLock::new(Client::new);
+17
+18#[derive(Debug)]
+19struct Node {
+20    name: String,
+21    nick: Option<String>,
+22    media_class: Option<String>,
+23    media_role: Option<String>,
+24    description: Option<String>,
+25}
+26
+27impl Node {
+28    fn new(global_id: u32, global_props: &DictRef) -> Self {
+29        Self {
+30            name: global_props
+31                .get(&keys::NODE_NAME)
+32                .map_or_else(|| format!("node_{global_id}"), |s| s.to_string()),
+33            nick: global_props.get(&keys::NODE_NICK).map(|s| s.to_string()),
+34            media_class: global_props.get(&keys::MEDIA_CLASS).map(|s| s.to_string()),
+35            media_role: global_props.get(&keys::MEDIA_ROLE).map(|s| s.to_string()),
+36            description: global_props
+37                .get(&keys::NODE_DESCRIPTION)
+38                .map(|s| s.to_string()),
+39        }
+40    }
+41}
+42
+43#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)]
+44struct Link {
+45    link_output_node: u32,
+46    link_input_node: u32,
+47}
+48
+49impl Link {
+50    fn new(global_props: &DictRef) -> Option<Self> {
+51        if let Some(link_output_node) = global_props
+52            .get(&keys::LINK_OUTPUT_NODE)
+53            .and_then(|s| s.parse().ok())
+54            && let Some(link_input_node) = global_props
+55                .get(&keys::LINK_INPUT_NODE)
+56                .and_then(|s| s.parse().ok())
+57        {
+58            Some(Self {
+59                link_output_node,
+60                link_input_node,
+61            })
+62        } else {
+63            None
+64        }
+65    }
+66}
+67
+68#[derive(Default)]
+69struct Data {
+70    nodes: HashMap<u32, Node>,
+71    links: HashMap<u32, Link>,
+72}
+73
+74#[derive(Default)]
+75struct Client {
+76    event_listeners: Mutex<Vec<Weak<Notify>>>,
+77    data: Mutex<Data>,
+78}
+79
+80impl Client {
+81    fn new() -> Result<Client> {
+82        thread::Builder::new()
+83            .name("privacy_pipewire".to_string())
+84            .spawn(Client::main_loop_thread)
+85            .error("failed to spawn a thread")?;
+86
+87        Ok(Client::default())
+88    }
+89
+90    fn main_loop_thread() {
+91        let client = CLIENT.as_ref().error("Could not get client").unwrap();
+92
+93        let proplist = properties! {*keys::APP_NAME => env!("CARGO_PKG_NAME")};
+94
+95        let main_loop = MainLoop::new(None).expect("Failed to create main loop");
+96
+97        let context =
+98            Context::with_properties(&main_loop, proplist).expect("Failed to create context");
+99        let core = context.connect(None).expect("Failed to connect");
+100        let registry = core.get_registry().expect("Failed to get registry");
+101
+102        let updated = Rc::new(Cell::new(false));
+103        let updated_copy = updated.clone();
+104        let updated_copy2 = updated.clone();
+105
+106        // Register a callback to the `global` event on the registry, which notifies of any new global objects
+107        // appearing on the remote.
+108        // The callback will only get called as long as we keep the returned listener alive.
+109        let _registry_listener = registry
+110            .add_listener_local()
+111            .global(move |global| {
+112                let Some(global_props) = global.props else {
+113                    return;
+114                };
+115                match &global.type_ {
+116                    ObjectType::Node => {
+117                        client
+118                            .data
+119                            .lock()
+120                            .unwrap()
+121                            .nodes
+122                            .insert(global.id, Node::new(global.id, global_props));
+123                        updated_copy.set(true);
+124                    }
+125                    ObjectType::Link => {
+126                        let Some(link) = Link::new(global_props) else {
+127                            return;
+128                        };
+129                        client.data.lock().unwrap().links.insert(global.id, link);
+130                        updated_copy.set(true);
+131                    }
+132                    _ => (),
+133                }
+134            })
+135            .global_remove(move |uid| {
+136                let mut data = client.data.lock().unwrap();
+137                if data.nodes.remove(&uid).is_some() || data.links.remove(&uid).is_some() {
+138                    updated_copy2.set(true);
+139                }
+140            })
+141            .register();
+142
+143        loop {
+144            main_loop.loop_().iterate(Duration::from_secs(60 * 60 * 24));
+145            if updated.get() {
+146                updated.set(false);
+147                client
+148                    .event_listeners
+149                    .lock()
+150                    .unwrap()
+151                    .retain(|notify| notify.upgrade().inspect(|x| x.notify_one()).is_some());
+152            }
+153        }
+154    }
+155
+156    fn add_event_listener(&self, notify: &Arc<Notify>) {
+157        self.event_listeners
+158            .lock()
+159            .unwrap()
+160            .push(Arc::downgrade(notify));
+161    }
+162}
+163
+164#[derive(Deserialize, Debug, SmartDefault)]
+165#[serde(rename_all = "lowercase", deny_unknown_fields, default)]
+166pub struct Config {
+167    exclude_output: Vec<String>,
+168    exclude_input: Vec<String>,
+169    display: NodeDisplay,
+170}
+171
+172#[derive(Deserialize, Debug, SmartDefault)]
+173#[serde(rename_all = "snake_case")]
+174enum NodeDisplay {
+175    #[default]
+176    Name,
+177    Description,
+178    Nickname,
+179}
+180
+181impl NodeDisplay {
+182    fn map_node(&self, node: &Node) -> String {
+183        match self {
+184            NodeDisplay::Name => node.name.clone(),
+185            NodeDisplay::Description => node.description.clone().unwrap_or(node.name.clone()),
+186            NodeDisplay::Nickname => node.nick.clone().unwrap_or(node.name.clone()),
+187        }
+188    }
+189}
+190
+191pub(super) struct Monitor<'a> {
+192    config: &'a Config,
+193    notify: Arc<Notify>,
+194}
+195
+196impl<'a> Monitor<'a> {
+197    pub(super) async fn new(config: &'a Config) -> Result<Self> {
+198        let client = CLIENT.as_ref().error("Could not get client")?;
+199        let notify = Arc::new(Notify::new());
+200        client.add_event_listener(&notify);
+201        Ok(Self { config, notify })
+202    }
+203}
+204
+205#[async_trait]
+206impl PrivacyMonitor for Monitor<'_> {
+207    async fn get_info(&mut self) -> Result<PrivacyInfo> {
+208        let client = CLIENT.as_ref().error("Could not get client")?;
+209        let data = client.data.lock().unwrap();
+210        let mut mapping: PrivacyInfo = PrivacyInfo::new();
+211
+212        for node in data.nodes.values() {
+213            debug! {"{:?}", node};
+214        }
+215
+216        // The links must be sorted and then dedup'ed since you can multiple links between any given pair of nodes
+217        for Link {
+218            link_output_node,
+219            link_input_node,
+220            ..
+221        } in data.links.values().sorted().dedup()
+222        {
+223            if let Some(output_node) = data.nodes.get(link_output_node)
+224                && let Some(input_node) = data.nodes.get(link_input_node)
+225                && input_node.media_class != Some("Audio/Sink".into())
+226                && !self.config.exclude_output.contains(&output_node.name)
+227                && !self.config.exclude_input.contains(&input_node.name)
+228            {
+229                let type_ = if input_node.media_class == Some("Stream/Input/Video".into()) {
+230                    if output_node.media_role == Some("Camera".into()) {
+231                        Type::Webcam
+232                    } else {
+233                        Type::Video
+234                    }
+235                } else if input_node.media_class == Some("Stream/Input/Audio".into()) {
+236                    if output_node.media_class == Some("Audio/Sink".into()) {
+237                        Type::AudioSink
+238                    } else {
+239                        Type::Audio
+240                    }
+241                } else {
+242                    Type::Unknown
+243                };
+244                *mapping
+245                    .entry(type_)
+246                    .or_default()
+247                    .entry(self.config.display.map_node(output_node))
+248                    .or_default()
+249                    .entry(self.config.display.map_node(input_node))
+250                    .or_default() += 1;
+251            }
+252        }
+253
+254        Ok(mapping)
+255    }
+256
+257    async fn wait_for_change(&mut self) -> Result<()> {
+258        self.notify.notified().await;
+259        Ok(())
+260    }
+261}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/privacy/v4l.rs.html b/src/i3status_rs/blocks/privacy/v4l.rs.html new file mode 100644 index 0000000000..3cd6fa0e00 --- /dev/null +++ b/src/i3status_rs/blocks/privacy/v4l.rs.html @@ -0,0 +1,149 @@ +v4l.rs - source

i3status_rs/blocks/privacy/
v4l.rs

1use inotify::{EventStream, Inotify, WatchDescriptor, WatchMask, Watches};
+2use tokio::fs::{File, read_dir};
+3use tokio::time::{Interval, interval};
+4
+5use std::path::PathBuf;
+6
+7use super::*;
+8
+9#[derive(Deserialize, Debug, SmartDefault)]
+10#[serde(rename_all = "lowercase", deny_unknown_fields, default)]
+11pub struct Config {
+12    exclude_device: Vec<PathBuf>,
+13    #[default(vec!["pipewire".into(), "wireplumber".into()])]
+14    exclude_consumer: Vec<String>,
+15}
+16
+17pub(super) struct Monitor<'a> {
+18    config: &'a Config,
+19    devices: HashMap<PathBuf, WatchDescriptor>,
+20    interval: Interval,
+21    watches: Watches,
+22    stream: EventStream<[u8; 1024]>,
+23}
+24
+25impl<'a> Monitor<'a> {
+26    pub(super) async fn new(config: &'a Config, duration: Duration) -> Result<Self> {
+27        let notify = Inotify::init().error("Failed to start inotify")?;
+28        let watches = notify.watches();
+29
+30        let stream = notify
+31            .into_event_stream([0; 1024])
+32            .error("Failed to create event stream")?;
+33
+34        let mut s = Self {
+35            config,
+36            devices: HashMap::new(),
+37            interval: interval(duration),
+38            watches,
+39            stream,
+40        };
+41        s.update_devices().await?;
+42
+43        Ok(s)
+44    }
+45
+46    async fn update_devices(&mut self) -> Result<bool> {
+47        let mut changes = false;
+48        let mut devices_to_remove: HashMap<PathBuf, WatchDescriptor> = self.devices.clone();
+49        let mut sysfs_paths = read_dir("/dev").await.error("Unable to read /dev")?;
+50        while let Some(entry) = sysfs_paths
+51            .next_entry()
+52            .await
+53            .error("Unable to get next device in /dev")?
+54        {
+55            if let Some(file_name) = entry.file_name().to_str()
+56                && !file_name.starts_with("video")
+57            {
+58                continue;
+59            }
+60
+61            let sysfs_path = entry.path();
+62
+63            if self.config.exclude_device.contains(&sysfs_path) {
+64                debug!("ignoring {:?}", sysfs_path);
+65                continue;
+66            }
+67
+68            if self.devices.contains_key(&sysfs_path) {
+69                devices_to_remove.remove(&sysfs_path);
+70            } else {
+71                debug!("adding watch {:?}", sysfs_path);
+72                self.devices.insert(
+73                    sysfs_path.clone(),
+74                    self.watches
+75                        .add(&sysfs_path, WatchMask::OPEN | WatchMask::CLOSE)
+76                        .error("Failed to watch data location")?,
+77                );
+78                changes = true;
+79            }
+80        }
+81        for (sysfs_path, wd) in devices_to_remove {
+82            debug!("removing watch {:?}", sysfs_path);
+83            self.devices.remove(&sysfs_path);
+84            self.watches
+85                .remove(wd)
+86                .error("Failed to unwatch data location")?;
+87            changes = true;
+88        }
+89
+90        Ok(changes)
+91    }
+92}
+93
+94#[async_trait]
+95impl PrivacyMonitor for Monitor<'_> {
+96    async fn get_info(&mut self) -> Result<PrivacyInfo> {
+97        let mut mapping: PrivacyInfo = PrivacyInfo::new();
+98
+99        let mut proc_paths = read_dir("/proc").await.error("Unable to read /proc")?;
+100        while let Some(proc_path) = proc_paths
+101            .next_entry()
+102            .await
+103            .error("Unable to get next device in /proc")?
+104        {
+105            let proc_path = proc_path.path();
+106            let fd_path = proc_path.join("fd");
+107            let Ok(mut fd_paths) = read_dir(fd_path).await else {
+108                continue;
+109            };
+110            while let Ok(Some(fd_path)) = fd_paths.next_entry().await {
+111                let mut contents = String::new();
+112                if let Ok(link_path) = fd_path.path().read_link()
+113                    && self.devices.contains_key(&link_path)
+114                    && let Ok(mut file) = File::open(proc_path.join("comm")).await
+115                    && file.read_to_string(&mut contents).await.is_ok()
+116                {
+117                    let reader = contents.trim_end().to_string();
+118                    if self.config.exclude_consumer.contains(&reader) {
+119                        continue;
+120                    }
+121                    debug!("{} {:?}", reader, link_path);
+122                    *mapping
+123                        .entry(Type::Webcam)
+124                        .or_default()
+125                        .entry(link_path.to_string_lossy().to_string())
+126                        .or_default()
+127                        .entry(reader)
+128                        .or_default() += 1;
+129                    debug!("{:?}", mapping);
+130                }
+131            }
+132        }
+133        Ok(mapping)
+134    }
+135
+136    async fn wait_for_change(&mut self) -> Result<()> {
+137        loop {
+138            select! {
+139                _ = self.interval.tick() => {
+140                    if self.update_devices().await? {
+141                        break;
+142                    }
+143                },
+144                _ = self.stream.next_debounced() => break
+145            }
+146        }
+147        Ok(())
+148    }
+149}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/rofication.rs.html b/src/i3status_rs/blocks/rofication.rs.html new file mode 100644 index 0000000000..b899f5dc73 --- /dev/null +++ b/src/i3status_rs/blocks/rofication.rs.html @@ -0,0 +1,104 @@ +rofication.rs - source

i3status_rs/blocks/
rofication.rs

1//! The number of pending notifications in rofication-daemon
+2//!
+3//! A different color is used if there are critical notifications.
+4//!
+5//! # Configuration
+6//!
+7//! Key | Values | Default
+8//! ----|--------|--------
+9//! `interval` | Refresh rate in seconds. | `1`
+10//! `format` | A string to customise the output of this block. See below for placeholders. | `" $icon $num.eng(w:1) "`
+11//! `socket_path` | Socket path for the rofication daemon. Supports path expansions e.g. `~`. | `"/tmp/rofi_notification_daemon"`
+12//!
+13//!  Placeholder | Value | Type | Unit
+14//! -------------|-------|------|-----
+15//! `icon`       | A static icon  | Icon | -
+16//! `num`        | Number of pending notifications | Number | -
+17//!
+18//! # Example
+19//!
+20//! ```toml
+21//! [[block]]
+22//! block = "rofication"
+23//! interval = 1
+24//! socket_path = "/tmp/rofi_notification_daemon"
+25//! [[block.click]]
+26//! button = "left"
+27//! cmd = "rofication-gui"
+28//! ```
+29//!
+30//! # Icons Used
+31//! - `bell`
+32
+33use super::prelude::*;
+34use tokio::net::UnixStream;
+35
+36#[derive(Deserialize, Debug, SmartDefault)]
+37#[serde(deny_unknown_fields, default)]
+38pub struct Config {
+39    #[default(1.into())]
+40    pub interval: Seconds,
+41    #[default("/tmp/rofi_notification_daemon".into())]
+42    pub socket_path: ShellString,
+43    pub format: FormatConfig,
+44}
+45
+46pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+47    let format = config.format.with_default(" $icon $num.eng(w:1) ")?;
+48
+49    let path = config.socket_path.expand()?;
+50    let mut timer = config.interval.timer();
+51
+52    loop {
+53        let (num, crit) = rofication_status(&path).await?;
+54
+55        let mut widget = Widget::new().with_format(format.clone());
+56
+57        widget.set_values(map!(
+58            "icon" => Value::icon("bell"),
+59            "num" => Value::number(num)
+60        ));
+61
+62        widget.state = if crit > 0 {
+63            State::Warning
+64        } else if num > 0 {
+65            State::Info
+66        } else {
+67            State::Idle
+68        };
+69
+70        api.set_widget(widget)?;
+71
+72        tokio::select! {
+73            _ = timer.tick() => (),
+74            _ = api.wait_for_update_request() => (),
+75        }
+76    }
+77}
+78
+79async fn rofication_status(socket_path: &str) -> Result<(usize, usize)> {
+80    let mut stream = UnixStream::connect(socket_path)
+81        .await
+82        .error("Failed to connect to socket")?;
+83
+84    // Request count
+85    stream
+86        .write_all(b"num:\n")
+87        .await
+88        .error("Failed to write to socket")?;
+89
+90    let mut response = String::new();
+91    stream
+92        .read_to_string(&mut response)
+93        .await
+94        .error("Failed to read from socket")?;
+95
+96    // Response must be two integers: regular and critical, separated either by a comma or a \n
+97    let (num, crit) = response
+98        .split_once([',', '\n'])
+99        .error("Incorrect response")?;
+100    Ok((
+101        num.parse().error("Incorrect response")?,
+102        crit.parse().error("Incorrect response")?,
+103    ))
+104}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/scratchpad.rs.html b/src/i3status_rs/blocks/scratchpad.rs.html new file mode 100644 index 0000000000..5493e65b3a --- /dev/null +++ b/src/i3status_rs/blocks/scratchpad.rs.html @@ -0,0 +1,86 @@ +scratchpad.rs - source

i3status_rs/blocks/
scratchpad.rs

1//! Scratchpad indicator
+2//!
+3//! # Configuration
+4//!
+5//! Key | Values | Default
+6//! ----|--------|--------
+7//! `format`          | A string to customise the output of this block | ` $icon $count.eng(range:1..) |`
+8//!
+9//! Placeholder | Value                                      | Type   | Unit
+10//! ------------|--------------------------------------------|--------|-----
+11//! `icon`      | A static icon                              | Icon   | -
+12//! `count`     | Number of windows in scratchpad            | Number | -
+13//!
+14//! # Example
+15//!
+16//! ```toml
+17//! [[block]]
+18//! block = "scratchpad"
+19//! ```
+20//!
+21//! # Icons Used
+22//! - `scratchpad`
+23
+24use swayipc_async::{Connection, Event as SwayEvent, EventType, Node, WindowChange};
+25
+26use super::prelude::*;
+27
+28#[derive(Deserialize, Debug, SmartDefault)]
+29#[serde(deny_unknown_fields, default)]
+30pub struct Config {
+31    pub format: FormatConfig,
+32}
+33
+34fn count_scratchpad_windows(node: &Node) -> usize {
+35    node.find_as_ref(|n| n.name.as_deref() == Some("__i3_scratch"))
+36        .map_or(0, |node| node.floating_nodes.len())
+37}
+38
+39pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+40    let format = config
+41        .format
+42        .with_default(" $icon $count.eng(range:1..) |")?;
+43
+44    let connection_for_events = Connection::new()
+45        .await
+46        .error("failed to open connection with swayipc")?;
+47
+48    let mut connection_for_tree = Connection::new()
+49        .await
+50        .error("failed to open connection with swayipc")?;
+51
+52    let mut events = connection_for_events
+53        .subscribe(&[EventType::Window])
+54        .await
+55        .error("could not subscribe to window events")?;
+56
+57    loop {
+58        let mut widget = Widget::new().with_format(format.clone());
+59
+60        let root_node = connection_for_tree
+61            .get_tree()
+62            .await
+63            .error("could not get windows tree")?;
+64        let count = count_scratchpad_windows(&root_node);
+65
+66        widget.set_values(map! {
+67            "icon" => Value::icon("scratchpad"),
+68            "count" => Value::number(count),
+69        });
+70
+71        api.set_widget(widget)?;
+72
+73        loop {
+74            let event = events
+75                .next()
+76                .await
+77                .error("swayipc channel closed")?
+78                .error("bad event")?;
+79
+80            match event {
+81                SwayEvent::Window(e) if e.change == WindowChange::Move => break,
+82                _ => continue,
+83            }
+84        }
+85    }
+86}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/service_status.rs.html b/src/i3status_rs/blocks/service_status.rs.html new file mode 100644 index 0000000000..305015c5c7 --- /dev/null +++ b/src/i3status_rs/blocks/service_status.rs.html @@ -0,0 +1,212 @@ +service_status.rs - source

i3status_rs/blocks/
service_status.rs

1//! Display the status of a service
+2//!
+3//! Right now only `systemd` is supported.
+4//!
+5//! # Configuration
+6//!
+7//! Key | Values | Default
+8//! ----|--------|--------
+9//! `driver` | Which init system is running the service. Available drivers are: `"systemd"` | `"systemd"`
+10//! `service` | The name of the service | **Required**
+11//! `user` | If true, monitor the status of a user service instead of a system service. | `false`
+12//! `active_format` | A string to customise the output of this block. See below for available placeholders. | `" $service active "`
+13//! `inactive_format` | A string to customise the output of this block. See below for available placeholders. | `" $service inactive "`
+14//! `active_state` | A valid [`State`] | [`State::Idle`]
+15//! `inactive_state` | A valid [`State`]  | [`State::Critical`]
+16//!
+17//! Placeholder    | Value                     | Type   | Unit
+18//! ---------------|---------------------------|--------|-----
+19//! `service`      | The name of the service   | Text   | -
+20//!
+21//! # Example
+22//!
+23//! Example using an icon:
+24//!
+25//! ```toml
+26//! [[block]]
+27//! block = "service_status"
+28//! service = "cups"
+29//! active_format = " ^icon_tea "
+30//! inactive_format = " no ^icon_tea "
+31//! ```
+32//!
+33//! Example overriding the default `inactive_state`:
+34//!
+35//! ```toml
+36//! [[block]]
+37//! block = "service_status"
+38//! service = "shadow"
+39//! active_format = ""
+40//! inactive_format = " Integrity of password and group files failed "
+41//! inactive_state = "Warning"
+42//! ```
+43//!
+44
+45use super::prelude::*;
+46use zbus::proxy::PropertyStream;
+47
+48#[derive(Deserialize, Debug, Default)]
+49#[serde(deny_unknown_fields, default)]
+50pub struct Config {
+51    pub driver: DriverType,
+52    pub service: String,
+53    pub user: bool,
+54    pub active_format: FormatConfig,
+55    pub inactive_format: FormatConfig,
+56    pub active_state: Option<State>,
+57    pub inactive_state: Option<State>,
+58}
+59
+60#[derive(Deserialize, Debug, SmartDefault)]
+61#[serde(rename_all = "snake_case")]
+62pub enum DriverType {
+63    #[default]
+64    Systemd,
+65}
+66
+67pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+68    let active_format = config.active_format.with_default(" $service active ")?;
+69    let inactive_format = config.inactive_format.with_default(" $service inactive ")?;
+70
+71    let active_state = config.active_state.unwrap_or(State::Idle);
+72    let inactive_state = config.inactive_state.unwrap_or(State::Critical);
+73
+74    let mut driver: Box<dyn Driver> = match config.driver {
+75        DriverType::Systemd => {
+76            Box::new(SystemdDriver::new(config.user, config.service.clone()).await?)
+77        }
+78    };
+79
+80    loop {
+81        let service_active_state = driver.is_active().await?;
+82
+83        let mut widget = Widget::new();
+84
+85        if service_active_state {
+86            widget.state = active_state;
+87            widget.set_format(active_format.clone());
+88        } else {
+89            widget.state = inactive_state;
+90            widget.set_format(inactive_format.clone());
+91        };
+92
+93        widget.set_values(map! {
+94            "service" =>Value::text(config.service.clone()),
+95        });
+96
+97        api.set_widget(widget)?;
+98
+99        driver.wait_for_change().await?;
+100    }
+101}
+102
+103#[async_trait]
+104trait Driver {
+105    async fn is_active(&self) -> Result<bool>;
+106    async fn wait_for_change(&mut self) -> Result<()>;
+107}
+108
+109struct SystemdDriver {
+110    proxy: UnitProxy<'static>,
+111    service_proxy: ServiceProxy<'static>,
+112    active_state_changed: PropertyStream<'static, String>,
+113}
+114
+115impl SystemdDriver {
+116    async fn new(user: bool, service: String) -> Result<Self> {
+117        let dbus_conn = if user {
+118            new_dbus_connection().await?
+119        } else {
+120            new_system_dbus_connection().await?
+121        };
+122
+123        if !service.is_ascii() {
+124            return Err(Error::new(format!(
+125                "service name \"{service}\" must only contain ASCII characters"
+126            )));
+127        }
+128        let encoded_service = format!("{service}.service")
+129            // For each byte...
+130            .bytes()
+131            .map(|b| {
+132                if b.is_ascii_alphanumeric() {
+133                    // Just use the character as a string
+134                    char::from(b).to_string()
+135                } else {
+136                    // Otherwise use the hex representation of the byte preceded by an underscore
+137                    format!("_{b:02x}")
+138                }
+139            })
+140            .collect::<String>();
+141
+142        let path = format!("/org/freedesktop/systemd1/unit/{encoded_service}");
+143
+144        let proxy = UnitProxy::builder(&dbus_conn)
+145            .path(path.clone())
+146            .error("Could not set path")?
+147            .build()
+148            .await
+149            .error("Failed to create UnitProxy")?;
+150
+151        let service_proxy = ServiceProxy::builder(&dbus_conn)
+152            .path(path)
+153            .error("Could not set path")?
+154            .build()
+155            .await
+156            .error("Failed to create ServiceProxy")?;
+157
+158        Ok(Self {
+159            active_state_changed: proxy.receive_active_state_changed().await,
+160            proxy,
+161            service_proxy,
+162        })
+163    }
+164}
+165
+166#[async_trait]
+167impl Driver for SystemdDriver {
+168    async fn is_active(&self) -> Result<bool> {
+169        let active_state = self
+170            .proxy
+171            .active_state()
+172            .await
+173            .error("Could not get active_state")?;
+174
+175        Ok(match &*active_state {
+176            "active" => true,
+177            "activating" => {
+178                let service_type = self
+179                    .service_proxy
+180                    .type_()
+181                    .await
+182                    .error("Could not get service type")?;
+183
+184                service_type == "oneshot"
+185            }
+186            _ => false,
+187        })
+188    }
+189
+190    async fn wait_for_change(&mut self) -> Result<()> {
+191        self.active_state_changed.next().await;
+192        Ok(())
+193    }
+194}
+195
+196#[zbus::proxy(
+197    interface = "org.freedesktop.systemd1.Unit",
+198    default_service = "org.freedesktop.systemd1"
+199)]
+200trait Unit {
+201    #[zbus(property)]
+202    fn active_state(&self) -> zbus::Result<String>;
+203}
+204
+205#[zbus::proxy(
+206    interface = "org.freedesktop.systemd1.Service",
+207    default_service = "org.freedesktop.systemd1"
+208)]
+209trait Service {
+210    #[zbus(property, name = "Type")]
+211    fn type_(&self) -> zbus::Result<String>;
+212}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/sound.rs.html b/src/i3status_rs/blocks/sound.rs.html new file mode 100644 index 0000000000..ead836cfa4 --- /dev/null +++ b/src/i3status_rs/blocks/sound.rs.html @@ -0,0 +1,357 @@ +sound.rs - source

i3status_rs/blocks/
sound.rs

1//! Volume level
+2//!
+3//! This block displays the volume level (according to PulseAudio or ALSA). Right click to toggle mute, scroll to adjust volume.
+4//!
+5//! Requires a PulseAudio installation or `alsa-utils` for ALSA.
+6//!
+7//! Note that if you are using PulseAudio commands (such as `pactl`) to control your volume, you should select the `"pulseaudio"` (or `"auto"`) driver to see volume changes that exceed 100%.
+8//!
+9//! # Configuration
+10//!
+11//! Key | Values | Default
+12//! ----|--------|--------
+13//! `driver` | `"auto"`, `"pulseaudio"`, `"alsa"`. | `"auto"` (Pulseaudio with ALSA fallback)
+14//! `format` | A string to customise the output of this block. See below for available placeholders. | <code>\" $icon {$volume.eng(w:2) \|}\"</code>
+15//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click. | `None`
+16//! `name` | PulseAudio device name, or the ALSA control name as found in the output of `amixer -D yourdevice scontrols`. | PulseAudio: `@DEFAULT_SINK@` / ALSA: `Master`
+17//! `device` | ALSA device name, usually in the form "hw:X" or "hw:X,Y" where `X` is the card number and `Y` is the device number as found in the output of `aplay -l`. | `default`
+18//! `device_kind` | PulseAudio device kind: `source` or `sink`. | `"sink"`
+19//! `natural_mapping` | When using the ALSA driver, display the "mapped volume" as given by `alsamixer`/`amixer -M`, which represents the volume level more naturally with respect for the human ear. | `false`
+20//! `step_width` | The percent volume level is increased/decreased for the selected audio device when scrolling. Capped automatically at 50. | `5`
+21//! `max_vol` | Max volume in percent that can be set via scrolling. Note it can still be set above this value if changed by another application. | `None`
+22//! `show_volume_when_muted` | Show the volume even if it is currently muted. | `false`
+23//! `headphones_indicator` | Change icon when headphones are plugged in (pulseaudio only) | `false`
+24//! `mappings` | Map `output_name` to a custom name. | `None`
+25//! `mappings_use_regex` | Let `mappings` match using regex instead of string equality. The replacement will be regex aware and can contain capture groups. | `true`
+26//! `active_port_mappings` | Map `active_port` to a custom name. The replacement will be regex aware and can contain capture groups. | `None`
+27//!
+28//! Placeholder          | Value                             | Type   | Unit
+29//! ---------------------|-----------------------------------|--------|---------------
+30//! `icon`               | Icon based on volume              | Icon   | -
+31//! `volume`             | Current volume. Missing if muted. | Number | %
+32//! `output_name`        | PulseAudio or ALSA device name    | Text   | -
+33//! `output_description` | PulseAudio device description, will fallback to `output_name` if no description is available and will be overwritten by mappings (mappings will still use `output_name`) | Text | -
+34//! `active_port`        | Active port (same as information in Ports section of `pactl list cards`). Will be absent if not supported by `driver` or if mapped to `""` in `active_port_mappings`. | Text | -
+35//!
+36//! Action          | Default button
+37//! ----------------|---------------
+38//! `toggle_format` | Left
+39//! `toggle_mute`   | Right
+40//! `volume_down`   | Wheel Down
+41//! `volume_up`     | Wheel Up
+42//!
+43//! # Examples
+44//!
+45//! Change the default scrolling step width to 3 percent:
+46//!
+47//! ```toml
+48//! [[block]]
+49//! block = "sound"
+50//! step_width = 3
+51//! ```
+52//!
+53//! Change the output name shown:
+54//!
+55//! ```toml
+56//! [[block]]
+57//! block = "sound"
+58//! format = " $icon $output_name{ $volume|} "
+59//! [block.mappings]
+60//! "alsa_output.usb-Harman_Multimedia_JBL_Pebbles_1.0.0-00.analog-stereo" = "Speakers"
+61//! "alsa_output.pci-0000_00_1b.0.analog-stereo" = "Headset"
+62//! ```
+63//!
+64//! Since the default value for the `device_kind` key is `sink`,
+65//! to display ***microphone*** block you have to use the `source` value:
+66//!
+67//! ```toml
+68//! [[block]]
+69//! block = "sound"
+70//! driver = "pulseaudio"
+71//! device_kind = "source"
+72//! ```
+73//!
+74//! Display warning in block if microphone if using the wrong port:
+75//!
+76//! ```toml
+77//! [[block]]
+78//! block = "sound"
+79//! driver = "pulseaudio"
+80//! device_kind = "source"
+81//! format = " $icon { $volume|} {$active_port |}"
+82//! [block.active_port_mappings]
+83//! "analog-input-rear-mic" = "" # Mapping to an empty string makes `$active_port` absent
+84//! "analog-input-front-mic" = "ERR!"
+85//! ```
+86//!
+87//! #  Icons Used
+88//!
+89//! - `microphone_muted` (as a progression)
+90//! - `microphone` (as a progression)
+91//! - `volume_muted` (as a progression)
+92//! - `volume` (as a progression)
+93//! - `headphones`
+94
+95mod alsa;
+96#[cfg(feature = "pulseaudio")]
+97mod pulseaudio;
+98
+99use super::prelude::*;
+100use crate::wrappers::SerdeRegex;
+101use indexmap::IndexMap;
+102use regex::Regex;
+103
+104make_log_macro!(debug, "sound");
+105
+106#[derive(Deserialize, Debug, SmartDefault)]
+107#[serde(deny_unknown_fields, default)]
+108pub struct Config {
+109    pub driver: SoundDriver,
+110    pub name: Option<String>,
+111    pub device: Option<String>,
+112    pub device_kind: DeviceKind,
+113    pub natural_mapping: bool,
+114    #[default(5)]
+115    pub step_width: u32,
+116    pub format: FormatConfig,
+117    pub format_alt: Option<FormatConfig>,
+118    pub headphones_indicator: bool,
+119    pub show_volume_when_muted: bool,
+120    pub mappings: Option<IndexMap<String, String>>,
+121    #[default(true)]
+122    pub mappings_use_regex: bool,
+123    pub max_vol: Option<u32>,
+124    pub active_port_mappings: IndexMap<SerdeRegex, String>,
+125}
+126
+127enum Mappings<'a> {
+128    Exact(&'a IndexMap<String, String>),
+129    Regex(Vec<(Regex, &'a str)>),
+130}
+131
+132pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+133    let mut actions = api.get_actions()?;
+134    api.set_default_actions(&[
+135        (MouseButton::Left, None, "toggle_format"),
+136        (MouseButton::Right, None, "toggle_mute"),
+137        (MouseButton::WheelUp, None, "volume_up"),
+138        (MouseButton::WheelDown, None, "volume_down"),
+139    ])?;
+140
+141    let mut format = config.format.with_default(" $icon {$volume.eng(w:2)|} ")?;
+142    let mut format_alt = match &config.format_alt {
+143        Some(f) => Some(f.with_default("")?),
+144        None => None,
+145    };
+146
+147    let device_kind = config.device_kind;
+148    let step_width = config.step_width.clamp(0, 50) as i32;
+149
+150    let icon = |muted: bool, device: &dyn SoundDevice| -> &'static str {
+151        if config.headphones_indicator && device_kind == DeviceKind::Sink {
+152            let form_factor = device.form_factor();
+153            let active_port = device.active_port();
+154            debug!("form_factor = {form_factor:?} active_port = {active_port:?}");
+155            let headphones = match form_factor {
+156                // form_factor's possible values are listed at:
+157                // https://docs.rs/libpulse-binding/2.25.0/libpulse_binding/proplist/properties/constant.DEVICE_FORM_FACTOR.html
+158                Some("headset") | Some("headphone") | Some("hands-free") | Some("portable") => true,
+159                // Per discussion at
+160                // https://github.com/greshake/i3status-rust/pull/1363#issuecomment-1046095869,
+161                // some sinks may not have the form_factor property, so we should fall back to the
+162                // active_port if that property is not present.
+163                None => active_port.is_some_and(|p| p.to_lowercase().contains("headphones")),
+164                // form_factor is present and is some non-headphone value
+165                _ => false,
+166            };
+167            if headphones {
+168                return "headphones";
+169            }
+170        }
+171        if muted {
+172            match device_kind {
+173                DeviceKind::Source => "microphone_muted",
+174                DeviceKind::Sink => "volume_muted",
+175            }
+176        } else {
+177            match device_kind {
+178                DeviceKind::Source => "microphone",
+179                DeviceKind::Sink => "volume",
+180            }
+181        }
+182    };
+183
+184    type DeviceType = Box<dyn SoundDevice>;
+185    let mut device: DeviceType = match config.driver {
+186        SoundDriver::Alsa => Box::new(alsa::Device::new(
+187            config.name.clone().unwrap_or_else(|| "Master".into()),
+188            config.device.clone().unwrap_or_else(|| "default".into()),
+189            config.natural_mapping,
+190        )?),
+191        #[cfg(feature = "pulseaudio")]
+192        SoundDriver::PulseAudio => Box::new(pulseaudio::Device::new(
+193            config.device_kind,
+194            config.name.clone(),
+195        )?),
+196        #[cfg(feature = "pulseaudio")]
+197        SoundDriver::Auto => {
+198            if let Ok(pulse) = pulseaudio::Device::new(config.device_kind, config.name.clone()) {
+199                Box::new(pulse)
+200            } else {
+201                Box::new(alsa::Device::new(
+202                    config.name.clone().unwrap_or_else(|| "Master".into()),
+203                    config.device.clone().unwrap_or_else(|| "default".into()),
+204                    config.natural_mapping,
+205                )?)
+206            }
+207        }
+208        #[cfg(not(feature = "pulseaudio"))]
+209        SoundDriver::Auto => Box::new(alsa::Device::new(
+210            config.name.clone().unwrap_or_else(|| "Master".into()),
+211            config.device.clone().unwrap_or_else(|| "default".into()),
+212            config.natural_mapping,
+213        )?),
+214    };
+215
+216    let mappings = match &config.mappings {
+217        Some(m) => {
+218            if config.mappings_use_regex {
+219                Some(Mappings::Regex(
+220                    m.iter()
+221                        .map(|(key, val)| {
+222                            Ok((
+223                                Regex::new(key)
+224                                    .error("Failed to parse `{key}` in mappings as regex")?,
+225                                val.as_str(),
+226                            ))
+227                        })
+228                        .collect::<Result<_>>()?,
+229                ))
+230            } else {
+231                Some(Mappings::Exact(m))
+232            }
+233        }
+234        None => None,
+235    };
+236
+237    loop {
+238        device.get_info().await?;
+239        let volume = device.volume();
+240        let muted = device.muted();
+241        let mut output_name = device.output_name();
+242        let mut active_port = device.active_port();
+243        match &mappings {
+244            Some(Mappings::Regex(m)) => {
+245                if let Some((regex, mapped)) =
+246                    m.iter().find(|(regex, _)| regex.is_match(&output_name))
+247                {
+248                    output_name = regex.replace(&output_name, *mapped).into_owned();
+249                }
+250            }
+251            Some(Mappings::Exact(m)) => {
+252                if let Some(mapped) = m.get(&output_name) {
+253                    output_name.clone_from(mapped);
+254                }
+255            }
+256            None => (),
+257        }
+258        if let Some(ap) = &active_port
+259            && let Some((regex, mapped)) = config
+260                .active_port_mappings
+261                .iter()
+262                .find(|(regex, _)| regex.0.is_match(ap))
+263        {
+264            let mapped = regex.0.replace(ap, mapped);
+265            if mapped.is_empty() {
+266                active_port = None;
+267            } else {
+268                active_port = Some(mapped.into_owned());
+269            }
+270        }
+271
+272        let output_description = device
+273            .output_description()
+274            .unwrap_or_else(|| output_name.clone());
+275
+276        let mut values = map! {
+277            "icon" => Value::icon_progression(icon(muted, &*device), volume as f64 / 100.0),
+278            "volume" => Value::percents(volume),
+279            "output_name" => Value::text(output_name),
+280            "output_description" => Value::text(output_description),
+281            [if let Some(ap) = active_port] "active_port" => Value::text(ap),
+282        };
+283
+284        let mut widget = Widget::new().with_format(format.clone());
+285
+286        if muted {
+287            widget.state = State::Warning;
+288            if !config.show_volume_when_muted {
+289                values.remove("volume");
+290            }
+291        }
+292
+293        widget.set_values(values);
+294        api.set_widget(widget)?;
+295
+296        loop {
+297            select! {
+298                val = device.wait_for_update() => {
+299                    val?;
+300                    break;
+301                }
+302                _ = api.wait_for_update_request() => break,
+303                Some(action) = actions.recv() => match action.as_ref() {
+304                    "toggle_format" => {
+305                        if let Some(format_alt) = &mut format_alt {
+306                            std::mem::swap(format_alt, &mut format);
+307                            break;
+308                        }
+309                    }
+310                    "toggle_mute" => {
+311                        device.toggle().await?;
+312                    }
+313                    "volume_up" => {
+314                        device.set_volume(step_width, config.max_vol).await?;
+315                    }
+316                    "volume_down" => {
+317                        device.set_volume(-step_width, config.max_vol).await?;
+318                    }
+319                    _ => (),
+320                }
+321            }
+322        }
+323    }
+324}
+325
+326#[derive(Deserialize, Debug, SmartDefault, Clone, Copy)]
+327#[serde(rename_all = "lowercase")]
+328pub enum SoundDriver {
+329    #[default]
+330    Auto,
+331    Alsa,
+332    #[cfg(feature = "pulseaudio")]
+333    PulseAudio,
+334}
+335
+336#[derive(Deserialize, Debug, SmartDefault, Clone, Copy, PartialEq, Eq, Hash)]
+337#[serde(rename_all = "lowercase")]
+338pub enum DeviceKind {
+339    #[default]
+340    Sink,
+341    Source,
+342}
+343
+344#[async_trait::async_trait]
+345trait SoundDevice {
+346    fn volume(&self) -> u32;
+347    fn muted(&self) -> bool;
+348    fn output_name(&self) -> String;
+349    fn output_description(&self) -> Option<String>;
+350    fn active_port(&self) -> Option<String>;
+351    fn form_factor(&self) -> Option<&str>;
+352
+353    async fn get_info(&mut self) -> Result<()>;
+354    async fn set_volume(&mut self, step: i32, max_vol: Option<u32>) -> Result<()>;
+355    async fn toggle(&mut self) -> Result<()>;
+356    async fn wait_for_update(&mut self) -> Result<()>;
+357}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/sound/alsa.rs.html b/src/i3status_rs/blocks/sound/alsa.rs.html new file mode 100644 index 0000000000..86cff111dc --- /dev/null +++ b/src/i3status_rs/blocks/sound/alsa.rs.html @@ -0,0 +1,147 @@ +alsa.rs - source

i3status_rs/blocks/sound/
alsa.rs

1use std::cmp::{max, min};
+2use std::process::Stdio;
+3use tokio::process::{ChildStdout, Command};
+4
+5use super::super::prelude::*;
+6use super::SoundDevice;
+7
+8pub(super) struct Device {
+9    name: String,
+10    device: String,
+11    natural_mapping: bool,
+12    volume: u32,
+13    muted: bool,
+14    monitor: ChildStdout,
+15}
+16
+17impl Device {
+18    pub(super) fn new(name: String, device: String, natural_mapping: bool) -> Result<Self> {
+19        Ok(Device {
+20            name,
+21            device,
+22            natural_mapping,
+23            volume: 0,
+24            muted: false,
+25            monitor: Command::new("alsactl")
+26                .arg("monitor")
+27                .stdout(Stdio::piped())
+28                .spawn()
+29                .error("Failed to start alsactl monitor")?
+30                .stdout
+31                .error("Failed to pipe alsactl monitor output")?,
+32        })
+33    }
+34}
+35
+36#[async_trait::async_trait]
+37impl SoundDevice for Device {
+38    fn volume(&self) -> u32 {
+39        self.volume
+40    }
+41
+42    fn muted(&self) -> bool {
+43        self.muted
+44    }
+45
+46    fn output_name(&self) -> String {
+47        self.name.clone()
+48    }
+49
+50    fn output_description(&self) -> Option<String> {
+51        // TODO Does Alsa has something similar like descriptions in Pulse?
+52        None
+53    }
+54
+55    fn active_port(&self) -> Option<String> {
+56        None
+57    }
+58
+59    fn form_factor(&self) -> Option<&str> {
+60        None
+61    }
+62
+63    async fn get_info(&mut self) -> Result<()> {
+64        let mut args = Vec::new();
+65        if self.natural_mapping {
+66            args.push("-M");
+67        };
+68        args.extend(["-D", &self.device, "get", &self.name]);
+69
+70        let output: String = Command::new("amixer")
+71            .args(&args)
+72            .output()
+73            .await
+74            .map(|o| std::str::from_utf8(&o.stdout).unwrap().trim().into())
+75            .error("could not run amixer to get sound info")?;
+76
+77        let last_line = &output.lines().last().error("could not get sound info")?;
+78
+79        const FILTER: &[char] = &['[', ']', '%'];
+80        let mut last = last_line
+81            .split_whitespace()
+82            .filter(|x| x.starts_with('[') && !x.contains("dB"))
+83            .map(|s| s.trim_matches(FILTER));
+84
+85        self.volume = last
+86            .next()
+87            .error("could not get volume")?
+88            .parse::<u32>()
+89            .error("could not parse volume to u32")?;
+90
+91        self.muted = last.next().map(|muted| muted == "off").unwrap_or(false);
+92
+93        Ok(())
+94    }
+95
+96    async fn set_volume(&mut self, step: i32, max_vol: Option<u32>) -> Result<()> {
+97        let new_vol = max(0, self.volume as i32 + step) as u32;
+98        let capped_volume = if let Some(vol_cap) = max_vol {
+99            min(new_vol, vol_cap)
+100        } else {
+101            new_vol
+102        };
+103        let mut args = Vec::new();
+104        if self.natural_mapping {
+105            args.push("-M");
+106        };
+107        let vol_str = format!("{capped_volume}%");
+108        args.extend(["-D", &self.device, "set", &self.name, &vol_str]);
+109
+110        Command::new("amixer")
+111            .args(&args)
+112            .output()
+113            .await
+114            .error("failed to set volume")?;
+115
+116        self.volume = capped_volume;
+117
+118        Ok(())
+119    }
+120
+121    async fn toggle(&mut self) -> Result<()> {
+122        let mut args = Vec::new();
+123        if self.natural_mapping {
+124            args.push("-M");
+125        };
+126        args.extend(["-D", &self.device, "set", &self.name, "toggle"]);
+127
+128        Command::new("amixer")
+129            .args(&args)
+130            .output()
+131            .await
+132            .error("failed to toggle mute")?;
+133
+134        self.muted = !self.muted;
+135
+136        Ok(())
+137    }
+138
+139    async fn wait_for_update(&mut self) -> Result<()> {
+140        let mut buf = [0u8; 1024];
+141        self.monitor
+142            .read(&mut buf)
+143            .await
+144            .error("Failed to read stdbuf output")?;
+145        Ok(())
+146    }
+147}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/sound/pulseaudio.rs.html b/src/i3status_rs/blocks/sound/pulseaudio.rs.html new file mode 100644 index 0000000000..6619a45f81 --- /dev/null +++ b/src/i3status_rs/blocks/sound/pulseaudio.rs.html @@ -0,0 +1,526 @@ +pulseaudio.rs - source

i3status_rs/blocks/sound/
pulseaudio.rs

1use std::cmp::{max, min};
+2use std::convert::{TryFrom, TryInto};
+3use std::io;
+4use std::os::fd::{IntoRawFd as _, RawFd};
+5use std::sync::{Arc, Mutex, Weak};
+6use std::thread;
+7
+8use libc::c_void;
+9use libpulse_binding::callbacks::ListResult;
+10use libpulse_binding::context::{
+11    Context, FlagSet, State as PulseState, introspect::ServerInfo, introspect::SinkInfo,
+12    introspect::SourceInfo, subscribe::Facility, subscribe::InterestMaskSet,
+13};
+14use libpulse_binding::mainloop::api::MainloopApi;
+15use libpulse_binding::mainloop::standard::{IterateResult, Mainloop};
+16use libpulse_binding::proplist::{Proplist, properties};
+17use libpulse_binding::volume::{ChannelVolumes, Volume};
+18use tokio::sync::Notify;
+19
+20use super::super::prelude::*;
+21use super::{DeviceKind, SoundDevice};
+22
+23static CLIENT: LazyLock<Result<Client>> = LazyLock::new(Client::new);
+24static EVENT_LISTENER: Mutex<Vec<Weak<Notify>>> = Mutex::new(Vec::new());
+25static DEVICES: LazyLock<Mutex<HashMap<(DeviceKind, String), VolInfo>>> = LazyLock::new(default);
+26
+27// Default device names
+28pub(super) static DEFAULT_SOURCE: Mutex<Cow<'static, str>> =
+29    Mutex::new(Cow::Borrowed("@DEFAULT_SOURCE@"));
+30pub(super) static DEFAULT_SINK: Mutex<Cow<'static, str>> =
+31    Mutex::new(Cow::Borrowed("@DEFAULT_SINK@"));
+32
+33impl DeviceKind {
+34    pub fn default_name(self) -> Cow<'static, str> {
+35        match self {
+36            Self::Sink => DEFAULT_SINK.lock().unwrap().clone(),
+37            Self::Source => DEFAULT_SOURCE.lock().unwrap().clone(),
+38        }
+39    }
+40}
+41
+42pub(super) struct Device {
+43    name: Option<String>,
+44    description: Option<String>,
+45    active_port: Option<String>,
+46    form_factor: Option<String>,
+47    device_kind: DeviceKind,
+48    volume: Option<ChannelVolumes>,
+49    volume_avg: u32,
+50    muted: bool,
+51    notify: Arc<Notify>,
+52}
+53
+54struct Connection {
+55    mainloop: Mainloop,
+56    context: Context,
+57}
+58
+59struct Client {
+60    send_req: std::sync::mpsc::Sender<ClientRequest>,
+61    ml_waker: MainloopWaker,
+62}
+63
+64#[derive(Debug)]
+65struct VolInfo {
+66    volume: ChannelVolumes,
+67    mute: bool,
+68    name: String,
+69    description: Option<String>,
+70    active_port: Option<String>,
+71    form_factor: Option<String>,
+72}
+73
+74impl TryFrom<&SourceInfo<'_>> for VolInfo {
+75    type Error = ();
+76
+77    fn try_from(source_info: &SourceInfo) -> std::result::Result<Self, Self::Error> {
+78        match source_info.name.as_ref() {
+79            None => Err(()),
+80            Some(name) => Ok(VolInfo {
+81                volume: source_info.volume,
+82                mute: source_info.mute,
+83                name: name.to_string(),
+84                description: source_info.description.as_ref().map(|d| d.to_string()),
+85                active_port: source_info
+86                    .active_port
+87                    .as_ref()
+88                    .and_then(|a| a.name.as_ref().map(|n| n.to_string())),
+89                form_factor: source_info.proplist.get_str(properties::DEVICE_FORM_FACTOR),
+90            }),
+91        }
+92    }
+93}
+94
+95impl TryFrom<&SinkInfo<'_>> for VolInfo {
+96    type Error = ();
+97
+98    fn try_from(sink_info: &SinkInfo) -> std::result::Result<Self, Self::Error> {
+99        match sink_info.name.as_ref() {
+100            None => Err(()),
+101            Some(name) => Ok(VolInfo {
+102                volume: sink_info.volume,
+103                mute: sink_info.mute,
+104                name: name.to_string(),
+105                description: sink_info.description.as_ref().map(|d| d.to_string()),
+106                active_port: sink_info
+107                    .active_port
+108                    .as_ref()
+109                    .and_then(|a| a.name.as_ref().map(|n| n.to_string())),
+110                form_factor: sink_info.proplist.get_str(properties::DEVICE_FORM_FACTOR),
+111            }),
+112        }
+113    }
+114}
+115
+116#[derive(Debug)]
+117enum ClientRequest {
+118    GetDefaultDevice,
+119    GetInfoByName(DeviceKind, String),
+120    SetVolumeByName(DeviceKind, String, ChannelVolumes),
+121    SetMuteByName(DeviceKind, String, bool),
+122}
+123
+124impl Connection {
+125    fn new() -> Result<Self> {
+126        let mut proplist = Proplist::new().unwrap();
+127        proplist
+128            .set_str(properties::APPLICATION_NAME, env!("CARGO_PKG_NAME"))
+129            .map_err(|_| Error::new("Could not set pulseaudio APPLICATION_NAME property"))?;
+130
+131        let mainloop = Mainloop::new().error("Failed to create pulseaudio mainloop")?;
+132
+133        let mut context = Context::new_with_proplist(
+134            &mainloop,
+135            concat!(env!("CARGO_PKG_NAME"), "_context"),
+136            &proplist,
+137        )
+138        .error("Failed to create new pulseaudio context")?;
+139
+140        context
+141            .connect(None, FlagSet::NOFLAGS, None)
+142            .error("Failed to connect to pulseaudio context")?;
+143
+144        let mut connection = Connection { mainloop, context };
+145
+146        // Wait for context to be ready
+147        loop {
+148            connection.iterate(false)?;
+149            match connection.context.get_state() {
+150                PulseState::Ready => {
+151                    break;
+152                }
+153                PulseState::Failed | PulseState::Terminated => {
+154                    return Err(Error::new("pulseaudio context state failed/terminated"));
+155                }
+156                _ => {}
+157            }
+158        }
+159
+160        Ok(connection)
+161    }
+162
+163    fn iterate(&mut self, blocking: bool) -> Result<()> {
+164        match self.mainloop.iterate(blocking) {
+165            IterateResult::Quit(_) | IterateResult::Err(_) => {
+166                Err(Error::new("failed to iterate pulseaudio state"))
+167            }
+168            IterateResult::Success(_) => Ok(()),
+169        }
+170    }
+171
+172    /// Create connection in a new thread.
+173    ///
+174    /// If connection can't be created, Err is returned.
+175    fn spawn(thread_name: &str, f: impl Fn(Self) -> bool + Send + 'static) -> Result<()> {
+176        let (tx, rx) = std::sync::mpsc::sync_channel(0);
+177        thread::Builder::new()
+178            .name(thread_name.to_owned())
+179            .spawn(move || match Self::new() {
+180                Ok(mut conn) => {
+181                    tx.send(Ok(())).unwrap();
+182                    while f(conn) {
+183                        let mut try_i = 0usize;
+184                        loop {
+185                            try_i += 1;
+186                            let delay =
+187                                Duration::from_millis(if try_i <= 10 { 100 } else { 5_000 });
+188                            eprintln!("reconnecting to pulseaudio in {delay:?}... (try {try_i})");
+189                            thread::sleep(delay);
+190                            if let Ok(c) = Self::new() {
+191                                eprintln!("reconnected to pulseaudio");
+192                                conn = c;
+193                                break;
+194                            }
+195                        }
+196                    }
+197                }
+198                Err(err) => {
+199                    tx.send(Err(err)).unwrap();
+200                }
+201            })
+202            .error("failed to spawn a thread")?;
+203        rx.recv().error("channel closed")?
+204    }
+205}
+206
+207impl Client {
+208    fn new() -> Result<Client> {
+209        let (send_req, recv_req) = std::sync::mpsc::channel();
+210        let ml_waker = MainloopWaker::new().unwrap();
+211
+212        Connection::spawn("sound_pulseaudio", move |mut connection| {
+213            ml_waker.attach(connection.mainloop.get_api());
+214
+215            let introspector = connection.context.introspect();
+216            connection
+217                .context
+218                .set_subscribe_callback(Some(Box::new(move |facility, _, index| match facility {
+219                    Some(Facility::Server) => {
+220                        introspector.get_server_info(Client::server_info_callback);
+221                    }
+222                    Some(Facility::Sink) => {
+223                        introspector.get_sink_info_by_index(index, Client::sink_info_callback);
+224                    }
+225                    Some(Facility::Source) => {
+226                        introspector.get_source_info_by_index(index, Client::source_info_callback);
+227                    }
+228                    _ => (),
+229                })));
+230
+231            connection.context.subscribe(
+232                InterestMaskSet::SERVER | InterestMaskSet::SINK | InterestMaskSet::SOURCE,
+233                |_| (),
+234            );
+235
+236            let mut introspector = connection.context.introspect();
+237
+238            loop {
+239                loop {
+240                    connection.iterate(true).unwrap();
+241                    match connection.context.get_state() {
+242                        PulseState::Ready => break,
+243                        PulseState::Failed => return true,
+244                        _ => (),
+245                    }
+246                }
+247
+248                loop {
+249                    use std::sync::mpsc::TryRecvError;
+250                    let req = match recv_req.try_recv() {
+251                        Ok(x) => x,
+252                        Err(TryRecvError::Empty) => break,
+253                        Err(TryRecvError::Disconnected) => return false,
+254                    };
+255
+256                    use ClientRequest::*;
+257                    match req {
+258                        GetDefaultDevice => {
+259                            introspector.get_server_info(Client::server_info_callback);
+260                        }
+261                        GetInfoByName(DeviceKind::Sink, name) => {
+262                            introspector.get_sink_info_by_name(&name, Client::sink_info_callback);
+263                        }
+264                        GetInfoByName(DeviceKind::Source, name) => {
+265                            introspector
+266                                .get_source_info_by_name(&name, Client::source_info_callback);
+267                        }
+268                        SetVolumeByName(DeviceKind::Sink, name, volumes) => {
+269                            introspector.set_sink_volume_by_name(&name, &volumes, None);
+270                        }
+271                        SetVolumeByName(DeviceKind::Source, name, volumes) => {
+272                            introspector.set_source_volume_by_name(&name, &volumes, None);
+273                        }
+274                        SetMuteByName(DeviceKind::Sink, name, mute) => {
+275                            introspector.set_sink_mute_by_name(&name, mute, None);
+276                        }
+277                        SetMuteByName(DeviceKind::Source, name, mute) => {
+278                            introspector.set_source_mute_by_name(&name, mute, None);
+279                        }
+280                    };
+281                }
+282            }
+283        })?;
+284
+285        Ok(Client { send_req, ml_waker })
+286    }
+287
+288    fn send(request: ClientRequest) -> Result<()> {
+289        match CLIENT.as_ref() {
+290            Ok(client) => {
+291                client.send_req.send(request).unwrap();
+292                client.ml_waker.wake().unwrap();
+293                Ok(())
+294            }
+295            Err(err) => Err(Error::new(format!(
+296                "pulseaudio connection failed with error: {err}",
+297            ))),
+298        }
+299    }
+300
+301    fn server_info_callback(server_info: &ServerInfo) {
+302        if let Some(default_sink) = server_info.default_sink_name.as_ref() {
+303            *DEFAULT_SINK.lock().unwrap() = default_sink.to_string().into();
+304        }
+305
+306        if let Some(default_source) = server_info.default_source_name.as_ref() {
+307            *DEFAULT_SOURCE.lock().unwrap() = default_source.to_string().into();
+308        }
+309
+310        Client::send_update_event();
+311    }
+312
+313    fn get_info_callback<I: TryInto<VolInfo>>(result: ListResult<I>) -> Option<VolInfo> {
+314        match result {
+315            ListResult::End | ListResult::Error => None,
+316            ListResult::Item(info) => info.try_into().ok(),
+317        }
+318    }
+319
+320    fn sink_info_callback(result: ListResult<&SinkInfo>) {
+321        if let Some(vol_info) = Self::get_info_callback(result) {
+322            DEVICES
+323                .lock()
+324                .unwrap()
+325                .insert((DeviceKind::Sink, vol_info.name.to_string()), vol_info);
+326
+327            Client::send_update_event();
+328        }
+329    }
+330
+331    fn source_info_callback(result: ListResult<&SourceInfo>) {
+332        if let Some(vol_info) = Self::get_info_callback(result) {
+333            DEVICES
+334                .lock()
+335                .unwrap()
+336                .insert((DeviceKind::Source, vol_info.name.to_string()), vol_info);
+337
+338            Client::send_update_event();
+339        }
+340    }
+341
+342    fn send_update_event() {
+343        EVENT_LISTENER
+344            .lock()
+345            .unwrap()
+346            .retain(|notify| notify.upgrade().inspect(|x| x.notify_one()).is_some());
+347    }
+348}
+349
+350impl Device {
+351    pub(super) fn new(device_kind: DeviceKind, name: Option<String>) -> Result<Self> {
+352        let notify = Arc::new(Notify::new());
+353        EVENT_LISTENER.lock().unwrap().push(Arc::downgrade(&notify));
+354
+355        Client::send(ClientRequest::GetDefaultDevice)?;
+356
+357        let device = Device {
+358            name,
+359            description: None,
+360            active_port: None,
+361            form_factor: None,
+362            device_kind,
+363            volume: None,
+364            volume_avg: 0,
+365            muted: false,
+366            notify,
+367        };
+368
+369        Client::send(ClientRequest::GetInfoByName(device_kind, device.name()))?;
+370
+371        Ok(device)
+372    }
+373
+374    fn name(&self) -> String {
+375        self.name
+376            .clone()
+377            .unwrap_or_else(|| self.device_kind.default_name().into())
+378    }
+379
+380    fn volume(&mut self, volume: ChannelVolumes) {
+381        self.volume = Some(volume);
+382        self.volume_avg = (volume.avg().0 as f32 / Volume::NORMAL.0 as f32 * 100.0).round() as u32;
+383    }
+384}
+385
+386#[async_trait::async_trait]
+387impl SoundDevice for Device {
+388    fn volume(&self) -> u32 {
+389        self.volume_avg
+390    }
+391
+392    fn muted(&self) -> bool {
+393        self.muted
+394    }
+395
+396    fn output_name(&self) -> String {
+397        self.name()
+398    }
+399
+400    fn output_description(&self) -> Option<String> {
+401        self.description.clone()
+402    }
+403
+404    fn active_port(&self) -> Option<String> {
+405        self.active_port.clone()
+406    }
+407
+408    fn form_factor(&self) -> Option<&str> {
+409        self.form_factor.as_deref()
+410    }
+411
+412    async fn get_info(&mut self) -> Result<()> {
+413        let devices = DEVICES.lock().unwrap();
+414
+415        if let Some(info) = devices.get(&(self.device_kind, self.name())) {
+416            self.volume(info.volume);
+417            self.muted = info.mute;
+418            self.description.clone_from(&info.description);
+419            self.active_port.clone_from(&info.active_port);
+420            self.form_factor.clone_from(&info.form_factor);
+421        }
+422
+423        Ok(())
+424    }
+425
+426    async fn set_volume(&mut self, step: i32, max_vol: Option<u32>) -> Result<()> {
+427        let mut volume = self.volume.error("Volume unknown")?;
+428
+429        // apply step to volumes
+430        let step = (step as f32 * Volume::NORMAL.0 as f32 / 100.0).round() as i32;
+431        for vol in volume.get_mut().iter_mut() {
+432            let uncapped_vol = max(0, vol.0 as i32 + step) as u32;
+433            let capped_vol = if let Some(vol_cap) = max_vol {
+434                min(
+435                    uncapped_vol,
+436                    (vol_cap as f32 * Volume::NORMAL.0 as f32 / 100.0).round() as u32,
+437                )
+438            } else {
+439                uncapped_vol
+440            };
+441            vol.0 = min(capped_vol, Volume::MAX.0);
+442        }
+443
+444        // update volumes
+445        self.volume(volume);
+446        Client::send(ClientRequest::SetVolumeByName(
+447            self.device_kind,
+448            self.name(),
+449            volume,
+450        ))?;
+451
+452        Ok(())
+453    }
+454
+455    async fn toggle(&mut self) -> Result<()> {
+456        self.muted = !self.muted;
+457
+458        Client::send(ClientRequest::SetMuteByName(
+459            self.device_kind,
+460            self.name(),
+461            self.muted,
+462        ))?;
+463
+464        Ok(())
+465    }
+466
+467    async fn wait_for_update(&mut self) -> Result<()> {
+468        self.notify.notified().await;
+469        Ok(())
+470    }
+471}
+472
+473/// Thread safe [`Mainloop`] waker.
+474///
+475/// Has the same purpose as [`Mainloop::wake`], but can be shared across threads.
+476#[derive(Debug, Clone, Copy)]
+477struct MainloopWaker {
+478    // Note: these fds are never closed, but this is OK because there is only one instance of this struct.
+479    pipe_tx: RawFd,
+480    pipe_rx: RawFd,
+481}
+482
+483impl MainloopWaker {
+484    /// Create new waker.
+485    fn new() -> io::Result<Self> {
+486        let (pipe_rx, pipe_tx) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?;
+487        Ok(Self {
+488            pipe_tx: pipe_tx.into_raw_fd(),
+489            pipe_rx: pipe_rx.into_raw_fd(),
+490        })
+491    }
+492
+493    /// Attach this waker to a [`Mainloop`].
+494    ///
+495    /// A waker should be attached to _one_ mainloop.
+496    fn attach(self, ml: &MainloopApi) {
+497        extern "C" fn wake_cb(
+498            _: *const MainloopApi,
+499            _: *mut libpulse_binding::mainloop::events::io::IoEventInternal,
+500            fd: RawFd,
+501            _: libpulse_binding::mainloop::events::io::FlagSet,
+502            _: *mut c_void,
+503        ) {
+504            nix::unistd::read(fd, &mut [0; 32]).unwrap();
+505        }
+506
+507        (ml.io_new.unwrap())(
+508            ml as *const _,
+509            self.pipe_rx,
+510            libpulse_binding::mainloop::events::io::FlagSet::INPUT,
+511            Some(wake_cb),
+512            std::ptr::null_mut(),
+513        );
+514    }
+515
+516    /// Interrupt blocking [`Mainloop::iterate`].
+517    fn wake(self) -> io::Result<()> {
+518        let buf = [0u8];
+519        let res = unsafe { libc::write(self.pipe_tx, buf.as_ptr().cast(), 1) };
+520        if res == -1 {
+521            Err(io::Error::last_os_error())
+522        } else {
+523            Ok(())
+524        }
+525    }
+526}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/speedtest.rs.html b/src/i3status_rs/blocks/speedtest.rs.html new file mode 100644 index 0000000000..4ca352750f --- /dev/null +++ b/src/i3status_rs/blocks/speedtest.rs.html @@ -0,0 +1,96 @@ +speedtest.rs - source

i3status_rs/blocks/
speedtest.rs

1//! Ping, download, and upload speeds
+2//!
+3//! This block which requires [`speedtest-cli`](https://github.com/sivel/speedtest-cli).
+4//!
+5//! # Configuration
+6//!
+7//! Key | Values | Default
+8//! ----|--------|--------
+9//! `format` | A string to customise the output of this block. See below for available placeholders. | `" ^icon_ping $ping ^icon_net_down $speed_down ^icon_net_up $speed_up "`
+10//! `interval` | Update interval in seconds | `1800`
+11//!
+12//! Placeholder  | Value          | Type   | Unit
+13//! -------------|----------------|--------|---------------
+14//! `ping`       | Ping delay     | Number | Seconds
+15//! `speed_down` | Download speed | Number | Bits per second
+16//! `speed_up`   | Upload speed   | Number | Bits per second
+17//!
+18//! # Example
+19//!
+20//! Show only ping (with an icon)
+21//!
+22//! ```toml
+23//! [[block]]
+24//! block = "speedtest"
+25//! interval = 1800
+26//! format = " ^icon_ping $ping "
+27//! ```
+28//!
+29//! Hide ping and display speed in bytes per second each using 4 characters (without icons)
+30//!
+31//! ```toml
+32//! [[block]]
+33//! block = "speedtest"
+34//! interval = 1800
+35//! format = " $speed_down.eng(w:4,u:B) $speed_up(w:4,u:B) "
+36//! ```
+37//!
+38//! # Icons Used
+39//! - `ping`
+40//! - `net_down`
+41//! - `net_up`
+42
+43use super::prelude::*;
+44use tokio::process::Command;
+45
+46#[derive(Deserialize, Debug, SmartDefault)]
+47#[serde(deny_unknown_fields, default)]
+48pub struct Config {
+49    pub format: FormatConfig,
+50    #[default(1800.into())]
+51    pub interval: Seconds,
+52}
+53
+54pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+55    let format = config
+56        .format
+57        .with_default(" ^icon_ping $ping ^icon_net_down $speed_down ^icon_net_up $speed_up ")?;
+58
+59    let mut command = Command::new("speedtest-cli");
+60    command.arg("--json");
+61
+62    loop {
+63        let output = command
+64            .output()
+65            .await
+66            .error("failed to run 'speedtest-cli'")?
+67            .stdout;
+68        let output =
+69            std::str::from_utf8(&output).error("'speedtest-cli' produced non-UTF8 output")?;
+70        let output: SpeedtestCliOutput =
+71            serde_json::from_str(output).error("'speedtest-cli' produced wrong JSON")?;
+72
+73        let mut widget = Widget::new().with_format(format.clone());
+74        widget.set_values(map! {
+75            "ping" => Value::seconds(output.ping * 1e-3),
+76            "speed_down" => Value::bits(output.download),
+77            "speed_up" => Value::bits(output.upload),
+78        });
+79        api.set_widget(widget)?;
+80
+81        select! {
+82            _ = sleep(config.interval.0) => (),
+83            _ = api.wait_for_update_request() => (),
+84        }
+85    }
+86}
+87
+88#[derive(Deserialize, Debug, Clone, Copy)]
+89struct SpeedtestCliOutput {
+90    /// Download speed in bits per second
+91    download: f64,
+92    /// Upload speed in bits per second
+93    upload: f64,
+94    /// Ping time in ms
+95    ping: f64,
+96}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/taskwarrior.rs.html b/src/i3status_rs/blocks/taskwarrior.rs.html new file mode 100644 index 0000000000..e78be33f5d --- /dev/null +++ b/src/i3status_rs/blocks/taskwarrior.rs.html @@ -0,0 +1,190 @@ +taskwarrior.rs - source

i3status_rs/blocks/
taskwarrior.rs

1//! The number of tasks from the taskwarrior list
+2//!
+3//! Clicking the right mouse button on the icon cycles the view of the block through the user's filters.
+4//!
+5//! # Configuration
+6//!
+7//! Key | Values | Default
+8//! ----|--------|--------
+9//! `interval` | Update interval in seconds | `600` (10min)
+10//! `warning_threshold` | The threshold of pending (or started) tasks when the block turns into a warning state | `10`
+11//! `critical_threshold` | The threshold of pending (or started) tasks when the block turns into a critical state | `20`
+12//! `filters` | A list of tables describing filters (see bellow) | ```[{name = "pending", filter = "-COMPLETED -DELETED"}]```
+13//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $count.eng(w:1) "`
+14//! `format_singular` | Same as `format` but for when exactly one task is pending. | `" $icon $count.eng(w:1) "`
+15//! `format_everything_done` | Same as `format` but for when all tasks are completed. | `" $icon $count.eng(w:1) "`
+16//! `data_location`| Directory in which taskwarrior stores its data files. Supports path expansions e.g. `~`. | `"~/.task"`
+17//!
+18//! ## Filter configuration
+19//!
+20//! Key | Values | Default
+21//! ----|--------|--------
+22//! `name` | The name of the filter |
+23//! `filter` | Specifies the criteria that must be met for a task to be counted towards this filter |
+24//! `config_override` | An array containing configuration overrides, useful for explicitly setting context or other configuration variables | `[]`
+25//!
+26//! # Placeholders
+27//!
+28//! Placeholder   | Value                                       | Type   | Unit
+29//! --------------|---------------------------------------------|--------|-----
+30//! `icon`        | A static icon                               | Icon   | -
+31//! `count`       | The number of tasks matching current filter | Number | -
+32//! `filter_name` | The name of current filter                  | Text   | -
+33//!
+34//! # Actions
+35//!
+36//! Action        | Default button
+37//! --------------|---------------
+38//! `next_filter` | Right
+39//!
+40//! # Example
+41//!
+42//! In this example, block will be hidden if `count` is zero.
+43//!
+44//! ```toml
+45//! [[block]]
+46//! block = "taskwarrior"
+47//! interval = 60
+48//! format = " $icon count.eng(w:1) tasks "
+49//! format_singular = " $icon 1 task "
+50//! format_everything_done = ""
+51//! warning_threshold = 10
+52//! critical_threshold = 20
+53//! [[block.filters]]
+54//! name = "today"
+55//! filter = "+PENDING +OVERDUE or +DUETODAY"
+56//! [[block.filters]]
+57//! name = "some-project"
+58//! filter = "project:some-project +PENDING"
+59//! config_override = ["rc.context:none"]
+60//! ```
+61//!
+62//! # Icons Used
+63//! - `tasks`
+64
+65use super::prelude::*;
+66use inotify::{Inotify, WatchMask};
+67use tokio::process::Command;
+68
+69#[derive(Deserialize, Debug)]
+70#[serde(deny_unknown_fields, default)]
+71pub struct Config {
+72    pub interval: Seconds,
+73    pub warning_threshold: u32,
+74    pub critical_threshold: u32,
+75    pub filters: Vec<Filter>,
+76    pub format: FormatConfig,
+77    pub format_singular: FormatConfig,
+78    pub format_everything_done: FormatConfig,
+79    pub data_location: ShellString,
+80}
+81
+82impl Default for Config {
+83    fn default() -> Self {
+84        Self {
+85            interval: Seconds::new(600),
+86            warning_threshold: 10,
+87            critical_threshold: 20,
+88            filters: vec![Filter {
+89                name: "pending".into(),
+90                filter: "-COMPLETED -DELETED".into(),
+91                config_override: Default::default(),
+92            }],
+93            format: default(),
+94            format_singular: default(),
+95            format_everything_done: default(),
+96            data_location: ShellString::new("~/.task"),
+97        }
+98    }
+99}
+100
+101pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+102    let mut actions = api.get_actions()?;
+103    api.set_default_actions(&[(MouseButton::Right, None, "next_filter")])?;
+104
+105    let format = config.format.with_default(" $icon $count.eng(w:1) ")?;
+106    let format_singular = config
+107        .format_singular
+108        .with_default(" $icon $count.eng(w:1) ")?;
+109    let format_everything_done = config
+110        .format_everything_done
+111        .with_default(" $icon $count.eng(w:1) ")?;
+112
+113    let mut filters = config.filters.iter().cycle();
+114    let mut filter = filters.next().error("`filters` is empty")?;
+115
+116    let notify = Inotify::init().error("Failed to start inotify")?;
+117    notify
+118        .watches()
+119        .add(&*config.data_location.expand()?, WatchMask::MODIFY)
+120        .error("Failed to watch data location")?;
+121    let mut updates = notify
+122        .into_event_stream([0; 1024])
+123        .error("Failed to create event stream")?;
+124
+125    loop {
+126        let number_of_tasks = get_number_of_tasks(filter).await?;
+127
+128        let mut widget = Widget::new();
+129
+130        widget.set_format(match number_of_tasks {
+131            0 => format_everything_done.clone(),
+132            1 => format_singular.clone(),
+133            _ => format.clone(),
+134        });
+135
+136        widget.set_values(map! {
+137            "icon" => Value::icon("tasks"),
+138            "count" => Value::number(number_of_tasks),
+139            "filter_name" => Value::text(filter.name.clone()),
+140        });
+141
+142        widget.state = match number_of_tasks {
+143            x if x >= config.critical_threshold => State::Critical,
+144            x if x >= config.warning_threshold => State::Warning,
+145            _ => State::Idle,
+146        };
+147
+148        api.set_widget(widget)?;
+149
+150        select! {
+151            _ = sleep(config.interval.0) =>(),
+152            _ = updates.next() => (),
+153            _ = api.wait_for_update_request() => (),
+154            Some(action) = actions.recv() => match action.as_ref() {
+155                "next_filter" => {
+156                    filter = filters.next().unwrap();
+157                }
+158                _ => (),
+159            }
+160        }
+161    }
+162}
+163
+164async fn get_number_of_tasks(filter: &Filter) -> Result<u32> {
+165    let args_iter = filter.config_override.iter().map(String::as_str).chain([
+166        "rc.gc=off",
+167        &filter.filter,
+168        "count",
+169    ]);
+170    let output = Command::new("task")
+171        .args(args_iter)
+172        .output()
+173        .await
+174        .error("failed to run taskwarrior for getting the number of tasks")?
+175        .stdout;
+176    std::str::from_utf8(&output)
+177        .error("failed to get the number of tasks from taskwarrior (invalid UTF-8)")?
+178        .trim()
+179        .parse::<u32>()
+180        .error("could not parse the result of taskwarrior")
+181}
+182
+183#[derive(Deserialize, Debug, Default, Clone)]
+184#[serde(deny_unknown_fields)]
+185pub struct Filter {
+186    pub name: String,
+187    pub filter: String,
+188    #[serde(default)]
+189    pub config_override: Vec<String>,
+190}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/tea_timer.rs.html b/src/i3status_rs/blocks/tea_timer.rs.html new file mode 100644 index 0000000000..b62646c5db --- /dev/null +++ b/src/i3status_rs/blocks/tea_timer.rs.html @@ -0,0 +1,131 @@ +tea_timer.rs - source

i3status_rs/blocks/
tea_timer.rs

1//! Timer
+2//!
+3//! # Configuration
+4//!
+5//! Key | Values | Default
+6//! ----|--------|--------
+7//! `format` | A string to customise the output of this block. See below for available placeholders. | <code>\" $icon {$time.duration(hms:true) \|}\"</code>
+8//! `increment` | The numbers of seconds to add each time the block is clicked. | 30
+9//! `done_cmd` | A command to run in `sh` when timer finishes. | None
+10//!
+11//! Placeholder            | Value                                                          | Type     | Unit
+12//! -----------------------|----------------------------------------------------------------|----------|---------------
+13//! `icon`                 | A static icon                                                  | Icon     | -
+14//! `time`                 | The time remaining on the timer                                | Duration | -
+15//! `hours` *DEPRECATED*   | The hours remaining on the timer                               | Text     | h
+16//! `minutes` *DEPRECATED* | The minutes remaining on the timer                             | Text     | mn
+17//! `seconds` *DEPRECATED* | The seconds remaining on the timer                             | Text     | s
+18//!
+19//! `time`, `hours`, `minutes`, and `seconds` are unset when the timer is inactive.
+20//!
+21//! `hours`, `minutes`, and `seconds` have been deprecated in favor of `time`.
+22//!
+23//! Action      | Default button
+24//! ------------|---------------
+25//! `increment` | Left / Wheel Up
+26//! `decrement` | Wheel Down
+27//! `reset`     | Right
+28//!
+29//! # Example
+30//!
+31//! ```toml
+32//! [[block]]
+33//! block = "tea_timer"
+34//! format = " $icon {$minutes:$seconds |}"
+35//! done_cmd = "notify-send 'Timer Finished'"
+36//! ```
+37//!
+38//! # Icons Used
+39//! - `tea`
+40
+41use super::prelude::*;
+42use crate::subprocess::spawn_shell;
+43
+44use std::time::{Duration, Instant};
+45
+46#[derive(Deserialize, Debug, SmartDefault)]
+47#[serde(deny_unknown_fields, default)]
+48pub struct Config {
+49    pub format: FormatConfig,
+50    pub increment: Option<u64>,
+51    pub done_cmd: Option<String>,
+52}
+53
+54pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+55    let mut actions = api.get_actions()?;
+56    api.set_default_actions(&[
+57        (MouseButton::Left, None, "increment"),
+58        (MouseButton::WheelUp, None, "increment"),
+59        (MouseButton::WheelDown, None, "decrement"),
+60        (MouseButton::Right, None, "reset"),
+61    ])?;
+62
+63    let interval: Seconds = 1.into();
+64    let mut timer = interval.timer();
+65
+66    let format = config
+67        .format
+68        .with_default(" $icon {$time.duration(hms:true) |}")?;
+69
+70    let increment = Duration::from_secs(config.increment.unwrap_or(30));
+71    let mut timer_end = Instant::now();
+72
+73    let mut timer_was_active = false;
+74
+75    loop {
+76        let mut widget = Widget::new().with_format(format.clone());
+77
+78        let remaining_time = timer_end - Instant::now();
+79        let is_timer_active = !remaining_time.is_zero();
+80
+81        if !is_timer_active
+82            && timer_was_active
+83            && let Some(cmd) = &config.done_cmd
+84        {
+85            spawn_shell(cmd).error("done_cmd error")?;
+86        }
+87        timer_was_active = is_timer_active;
+88
+89        let mut values = map!(
+90            "icon" => Value::icon("tea"),
+91        );
+92
+93        if is_timer_active {
+94            values.insert("time".into(), Value::duration(remaining_time));
+95            let mut seconds = remaining_time.as_secs();
+96
+97            if format.contains_key("hours") {
+98                let hours = seconds / 3_600;
+99                values.insert("hours".into(), Value::text(format!("{hours:02}")));
+100                seconds %= 3_600;
+101            }
+102
+103            if format.contains_key("minutes") {
+104                let minutes = seconds / 60;
+105                values.insert("minutes".into(), Value::text(format!("{minutes:02}")));
+106                seconds %= 60;
+107            }
+108
+109            values.insert("seconds".into(), Value::text(format!("{seconds:02}")));
+110        }
+111
+112        widget.set_values(values);
+113
+114        api.set_widget(widget)?;
+115
+116        select! {
+117            _ = timer.tick(), if is_timer_active => (),
+118            _ = api.wait_for_update_request() => (),
+119            Some(action) = actions.recv() => {
+120                let now = Instant::now();
+121                match action.as_ref() {
+122                    "increment" if is_timer_active => timer_end += increment,
+123                    "increment" => timer_end = now + increment,
+124                    "decrement" if is_timer_active => timer_end -= increment,
+125                    "reset" => timer_end = now,
+126                    _ => (),
+127                }
+128            }
+129        }
+130    }
+131}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/temperature.rs.html b/src/i3status_rs/blocks/temperature.rs.html new file mode 100644 index 0000000000..cd4e832bf9 --- /dev/null +++ b/src/i3status_rs/blocks/temperature.rs.html @@ -0,0 +1,213 @@ +temperature.rs - source

i3status_rs/blocks/
temperature.rs

1//! The system temperature
+2//!
+3//! This block displays the system temperature, based on `libsensors` library.
+4//!
+5//! This block has two modes: "collapsed", which uses only color as an indicator, and "expanded",
+6//! which shows the content of a `format` string. The average, minimum, and maximum temperatures
+7//! are computed using all sensors displayed by `sensors`, or optionally filtered by `chip` and
+8//! `inputs`.
+9//!
+10//! Requires `libsensors` and appropriate kernel modules for your hardware.
+11//!
+12//! Run `sensors` command to list available chips and inputs.
+13//!
+14//! Note that the colour of the block is always determined by the maximum temperature across all
+15//! sensors, not the average. You may need to keep this in mind if you have a misbehaving sensor.
+16//!
+17//! # Configuration
+18//!
+19//! Key | Values | Default
+20//! ----|--------|--------
+21//! `format` | A string to customise the output of this block. See below for available placeholders | `" $icon $average avg, $max max "`
+22//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None`
+23//! `interval` | Update interval in seconds | `5`
+24//! `scale` | Either `"celsius"` or `"fahrenheit"` | `"celsius"`
+25//! `good` | Maximum temperature to set state to good | `20` °C (`68` °F)
+26//! `idle` | Maximum temperature to set state to idle | `45` °C (`113` °F)
+27//! `info` | Maximum temperature to set state to info | `60` °C (`140` °F)
+28//! `warning` | Maximum temperature to set state to warning. Beyond this temperature, state is set to critical | `80` °C (`176` °F)
+29//! `chip` | Narrows the results to a given chip name. `*` may be used as a wildcard. | None
+30//! `inputs` | Narrows the results to individual inputs reported by each chip. | None
+31//!
+32//! Action          | Description                               | Default button
+33//! ----------------|-------------------------------------------|---------------
+34//! `toggle_format` | Toggles between `format` and `format_alt` | Left
+35//!
+36//! Placeholder | Value                                | Type   | Unit
+37//! ------------|--------------------------------------|--------|--------
+38//! `min`       | Minimum temperature among all inputs | Number | Degrees
+39//! `average`   | Average temperature among all inputs | Number | Degrees
+40//! `max`       | Maximum temperature among all inputs | Number | Degrees
+41//!
+42//! Note that when block is collapsed, no placeholders are provided.
+43//!
+44//! # Example
+45//!
+46//! ```toml
+47//! [[block]]
+48//! block = "temperature"
+49//! format = " $icon $max max "
+50//! format_alt = " $icon $min min, $max max, $average avg "
+51//! interval = 10
+52//! chip = "*-isa-*"
+53//! ```
+54//!
+55//! # Icons Used
+56//! - `thermometer`
+57
+58use super::prelude::*;
+59use sensors::FeatureType::SENSORS_FEATURE_TEMP;
+60use sensors::Sensors;
+61use sensors::SubfeatureType::SENSORS_SUBFEATURE_TEMP_INPUT;
+62
+63const DEFAULT_GOOD: f64 = 20.0;
+64const DEFAULT_IDLE: f64 = 45.0;
+65const DEFAULT_INFO: f64 = 60.0;
+66const DEFAULT_WARN: f64 = 80.0;
+67
+68#[derive(Deserialize, Debug, SmartDefault)]
+69#[serde(deny_unknown_fields, default)]
+70pub struct Config {
+71    pub format: FormatConfig,
+72    pub format_alt: Option<FormatConfig>,
+73    #[default(5.into())]
+74    pub interval: Seconds,
+75    pub scale: TemperatureScale,
+76    pub good: Option<f64>,
+77    pub idle: Option<f64>,
+78    pub info: Option<f64>,
+79    pub warning: Option<f64>,
+80    pub chip: Option<String>,
+81    pub inputs: Option<Vec<String>>,
+82}
+83
+84#[derive(Deserialize, Debug, SmartDefault, Clone, Copy, PartialEq, Eq)]
+85#[serde(rename_all = "lowercase")]
+86pub enum TemperatureScale {
+87    #[default]
+88    Celsius,
+89    Fahrenheit,
+90}
+91
+92impl TemperatureScale {
+93    #[allow(clippy::wrong_self_convention)]
+94    pub fn from_celsius(self, val: f64) -> f64 {
+95        match self {
+96            Self::Celsius => val,
+97            Self::Fahrenheit => val * 1.8 + 32.0,
+98        }
+99    }
+100}
+101
+102pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+103    let mut actions = api.get_actions()?;
+104    api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
+105
+106    let mut format = config
+107        .format
+108        .with_default(" $icon $average avg, $max max ")?;
+109    let mut format_alt = match &config.format_alt {
+110        Some(f) => Some(f.with_default("")?),
+111        None => None,
+112    };
+113
+114    let good = config
+115        .good
+116        .unwrap_or_else(|| config.scale.from_celsius(DEFAULT_GOOD));
+117    let idle = config
+118        .idle
+119        .unwrap_or_else(|| config.scale.from_celsius(DEFAULT_IDLE));
+120    let info = config
+121        .info
+122        .unwrap_or_else(|| config.scale.from_celsius(DEFAULT_INFO));
+123    let warn = config
+124        .warning
+125        .unwrap_or_else(|| config.scale.from_celsius(DEFAULT_WARN));
+126
+127    loop {
+128        let chip = config.chip.clone();
+129        let inputs = config.inputs.clone();
+130        let config_scale = config.scale;
+131        let temp = tokio::task::spawn_blocking(move || {
+132            let mut vals = Vec::new();
+133            let sensors = Sensors::new();
+134            let chips = match &chip {
+135                Some(chip) => sensors
+136                    .detected_chips(chip)
+137                    .error("Failed to create chip iterator")?,
+138                None => sensors.into_iter(),
+139            };
+140            for chip in chips {
+141                for feat in chip {
+142                    if *feat.feature_type() != SENSORS_FEATURE_TEMP {
+143                        continue;
+144                    }
+145                    if let Some(inputs) = &inputs {
+146                        let label = feat.get_label().error("Failed to get input label")?;
+147                        if !inputs.contains(&label) {
+148                            continue;
+149                        }
+150                    }
+151                    for subfeat in feat {
+152                        if *subfeat.subfeature_type() == SENSORS_SUBFEATURE_TEMP_INPUT
+153                            && let Ok(value) = subfeat.get_value()
+154                        {
+155                            if (-100.0..=150.0).contains(&value) {
+156                                vals.push(config_scale.from_celsius(value));
+157                            } else {
+158                                eprintln!("Temperature ({value}) outside of range ([-100, 150])");
+159                            }
+160                        }
+161                    }
+162                }
+163            }
+164            Ok(vals)
+165        })
+166        .await
+167        .error("Failed to join tokio task")??;
+168
+169        let min_temp = temp
+170            .iter()
+171            .min_by(|a, b| a.partial_cmp(b).unwrap())
+172            .cloned()
+173            .unwrap_or(0.0);
+174        let max_temp = temp
+175            .iter()
+176            .max_by(|a, b| a.partial_cmp(b).unwrap())
+177            .cloned()
+178            .unwrap_or(0.0);
+179        let avg_temp = temp.iter().sum::<f64>() / temp.len() as f64;
+180
+181        let mut widget = Widget::new().with_format(format.clone());
+182
+183        widget.state = match max_temp {
+184            x if x <= good => State::Good,
+185            x if x <= idle => State::Idle,
+186            x if x <= info => State::Info,
+187            x if x <= warn => State::Warning,
+188            _ => State::Critical,
+189        };
+190
+191        widget.set_values(map! {
+192            "icon" => Value::icon_progression_bound("thermometer", max_temp, good, warn),
+193            "average" => Value::degrees(avg_temp),
+194            "min" => Value::degrees(min_temp),
+195            "max" => Value::degrees(max_temp),
+196        });
+197
+198        api.set_widget(widget)?;
+199
+200        select! {
+201            _ = sleep(config.interval.0) => (),
+202            _ = api.wait_for_update_request() => (),
+203            Some(action) = actions.recv() => match action.as_ref() {
+204                "toggle_format" => {
+205                    if let Some(format_alt) = &mut format_alt {
+206                        std::mem::swap(format_alt, &mut format);
+207                    }
+208                }
+209                _ => (),
+210            }
+211        }
+212    }
+213}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/time.rs.html b/src/i3status_rs/blocks/time.rs.html new file mode 100644 index 0000000000..ee2e57c01e --- /dev/null +++ b/src/i3status_rs/blocks/time.rs.html @@ -0,0 +1,136 @@ +time.rs - source

i3status_rs/blocks/
time.rs

1//! The current time.
+2//!
+3//! # Configuration
+4//!
+5//! Key        | Values | Default
+6//! -----------|--------|--------
+7//! `format`   | Format string. See [chrono docs](https://docs.rs/chrono/0.3.0/chrono/format/strftime/index.html#specifiers) for all options. | `" $icon $timestamp.datetime() "`
+8//! `interval` | Update interval in seconds | `10`
+9//! `timezone` | A timezone specifier (e.g. "Europe/Lisbon") | Local timezone
+10//!
+11//! Placeholder   | Value                                       | Type     | Unit
+12//! --------------|---------------------------------------------|----------|-----
+13//! `icon`        | A static icon                               | Icon     | -
+14//! `timestamp`   | The current time                            | Datetime | -
+15//!
+16//! Action          | Default button
+17//! ----------------|---------------
+18//! `next_timezone` | Left
+19//! `prev_timezone` | Right
+20//!
+21//! # Example
+22//!
+23//! ```toml
+24//! [[block]]
+25//! block = "time"
+26//! interval = 60
+27//! [block.format]
+28//! full = " $icon $timestamp.datetime(f:'%a %Y-%m-%d %R %Z', l:fr_BE) "
+29//! short = " $icon $timestamp.datetime(f:%R) "
+30//! ```
+31//!
+32//! # Non Gregorian calendars
+33//!
+34//! You can use calendars other than the Gregorian calendar by adding the calendar specifier in the locale string. When using
+35//! this feature you can't use chrono style format string, and you should use one of the options provided by
+36//! the `icu4x` crate: `short`, `medium`, `long`, `full`.
+37//!
+38//! ** Only available using feature `icu_calendar`. **
+39//!
+40//! ## Example
+41//!
+42//! ```toml
+43//! [[block]]
+44//! block = "time"
+45//! interval = 60
+46//! format = "$timestamp.datetime(locale:'fa_IR-u-ca-persian', f:'full')"
+47//! ```
+48//!
+49//! # Icons Used
+50//! - `time`
+51
+52use chrono::{Timelike as _, Utc};
+53use chrono_tz::Tz;
+54
+55use super::prelude::*;
+56
+57#[derive(Deserialize, Debug, SmartDefault)]
+58#[serde(deny_unknown_fields, default)]
+59pub struct Config {
+60    pub format: FormatConfig,
+61    #[default(10.into())]
+62    pub interval: Seconds,
+63    pub timezone: Option<Timezone>,
+64}
+65
+66#[derive(Deserialize, Debug, Clone)]
+67#[serde(untagged)]
+68pub enum Timezone {
+69    Timezone(Tz),
+70    Timezones(Vec<Tz>),
+71}
+72
+73pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+74    let mut actions = api.get_actions()?;
+75    api.set_default_actions(&[
+76        (MouseButton::Left, None, "next_timezone"),
+77        (MouseButton::Right, None, "prev_timezone"),
+78    ])?;
+79
+80    let format = config
+81        .format
+82        .with_default(" $icon $timestamp.datetime() ")?;
+83
+84    let timezones = match config.timezone.clone() {
+85        Some(tzs) => match tzs {
+86            Timezone::Timezone(tz) => vec![tz],
+87            Timezone::Timezones(tzs) => tzs,
+88        },
+89        None => Vec::new(),
+90    };
+91
+92    let prev_step_length = timezones.len().saturating_sub(2);
+93
+94    let mut timezone_iter = timezones.iter().cycle();
+95
+96    let mut timezone = timezone_iter.next();
+97
+98    let interval_seconds = config.interval.seconds().max(1);
+99
+100    let mut timer = tokio::time::interval_at(
+101        tokio::time::Instant::now() + Duration::from_secs(interval_seconds),
+102        Duration::from_secs(interval_seconds),
+103    );
+104    timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
+105
+106    loop {
+107        let mut widget = Widget::new().with_format(format.clone());
+108        let now = Utc::now();
+109
+110        widget.set_values(map! {
+111            "icon" => Value::icon("time"),
+112            "timestamp" => Value::datetime(now, timezone.copied())
+113        });
+114
+115        api.set_widget(widget)?;
+116
+117        let phase = now.second() as u64 % interval_seconds;
+118        if phase != 0 {
+119            timer.reset_after(Duration::from_secs(interval_seconds - phase));
+120        }
+121
+122        tokio::select! {
+123            _ = timer.tick() => (),
+124            _ = api.wait_for_update_request() => (),
+125            Some(action) = actions.recv() => match action.as_ref() {
+126                "next_timezone" => {
+127                    timezone = timezone_iter.next();
+128                },
+129                "prev_timezone" => {
+130                    timezone = timezone_iter.nth(prev_step_length);
+131                },
+132                _ => (),
+133            }
+134        }
+135    }
+136}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/toggle.rs.html b/src/i3status_rs/blocks/toggle.rs.html new file mode 100644 index 0000000000..5564db3608 --- /dev/null +++ b/src/i3status_rs/blocks/toggle.rs.html @@ -0,0 +1,150 @@ +toggle.rs - source

i3status_rs/blocks/
toggle.rs

1//! A Toggle block
+2//!
+3//! You can add commands to be executed to disable the toggle (`command_off`), and to enable it
+4//! (`command_on`). If these command exit with a non-zero status, the block will not be toggled and
+5//! the block state will be changed to `critical` to give a visual warning of the failure. You also need to
+6//! specify a command to determine the state of the toggle (`command_state`). When the command outputs
+7//! nothing, the toggle is disabled, otherwise enabled. By specifying the interval property you can
+8//! let the command_state be executed continuously.
+9//!
+10//! To run those commands, the shell form `$SHELL` environment variable is used. If such variable
+11//! is not presented, `sh` is used.
+12//!
+13//! # Configuration
+14//!
+15//! Key | Values | Default
+16//! ----|--------|--------
+17//! `format` | A string to customise the output of this block. See below for available placeholders | `" $icon "`
+18//! `command_on` | Shell command to enable the toggle | **Required**
+19//! `command_off` | Shell command to disable the toggle | **Required**
+20//! `command_state` | Shell command to determine the state. Empty output => No, otherwise => Yes. | **Required**
+21//! `icon_on` | Icon override for the toggle button while on | `"toggle_on"`
+22//! `icon_off` | Icon override for the toggle button while off | `"toggle_off"`
+23//! `interval` | Update interval in seconds. If not set, `command_state` will run only on click. | None
+24//! `state_on` | [`State`] (color) of this block while on | [idle][State::Idle]
+25//! `state_off` | [`State`] (color) of this block while off | [idle][State::Idle]
+26//!
+27//! Placeholder   | Value                                       | Type   | Unit
+28//! --------------|---------------------------------------------|--------|-----
+29//! `icon`        | Icon based on toggle's state                | Icon   | -
+30//!
+31//! Action   | Default button
+32//! ---------|---------------
+33//! `toggle` | Left
+34//!
+35//! # Examples
+36//!
+37//! This is what can be used to toggle an external monitor configuration:
+38//!
+39//! ```toml
+40//! [[block]]
+41//! block = "toggle"
+42//! format = " $icon 4k "
+43//! command_state = "xrandr | grep 'DP1 connected 38' | grep -v eDP1"
+44//! command_on = "~/.screenlayout/4kmon_default.sh"
+45//! command_off = "~/.screenlayout/builtin.sh"
+46//! interval = 5
+47//! state_on = "good"
+48//! state_off = "warning"
+49//! ```
+50//!
+51//! # Icons Used
+52//! - `toggle_off`
+53//! - `toggle_on`
+54
+55use super::prelude::*;
+56use std::env;
+57use tokio::process::Command;
+58
+59#[derive(Deserialize, Debug)]
+60#[serde(deny_unknown_fields)]
+61pub struct Config {
+62    pub format: FormatConfig,
+63    pub command_on: String,
+64    pub command_off: String,
+65    pub command_state: String,
+66    #[serde(default)]
+67    pub icon_on: Option<String>,
+68    #[serde(default)]
+69    pub icon_off: Option<String>,
+70    #[serde(default)]
+71    pub interval: Option<u64>,
+72    pub state_on: Option<State>,
+73    pub state_off: Option<State>,
+74}
+75
+76async fn sleep_opt(dur: Option<Duration>) {
+77    match dur {
+78        Some(dur) => tokio::time::sleep(dur).await,
+79        None => std::future::pending().await,
+80    }
+81}
+82
+83pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+84    let mut actions = api.get_actions()?;
+85    api.set_default_actions(&[(MouseButton::Left, None, "toggle")])?;
+86
+87    let interval = config.interval.map(Duration::from_secs);
+88    let mut widget = Widget::new().with_format(config.format.with_default(" $icon ")?);
+89
+90    let icon_on = config.icon_on.as_deref().unwrap_or("toggle_on");
+91    let icon_off = config.icon_off.as_deref().unwrap_or("toggle_off");
+92
+93    let shell = env::var("SHELL").unwrap_or_else(|_| "sh".to_string());
+94
+95    loop {
+96        // Check state
+97        let output = Command::new(&shell)
+98            .args(["-c", &config.command_state])
+99            .output()
+100            .await
+101            .error("Failed to run command_state")?;
+102        let is_on = !std::str::from_utf8(&output.stdout)
+103            .error("The output of command_state is invalid UTF-8")?
+104            .trim()
+105            .is_empty();
+106
+107        widget.set_values(map!(
+108            "icon" => Value::icon(
+109                if is_on { icon_on.to_string() } else { icon_off.to_string() }
+110            )
+111        ));
+112        if widget.state != State::Critical {
+113            widget.state = if is_on {
+114                config.state_on.unwrap_or(State::Idle)
+115            } else {
+116                config.state_off.unwrap_or(State::Idle)
+117            };
+118        }
+119        api.set_widget(widget.clone())?;
+120
+121        loop {
+122            select! {
+123                _ = sleep_opt(interval) => break,
+124                _ = api.wait_for_update_request() => break,
+125                Some(action) = actions.recv() => match action.as_ref() {
+126                    "toggle" => {
+127                        let cmd = if is_on {
+128                            &config.command_off
+129                        } else {
+130                            &config.command_on
+131                        };
+132                        let output = Command::new(&shell)
+133                            .args(["-c", cmd])
+134                            .output()
+135                            .await
+136                            .error("Failed to run command")?;
+137                        if output.status.success() {
+138                            // Temporary; it will immediately be updated by the outer loop
+139                            widget.state = State::Idle;
+140                            break;
+141                        } else {
+142                            widget.state = State::Critical;
+143                        }
+144                    }
+145                    _ => (),
+146                }
+147            }
+148        }
+149    }
+150}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/uptime.rs.html b/src/i3status_rs/blocks/uptime.rs.html new file mode 100644 index 0000000000..69bd585bfb --- /dev/null +++ b/src/i3status_rs/blocks/uptime.rs.html @@ -0,0 +1,90 @@ +uptime.rs - source

i3status_rs/blocks/
uptime.rs

1//! System's uptime
+2//!
+3//! This block displays system uptime in terms of two biggest units, so minutes and seconds, or
+4//! hours and minutes or days and hours or weeks and days.
+5//!
+6//! # Configuration
+7//!
+8//! Key        | Values                     | Default
+9//! -----------|----------------------------|--------
+10//! `format` | A string to customise the output of this block. See below for available placeholders | `" $icon $uptime "`
+11//! `interval` | Update interval in seconds | `60`
+12//!
+13//! Placeholder         | Value                   | Type     | Unit
+14//! --------------------|-------------------------|----------|-----
+15//! `icon`              | A static icon           | Icon     | -
+16//! `text` *DEPRECATED* | Current uptime          | Text     | -
+17//! `uptime`            | Current uptime          | Duration | -
+18//!
+19//! `text` has been deprecated in favor of `uptime`.
+20//!
+21//! # Example
+22//!
+23//! ```toml
+24//! [[block]]
+25//! block = "uptime"
+26//! interval = 3600 # update every hour
+27//! ```
+28//!
+29//! # Used Icons
+30//! - `uptime`
+31
+32use super::prelude::*;
+33use tokio::fs::read_to_string;
+34
+35#[derive(Deserialize, Debug, SmartDefault)]
+36#[serde(deny_unknown_fields, default)]
+37pub struct Config {
+38    pub format: FormatConfig,
+39    #[default(60.into())]
+40    pub interval: Seconds,
+41}
+42
+43pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+44    let format = config.format.with_default(" $icon $uptime ")?;
+45
+46    loop {
+47        let uptime = read_to_string("/proc/uptime")
+48            .await
+49            .error("Failed to read /proc/uptime")?;
+50        let mut seconds: u64 = uptime
+51            .split('.')
+52            .next()
+53            .and_then(|u| u.parse().ok())
+54            .error("/proc/uptime has invalid content")?;
+55
+56        let uptime = Duration::from_secs(seconds);
+57
+58        let weeks = seconds / 604_800;
+59        seconds %= 604_800;
+60        let days = seconds / 86_400;
+61        seconds %= 86_400;
+62        let hours = seconds / 3_600;
+63        seconds %= 3_600;
+64        let minutes = seconds / 60;
+65        seconds %= 60;
+66
+67        let text = if weeks > 0 {
+68            format!("{weeks}w {days}d")
+69        } else if days > 0 {
+70            format!("{days}d {hours}h")
+71        } else if hours > 0 {
+72            format!("{hours}h {minutes}m")
+73        } else {
+74            format!("{minutes}m {seconds}s")
+75        };
+76
+77        let mut widget = Widget::new().with_format(format.clone());
+78        widget.set_values(map! {
+79          "icon" => Value::icon("uptime"),
+80          "text" => Value::text(text),
+81          "uptime" => Value::duration(uptime)
+82        });
+83        api.set_widget(widget)?;
+84
+85        select! {
+86            _ = sleep(config.interval.0) => (),
+87            _ = api.wait_for_update_request() => (),
+88        }
+89    }
+90}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/vpn.rs.html b/src/i3status_rs/blocks/vpn.rs.html new file mode 100644 index 0000000000..84f8c74780 --- /dev/null +++ b/src/i3status_rs/blocks/vpn.rs.html @@ -0,0 +1,180 @@ +vpn.rs - source

i3status_rs/blocks/
vpn.rs

1//! Shows the current connection status for VPN networks
+2//!
+3//! This widget toggles the connection on left click.
+4//!
+5//! # Configuration
+6//!
+7//! Key | Values | Default
+8//! ----|--------|--------
+9//! `driver` | Which vpn should be used . Available drivers are: `"nordvpn"` and `"mullvad"` | `"nordvpn"`
+10//! `interval` | Update interval in seconds. | `10`
+11//! `format_connected` | A string to customise the output in case the network is connected. See below for available placeholders. | `" VPN: $icon "`
+12//! `format_disconnected` | A string to customise the output in case the network is disconnected. See below for available placeholders. | `" VPN: $icon "`
+13//! `state_connected` | The widgets state if the vpn network is connected. | `info`
+14//! `state_disconnected` | The widgets state if the vpn network is disconnected | `idle`
+15//!
+16//! Placeholder | Value                                                     | Type   | Unit
+17//! ------------|-----------------------------------------------------------|--------|------
+18//! `icon`      | A static icon                                             | Icon   | -
+19//! `country`   | Country currently connected to                            | Text   | -
+20//! `flag`      | Country specific flag (depends on a font supporting them) | Text   | -
+21//!
+22//! Action    | Default button | Description
+23//! ----------|----------------|-----------------------------------
+24//! `toggle`  | Left           | toggles the vpn network connection
+25//!
+26//! # Drivers
+27//!
+28//! ## nordvpn
+29//! Behind the scenes the nordvpn driver uses the `nordvpn` command line binary. In order for this to work
+30//! properly the binary should be executable without root privileges.
+31//!
+32//! ## Mullvad
+33//! Behind the scenes the mullvad driver uses the `mullvad` command line binary. In order for this to work properly the binary should be executable and mullvad daemon should be running.
+34//!
+35//! # Example
+36//!
+37//! Shows the current vpn network state:
+38//!
+39//! ```toml
+40//! [[block]]
+41//! block = "vpn"
+42//! driver = "nordvpn"
+43//! interval = 10
+44//! format_connected = "VPN: $icon "
+45//! format_disconnected = "VPN: $icon "
+46//! state_connected = "good"
+47//! state_disconnected = "warning"
+48//! ```
+49//!
+50//! Possible values for `state_connected` and `state_disconnected`:
+51//!
+52//! ```text
+53//! warning
+54//! critical
+55//! good
+56//! info
+57//! idle
+58//! ```
+59//!
+60//! # Icons Used
+61//!
+62//! - `net_vpn`
+63//! - `net_wired`
+64//! - `net_down`
+65//! - country code flags (if supported by font)
+66//!
+67//! Flags: They are not icons but unicode glyphs. You will need a font that
+68//! includes them. Tested with: <https://www.babelstone.co.uk/Fonts/Flags.html>
+69
+70mod nordvpn;
+71use nordvpn::NordVpnDriver;
+72mod mullvad;
+73use mullvad::MullvadDriver;
+74
+75use super::prelude::*;
+76
+77#[derive(Deserialize, Debug, SmartDefault)]
+78#[serde(rename_all = "snake_case")]
+79pub enum DriverType {
+80    #[default]
+81    Nordvpn,
+82    Mullvad,
+83}
+84
+85#[derive(Deserialize, Debug, SmartDefault)]
+86#[serde(deny_unknown_fields, default)]
+87pub struct Config {
+88    pub driver: DriverType,
+89    #[default(10.into())]
+90    pub interval: Seconds,
+91    pub format_connected: FormatConfig,
+92    pub format_disconnected: FormatConfig,
+93    pub state_connected: State,
+94    pub state_disconnected: State,
+95}
+96
+97enum Status {
+98    Connected {
+99        country: String,
+100        country_flag: String,
+101    },
+102    Disconnected,
+103    Error,
+104}
+105
+106impl Status {
+107    fn icon(&self) -> Cow<'static, str> {
+108        match self {
+109            Status::Connected { .. } => "net_vpn".into(),
+110            Status::Disconnected => "net_wired".into(),
+111            Status::Error => "net_down".into(),
+112        }
+113    }
+114}
+115
+116pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+117    let mut actions = api.get_actions()?;
+118    api.set_default_actions(&[(MouseButton::Left, None, "toggle")])?;
+119
+120    let format_connected = config.format_connected.with_default(" VPN: $icon ")?;
+121    let format_disconnected = config.format_disconnected.with_default(" VPN: $icon ")?;
+122
+123    let driver: Box<dyn Driver> = match config.driver {
+124        DriverType::Nordvpn => Box::new(NordVpnDriver::new().await),
+125        DriverType::Mullvad => Box::new(MullvadDriver::new().await),
+126    };
+127
+128    loop {
+129        let status = driver.get_status().await?;
+130
+131        let mut widget = Widget::new();
+132
+133        widget.state = match &status {
+134            Status::Connected {
+135                country,
+136                country_flag,
+137            } => {
+138                widget.set_values(map!(
+139                        "icon" => Value::icon(status.icon()),
+140                        "country" => Value::text(country.to_string()),
+141                        "flag" => Value::text(country_flag.to_string()),
+142
+143                ));
+144                widget.set_format(format_connected.clone());
+145                config.state_connected
+146            }
+147            Status::Disconnected => {
+148                widget.set_values(map!(
+149                        "icon" => Value::icon(status.icon()),
+150                ));
+151                widget.set_format(format_disconnected.clone());
+152                config.state_disconnected
+153            }
+154            Status::Error => {
+155                widget.set_values(map!(
+156                        "icon" => Value::icon(status.icon()),
+157                ));
+158                widget.set_format(format_disconnected.clone());
+159                State::Critical
+160            }
+161        };
+162
+163        api.set_widget(widget)?;
+164
+165        select! {
+166            _ = sleep(config.interval.0) => (),
+167            _ = api.wait_for_update_request() => (),
+168            Some(action) = actions.recv() => match action.as_ref() {
+169                "toggle" => driver.toggle_connection(&status).await?,
+170                _ => (),
+171            }
+172        }
+173    }
+174}
+175
+176#[async_trait]
+177trait Driver {
+178    async fn get_status(&self) -> Result<Status>;
+179    async fn toggle_connection(&self, status: &Status) -> Result<()>;
+180}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/vpn/mullvad.rs.html b/src/i3status_rs/blocks/vpn/mullvad.rs.html new file mode 100644 index 0000000000..9703f2ee52 --- /dev/null +++ b/src/i3status_rs/blocks/vpn/mullvad.rs.html @@ -0,0 +1,85 @@ +mullvad.rs - source

i3status_rs/blocks/vpn/
mullvad.rs

1use regex::Regex;
+2use std::process::Stdio;
+3use tokio::process::Command;
+4
+5use crate::blocks::prelude::*;
+6use crate::util::country_flag_from_iso_code;
+7
+8use super::{Driver, Status};
+9
+10pub struct MullvadDriver {
+11    regex_country_code: Regex,
+12}
+13
+14impl MullvadDriver {
+15    pub async fn new() -> MullvadDriver {
+16        MullvadDriver {
+17            regex_country_code: Regex::new("Connected to ([a-z]{2}).*, ([A-Z][a-z]*).*\n").unwrap(),
+18        }
+19    }
+20
+21    async fn run_network_command(arg: &str) -> Result<()> {
+22        let code = Command::new("mullvad")
+23            .args([arg])
+24            .stdin(Stdio::null())
+25            .stdout(Stdio::null())
+26            .spawn()
+27            .error(format!("Problem running mullvad command: {arg}"))?
+28            .wait()
+29            .await
+30            .error(format!("Problem running mullvad command: {arg}"))?;
+31
+32        if code.success() {
+33            Ok(())
+34        } else {
+35            Err(Error::new(format!(
+36                "mullvad command failed with nonzero status: {code:?}"
+37            )))
+38        }
+39    }
+40}
+41
+42#[async_trait]
+43impl Driver for MullvadDriver {
+44    async fn get_status(&self) -> Result<Status> {
+45        let stdout = Command::new("mullvad")
+46            .args(["status"])
+47            .output()
+48            .await
+49            .error("Problem running mullvad command")?
+50            .stdout;
+51
+52        let status = String::from_utf8(stdout).error("mullvad produced non-UTF8 output")?;
+53
+54        if status.contains("Disconnected") {
+55            return Ok(Status::Disconnected);
+56        } else if status.contains("Connected") {
+57            let (country_flag, country) = self
+58                .regex_country_code
+59                .captures_iter(&status)
+60                .next()
+61                .map(|capture| {
+62                    let country_code = capture[1].to_uppercase();
+63                    let country = capture[2].to_owned();
+64                    let country_flag = country_flag_from_iso_code(&country_code);
+65                    (country_flag, country)
+66                })
+67                .unwrap_or_default();
+68
+69            return Ok(Status::Connected {
+70                country,
+71                country_flag,
+72            });
+73        }
+74        Ok(Status::Error)
+75    }
+76
+77    async fn toggle_connection(&self, status: &Status) -> Result<()> {
+78        match status {
+79            Status::Connected { .. } => Self::run_network_command("disconnect").await?,
+80            Status::Disconnected => Self::run_network_command("connect").await?,
+81            Status::Error => (),
+82        }
+83        Ok(())
+84    }
+85}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/vpn/nordvpn.rs.html b/src/i3status_rs/blocks/vpn/nordvpn.rs.html new file mode 100644 index 0000000000..4be037da0d --- /dev/null +++ b/src/i3status_rs/blocks/vpn/nordvpn.rs.html @@ -0,0 +1,95 @@ +nordvpn.rs - source

i3status_rs/blocks/vpn/
nordvpn.rs

1use regex::Regex;
+2use std::process::Stdio;
+3use tokio::process::Command;
+4
+5use crate::blocks::prelude::*;
+6use crate::util::country_flag_from_iso_code;
+7
+8use super::{Driver, Status};
+9
+10pub struct NordVpnDriver {
+11    regex_country_code: Regex,
+12}
+13
+14impl NordVpnDriver {
+15    pub async fn new() -> NordVpnDriver {
+16        NordVpnDriver {
+17            regex_country_code: Regex::new("^.*Hostname:\\s+([a-z]{2}).*$").unwrap(),
+18        }
+19    }
+20
+21    async fn run_network_command(arg: &str) -> Result<()> {
+22        Command::new("nordvpn")
+23            .args([arg])
+24            .stdin(Stdio::null())
+25            .stdout(Stdio::null())
+26            .spawn()
+27            .error(format!("Problem running nordvpn command: {arg}"))?
+28            .wait()
+29            .await
+30            .error(format!("Problem running nordvpn command: {arg}"))?;
+31        Ok(())
+32    }
+33
+34    async fn find_line(stdout: &str, needle: &str) -> Option<String> {
+35        stdout
+36            .lines()
+37            .find(|s| s.contains(needle))
+38            .map(|s| s.to_owned())
+39    }
+40}
+41
+42#[async_trait]
+43impl Driver for NordVpnDriver {
+44    async fn get_status(&self) -> Result<Status> {
+45        let stdout = Command::new("nordvpn")
+46            .args(["status"])
+47            .output()
+48            .await
+49            .error("Problem running nordvpn command")?
+50            .stdout;
+51
+52        let stdout = String::from_utf8(stdout).error("nordvpn produced non-UTF8 output")?;
+53        let line_status = Self::find_line(&stdout, "Status:").await;
+54        let line_country = Self::find_line(&stdout, "Country:").await;
+55        let line_country_flag = Self::find_line(&stdout, "Hostname:").await;
+56        if line_status.is_none() {
+57            return Ok(Status::Error);
+58        }
+59        let line_status = line_status.unwrap();
+60
+61        if line_status.ends_with("Disconnected") {
+62            return Ok(Status::Disconnected);
+63        } else if line_status.ends_with("Connected") {
+64            let country = match line_country {
+65                Some(country_line) => country_line.rsplit(": ").next().unwrap().to_string(),
+66                None => String::default(),
+67            };
+68            let country_flag = match line_country_flag {
+69                Some(country_line_flag) => self
+70                    .regex_country_code
+71                    .captures_iter(&country_line_flag)
+72                    .last()
+73                    .map(|capture| capture[1].to_owned())
+74                    .map(|code| code.to_uppercase())
+75                    .map(|code| country_flag_from_iso_code(&code))
+76                    .unwrap_or_default(),
+77                None => String::default(),
+78            };
+79            return Ok(Status::Connected {
+80                country,
+81                country_flag,
+82            });
+83        }
+84        Ok(Status::Error)
+85    }
+86
+87    async fn toggle_connection(&self, status: &Status) -> Result<()> {
+88        match status {
+89            Status::Connected { .. } => Self::run_network_command("disconnect").await?,
+90            Status::Disconnected => Self::run_network_command("connect").await?,
+91            Status::Error => (),
+92        }
+93        Ok(())
+94    }
+95}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/watson.rs.html b/src/i3status_rs/blocks/watson.rs.html new file mode 100644 index 0000000000..423121fdba --- /dev/null +++ b/src/i3status_rs/blocks/watson.rs.html @@ -0,0 +1,232 @@ +watson.rs - source

i3status_rs/blocks/
watson.rs

1//! Watson statistics
+2//!
+3//! [Watson](http://tailordev.github.io/Watson/) is a simple CLI time tracking application. This block will show the name of your current active project, tags and optionally recorded time. Clicking the widget will toggle the `show_time` variable dynamically.
+4//!
+5//! # Configuration
+6//!
+7//! Key | Values | Default
+8//! ----|--------|--------
+9//! `format` | A string to customise the output of this block. See below for available placeholders | `" $text |"`
+10//! `show_time` | Whether to show recorded time. | `false`
+11//! `state_path` | Path to the Watson state file. Supports path expansions e.g. `~`. | `$XDG_CONFIG_HOME/watson/state`
+12//! `interval` | Update interval, in seconds. | `60`
+13//!
+14//! Placeholder   | Value                   | Type   | Unit
+15//! --------------|-------------------------|--------|-----
+16//! `text`        | Current activity        | Text   | -
+17//!
+18//! Action             | Description                     | Default button
+19//! -------------------|---------------------------------|---------------
+20//! `toggle_show_time` | Toggle the value of `show_time` | Left
+21//!
+22//! # Example
+23//!
+24//! ```toml
+25//! [[block]]
+26//! block = "watson"
+27//! show_time = true
+28//! state_path = "~/.config/watson/state"
+29//! ```
+30//!
+31//! # TODO
+32//! - Extend functionality: start / stop watson using this block
+33
+34use chrono::{DateTime, offset::Local};
+35use dirs::config_dir;
+36use inotify::{Inotify, WatchMask};
+37use serde::de::Deserializer;
+38use std::path::PathBuf;
+39use tokio::fs::read_to_string;
+40
+41use super::prelude::*;
+42
+43#[derive(Deserialize, Debug, SmartDefault)]
+44#[serde(deny_unknown_fields, default)]
+45pub struct Config {
+46    pub format: FormatConfig,
+47    pub state_path: Option<ShellString>,
+48    #[default(60.into())]
+49    pub interval: Seconds,
+50    pub show_time: bool,
+51}
+52
+53pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+54    let mut actions = api.get_actions()?;
+55    api.set_default_actions(&[(MouseButton::Left, None, "toggle_show_time")])?;
+56
+57    let format = config.format.with_default(" $text |")?;
+58
+59    let mut show_time = config.show_time;
+60
+61    let (state_dir, state_file, state_path) = match &config.state_path {
+62        Some(p) => {
+63            let mut p: PathBuf = (*p.expand()?).into();
+64            let path = p.clone();
+65            let file = p.file_name().error("Failed to parse state_dir")?.to_owned();
+66            p.pop();
+67            (p, file, path)
+68        }
+69        None => {
+70            let mut path = config_dir().error("xdg config directory not found")?;
+71            path.push("watson");
+72            let dir = path.clone();
+73            path.push("state");
+74            (dir, "state".into(), path)
+75        }
+76    };
+77
+78    let notify = Inotify::init().error("Failed to start inotify")?;
+79    notify
+80        .watches()
+81        .add(&state_dir, WatchMask::CREATE | WatchMask::MOVED_TO)
+82        .error("Failed to watch watson state directory")?;
+83    let mut state_updates = notify
+84        .into_event_stream([0; 1024])
+85        .error("Failed to create event stream")?;
+86
+87    let mut timer = config.interval.timer();
+88    let mut prev_state = None;
+89
+90    loop {
+91        let state = read_to_string(&state_path)
+92            .await
+93            .error("Failed to read state file")?;
+94        let state = serde_json::from_str(&state).unwrap_or(WatsonState::Idle {});
+95
+96        let mut widget = Widget::new().with_format(format.clone());
+97
+98        match state {
+99            state @ WatsonState::Active { .. } => {
+100                widget.state = State::Good;
+101                widget.set_values(map!(
+102                  "text" => Value::text(state.format(show_time, "started", format_delta_past))
+103                ));
+104                prev_state = Some(state);
+105            }
+106            WatsonState::Idle {} => {
+107                if let Some(prev @ WatsonState::Active { .. }) = &prev_state {
+108                    // The previous state was active, which means that we just now stopped the time
+109                    // tracking. This means that we could show some statistics.
+110                    widget.state = State::Idle;
+111                    widget.set_values(map!(
+112                      "text" => Value::text(prev.format(true, "stopped", format_delta_after))
+113                    ));
+114                } else {
+115                    // File is empty which means that there is currently no active time tracking,
+116                    // and the previous state wasn't time tracking neither so we reset the
+117                    // contents.
+118                    widget.state = State::Idle;
+119                    widget.set_values(Values::default());
+120                }
+121                prev_state = Some(state);
+122            }
+123        }
+124
+125        api.set_widget(widget)?;
+126
+127        loop {
+128            select! {
+129                _ = timer.tick() => break,
+130                _ = api.wait_for_update_request() => break,
+131                Some(update) = state_updates.next() => {
+132                    let update = update.error("Bad inotify update")?;
+133                    if update.name.is_some_and(|x| state_file == x) {
+134                        break;
+135                    }
+136                }
+137                Some(action) = actions.recv() => match action.as_ref() {
+138                    "toggle_show_time" => {
+139                        show_time = !show_time;
+140                        break;
+141                    }
+142                    _ => (),
+143                }
+144            }
+145        }
+146    }
+147}
+148
+149fn format_delta_past(delta: &chrono::Duration) -> String {
+150    let spans = &[
+151        ("week", delta.num_weeks()),
+152        ("day", delta.num_days()),
+153        ("hour", delta.num_hours()),
+154        ("minute", delta.num_minutes()),
+155    ];
+156
+157    spans
+158        .iter()
+159        .filter(|&(_, n)| *n != 0)
+160        .map(|&(label, n)| format!("{n} {label}{} ago", if n > 1 { "s" } else { "" }))
+161        .next()
+162        .unwrap_or_else(|| "now".into())
+163}
+164
+165fn format_delta_after(delta: &chrono::Duration) -> String {
+166    let spans = &[
+167        ("week", delta.num_weeks()),
+168        ("day", delta.num_days()),
+169        ("hour", delta.num_hours()),
+170        ("minute", delta.num_minutes()),
+171        ("second", delta.num_seconds()),
+172    ];
+173
+174    spans
+175        .iter()
+176        .find(|&(_, n)| *n != 0)
+177        .map(|&(label, n)| format!("after {n} {label}{}", if n > 1 { "s" } else { "" }))
+178        .unwrap_or_else(|| "now".into())
+179}
+180
+181#[derive(Deserialize, Clone, Debug)]
+182#[serde(untagged)]
+183enum WatsonState {
+184    Active {
+185        project: String,
+186        #[serde(deserialize_with = "deserialize_local_timestamp")]
+187        start: DateTime<Local>,
+188        tags: Vec<String>,
+189    },
+190    // This matches an empty JSON object
+191    Idle {},
+192}
+193
+194impl WatsonState {
+195    fn format(&self, show_time: bool, verb: &str, f: fn(&chrono::Duration) -> String) -> String {
+196        if let WatsonState::Active {
+197            project,
+198            start,
+199            tags,
+200        } = self
+201        {
+202            let mut s = project.clone();
+203            if let [first, other @ ..] = &tags[..] {
+204                s.push_str(" [");
+205                s.push_str(first);
+206                for tag in other {
+207                    s.push(' ');
+208                    s.push_str(tag);
+209                }
+210                s.push(']');
+211            }
+212            if show_time {
+213                s.push(' ');
+214                s.push_str(verb);
+215                let delta = Local::now() - *start;
+216                s.push(' ');
+217                s.push_str(&f(&delta));
+218            }
+219            s
+220        } else {
+221            panic!("WatsonState::Idle does not have a specified format")
+222        }
+223    }
+224}
+225
+226pub fn deserialize_local_timestamp<'de, D>(deserializer: D) -> Result<DateTime<Local>, D::Error>
+227where
+228    D: Deserializer<'de>,
+229{
+230    use chrono::TimeZone as _;
+231    i64::deserialize(deserializer).map(|seconds| Local.timestamp_opt(seconds, 0).single().unwrap())
+232}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/weather.rs.html b/src/i3status_rs/blocks/weather.rs.html new file mode 100644 index 0000000000..d4777dac38 --- /dev/null +++ b/src/i3status_rs/blocks/weather.rs.html @@ -0,0 +1,673 @@ +weather.rs - source

i3status_rs/blocks/
weather.rs

1//! Current weather
+2//!
+3//! This block displays local weather and temperature information. In order to use this block, you
+4//! will need access to a supported weather API service. At the time of writing, OpenWeatherMap,
+5//! met.no, and the US National Weather Service are supported.
+6//!
+7//! Configuring this block requires configuring a weather service, which may require API keys and
+8//! other parameters.
+9//!
+10//! If using the `autolocate` feature, set the autolocate update interval such that you do not exceed ipapi.co's free daily limit of 1000 hits. Or use `autolocate_interval = "once"` to only run on initialization.
+11//!
+12//! # Configuration
+13//!
+14//! Key | Values | Default
+15//! ----|--------|--------
+16//! `service` | The configuration of a weather service (see below). | **Required**
+17//! `format` | A string to customise the output of this block. See below for available placeholders. Text may need to be escaped, refer to [Escaping Text](#escaping-text). | `" $icon $weather $temp "`
+18//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None`
+19//! `interval` | Update interval, in seconds. | `600`
+20//! `autolocate` | Gets your location using the ipapi.co IP location service (no API key required). If the API call fails then the block will fallback to service specific location config. | `false`
+21//! `autolocate_interval` | Update interval for `autolocate` in seconds or "once" | `interval`
+22//!
+23//! # OpenWeatherMap Options
+24//!
+25//! To use the service you will need a (free) API key.
+26//!
+27//! Key | Values | Required | Default
+28//! ----|--------|----------|--------
+29//! `name` | `openweathermap`. | Yes | None
+30//! `api_key` | Your OpenWeatherMap API key. | Yes | None
+31//! `coordinates` | GPS latitude longitude coordinates as a tuple, example: `["39.2362","9.3317"]` | Yes* | None
+32//! `city_id` | OpenWeatherMap's ID for the city. (Deprecated) | Yes* | None
+33//! `place` | OpenWeatherMap 'By {city name},{state code},{country code}' search query. See [here](https://openweathermap.org/api/geocoding-api#direct_name). Consumes an additional API call | Yes* | None
+34//! `zip` | OpenWeatherMap 'By {zip code},{country code}' search query. See [here](https://openweathermap.org/api/geocoding-api#direct_zip). Consumes an additional API call | Yes* | None
+35//! `units` | Either `"metric"` or `"imperial"`. | No | `"metric"`
+36//! `lang` | Language code. See [here](https://openweathermap.org/current#multi). Currently only affects `weather_verbose` key. | No | `"en"`
+37//! `forecast_hours` | How many hours should be forecast (must be increments of 3 hours, max 120 hours) | No | 12
+38//!
+39//! One of `coordinates`, `city_id`, `place`, or `zip` is required. If more than one are supplied, `coordinates` takes precedence over `city_id` which takes precedence over `place` which takes precedence over `zip`.
+40//!
+41//! The options `api_key`, `city_id`, `place`, `zip`, can be omitted from configuration,
+42//! in which case they must be provided in the environment variables
+43//! `OPENWEATHERMAP_API_KEY`, `OPENWEATHERMAP_CITY_ID`, `OPENWEATHERMAP_PLACE`, `OPENWEATHERMAP_ZIP`.
+44//!
+45//! Forecasts are only fetched if forecast_hours > 0 and the format has keys related to forecast.
+46//!
+47//! # met.no Options
+48//!
+49//! Key | Values | Required | Default
+50//! ----|--------|----------|--------
+51//! `name` | `metno`. | Yes | None
+52//! `coordinates` | GPS latitude longitude coordinates as a tuple, example: `["39.2362","9.3317"]` | Required if `autolocate = false` | None
+53//! `lang` | Language code: `en`, `nn` or `nb` | No | `en`
+54//! `altitude` | Meters above sea level of the ground | No | Approximated by server
+55//! `forecast_hours` | How many hours should be forecast | No | 12
+56//!
+57//! Met.no does not support location name, but if autolocate is enabled then autolocate's city value is used.
+58//!
+59//! # US National Weather Service Options
+60//!
+61//! Key | Values | Required | Default
+62//! ----|--------|----------|--------
+63//! `name` | `nws`. | Yes | None
+64//! `coordinates` | GPS latitude longitude coordinates as a tuple, example: `["39.2362","9.3317"]` | Required if `autolocate = false` | None
+65//! `forecast_hours` | How many hours should be forecast | No | 12
+66//! `units` | Either `"metric"` or `"imperial"`. | No | `"metric"`
+67//!
+68//! Forecasts gather statistics from each hour between now and the `forecast_hours` value, and
+69//! provide predicted weather at the set number of hours into the future.
+70//!
+71//! # Available Format Keys
+72//!
+73//!  Key                                         | Value                                                                         | Type     | Unit
+74//! ---------------------------------------------|-------------------------------------------------------------------------------|----------|-----
+75//! `location`                                   | Location name (exact format depends on the service)                           | Text     | -
+76//! `icon{,_ffin}`                               | Icon representing the weather                                                 | Icon     | -
+77//! `weather{,_ffin}`                            | Textual brief description of the weather, e.g. "Raining"                      | Text     | -
+78//! `weather_verbose{,_ffin}`                    | Textual verbose description of the weather, e.g. "overcast clouds"            | Text     | -
+79//! `temp{,_{favg,fmin,fmax,ffin}}`              | Temperature                                                                   | Number   | degrees
+80//! `apparent{,_{favg,fmin,fmax,ffin}}`          | Australian Apparent Temperature                                               | Number   | degrees
+81//! `humidity{,_{favg,fmin,fmax,ffin}}`          | Humidity                                                                      | Number   | %
+82//! `wind{,_{favg,fmin,fmax,ffin}}`              | Wind speed                                                                    | Number   | -
+83//! `wind_kmh{,_{favg,fmin,fmax,ffin}}`          | Wind speed. The wind speed in km/h                                            | Number   | -
+84//! `direction{,_{favg,fmin,fmax,ffin}}`         | Wind direction, e.g. "NE"                                                     | Text     | -
+85//! `sunrise`                                    | Time of sunrise                                                               | DateTime | -
+86//! `sunset`                                     | Time of sunset                                                                | DateTime | -
+87//!
+88//! You can use the suffixes noted above to get the following:
+89//!
+90//! Suffix    | Description
+91//! ----------|------------
+92//! None      | Current weather
+93//! `_favg`   | Average forecast value
+94//! `_fmin`   | Minimum forecast value
+95//! `_fmax`   | Maximum forecast value
+96//! `_ffin`   | Final forecast value
+97//!
+98//! Action          | Description                               | Default button
+99//! ----------------|-------------------------------------------|---------------
+100//! `toggle_format` | Toggles between `format` and `format_alt` | Left
+101//!
+102//! # Examples
+103//!
+104//! Show detailed weather in San Francisco through the OpenWeatherMap service:
+105//!
+106//! ```toml
+107//! [[block]]
+108//! block = "weather"
+109//! format = " $icon $weather ($location) $temp, $wind m/s $direction "
+110//! format_alt = " $icon_ffin Forecast (9 hour avg) {$temp_favg ({$temp_fmin}-{$temp_fmax})|Unavailable} "
+111//! [block.service]
+112//! name = "openweathermap"
+113//! api_key = "XXX"
+114//! city_id = "5398563"
+115//! units = "metric"
+116//! forecast_hours = 9
+117//! ```
+118//!
+119//! Show sunrise and sunset times in null island
+120//!
+121//! ```toml
+122//! [[block]]
+123//! block = "weather"
+124//! format = "up $sunrise.datetime(f:'%R') down $sunset.datetime(f:'%R')"
+125//! [block.service]
+126//! name = "metno"
+127//! coordinates = ["0", "0"]
+128//! ```
+129//!
+130//! # Used Icons
+131//!
+132//! - `weather_sun` (when weather is reported as "Clear" during the day)
+133//! - `weather_moon` (when weather is reported as "Clear" at night)
+134//! - `weather_clouds` (when weather is reported as "Clouds" during the day)
+135//! - `weather_clouds_night` (when weather is reported as "Clouds" at night)
+136//! - `weather_fog` (when weather is reported as "Fog" or "Mist" during the day)
+137//! - `weather_fog_night` (when weather is reported as "Fog" or "Mist" at night)
+138//! - `weather_rain` (when weather is reported as "Rain" or "Drizzle" during the day)
+139//! - `weather_rain_night` (when weather is reported as "Rain" or "Drizzle" at night)
+140//! - `weather_snow` (when weather is reported as "Snow")
+141//! - `weather_thunder` (when weather is reported as "Thunderstorm" during the day)
+142//! - `weather_thunder_night` (when weather is reported as "Thunderstorm" at night)
+143
+144use chrono::{DateTime, Utc};
+145use sunrise::{SolarDay, SolarEvent};
+146
+147use crate::formatting::Format;
+148pub(super) use crate::geolocator::IPAddressInfo;
+149
+150use super::prelude::*;
+151
+152pub mod met_no;
+153pub mod nws;
+154pub mod open_weather_map;
+155
+156#[derive(Deserialize, Debug)]
+157#[serde(deny_unknown_fields)]
+158pub struct Config {
+159    #[serde(default = "default_interval")]
+160    pub interval: Seconds,
+161    #[serde(default)]
+162    pub format: FormatConfig,
+163    pub format_alt: Option<FormatConfig>,
+164    pub service: WeatherService,
+165    #[serde(default)]
+166    pub autolocate: bool,
+167    pub autolocate_interval: Option<Seconds>,
+168}
+169
+170fn default_interval() -> Seconds {
+171    Seconds::new(600)
+172}
+173
+174#[async_trait]
+175trait WeatherProvider {
+176    async fn get_weather(
+177        &self,
+178        autolocated_location: Option<&IPAddressInfo>,
+179        need_forecast: bool,
+180    ) -> Result<WeatherResult>;
+181}
+182
+183#[derive(Deserialize, Debug)]
+184#[serde(tag = "name", rename_all = "lowercase")]
+185pub enum WeatherService {
+186    OpenWeatherMap(open_weather_map::Config),
+187    MetNo(met_no::Config),
+188    Nws(nws::Config),
+189}
+190
+191#[derive(Clone, Copy, Default)]
+192enum WeatherIcon {
+193    Clear {
+194        is_night: bool,
+195    },
+196    Clouds {
+197        is_night: bool,
+198    },
+199    Fog {
+200        is_night: bool,
+201    },
+202    Rain {
+203        is_night: bool,
+204    },
+205    Snow,
+206    Thunder {
+207        is_night: bool,
+208    },
+209    #[default]
+210    Default,
+211}
+212
+213impl WeatherIcon {
+214    fn to_icon_str(self) -> &'static str {
+215        match self {
+216            Self::Clear { is_night: false } => "weather_sun",
+217            Self::Clear { is_night: true } => "weather_moon",
+218            Self::Clouds { is_night: false } => "weather_clouds",
+219            Self::Clouds { is_night: true } => "weather_clouds_night",
+220            Self::Fog { is_night: false } => "weather_fog",
+221            Self::Fog { is_night: true } => "weather_fog_night",
+222            Self::Rain { is_night: false } => "weather_rain",
+223            Self::Rain { is_night: true } => "weather_rain_night",
+224            Self::Snow => "weather_snow",
+225            Self::Thunder { is_night: false } => "weather_thunder",
+226            Self::Thunder { is_night: true } => "weather_thunder_night",
+227            Self::Default => "weather_default",
+228        }
+229    }
+230}
+231
+232#[derive(Default)]
+233struct WeatherMoment {
+234    icon: WeatherIcon,
+235    weather: String,
+236    weather_verbose: String,
+237    temp: f64,
+238    apparent: f64,
+239    humidity: f64,
+240    wind: f64,
+241    wind_kmh: f64,
+242    wind_direction: Option<f64>,
+243}
+244
+245struct ForecastAggregate {
+246    temp: f64,
+247    apparent: f64,
+248    humidity: f64,
+249    wind: f64,
+250    wind_kmh: f64,
+251    wind_direction: Option<f64>,
+252}
+253
+254struct ForecastAggregateSegment {
+255    temp: Option<f64>,
+256    apparent: Option<f64>,
+257    humidity: Option<f64>,
+258    wind: Option<f64>,
+259    wind_kmh: Option<f64>,
+260    wind_direction: Option<f64>,
+261}
+262
+263struct WeatherResult {
+264    location: String,
+265    current_weather: WeatherMoment,
+266    forecast: Option<Forecast>,
+267    sunrise: DateTime<Utc>,
+268    sunset: DateTime<Utc>,
+269}
+270
+271impl WeatherResult {
+272    fn into_values(self) -> Values {
+273        let mut values = map! {
+274            "location" => Value::text(self.location),
+275            //current_weather
+276            "icon" => Value::icon(self.current_weather.icon.to_icon_str()),
+277            "temp" => Value::degrees(self.current_weather.temp),
+278            "apparent" => Value::degrees(self.current_weather.apparent),
+279            "humidity" => Value::percents(self.current_weather.humidity),
+280            "weather" => Value::text(self.current_weather.weather),
+281            "weather_verbose" => Value::text(self.current_weather.weather_verbose),
+282            "wind" => Value::number(self.current_weather.wind),
+283            "wind_kmh" => Value::number(self.current_weather.wind_kmh),
+284            "direction" => Value::text(convert_wind_direction(self.current_weather.wind_direction).into()),
+285            "sunrise" => Value::datetime(self.sunrise, None),
+286            "sunset" => Value::datetime(self.sunset, None),
+287        };
+288
+289        if let Some(forecast) = self.forecast {
+290            macro_rules! map_forecasts {
+291                ({$($suffix: literal => $src: expr),* $(,)?}) => {
+292                    map!{ @extend values
+293                        $(
+294                            concat!("temp_f", $suffix) => Value::degrees($src.temp),
+295                            concat!("apparent_f", $suffix) => Value::degrees($src.apparent),
+296                            concat!("humidity_f", $suffix) => Value::percents($src.humidity),
+297                            concat!("wind_f", $suffix) => Value::number($src.wind),
+298                            concat!("wind_kmh_f", $suffix) => Value::number($src.wind_kmh),
+299                            concat!("direction_f", $suffix) => Value::text(convert_wind_direction($src.wind_direction).into()),
+300                        )*
+301                    }
+302                };
+303            }
+304            map_forecasts!({
+305                "avg" => forecast.avg,
+306                "min" => forecast.min,
+307                "max" => forecast.max,
+308                "fin" => forecast.fin,
+309            });
+310
+311            map! { @extend values
+312                "icon_ffin" => Value::icon(forecast.fin.icon.to_icon_str()),
+313                "weather_ffin" => Value::text(forecast.fin.weather.clone()),
+314                "weather_verbose_ffin" => Value::text(forecast.fin.weather_verbose.clone()),
+315            }
+316        }
+317
+318        values
+319    }
+320}
+321
+322struct Forecast {
+323    avg: ForecastAggregate,
+324    min: ForecastAggregate,
+325    max: ForecastAggregate,
+326    fin: WeatherMoment,
+327}
+328
+329impl Forecast {
+330    fn new(data: &[ForecastAggregateSegment], fin: WeatherMoment) -> Self {
+331        let mut temp_avg = 0.0;
+332        let mut temp_count = 0.0;
+333        let mut apparent_avg = 0.0;
+334        let mut apparent_count = 0.0;
+335        let mut humidity_avg = 0.0;
+336        let mut humidity_count = 0.0;
+337        let mut wind_north_avg = 0.0;
+338        let mut wind_east_avg = 0.0;
+339        let mut wind_kmh_north_avg = 0.0;
+340        let mut wind_kmh_east_avg = 0.0;
+341        let mut wind_count = 0.0;
+342        let mut max = ForecastAggregate {
+343            temp: f64::MIN,
+344            apparent: f64::MIN,
+345            humidity: f64::MIN,
+346            wind: f64::MIN,
+347            wind_kmh: f64::MIN,
+348            wind_direction: None,
+349        };
+350        let mut min = ForecastAggregate {
+351            temp: f64::MAX,
+352            apparent: f64::MAX,
+353            humidity: f64::MAX,
+354            wind: f64::MAX,
+355            wind_kmh: f64::MAX,
+356            wind_direction: None,
+357        };
+358        for val in data {
+359            if let Some(temp) = val.temp {
+360                temp_avg += temp;
+361                max.temp = max.temp.max(temp);
+362                min.temp = min.temp.min(temp);
+363                temp_count += 1.0;
+364            }
+365            if let Some(apparent) = val.apparent {
+366                apparent_avg += apparent;
+367                max.apparent = max.apparent.max(apparent);
+368                min.apparent = min.apparent.min(apparent);
+369                apparent_count += 1.0;
+370            }
+371            if let Some(humidity) = val.humidity {
+372                humidity_avg += humidity;
+373                max.humidity = max.humidity.max(humidity);
+374                min.humidity = min.humidity.min(humidity);
+375                humidity_count += 1.0;
+376            }
+377
+378            if let Some(wind) = val.wind
+379                && let Some(wind_kmh) = val.wind_kmh
+380            {
+381                if let Some(degrees) = val.wind_direction {
+382                    let (sin, cos) = degrees.to_radians().sin_cos();
+383                    wind_north_avg += wind * cos;
+384                    wind_east_avg += wind * sin;
+385                    wind_kmh_north_avg += wind_kmh * cos;
+386                    wind_kmh_east_avg += wind_kmh * sin;
+387                    wind_count += 1.0;
+388                }
+389
+390                if wind > max.wind {
+391                    max.wind_direction = val.wind_direction;
+392                    max.wind = wind;
+393                    max.wind_kmh = wind_kmh;
+394                }
+395
+396                if wind < min.wind {
+397                    min.wind_direction = val.wind_direction;
+398                    min.wind = wind;
+399                    min.wind_kmh = wind_kmh;
+400                }
+401            }
+402        }
+403
+404        temp_avg /= temp_count;
+405        humidity_avg /= humidity_count;
+406        apparent_avg /= apparent_count;
+407
+408        // Calculate the wind results separately, discarding invalid wind values
+409        let (wind_avg, wind_kmh_avg, wind_direction_avg) = if wind_count == 0.0 {
+410            (0.0, 0.0, None)
+411        } else {
+412            (
+413                wind_east_avg.hypot(wind_north_avg) / wind_count,
+414                wind_kmh_east_avg.hypot(wind_kmh_north_avg) / wind_count,
+415                Some(
+416                    wind_east_avg
+417                        .atan2(wind_north_avg)
+418                        .to_degrees()
+419                        .rem_euclid(360.0),
+420                ),
+421            )
+422        };
+423
+424        let avg = ForecastAggregate {
+425            temp: temp_avg,
+426            apparent: apparent_avg,
+427            humidity: humidity_avg,
+428            wind: wind_avg,
+429            wind_kmh: wind_kmh_avg,
+430            wind_direction: wind_direction_avg,
+431        };
+432        Self { avg, min, max, fin }
+433    }
+434}
+435
+436pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+437    let mut actions = api.get_actions()?;
+438    api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
+439
+440    let mut format = config.format.with_default(" $icon $weather $temp ")?;
+441    let mut format_alt = match &config.format_alt {
+442        Some(f) => Some(f.with_default("")?),
+443        None => None,
+444    };
+445
+446    let provider: Box<dyn WeatherProvider + Send + Sync> = match &config.service {
+447        WeatherService::MetNo(service_config) => Box::new(met_no::Service::new(service_config)?),
+448        WeatherService::OpenWeatherMap(service_config) => {
+449            Box::new(open_weather_map::Service::new(config.autolocate, service_config).await?)
+450        }
+451        WeatherService::Nws(service_config) => {
+452            Box::new(nws::Service::new(config.autolocate, service_config).await?)
+453        }
+454    };
+455
+456    let autolocate_interval = config.autolocate_interval.unwrap_or(config.interval);
+457    let need_forecast = need_forecast(&format, format_alt.as_ref());
+458
+459    let mut timer = config.interval.timer();
+460
+461    loop {
+462        let location = if config.autolocate {
+463            let fetch = || api.find_ip_location(&REQWEST_CLIENT, autolocate_interval.0);
+464            Some(fetch.retry(ExponentialBuilder::default()).await?)
+465        } else {
+466            None
+467        };
+468
+469        let fetch = || provider.get_weather(location.as_ref(), need_forecast);
+470        let data = fetch.retry(ExponentialBuilder::default()).await?;
+471        let data_values = data.into_values();
+472
+473        loop {
+474            let mut widget = Widget::new().with_format(format.clone());
+475            widget.set_values(data_values.clone());
+476            api.set_widget(widget)?;
+477
+478            select! {
+479                _ = timer.tick() => break,
+480                _ = api.wait_for_update_request() => break,
+481                Some(action) = actions.recv() => match action.as_ref() {
+482                        "toggle_format" => {
+483                            if let Some(ref mut format_alt) = format_alt {
+484                                std::mem::swap(format_alt, &mut format);
+485                            }
+486                        }
+487                        _ => (),
+488                    }
+489            }
+490        }
+491    }
+492}
+493
+494fn need_forecast(format: &Format, format_alt: Option<&Format>) -> bool {
+495    fn has_forecast_key(format: &Format) -> bool {
+496        macro_rules! format_suffix {
+497            ($($suffix: literal),* $(,)?) => {
+498                false
+499                $(
+500                    || format.contains_key(concat!("temp_f", $suffix))
+501                    || format.contains_key(concat!("apparent_f", $suffix))
+502                    || format.contains_key(concat!("humidity_f", $suffix))
+503                    || format.contains_key(concat!("wind_f", $suffix))
+504                    || format.contains_key(concat!("wind_kmh_f", $suffix))
+505                    || format.contains_key(concat!("direction_f", $suffix))
+506                )*
+507            };
+508        }
+509
+510        format_suffix!("avg", "min", "max", "fin")
+511            || format.contains_key("icon_ffin")
+512            || format.contains_key("weather_ffin")
+513            || format.contains_key("weather_verbose_ffin")
+514    }
+515    has_forecast_key(format) || format_alt.is_some_and(has_forecast_key)
+516}
+517
+518fn calculate_sunrise_sunset(
+519    lat: f64,
+520    lon: f64,
+521    altitude: Option<f64>,
+522) -> Result<(DateTime<Utc>, DateTime<Utc>)> {
+523    let date = Utc::now().date_naive();
+524    let coordinates = sunrise::Coordinates::new(lat, lon).error("Invalid coordinates")?;
+525    let solar_day = SolarDay::new(coordinates, date).with_altitude(altitude.unwrap_or_default());
+526
+527    Ok((
+528        solar_day.event_time(SolarEvent::Sunrise),
+529        solar_day.event_time(SolarEvent::Sunset),
+530    ))
+531}
+532
+533#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq, SmartDefault)]
+534#[serde(rename_all = "lowercase")]
+535enum UnitSystem {
+536    #[default]
+537    Metric,
+538    Imperial,
+539}
+540
+541// Convert wind direction in azimuth degrees to abbreviation names
+542fn convert_wind_direction(direction_opt: Option<f64>) -> &'static str {
+543    match direction_opt {
+544        Some(direction) => match direction.round() as i64 {
+545            24..=68 => "NE",
+546            69..=113 => "E",
+547            114..=158 => "SE",
+548            159..=203 => "S",
+549            204..=248 => "SW",
+550            249..=293 => "W",
+551            294..=338 => "NW",
+552            _ => "N",
+553        },
+554        None => "-",
+555    }
+556}
+557
+558/// Compute the Australian Apparent Temperature from metric units
+559fn australian_apparent_temp(temp: f64, humidity: f64, wind_speed: f64) -> f64 {
+560    let exponent = 17.27 * temp / (237.7 + temp);
+561    let water_vapor_pressure = humidity * 0.06105 * exponent.exp();
+562    temp + 0.33 * water_vapor_pressure - 0.7 * wind_speed - 4.0
+563}
+564
+565#[cfg(test)]
+566mod tests {
+567    use super::*;
+568
+569    #[test]
+570    fn test_new_forecast_average_wind_speed() {
+571        let mut degrees = 0.0;
+572        while degrees < 360.0 {
+573            let forecast = Forecast::new(
+574                &[
+575                    ForecastAggregateSegment {
+576                        temp: None,
+577                        apparent: None,
+578                        humidity: None,
+579                        wind: Some(1.0),
+580                        wind_kmh: Some(3.6),
+581                        wind_direction: Some(degrees),
+582                    },
+583                    ForecastAggregateSegment {
+584                        temp: None,
+585                        apparent: None,
+586                        humidity: None,
+587                        wind: Some(2.0),
+588                        wind_kmh: Some(7.2),
+589                        wind_direction: Some(degrees),
+590                    },
+591                ],
+592                WeatherMoment::default(),
+593            );
+594            assert!((forecast.avg.wind - 1.5).abs() < 0.1);
+595            assert!((forecast.avg.wind_kmh - 5.4).abs() < 0.1);
+596            assert!((forecast.avg.wind_direction.unwrap() - degrees).abs() < 0.1);
+597
+598            degrees += 15.0;
+599        }
+600    }
+601
+602    #[test]
+603    fn test_new_forecast_average_wind_degrees() {
+604        let mut degrees = 0.0;
+605        while degrees < 360.0 {
+606            let low = degrees - 1.0;
+607            let high = degrees + 1.0;
+608            let forecast = Forecast::new(
+609                &[
+610                    ForecastAggregateSegment {
+611                        temp: None,
+612                        apparent: None,
+613                        humidity: None,
+614                        wind: Some(1.0),
+615                        wind_kmh: Some(3.6),
+616                        wind_direction: Some(low),
+617                    },
+618                    ForecastAggregateSegment {
+619                        temp: None,
+620                        apparent: None,
+621                        humidity: None,
+622                        wind: Some(1.0),
+623                        wind_kmh: Some(3.6),
+624                        wind_direction: Some(high),
+625                    },
+626                ],
+627                WeatherMoment::default(),
+628            );
+629            // For winds of equal strength the direction should will be the
+630            // average of the low and high degrees
+631            assert!((forecast.avg.wind_direction.unwrap() - degrees).abs() < 0.1);
+632
+633            degrees += 15.0;
+634        }
+635    }
+636
+637    #[test]
+638    fn test_new_forecast_average_wind_speed_and_degrees() {
+639        let mut degrees = 0.0;
+640        while degrees < 360.0 {
+641            let low = degrees - 1.0;
+642            let high = degrees + 1.0;
+643            let forecast = Forecast::new(
+644                &[
+645                    ForecastAggregateSegment {
+646                        temp: None,
+647                        apparent: None,
+648                        humidity: None,
+649                        wind: Some(1.0),
+650                        wind_kmh: Some(3.6),
+651                        wind_direction: Some(low),
+652                    },
+653                    ForecastAggregateSegment {
+654                        temp: None,
+655                        apparent: None,
+656                        humidity: None,
+657                        wind: Some(2.0),
+658                        wind_kmh: Some(7.2),
+659                        wind_direction: Some(high),
+660                    },
+661                ],
+662                WeatherMoment::default(),
+663            );
+664            // Wind degree will be higher than the centerpoint of the low
+665            // and high winds since the high wind is stronger and will be
+666            // less than high
+667            // (low+high)/2 < average.degrees < high
+668            assert!((low + high) / 2.0 < forecast.avg.wind_direction.unwrap());
+669            assert!(forecast.avg.wind_direction.unwrap() < high);
+670            degrees += 15.0;
+671        }
+672    }
+673}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/weather/met_no.rs.html b/src/i3status_rs/blocks/weather/met_no.rs.html new file mode 100644 index 0000000000..9902525744 --- /dev/null +++ b/src/i3status_rs/blocks/weather/met_no.rs.html @@ -0,0 +1,295 @@ +met_no.rs - source

i3status_rs/blocks/weather/
met_no.rs

1use super::*;
+2
+3type LegendsStore = HashMap<String, LegendsResult>;
+4
+5#[derive(Deserialize, Debug, SmartDefault)]
+6#[serde(tag = "name", rename_all = "lowercase", deny_unknown_fields, default)]
+7pub struct Config {
+8    coordinates: Option<(String, String)>,
+9    altitude: Option<String>,
+10    #[serde(default)]
+11    lang: ApiLanguage,
+12    #[default(12)]
+13    forecast_hours: usize,
+14}
+15
+16pub(super) struct Service<'a> {
+17    config: &'a Config,
+18    legend: &'static LegendsStore,
+19}
+20
+21impl<'a> Service<'a> {
+22    pub(super) fn new(config: &'a Config) -> Result<Service<'a>> {
+23        Ok(Self {
+24            config,
+25            legend: LEGENDS.as_ref().error("Invalid legends file")?,
+26        })
+27    }
+28
+29    fn translate(&self, summary: &str) -> String {
+30        self.legend
+31            .get(summary)
+32            .map(|res| match self.config.lang {
+33                ApiLanguage::English => res.desc_en.as_str(),
+34                ApiLanguage::NorwegianBokmaal => res.desc_nb.as_str(),
+35                ApiLanguage::NorwegianNynorsk => res.desc_nn.as_str(),
+36            })
+37            .unwrap_or(summary)
+38            .into()
+39    }
+40}
+41
+42#[derive(Deserialize)]
+43struct LegendsResult {
+44    desc_en: String,
+45    desc_nb: String,
+46    desc_nn: String,
+47}
+48
+49#[derive(Deserialize, Debug, Clone, Default)]
+50pub(super) enum ApiLanguage {
+51    #[serde(rename = "en")]
+52    #[default]
+53    English,
+54    #[serde(rename = "nn")]
+55    NorwegianNynorsk,
+56    #[serde(rename = "nb")]
+57    NorwegianBokmaal,
+58}
+59
+60#[derive(Deserialize, Debug)]
+61struct ForecastResponse {
+62    properties: ForecastProperties,
+63}
+64
+65#[derive(Deserialize, Debug)]
+66struct ForecastProperties {
+67    timeseries: Vec<ForecastTimeStep>,
+68}
+69
+70#[derive(Deserialize, Debug)]
+71struct ForecastTimeStep {
+72    data: ForecastData,
+73    // time: String,
+74}
+75
+76impl ForecastTimeStep {
+77    fn to_moment(&self, service: &Service) -> WeatherMoment {
+78        let instant = &self.data.instant.details;
+79
+80        let mut symbol_code_split = self
+81            .data
+82            .next_1_hours
+83            .as_ref()
+84            .unwrap()
+85            .summary
+86            .symbol_code
+87            .split('_');
+88
+89        let summary = symbol_code_split.next().unwrap();
+90
+91        // Times of day can be day, night, and polartwilight
+92        let is_night = symbol_code_split.next() == Some("night");
+93
+94        let translated = service.translate(summary);
+95
+96        let temp = instant.air_temperature.unwrap_or_default();
+97        let humidity = instant.relative_humidity.unwrap_or_default();
+98        let wind_speed = instant.wind_speed.unwrap_or_default();
+99
+100        WeatherMoment {
+101            temp,
+102            apparent: australian_apparent_temp(temp, humidity, wind_speed),
+103            humidity,
+104            weather: translated.clone(),
+105            weather_verbose: translated,
+106            wind: wind_speed,
+107            wind_kmh: wind_speed * 3.6,
+108            wind_direction: instant.wind_from_direction,
+109            icon: weather_to_icon(summary, is_night),
+110        }
+111    }
+112
+113    fn to_aggregate(&self) -> ForecastAggregateSegment {
+114        let instant = &self.data.instant.details;
+115
+116        let apparent = if let Some(air_temperature) = instant.air_temperature
+117            && let Some(relative_humidity) = instant.relative_humidity
+118            && let Some(wind_speed) = instant.wind_speed
+119        {
+120            Some(australian_apparent_temp(
+121                air_temperature,
+122                relative_humidity,
+123                wind_speed,
+124            ))
+125        } else {
+126            None
+127        };
+128
+129        ForecastAggregateSegment {
+130            temp: instant.air_temperature,
+131            apparent,
+132            humidity: instant.relative_humidity,
+133            wind: instant.wind_speed,
+134            wind_kmh: instant.wind_speed.map(|t| t * 3.6),
+135            wind_direction: instant.wind_from_direction,
+136        }
+137    }
+138}
+139
+140#[derive(Deserialize, Debug)]
+141struct ForecastData {
+142    instant: ForecastModelInstant,
+143    // next_12_hours: ForecastModelPeriod,
+144    next_1_hours: Option<ForecastModelPeriod>,
+145    // next_6_hours: ForecastModelPeriod,
+146}
+147
+148#[derive(Deserialize, Debug)]
+149struct ForecastModelInstant {
+150    details: ForecastTimeInstant,
+151}
+152
+153#[derive(Deserialize, Debug)]
+154struct ForecastModelPeriod {
+155    summary: ForecastSummary,
+156}
+157
+158#[derive(Deserialize, Debug)]
+159struct ForecastSummary {
+160    symbol_code: String,
+161}
+162
+163#[derive(Deserialize, Debug, Default)]
+164struct ForecastTimeInstant {
+165    air_temperature: Option<f64>,
+166    wind_from_direction: Option<f64>,
+167    wind_speed: Option<f64>,
+168    relative_humidity: Option<f64>,
+169}
+170
+171static LEGENDS: LazyLock<Option<LegendsStore>> =
+172    LazyLock::new(|| serde_json::from_str(include_str!("met_no_legends.json")).ok());
+173
+174const FORECAST_URL: &str = "https://api.met.no/weatherapi/locationforecast/2.0/compact";
+175
+176#[async_trait]
+177impl WeatherProvider for Service<'_> {
+178    async fn get_weather(
+179        &self,
+180        autolocated: Option<&IPAddressInfo>,
+181        need_forecast: bool,
+182    ) -> Result<WeatherResult> {
+183        let (lat, lon) = autolocated
+184            .as_ref()
+185            .map(|loc| (loc.latitude.to_string(), loc.longitude.to_string()))
+186            .or_else(|| self.config.coordinates.clone())
+187            .error("No location given")?;
+188
+189        let altitude = if let Some(altitude) = &self.config.altitude {
+190            Some(altitude.parse().error("Unable to convert string to f64")?)
+191        } else {
+192            None
+193        };
+194
+195        let (sunrise, sunset) = calculate_sunrise_sunset(
+196            lat.parse().error("Unable to convert string to f64")?,
+197            lon.parse().error("Unable to convert string to f64")?,
+198            altitude,
+199        )?;
+200
+201        let querystr: HashMap<&str, String> = map! {
+202            "lat" => &lat,
+203            "lon" => &lon,
+204            [if let Some(alt) = &self.config.altitude] "altitude" => alt,
+205        };
+206
+207        let data: ForecastResponse = REQWEST_CLIENT
+208            .get(FORECAST_URL)
+209            .query(&querystr)
+210            .header(reqwest::header::CONTENT_TYPE, "application/json")
+211            .send()
+212            .await
+213            .error("Forecast request failed")?
+214            .json()
+215            .await
+216            .error("Forecast request failed")?;
+217
+218        let forecast_hours = self.config.forecast_hours;
+219        let location_name = autolocated.map_or("Unknown".to_string(), |c| c.city.clone());
+220
+221        let current_weather = data.properties.timeseries.first().unwrap().to_moment(self);
+222
+223        if !need_forecast || forecast_hours == 0 {
+224            return Ok(WeatherResult {
+225                location: location_name,
+226                current_weather,
+227                forecast: None,
+228                sunrise,
+229                sunset,
+230            });
+231        }
+232
+233        if data.properties.timeseries.len() < forecast_hours {
+234            return Err(Error::new(format!(
+235                "Unable to fetch the specified number of forecast_hours specified {}, only {} hours available",
+236                forecast_hours,
+237                data.properties.timeseries.len()
+238            )))?;
+239        }
+240
+241        let data_agg: Vec<ForecastAggregateSegment> = data
+242            .properties
+243            .timeseries
+244            .iter()
+245            .take(forecast_hours)
+246            .map(|f| f.to_aggregate())
+247            .collect();
+248
+249        let fin = data.properties.timeseries[forecast_hours - 1].to_moment(self);
+250
+251        let forecast = Some(Forecast::new(&data_agg, fin));
+252
+253        Ok(WeatherResult {
+254            location: location_name,
+255            current_weather,
+256            forecast,
+257            sunset,
+258            sunrise,
+259        })
+260    }
+261}
+262
+263fn weather_to_icon(weather: &str, is_night: bool) -> WeatherIcon {
+264    match weather {
+265        "cloudy" | "partlycloudy" | "fair" => WeatherIcon::Clouds{is_night},
+266        "fog" => WeatherIcon::Fog{is_night},
+267        "clearsky" => WeatherIcon::Clear{is_night},
+268        "heavyrain" | "heavyrainshowers" | "lightrain" | "lightrainshowers" | "rain"
+269        | "rainshowers" => WeatherIcon::Rain{is_night},
+270        "rainandthunder"
+271        | "heavyrainandthunder"
+272        | "rainshowersandthunder"
+273        | "sleetandthunder"
+274        | "sleetshowersandthunder"
+275        | "snowandthunder"
+276        | "snowshowersandthunder"
+277        | "heavyrainshowersandthunder"
+278        | "heavysleetandthunder"
+279        | "heavysleetshowersandthunder"
+280        | "heavysnowandthunder"
+281        | "heavysnowshowersandthunder"
+282        | "lightsleetandthunder"
+283        | "lightrainandthunder"
+284        | "lightsnowandthunder"
+285        | "lightssleetshowersandthunder" // There's a typo in the api it will be fixed in the next version to the following entry
+286        | "lightsleetshowersandthunder"
+287        | "lightssnowshowersandthunder"// There's a typo in the api it will be fixed in the next version to the following entry
+288        | "lightsnowshowersandthunder"
+289        | "lightrainshowersandthunder" => WeatherIcon::Thunder{is_night},
+290        "heavysleet" | "heavysleetshowers" | "heavysnow" | "heavysnowshowers" | "lightsleet"
+291        | "lightsleetshowers" | "lightsnow" | "lightsnowshowers" | "sleet" | "sleetshowers"
+292        | "snow" | "snowshowers" => WeatherIcon::Snow,
+293        _ => WeatherIcon::Default,
+294    }
+295}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/weather/nws.rs.html b/src/i3status_rs/blocks/weather/nws.rs.html new file mode 100644 index 0000000000..6c38041ce6 --- /dev/null +++ b/src/i3status_rs/blocks/weather/nws.rs.html @@ -0,0 +1,335 @@ +nws.rs - source

i3status_rs/blocks/weather/
nws.rs

1//! Support for using the US National Weather Service API.
+2//!
+3//! The API is documented [here](https://www.weather.gov/documentation/services-web-api).
+4//! There is a corresponding [OpenAPI document](https://api.weather.gov/openapi.json). The forecast
+5//! descriptions are translated into the set of supported icons as best as possible, and a more
+6//! complete summary forecast is available in the `weather_verbose` format key. The full NWS list
+7//! of icons and corresponding descriptions can be found [here](https://api.weather.gov/icons),
+8//! though these are slated for deprecation.
+9//!
+10//! All data is gathered using the hourly weather forecast service, after resolving from latitude &
+11//! longitude coordinates to a specific forecast office and grid point.
+12//!
+13
+14use super::*;
+15use serde::Deserialize;
+16
+17const API_URL: &str = "https://api.weather.gov/";
+18
+19const MPH_TO_KPH: f64 = 1.609344;
+20
+21#[derive(Deserialize, Debug, SmartDefault)]
+22#[serde(tag = "name", rename_all = "lowercase", deny_unknown_fields, default)]
+23pub struct Config {
+24    coordinates: Option<(String, String)>,
+25    #[default(12)]
+26    forecast_hours: usize,
+27    #[serde(default)]
+28    units: UnitSystem,
+29}
+30
+31#[derive(Clone, Debug)]
+32struct LocationInfo {
+33    query: String,
+34    name: String,
+35    lat: f64,
+36    lon: f64,
+37}
+38
+39pub(super) struct Service<'a> {
+40    config: &'a Config,
+41    location: Option<LocationInfo>,
+42}
+43
+44impl<'a> Service<'a> {
+45    pub(super) async fn new(autolocate: bool, config: &'a Config) -> Result<Service<'a>> {
+46        let location = if autolocate {
+47            None
+48        } else {
+49            let coords = config.coordinates.as_ref().error("no location given")?;
+50            Some(
+51                Self::get_location_query(
+52                    coords.0.parse().error("Unable to convert string to f64")?,
+53                    coords.1.parse().error("Unable to convert string to f64")?,
+54                    config.units,
+55                )
+56                .await?,
+57            )
+58        };
+59        Ok(Self { config, location })
+60    }
+61
+62    async fn get_location_query(lat: f64, lon: f64, units: UnitSystem) -> Result<LocationInfo> {
+63        let points_url = format!("{API_URL}/points/{lat},{lon}");
+64
+65        let response: ApiPoints = REQWEST_CLIENT
+66            .get(points_url)
+67            .send()
+68            .await
+69            .error("Zone resolution request failed")?
+70            .json()
+71            .await
+72            .error("Failed to parse zone resolution request")?;
+73        let mut query = response.properties.forecast_hourly;
+74        query.push_str(match units {
+75            UnitSystem::Metric => "?units=si",
+76            UnitSystem::Imperial => "?units=us",
+77        });
+78        let location = response.properties.relative_location.properties;
+79        let name = format!("{}, {}", location.city, location.state);
+80        Ok(LocationInfo {
+81            query,
+82            name,
+83            lat,
+84            lon,
+85        })
+86    }
+87}
+88
+89#[derive(Deserialize, Debug)]
+90struct ApiPoints {
+91    properties: ApiPointsProperties,
+92}
+93
+94#[derive(Deserialize, Debug)]
+95#[serde(rename_all = "camelCase")]
+96struct ApiPointsProperties {
+97    forecast_hourly: String,
+98    relative_location: ApiRelativeLocation,
+99}
+100
+101#[derive(Deserialize, Debug)]
+102#[serde(rename_all = "camelCase")]
+103struct ApiRelativeLocation {
+104    properties: ApiRelativeLocationProperties,
+105}
+106
+107#[derive(Deserialize, Debug)]
+108#[serde(rename_all = "camelCase")]
+109struct ApiRelativeLocationProperties {
+110    city: String,
+111    state: String,
+112}
+113
+114#[derive(Deserialize, Debug)]
+115struct ApiForecastResponse {
+116    properties: ApiForecastProperties,
+117}
+118
+119#[derive(Deserialize, Debug)]
+120struct ApiForecastProperties {
+121    periods: Vec<ApiForecast>,
+122}
+123
+124#[derive(Deserialize, Debug)]
+125#[serde(rename_all = "camelCase")]
+126struct ApiValue {
+127    value: f64,
+128    unit_code: String,
+129}
+130
+131#[derive(Deserialize, Debug)]
+132#[serde(rename_all = "camelCase")]
+133struct ApiForecast {
+134    is_daytime: bool,
+135    temperature: ApiValue,
+136    relative_humidity: ApiValue,
+137    wind_speed: ApiValue,
+138    wind_direction: String,
+139    short_forecast: String,
+140}
+141
+142impl ApiForecast {
+143    fn wind_direction(&self) -> Option<f64> {
+144        let dir = match self.wind_direction.as_str() {
+145            "N" => 0,
+146            "NNE" => 1,
+147            "NE" => 2,
+148            "ENE" => 3,
+149            "E" => 4,
+150            "ESE" => 5,
+151            "SE" => 6,
+152            "SSE" => 7,
+153            "S" => 8,
+154            "SSW" => 9,
+155            "SW" => 10,
+156            "WSW" => 11,
+157            "W" => 12,
+158            "WNW" => 13,
+159            "NW" => 14,
+160            "NNW" => 15,
+161            _ => return None,
+162        };
+163        Some((dir as f64) * (360.0 / 16.0))
+164    }
+165
+166    fn icon_to_word(icon: WeatherIcon) -> String {
+167        match icon {
+168            WeatherIcon::Clear { .. } => "Clear",
+169            WeatherIcon::Clouds { .. } => "Clouds",
+170            WeatherIcon::Fog { .. } => "Fog",
+171            WeatherIcon::Thunder { .. } => "Thunder",
+172            WeatherIcon::Rain { .. } => "Rain",
+173            WeatherIcon::Snow => "Snow",
+174            WeatherIcon::Default => "Unknown",
+175        }
+176        .to_string()
+177    }
+178
+179    fn wind_speed(&self) -> f64 {
+180        if self.wind_speed.unit_code.ends_with("km_h-1") {
+181            // m/s
+182            self.wind_speed.value / 3.6
+183        } else {
+184            // mph
+185            self.wind_speed.value
+186        }
+187    }
+188
+189    fn wind_kmh(&self) -> f64 {
+190        if self.wind_speed.unit_code.ends_with("km_h-1") {
+191            self.wind_speed.value
+192        } else {
+193            self.wind_speed.value * MPH_TO_KPH
+194        }
+195    }
+196
+197    fn apparent_temp(&self) -> f64 {
+198        let temp = if self.temperature.unit_code.ends_with("degC") {
+199            self.temperature.value
+200        } else {
+201            (self.temperature.value - 32.0) * 5.0 / 9.0
+202        };
+203        let humidity = self.relative_humidity.value;
+204        // wind_speed in m/s
+205        let wind_speed = self.wind_kmh() / 3.6;
+206        let apparent = australian_apparent_temp(temp, humidity, wind_speed);
+207        if self.temperature.unit_code.ends_with("degC") {
+208            apparent
+209        } else {
+210            (apparent * 9.0 / 5.0) + 32.0
+211        }
+212    }
+213
+214    fn to_moment(&self) -> WeatherMoment {
+215        let icon = short_forecast_to_icon(&self.short_forecast, !self.is_daytime);
+216        let weather = Self::icon_to_word(icon);
+217        WeatherMoment {
+218            icon,
+219            weather,
+220            weather_verbose: self.short_forecast.clone(),
+221            temp: self.temperature.value,
+222            apparent: self.apparent_temp(),
+223            humidity: self.relative_humidity.value,
+224            wind: self.wind_speed(),
+225            wind_kmh: self.wind_kmh(),
+226            wind_direction: self.wind_direction(),
+227        }
+228    }
+229
+230    fn to_aggregate(&self) -> ForecastAggregateSegment {
+231        ForecastAggregateSegment {
+232            temp: Some(self.temperature.value),
+233            apparent: Some(self.apparent_temp()),
+234            humidity: Some(self.relative_humidity.value),
+235            wind: Some(self.wind_speed()),
+236            wind_kmh: Some(self.wind_kmh()),
+237            wind_direction: self.wind_direction(),
+238        }
+239    }
+240}
+241
+242#[async_trait]
+243impl WeatherProvider for Service<'_> {
+244    async fn get_weather(
+245        &self,
+246        autolocated: Option<&IPAddressInfo>,
+247        need_forecast: bool,
+248    ) -> Result<WeatherResult> {
+249        let location = if let Some(coords) = autolocated {
+250            Self::get_location_query(coords.latitude, coords.longitude, self.config.units).await?
+251        } else {
+252            self.location.clone().error("No location was provided")?
+253        };
+254
+255        let (sunrise, sunset) = calculate_sunrise_sunset(location.lat, location.lon, None)?;
+256
+257        let data: ApiForecastResponse = REQWEST_CLIENT
+258            .get(location.query)
+259            .header(
+260                "Feature-Flags",
+261                "forecast_wind_speed_qv,forecast_temperature_qv",
+262            )
+263            .send()
+264            .await
+265            .error("weather request failed")?
+266            .json()
+267            .await
+268            .error("parsing weather data failed")?;
+269
+270        let data = data.properties.periods;
+271        let current_weather = data.first().error("No current weather")?.to_moment();
+272
+273        if !need_forecast || self.config.forecast_hours == 0 {
+274            return Ok(WeatherResult {
+275                location: location.name,
+276                current_weather,
+277                forecast: None,
+278                sunrise,
+279                sunset,
+280            });
+281        }
+282
+283        let data_agg: Vec<ForecastAggregateSegment> = data
+284            .iter()
+285            .take(self.config.forecast_hours)
+286            .map(|f| f.to_aggregate())
+287            .collect();
+288
+289        let fin = data.last().error("no weather available")?.to_moment();
+290
+291        let forecast = Some(Forecast::new(&data_agg, fin));
+292
+293        Ok(WeatherResult {
+294            location: location.name,
+295            current_weather,
+296            forecast,
+297            sunrise,
+298            sunset,
+299        })
+300    }
+301}
+302
+303/// Try to turn the short forecast into an icon.
+304///
+305/// The official API has an icon field, but it's been marked as deprecated.
+306/// Unfortunately, the short forecast cannot actually be fully enumerated, so
+307/// we're reduced to checking for the presence of specific strings.
+308fn short_forecast_to_icon(weather: &str, is_night: bool) -> WeatherIcon {
+309    let weather = weather.to_lowercase();
+310    // snow, flurries, flurry, blizzard
+311    if weather.contains("snow") || weather.contains("flurr") || weather.contains("blizzard") {
+312        return WeatherIcon::Snow;
+313    }
+314    // thunderstorms
+315    if weather.contains("thunder") {
+316        return WeatherIcon::Thunder { is_night };
+317    }
+318    // fog or mist
+319    if weather.contains("fog") || weather.contains("mist") {
+320        return WeatherIcon::Fog { is_night };
+321    }
+322    // rain, rainy, shower, drizzle (drizzle might not be present)
+323    if weather.contains("rain") || weather.contains("shower") || weather.contains("drizzle") {
+324        return WeatherIcon::Rain { is_night };
+325    }
+326    // cloudy, clouds, partly cloudy, overcast, etc.
+327    if weather.contains("cloud") || weather.contains("overcast") {
+328        return WeatherIcon::Clouds { is_night };
+329    }
+330    // clear (night), sunny (day). "Mostly sunny" / "Mostly clear" fit here too
+331    if weather.contains("clear") || weather.contains("sunny") {
+332        return WeatherIcon::Clear { is_night };
+333    }
+334    WeatherIcon::Default
+335}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/weather/open_weather_map.rs.html b/src/i3status_rs/blocks/weather/open_weather_map.rs.html new file mode 100644 index 0000000000..cc2d1aad2c --- /dev/null +++ b/src/i3status_rs/blocks/weather/open_weather_map.rs.html @@ -0,0 +1,350 @@ +open_weather_map.rs - source

i3status_rs/blocks/weather/
open_weather_map.rs

1use super::*;
+2use chrono::{DateTime, Utc};
+3use serde::{Deserializer, de};
+4
+5pub(super) const GEO_URL: &str = "https://api.openweathermap.org/geo/1.0";
+6pub(super) const CURRENT_URL: &str = "https://api.openweathermap.org/data/2.5/weather";
+7pub(super) const FORECAST_URL: &str = "https://api.openweathermap.org/data/2.5/forecast";
+8pub(super) const API_KEY_ENV: &str = "OPENWEATHERMAP_API_KEY";
+9pub(super) const CITY_ID_ENV: &str = "OPENWEATHERMAP_CITY_ID";
+10pub(super) const PLACE_ENV: &str = "OPENWEATHERMAP_PLACE";
+11pub(super) const ZIP_ENV: &str = "OPENWEATHERMAP_ZIP";
+12
+13#[derive(Deserialize, Debug, SmartDefault)]
+14#[serde(tag = "name", rename_all = "lowercase", deny_unknown_fields, default)]
+15pub struct Config {
+16    #[serde(default = "getenv_openweathermap_api_key")]
+17    api_key: Option<String>,
+18    #[serde(default = "getenv_openweathermap_city_id")]
+19    city_id: Option<String>,
+20    #[serde(default = "getenv_openweathermap_place")]
+21    place: Option<String>,
+22    #[serde(default = "getenv_openweathermap_zip")]
+23    zip: Option<String>,
+24    coordinates: Option<(String, String)>,
+25    #[serde(default)]
+26    units: UnitSystem,
+27    #[default("en")]
+28    lang: String,
+29    #[default(12)]
+30    #[serde(deserialize_with = "deserialize_forecast_hours")]
+31    forecast_hours: usize,
+32}
+33
+34pub fn deserialize_forecast_hours<'de, D>(deserializer: D) -> Result<usize, D::Error>
+35where
+36    D: Deserializer<'de>,
+37{
+38    usize::deserialize(deserializer).and_then(|hours| {
+39        if hours % 3 != 0 && hours > 120 {
+40            Err(de::Error::custom(
+41                "'forecast_hours' is not divisible by 3 and must be <= 120",
+42            ))
+43        } else if hours % 3 != 0 {
+44            Err(de::Error::custom("'forecast_hours' is not divisible by 3"))
+45        } else if hours > 120 {
+46            Err(de::Error::custom("'forecast_hours' must be <= 120"))
+47        } else {
+48            Ok(hours)
+49        }
+50    })
+51}
+52
+53pub(super) struct Service<'a> {
+54    api_key: &'a String,
+55    units: &'a UnitSystem,
+56    lang: &'a String,
+57    location_query: Option<String>,
+58    forecast_hours: usize,
+59}
+60
+61impl<'a> Service<'a> {
+62    pub(super) async fn new(autolocate: bool, config: &'a Config) -> Result<Service<'a>> {
+63        let api_key = config.api_key.as_ref().or_error(|| {
+64            format!("missing key 'service.api_key' and environment variable {API_KEY_ENV}",)
+65        })?;
+66        Ok(Self {
+67            api_key,
+68            units: &config.units,
+69            lang: &config.lang,
+70            location_query: Service::get_location_query(autolocate, api_key, config).await?,
+71            forecast_hours: config.forecast_hours,
+72        })
+73    }
+74
+75    async fn get_location_query(
+76        autolocate: bool,
+77        api_key: &String,
+78        config: &Config,
+79    ) -> Result<Option<String>> {
+80        if autolocate {
+81            return Ok(None);
+82        }
+83
+84        let mut location_query = config
+85            .coordinates
+86            .as_ref()
+87            .map(|(lat, lon)| format!("lat={lat}&lon={lon}"))
+88            .or_else(|| config.city_id.as_ref().map(|x| format!("id={x}")));
+89
+90        location_query = match location_query {
+91            Some(x) => Some(x),
+92            None => match config.place.as_ref() {
+93                Some(place) => {
+94                    let url = format!("{GEO_URL}/direct?q={place}&appid={api_key}");
+95
+96                    REQWEST_CLIENT
+97                        .get(url)
+98                        .send()
+99                        .await
+100                        .error("Geo request failed")?
+101                        .json::<Vec<CityCoord>>()
+102                        .await
+103                        .error("Geo failed to parse json")?
+104                        .first()
+105                        .map(|city| format!("lat={}&lon={}", city.lat, city.lon))
+106                }
+107                None => None,
+108            },
+109        };
+110
+111        location_query = match location_query {
+112            Some(x) => Some(x),
+113            None => match config.zip.as_ref() {
+114                Some(zip) => {
+115                    let url = format!("{GEO_URL}/zip?zip={zip}&appid={api_key}");
+116                    let city: CityCoord = REQWEST_CLIENT
+117                        .get(url)
+118                        .send()
+119                        .await
+120                        .error("Geo request failed")?
+121                        .json()
+122                        .await
+123                        .error("Geo failed to parse json")?;
+124
+125                    Some(format!("lat={}&lon={}", city.lat, city.lon))
+126                }
+127                None => None,
+128            },
+129        };
+130
+131        Ok(location_query)
+132    }
+133}
+134
+135fn getenv_openweathermap_api_key() -> Option<String> {
+136    std::env::var(API_KEY_ENV).ok()
+137}
+138fn getenv_openweathermap_city_id() -> Option<String> {
+139    std::env::var(CITY_ID_ENV).ok()
+140}
+141fn getenv_openweathermap_place() -> Option<String> {
+142    std::env::var(PLACE_ENV).ok()
+143}
+144fn getenv_openweathermap_zip() -> Option<String> {
+145    std::env::var(ZIP_ENV).ok()
+146}
+147
+148#[derive(Deserialize, Debug)]
+149struct ApiForecastResponse {
+150    list: Vec<ApiInstantResponse>,
+151}
+152
+153#[derive(Deserialize, Debug)]
+154struct ApiInstantResponse {
+155    weather: Vec<ApiWeather>,
+156    main: ApiMain,
+157    wind: ApiWind,
+158    dt: i64,
+159}
+160
+161impl ApiInstantResponse {
+162    fn wind_kmh(&self, units: &UnitSystem) -> f64 {
+163        self.wind.speed
+164            * match units {
+165                UnitSystem::Metric => 3.6,
+166                UnitSystem::Imperial => 3.6 * 0.447,
+167            }
+168    }
+169
+170    fn to_moment(&self, units: &UnitSystem, current_data: &ApiCurrentResponse) -> WeatherMoment {
+171        let is_night = current_data.sys.sunrise >= self.dt || self.dt >= current_data.sys.sunset;
+172
+173        WeatherMoment {
+174            icon: weather_to_icon(self.weather[0].main.as_str(), is_night),
+175            weather: self.weather[0].main.clone(),
+176            weather_verbose: self.weather[0].description.clone(),
+177            temp: self.main.temp,
+178            apparent: self.main.feels_like,
+179            humidity: self.main.humidity,
+180            wind: self.wind.speed,
+181            wind_kmh: self.wind_kmh(units),
+182            wind_direction: self.wind.deg,
+183        }
+184    }
+185
+186    fn to_aggregate(&self, units: &UnitSystem) -> ForecastAggregateSegment {
+187        ForecastAggregateSegment {
+188            temp: Some(self.main.temp),
+189            apparent: Some(self.main.feels_like),
+190            humidity: Some(self.main.humidity),
+191            wind: Some(self.wind.speed),
+192            wind_kmh: Some(self.wind_kmh(units)),
+193            wind_direction: self.wind.deg,
+194        }
+195    }
+196}
+197
+198#[derive(Deserialize, Debug)]
+199struct ApiCurrentResponse {
+200    #[serde(flatten)]
+201    instant: ApiInstantResponse,
+202    sys: ApiSys,
+203    name: String,
+204}
+205
+206impl ApiCurrentResponse {
+207    fn to_moment(&self, units: &UnitSystem) -> WeatherMoment {
+208        self.instant.to_moment(units, self)
+209    }
+210}
+211
+212#[derive(Deserialize, Debug)]
+213struct ApiWind {
+214    speed: f64,
+215    deg: Option<f64>,
+216}
+217
+218#[derive(Deserialize, Debug)]
+219struct ApiMain {
+220    temp: f64,
+221    feels_like: f64,
+222    humidity: f64,
+223}
+224
+225#[derive(Deserialize, Debug)]
+226struct ApiSys {
+227    sunrise: i64,
+228    sunset: i64,
+229}
+230
+231#[derive(Deserialize, Debug)]
+232struct ApiWeather {
+233    main: String,
+234    description: String,
+235}
+236
+237#[derive(Deserialize, Debug)]
+238struct CityCoord {
+239    lat: f64,
+240    lon: f64,
+241}
+242
+243#[async_trait]
+244impl WeatherProvider for Service<'_> {
+245    async fn get_weather(
+246        &self,
+247        autolocated: Option<&IPAddressInfo>,
+248        need_forecast: bool,
+249    ) -> Result<WeatherResult> {
+250        let location_query = autolocated
+251            .as_ref()
+252            .map(|al| format!("lat={}&lon={}", al.latitude, al.longitude))
+253            .or_else(|| self.location_query.clone())
+254            .error("no location was provided")?;
+255
+256        // Refer to https://openweathermap.org/current
+257        let current_url = format!(
+258            "{CURRENT_URL}?{location_query}&appid={api_key}&units={units}&lang={lang}",
+259            api_key = self.api_key,
+260            units = match self.units {
+261                UnitSystem::Metric => "metric",
+262                UnitSystem::Imperial => "imperial",
+263            },
+264            lang = self.lang,
+265        );
+266
+267        let current_data: ApiCurrentResponse = REQWEST_CLIENT
+268            .get(current_url)
+269            .send()
+270            .await
+271            .error("Current weather request failed")?
+272            .json()
+273            .await
+274            .error("Current weather request failed")?;
+275
+276        let current_weather = current_data.to_moment(self.units);
+277
+278        let sunrise = DateTime::<Utc>::from_timestamp(current_data.sys.sunrise, 0)
+279            .error("Unable to convert timestamp to DateTime")?;
+280
+281        let sunset = DateTime::<Utc>::from_timestamp(current_data.sys.sunset, 0)
+282            .error("Unable to convert timestamp to DateTime")?;
+283
+284        if !need_forecast || self.forecast_hours == 0 {
+285            return Ok(WeatherResult {
+286                location: current_data.name,
+287                current_weather,
+288                forecast: None,
+289                sunrise,
+290                sunset,
+291            });
+292        }
+293
+294        // Refer to https://openweathermap.org/forecast5
+295        let forecast_url = format!(
+296            "{FORECAST_URL}?{location_query}&appid={api_key}&units={units}&lang={lang}&cnt={cnt}",
+297            api_key = self.api_key,
+298            units = match self.units {
+299                UnitSystem::Metric => "metric",
+300                UnitSystem::Imperial => "imperial",
+301            },
+302            lang = self.lang,
+303            cnt = self.forecast_hours / 3,
+304        );
+305
+306        let forecast_data: ApiForecastResponse = REQWEST_CLIENT
+307            .get(forecast_url)
+308            .send()
+309            .await
+310            .error("Forecast weather request failed")?
+311            .json()
+312            .await
+313            .error("Forecast weather request failed")?;
+314
+315        let data_agg: Vec<ForecastAggregateSegment> = forecast_data
+316            .list
+317            .iter()
+318            .take(self.forecast_hours)
+319            .map(|f| f.to_aggregate(self.units))
+320            .collect();
+321
+322        let fin = forecast_data
+323            .list
+324            .last()
+325            .error("no weather available")?
+326            .to_moment(self.units, &current_data);
+327
+328        let forecast = Some(Forecast::new(&data_agg, fin));
+329
+330        Ok(WeatherResult {
+331            location: current_data.name,
+332            current_weather,
+333            forecast,
+334            sunrise,
+335            sunset,
+336        })
+337    }
+338}
+339
+340fn weather_to_icon(weather: &str, is_night: bool) -> WeatherIcon {
+341    match weather {
+342        "Clear" => WeatherIcon::Clear { is_night },
+343        "Rain" | "Drizzle" => WeatherIcon::Rain { is_night },
+344        "Clouds" => WeatherIcon::Clouds { is_night },
+345        "Fog" | "Mist" => WeatherIcon::Fog { is_night },
+346        "Thunderstorm" => WeatherIcon::Thunder { is_night },
+347        "Snow" => WeatherIcon::Snow,
+348        _ => WeatherIcon::Default,
+349    }
+350}
\ No newline at end of file diff --git a/src/i3status_rs/blocks/xrandr.rs.html b/src/i3status_rs/blocks/xrandr.rs.html new file mode 100644 index 0000000000..5487c1bb7c --- /dev/null +++ b/src/i3status_rs/blocks/xrandr.rs.html @@ -0,0 +1,206 @@ +xrandr.rs - source

i3status_rs/blocks/
xrandr.rs

1//! X11 screen information
+2//!
+3//! X11 screen information (name, brightness, resolution). With a click you can toggle through your active screens and with wheel up and down you can adjust the selected screens brightness. Regarding brightness control, xrandr changes the brightness of the display using gamma rather than changing the brightness in hardware, so if that is not desirable then consider using the `backlight` block instead.
+4//!
+5//! NOTE: Some users report issues (e.g. [here](https://github.com/greshake/i3status-rust/issues/274) and [here](https://github.com/greshake/i3status-rust/issues/668) when using this block. The cause is currently unknown, however setting a higher update interval may help.
+6//!
+7//! # Configuration
+8//!
+9//! Key | Values | Default
+10//! ----|--------|--------
+11//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $display $brightness_icon $brightness "`
+12//! `step_width` | The steps brightness is in/decreased for the selected screen (When greater than 50 it gets limited to 50). | `5`
+13//! `interval` | Update interval in seconds. | `5`
+14//! `invert_icons` | Invert icons' ordering, useful if you have colorful emoji | `false`
+15//!
+16//! Placeholder       | Value                        | Type   | Unit
+17//! ------------------|------------------------------|--------|---------------
+18//! `icon`            | A static icon                | Icon   | -
+19//! `display`         | The name of a monitor        | Text   | -
+20//! `brightness`      | The brightness of a monitor  | Number | %
+21//! `brightness_icon` | A static icon                | Icon   | -
+22//! `resolution`      | The resolution of a monitor  | Text   | -
+23//! `res_icon`        | A static icon                | Icon   | -
+24//!
+25//! Action            | Default button
+26//! ------------------|---------------
+27//! `cycle_outputs`   | Left
+28//! `brightness_up`   | Wheel Up
+29//! `brightness_down` | Wheel Down
+30//!
+31//! # Example
+32//!
+33//! ```toml
+34//! [[block]]
+35//! block = "xrandr"
+36//! format = " $icon $brightness $resolution "
+37//! ```
+38//!
+39//! # Used Icons
+40//! - `xrandr`
+41//! - `backlight`
+42//! - `resolution`
+43
+44use super::prelude::*;
+45use crate::subprocess::spawn_shell;
+46use regex::RegexSet;
+47use tokio::process::Command;
+48
+49#[derive(Deserialize, Debug, SmartDefault)]
+50#[serde(deny_unknown_fields, default)]
+51pub struct Config {
+52    #[default(5.into())]
+53    pub interval: Seconds,
+54    pub format: FormatConfig,
+55    #[default(5)]
+56    pub step_width: u32,
+57    pub invert_icons: bool,
+58}
+59
+60pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+61    let mut actions = api.get_actions()?;
+62    api.set_default_actions(&[
+63        (MouseButton::Left, None, "cycle_outputs"),
+64        (MouseButton::WheelUp, None, "brightness_up"),
+65        (MouseButton::WheelDown, None, "brightness_down"),
+66    ])?;
+67
+68    let format = config
+69        .format
+70        .with_default(" $icon $display $brightness_icon $brightness ")?;
+71
+72    let mut cur_indx = 0;
+73    let mut timer = config.interval.timer();
+74
+75    loop {
+76        let mut monitors = get_monitors().await?;
+77        if cur_indx > monitors.len() {
+78            cur_indx = 0;
+79        }
+80
+81        loop {
+82            let mut widget = Widget::new().with_format(format.clone());
+83
+84            if let Some(mon) = monitors.get(cur_indx) {
+85                let mut icon_value = (mon.brightness as f64) / 100.0;
+86                if config.invert_icons {
+87                    icon_value = 1.0 - icon_value;
+88                }
+89                widget.set_values(map! {
+90                    "display" => Value::text(mon.name.clone()),
+91                    "brightness" => Value::percents(mon.brightness),
+92                    "brightness_icon" => Value::icon_progression("backlight", icon_value),
+93                    "resolution" => Value::text(mon.resolution.clone()),
+94                    "icon" => Value::icon("xrandr"),
+95                    "res_icon" => Value::icon("resolution"),
+96                });
+97            }
+98            api.set_widget(widget)?;
+99
+100            select! {
+101                _ = timer.tick() => break,
+102                _ = api.wait_for_update_request() => break,
+103                Some(action) = actions.recv() => match action.as_ref() {
+104                    "cycle_outputs" => {
+105                        cur_indx = (cur_indx + 1) % monitors.len();
+106                    }
+107                    "brightness_up" => {
+108                        if let Some(monitor) = monitors.get_mut(cur_indx) {
+109                            let bright = (monitor.brightness + config.step_width).min(100);
+110                            monitor.set_brightness(bright);
+111                        }
+112                    }
+113                    "brightness_down" => {
+114                        if let Some(monitor) = monitors.get_mut(cur_indx) {
+115                            let bright = monitor.brightness.saturating_sub(config.step_width);
+116                            monitor.set_brightness(bright);
+117                        }
+118                    }
+119                    _ => (),
+120                }
+121            }
+122        }
+123    }
+124}
+125
+126struct Monitor {
+127    name: String,
+128    brightness: u32,
+129    resolution: String,
+130}
+131
+132impl Monitor {
+133    fn set_brightness(&mut self, brightness: u32) {
+134        let _ = spawn_shell(&format!(
+135            "xrandr --output {} --brightness  {}",
+136            self.name,
+137            brightness as f64 / 100.0
+138        ));
+139        self.brightness = brightness;
+140    }
+141}
+142
+143async fn get_monitors() -> Result<Vec<Monitor>> {
+144    let mut monitors = Vec::new();
+145
+146    let active_monitors = Command::new("xrandr")
+147        .arg("--listactivemonitors")
+148        .output()
+149        .await
+150        .error("Failed to collect active xrandr monitors")?
+151        .stdout;
+152    let active_monitors =
+153        String::from_utf8(active_monitors).error("xrandr produced non-UTF8 output")?;
+154
+155    let regex = active_monitors
+156        .lines()
+157        .filter_map(|line| line.split_ascii_whitespace().last())
+158        .map(|name| format!("{name} connected"))
+159        .chain(Some("Brightness:".into()));
+160    let regex = RegexSet::new(regex).error("Failed to create RegexSet")?;
+161
+162    let monitors_info = Command::new("xrandr")
+163        .arg("--verbose")
+164        .output()
+165        .await
+166        .error("Failed to collect xrandr monitors info")?
+167        .stdout;
+168    let monitors_info =
+169        String::from_utf8(monitors_info).error("xrandr produced non-UTF8 output")?;
+170
+171    let mut it = monitors_info.lines().filter(|line| regex.is_match(line));
+172
+173    while let Some(line1) = it.next()
+174        && let Some(line2) = it.next()
+175    {
+176        let mut tokens = line1.split_ascii_whitespace().peekable();
+177        let name = tokens.next().error("Failed to parse xrandr output")?.into();
+178        let _ = tokens.next();
+179
+180        // The output may be "<name> connected <resolution>" or "<name> connected primary <resolution>"
+181        let _ = tokens.next_if_eq(&"primary");
+182
+183        let resolution = tokens
+184            .next()
+185            .and_then(|x| x.split('+').next())
+186            .error("Failed to parse xrandr output")?
+187            .into();
+188
+189        let brightness = (line2
+190            .split(':')
+191            .nth(1)
+192            .error("Failed to parse xrandr output")?
+193            .trim()
+194            .parse::<f64>()
+195            .error("Failed to parse xrandr output")?
+196            * 100.0) as u32;
+197
+198        monitors.push(Monitor {
+199            name,
+200            brightness,
+201            resolution,
+202        });
+203    }
+204
+205    Ok(monitors)
+206}
\ No newline at end of file diff --git a/src/i3status_rs/click.rs.html b/src/i3status_rs/click.rs.html new file mode 100644 index 0000000000..2acbf6a771 --- /dev/null +++ b/src/i3status_rs/click.rs.html @@ -0,0 +1,160 @@ +click.rs - source

i3status_rs/
click.rs

1use std::fmt;
+2
+3use serde::Deserialize;
+4use serde::de::{self, Deserializer, Visitor};
+5
+6use crate::errors::{ErrorContext as _, Result};
+7use crate::protocol::i3bar_event::I3BarEvent;
+8use crate::subprocess::{spawn_shell, spawn_shell_sync};
+9use crate::wrappers::SerdeRegex;
+10
+11/// Can be one of `left`, `middle`, `right`, `up`/`wheel_up`, `down`/`wheel_down`, `wheel_left`, `wheel_right`, `forward`, `back` or `double_left`.
+12///
+13/// Note that in order for double clicks to be registered, you have to set `double_click_delay` to a
+14/// non-zero value. `200` might be a good choice. Note that enabling this functionality will
+15/// make left clicks less responsive and feel a bit laggy.
+16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+17pub enum MouseButton {
+18    Left,
+19    Middle,
+20    Right,
+21    WheelUp,
+22    WheelDown,
+23    WheelLeft,
+24    WheelRight,
+25    Forward,
+26    Back,
+27    DoubleLeft,
+28}
+29
+30#[derive(Debug, Clone)]
+31pub struct PostActions {
+32    pub action: Option<String>,
+33    pub update: bool,
+34}
+35
+36#[derive(Deserialize, Debug, Clone, Default)]
+37pub struct ClickHandler(Vec<ClickConfigEntry>);
+38
+39impl ClickHandler {
+40    pub async fn handle(&self, event: &I3BarEvent) -> Result<Option<PostActions>> {
+41        let Some(entry) = self
+42            .0
+43            .iter()
+44            .filter(|e| e.button == event.button)
+45            .find(|e| match &e.widget {
+46                None => event.instance.is_none(),
+47                Some(re) => re.0.is_match(event.instance.as_deref().unwrap_or("block")),
+48            })
+49        else {
+50            return Ok(None);
+51        };
+52
+53        if let Some(cmd) = &entry.cmd {
+54            if entry.sync {
+55                spawn_shell_sync(cmd).await
+56            } else {
+57                spawn_shell(cmd)
+58            }
+59            .or_error(|| format!("'{:?}' button handler: Failed to run '{cmd}", event.button))?;
+60        }
+61
+62        Ok(Some(PostActions {
+63            action: entry.action.clone(),
+64            update: entry.update,
+65        }))
+66    }
+67}
+68
+69#[derive(Deserialize, Debug, Clone)]
+70#[serde(deny_unknown_fields)]
+71pub struct ClickConfigEntry {
+72    /// Which button to handle
+73    button: MouseButton,
+74    /// To which part of the block this entry applies
+75    #[serde(default)]
+76    widget: Option<SerdeRegex>,
+77    /// Which command to run
+78    #[serde(default)]
+79    cmd: Option<String>,
+80    /// Which block action to trigger
+81    #[serde(default)]
+82    action: Option<String>,
+83    /// Whether to wait for command to exit or not (default is `false`)
+84    #[serde(default)]
+85    sync: bool,
+86    /// Whether to update the block on click (default is `false`)
+87    #[serde(default)]
+88    update: bool,
+89}
+90
+91impl<'de> Deserialize<'de> for MouseButton {
+92    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+93    where
+94        D: Deserializer<'de>,
+95    {
+96        struct MouseButtonVisitor;
+97
+98        impl Visitor<'_> for MouseButtonVisitor {
+99            type Value = MouseButton;
+100
+101            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+102                formatter.write_str("button as int or string")
+103            }
+104
+105            // ```toml
+106            // button = "left"
+107            // ```
+108            fn visit_str<E>(self, name: &str) -> Result<MouseButton, E>
+109            where
+110                E: de::Error,
+111            {
+112                use MouseButton::*;
+113                Ok(match name {
+114                    "left" => Left,
+115                    "middle" => Middle,
+116                    "right" => Right,
+117                    "up" | "wheel_up" => WheelUp,
+118                    "down" | "wheel_down" => WheelDown,
+119                    "wheel_left" => WheelLeft,
+120                    "wheel_right" => WheelRight,
+121                    "forward" => Forward,
+122                    "back" => Back,
+123                    // Experimental
+124                    "double_left" => DoubleLeft,
+125                    other => return Err(E::custom(format!("unknown button '{other}'"))),
+126                })
+127            }
+128
+129            // ```toml
+130            // button = 1
+131            // ```
+132            fn visit_i64<E>(self, number: i64) -> Result<MouseButton, E>
+133            where
+134                E: de::Error,
+135            {
+136                use MouseButton::*;
+137                Ok(match number {
+138                    1 => Left,
+139                    2 => Middle,
+140                    3 => Right,
+141                    4 => WheelUp,
+142                    5 => WheelDown,
+143                    6 => WheelLeft,
+144                    7 => WheelRight,
+145                    8 => Back,
+146                    9 => Forward,
+147                    other => return Err(E::custom(format!("unknown button '{other}'"))),
+148                })
+149            }
+150            fn visit_u64<E>(self, number: u64) -> Result<MouseButton, E>
+151            where
+152                E: de::Error,
+153            {
+154                self.visit_i64(number as i64)
+155            }
+156        }
+157
+158        deserializer.deserialize_any(MouseButtonVisitor)
+159    }
+160}
\ No newline at end of file diff --git a/src/i3status_rs/config.rs.html b/src/i3status_rs/config.rs.html new file mode 100644 index 0000000000..795723e786 --- /dev/null +++ b/src/i3status_rs/config.rs.html @@ -0,0 +1,122 @@ +config.rs - source

i3status_rs/
config.rs

1use serde::{Deserialize, Deserializer};
+2use smart_default::SmartDefault;
+3use std::collections::HashMap;
+4use std::sync::Arc;
+5
+6use crate::blocks::BlockConfig;
+7use crate::click::ClickHandler;
+8use crate::errors::*;
+9use crate::formatting::config::Config as FormatConfig;
+10use crate::geolocator::Geolocator;
+11use crate::icons::{Icon, Icons};
+12use crate::themes::{Theme, ThemeOverrides, ThemeUserConfig};
+13
+14#[derive(Deserialize, Debug)]
+15#[serde(deny_unknown_fields)]
+16pub struct Config {
+17    #[serde(flatten)]
+18    pub shared: SharedConfig,
+19
+20    /// Set to `true` to invert mouse wheel direction
+21    #[serde(default)]
+22    pub invert_scrolling: bool,
+23
+24    #[serde(default)]
+25    pub geolocator: Arc<Geolocator>,
+26
+27    /// The maximum delay (ms) between two clicks that are considered as double click
+28    #[serde(default)]
+29    pub double_click_delay: u64,
+30
+31    #[serde(default = "default_error_format")]
+32    pub error_format: FormatConfig,
+33    #[serde(default = "default_error_fullscreen")]
+34    pub error_fullscreen_format: FormatConfig,
+35
+36    #[serde(default)]
+37    #[serde(rename = "block")]
+38    pub blocks: Vec<BlockConfigEntry>,
+39}
+40
+41#[derive(Deserialize, Debug, Clone)]
+42pub struct SharedConfig {
+43    #[serde(default)]
+44    #[serde(deserialize_with = "deserialize_theme_config")]
+45    pub theme: Arc<Theme>,
+46    #[serde(default)]
+47    pub icons: Arc<Icons>,
+48    #[serde(default = "default_icons_format")]
+49    pub icons_format: Arc<String>,
+50}
+51
+52impl Default for SharedConfig {
+53    fn default() -> Self {
+54        Self {
+55            theme: Default::default(),
+56            icons: Default::default(),
+57            icons_format: default_icons_format(),
+58        }
+59    }
+60}
+61
+62fn default_error_format() -> FormatConfig {
+63    " {$short_error_message|X} ".parse().unwrap()
+64}
+65
+66fn default_error_fullscreen() -> FormatConfig {
+67    " $full_error_message ".parse().unwrap()
+68}
+69
+70fn default_icons_format() -> Arc<String> {
+71    Arc::new("{icon}".into())
+72}
+73
+74impl SharedConfig {
+75    pub fn get_icon(&self, icon: &str, value: Option<f64>) -> Result<String> {
+76        if icon.is_empty() {
+77            Ok(String::new())
+78        } else {
+79            Ok(self.icons_format.replace(
+80                "{icon}",
+81                self.icons
+82                    .get(icon, value)
+83                    .or_error(|| format!("Icon '{icon}' not found"))?,
+84            ))
+85        }
+86    }
+87}
+88
+89#[derive(Deserialize, Debug)]
+90pub struct BlockConfigEntry {
+91    #[serde(flatten)]
+92    pub common: CommonBlockConfig,
+93    #[serde(flatten)]
+94    pub config: BlockConfig,
+95}
+96
+97#[derive(Deserialize, Debug, SmartDefault)]
+98#[serde(default)]
+99pub struct CommonBlockConfig {
+100    pub click: ClickHandler,
+101    pub signal: Option<i32>,
+102    pub icons_format: Option<String>,
+103    pub theme_overrides: Option<ThemeOverrides>,
+104    pub icons_overrides: Option<HashMap<String, Icon>>,
+105    pub merge_with_next: bool,
+106
+107    #[default(5)]
+108    pub error_interval: u64,
+109    pub error_format: FormatConfig,
+110    pub error_fullscreen_format: FormatConfig,
+111
+112    pub if_command: Option<String>,
+113}
+114
+115fn deserialize_theme_config<'de, D>(deserializer: D) -> Result<Arc<Theme>, D::Error>
+116where
+117    D: Deserializer<'de>,
+118{
+119    let theme_config = ThemeUserConfig::deserialize(deserializer)?;
+120    let theme = Theme::try_from(theme_config).serde_error()?;
+121    Ok(Arc::new(theme))
+122}
\ No newline at end of file diff --git a/src/i3status_rs/errors.rs.html b/src/i3status_rs/errors.rs.html new file mode 100644 index 0000000000..dc92f517d9 --- /dev/null +++ b/src/i3status_rs/errors.rs.html @@ -0,0 +1,112 @@ +errors.rs - source

i3status_rs/
errors.rs

1use std::borrow::Cow;
+2use std::fmt;
+3use std::sync::Arc;
+4
+5pub use std::error::Error as StdError;
+6
+7/// Result type returned from functions that can have our `Error`s.
+8pub type Result<T, E = Error> = std::result::Result<T, E>;
+9
+10type ErrorMsg = Cow<'static, str>;
+11
+12/// Error type
+13#[derive(Debug, Clone)]
+14pub struct Error {
+15    pub message: Option<ErrorMsg>,
+16    pub cause: Option<Arc<dyn StdError + Send + Sync + 'static>>,
+17}
+18
+19impl Error {
+20    pub fn new<T: Into<ErrorMsg>>(message: T) -> Self {
+21        Self {
+22            message: Some(message.into()),
+23            cause: None,
+24        }
+25    }
+26}
+27
+28pub trait ErrorContext<T> {
+29    fn error<M: Into<ErrorMsg>>(self, message: M) -> Result<T>;
+30    fn or_error<M: Into<ErrorMsg>, F: FnOnce() -> M>(self, f: F) -> Result<T>;
+31}
+32
+33impl<T, E: StdError + Send + Sync + 'static> ErrorContext<T> for Result<T, E> {
+34    fn error<M: Into<ErrorMsg>>(self, message: M) -> Result<T> {
+35        self.map_err(|e| Error {
+36            message: Some(message.into()),
+37            cause: Some(Arc::new(e)),
+38        })
+39    }
+40
+41    fn or_error<M: Into<ErrorMsg>, F: FnOnce() -> M>(self, f: F) -> Result<T> {
+42        self.map_err(|e| Error {
+43            message: Some(f().into()),
+44            cause: Some(Arc::new(e)),
+45        })
+46    }
+47}
+48
+49impl<T> ErrorContext<T> for Option<T> {
+50    fn error<M: Into<ErrorMsg>>(self, message: M) -> Result<T> {
+51        self.ok_or_else(|| Error {
+52            message: Some(message.into()),
+53            cause: None,
+54        })
+55    }
+56
+57    fn or_error<M: Into<ErrorMsg>, F: FnOnce() -> M>(self, f: F) -> Result<T> {
+58        self.ok_or_else(|| Error {
+59            message: Some(f().into()),
+60            cause: None,
+61        })
+62    }
+63}
+64
+65impl fmt::Display for Error {
+66    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+67        f.write_str(self.message.as_deref().unwrap_or("Error"))?;
+68
+69        if let Some(cause) = &self.cause {
+70            write!(f, ". Cause: {cause}")?;
+71        }
+72
+73        Ok(())
+74    }
+75}
+76
+77impl From<Error> for zbus::fdo::Error {
+78    fn from(err: Error) -> Self {
+79        Self::Failed(err.to_string())
+80    }
+81}
+82
+83impl StdError for Error {}
+84
+85pub trait ToSerdeError<T> {
+86    fn serde_error<E: serde::de::Error>(self) -> Result<T, E>;
+87}
+88
+89impl<T, F> ToSerdeError<T> for Result<T, F>
+90where
+91    F: fmt::Display,
+92{
+93    fn serde_error<E: serde::de::Error>(self) -> Result<T, E> {
+94        self.map_err(E::custom)
+95    }
+96}
+97
+98pub struct BoxErrorWrapper(pub Box<dyn StdError + Send + Sync + 'static>);
+99
+100impl fmt::Debug for BoxErrorWrapper {
+101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+102        fmt::Debug::fmt(&self.0, f)
+103    }
+104}
+105
+106impl fmt::Display for BoxErrorWrapper {
+107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+108        fmt::Display::fmt(&self.0, f)
+109    }
+110}
+111
+112impl StdError for BoxErrorWrapper {}
\ No newline at end of file diff --git a/src/i3status_rs/escape.rs.html b/src/i3status_rs/escape.rs.html new file mode 100644 index 0000000000..a8fa18ce01 --- /dev/null +++ b/src/i3status_rs/escape.rs.html @@ -0,0 +1,81 @@ +escape.rs - source

i3status_rs/
escape.rs

1//! Simple json escaping
+2
+3use std::fmt::Write;
+4
+5use unicode_segmentation::UnicodeSegmentation as _;
+6
+7pub trait CollectEscaped {
+8    /// Write escaped version of `self` to `out`
+9    fn collect_pango_escaped_into<T: Write>(self, out: &mut T);
+10
+11    /// Write escaped version of `self` to a new buffer
+12    #[inline]
+13    fn collect_pango_escaped<T: Write + Default>(self) -> T
+14    where
+15        Self: Sized,
+16    {
+17        let mut out = T::default();
+18        self.collect_pango_escaped_into(&mut out);
+19        out
+20    }
+21}
+22
+23impl<I, R> CollectEscaped for I
+24where
+25    I: Iterator<Item = R>,
+26    R: AsRef<str>,
+27{
+28    fn collect_pango_escaped_into<T: Write>(self, out: &mut T) {
+29        for c in self {
+30            let _ = match c.as_ref() {
+31                "&" => out.write_str("&amp;"),
+32                "<" => out.write_str("&lt;"),
+33                ">" => out.write_str("&gt;"),
+34                "'" => out.write_str("&#39;"),
+35                x => out.write_str(x),
+36            };
+37        }
+38    }
+39}
+40
+41pub trait Escaped {
+42    /// Write escaped version of `self` to `out`
+43    fn pango_escaped_into<T: Write>(self, out: &mut T);
+44
+45    /// Write escaped version of `self` to a new buffer
+46    #[inline]
+47    fn pango_escaped<T: Write + Default>(self) -> T
+48    where
+49        Self: Sized,
+50    {
+51        let mut out = T::default();
+52        self.pango_escaped_into(&mut out);
+53        out
+54    }
+55}
+56
+57impl<R: AsRef<str>> Escaped for R {
+58    fn pango_escaped_into<T: Write>(self, out: &mut T) {
+59        self.as_ref()
+60            .split_word_bounds()
+61            .collect_pango_escaped_into(out);
+62    }
+63}
+64
+65#[cfg(test)]
+66mod tests {
+67    use super::*;
+68
+69    #[test]
+70    fn collect_pango() {
+71        let orig = "&my 'text' <a̐>";
+72        let escaped: String = orig.graphemes(true).collect_pango_escaped();
+73        assert_eq!(escaped, "&amp;my &#39;text&#39; &lt;a̐&gt;");
+74    }
+75    #[test]
+76    fn pango() {
+77        let orig = "&my 'text' <a̐>";
+78        let escaped: String = orig.pango_escaped();
+79        assert_eq!(escaped, "&amp;my &#39;text&#39; &lt;a̐&gt;");
+80    }
+81}
\ No newline at end of file diff --git a/src/i3status_rs/formatting.rs.html b/src/i3status_rs/formatting.rs.html new file mode 100644 index 0000000000..a6880b7eaa --- /dev/null +++ b/src/i3status_rs/formatting.rs.html @@ -0,0 +1,235 @@ +formatting.rs - source

i3status_rs/
formatting.rs

1//! # Formatting system
+2//! Many blocks have a `format` configuration option, which allows to heavily customize the block's
+3//! appearance. In short, each block with `format` option provides a set of values, which are
+4//! displayed according to `format`. `format`'s value is just a text with embedded variables.
+5//! Similarly to PHP and shell, variable name must start with a `$`:
+6//! `this is a variable: -> $var <-`.
+7//!
+8//! Also, format strings can embed icons. For example, `^icon_ping` in `" ^icon_ping $ping "` gets
+9//! substituted with a "ping" icon from your icon set. For a complete list of icons, see
+10//! [this](https://github.com/greshake/i3status-rust/blob/master/doc/themes.md#available-icon-overrides).
+11//!
+12//! # Types
+13//!
+14//! The allowed types of variables are:
+15//!
+16//! Type                      | Default formatter
+17//! --------------------------|------------------
+18//! Text                      | `str`
+19//! Number                    | `eng`
+20//! Datetime                  | `datetime`
+21//! Duration                  | `duration`
+22//! [Flag](#how-to-use-flags) | N/A
+23//!
+24//! # Formatters
+25//!
+26//! A formatter is something that converts a value into a text. Because there are many ways to do
+27//! this, a number of formatters is available. Formatter can be specified using the syntax similar
+28//! to method calls in many programming languages: `<variable>.<formatter>(<args>)`. For example:
+29//! `$title.str(min_w:10, max_w:20)`.
+30//!
+31//! Note: for arguments that accept a boolean value, just specifying the argument will be treated as `arg:true`.
+32//!
+33//! ## `str` - Format text
+34//!
+35//! Argument               | Description                                       |Default value
+36//! -----------------------|---------------------------------------------------|-------------
+37//! `min_width` or `min_w` | if text is shorter it will be padded using spaces | `0`
+38//! `max_width` or `max_w` | if text is longer it will be truncated            | Infinity
+39//! `width` or `w`         | Text will be exactly this length by padding or truncating as needed | N/A
+40//! `rot_interval`         | if text is longer than `max_width` it will be rotated every `rot_interval` seconds, if set | None
+41//! `rot_separator`        | if text is longer than `max_width` it will be rotated with this seporator | <code>\"\|\"</code>
+42//!
+43//! Note: width just changes the values of both min_width and max_width to be the same. Use width
+44//! if you want the values to be the same, or the other two otherwise. Don't mix width with
+45//! min_width or max_width.
+46//!
+47//! ## `eng` - Format numbers using engineering notation
+48//!
+49//! Argument        | Description                                                                                      |Default value
+50//! ----------------|--------------------------------------------------------------------------------------------------|-------------
+51//! `width` or `w`  | the resulting text will be at least `width` characters long                                      | `2`
+52//! `unit` or `u`   | some values have a [unit](unit::Unit), and it is possible to convert them by setting this option | N/A
+53//! `hide_unit`     | hide the unit symbol                                                                             | `false`
+54//! `unit_space`    | have a whitespace before unit symbol                                                             | `false`
+55//! `prefix` or `p` | specify this argument if you want to set the minimal [SI prefix](prefix::Prefix)                 | N/A
+56//! `hide_prefix`   | hide the prefix symbol                                                                           | `false`
+57//! `prefix_space`  | have a whitespace before prefix symbol                                                           | `false`
+58//! `force_prefix`  | force the prefix value instead of setting a "minimal prefix"                                     | `false`
+59//! `pad_with`      | the character that is used to pad the number to be `width` long                                  | ` ` (a space)
+60//! `range`         | a range of allowed values, in the format `<start>..<end>`, inclusive. Both start and end are optional. Can be used to, for example, hide the block when the value is not in a given range. | `..`
+61//! `show`          | show this value. Can be used with `range` for conditional formatting                             | `true`
+62//!
+63//! ## `bar` - Display numbers as progress bars
+64//!
+65//! Argument               | Description                                                                     |Default value
+66//! -----------------------|---------------------------------------------------------------------------------|-------------------------
+67//! `width` or `w`         | the width of the bar (in characters)                                            | `5` (`1` for `vertical`)
+68//! `max_value`            | which value is treated as "full". For example, for battery level `100` is full. | `100`
+69//! `vertical` or `v`      | whether to render the bar vertically or not                                     | `false`
+70//!
+71//! ## `tally` - Display numbers as tally marks
+72//!
+73//! Argument       | Description                                                                                                                                                                     |Default value
+74//! ---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------
+75//! `style` or `s` | One of [`chinese_counting_rods`/`ccr`](https://en.wikipedia.org/wiki/Counting_rods), [`chinese_tally`/`ct`, `western_tally`/`wt`, `western_tally_ungrouped`/`wtu`](https://en.wikipedia.org/wiki/Tally_marks) | western_tally
+76//!
+77//! ## `pango-str` - Just display the text without pango markup escaping
+78//!
+79//! No arguments.
+80//!
+81//! ## `datetime` - Display datetime
+82//!
+83//! Argument               | Description                                                                                               |Default value
+84//! -----------------------|-----------------------------------------------------------------------------------------------------------|-------------
+85//! `format` or `f`        | [chrono docs](https://docs.rs/chrono/0.3.0/chrono/format/strftime/index.html#specifiers) for all options. | `'%a %d/%m %R'`
+86//! `locale` or `l`        | Locale to apply when formatting the time                                                                  | System locale
+87//!
+88//!
+89//! ## `duration`/`dur` - Format durations
+90//!
+91//! Argument                     | Description                                                                                      |Default value
+92//! -----------------|--------------------------------------------------------------------------------------------------|------------------------------------------------------
+93//! `hms`            | Should the format be hours:minutes:seconds.milliseconds                                          | `false`
+94//! `max_unit`       | The largest unit to display the duration with (see below for the list of all possible units)     | hms ? `h` : `y`
+95//! `min_unit`       | The smallest unit to display the duration with (see below for the list of all possible units)    | `s`
+96//! `units`          | The number of units to display                                                                   | min(# of units between `max_unit` and `min_unit``, 2)
+97//! `round_up`       | Round up to the nearest minimum displayed unit                                                   | `true`
+98//! `unit_space`     | Should there be a space between the value and unit symbol (not allowed when `hms:true`)          | `false`
+99//! `pad_with`       | The character that is used to pad the numbers                                                    | hms ? `0` : ` ` (a space)
+100//! `leading_zeroes` | If fewer than `units` are non-zero should leading numbers that have a value of zero be shown     | `true`
+101//!
+102//! Unit | Description
+103//! -----|------------
+104//! y    | years
+105//! w    | weeks
+106//! d    | days
+107//! h    | hours
+108//! m    | minutes
+109//! s    | seconds
+110//! ms   | milliseconds
+111//!
+112//! # Handling missing placeholders and incorrect types
+113//!
+114//! Some blocks allow missing placeholders, for example [bluetooth](crate::blocks::bluetooth)'s
+115//! "percentage" may be absent if the device is not supported. To handle such cases it is possible
+116//! to queue multiple formats together by using `|` symbol: `<something that can fail>|<otherwise
+117//! try this>|<or this>`.
+118//!
+119//! In addition, formats can be recursive. To set a format inside of another format, place it
+120//! inside of `{}`. For example, in `Percentage: {$percentage|N/A}` the text "Percentage: " will be
+121//! always displayed, followed by the actual percentage or "N/A" in case percentage is not
+122//! available. This example does exactly the same thing as `Percentage: $percentage|Percentage: N/A`
+123//!
+124//! # How to use flags
+125//!
+126//! Some blocks provide flags, which can be used to change the format based on some criteria. For
+127//! example, [taskwarrior](crate::blocks::taskwarrior) defines `done` if the count is zero. In
+128//! general, flags are used in this way:
+129//!
+130//! ```text
+131//! $a{a is set}|$b$c{b and c are set}|${b|c}{b or c is set}|neither flag is set
+132//! ```
+133
+134pub mod config;
+135pub mod formatter;
+136pub mod parse;
+137pub mod prefix;
+138pub mod scheduling;
+139pub mod template;
+140pub mod unit;
+141pub mod value;
+142
+143use std::borrow::Cow;
+144use std::collections::HashMap;
+145
+146use crate::config::SharedConfig;
+147use crate::errors::*;
+148use template::FormatTemplate;
+149use value::Value;
+150
+151pub type Values = HashMap<Cow<'static, str>, Value>;
+152
+153#[derive(Debug, thiserror::Error)]
+154pub enum FormatError {
+155    #[error("Placeholder '{0}' not found")]
+156    PlaceholderNotFound(String),
+157    #[error("{} cannot be formatted with '{}' formatter", .ty, .fmt)]
+158    IncompatibleFormatter { ty: &'static str, fmt: &'static str },
+159    #[error("Number {0} is out of range")]
+160    NumberOutOfRange(f64),
+161    #[error(transparent)]
+162    Other(#[from] Error),
+163}
+164
+165#[derive(Debug, Clone)]
+166pub struct Format {
+167    full: FormatTemplate,
+168    short: FormatTemplate,
+169    intervals: Vec<u64>,
+170}
+171
+172impl Format {
+173    pub fn contains_key(&self, key: &str) -> bool {
+174        self.full.contains_key(key) || self.short.contains_key(key)
+175    }
+176
+177    pub fn intervals(&self) -> Vec<u64> {
+178        self.intervals.clone()
+179    }
+180
+181    pub fn render(
+182        &self,
+183        values: &Values,
+184        config: &SharedConfig,
+185    ) -> Result<(Vec<Fragment>, Vec<Fragment>)> {
+186        let full = self
+187            .full
+188            .render(values, config)
+189            .error("Failed to render full text")?;
+190        let short = self
+191            .short
+192            .render(values, config)
+193            .error("Failed to render short text")?;
+194        Ok((full, short))
+195    }
+196}
+197
+198#[derive(Debug, Default, Clone)]
+199pub struct Fragment {
+200    pub text: String,
+201    pub metadata: Metadata,
+202}
+203
+204impl From<String> for Fragment {
+205    fn from(text: String) -> Self {
+206        Self {
+207            text,
+208            metadata: Default::default(),
+209        }
+210    }
+211}
+212
+213impl Fragment {
+214    pub fn formatted_text(&self) -> String {
+215        match (self.metadata.italic, self.metadata.underline) {
+216            (true, true) => format!("<i><u>{}</u></i>", self.text),
+217            (false, true) => format!("<u>{}</u>", self.text),
+218            (true, false) => format!("<i>{}</i>", self.text),
+219            (false, false) => self.text.clone(),
+220        }
+221    }
+222}
+223
+224#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
+225pub struct Metadata {
+226    pub instance: Option<&'static str>,
+227    pub underline: bool,
+228    pub italic: bool,
+229}
+230
+231impl Metadata {
+232    pub fn is_default(&self) -> bool {
+233        *self == Default::default()
+234    }
+235}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/config.rs.html b/src/i3status_rs/formatting/config.rs.html new file mode 100644 index 0000000000..2eb29c33dd --- /dev/null +++ b/src/i3status_rs/formatting/config.rs.html @@ -0,0 +1,165 @@ +config.rs - source

i3status_rs/formatting/
config.rs

1use super::{Format, template::FormatTemplate};
+2use crate::errors::*;
+3use serde::de::{MapAccess, Visitor};
+4use serde::{Deserialize, Deserializer, de};
+5use std::fmt;
+6use std::str::FromStr;
+7
+8#[derive(Debug, Default, Clone)]
+9pub struct Config {
+10    pub full: Option<FormatTemplate>,
+11    pub short: Option<FormatTemplate>,
+12}
+13
+14impl Config {
+15    pub fn with_default(&self, default_full: &str) -> Result<Format> {
+16        self.with_defaults(default_full, "")
+17    }
+18
+19    pub fn with_defaults(&self, default_full: &str, default_short: &str) -> Result<Format> {
+20        let full = match self.full.clone() {
+21            Some(full) => full,
+22            None => default_full.parse()?,
+23        };
+24
+25        let short = match self.short.clone() {
+26            Some(short) => short,
+27            None => default_short.parse()?,
+28        };
+29
+30        let mut intervals = Vec::new();
+31        full.init_intervals(&mut intervals);
+32        short.init_intervals(&mut intervals);
+33
+34        Ok(Format {
+35            full,
+36            short,
+37            intervals,
+38        })
+39    }
+40
+41    pub fn with_default_config(&self, default_config: &Self) -> Format {
+42        let full = self
+43            .full
+44            .clone()
+45            .or_else(|| default_config.full.clone())
+46            .unwrap_or_default();
+47        let short = self
+48            .short
+49            .clone()
+50            .or_else(|| default_config.short.clone())
+51            .unwrap_or_default();
+52
+53        let mut intervals = Vec::new();
+54        full.init_intervals(&mut intervals);
+55        short.init_intervals(&mut intervals);
+56
+57        Format {
+58            full,
+59            short,
+60            intervals,
+61        }
+62    }
+63
+64    pub fn with_default_format(&self, default_format: &Format) -> Format {
+65        let full = self
+66            .full
+67            .clone()
+68            .unwrap_or_else(|| default_format.full.clone());
+69        let short = self
+70            .short
+71            .clone()
+72            .unwrap_or_else(|| default_format.short.clone());
+73
+74        let mut intervals = Vec::new();
+75        full.init_intervals(&mut intervals);
+76        short.init_intervals(&mut intervals);
+77
+78        Format {
+79            full,
+80            short,
+81            intervals,
+82        }
+83    }
+84}
+85
+86impl FromStr for Config {
+87    type Err = Error;
+88
+89    fn from_str(s: &str) -> Result<Self, Self::Err> {
+90        Ok(Self {
+91            full: Some(s.parse()?),
+92            short: None,
+93        })
+94    }
+95}
+96
+97impl<'de> Deserialize<'de> for Config {
+98    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+99    where
+100        D: Deserializer<'de>,
+101    {
+102        #[derive(Deserialize)]
+103        #[serde(field_identifier, rename_all = "lowercase")]
+104        enum Field {
+105            Full,
+106            Short,
+107        }
+108
+109        struct FormatTemplateVisitor;
+110
+111        impl<'de> Visitor<'de> for FormatTemplateVisitor {
+112            type Value = Config;
+113
+114            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+115                formatter.write_str("format structure")
+116            }
+117
+118            /// Handle configs like:
+119            ///
+120            /// ```toml
+121            /// format = "{layout}"
+122            /// ```
+123            fn visit_str<E>(self, full: &str) -> Result<Config, E>
+124            where
+125                E: de::Error,
+126            {
+127                full.parse().serde_error()
+128            }
+129
+130            /// Handle configs like:
+131            ///
+132            /// ```toml
+133            /// [block.format]
+134            /// full = "{layout}"
+135            /// short = "{layout^2}"
+136            /// ```
+137            fn visit_map<V>(self, mut map: V) -> Result<Config, V::Error>
+138            where
+139                V: MapAccess<'de>,
+140            {
+141                let mut full: Option<FormatTemplate> = None;
+142                let mut short: Option<FormatTemplate> = None;
+143                while let Some(key) = map.next_key()? {
+144                    match key {
+145                        Field::Full => {
+146                            if full.is_some() {
+147                                return Err(de::Error::duplicate_field("full"));
+148                            }
+149                            full = Some(map.next_value::<String>()?.parse().serde_error()?);
+150                        }
+151                        Field::Short => {
+152                            if short.is_some() {
+153                                return Err(de::Error::duplicate_field("short"));
+154                            }
+155                            short = Some(map.next_value::<String>()?.parse().serde_error()?);
+156                        }
+157                    }
+158                }
+159                Ok(Config { full, short })
+160            }
+161        }
+162
+163        deserializer.deserialize_any(FormatTemplateVisitor)
+164    }
+165}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/formatter.rs.html b/src/i3status_rs/formatting/formatter.rs.html new file mode 100644 index 0000000000..8dc0a47a0d --- /dev/null +++ b/src/i3status_rs/formatting/formatter.rs.html @@ -0,0 +1,66 @@ +formatter.rs - source

i3status_rs/formatting/
formatter.rs

1use unicode_segmentation::UnicodeSegmentation as _;
+2
+3use std::time::Duration;
+4use std::{borrow::Cow, fmt::Debug};
+5
+6use super::FormatError;
+7use super::parse::Arg;
+8use super::value::ValueInner as Value;
+9use crate::config::SharedConfig;
+10use crate::errors::*;
+11
+12// A helper macro for testing formatters
+13#[cfg(test)]
+14#[macro_export]
+15macro_rules! new_fmt {
+16    ($name:ident) => {{
+17        new_fmt!($name,)
+18    }};
+19    ($name:ident, $($key:ident : $value:tt),* $(,)?) => {
+20        new_formatter(stringify!($name), &[
+21            $( Arg { key: stringify!($key), val: Some(stringify!($value)) } ),*
+22        ])
+23    };
+24}
+25
+26mod bar;
+27pub use bar::BarFormatter;
+28mod tally;
+29pub use tally::TallyFormatter;
+30mod datetime;
+31pub use datetime::{DEFAULT_DATETIME_FORMATTER, DatetimeFormatter};
+32mod duration;
+33pub use duration::{DEFAULT_DURATION_FORMATTER, DurationFormatter};
+34mod eng;
+35pub use eng::{DEFAULT_NUMBER_FORMATTER, EngFormatter};
+36mod flag;
+37pub use flag::{DEFAULT_FLAG_FORMATTER, FlagFormatter};
+38mod pango;
+39pub use pango::PangoStrFormatter;
+40mod str;
+41pub use str::{DEFAULT_STRING_FORMATTER, StrFormatter};
+42
+43type PadWith = Cow<'static, str>;
+44
+45const DEFAULT_NUMBER_PAD_WITH: PadWith = Cow::Borrowed(" ");
+46
+47pub trait Formatter: Debug + Send + Sync {
+48    fn format(&self, val: &Value, config: &SharedConfig) -> Result<String, FormatError>;
+49
+50    fn interval(&self) -> Option<Duration> {
+51        None
+52    }
+53}
+54
+55pub fn new_formatter(name: &str, args: &[Arg]) -> Result<Box<dyn Formatter>> {
+56    match name {
+57        "bar" => Ok(Box::new(BarFormatter::from_args(args)?)),
+58        "datetime" => Ok(Box::new(DatetimeFormatter::from_args(args)?)),
+59        "dur" | "duration" => Ok(Box::new(DurationFormatter::from_args(args)?)),
+60        "eng" => Ok(Box::new(EngFormatter::from_args(args)?)),
+61        "pango-str" => Ok(Box::new(PangoStrFormatter::from_args(args)?)),
+62        "str" => Ok(Box::new(StrFormatter::from_args(args)?)),
+63        "tally" => Ok(Box::new(TallyFormatter::from_args(args)?)),
+64        _ => Err(Error::new(format!("Unknown formatter: '{name}'"))),
+65    }
+66}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/formatter/bar.rs.html b/src/i3status_rs/formatting/formatter/bar.rs.html new file mode 100644 index 0000000000..0d214f70cc --- /dev/null +++ b/src/i3status_rs/formatting/formatter/bar.rs.html @@ -0,0 +1,81 @@ +bar.rs - source

i3status_rs/formatting/formatter/
bar.rs

1use super::*;
+2
+3const DEFAULT_BAR_VERTICAL: bool = false;
+4const DEFAULT_BAR_WIDTH_HORIZONTAL: usize = 5;
+5const DEFAULT_BAR_WIDTH_VERTICAL: usize = 1;
+6const DEFAULT_BAR_MAX_VAL: f64 = 100.0;
+7
+8#[derive(Debug)]
+9pub struct BarFormatter {
+10    width: usize,
+11    max_value: f64,
+12    vertical: bool,
+13}
+14
+15impl BarFormatter {
+16    pub(super) fn from_args(args: &[Arg]) -> Result<Self> {
+17        let mut vertical = DEFAULT_BAR_VERTICAL;
+18        let mut width = None;
+19        let mut max_value = DEFAULT_BAR_MAX_VAL;
+20        for arg in args {
+21            match arg.key {
+22                "width" | "w" => {
+23                    width = Some(arg.parse_value()?);
+24                }
+25                "max_value" => {
+26                    max_value = arg.parse_value()?;
+27                }
+28                "vertical" | "v" => {
+29                    vertical = arg.parse_value()?;
+30                }
+31                other => {
+32                    return Err(Error::new(format!("Unknown argument for 'bar': '{other}'")));
+33                }
+34            }
+35        }
+36        Ok(Self {
+37            width: width.unwrap_or(match vertical {
+38                false => DEFAULT_BAR_WIDTH_HORIZONTAL,
+39                true => DEFAULT_BAR_WIDTH_VERTICAL,
+40            }),
+41            max_value,
+42            vertical,
+43        })
+44    }
+45}
+46
+47const HORIZONTAL_BAR_CHARS: [char; 9] = [
+48    ' ', '\u{258f}', '\u{258e}', '\u{258d}', '\u{258c}', '\u{258b}', '\u{258a}', '\u{2589}',
+49    '\u{2588}',
+50];
+51
+52const VERTICAL_BAR_CHARS: [char; 9] = [
+53    ' ', '\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}',
+54    '\u{2588}',
+55];
+56
+57impl Formatter for BarFormatter {
+58    fn format(&self, val: &Value, _config: &SharedConfig) -> Result<String, FormatError> {
+59        match val {
+60            &Value::Number { mut val, .. } => {
+61                val = (val / self.max_value).clamp(0., 1.);
+62                if self.vertical {
+63                    let vert_char = VERTICAL_BAR_CHARS[(val * 8.) as usize];
+64                    Ok((0..self.width).map(|_| vert_char).collect())
+65                } else {
+66                    let chars_to_fill = val * self.width as f64;
+67                    Ok((0..self.width)
+68                        .map(|i| {
+69                            HORIZONTAL_BAR_CHARS
+70                                [((chars_to_fill - i as f64).clamp(0., 1.) * 8.) as usize]
+71                        })
+72                        .collect())
+73                }
+74            }
+75            other => Err(FormatError::IncompatibleFormatter {
+76                ty: other.type_name(),
+77                fmt: "bar",
+78            }),
+79        }
+80    }
+81}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/formatter/datetime.rs.html b/src/i3status_rs/formatting/formatter/datetime.rs.html new file mode 100644 index 0000000000..73cb91dd08 --- /dev/null +++ b/src/i3status_rs/formatting/formatter/datetime.rs.html @@ -0,0 +1,198 @@ +datetime.rs - source

i3status_rs/formatting/formatter/
datetime.rs

1use chrono::format::{Fixed, Item, StrftimeItems};
+2use chrono::{DateTime, Local, Locale, TimeZone};
+3use chrono_tz::{OffsetName as _, Tz};
+4
+5use std::fmt::Display;
+6use std::sync::LazyLock;
+7
+8use super::*;
+9
+10make_log_macro!(error, "datetime");
+11
+12const DEFAULT_DATETIME_FORMAT: &str = "%a %d/%m %R";
+13
+14pub static DEFAULT_DATETIME_FORMATTER: LazyLock<DatetimeFormatter> =
+15    LazyLock::new(|| DatetimeFormatter::new(Some(DEFAULT_DATETIME_FORMAT), None).unwrap());
+16
+17#[derive(Debug)]
+18pub enum DatetimeFormatter {
+19    Chrono {
+20        items: Vec<Item<'static>>,
+21        locale: Option<Locale>,
+22    },
+23    #[cfg(feature = "icu_calendar")]
+24    Icu {
+25        length: icu_datetime::options::length::Date,
+26        locale: icu_locid::Locale,
+27    },
+28}
+29
+30impl DatetimeFormatter {
+31    pub(super) fn from_args(args: &[Arg]) -> Result<Self> {
+32        let mut format = None;
+33        let mut locale = None;
+34        for arg in args {
+35            match arg.key {
+36                "format" | "f" => {
+37                    format = Some(arg.val.error("format must be specified")?);
+38                }
+39                "locale" | "l" => {
+40                    locale = Some(arg.val.error("locale must be specified")?);
+41                }
+42                other => {
+43                    return Err(Error::new(format!(
+44                        "Unknown argument for 'datetime': '{other}'"
+45                    )));
+46                }
+47            }
+48        }
+49        Self::new(format, locale)
+50    }
+51
+52    fn new(format: Option<&str>, locale: Option<&str>) -> Result<Self> {
+53        let (items, locale) = match locale {
+54            Some(locale) => {
+55                #[cfg(feature = "icu_calendar")]
+56                let Ok(locale) = locale.try_into() else {
+57                    use std::str::FromStr as _;
+58                    // try with icu4x
+59                    let locale = icu_locid::Locale::from_str(locale)
+60                        .ok()
+61                        .error("invalid locale")?;
+62                    let length = match format {
+63                        Some("full") => icu_datetime::options::length::Date::Full,
+64                        None | Some("long") => icu_datetime::options::length::Date::Long,
+65                        Some("medium") => icu_datetime::options::length::Date::Medium,
+66                        Some("short") => icu_datetime::options::length::Date::Short,
+67                        _ => return Err(Error::new("Unknown format option for icu based locale")),
+68                    };
+69                    return Ok(Self::Icu { locale, length });
+70                };
+71                #[cfg(not(feature = "icu_calendar"))]
+72                let locale = locale.try_into().ok().error("invalid locale")?;
+73                (
+74                    StrftimeItems::new_with_locale(
+75                        format.unwrap_or(DEFAULT_DATETIME_FORMAT),
+76                        locale,
+77                    ),
+78                    Some(locale),
+79                )
+80            }
+81            None => (
+82                StrftimeItems::new(format.unwrap_or(DEFAULT_DATETIME_FORMAT)),
+83                None,
+84            ),
+85        };
+86
+87        Ok(Self::Chrono {
+88            items: items.parse_to_owned().error(format!(
+89                "Invalid format: \"{}\"",
+90                format.unwrap_or(DEFAULT_DATETIME_FORMAT)
+91            ))?,
+92            locale,
+93        })
+94    }
+95}
+96
+97pub(crate) trait TimezoneName {
+98    fn timezone_name(datetime: &DateTime<Self>) -> Result<Item<'_>>
+99    where
+100        Self: TimeZone;
+101}
+102
+103impl TimezoneName for Tz {
+104    fn timezone_name(datetime: &DateTime<Tz>) -> Result<Item<'_>> {
+105        Ok(Item::Literal(
+106            datetime
+107                .offset()
+108                .abbreviation()
+109                .error("Timezone name unknown")?,
+110        ))
+111    }
+112}
+113
+114impl TimezoneName for Local {
+115    fn timezone_name(datetime: &DateTime<Local>) -> Result<Item<'_>> {
+116        let tz_name = iana_time_zone::get_timezone().error("Could not get local timezone")?;
+117        let tz = tz_name
+118            .parse::<Tz>()
+119            .error("Could not parse local timezone")?;
+120        Tz::timezone_name(&datetime.with_timezone(&tz)).map(|x| x.to_owned())
+121    }
+122}
+123
+124fn borrow_item<'a>(item: &'a Item) -> Item<'a> {
+125    match item {
+126        Item::Literal(s) => Item::Literal(s),
+127        Item::OwnedLiteral(s) => Item::Literal(s),
+128        Item::Space(s) => Item::Space(s),
+129        Item::OwnedSpace(s) => Item::Space(s),
+130        Item::Numeric(n, p) => Item::Numeric(n.clone(), *p),
+131        Item::Fixed(f) => Item::Fixed(f.clone()),
+132        Item::Error => Item::Error,
+133    }
+134}
+135
+136impl Formatter for DatetimeFormatter {
+137    fn format(&self, val: &Value, _config: &SharedConfig) -> Result<String, FormatError> {
+138        #[allow(clippy::unnecessary_wraps)]
+139        fn for_generic_datetime<T>(
+140            this: &DatetimeFormatter,
+141            datetime: DateTime<T>,
+142        ) -> Result<String, FormatError>
+143        where
+144            T: TimeZone + TimezoneName,
+145            T::Offset: Display,
+146        {
+147            Ok(match this {
+148                DatetimeFormatter::Chrono { items, locale } => {
+149                    let new_items = items.iter().map(|item| match item {
+150                        Item::Fixed(Fixed::TimezoneName) => match T::timezone_name(&datetime) {
+151                            Ok(name) => name,
+152                            Err(e) => {
+153                                error!("{e}");
+154                                Item::Fixed(Fixed::TimezoneName)
+155                            }
+156                        },
+157                        item => borrow_item(item),
+158                    });
+159                    match *locale {
+160                        Some(locale) => datetime
+161                            .format_localized_with_items(new_items, locale)
+162                            .to_string(),
+163                        None => datetime.format_with_items(new_items).to_string(),
+164                    }
+165                }
+166                #[cfg(feature = "icu_calendar")]
+167                DatetimeFormatter::Icu { locale, length } => {
+168                    use chrono::Datelike as _;
+169                    let date = icu_calendar::Date::try_new_iso_date(
+170                        datetime.year(),
+171                        datetime.month() as u8,
+172                        datetime.day() as u8,
+173                    )
+174                    .ok()
+175                    .error("Current date should be a valid date")?;
+176                    let date = date.to_any();
+177                    let dft =
+178                        icu_datetime::DateFormatter::try_new_with_length(&locale.into(), *length)
+179                            .ok()
+180                            .error("locale should be present in compiled data")?;
+181                    dft.format_to_string(&date)
+182                        .ok()
+183                        .error("formatting date using icu failed")?
+184                }
+185            })
+186        }
+187        match val {
+188            Value::Datetime(datetime, timezone) => match timezone {
+189                Some(tz) => for_generic_datetime(self, datetime.with_timezone(tz)),
+190                None => for_generic_datetime(self, datetime.with_timezone(&Local)),
+191            },
+192            other => Err(FormatError::IncompatibleFormatter {
+193                ty: other.type_name(),
+194                fmt: "datetime",
+195            }),
+196        }
+197    }
+198}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/formatter/duration.rs.html b/src/i3status_rs/formatting/formatter/duration.rs.html new file mode 100644 index 0000000000..cfc61714e8 --- /dev/null +++ b/src/i3status_rs/formatting/formatter/duration.rs.html @@ -0,0 +1,513 @@ +duration.rs - source

i3status_rs/formatting/formatter/
duration.rs

1use std::cmp::min;
+2
+3use super::*;
+4
+5const UNIT_COUNT: usize = 7;
+6const UNITS: [&str; UNIT_COUNT] = ["y", "w", "d", "h", "m", "s", "ms"];
+7const UNIT_CONVERSION_RATES: [u128; UNIT_COUNT] = [
+8    31_556_952_000, // Based on there being 365.2425 days/year
+9    604_800_000,
+10    86_400_000,
+11    3_600_000,
+12    60_000,
+13    1_000,
+14    1,
+15];
+16const UNIT_PAD_WIDTHS: [usize; UNIT_COUNT] = [1, 2, 1, 2, 2, 2, 3];
+17
+18pub const DEFAULT_DURATION_FORMATTER: DurationFormatter = DurationFormatter {
+19    hms: false,
+20    max_unit_index: 0,
+21    min_unit_index: 5,
+22    units: 2,
+23    round_up: true,
+24    unit_has_space: false,
+25    pad_with: DEFAULT_NUMBER_PAD_WITH,
+26    leading_zeroes: true,
+27};
+28
+29#[derive(Debug, Default)]
+30pub struct DurationFormatter {
+31    hms: bool,
+32    max_unit_index: usize,
+33    min_unit_index: usize,
+34    units: usize,
+35    round_up: bool,
+36    unit_has_space: bool,
+37    pad_with: PadWith,
+38    leading_zeroes: bool,
+39}
+40
+41impl DurationFormatter {
+42    pub(super) fn from_args(args: &[Arg]) -> Result<Self> {
+43        let mut hms = false;
+44        let mut max_unit = None;
+45        let mut min_unit = "s";
+46        let mut units: Option<usize> = None;
+47        let mut round_up = true;
+48        let mut unit_has_space = false;
+49        let mut pad_with = None;
+50        let mut leading_zeroes = true;
+51        for arg in args {
+52            match arg.key {
+53                "hms" => {
+54                    hms = arg.parse_value()?;
+55                }
+56                "max_unit" => {
+57                    max_unit = Some(arg.val.error("max_unit must be specified")?);
+58                }
+59                "min_unit" => {
+60                    min_unit = arg.val.error("min_unit must be specified")?;
+61                }
+62                "units" => {
+63                    units = Some(arg.parse_value()?);
+64                }
+65                "round_up" => {
+66                    round_up = arg.parse_value()?;
+67                }
+68                "unit_space" => {
+69                    unit_has_space = arg.parse_value()?;
+70                }
+71                "pad_with" => {
+72                    let pad_with_str = arg.val.error("pad_with must be specified")?;
+73                    if pad_with_str.graphemes(true).count() < 2 {
+74                        pad_with = Some(Cow::Owned(pad_with_str.into()));
+75                    } else {
+76                        return Err(Error::new(
+77                            "pad_with must be an empty string or a single character",
+78                        ));
+79                    };
+80                }
+81                "leading_zeroes" => {
+82                    leading_zeroes = arg.parse_value()?;
+83                }
+84
+85                _ => return Err(Error::new(format!("Unexpected argument {:?}", arg.key))),
+86            }
+87        }
+88
+89        if hms && unit_has_space {
+90            return Err(Error::new(
+91                "When hms is enabled unit_space should not be true",
+92            ));
+93        }
+94
+95        let max_unit = max_unit.unwrap_or(if hms { "h" } else { "y" });
+96        let pad_with = pad_with.unwrap_or(if hms {
+97            Cow::Borrowed("0")
+98        } else {
+99            DEFAULT_NUMBER_PAD_WITH
+100        });
+101
+102        let max_unit_index = UNITS
+103            .iter()
+104            .position(|&x| x == max_unit)
+105            .error("max_unit must be one of \"y\", \"w\", \"d\", \"h\", \"m\", \"s\", or \"ms\"")?;
+106
+107        let min_unit_index = UNITS
+108            .iter()
+109            .position(|&x| x == min_unit)
+110            .error("min_unit must be one of \"y\", \"w\", \"d\", \"h\", \"m\", \"s\", or \"ms\"")?;
+111
+112        if hms && max_unit_index < 3 {
+113            return Err(Error::new(
+114                "When hms is enabled the max unit must be h,m,s,ms",
+115            ));
+116        }
+117
+118        // UNITS are sorted largest to smallest
+119        if min_unit_index < max_unit_index {
+120            return Err(Error::new(format!(
+121                "min_unit({min_unit}) must be smaller than or equal to max_unit({max_unit})",
+122            )));
+123        }
+124
+125        let units_upper_bound = min_unit_index - max_unit_index + 1;
+126        let units = units.unwrap_or_else(|| min(units_upper_bound, 2));
+127
+128        if units > units_upper_bound {
+129            return Err(Error::new(format!(
+130                "there aren't {units} units between min_unit({min_unit}) and max_unit({max_unit})",
+131            )));
+132        }
+133
+134        Ok(Self {
+135            hms,
+136            max_unit_index,
+137            min_unit_index,
+138            units,
+139            round_up,
+140            unit_has_space,
+141            pad_with,
+142            leading_zeroes,
+143        })
+144    }
+145
+146    fn get_time_parts(&self, mut ms: u128) -> Vec<(usize, u128)> {
+147        let mut should_push = false;
+148        // A Vec of the unit index and value pairs
+149        let mut v = Vec::with_capacity(self.units);
+150        for (i, div) in UNIT_CONVERSION_RATES[self.max_unit_index..=self.min_unit_index]
+151            .iter()
+152            .enumerate()
+153        {
+154            // Offset i by the offset used to slice UNIT_CONVERSION_RATES
+155            let index = i + self.max_unit_index;
+156            let value = ms / div;
+157
+158            // Only add the non-zero, unless we want to display the leading units of time with value of zero.
+159            // For example we want to have a minimum unit of seconds but to always show two values we could have:
+160            // " 0m 15s"
+161            if !should_push {
+162                should_push = value != 0
+163                    || (self.leading_zeroes && index >= self.min_unit_index + 1 - self.units);
+164            }
+165
+166            if should_push {
+167                v.push((index, value));
+168                // We have the right number of values/units
+169                if v.len() == self.units {
+170                    break;
+171                }
+172            }
+173            ms %= div;
+174        }
+175
+176        v
+177    }
+178}
+179
+180impl Formatter for DurationFormatter {
+181    fn format(&self, val: &Value, _config: &SharedConfig) -> Result<String, FormatError> {
+182        match val {
+183            Value::Duration(duration) => {
+184                let mut v = self.get_time_parts(duration.as_millis());
+185
+186                if self.round_up {
+187                    // Get the index for which unit we should round up to
+188                    let i = v.last().map_or(self.min_unit_index, |&(i, _)| i);
+189                    v = self.get_time_parts(duration.as_millis() + UNIT_CONVERSION_RATES[i] - 1);
+190                }
+191
+192                let mut first_entry = true;
+193                let mut result = String::new();
+194                for (i, value) in v {
+195                    // No separator before the first entry
+196                    if !first_entry {
+197                        if self.hms {
+198                            // Separator between s and ms should be a '.'
+199                            if i == 6 {
+200                                result.push('.');
+201                            } else {
+202                                result.push(':');
+203                            }
+204                        } else {
+205                            result.push(' ');
+206                        }
+207                    } else {
+208                        first_entry = false;
+209                    }
+210
+211                    // Pad the value
+212                    let value_str = value.to_string();
+213                    for _ in value_str.len()..UNIT_PAD_WIDTHS[i] {
+214                        result.push_str(&self.pad_with);
+215                    }
+216                    result.push_str(&value_str);
+217
+218                    // No units in hms mode
+219                    if !self.hms {
+220                        if self.unit_has_space {
+221                            result.push(' ');
+222                        }
+223                        result.push_str(UNITS[i]);
+224                    }
+225                }
+226
+227                Ok(result)
+228            }
+229            other => Err(FormatError::IncompatibleFormatter {
+230                ty: other.type_name(),
+231                fmt: "duration",
+232            }),
+233        }
+234    }
+235}
+236
+237#[cfg(test)]
+238mod tests {
+239    use super::*;
+240
+241    macro_rules! dur {
+242        ($($key:ident : $value:expr),*) => {{
+243            let mut ms = 0;
+244            $(
+245            let unit = stringify!($key);
+246            ms += $value
+247                * (UNIT_CONVERSION_RATES[UNITS
+248                    .iter()
+249                    .position(|&x| x == unit)
+250                    .expect("unit must be one of \"y\", \"w\", \"d\", \"h\", \"m\", \"s\", or \"ms\"")]
+251                    as u64);
+252            )*
+253           Value::Duration(std::time::Duration::from_millis(ms))
+254        }};
+255    }
+256
+257    #[test]
+258    fn dur_default_single_unit() {
+259        let config = SharedConfig::default();
+260        let fmt = new_fmt!(dur).unwrap();
+261
+262        let result = fmt.format(&dur!(y:1), &config).unwrap();
+263        assert_eq!(result, "1y  0w");
+264
+265        let result = fmt.format(&dur!(w:1), &config).unwrap();
+266        assert_eq!(result, " 1w 0d");
+267
+268        let result = fmt.format(&dur!(d:1), &config).unwrap();
+269        assert_eq!(result, "1d  0h");
+270
+271        let result = fmt.format(&dur!(h:1), &config).unwrap();
+272        assert_eq!(result, " 1h  0m");
+273
+274        let result = fmt.format(&dur!(m:1), &config).unwrap();
+275        assert_eq!(result, " 1m  0s");
+276
+277        let result = fmt.format(&dur!(s:1), &config).unwrap();
+278        assert_eq!(result, " 0m  1s");
+279
+280        //This is rounded to 1s since min_unit is 's' and round_up is true
+281        let result = fmt.format(&dur!(ms:1), &config).unwrap();
+282        assert_eq!(result, " 0m  1s");
+283    }
+284
+285    #[test]
+286    fn dur_default_consecutive_units() {
+287        let config = SharedConfig::default();
+288        let fmt = new_fmt!(dur).unwrap();
+289
+290        let result = fmt.format(&dur!(y:1, w:2), &config).unwrap();
+291        assert_eq!(result, "1y  2w");
+292
+293        let result = fmt.format(&dur!(w:1, d:2), &config).unwrap();
+294        assert_eq!(result, " 1w 2d");
+295
+296        let result = fmt.format(&dur!(d:1, h:2), &config).unwrap();
+297        assert_eq!(result, "1d  2h");
+298
+299        let result = fmt.format(&dur!(h:1, m:2), &config).unwrap();
+300        assert_eq!(result, " 1h  2m");
+301
+302        let result = fmt.format(&dur!(m:1, s:2), &config).unwrap();
+303        assert_eq!(result, " 1m  2s");
+304
+305        //This is rounded to 2s since min_unit is 's' and round_up is true
+306        let result = fmt.format(&dur!(s:1, ms:2), &config).unwrap();
+307        assert_eq!(result, " 0m  2s");
+308    }
+309
+310    #[test]
+311    fn dur_hms_no_ms() {
+312        let config = SharedConfig::default();
+313        let fmt = new_fmt!(dur, hms:true, min_unit:s).unwrap();
+314
+315        let result = fmt.format(&dur!(d:1, h:2), &config).unwrap();
+316        assert_eq!(result, "26:00");
+317
+318        let result = fmt.format(&dur!(h:1, m:2), &config).unwrap();
+319        assert_eq!(result, "01:02");
+320
+321        let result = fmt.format(&dur!(m:1, s:2), &config).unwrap();
+322        assert_eq!(result, "01:02");
+323
+324        //This is rounded to 2s since min_unit is 's' and round_up is true
+325        let result = fmt.format(&dur!(s:1, ms:2), &config).unwrap();
+326        assert_eq!(result, "00:02");
+327    }
+328
+329    #[test]
+330    fn dur_hms_with_ms() {
+331        let config = SharedConfig::default();
+332        let fmt = new_fmt!(dur, hms:true, min_unit:ms).unwrap();
+333
+334        let result = fmt.format(&dur!(d:1, h:2), &config).unwrap();
+335        assert_eq!(result, "26:00");
+336
+337        let result = fmt.format(&dur!(h:1, m:2), &config).unwrap();
+338        assert_eq!(result, "01:02");
+339
+340        let result = fmt.format(&dur!(m:1, s:2), &config).unwrap();
+341        assert_eq!(result, "01:02");
+342
+343        let result = fmt.format(&dur!(s:1, ms:2), &config).unwrap();
+344        assert_eq!(result, "01.002");
+345    }
+346
+347    #[test]
+348    fn dur_round_up_true() {
+349        let config = SharedConfig::default();
+350        let fmt = new_fmt!(dur, round_up:true).unwrap();
+351
+352        let result = fmt.format(&dur!(y:1, ms:1), &config).unwrap();
+353        assert_eq!(result, "1y  1w");
+354
+355        let result = fmt.format(&dur!(w:1, ms:1), &config).unwrap();
+356        assert_eq!(result, " 1w 1d");
+357
+358        let result = fmt.format(&dur!(d:1, ms:1), &config).unwrap();
+359        assert_eq!(result, "1d  1h");
+360
+361        let result = fmt.format(&dur!(h:1, ms:1), &config).unwrap();
+362        assert_eq!(result, " 1h  1m");
+363
+364        let result = fmt.format(&dur!(m:1, ms:1), &config).unwrap();
+365        assert_eq!(result, " 1m  1s");
+366
+367        //This is rounded to 2s since min_unit is 's' and round_up is true
+368        let result = fmt.format(&dur!(s:1, ms:1), &config).unwrap();
+369        assert_eq!(result, " 0m  2s");
+370    }
+371
+372    #[test]
+373    fn dur_units() {
+374        let config = SharedConfig::default();
+375        let val = dur!(y:1, w:2, d:3, h:4, m:5, s:6, ms:7);
+376
+377        let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 1).unwrap();
+378        let result = fmt.format(&val, &config).unwrap();
+379        assert_eq!(result, "1y");
+380
+381        let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 2).unwrap();
+382        let result = fmt.format(&val, &config).unwrap();
+383        assert_eq!(result, "1y  2w");
+384
+385        let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 3).unwrap();
+386        let result = fmt.format(&val, &config).unwrap();
+387        assert_eq!(result, "1y  2w 3d");
+388
+389        let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 4).unwrap();
+390        let result = fmt.format(&val, &config).unwrap();
+391        assert_eq!(result, "1y  2w 3d  4h");
+392
+393        let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 5).unwrap();
+394        let result = fmt.format(&val, &config).unwrap();
+395        assert_eq!(result, "1y  2w 3d  4h  5m");
+396
+397        let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 6).unwrap();
+398        let result = fmt.format(&val, &config).unwrap();
+399        assert_eq!(result, "1y  2w 3d  4h  5m  6s");
+400
+401        let fmt = new_fmt!(dur, round_up:false, min_unit:ms, units: 7).unwrap();
+402        let result = fmt.format(&val, &config).unwrap();
+403        assert_eq!(result, "1y  2w 3d  4h  5m  6s   7ms");
+404    }
+405
+406    #[test]
+407    fn dur_round_up_false() {
+408        let config = SharedConfig::default();
+409        let fmt = new_fmt!(dur, round_up:false).unwrap();
+410
+411        let result = fmt.format(&dur!(y:1, ms:1), &config).unwrap();
+412        assert_eq!(result, "1y  0w");
+413
+414        let result = fmt.format(&dur!(w:1, ms:1), &config).unwrap();
+415        assert_eq!(result, " 1w 0d");
+416
+417        let result = fmt.format(&dur!(d:1, ms:1), &config).unwrap();
+418        assert_eq!(result, "1d  0h");
+419
+420        let result = fmt.format(&dur!(h:1, ms:1), &config).unwrap();
+421        assert_eq!(result, " 1h  0m");
+422
+423        let result = fmt.format(&dur!(m:1, ms:1), &config).unwrap();
+424        assert_eq!(result, " 1m  0s");
+425
+426        let result = fmt.format(&dur!(s:1, ms:1), &config).unwrap();
+427        assert_eq!(result, " 0m  1s");
+428
+429        let result = fmt.format(&dur!(ms:1), &config).unwrap();
+430        assert_eq!(result, " 0m  0s");
+431    }
+432
+433    #[test]
+434    fn dur_invalid_config_hms_and_unit_space() {
+435        let fmt_err = new_fmt!(dur, hms:true, unit_space:true).unwrap_err();
+436        assert_eq!(
+437            fmt_err.message,
+438            Some("When hms is enabled unit_space should not be true".into())
+439        );
+440    }
+441
+442    #[test]
+443    fn dur_invalid_config_invalid_unit() {
+444        let fmt_err = new_fmt!(dur, max_unit:does_not_exist).unwrap_err();
+445        assert_eq!(
+446            fmt_err.message,
+447            Some(
+448                "max_unit must be one of \"y\", \"w\", \"d\", \"h\", \"m\", \"s\", or \"ms\""
+449                    .into()
+450            )
+451        );
+452
+453        let fmt_err = new_fmt!(dur, min_unit:does_not_exist).unwrap_err();
+454        assert_eq!(
+455            fmt_err.message,
+456            Some(
+457                "min_unit must be one of \"y\", \"w\", \"d\", \"h\", \"m\", \"s\", or \"ms\""
+458                    .into()
+459            )
+460        );
+461    }
+462
+463    #[test]
+464    fn dur_invalid_config_hms_max_unit_too_large() {
+465        let fmt_err = new_fmt!(dur, max_unit:d, hms:true).unwrap_err();
+466        assert_eq!(
+467            fmt_err.message,
+468            Some("When hms is enabled the max unit must be h,m,s,ms".into())
+469        );
+470    }
+471
+472    #[test]
+473    fn dur_invalid_config_min_larger_than_max() {
+474        let fmt = new_fmt!(dur, max_unit:h, min_unit:h);
+475        assert!(fmt.is_ok());
+476
+477        let fmt_err = new_fmt!(dur, max_unit:h, min_unit:d).unwrap_err();
+478        assert_eq!(
+479            fmt_err.message,
+480            Some("min_unit(d) must be smaller than or equal to max_unit(h)".into())
+481        );
+482    }
+483
+484    #[test]
+485    fn dur_invalid_config_too_many_units() {
+486        let fmt = new_fmt!(dur, max_unit:y, min_unit:s, units:6);
+487        assert!(fmt.is_ok());
+488
+489        let fmt_err = new_fmt!(dur, max_unit:y, min_unit:s, units:7).unwrap_err();
+490        assert_eq!(
+491            fmt_err.message,
+492            Some("there aren't 7 units between min_unit(s) and max_unit(y)".into())
+493        );
+494
+495        let fmt = new_fmt!(dur, max_unit:w, min_unit:s, units:5);
+496        assert!(fmt.is_ok());
+497
+498        let fmt_err = new_fmt!(dur, max_unit:w, min_unit:s, units:6).unwrap_err();
+499        assert_eq!(
+500            fmt_err.message,
+501            Some("there aren't 6 units between min_unit(s) and max_unit(w)".into())
+502        );
+503
+504        let fmt = new_fmt!(dur, max_unit:y, min_unit:ms, units:7);
+505        assert!(fmt.is_ok());
+506
+507        let fmt_err = new_fmt!(dur, max_unit:y, min_unit:ms, units:8).unwrap_err();
+508        assert_eq!(
+509            fmt_err.message,
+510            Some("there aren't 8 units between min_unit(ms) and max_unit(y)".into())
+511        );
+512    }
+513}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/formatter/eng.rs.html b/src/i3status_rs/formatting/formatter/eng.rs.html new file mode 100644 index 0000000000..f753e22d90 --- /dev/null +++ b/src/i3status_rs/formatting/formatter/eng.rs.html @@ -0,0 +1,302 @@ +eng.rs - source

i3status_rs/formatting/formatter/
eng.rs

1use crate::formatting::prefix::Prefix;
+2use crate::formatting::unit::Unit;
+3
+4use std::borrow::Cow;
+5use std::ops::RangeInclusive;
+6
+7use super::*;
+8
+9const DEFAULT_NUMBER_WIDTH: usize = 2;
+10
+11pub const DEFAULT_NUMBER_FORMATTER: EngFormatter = EngFormatter {
+12    show: true,
+13    width: DEFAULT_NUMBER_WIDTH,
+14    unit: None,
+15    unit_has_space: false,
+16    unit_hidden: false,
+17    prefix: None,
+18    prefix_has_space: false,
+19    prefix_hidden: false,
+20    prefix_forced: false,
+21    pad_with: DEFAULT_NUMBER_PAD_WITH,
+22    range: f64::NEG_INFINITY..=f64::INFINITY,
+23};
+24
+25#[derive(Debug)]
+26pub struct EngFormatter {
+27    show: bool,
+28    width: usize,
+29    unit: Option<Unit>,
+30    unit_has_space: bool,
+31    unit_hidden: bool,
+32    prefix: Option<Prefix>,
+33    prefix_has_space: bool,
+34    prefix_hidden: bool,
+35    prefix_forced: bool,
+36    pad_with: PadWith,
+37    range: RangeInclusive<f64>,
+38}
+39
+40impl EngFormatter {
+41    pub(super) fn from_args(args: &[Arg]) -> Result<Self> {
+42        let mut result = DEFAULT_NUMBER_FORMATTER;
+43
+44        for arg in args {
+45            match arg.key {
+46                "width" | "w" => {
+47                    result.width = arg.parse_value()?;
+48                }
+49                "unit" | "u" => {
+50                    result.unit = Some(arg.parse_value()?);
+51                }
+52                "hide_unit" => {
+53                    result.unit_hidden = arg.parse_value()?;
+54                }
+55                "unit_space" => {
+56                    result.unit_has_space = arg.parse_value()?;
+57                }
+58                "prefix" | "p" => {
+59                    result.prefix = Some(arg.parse_value()?);
+60                }
+61                "hide_prefix" => {
+62                    result.prefix_hidden = arg.parse_value()?;
+63                }
+64                "prefix_space" => {
+65                    result.prefix_has_space = arg.parse_value()?;
+66                }
+67                "force_prefix" => {
+68                    result.prefix_forced = arg.parse_value()?;
+69                }
+70                "pad_with" => {
+71                    let pad_with_str = arg.val.error("pad_with must be specified")?;
+72                    if pad_with_str.graphemes(true).count() < 2 {
+73                        result.pad_with = Cow::Owned(pad_with_str.into());
+74                    } else {
+75                        return Err(Error::new(
+76                            "pad_with must be an empty string or a single character",
+77                        ));
+78                    }
+79                }
+80                "range" => {
+81                    let (start, end) = arg
+82                        .val
+83                        .error("range must be specified")?
+84                        .split_once("..")
+85                        .error("invalid range")?;
+86                    if !start.is_empty() {
+87                        result.range = start.parse::<f64>().error("invalid range start")?
+88                            ..=*result.range.end();
+89                    }
+90                    if !end.is_empty() {
+91                        result.range = *result.range.start()
+92                            ..=end.parse::<f64>().error("invalid range end")?;
+93                    }
+94                }
+95                "show" => {
+96                    result.show = arg.parse_value()?;
+97                }
+98                other => {
+99                    return Err(Error::new(format!("Unknown argument for 'eng': '{other}'")));
+100                }
+101            }
+102        }
+103
+104        Ok(result)
+105    }
+106}
+107
+108impl Formatter for EngFormatter {
+109    fn format(&self, val: &Value, _config: &SharedConfig) -> Result<String, FormatError> {
+110        match val {
+111            &Value::Number { mut val, mut unit } => {
+112                if !self.range.contains(&val) {
+113                    return Err(FormatError::NumberOutOfRange(val));
+114                }
+115
+116                if !self.show {
+117                    return Ok(String::new());
+118                }
+119
+120                let is_negative = val.is_sign_negative();
+121                if is_negative {
+122                    val = -val;
+123                }
+124
+125                if let Some(new_unit) = self.unit {
+126                    val = unit.convert(val, new_unit)?;
+127                    unit = new_unit;
+128                }
+129
+130                let (min_prefix, max_prefix) = match (self.prefix, self.prefix_forced) {
+131                    (Some(prefix), true) => (prefix, prefix),
+132                    (Some(prefix), false) => (prefix, Prefix::max_available()),
+133                    (None, _) => (Prefix::min_available(), Prefix::max_available()),
+134                };
+135
+136                let prefix = unit
+137                    .clamp_prefix(if min_prefix.is_binary() {
+138                        Prefix::eng_binary(val)
+139                    } else {
+140                        Prefix::eng(val)
+141                    })
+142                    .clamp(min_prefix, max_prefix);
+143                val = prefix.apply(val);
+144
+145                let mut digits = (val.max(1.).log10().floor() + 1.0) as i32 + is_negative as i32;
+146
+147                // handle rounding
+148                if self.width as i32 - digits >= 1 {
+149                    let round_up_to = self.width as i32 - digits - 1;
+150                    let m = 10f64.powi(round_up_to);
+151                    val = (val * m).round() / m;
+152                    digits = (val.max(1.).log10().floor() + 1.0) as i32 + is_negative as i32;
+153                }
+154
+155                let sign = if is_negative { "-" } else { "" };
+156                let mut retval = match self.width as i32 - digits {
+157                    i32::MIN..=0 => format!("{sign}{}", val.round()),
+158                    1 => format!("{}{sign}{}", self.pad_with, val.round() as i64),
+159                    rest => format!("{sign}{val:.*}", rest as usize - 1),
+160                };
+161
+162                let display_prefix =
+163                    !self.prefix_hidden && prefix != Prefix::One && prefix != Prefix::OneButBinary;
+164                let display_unit = !self.unit_hidden && unit != Unit::None;
+165
+166                if display_prefix {
+167                    if self.prefix_has_space {
+168                        retval.push(' ');
+169                    }
+170                    retval.push_str(&prefix.to_string());
+171                }
+172                if display_unit {
+173                    if self.unit_has_space || (self.prefix_has_space && !display_prefix) {
+174                        retval.push(' ');
+175                    }
+176                    retval.push_str(&unit.to_string());
+177                }
+178
+179                Ok(retval)
+180            }
+181            other => Err(FormatError::IncompatibleFormatter {
+182                ty: other.type_name(),
+183                fmt: "eng",
+184            }),
+185        }
+186    }
+187}
+188
+189#[cfg(test)]
+190mod tests {
+191    use super::*;
+192
+193    #[test]
+194    fn eng_rounding_and_negatives() {
+195        let fmt = new_fmt!(eng, w: 3).unwrap();
+196        let config = SharedConfig::default();
+197
+198        let result = fmt
+199            .format(
+200                &Value::Number {
+201                    val: -1.0,
+202                    unit: Unit::None,
+203                },
+204                &config,
+205            )
+206            .unwrap();
+207        assert_eq!(result, " -1");
+208
+209        let result = fmt
+210            .format(
+211                &Value::Number {
+212                    val: 9.9999,
+213                    unit: Unit::None,
+214                },
+215                &config,
+216            )
+217            .unwrap();
+218        assert_eq!(result, " 10");
+219
+220        let result = fmt
+221            .format(
+222                &Value::Number {
+223                    val: 999.9,
+224                    unit: Unit::Bytes,
+225                },
+226                &config,
+227            )
+228            .unwrap();
+229        assert_eq!(result, "1.0KB");
+230
+231        let result = fmt
+232            .format(
+233                &Value::Number {
+234                    val: -9.99,
+235                    unit: Unit::None,
+236                },
+237                &config,
+238            )
+239            .unwrap();
+240        assert_eq!(result, "-10");
+241
+242        let result = fmt
+243            .format(
+244                &Value::Number {
+245                    val: 9.94,
+246                    unit: Unit::None,
+247                },
+248                &config,
+249            )
+250            .unwrap();
+251        assert_eq!(result, "9.9");
+252
+253        let result = fmt
+254            .format(
+255                &Value::Number {
+256                    val: 9.95,
+257                    unit: Unit::None,
+258                },
+259                &config,
+260            )
+261            .unwrap();
+262        assert_eq!(result, " 10");
+263
+264        let fmt = new_fmt!(eng, w: 5, p: 1).unwrap();
+265        let result = fmt
+266            .format(
+267                &Value::Number {
+268                    val: 321_600_000_000.,
+269                    unit: Unit::Bytes,
+270                },
+271                &config,
+272            )
+273            .unwrap();
+274        assert_eq!(result, "321.6GB");
+275    }
+276
+277    #[test]
+278    fn eng_prefixes() {
+279        let config = SharedConfig::default();
+280        // 14.96 GiB
+281        let val = Value::Number {
+282            val: 14.96 * 1024. * 1024. * 1024.,
+283            unit: Unit::Bytes,
+284        };
+285
+286        let fmt = new_fmt!(eng, w: 5, p: Mi).unwrap();
+287        let result = fmt.format(&val, &config).unwrap();
+288        assert_eq!(result, "14.96GiB");
+289
+290        let fmt = new_fmt!(eng, w: 4, p: Mi).unwrap();
+291        let result = fmt.format(&val, &config).unwrap();
+292        assert_eq!(result, "15.0GiB");
+293
+294        let fmt = new_fmt!(eng, w: 3, p: Mi).unwrap();
+295        let result = fmt.format(&val, &config).unwrap();
+296        assert_eq!(result, " 15GiB");
+297
+298        let fmt = new_fmt!(eng, w: 2, p: Mi).unwrap();
+299        let result = fmt.format(&val, &config).unwrap();
+300        assert_eq!(result, "15GiB");
+301    }
+302}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/formatter/flag.rs.html b/src/i3status_rs/formatting/formatter/flag.rs.html new file mode 100644 index 0000000000..d4bbc30d30 --- /dev/null +++ b/src/i3status_rs/formatting/formatter/flag.rs.html @@ -0,0 +1,17 @@ +flag.rs - source

i3status_rs/formatting/formatter/
flag.rs

1use super::*;
+2
+3pub const DEFAULT_FLAG_FORMATTER: FlagFormatter = FlagFormatter;
+4
+5#[derive(Debug)]
+6pub struct FlagFormatter;
+7
+8impl Formatter for FlagFormatter {
+9    fn format(&self, val: &Value, _config: &SharedConfig) -> Result<String, FormatError> {
+10        match val {
+11            Value::Flag => Ok(String::new()),
+12            _ => {
+13                unreachable!()
+14            }
+15        }
+16    }
+17}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/formatter/pango.rs.html b/src/i3status_rs/formatting/formatter/pango.rs.html new file mode 100644 index 0000000000..f7afdc3cde --- /dev/null +++ b/src/i3status_rs/formatting/formatter/pango.rs.html @@ -0,0 +1,29 @@ +pango.rs - source

i3status_rs/formatting/formatter/
pango.rs

1use super::*;
+2
+3#[derive(Debug)]
+4pub struct PangoStrFormatter;
+5
+6impl PangoStrFormatter {
+7    pub(super) fn from_args(args: &[Arg]) -> Result<Self> {
+8        if let Some(arg) = args.first() {
+9            return Err(Error::new(format!(
+10                "Unknown argument for 'pango-str': '{}'",
+11                arg.key
+12            )));
+13        }
+14        Ok(Self)
+15    }
+16}
+17
+18impl Formatter for PangoStrFormatter {
+19    fn format(&self, val: &Value, config: &SharedConfig) -> Result<String, FormatError> {
+20        match val {
+21            Value::Text(x) => Ok(x.clone()), // No escaping
+22            Value::Icon(icon, value) => config.get_icon(icon, *value).map_err(Into::into),
+23            other => Err(FormatError::IncompatibleFormatter {
+24                ty: other.type_name(),
+25                fmt: "pango-str",
+26            }),
+27        }
+28    }
+29}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/formatter/str.rs.html b/src/i3status_rs/formatting/formatter/str.rs.html new file mode 100644 index 0000000000..f1fcb7f574 --- /dev/null +++ b/src/i3status_rs/formatting/formatter/str.rs.html @@ -0,0 +1,124 @@ +str.rs - source

i3status_rs/formatting/formatter/
str.rs

1use std::iter::repeat_n;
+2use std::time::Instant;
+3
+4use crate::escape::CollectEscaped as _;
+5
+6use super::*;
+7
+8const DEFAULT_STR_MIN_WIDTH: usize = 0;
+9const DEFAULT_STR_MAX_WIDTH: usize = usize::MAX;
+10const DEFAULT_STR_ROT_INTERVAL: Option<f64> = None;
+11const DEFAULT_STR_ROT_SEP: Option<String> = None;
+12
+13pub const DEFAULT_STRING_FORMATTER: StrFormatter = StrFormatter {
+14    min_width: DEFAULT_STR_MIN_WIDTH,
+15    max_width: DEFAULT_STR_MAX_WIDTH,
+16    rot_interval_ms: None,
+17    init_time: None,
+18    rot_separator: None,
+19};
+20
+21#[derive(Debug)]
+22pub struct StrFormatter {
+23    min_width: usize,
+24    max_width: usize,
+25    rot_interval_ms: Option<u64>,
+26    init_time: Option<Instant>,
+27    rot_separator: Option<String>,
+28}
+29
+30impl StrFormatter {
+31    pub(super) fn from_args(args: &[Arg]) -> Result<Self> {
+32        let mut min_width = DEFAULT_STR_MIN_WIDTH;
+33        let mut max_width = DEFAULT_STR_MAX_WIDTH;
+34        let mut rot_interval = DEFAULT_STR_ROT_INTERVAL;
+35        let mut rot_separator = DEFAULT_STR_ROT_SEP;
+36        for arg in args {
+37            match arg.key {
+38                "min_width" | "min_w" => {
+39                    min_width = arg.parse_value()?;
+40                }
+41                "max_width" | "max_w" => {
+42                    max_width = arg.parse_value()?;
+43                }
+44                "width" | "w" => {
+45                    min_width = arg.parse_value()?;
+46                    max_width = min_width;
+47                }
+48                "rot_interval" => {
+49                    rot_interval = Some(arg.parse_value()?);
+50                }
+51                "rot_separator" => {
+52                    rot_separator = Some(arg.parse_value()?);
+53                }
+54                other => {
+55                    return Err(Error::new(format!("Unknown argument for 'str': '{other}'")));
+56                }
+57            }
+58        }
+59        if max_width < min_width {
+60            return Err(Error::new(
+61                "Max width must be greater of equal to min width",
+62            ));
+63        }
+64        if let Some(rot_interval) = rot_interval
+65            && rot_interval < 0.1
+66        {
+67            return Err(Error::new("Interval must be greater than 0.1"));
+68        }
+69        Ok(StrFormatter {
+70            min_width,
+71            max_width,
+72            rot_interval_ms: rot_interval.map(|x| (x * 1e3) as u64),
+73            init_time: Some(Instant::now()),
+74            rot_separator,
+75        })
+76    }
+77}
+78
+79impl Formatter for StrFormatter {
+80    fn format(&self, val: &Value, config: &SharedConfig) -> Result<String, FormatError> {
+81        match val {
+82            Value::Text(text) => {
+83                let text: Vec<&str> = text.graphemes(true).collect();
+84                let width = text.len();
+85                Ok(match (self.rot_interval_ms, self.init_time) {
+86                    (Some(rot_interval_ms), Some(init_time)) if width > self.max_width => {
+87                        let rot_separator: Vec<&str> = self
+88                            .rot_separator
+89                            .as_deref()
+90                            .unwrap_or("|")
+91                            .graphemes(true)
+92                            .collect();
+93                        let width = width + rot_separator.len(); // Now we include `rot_separator` at the end
+94                        let step = (init_time.elapsed().as_millis() as u64 / rot_interval_ms)
+95                            as usize
+96                            % width;
+97                        let w1 = self.max_width.min(width - step);
+98                        text.iter()
+99                            .chain(rot_separator.iter())
+100                            .skip(step)
+101                            .take(w1)
+102                            .chain(text.iter())
+103                            .take(self.max_width)
+104                            .collect_pango_escaped()
+105                    }
+106                    _ => text
+107                        .iter()
+108                        .chain(repeat_n(&" ", self.min_width.saturating_sub(width)))
+109                        .take(self.max_width)
+110                        .collect_pango_escaped(),
+111                })
+112            }
+113            Value::Icon(icon, value) => config.get_icon(icon, *value).map_err(Into::into),
+114            other => Err(FormatError::IncompatibleFormatter {
+115                ty: other.type_name(),
+116                fmt: "str",
+117            }),
+118        }
+119    }
+120
+121    fn interval(&self) -> Option<Duration> {
+122        self.rot_interval_ms.map(Duration::from_millis)
+123    }
+124}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/formatter/tally.rs.html b/src/i3status_rs/formatting/formatter/tally.rs.html new file mode 100644 index 0000000000..fef79d058c --- /dev/null +++ b/src/i3status_rs/formatting/formatter/tally.rs.html @@ -0,0 +1,582 @@ +tally.rs - source

i3status_rs/formatting/formatter/
tally.rs

1use std::str::FromStr;
+2
+3use crate::formatting::unit::Unit;
+4
+5use super::*;
+6
+7#[derive(Debug)]
+8enum Style {
+9    ChineseCountingRods,
+10    ChineseTally,
+11    WesternTally,
+12    WesternTallyUngrouped,
+13}
+14
+15impl FromStr for Style {
+16    type Err = Error;
+17
+18    fn from_str(s: &str) -> Result<Self> {
+19        match s {
+20            "chinese_counting_rods" | "ccr" => Ok(Style::ChineseCountingRods),
+21            "chinese_tally" | "ct" => Ok(Style::ChineseTally),
+22            "western_tally" | "wt" => Ok(Style::WesternTally),
+23            "western_tally_ungrouped" | "wtu" => Ok(Style::WesternTallyUngrouped),
+24            x => Err(Error::new(format!("Unknown Style: '{x}'"))),
+25        }
+26    }
+27}
+28
+29#[derive(Debug)]
+30pub struct TallyFormatter {
+31    style: Style,
+32}
+33
+34impl TallyFormatter {
+35    pub(super) fn from_args(args: &[Arg]) -> Result<Self> {
+36        let mut style = Style::WesternTally;
+37        for arg in args {
+38            match arg.key {
+39                "style" | "s" => {
+40                    style = arg.parse_value()?;
+41                }
+42                other => {
+43                    return Err(Error::new(format!(
+44                        "Unknown argument for 'tally': '{other}'"
+45                    )));
+46                }
+47            }
+48        }
+49        Ok(Self { style })
+50    }
+51}
+52
+53const HORIZONTAL_CHINESE_COUNTING_RODS_CHARS: [char; 10] =
+54    ['〇', '𝍠', '𝍡', '𝍢', '𝍣', '𝍤', '𝍥', '𝍦', '𝍧', '𝍨'];
+55
+56const VERTICAL_CHINESE_COUNTING_RODS_CHARS: [char; 10] =
+57    ['〇', '𝍩', '𝍪', '𝍫', '𝍬', '𝍭', '𝍮', '𝍯', '𝍰', '𝍱'];
+58
+59const CHINESE_TALLY_CHARS: [char; 5] = ['𝍲', '𝍳', '𝍴', '𝍵', '𝍶'];
+60
+61impl Formatter for TallyFormatter {
+62    fn format(&self, val: &Value, _config: &SharedConfig) -> Result<String, FormatError> {
+63        match val {
+64            Value::Number {
+65                val,
+66                unit: Unit::None,
+67            } => {
+68                let is_negative = val.is_sign_negative();
+69                let mut val = val.abs().round() as u64;
+70                let mut result = String::new();
+71                match self.style {
+72                    Style::ChineseCountingRods => {
+73                        if is_negative {
+74                            result.push('\u{20E5}');
+75                        }
+76                        if val == 0 {
+77                            result.insert(0, '〇');
+78                        } else {
+79                            let mut horizontal = true;
+80                            while val != 0 {
+81                                let digit = val % 10;
+82                                val /= 10;
+83                                let charset = if horizontal {
+84                                    horizontal = false;
+85                                    HORIZONTAL_CHINESE_COUNTING_RODS_CHARS
+86                                } else {
+87                                    horizontal = true;
+88                                    VERTICAL_CHINESE_COUNTING_RODS_CHARS
+89                                };
+90                                result.insert(0, charset[digit as usize]);
+91                            }
+92                        }
+93                    }
+94                    Style::ChineseTally => {
+95                        if is_negative {
+96                            return Err(FormatError::Other(Error::new(
+97                                "Chinese Tally marks do not support negative numbers",
+98                            )));
+99                        }
+100                        let (fives, rem) = (val / 5, val % 5);
+101                        for _ in 0..fives {
+102                            result.push(CHINESE_TALLY_CHARS[4]);
+103                        }
+104                        if rem != 0 {
+105                            result.push(CHINESE_TALLY_CHARS[rem as usize - 1]);
+106                        }
+107                    }
+108                    Style::WesternTally | Style::WesternTallyUngrouped => {
+109                        if is_negative {
+110                            return Err(FormatError::Other(Error::new(
+111                                "Western Tally marks do not support negative numbers",
+112                            )));
+113                        }
+114                        if matches!(self.style, Style::WesternTally) {
+115                            let fives = val / 5;
+116                            val %= 5;
+117                            for _ in 0..fives {
+118                                result.push('𝍸');
+119                            }
+120                        }
+121                        for _ in 0..val {
+122                            result.push('𝍷');
+123                        }
+124                    }
+125                }
+126                Ok(result)
+127            }
+128            Value::Number { .. } => Err(FormatError::Other(Error::new(
+129                "Tally can only format Numbers with Unit::None",
+130            ))),
+131            other => Err(FormatError::IncompatibleFormatter {
+132                ty: other.type_name(),
+133                fmt: "tally",
+134            }),
+135        }
+136    }
+137}
+138
+139#[cfg(test)]
+140mod tests {
+141    use super::*;
+142
+143    #[test]
+144    fn tally_chinese_counting_rods_negative() {
+145        let fmt = new_fmt!(tally, style: chinese_counting_rods).unwrap();
+146        let config = SharedConfig::default();
+147
+148        let result = fmt
+149            .format(
+150                &Value::Number {
+151                    val: -0.0,
+152                    unit: Unit::None,
+153                },
+154                &config,
+155            )
+156            .unwrap();
+157        assert_eq!(result, "〇\u{20E5}");
+158
+159        for (hundreds, hundreds_char) in HORIZONTAL_CHINESE_COUNTING_RODS_CHARS
+160            .into_iter()
+161            .enumerate()
+162        {
+163            for (tens, tens_char) in VERTICAL_CHINESE_COUNTING_RODS_CHARS.into_iter().enumerate() {
+164                for (ones, ones_char) in HORIZONTAL_CHINESE_COUNTING_RODS_CHARS
+165                    .into_iter()
+166                    .enumerate()
+167                {
+168                    let val = -((hundreds * 100 + tens * 10 + ones) as f64);
+169                    if val == 0.0 {
+170                        continue;
+171                    }
+172                    // Contcat characters, excluding leading 〇
+173                    let expected = String::from_iter(
+174                        [hundreds_char, tens_char, ones_char, '\u{20E5}']
+175                            .into_iter()
+176                            .skip_while(|c| *c == '〇'),
+177                    );
+178
+179                    let result = fmt
+180                        .format(
+181                            &Value::Number {
+182                                val,
+183                                unit: Unit::None,
+184                            },
+185                            &config,
+186                        )
+187                        .unwrap();
+188                    assert_eq!(result, expected);
+189                }
+190            }
+191        }
+192    }
+193
+194    #[test]
+195    fn tally_chinese_counting_rods_positive() {
+196        let fmt = new_fmt!(tally, style: chinese_counting_rods).unwrap();
+197        let config = SharedConfig::default();
+198
+199        let result = fmt
+200            .format(
+201                &Value::Number {
+202                    val: 0.0,
+203                    unit: Unit::None,
+204                },
+205                &config,
+206            )
+207            .unwrap();
+208        assert_eq!(result, "〇");
+209
+210        for (hundreds, hundreds_char) in HORIZONTAL_CHINESE_COUNTING_RODS_CHARS
+211            .into_iter()
+212            .enumerate()
+213        {
+214            for (tens, tens_char) in VERTICAL_CHINESE_COUNTING_RODS_CHARS.into_iter().enumerate() {
+215                for (ones, ones_char) in HORIZONTAL_CHINESE_COUNTING_RODS_CHARS
+216                    .into_iter()
+217                    .enumerate()
+218                {
+219                    let val = (hundreds * 100 + tens * 10 + ones) as f64;
+220                    if val == 0.0 {
+221                        continue;
+222                    }
+223                    // Contcat characters, excluding leading 〇
+224                    let expected = String::from_iter(
+225                        [hundreds_char, tens_char, ones_char]
+226                            .into_iter()
+227                            .skip_while(|c| *c == '〇'),
+228                    );
+229
+230                    let result = fmt
+231                        .format(
+232                            &Value::Number {
+233                                val,
+234                                unit: Unit::None,
+235                            },
+236                            &config,
+237                        )
+238                        .unwrap();
+239                    assert_eq!(result, expected);
+240                }
+241            }
+242        }
+243    }
+244
+245    #[test]
+246    fn tally_chinese_tally_negative() {
+247        let fmt = new_fmt!(tally, style: chinese_tally).unwrap();
+248        let config = SharedConfig::default();
+249
+250        let result = fmt.format(
+251            &Value::Number {
+252                val: -1.0,
+253                unit: Unit::None,
+254            },
+255            &config,
+256        );
+257        assert!(result.is_err());
+258    }
+259
+260    #[test]
+261    fn tally_chinese_tally_positive() {
+262        let fmt = new_fmt!(tally, style: chinese_tally).unwrap();
+263        let config = SharedConfig::default();
+264
+265        let result = fmt
+266            .format(
+267                &Value::Number {
+268                    val: 0.0,
+269                    unit: Unit::None,
+270                },
+271                &config,
+272            )
+273            .unwrap();
+274        assert_eq!(result, "");
+275
+276        let result = fmt
+277            .format(
+278                &Value::Number {
+279                    val: 1.0,
+280                    unit: Unit::None,
+281                },
+282                &config,
+283            )
+284            .unwrap();
+285        assert_eq!(result, "𝍲");
+286
+287        let result = fmt
+288            .format(
+289                &Value::Number {
+290                    val: 2.0,
+291                    unit: Unit::None,
+292                },
+293                &config,
+294            )
+295            .unwrap();
+296        assert_eq!(result, "𝍳");
+297
+298        let result = fmt
+299            .format(
+300                &Value::Number {
+301                    val: 3.0,
+302                    unit: Unit::None,
+303                },
+304                &config,
+305            )
+306            .unwrap();
+307        assert_eq!(result, "𝍴");
+308
+309        let result = fmt
+310            .format(
+311                &Value::Number {
+312                    val: 4.0,
+313                    unit: Unit::None,
+314                },
+315                &config,
+316            )
+317            .unwrap();
+318        assert_eq!(result, "𝍵");
+319
+320        let result = fmt
+321            .format(
+322                &Value::Number {
+323                    val: 5.0,
+324                    unit: Unit::None,
+325                },
+326                &config,
+327            )
+328            .unwrap();
+329        assert_eq!(result, "𝍶");
+330
+331        let result = fmt
+332            .format(
+333                &Value::Number {
+334                    val: 6.0,
+335                    unit: Unit::None,
+336                },
+337                &config,
+338            )
+339            .unwrap();
+340        assert_eq!(result, "𝍶𝍲");
+341
+342        let result = fmt
+343            .format(
+344                &Value::Number {
+345                    val: 7.0,
+346                    unit: Unit::None,
+347                },
+348                &config,
+349            )
+350            .unwrap();
+351        assert_eq!(result, "𝍶𝍳");
+352
+353        let result = fmt
+354            .format(
+355                &Value::Number {
+356                    val: 8.0,
+357                    unit: Unit::None,
+358                },
+359                &config,
+360            )
+361            .unwrap();
+362        assert_eq!(result, "𝍶𝍴");
+363
+364        let result = fmt
+365            .format(
+366                &Value::Number {
+367                    val: 9.0,
+368                    unit: Unit::None,
+369                },
+370                &config,
+371            )
+372            .unwrap();
+373        assert_eq!(result, "𝍶𝍵");
+374
+375        let result = fmt
+376            .format(
+377                &Value::Number {
+378                    val: 10.0,
+379                    unit: Unit::None,
+380                },
+381                &config,
+382            )
+383            .unwrap();
+384        assert_eq!(result, "𝍶𝍶");
+385    }
+386
+387    #[test]
+388    fn tally_western_tally_negative() {
+389        let fmt = new_fmt!(tally, style: western_tally).unwrap();
+390        let config = SharedConfig::default();
+391
+392        let result = fmt.format(
+393            &Value::Number {
+394                val: -1.0,
+395                unit: Unit::None,
+396            },
+397            &config,
+398        );
+399        assert!(result.is_err());
+400    }
+401
+402    #[test]
+403    fn tally_western_tally_positive() {
+404        let fmt = new_fmt!(tally, style: western_tally).unwrap();
+405        let config = SharedConfig::default();
+406
+407        let result = fmt
+408            .format(
+409                &Value::Number {
+410                    val: 0.0,
+411                    unit: Unit::None,
+412                },
+413                &config,
+414            )
+415            .unwrap();
+416        assert_eq!(result, "");
+417
+418        let result = fmt
+419            .format(
+420                &Value::Number {
+421                    val: 1.0,
+422                    unit: Unit::None,
+423                },
+424                &config,
+425            )
+426            .unwrap();
+427        assert_eq!(result, "𝍷");
+428
+429        let result = fmt
+430            .format(
+431                &Value::Number {
+432                    val: 2.0,
+433                    unit: Unit::None,
+434                },
+435                &config,
+436            )
+437            .unwrap();
+438        assert_eq!(result, "𝍷𝍷");
+439
+440        let result = fmt
+441            .format(
+442                &Value::Number {
+443                    val: 3.0,
+444                    unit: Unit::None,
+445                },
+446                &config,
+447            )
+448            .unwrap();
+449        assert_eq!(result, "𝍷𝍷𝍷");
+450
+451        let result = fmt
+452            .format(
+453                &Value::Number {
+454                    val: 4.0,
+455                    unit: Unit::None,
+456                },
+457                &config,
+458            )
+459            .unwrap();
+460        assert_eq!(result, "𝍷𝍷𝍷𝍷");
+461
+462        let result = fmt
+463            .format(
+464                &Value::Number {
+465                    val: 5.0,
+466                    unit: Unit::None,
+467                },
+468                &config,
+469            )
+470            .unwrap();
+471        assert_eq!(result, "𝍸");
+472
+473        let result = fmt
+474            .format(
+475                &Value::Number {
+476                    val: 6.0,
+477                    unit: Unit::None,
+478                },
+479                &config,
+480            )
+481            .unwrap();
+482        assert_eq!(result, "𝍸𝍷");
+483    }
+484
+485    #[test]
+486    fn tally_western_tally_ungrouped_negative() {
+487        let fmt = new_fmt!(tally, style: western_tally_ungrouped).unwrap();
+488        let config = SharedConfig::default();
+489
+490        let result = fmt.format(
+491            &Value::Number {
+492                val: -1.0,
+493                unit: Unit::None,
+494            },
+495            &config,
+496        );
+497        assert!(result.is_err());
+498    }
+499
+500    #[test]
+501    fn tally_western_tally_ungrouped_positive() {
+502        let fmt = new_fmt!(tally, style: western_tally_ungrouped).unwrap();
+503        let config = SharedConfig::default();
+504
+505        let result = fmt
+506            .format(
+507                &Value::Number {
+508                    val: 0.0,
+509                    unit: Unit::None,
+510                },
+511                &config,
+512            )
+513            .unwrap();
+514        assert_eq!(result, "");
+515
+516        let result = fmt
+517            .format(
+518                &Value::Number {
+519                    val: 1.0,
+520                    unit: Unit::None,
+521                },
+522                &config,
+523            )
+524            .unwrap();
+525        assert_eq!(result, "𝍷");
+526
+527        let result = fmt
+528            .format(
+529                &Value::Number {
+530                    val: 2.0,
+531                    unit: Unit::None,
+532                },
+533                &config,
+534            )
+535            .unwrap();
+536        assert_eq!(result, "𝍷𝍷");
+537
+538        let result = fmt
+539            .format(
+540                &Value::Number {
+541                    val: 3.0,
+542                    unit: Unit::None,
+543                },
+544                &config,
+545            )
+546            .unwrap();
+547        assert_eq!(result, "𝍷𝍷𝍷");
+548
+549        let result = fmt
+550            .format(
+551                &Value::Number {
+552                    val: 4.0,
+553                    unit: Unit::None,
+554                },
+555                &config,
+556            )
+557            .unwrap();
+558        assert_eq!(result, "𝍷𝍷𝍷𝍷");
+559
+560        let result = fmt
+561            .format(
+562                &Value::Number {
+563                    val: 5.0,
+564                    unit: Unit::None,
+565                },
+566                &config,
+567            )
+568            .unwrap();
+569        assert_eq!(result, "𝍷𝍷𝍷𝍷𝍷");
+570
+571        let result = fmt
+572            .format(
+573                &Value::Number {
+574                    val: 6.0,
+575                    unit: Unit::None,
+576                },
+577                &config,
+578            )
+579            .unwrap();
+580        assert_eq!(result, "𝍷𝍷𝍷𝍷𝍷𝍷");
+581    }
+582}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/parse.rs.html b/src/i3status_rs/formatting/parse.rs.html new file mode 100644 index 0000000000..5119daad15 --- /dev/null +++ b/src/i3status_rs/formatting/parse.rs.html @@ -0,0 +1,527 @@ +parse.rs - source

i3status_rs/formatting/
parse.rs

1use std::{any::TypeId, str::FromStr};
+2
+3use nom::{
+4    IResult, Parser as _,
+5    branch::alt,
+6    bytes::complete::{escaped_transform, tag, take_while, take_while1},
+7    character::complete::{anychar, char},
+8    combinator::{cut, eof, map, not, opt},
+9    multi::{many0, separated_list0},
+10    sequence::{preceded, separated_pair, terminated, tuple},
+11};
+12
+13use crate::errors::*;
+14
+15#[derive(Debug, PartialEq, Eq)]
+16pub struct Arg<'a> {
+17    pub key: &'a str,
+18    pub val: Option<&'a str>,
+19}
+20
+21impl Arg<'_> {
+22    pub fn parse_value<T>(&self) -> Result<T>
+23    where
+24        T: FromStr + 'static,
+25        T::Err: StdError + Send + Sync + 'static,
+26    {
+27        if TypeId::of::<T>() == TypeId::of::<bool>() && self.val.is_none() {
+28            Ok("true".parse().expect("'true' is valid bool"))
+29        } else {
+30            self.val
+31                .or_error(|| format!("missing value for argument '{}'", self.key))?
+32                .parse()
+33                .or_error(|| format!("invalid value for argument '{}'", self.key))
+34        }
+35    }
+36}
+37
+38#[derive(Debug, PartialEq, Eq)]
+39pub struct Formatter<'a> {
+40    pub name: &'a str,
+41    pub args: Vec<Arg<'a>>,
+42}
+43
+44#[derive(Debug, PartialEq, Eq)]
+45pub struct Placeholder<'a> {
+46    pub name: &'a str,
+47    pub formatter: Option<Formatter<'a>>,
+48}
+49
+50#[derive(Debug, PartialEq, Eq)]
+51pub enum Token<'a> {
+52    Text(String),
+53    Placeholder(Placeholder<'a>),
+54    Icon(&'a str),
+55    Recursive(FormatTemplate<'a>),
+56}
+57
+58#[derive(Debug, PartialEq, Eq)]
+59pub struct TokenList<'a>(pub Vec<Token<'a>>);
+60
+61#[derive(Debug, PartialEq, Eq)]
+62pub struct FormatTemplate<'a>(pub Vec<TokenList<'a>>);
+63
+64#[derive(Debug, PartialEq, Eq)]
+65enum PError<'a> {
+66    Expected {
+67        expected: char,
+68        actual: Option<char>,
+69    },
+70    Other {
+71        input: &'a str,
+72        kind: nom::error::ErrorKind,
+73    },
+74}
+75
+76impl<'a> nom::error::ParseError<&'a str> for PError<'a> {
+77    fn from_error_kind(input: &'a str, kind: nom::error::ErrorKind) -> Self {
+78        Self::Other { input, kind }
+79    }
+80
+81    fn append(_: &'a str, _: nom::error::ErrorKind, other: Self) -> Self {
+82        other
+83    }
+84
+85    fn from_char(input: &'a str, expected: char) -> Self {
+86        let actual = input.chars().next();
+87        Self::Expected { expected, actual }
+88    }
+89
+90    fn or(self, other: Self) -> Self {
+91        other
+92    }
+93}
+94
+95fn spaces(i: &str) -> IResult<&str, &str, PError<'_>> {
+96    take_while(|x: char| x.is_ascii_whitespace())(i)
+97}
+98
+99fn alphanum1(i: &str) -> IResult<&str, &str, PError<'_>> {
+100    take_while1(|x: char| x.is_alphanumeric() || x == '_' || x == '-')(i)
+101}
+102
+103//val
+104//'val ue'
+105fn arg1(i: &str) -> IResult<&str, &str, PError<'_>> {
+106    alt((
+107        take_while1(|x: char| x.is_alphanumeric() || x == '_' || x == '-' || x == '.' || x == '%'),
+108        preceded(
+109            char('\''),
+110            cut(terminated(take_while(|x: char| x != '\''), char('\''))),
+111        ),
+112    ))(i)
+113}
+114
+115// `key:val`
+116// `abc`
+117fn parse_arg(i: &str) -> IResult<&str, Arg<'_>, PError<'_>> {
+118    alt((
+119        map(
+120            separated_pair(alphanum1, char(':'), cut(arg1)),
+121            |(key, val)| Arg {
+122                key,
+123                val: Some(val),
+124            },
+125        ),
+126        map(alphanum1, |key| Arg { key, val: None }),
+127    ))(i)
+128}
+129
+130// `(arg,key:val)`
+131// `( arg, key:val , abc)`
+132fn parse_args(i: &str) -> IResult<&str, Vec<Arg<'_>>, PError<'_>> {
+133    let inner = separated_list0(preceded(spaces, char(',')), preceded(spaces, parse_arg));
+134    preceded(
+135        char('('),
+136        cut(terminated(inner, preceded(spaces, char(')')))),
+137    )(i)
+138}
+139
+140// `.str(width:2)`
+141// `.eng(unit:bits,show)`
+142fn parse_formatter(i: &str) -> IResult<&str, Formatter<'_>, PError<'_>> {
+143    preceded(char('.'), cut(tuple((alphanum1, opt(parse_args)))))
+144        .map(|(name, args)| Formatter {
+145            name,
+146            args: args.unwrap_or_default(),
+147        })
+148        .parse(i)
+149}
+150
+151// `$var`
+152// `$key.eng(unit:bits,show)`
+153fn parse_placeholder(i: &str) -> IResult<&str, Placeholder<'_>, PError<'_>> {
+154    preceded(char('$'), cut(tuple((alphanum1, opt(parse_formatter)))))
+155        .map(|(name, formatter)| Placeholder { name, formatter })
+156        .parse(i)
+157}
+158
+159// `just escaped \| text`
+160fn parse_string(i: &str) -> IResult<&str, String, PError<'_>> {
+161    preceded(
+162        not(eof),
+163        escaped_transform(
+164            take_while1(|x| x != '$' && x != '^' && x != '{' && x != '}' && x != '|' && x != '\\'),
+165            '\\',
+166            anychar,
+167        ),
+168    )(i)
+169}
+170
+171// `^icon_name`
+172fn parse_icon(i: &str) -> IResult<&str, &str, PError<'_>> {
+173    preceded(char('^'), cut(preceded(tag("icon_"), alphanum1)))(i)
+174}
+175
+176// `{ a | b | c }`
+177fn parse_recursive_template(i: &str) -> IResult<&str, FormatTemplate<'_>, PError<'_>> {
+178    preceded(char('{'), cut(terminated(parse_format_template, char('}'))))(i)
+179}
+180
+181fn parse_token_list(i: &str) -> IResult<&str, TokenList<'_>, PError<'_>> {
+182    map(
+183        many0(alt((
+184            map(parse_string, Token::Text),
+185            map(parse_placeholder, Token::Placeholder),
+186            map(parse_icon, Token::Icon),
+187            map(parse_recursive_template, Token::Recursive),
+188        ))),
+189        TokenList,
+190    )(i)
+191}
+192
+193fn parse_format_template(i: &str) -> IResult<&str, FormatTemplate<'_>, PError<'_>> {
+194    map(separated_list0(char('|'), parse_token_list), FormatTemplate)(i)
+195}
+196
+197pub fn parse_full(i: &str) -> Result<FormatTemplate<'_>> {
+198    match parse_format_template(i) {
+199        Ok((rest, template)) => {
+200            if rest.is_empty() {
+201                Ok(template)
+202            } else {
+203                Err(Error::new(format!(
+204                    "unexpected '{}'",
+205                    rest.chars().next().unwrap()
+206                )))
+207            }
+208        }
+209        Err(err) => Err(match err {
+210            nom::Err::Incomplete(_) => unreachable!(),
+211            nom::Err::Error(err) | nom::Err::Failure(err) => match err {
+212                PError::Expected { expected, actual } => {
+213                    if let Some(actual) = actual {
+214                        Error::new(format!("expected '{expected}', got '{actual}'"))
+215                    } else {
+216                        Error::new(format!("expected '{expected}', got EOF"))
+217                    }
+218                }
+219                PError::Other { input, kind } => {
+220                    // TODO: improve?
+221                    Error::new(format!("{kind:?} error near '{input}'"))
+222                }
+223            },
+224        }),
+225    }
+226}
+227
+228#[cfg(test)]
+229mod tests {
+230    use super::*;
+231
+232    #[test]
+233    fn arg() {
+234        assert_eq!(
+235            parse_arg("key:val,"),
+236            Ok((
+237                ",",
+238                Arg {
+239                    key: "key",
+240                    val: Some("val")
+241                }
+242            ))
+243        );
+244        assert_eq!(
+245            parse_arg("key:'val ue',"),
+246            Ok((
+247                ",",
+248                Arg {
+249                    key: "key",
+250                    val: Some("val ue")
+251                }
+252            ))
+253        );
+254        assert_eq!(
+255            parse_arg("key:'',"),
+256            Ok((
+257                ",",
+258                Arg {
+259                    key: "key",
+260                    val: Some("")
+261                }
+262            ))
+263        );
+264        assert_eq!(
+265            parse_arg("key,"),
+266            Ok((
+267                ",",
+268                Arg {
+269                    key: "key",
+270                    val: None
+271                }
+272            ))
+273        );
+274        assert_eq!(
+275            parse_arg("key:,"),
+276            Err(nom::Err::Failure(PError::Expected {
+277                expected: '\'',
+278                actual: Some(',')
+279            }))
+280        );
+281    }
+282
+283    #[test]
+284    fn args() {
+285        assert_eq!(
+286            parse_args("(key:val)"),
+287            Ok((
+288                "",
+289                vec![Arg {
+290                    key: "key",
+291                    val: Some("val")
+292                }]
+293            ))
+294        );
+295        assert_eq!(
+296            parse_args("( abc:d , key:val )"),
+297            Ok((
+298                "",
+299                vec![
+300                    Arg {
+301                        key: "abc",
+302                        val: Some("d"),
+303                    },
+304                    Arg {
+305                        key: "key",
+306                        val: Some("val")
+307                    }
+308                ]
+309            ))
+310        );
+311        assert_eq!(
+312            parse_args("(abc)"),
+313            Ok((
+314                "",
+315                vec![Arg {
+316                    key: "abc",
+317                    val: None
+318                }]
+319            ))
+320        );
+321        assert_eq!(
+322            parse_args("( key:, )"),
+323            Err(nom::Err::Failure(PError::Expected {
+324                expected: '\'',
+325                actual: Some(',')
+326            }))
+327        );
+328    }
+329
+330    #[test]
+331    fn formatter() {
+332        assert_eq!(
+333            parse_formatter(".str(key:val)"),
+334            Ok((
+335                "",
+336                Formatter {
+337                    name: "str",
+338                    args: vec![Arg {
+339                        key: "key",
+340                        val: Some("val")
+341                    }]
+342                }
+343            ))
+344        );
+345        assert_eq!(
+346            parse_formatter(".eng(w:3 , show:true )"),
+347            Ok((
+348                "",
+349                Formatter {
+350                    name: "eng",
+351                    args: vec![
+352                        Arg {
+353                            key: "w",
+354                            val: Some("3")
+355                        },
+356                        Arg {
+357                            key: "show",
+358                            val: Some("true")
+359                        }
+360                    ]
+361                }
+362            ))
+363        );
+364        assert_eq!(
+365            parse_formatter(".eng(w:3 , show)"),
+366            Ok((
+367                "",
+368                Formatter {
+369                    name: "eng",
+370                    args: vec![
+371                        Arg {
+372                            key: "w",
+373                            val: Some("3")
+374                        },
+375                        Arg {
+376                            key: "show",
+377                            val: None
+378                        }
+379                    ]
+380                }
+381            ))
+382        );
+383    }
+384
+385    #[test]
+386    fn placeholder() {
+387        assert_eq!(
+388            parse_placeholder("$key"),
+389            Ok((
+390                "",
+391                Placeholder {
+392                    name: "key",
+393                    formatter: None,
+394                }
+395            ))
+396        );
+397        assert_eq!(
+398            parse_placeholder("$var.str()"),
+399            Ok((
+400                "",
+401                Placeholder {
+402                    name: "var",
+403                    formatter: Some(Formatter {
+404                        name: "str",
+405                        args: vec![]
+406                    }),
+407                }
+408            ))
+409        );
+410        assert_eq!(
+411            parse_placeholder("$var.str(a:b, c:d)"),
+412            Ok((
+413                "",
+414                Placeholder {
+415                    name: "var",
+416                    formatter: Some(Formatter {
+417                        name: "str",
+418                        args: vec![
+419                            Arg {
+420                                key: "a",
+421                                val: Some("b")
+422                            },
+423                            Arg {
+424                                key: "c",
+425                                val: Some("d")
+426                            }
+427                        ]
+428                    }),
+429                }
+430            ))
+431        );
+432        assert!(parse_placeholder("$key.").is_err());
+433    }
+434
+435    #[test]
+436    fn icon() {
+437        assert_eq!(parse_icon("^icon_my_icon"), Ok(("", "my_icon")));
+438        assert_eq!(parse_icon("^icon_m"), Ok(("", "m")));
+439        assert!(parse_icon("^icon_").is_err());
+440        assert!(parse_icon("^2").is_err());
+441    }
+442
+443    #[test]
+444    fn token_list() {
+445        assert_eq!(
+446            parse_token_list(" abc \\$ $var.str(a:b)$x "),
+447            Ok((
+448                "",
+449                TokenList(vec![
+450                    Token::Text(" abc $ ".into()),
+451                    Token::Placeholder(Placeholder {
+452                        name: "var",
+453                        formatter: Some(Formatter {
+454                            name: "str",
+455                            args: vec![Arg {
+456                                key: "a",
+457                                val: Some("b")
+458                            }]
+459                        })
+460                    }),
+461                    Token::Placeholder(Placeholder {
+462                        name: "x",
+463                        formatter: None,
+464                    }),
+465                    Token::Text(" ".into())
+466                ])
+467            ))
+468        );
+469    }
+470
+471    #[test]
+472    fn format_template() {
+473        assert_eq!(
+474            parse_format_template("simple"),
+475            Ok((
+476                "",
+477                FormatTemplate(vec![TokenList(vec![Token::Text("simple".into())]),])
+478            ))
+479        );
+480        assert_eq!(
+481            parse_format_template(" $x.str() | N/A "),
+482            Ok((
+483                "",
+484                FormatTemplate(vec![
+485                    TokenList(vec![
+486                        Token::Text(" ".into()),
+487                        Token::Placeholder(Placeholder {
+488                            name: "x",
+489                            formatter: Some(Formatter {
+490                                name: "str",
+491                                args: vec![]
+492                            })
+493                        }),
+494                        Token::Text(" ".into()),
+495                    ]),
+496                    TokenList(vec![Token::Text(" N/A ".into())]),
+497                ])
+498            ))
+499        );
+500    }
+501
+502    #[test]
+503    fn full() {
+504        assert_eq!(
+505            parse_format_template(" ^icon_my_icon {$x.str()|N/A} "),
+506            Ok((
+507                "",
+508                FormatTemplate(vec![TokenList(vec![
+509                    Token::Text(" ".into()),
+510                    Token::Icon("my_icon"),
+511                    Token::Text(" ".into()),
+512                    Token::Recursive(FormatTemplate(vec![
+513                        TokenList(vec![Token::Placeholder(Placeholder {
+514                            name: "x",
+515                            formatter: Some(Formatter {
+516                                name: "str",
+517                                args: vec![]
+518                            })
+519                        })]),
+520                        TokenList(vec![Token::Text("N/A".into())]),
+521                    ])),
+522                    Token::Text(" ".into()),
+523                ]),])
+524            ))
+525        );
+526    }
+527}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/prefix.rs.html b/src/i3status_rs/formatting/prefix.rs.html new file mode 100644 index 0000000000..629b408367 --- /dev/null +++ b/src/i3status_rs/formatting/prefix.rs.html @@ -0,0 +1,256 @@ +prefix.rs - source

i3status_rs/formatting/
prefix.rs

1use crate::errors::*;
+2use std::fmt;
+3use std::str::FromStr;
+4
+5/// SI prefix
+6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+7pub enum Prefix {
+8    /// `n`
+9    Nano,
+10    /// `u`
+11    Micro,
+12    /// `m`
+13    Milli,
+14    /// `1`
+15    One,
+16    /// `1i`
+17    /// `1i` is a special prefix which means "one but binary". `1i` is to `1` as `Ki` is to `K`.
+18    OneButBinary,
+19    /// `K`
+20    Kilo,
+21    /// `Ki`
+22    Kibi,
+23    /// `M`
+24    Mega,
+25    /// `Mi`
+26    Mebi,
+27    /// `G`
+28    Giga,
+29    /// `Gi`
+30    Gibi,
+31    /// `T`
+32    Tera,
+33    /// `Ti`
+34    Tebi,
+35}
+36
+37const MUL: [f64; 13] = [
+38    1e-9,
+39    1e-6,
+40    1e-3,
+41    1.0,
+42    1.0,
+43    1e3,
+44    1024.0,
+45    1e6,
+46    1024.0 * 1024.0,
+47    1e9,
+48    1024.0 * 1024.0 * 1024.0,
+49    1e12,
+50    1024.0 * 1024.0 * 1024.0 * 1024.0,
+51];
+52
+53impl Prefix {
+54    pub fn min_available() -> Self {
+55        Self::Nano
+56    }
+57
+58    pub fn max_available() -> Self {
+59        Self::Tebi
+60    }
+61
+62    pub fn max(self, other: Self) -> Self {
+63        if other > self { other } else { self }
+64    }
+65
+66    pub fn apply(self, value: f64) -> f64 {
+67        value / MUL[self as usize]
+68    }
+69
+70    pub fn eng(mut number: f64) -> Self {
+71        if number == 0.0 {
+72            Self::One
+73        } else {
+74            number = number.abs();
+75            if number > 1.0 {
+76                number = number.round();
+77            } else {
+78                let round_up_to = -(number.log10().ceil() as i32);
+79                let m = 10f64.powi(round_up_to);
+80                number = (number * m).round() / m;
+81            }
+82            match number.log10().div_euclid(3.) as i32 {
+83                i32::MIN..=-3 => Prefix::Nano,
+84                -2 => Prefix::Micro,
+85                -1 => Prefix::Milli,
+86                0 => Prefix::One,
+87                1 => Prefix::Kilo,
+88                2 => Prefix::Mega,
+89                3 => Prefix::Giga,
+90                4..=i32::MAX => Prefix::Tera,
+91            }
+92        }
+93    }
+94
+95    pub fn eng_binary(number: f64) -> Self {
+96        if number == 0.0 {
+97            Self::One
+98        } else {
+99            match number.abs().round().log2().div_euclid(10.) as i32 {
+100                i32::MIN..=0 => Prefix::OneButBinary,
+101                1 => Prefix::Kibi,
+102                2 => Prefix::Mebi,
+103                3 => Prefix::Gibi,
+104                4..=i32::MAX => Prefix::Tebi,
+105            }
+106        }
+107    }
+108
+109    pub fn is_binary(&self) -> bool {
+110        matches!(
+111            self,
+112            Self::OneButBinary | Self::Kibi | Self::Mebi | Self::Gibi | Self::Tebi
+113        )
+114    }
+115}
+116
+117impl fmt::Display for Prefix {
+118    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+119        f.write_str(match self {
+120            Self::Nano => "n",
+121            Self::Micro => "u",
+122            Self::Milli => "m",
+123            Self::One | Self::OneButBinary => "",
+124            Self::Kilo => "K",
+125            Self::Kibi => "Ki",
+126            Self::Mega => "M",
+127            Self::Mebi => "Mi",
+128            Self::Giga => "G",
+129            Self::Gibi => "Gi",
+130            Self::Tera => "T",
+131            Self::Tebi => "Ti",
+132        })
+133    }
+134}
+135
+136impl FromStr for Prefix {
+137    type Err = Error;
+138
+139    fn from_str(s: &str) -> Result<Self> {
+140        match s {
+141            "n" => Ok(Prefix::Nano),
+142            "u" => Ok(Prefix::Micro),
+143            "m" => Ok(Prefix::Milli),
+144            "1" => Ok(Prefix::One),
+145            "1i" => Ok(Prefix::OneButBinary),
+146            "K" => Ok(Prefix::Kilo),
+147            "Ki" => Ok(Prefix::Kibi),
+148            "M" => Ok(Prefix::Mega),
+149            "Mi" => Ok(Prefix::Mebi),
+150            "G" => Ok(Prefix::Giga),
+151            "Gi" => Ok(Prefix::Gibi),
+152            "T" => Ok(Prefix::Tera),
+153            "Ti" => Ok(Prefix::Tebi),
+154            x => Err(Error::new(format!("Unknown prefix: '{x}'"))),
+155        }
+156    }
+157}
+158
+159#[cfg(test)]
+160mod tests {
+161    use super::*;
+162
+163    #[test]
+164    fn eng() {
+165        assert_eq!(Prefix::eng(0.000_000_000_1), Prefix::Nano);
+166        assert_eq!(Prefix::eng(0.000_000_001), Prefix::Nano);
+167        assert_eq!(Prefix::eng(0.000_000_01), Prefix::Nano);
+168        assert_eq!(Prefix::eng(0.000_000_1), Prefix::Nano);
+169        assert_eq!(Prefix::eng(0.000_001), Prefix::Micro);
+170        assert_eq!(Prefix::eng(0.000_01), Prefix::Micro);
+171        assert_eq!(Prefix::eng(0.000_1), Prefix::Micro);
+172        assert_eq!(Prefix::eng(0.001), Prefix::Milli);
+173        assert_eq!(Prefix::eng(0.01), Prefix::Milli);
+174        assert_eq!(Prefix::eng(0.1), Prefix::Milli);
+175        assert_eq!(Prefix::eng(1.0), Prefix::One);
+176        assert_eq!(Prefix::eng(10.0), Prefix::One);
+177        assert_eq!(Prefix::eng(100.0), Prefix::One);
+178        assert_eq!(Prefix::eng(1_000.0), Prefix::Kilo);
+179        assert_eq!(Prefix::eng(10_000.0), Prefix::Kilo);
+180        assert_eq!(Prefix::eng(100_000.0), Prefix::Kilo);
+181        assert_eq!(Prefix::eng(1_000_000.0), Prefix::Mega);
+182        assert_eq!(Prefix::eng(10_000_000.0), Prefix::Mega);
+183        assert_eq!(Prefix::eng(100_000_000.0), Prefix::Mega);
+184        assert_eq!(Prefix::eng(1_000_000_000.0), Prefix::Giga);
+185        assert_eq!(Prefix::eng(10_000_000_000.0), Prefix::Giga);
+186        assert_eq!(Prefix::eng(100_000_000_000.0), Prefix::Giga);
+187        assert_eq!(Prefix::eng(1_000_000_000_000.0), Prefix::Tera);
+188        assert_eq!(Prefix::eng(10_000_000_000_000.0), Prefix::Tera);
+189        assert_eq!(Prefix::eng(100_000_000_000_000.0), Prefix::Tera);
+190        assert_eq!(Prefix::eng(1_000_000_000_000_000.0), Prefix::Tera);
+191    }
+192
+193    #[test]
+194    fn eng_round() {
+195        assert_eq!(Prefix::eng(0.000_000_000_09), Prefix::Nano);
+196        assert_eq!(Prefix::eng(0.000_000_000_9), Prefix::Nano);
+197        assert_eq!(Prefix::eng(0.000_000_009), Prefix::Nano);
+198        assert_eq!(Prefix::eng(0.000_000_09), Prefix::Nano);
+199        assert_eq!(Prefix::eng(0.000_000_9), Prefix::Micro);
+200        assert_eq!(Prefix::eng(0.000_009), Prefix::Micro);
+201        assert_eq!(Prefix::eng(0.000_09), Prefix::Micro);
+202        assert_eq!(Prefix::eng(0.000_9), Prefix::Milli);
+203        assert_eq!(Prefix::eng(0.009), Prefix::Milli);
+204        assert_eq!(Prefix::eng(0.09), Prefix::Milli);
+205        assert_eq!(Prefix::eng(0.9), Prefix::One);
+206        assert_eq!(Prefix::eng(9.9), Prefix::One);
+207        assert_eq!(Prefix::eng(99.9), Prefix::One);
+208        assert_eq!(Prefix::eng(999.9), Prefix::Kilo);
+209        assert_eq!(Prefix::eng(9_999.9), Prefix::Kilo);
+210        assert_eq!(Prefix::eng(99_999.9), Prefix::Kilo);
+211        assert_eq!(Prefix::eng(999_999.9), Prefix::Mega);
+212        assert_eq!(Prefix::eng(9_999_999.9), Prefix::Mega);
+213        assert_eq!(Prefix::eng(99_999_999.9), Prefix::Mega);
+214        assert_eq!(Prefix::eng(999_999_999.9), Prefix::Giga);
+215        assert_eq!(Prefix::eng(9_999_999_999.9), Prefix::Giga);
+216        assert_eq!(Prefix::eng(99_999_999_999.9), Prefix::Giga);
+217        assert_eq!(Prefix::eng(999_999_999_999.9), Prefix::Tera);
+218        assert_eq!(Prefix::eng(9_999_999_999_999.9), Prefix::Tera);
+219        assert_eq!(Prefix::eng(99_999_999_999_999.9), Prefix::Tera);
+220        assert_eq!(Prefix::eng(999_999_999_999_999.9), Prefix::Tera);
+221    }
+222
+223    #[test]
+224    fn eng_binary() {
+225        assert_eq!(Prefix::eng_binary(0.1), Prefix::OneButBinary);
+226        assert_eq!(Prefix::eng_binary(1.0), Prefix::OneButBinary);
+227        assert_eq!(Prefix::eng_binary((1 << 9) as f64), Prefix::OneButBinary);
+228        assert_eq!(Prefix::eng_binary((1 << 10) as f64), Prefix::Kibi);
+229        assert_eq!(Prefix::eng_binary((1 << 19) as f64), Prefix::Kibi);
+230        assert_eq!(Prefix::eng_binary((1 << 29) as f64), Prefix::Mebi);
+231        assert_eq!(Prefix::eng_binary((1 << 20) as f64), Prefix::Mebi);
+232        assert_eq!(Prefix::eng_binary((1 << 30) as f64), Prefix::Gibi);
+233        assert_eq!(Prefix::eng_binary((1_u64 << 39) as f64), Prefix::Gibi);
+234        assert_eq!(Prefix::eng_binary((1_u64 << 40) as f64), Prefix::Tebi);
+235        assert_eq!(Prefix::eng_binary((1_u64 << 49) as f64), Prefix::Tebi);
+236        assert_eq!(Prefix::eng_binary((1_u64 << 50) as f64), Prefix::Tebi);
+237    }
+238
+239    #[test]
+240    fn eng_binary_round() {
+241        assert_eq!(Prefix::eng_binary(0.9), Prefix::OneButBinary);
+242        assert_eq!(
+243            Prefix::eng_binary((1 << 9) as f64 - 0.1),
+244            Prefix::OneButBinary
+245        );
+246        assert_eq!(Prefix::eng_binary((1 << 10) as f64 - 0.1), Prefix::Kibi);
+247        assert_eq!(Prefix::eng_binary((1 << 19) as f64 - 0.1), Prefix::Kibi);
+248        assert_eq!(Prefix::eng_binary((1 << 29) as f64 - 0.1), Prefix::Mebi);
+249        assert_eq!(Prefix::eng_binary((1 << 20) as f64 - 0.1), Prefix::Mebi);
+250        assert_eq!(Prefix::eng_binary((1 << 30) as f64 - 0.1), Prefix::Gibi);
+251        assert_eq!(Prefix::eng_binary((1_u64 << 39) as f64 - 0.1), Prefix::Gibi);
+252        assert_eq!(Prefix::eng_binary((1_u64 << 40) as f64 - 0.1), Prefix::Tebi);
+253        assert_eq!(Prefix::eng_binary((1_u64 << 49) as f64 - 0.1), Prefix::Tebi);
+254        assert_eq!(Prefix::eng_binary((1_u64 << 50) as f64 - 0.1), Prefix::Tebi);
+255    }
+256}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/scheduling.rs.html b/src/i3status_rs/formatting/scheduling.rs.html new file mode 100644 index 0000000000..2fce7b73e4 --- /dev/null +++ b/src/i3status_rs/formatting/scheduling.rs.html @@ -0,0 +1,102 @@ +scheduling.rs - source

i3status_rs/formatting/
scheduling.rs

1use crate::BoxedStream;
+2use futures::stream::StreamExt as _;
+3use std::time::{Duration, Instant};
+4use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};
+5
+6pub fn manage_widgets_updates() -> (UnboundedSender<(usize, Vec<u64>)>, BoxedStream<Vec<usize>>) {
+7    let (intervals_tx, intervals_rx) = unbounded_channel::<(usize, Vec<u64>)>();
+8    struct State {
+9        time_anchor: Instant,
+10        last_update: u64,
+11        intervals_rx: UnboundedReceiver<(usize, Vec<u64>)>,
+12        intervals: Vec<(usize, Vec<u64>)>,
+13    }
+14    let stream = futures::stream::unfold(
+15        State {
+16            time_anchor: Instant::now(),
+17            last_update: 0,
+18            intervals_rx,
+19            intervals: Vec::new(),
+20        },
+21        |mut state| async move {
+22            loop {
+23                if state.intervals.is_empty() {
+24                    let (id, new_intervals) = state.intervals_rx.recv().await?;
+25                    state.intervals.retain(|(i, _)| *i != id);
+26                    if !new_intervals.is_empty() {
+27                        state.intervals.push((id, new_intervals));
+28                    }
+29                    continue;
+30                }
+31
+32                let time = state.time_anchor.elapsed().as_millis() as u64;
+33
+34                let mut blocks = Vec::new();
+35                let mut delay = 100000;
+36                for (id, intervals) in &state.intervals {
+37                    let block_delay = single_block_next_update(intervals, time, state.last_update);
+38                    if block_delay < delay {
+39                        delay = block_delay;
+40                        blocks.clear();
+41                    }
+42                    if block_delay == delay {
+43                        blocks.push(*id);
+44                    }
+45                }
+46
+47                if delay == 0 {
+48                    state.last_update = time;
+49                    return Some((blocks, state));
+50                }
+51
+52                if let Ok(Some((id, new_intervals))) =
+53                    tokio::time::timeout(Duration::from_millis(delay), state.intervals_rx.recv())
+54                        .await
+55                {
+56                    state.intervals.retain(|(i, _)| *i != id);
+57                    if !new_intervals.is_empty() {
+58                        state.intervals.push((id, new_intervals));
+59                    }
+60                }
+61            }
+62        },
+63    )
+64    .boxed();
+65    (intervals_tx, stream)
+66}
+67
+68fn single_block_next_update(intervals: &[u64], time: u64, last_update: u64) -> u64 {
+69    fn next_update(time: u64, interval: u64) -> u64 {
+70        time + interval - time % interval
+71    }
+72    let mut time_to_next = u64::MAX;
+73    for &interval in intervals {
+74        if next_update(last_update, interval) <= time {
+75            return 0;
+76        }
+77        time_to_next = time_to_next.min(next_update(time, interval) - time);
+78    }
+79    time_to_next
+80}
+81
+82#[cfg(test)]
+83mod tests {
+84    use super::*;
+85
+86    #[test]
+87    fn single_block() {
+88        //     0   100  200  300  400  500  600  700  800  900  1000
+89        //     |    |    |    |    |    |    |    |    |    |    |
+90        // 200 x         x         x         x         x         x
+91        // 300 x              x              x              x
+92        // 500 x                        x                        x
+93        let intervals = &[200, 300, 500];
+94        assert_eq!(single_block_next_update(intervals, 0, 0), 200);
+95        assert_eq!(single_block_next_update(intervals, 50, 0), 150);
+96        assert_eq!(single_block_next_update(intervals, 210, 50), 0);
+97        assert_eq!(single_block_next_update(intervals, 290, 210), 10);
+98        assert_eq!(single_block_next_update(intervals, 300, 290), 0);
+99        assert_eq!(single_block_next_update(intervals, 300, 300), 100);
+100        assert_eq!(single_block_next_update(intervals, 800, 300), 0);
+101    }
+102}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/template.rs.html b/src/i3status_rs/formatting/template.rs.html new file mode 100644 index 0000000000..b280afeda5 --- /dev/null +++ b/src/i3status_rs/formatting/template.rs.html @@ -0,0 +1,208 @@ +template.rs - source

i3status_rs/formatting/
template.rs

1use super::formatter::{Formatter, new_formatter};
+2use super::{FormatError, Fragment, Values, parse};
+3use crate::config::SharedConfig;
+4use crate::errors::*;
+5
+6use std::str::FromStr;
+7use std::sync::Arc;
+8
+9#[derive(Debug, Clone)]
+10pub struct FormatTemplate(Arc<[TokenList]>);
+11
+12impl Default for FormatTemplate {
+13    fn default() -> Self {
+14        Self(Arc::new([]))
+15    }
+16}
+17
+18#[derive(Debug)]
+19pub struct TokenList(pub Vec<Token>);
+20
+21#[derive(Debug)]
+22pub enum Token {
+23    Text(String),
+24    Recursive(FormatTemplate),
+25    Placeholder {
+26        name: String,
+27        formatter: Option<Box<dyn Formatter>>,
+28    },
+29    Icon {
+30        name: String,
+31    },
+32}
+33
+34impl FormatTemplate {
+35    pub fn contains_key(&self, key: &str) -> bool {
+36        self.0.iter().any(|token_list| {
+37            token_list.0.iter().any(|token| match token {
+38                Token::Placeholder { name, .. } => name == key,
+39                Token::Recursive(rec) => rec.contains_key(key),
+40                _ => false,
+41            })
+42        })
+43    }
+44
+45    pub fn render(
+46        &self,
+47        values: &Values,
+48        config: &SharedConfig,
+49    ) -> Result<Vec<Fragment>, FormatError> {
+50        for (i, token_list) in self.0.iter().enumerate() {
+51            match token_list.render(values, config) {
+52                Ok(res) => return Ok(res),
+53                Err(
+54                    FormatError::PlaceholderNotFound(_)
+55                    | FormatError::IncompatibleFormatter { .. }
+56                    | FormatError::NumberOutOfRange(_),
+57                ) if i != self.0.len() - 1 => (),
+58                Err(e) => return Err(e),
+59            }
+60        }
+61        Ok(Vec::new())
+62    }
+63
+64    pub fn init_intervals(&self, intervals: &mut Vec<u64>) {
+65        for tl in self.0.iter() {
+66            for t in &tl.0 {
+67                match t {
+68                    Token::Recursive(r) => r.init_intervals(intervals),
+69                    Token::Placeholder {
+70                        formatter: Some(f), ..
+71                    } => {
+72                        if let Some(i) = f.interval() {
+73                            intervals.push(i.as_millis() as u64);
+74                        }
+75                    }
+76                    _ => (),
+77                }
+78            }
+79        }
+80    }
+81}
+82
+83impl TokenList {
+84    pub fn render(
+85        &self,
+86        values: &Values,
+87        config: &SharedConfig,
+88    ) -> Result<Vec<Fragment>, FormatError> {
+89        let mut retval = Vec::new();
+90        let mut cur = Fragment::default();
+91        for token in &self.0 {
+92            match token {
+93                Token::Text(text) => {
+94                    if cur.metadata.is_default() {
+95                        cur.text.push_str(text);
+96                    } else {
+97                        if !cur.text.is_empty() {
+98                            retval.push(cur);
+99                        }
+100                        cur = text.clone().into();
+101                    }
+102                }
+103                Token::Recursive(rec) => {
+104                    if !cur.text.is_empty() {
+105                        retval.push(cur);
+106                    }
+107                    retval.extend(rec.render(values, config)?);
+108                    cur = retval.pop().unwrap_or_default();
+109                }
+110                Token::Placeholder { name, formatter } => {
+111                    let value = values
+112                        .get(name.as_str())
+113                        .ok_or_else(|| FormatError::PlaceholderNotFound(name.into()))?;
+114                    let formatter = formatter
+115                        .as_ref()
+116                        .map(Box::as_ref)
+117                        .unwrap_or_else(|| value.default_formatter());
+118                    let formatted = formatter.format(&value.inner, config)?;
+119                    if value.metadata == cur.metadata {
+120                        cur.text.push_str(&formatted);
+121                    } else {
+122                        if !cur.text.is_empty() {
+123                            retval.push(cur);
+124                        }
+125                        cur = Fragment {
+126                            text: formatted,
+127                            metadata: value.metadata,
+128                        };
+129                    }
+130                }
+131                Token::Icon { name } => {
+132                    let icon = config.get_icon(name, None)?;
+133                    if cur.metadata.is_default() {
+134                        cur.text.push_str(&icon);
+135                    } else {
+136                        if !cur.text.is_empty() {
+137                            retval.push(cur);
+138                        }
+139                        cur = icon.into();
+140                    }
+141                }
+142            }
+143        }
+144
+145        if !cur.text.is_empty() {
+146            retval.push(cur);
+147        }
+148
+149        Ok(retval)
+150    }
+151}
+152
+153impl FromStr for FormatTemplate {
+154    type Err = Error;
+155
+156    fn from_str(s: &str) -> Result<Self> {
+157        parse::parse_full(s)
+158            .and_then(TryInto::try_into)
+159            .error("Incorrect format template")
+160    }
+161}
+162
+163impl TryFrom<parse::FormatTemplate<'_>> for FormatTemplate {
+164    type Error = Error;
+165
+166    fn try_from(value: parse::FormatTemplate) -> Result<Self, Self::Error> {
+167        value
+168            .0
+169            .into_iter()
+170            .map(TryInto::try_into)
+171            .collect::<Result<Arc<[_]>>>()
+172            .map(Self)
+173    }
+174}
+175
+176impl TryFrom<parse::TokenList<'_>> for TokenList {
+177    type Error = Error;
+178
+179    fn try_from(value: parse::TokenList) -> Result<Self, Self::Error> {
+180        value
+181            .0
+182            .into_iter()
+183            .map(TryInto::try_into)
+184            .collect::<Result<Vec<_>>>()
+185            .map(Self)
+186    }
+187}
+188
+189impl TryFrom<parse::Token<'_>> for Token {
+190    type Error = Error;
+191
+192    fn try_from(value: parse::Token) -> Result<Self, Self::Error> {
+193        Ok(match value {
+194            parse::Token::Text(text) => Self::Text(text),
+195            parse::Token::Placeholder(placeholder) => Self::Placeholder {
+196                name: placeholder.name.to_owned(),
+197                formatter: placeholder
+198                    .formatter
+199                    .map(|fmt| new_formatter(fmt.name, &fmt.args))
+200                    .transpose()?,
+201            },
+202            parse::Token::Icon(icon) => Self::Icon {
+203                name: icon.to_owned(),
+204            },
+205            parse::Token::Recursive(rec) => Self::Recursive(rec.try_into()?),
+206        })
+207    }
+208}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/unit.rs.html b/src/i3status_rs/formatting/unit.rs.html new file mode 100644 index 0000000000..d63cfc19b5 --- /dev/null +++ b/src/i3status_rs/formatting/unit.rs.html @@ -0,0 +1,77 @@ +unit.rs - source

i3status_rs/formatting/
unit.rs

1use std::fmt;
+2use std::str::FromStr;
+3
+4use super::prefix::Prefix;
+5use crate::errors::*;
+6
+7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+8pub enum Unit {
+9    /// `B`
+10    Bytes,
+11    /// `b`
+12    Bits,
+13    /// `%`
+14    Percents,
+15    /// `deg`
+16    Degrees,
+17    /// `s`
+18    Seconds,
+19    /// `W`
+20    Watts,
+21    /// `Hz`
+22    Hertz,
+23    /// ``
+24    None,
+25}
+26
+27impl fmt::Display for Unit {
+28    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+29        f.write_str(match self {
+30            Self::Bytes => "B",
+31            Self::Bits => "b",
+32            Self::Percents => "%",
+33            Self::Degrees => "°",
+34            Self::Seconds => "s",
+35            Self::Watts => "W",
+36            Self::Hertz => "Hz",
+37            Self::None => "",
+38        })
+39    }
+40}
+41
+42impl FromStr for Unit {
+43    type Err = Error;
+44
+45    fn from_str(s: &str) -> Result<Self> {
+46        match s {
+47            "B" => Ok(Unit::Bytes),
+48            "b" => Ok(Unit::Bits),
+49            "%" => Ok(Unit::Percents),
+50            "deg" => Ok(Unit::Degrees),
+51            "s" => Ok(Unit::Seconds),
+52            "W" => Ok(Unit::Watts),
+53            "Hz" => Ok(Unit::Hertz),
+54            "" => Ok(Unit::None),
+55            x => Err(Error::new(format!("Unknown unit: '{x}'"))),
+56        }
+57    }
+58}
+59
+60impl Unit {
+61    pub fn convert(self, value: f64, unit: Self) -> Result<f64> {
+62        match (self, unit) {
+63            (x, y) if x == y => Ok(value),
+64            (Self::Bytes, Self::Bits) => Ok(value * 8.),
+65            (Self::Bits, Self::Bytes) => Ok(value / 8.),
+66            _ => Err(Error::new(format!("Failed to convert '{self}' to '{unit}"))),
+67        }
+68    }
+69
+70    pub fn clamp_prefix(self, prefix: Prefix) -> Prefix {
+71        match self {
+72            Self::Bytes | Self::Bits => prefix.max(Prefix::One),
+73            Self::Percents | Self::Degrees | Self::None => Prefix::One,
+74            _ => prefix,
+75        }
+76    }
+77}
\ No newline at end of file diff --git a/src/i3status_rs/formatting/value.rs.html b/src/i3status_rs/formatting/value.rs.html new file mode 100644 index 0000000000..19801525bf --- /dev/null +++ b/src/i3status_rs/formatting/value.rs.html @@ -0,0 +1,160 @@ +value.rs - source

i3status_rs/formatting/
value.rs

1use std::borrow::Cow;
+2use std::time::Duration;
+3
+4use super::Metadata;
+5use super::formatter;
+6use super::unit::Unit;
+7use chrono::{DateTime, Utc};
+8use chrono_tz::Tz;
+9
+10#[derive(Debug, Clone)]
+11pub struct Value {
+12    pub inner: ValueInner,
+13    pub metadata: Metadata,
+14}
+15
+16#[derive(Debug, Clone)]
+17pub enum ValueInner {
+18    Text(String),
+19    Icon(Cow<'static, str>, Option<f64>),
+20    Number { val: f64, unit: Unit },
+21    Datetime(DateTime<Utc>, Option<Tz>),
+22    Duration(Duration),
+23    Flag,
+24}
+25
+26impl ValueInner {
+27    pub fn type_name(&self) -> &'static str {
+28        match self {
+29            ValueInner::Text(..) => "Text",
+30            ValueInner::Icon(..) => "Icon",
+31            ValueInner::Number { .. } => "Number",
+32            ValueInner::Datetime(..) => "Datetime",
+33            ValueInner::Duration(..) => "Duration",
+34            ValueInner::Flag => "Flag",
+35        }
+36    }
+37}
+38
+39pub trait IntoF64 {
+40    fn into_f64(self) -> f64;
+41}
+42
+43macro_rules! impl_into_f64 {
+44    ($($t:ty),+) => {
+45        $(
+46            impl IntoF64 for $t {
+47                fn into_f64(self) -> f64 {
+48                    self as _
+49                }
+50            }
+51        )+
+52    }
+53}
+54impl_into_f64!(f64, f32, i64, u64, i32, u32, i16, u16, i8, u8, usize, isize);
+55
+56/// Constructors
+57impl Value {
+58    pub fn new(val: ValueInner) -> Self {
+59        Self {
+60            inner: val,
+61            metadata: Default::default(),
+62        }
+63    }
+64
+65    pub fn flag() -> Self {
+66        Self::new(ValueInner::Flag)
+67    }
+68
+69    pub fn datetime(datetime: DateTime<Utc>, tz: Option<Tz>) -> Self {
+70        Self::new(ValueInner::Datetime(datetime, tz))
+71    }
+72
+73    pub fn duration(duration: Duration) -> Self {
+74        Self::new(ValueInner::Duration(duration))
+75    }
+76
+77    pub fn icon<S>(name: S) -> Self
+78    where
+79        S: Into<Cow<'static, str>>,
+80    {
+81        Self::new(ValueInner::Icon(name.into(), None))
+82    }
+83
+84    pub fn icon_progression<S>(name: S, value: f64) -> Self
+85    where
+86        S: Into<Cow<'static, str>>,
+87    {
+88        Self::new(ValueInner::Icon(name.into(), Some(value)))
+89    }
+90    pub fn icon_progression_bound<S>(name: S, value: f64, low: f64, high: f64) -> Self
+91    where
+92        S: Into<Cow<'static, str>>,
+93    {
+94        Self::icon_progression(name, (value.clamp(low, high) - low) / (high - low))
+95    }
+96
+97    pub fn text(text: String) -> Self {
+98        Self::new(ValueInner::Text(text))
+99    }
+100
+101    pub fn number_unit(val: impl IntoF64, unit: Unit) -> Self {
+102        Self::new(ValueInner::Number {
+103            val: val.into_f64(),
+104            unit,
+105        })
+106    }
+107
+108    pub fn bytes(val: impl IntoF64) -> Self {
+109        Self::number_unit(val, Unit::Bytes)
+110    }
+111    pub fn bits(val: impl IntoF64) -> Self {
+112        Self::number_unit(val, Unit::Bits)
+113    }
+114    pub fn percents(val: impl IntoF64) -> Self {
+115        Self::number_unit(val, Unit::Percents)
+116    }
+117    pub fn degrees(val: impl IntoF64) -> Self {
+118        Self::number_unit(val, Unit::Degrees)
+119    }
+120    pub fn seconds(val: impl IntoF64) -> Self {
+121        Self::number_unit(val, Unit::Seconds)
+122    }
+123    pub fn watts(val: impl IntoF64) -> Self {
+124        Self::number_unit(val, Unit::Watts)
+125    }
+126    pub fn hertz(val: impl IntoF64) -> Self {
+127        Self::number_unit(val, Unit::Hertz)
+128    }
+129    pub fn number(val: impl IntoF64) -> Self {
+130        Self::number_unit(val, Unit::None)
+131    }
+132}
+133
+134/// Set options
+135impl Value {
+136    pub fn with_instance(mut self, instance: &'static str) -> Self {
+137        self.metadata.instance = Some(instance);
+138        self
+139    }
+140
+141    pub fn underline(mut self, val: bool) -> Self {
+142        self.metadata.underline = val;
+143        self
+144    }
+145
+146    pub fn italic(mut self, val: bool) -> Self {
+147        self.metadata.italic = val;
+148        self
+149    }
+150
+151    pub fn default_formatter(&self) -> &'static dyn formatter::Formatter {
+152        match &self.inner {
+153            ValueInner::Text(_) | ValueInner::Icon(..) => &formatter::DEFAULT_STRING_FORMATTER,
+154            ValueInner::Number { .. } => &formatter::DEFAULT_NUMBER_FORMATTER,
+155            ValueInner::Datetime { .. } => &*formatter::DEFAULT_DATETIME_FORMATTER,
+156            ValueInner::Duration { .. } => &formatter::DEFAULT_DURATION_FORMATTER,
+157            ValueInner::Flag => &formatter::DEFAULT_FLAG_FORMATTER,
+158        }
+159    }
+160}
\ No newline at end of file diff --git a/src/i3status_rs/geolocator.rs.html b/src/i3status_rs/geolocator.rs.html new file mode 100644 index 0000000000..1f8685d69f --- /dev/null +++ b/src/i3status_rs/geolocator.rs.html @@ -0,0 +1,175 @@ +geolocator.rs - source

i3status_rs/
geolocator.rs

1//! Geolocation service
+2//!
+3//! This global module can be used to provide geolocation information
+4//! to blocks that support it.
+5//!
+6//! ipapi.co is the default geolocator service.
+7//!
+8//! # Configuration
+9//!
+10//! # ipapi.co Options
+11//!
+12//! Key | Values | Required | Default
+13//! ----|--------|----------|--------
+14//! `geolocator` | `ipapi` | Yes | None
+15//!
+16//! # Ip2Location.io Options
+17//!
+18//! Key | Values | Required | Default
+19//! ----|--------|----------|--------
+20//! `geolocator` | `ip2location` | Yes | None
+21//! `api_key` | Your Ip2Location.io API key. | No | None
+22//!
+23//! An api key is not required to get back basic information from ip2location.io.
+24//! However, to get more additional information, an api key is required.
+25//! See [pricing](https://www.ip2location.io/pricing) for more information.
+26//!
+27//! The `api_key` option can be omitted from configuration, in which case it
+28//! can be provided in the environment variable `IP2LOCATION_API_KEY`
+29//!
+30//!
+31//! # Examples
+32//!
+33//! Use the default geolocator service:
+34//!
+35//! ```toml
+36//! [geolocator]
+37//! geolocator = "ipapi"
+38//! ```
+39//!
+40//! Use Ip2Location.io
+41//!
+42//! ```toml
+43//! [geolocator]
+44//! geolocator = "ip2location"
+45//! api_key = "XXX"
+46//! ```
+47
+48use crate::errors::{Error, ErrorContext as _, Result, StdError};
+49use std::borrow::Cow;
+50use std::fmt;
+51use std::sync::{Arc, Mutex};
+52use std::time::{Duration, Instant};
+53
+54use serde::Deserialize;
+55use smart_default::SmartDefault;
+56
+57mod ip2location;
+58mod ipapi;
+59
+60#[derive(Debug)]
+61struct AutolocateResult {
+62    location: IPAddressInfo,
+63    timestamp: Instant,
+64}
+65
+66#[derive(Deserialize, Clone, Default, Debug)]
+67pub struct IPAddressInfo {
+68    // Required fields
+69    pub ip: String,
+70    pub latitude: f64,
+71    pub longitude: f64,
+72    pub city: String,
+73
+74    // Optional fields
+75    pub version: Option<String>,
+76    pub region: Option<String>,
+77    pub region_code: Option<String>,
+78    pub country: Option<String>,
+79    pub country_name: Option<String>,
+80    pub country_code: Option<String>,
+81    pub country_code_iso3: Option<String>,
+82    pub country_capital: Option<String>,
+83    pub country_tld: Option<String>,
+84    pub continent_code: Option<String>,
+85    pub in_eu: Option<bool>,
+86    pub postal: Option<String>,
+87    pub timezone: Option<String>,
+88    pub utc_offset: Option<String>,
+89    pub country_calling_code: Option<String>,
+90    pub currency: Option<String>,
+91    pub currency_name: Option<String>,
+92    pub languages: Option<String>,
+93    pub country_area: Option<f64>,
+94    pub country_population: Option<f64>,
+95    pub asn: Option<String>,
+96    pub org: Option<String>,
+97}
+98
+99#[derive(Default, Debug, Deserialize)]
+100#[serde(from = "GeolocatorBackend")]
+101pub struct Geolocator {
+102    backend: GeolocatorBackend,
+103    last_autolocate: Mutex<Option<AutolocateResult>>,
+104}
+105
+106impl Geolocator {
+107    pub fn name(&self) -> Cow<'static, str> {
+108        self.backend.name()
+109    }
+110
+111    /// No-op if last API call was made in the last `interval` seconds.
+112    pub async fn find_ip_location(
+113        &self,
+114        client: &reqwest::Client,
+115        interval: Duration,
+116    ) -> Result<IPAddressInfo> {
+117        {
+118            let guard = self.last_autolocate.lock().unwrap();
+119            if let Some(cached) = &*guard
+120                && cached.timestamp.elapsed() < interval
+121            {
+122                return Ok(cached.location.clone());
+123            }
+124        }
+125
+126        let location = self.backend.get_info(client).await?;
+127
+128        {
+129            let mut guard = self.last_autolocate.lock().unwrap();
+130            *guard = Some(AutolocateResult {
+131                location: location.clone(),
+132                timestamp: Instant::now(),
+133            });
+134        }
+135
+136        Ok(location)
+137    }
+138}
+139
+140#[derive(Deserialize, Debug, SmartDefault, Clone)]
+141#[serde(tag = "geolocator", rename_all = "lowercase", deny_unknown_fields)]
+142pub enum GeolocatorBackend {
+143    #[default]
+144    Ipapi(ipapi::Config),
+145    Ip2Location(ip2location::Config),
+146}
+147
+148impl GeolocatorBackend {
+149    fn name(&self) -> Cow<'static, str> {
+150        match self {
+151            GeolocatorBackend::Ipapi(_) => ipapi::Ipapi.name(),
+152            GeolocatorBackend::Ip2Location(_) => ip2location::Ip2Location.name(),
+153        }
+154    }
+155
+156    async fn get_info(&self, client: &reqwest::Client) -> Result<IPAddressInfo> {
+157        match self {
+158            GeolocatorBackend::Ipapi(_) => ipapi::Ipapi.get_info(client).await,
+159            GeolocatorBackend::Ip2Location(config) => {
+160                ip2location::Ip2Location
+161                    .get_info(client, config.api_key.as_ref())
+162                    .await
+163            }
+164        }
+165    }
+166}
+167
+168impl From<GeolocatorBackend> for Geolocator {
+169    fn from(backend: GeolocatorBackend) -> Self {
+170        Self {
+171            backend,
+172            last_autolocate: Mutex::new(None),
+173        }
+174    }
+175}
\ No newline at end of file diff --git a/src/i3status_rs/geolocator/ip2location.rs.html b/src/i3status_rs/geolocator/ip2location.rs.html new file mode 100644 index 0000000000..e190f91c3a --- /dev/null +++ b/src/i3status_rs/geolocator/ip2location.rs.html @@ -0,0 +1,235 @@ +ip2location.rs - source

i3status_rs/geolocator/
ip2location.rs

1use super::*;
+2
+3const IP_API_URL: &str = "https://api.ip2location.io/";
+4pub(super) const API_KEY_ENV: &str = "IP2LOCATION_API_KEY";
+5
+6#[derive(Deserialize, Debug, Default, Clone)]
+7#[serde(deny_unknown_fields)]
+8pub struct Config {
+9    #[serde(default = "getenv_api_key")]
+10    pub api_key: Option<String>,
+11}
+12
+13fn getenv_api_key() -> Option<String> {
+14    std::env::var(API_KEY_ENV).ok()
+15}
+16
+17#[derive(Deserialize)]
+18struct ApiResponse {
+19    #[serde(flatten)]
+20    data: Option<Ip2LocationAddressInfo>,
+21    #[serde(default)]
+22    error: Option<ApiError>,
+23}
+24
+25#[derive(Deserialize)]
+26struct Ip2LocationAddressInfo {
+27    // Provided without api key
+28    ip: String,
+29    country_code: String,
+30    country_name: String,
+31    region_name: String,
+32    city_name: String,
+33    latitude: f64,
+34    longitude: f64,
+35    zip_code: String,
+36    time_zone: String,
+37    asn: String,
+38    // #[serde(rename = "as")]
+39    // as_: String,
+40    // is_proxy: bool,
+41
+42    // Requires api key
+43    // isp
+44    // domain
+45    // net_speed
+46    // idd_code
+47    // area_code
+48    // weather_station_code
+49    // weather_station_name
+50    // mcc
+51    // mnc
+52    // mobile_brand
+53    // elevation
+54    // usage_type
+55    // address_type
+56    // ads_category
+57    // ads_category_name
+58    // district
+59    continent: Option<Continent>,
+60    country: Option<Country>,
+61    region: Option<Region>,
+62    // city.name
+63    // city.translation
+64    time_zone_info: Option<TimeZoneInfo>,
+65    // geotargeting.metro
+66    // fraud_score
+67    // proxy.last_seen
+68    // proxy.proxy_type
+69    // proxy.threat
+70    // proxy.provider
+71    // proxy.is_vpn
+72    // proxy.is_tor
+73    // proxy.is_data_center
+74    // proxy.is_public_proxy
+75    // proxy.is_web_proxy
+76    // proxy.is_web_crawler
+77    // proxy.is_residential_proxy
+78    // proxy.is_spammer
+79    // proxy.is_scanner
+80    // proxy.is_botnet
+81    // proxy.is_consumer_privacy_network
+82    // proxy.is_enterprise_private_network
+83}
+84
+85#[derive(Deserialize)]
+86struct Continent {
+87    // name,
+88    code: String,
+89    // hemisphere,
+90    // translation,
+91}
+92
+93#[derive(Deserialize)]
+94struct Country {
+95    // name: String,
+96    alpha3_code: String,
+97    // numeric_code: String,
+98    // demonym: String,
+99    // flag: String,
+100    capital: String,
+101    total_area: f64,
+102    population: f64,
+103    currency: Currency,
+104    language: Language,
+105    tld: String,
+106    // translation,
+107}
+108
+109#[derive(Deserialize)]
+110struct Currency {
+111    name: String,
+112    code: String,
+113    // translation,
+114}
+115#[derive(Deserialize)]
+116struct Language {
+117    // name: String,
+118    code: String,
+119}
+120#[derive(Deserialize)]
+121struct Region {
+122    // name: String,
+123    code: String,
+124    // translation,
+125}
+126
+127#[derive(Deserialize)]
+128struct TimeZoneInfo {
+129    olson: String,
+130    // current_time: String
+131    // gmt_offset: String
+132    // is_dst: String
+133    // sunrise: String
+134    // sunset: String
+135}
+136
+137impl From<Ip2LocationAddressInfo> for IPAddressInfo {
+138    fn from(val: Ip2LocationAddressInfo) -> Self {
+139        let mut info = IPAddressInfo {
+140            ip: val.ip,
+141            city: val.city_name,
+142            latitude: val.latitude,
+143            longitude: val.longitude,
+144            region: Some(val.region_name),
+145            country: Some(val.country_code.clone()),
+146            country_name: Some(val.country_name),
+147            country_code: Some(val.country_code),
+148            postal: Some(val.zip_code),
+149            utc_offset: Some(val.time_zone),
+150            asn: Some(val.asn),
+151            ..Default::default()
+152        };
+153
+154        if let Some(region) = val.region {
+155            info.region_code = Some(region.code);
+156        }
+157
+158        if let Some(country) = val.country {
+159            info.country_area = Some(country.total_area);
+160            info.country_population = Some(country.population);
+161            info.currency = Some(country.currency.code);
+162            info.currency_name = Some(country.currency.name);
+163            info.languages = Some(country.language.code);
+164            info.country_tld = Some(country.tld);
+165            info.country_capital = Some(country.capital);
+166            info.country_code_iso3 = Some(country.alpha3_code);
+167        }
+168
+169        if let Some(continent) = val.continent {
+170            info.continent_code = Some(continent.code);
+171        }
+172
+173        if let Some(time_zone_info) = val.time_zone_info {
+174            info.timezone = Some(time_zone_info.olson);
+175        }
+176
+177        info
+178    }
+179}
+180
+181#[derive(Deserialize, Default, Debug)]
+182struct ApiError {
+183    error_code: u32,
+184    error_message: String,
+185}
+186
+187impl fmt::Display for ApiError {
+188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+189        f.write_fmt(format_args!(
+190            "Error {}: {}",
+191            self.error_code, self.error_message
+192        ))
+193    }
+194}
+195impl StdError for ApiError {}
+196
+197pub struct Ip2Location;
+198
+199impl Ip2Location {
+200    pub fn name(&self) -> Cow<'static, str> {
+201        Cow::Borrowed("ip2location.io")
+202    }
+203
+204    pub async fn get_info(
+205        &self,
+206        client: &reqwest::Client,
+207        api_key: Option<&String>,
+208    ) -> Result<IPAddressInfo> {
+209        let mut request_builder = client.get(IP_API_URL);
+210
+211        if let Some(api_key) = api_key {
+212            request_builder = request_builder.query(&[("key", api_key)]);
+213        }
+214
+215        let response: ApiResponse = request_builder
+216            .send()
+217            .await
+218            .error("Failed during request for current location")?
+219            .json()
+220            .await
+221            .error("Failed while parsing location API result")?;
+222
+223        if let Some(error) = response.error {
+224            Err(Error {
+225                message: Some("ip2location.io error".into()),
+226                cause: Some(Arc::new(error)),
+227            })
+228        } else {
+229            Ok(response
+230                .data
+231                .error("Failed while parsing location API result")?
+232                .into())
+233        }
+234    }
+235}
\ No newline at end of file diff --git a/src/i3status_rs/geolocator/ipapi.rs.html b/src/i3status_rs/geolocator/ipapi.rs.html new file mode 100644 index 0000000000..37a087e4d3 --- /dev/null +++ b/src/i3status_rs/geolocator/ipapi.rs.html @@ -0,0 +1,124 @@ +ipapi.rs - source

i3status_rs/geolocator/
ipapi.rs

1use super::*;
+2
+3const IP_API_URL: &str = "https://ipapi.co/json";
+4
+5// This config is here just to make sure that no other config is provided
+6#[derive(Deserialize, Debug, Default, Clone)]
+7#[serde(deny_unknown_fields)]
+8pub struct Config {}
+9
+10#[derive(Deserialize)]
+11struct ApiResponse {
+12    #[serde(flatten)]
+13    data: Option<IpApiAddressInfo>,
+14    #[serde(default)]
+15    error: bool,
+16    #[serde(default)]
+17    reason: ApiError,
+18}
+19
+20#[derive(Deserialize, Default)]
+21#[serde(default)]
+22struct IpApiAddressInfo {
+23    ip: String,
+24    version: String,
+25    city: String,
+26    region: String,
+27    region_code: String,
+28    country: String,
+29    country_name: String,
+30    country_code: String,
+31    country_code_iso3: String,
+32    country_capital: String,
+33    country_tld: String,
+34    continent_code: String,
+35    in_eu: bool,
+36    postal: Option<String>,
+37    latitude: f64,
+38    longitude: f64,
+39    timezone: String,
+40    utc_offset: String,
+41    country_calling_code: String,
+42    currency: String,
+43    currency_name: String,
+44    languages: String,
+45    country_area: f64,
+46    country_population: f64,
+47    asn: String,
+48    org: String,
+49}
+50
+51impl From<IpApiAddressInfo> for IPAddressInfo {
+52    fn from(val: IpApiAddressInfo) -> Self {
+53        IPAddressInfo {
+54            ip: val.ip,
+55            version: Some(val.version),
+56            city: val.city,
+57            region: Some(val.region),
+58            region_code: Some(val.region_code),
+59            country: Some(val.country),
+60            country_name: Some(val.country_name),
+61            country_code: Some(val.country_code),
+62            country_code_iso3: Some(val.country_code_iso3),
+63            country_capital: Some(val.country_capital),
+64            country_tld: Some(val.country_tld),
+65            continent_code: Some(val.continent_code),
+66            in_eu: Some(val.in_eu),
+67            postal: val.postal,
+68            latitude: val.latitude,
+69            longitude: val.longitude,
+70            timezone: Some(val.timezone),
+71            utc_offset: Some(val.utc_offset),
+72            country_calling_code: Some(val.country_calling_code),
+73            currency: Some(val.currency),
+74            currency_name: Some(val.currency_name),
+75            languages: Some(val.languages),
+76            country_area: Some(val.country_area),
+77            country_population: Some(val.country_population),
+78            asn: Some(val.asn),
+79            org: Some(val.org),
+80        }
+81    }
+82}
+83
+84#[derive(Deserialize, Default, Debug)]
+85#[serde(transparent)]
+86struct ApiError(Option<String>);
+87
+88impl fmt::Display for ApiError {
+89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+90        f.write_str(self.0.as_deref().unwrap_or("Unknown Error"))
+91    }
+92}
+93impl StdError for ApiError {}
+94
+95pub struct Ipapi;
+96
+97impl Ipapi {
+98    pub fn name(&self) -> Cow<'static, str> {
+99        Cow::Borrowed("ipapi.co")
+100    }
+101
+102    pub async fn get_info(&self, client: &reqwest::Client) -> Result<IPAddressInfo> {
+103        let response: ApiResponse = client
+104            .get(IP_API_URL)
+105            .send()
+106            .await
+107            .error("Failed during request for current location")?
+108            .json()
+109            .await
+110            .error("Failed while parsing location API result")?;
+111
+112        if response.error {
+113            Err(Error {
+114                message: Some("ipapi.co error".into()),
+115                cause: Some(Arc::new(response.reason)),
+116            })
+117        } else {
+118            Ok(response
+119                .data
+120                .error("Failed while parsing location API result")?
+121                .into())
+122        }
+123    }
+124}
\ No newline at end of file diff --git a/src/i3status_rs/icons.rs.html b/src/i3status_rs/icons.rs.html new file mode 100644 index 0000000000..a9f523a036 --- /dev/null +++ b/src/i3status_rs/icons.rs.html @@ -0,0 +1,167 @@ +icons.rs - source

i3status_rs/
icons.rs

1use crate::errors::*;
+2use crate::util;
+3use serde::Deserialize;
+4use std::collections::HashMap;
+5
+6#[derive(Deserialize, Debug, Clone)]
+7#[serde(try_from = "IconsConfigRaw")]
+8pub struct Icons(pub HashMap<String, Icon>);
+9
+10#[derive(Deserialize, Debug, Clone)]
+11#[serde(untagged)]
+12pub enum Icon {
+13    Single(String),
+14    Progression(Vec<String>),
+15}
+16
+17impl From<&'static str> for Icon {
+18    fn from(value: &'static str) -> Self {
+19        Self::Single(value.into())
+20    }
+21}
+22
+23impl<const N: usize> From<[&str; N]> for Icon {
+24    fn from(value: [&str; N]) -> Self {
+25        Self::Progression(value.iter().map(|s| s.to_string()).collect())
+26    }
+27}
+28
+29impl Default for Icons {
+30    fn default() -> Self {
+31        // "none" icon set
+32        Self(map! {
+33            "backlight" => "BRIGHT",
+34            "bat" => "BAT",
+35            "bat_charging" => "CHG",
+36            "bat_not_available" => "BAT N/A",
+37            "bell" => "ON",
+38            "bell-slash" => "OFF",
+39            "bluetooth" => "BT",
+40            "calendar" => "CAL",
+41            "cogs" => "LOAD",
+42            "cpu" => "CPU",
+43            "cpu_boost_on" => "BOOST ON",
+44            "cpu_boost_off" => "BOOST OFF",
+45            "disk_drive" => "DISK",
+46            "docker" => "DOCKER",
+47            "github" => "GITHUB",
+48            "gpu" => "GPU",
+49            "headphones" => "HEAD",
+50            "hueshift" => "HUE",
+51            "joystick" => "JOY",
+52            "keyboard" => "KBD",
+53            "mail" => "MAIL",
+54            "memory_mem" => "MEM",
+55            "memory_swap" => "SWAP",
+56            "mouse" => "MOUSE",
+57            "music" => "MUSIC",
+58            "music_next" => ">",
+59            "music_pause" => "||",
+60            "music_play" => ">",
+61            "music_prev" => "<",
+62            "net_bridge" => "BRIDGE",
+63            "net_cellular" => [
+64                                "NO SIGNAL",
+65                                "0 BARS",
+66                                "1 BAR",
+67                                "2 BARS",
+68                                "3 BARS",
+69                                "4 BARS",
+70                              ],
+71            "net_down" => "DOWN",
+72            "net_loopback" => "LO",
+73            "net_modem" => "MODEM",
+74            "net_up" => "UP ",
+75            "net_vpn" => "VPN",
+76            "net_wired" => "ETH",
+77            "net_wireless" => "WLAN",
+78            "notification" => "NOTIF",
+79            "phone" => "PHONE",
+80            "phone_disconnected" => "PHONE",
+81            "ping" => "PING",
+82            "pomodoro" => "POMODORO",
+83            "pomodoro_break" => "BREAK",
+84            "pomodoro_paused" => "PAUSED",
+85            "pomodoro_started" => "STARTED",
+86            "pomodoro_stopped" => "STOPPED",
+87            "resolution" => "RES",
+88            "scratchpad" => "[]",
+89            "tasks" => "TSK",
+90            "tea" => "TEA",
+91            "thermometer" => "TEMP",
+92            "time" => "TIME",
+93            "toggle_off" => "OFF",
+94            "toggle_on" => "ON",
+95            "unknown" => "??",
+96            "update" => "UPD",
+97            "uptime" => "UP",
+98            "volume" => "VOL",
+99            "volume_muted" => "VOL MUTED",
+100            "microphone" => "MIC",
+101            "microphone_muted" => "MIC MUTED",
+102            "weather_clouds_night" => "CLOUDY",
+103            "weather_clouds" => "CLOUDY",
+104            "weather_default" => "WEATHER",
+105            "weather_fog_night" => "FOG",
+106            "weather_fog" => "FOG",
+107            "weather_moon" => "MOONY",
+108            "weather_rain_night" => "RAIN",
+109            "weather_rain" => "RAIN",
+110            "weather_snow" => "SNOW",
+111            "weather_sun" => "SUNNY",
+112            "weather_thunder_night" => "STORM",
+113            "weather_thunder" => "STORM",
+114            "webcam" => "CAM",
+115            "xrandr" => "SCREEN"
+116        })
+117    }
+118}
+119
+120impl Icons {
+121    pub fn from_file(file: &str) -> Result<Self> {
+122        if file == "none" {
+123            Ok(Icons::default())
+124        } else {
+125            let file = util::find_file(file, Some("icons"), Some("toml"))
+126                .or_error(|| format!("Icon set '{file}' not found"))?;
+127            Ok(Icons(util::deserialize_toml_file(file)?))
+128        }
+129    }
+130
+131    pub fn apply_overrides(&mut self, overrides: HashMap<String, Icon>) {
+132        self.0.extend(overrides);
+133    }
+134
+135    pub fn get(&self, icon: &'_ str, value: Option<f64>) -> Option<&str> {
+136        match (self.0.get(icon)?, value) {
+137            (Icon::Single(icon), _) => Some(icon),
+138            (Icon::Progression(prog), _) if prog.is_empty() => None,
+139            (Icon::Progression(prog), None) => Some(prog.last().unwrap()),
+140            (Icon::Progression(prog), Some(value)) => {
+141                let index = ((value * prog.len() as f64) as usize).clamp(0, prog.len() - 1);
+142                Some(prog[index].as_str())
+143            }
+144        }
+145    }
+146}
+147
+148#[derive(Deserialize, Default)]
+149#[serde(deny_unknown_fields, default)]
+150struct IconsConfigRaw {
+151    icons: Option<String>,
+152    overrides: Option<HashMap<String, Icon>>,
+153}
+154
+155impl TryFrom<IconsConfigRaw> for Icons {
+156    type Error = Error;
+157
+158    fn try_from(raw: IconsConfigRaw) -> Result<Self, Self::Error> {
+159        let mut icons = Self::from_file(raw.icons.as_deref().unwrap_or("none"))?;
+160        if let Some(overrides) = raw.overrides {
+161            for icon in overrides {
+162                icons.0.insert(icon.0, icon.1);
+163            }
+164        }
+165        Ok(icons)
+166    }
+167}
\ No newline at end of file diff --git a/src/i3status_rs/lib.rs.html b/src/i3status_rs/lib.rs.html new file mode 100644 index 0000000000..408616ae0c --- /dev/null +++ b/src/i3status_rs/lib.rs.html @@ -0,0 +1,469 @@ +lib.rs - source

i3status_rs/
lib.rs

1#![warn(clippy::match_same_arms)]
+2#![warn(clippy::semicolon_if_nothing_returned)]
+3#![warn(clippy::unnecessary_wraps)]
+4#![warn(clippy::unused_trait_names)]
+5#![allow(clippy::single_match)]
+6#![cfg_attr(docsrs, feature(doc_cfg))]
+7
+8#[macro_use]
+9pub mod util;
+10pub mod blocks;
+11pub mod click;
+12pub mod config;
+13pub mod errors;
+14pub mod escape;
+15pub mod formatting;
+16pub mod geolocator;
+17pub mod icons;
+18mod netlink;
+19pub mod protocol;
+20mod signals;
+21mod subprocess;
+22pub mod themes;
+23pub mod widget;
+24mod wrappers;
+25
+26pub use env_logger;
+27pub use serde_json;
+28pub use tokio;
+29
+30use std::borrow::Cow;
+31use std::pin::Pin;
+32use std::sync::{Arc, LazyLock};
+33use std::time::Duration;
+34
+35use futures::Stream;
+36use futures::stream::{FuturesUnordered, StreamExt as _};
+37use tokio::process::Command;
+38use tokio::sync::{Notify, mpsc};
+39
+40use crate::blocks::{BlockAction, BlockError, CommonApi};
+41use crate::click::{ClickHandler, MouseButton};
+42use crate::config::{BlockConfigEntry, Config, SharedConfig};
+43use crate::errors::*;
+44use crate::formatting::Format;
+45use crate::formatting::value::Value;
+46use crate::protocol::i3bar_block::I3BarBlock;
+47use crate::protocol::i3bar_event::{self, I3BarEvent};
+48use crate::signals::Signal;
+49use crate::widget::{State, Widget};
+50
+51const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
+52const REQWEST_TIMEOUT: Duration = Duration::from_secs(10);
+53
+54static REQWEST_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
+55    reqwest::Client::builder()
+56        .user_agent(APP_USER_AGENT)
+57        .timeout(REQWEST_TIMEOUT)
+58        .build()
+59        .unwrap()
+60});
+61
+62static REQWEST_CLIENT_IPV4: LazyLock<reqwest::Client> = LazyLock::new(|| {
+63    reqwest::Client::builder()
+64        .user_agent(APP_USER_AGENT)
+65        .local_address(Some(std::net::Ipv4Addr::UNSPECIFIED.into()))
+66        .timeout(REQWEST_TIMEOUT)
+67        .build()
+68        .unwrap()
+69});
+70
+71type BoxedFuture<T> = Pin<Box<dyn Future<Output = T>>>;
+72
+73type BoxedStream<T> = Pin<Box<dyn Stream<Item = T>>>;
+74
+75type WidgetUpdatesSender = mpsc::UnboundedSender<(usize, Vec<u64>)>;
+76
+77/// A feature-rich and resource-friendly replacement for i3status(1), written in Rust. The
+78/// i3status-rs program writes a stream of configurable "blocks" of system information (time,
+79/// battery status, volume, etc.) to standard output in the JSON format understood by i3bar(1) and
+80/// sway-bar(5).
+81#[derive(Debug, clap::Parser)]
+82#[clap(author, about, long_about, version = env!("VERSION"))]
+83pub struct CliArgs {
+84    /// Sets a TOML config file
+85    ///
+86    /// 1. If full absolute path given, then use it as is: `/home/foo/i3rs-config.toml`
+87    ///
+88    /// 2. If filename given, e.g. "custom_theme.toml", then first look in `$XDG_CONFIG_HOME/i3status-rust`
+89    ///
+90    /// 3. Then look for it in `$XDG_DATA_HOME/i3status-rust`
+91    ///
+92    /// 4. Otherwise look for it in `/usr/share/i3status-rust`
+93    #[clap(default_value = "config.toml")]
+94    pub config: String,
+95    /// Ignore any attempts by i3 to pause the bar when hidden/fullscreen
+96    #[clap(long = "never-pause")]
+97    pub never_pause: bool,
+98    /// Do not send the init sequence
+99    #[clap(hide = true, long = "no-init")]
+100    pub no_init: bool,
+101    /// The maximum number of blocking threads spawned by tokio
+102    #[clap(long = "threads", short = 'j', default_value = "2")]
+103    pub blocking_threads: usize,
+104}
+105
+106pub struct BarState {
+107    config: Config,
+108
+109    blocks: Vec<Block>,
+110    fullscreen_block: Option<usize>,
+111    running_blocks: FuturesUnordered<BoxedFuture<()>>,
+112
+113    widget_updates_sender: WidgetUpdatesSender,
+114    blocks_render_cache: Vec<RenderedBlock>,
+115
+116    request_sender: mpsc::UnboundedSender<Request>,
+117    request_receiver: mpsc::UnboundedReceiver<Request>,
+118
+119    widget_updates_stream: BoxedStream<Vec<usize>>,
+120    signals_stream: BoxedStream<Signal>,
+121    events_stream: BoxedStream<I3BarEvent>,
+122}
+123
+124#[derive(Debug)]
+125struct Request {
+126    block_id: usize,
+127    cmd: RequestCmd,
+128}
+129
+130#[derive(Debug)]
+131enum RequestCmd {
+132    SetWidget(Widget),
+133    UnsetWidget,
+134    SetError(Error),
+135    SetDefaultActions(&'static [(MouseButton, Option<&'static str>, &'static str)]),
+136    SubscribeToActions(mpsc::UnboundedSender<BlockAction>),
+137}
+138
+139#[derive(Debug, Clone)]
+140struct RenderedBlock {
+141    pub segments: Vec<I3BarBlock>,
+142    pub merge_with_next: bool,
+143}
+144
+145#[derive(Debug)]
+146pub struct Block {
+147    id: usize,
+148    name: &'static str,
+149
+150    update_request: Arc<Notify>,
+151    action_sender: Option<mpsc::UnboundedSender<BlockAction>>,
+152
+153    click_handler: ClickHandler,
+154    default_actions: &'static [(MouseButton, Option<&'static str>, &'static str)],
+155    signal: Option<i32>,
+156    shared_config: SharedConfig,
+157
+158    error_format: Format,
+159    error_fullscreen_format: Format,
+160
+161    state: BlockState,
+162}
+163
+164#[derive(Debug)]
+165enum BlockState {
+166    None,
+167    Normal { widget: Widget },
+168    Error { widget: Widget },
+169}
+170
+171impl Block {
+172    fn notify_intervals(&self, tx: &WidgetUpdatesSender) {
+173        let intervals = match &self.state {
+174            BlockState::None => Vec::new(),
+175            BlockState::Normal { widget } | BlockState::Error { widget } => widget.intervals(),
+176        };
+177        let _ = tx.send((self.id, intervals));
+178    }
+179
+180    fn send_action(&mut self, action: BlockAction) {
+181        if let Some(sender) = &self.action_sender
+182            && sender.send(action).is_err()
+183        {
+184            self.action_sender = None;
+185        }
+186    }
+187
+188    fn set_error(&mut self, fullscreen: bool, error: Error) {
+189        let error = BlockError {
+190            block_id: self.id,
+191            block_name: self.name,
+192            error,
+193        };
+194
+195        let mut widget = Widget::new()
+196            .with_state(State::Critical)
+197            .with_format(if fullscreen {
+198                self.error_fullscreen_format.clone()
+199            } else {
+200                self.error_format.clone()
+201            });
+202        widget.set_values(map! {
+203            "full_error_message" => Value::text(error.to_string()),
+204            [if let Some(v) = &error.error.message] "short_error_message" => Value::text(v.to_string()),
+205        });
+206        self.state = BlockState::Error { widget };
+207    }
+208}
+209
+210impl BarState {
+211    pub fn new(config: Config) -> Self {
+212        let (request_sender, request_receiver) = mpsc::unbounded_channel();
+213        let (widget_updates_sender, widget_updates_stream) =
+214            formatting::scheduling::manage_widgets_updates();
+215        Self {
+216            blocks: Vec::new(),
+217            fullscreen_block: None,
+218            running_blocks: FuturesUnordered::new(),
+219
+220            widget_updates_sender,
+221            blocks_render_cache: Vec::new(),
+222
+223            request_sender,
+224            request_receiver,
+225
+226            widget_updates_stream,
+227            signals_stream: signals::signals_stream(),
+228            events_stream: i3bar_event::events_stream(
+229                config.invert_scrolling,
+230                Duration::from_millis(config.double_click_delay),
+231            ),
+232
+233            config,
+234        }
+235    }
+236
+237    pub async fn spawn_block(&mut self, block_config: BlockConfigEntry) -> Result<()> {
+238        if let Some(cmd) = &block_config.common.if_command {
+239            // TODO: async
+240            if !Command::new("sh")
+241                .args(["-c", cmd])
+242                .output()
+243                .await
+244                .error("failed to run if_command")?
+245                .status
+246                .success()
+247            {
+248                return Ok(());
+249            }
+250        }
+251
+252        let mut shared_config = self.config.shared.clone();
+253
+254        // Overrides
+255        if let Some(icons_format) = block_config.common.icons_format {
+256            shared_config.icons_format = Arc::new(icons_format);
+257        }
+258        if let Some(theme_overrides) = block_config.common.theme_overrides {
+259            Arc::make_mut(&mut shared_config.theme).apply_overrides(theme_overrides)?;
+260        }
+261        if let Some(icons_overrides) = block_config.common.icons_overrides {
+262            Arc::make_mut(&mut shared_config.icons).apply_overrides(icons_overrides);
+263        }
+264
+265        let update_request = Arc::new(Notify::new());
+266
+267        let api = CommonApi {
+268            id: self.blocks.len(),
+269            update_request: update_request.clone(),
+270            request_sender: self.request_sender.clone(),
+271            error_interval: Duration::from_secs(block_config.common.error_interval),
+272            geolocator: self.config.geolocator.clone(),
+273        };
+274
+275        let error_format = block_config
+276            .common
+277            .error_format
+278            .with_default_config(&self.config.error_format);
+279        let error_fullscreen_format = block_config
+280            .common
+281            .error_fullscreen_format
+282            .with_default_config(&self.config.error_fullscreen_format);
+283
+284        let block = Block {
+285            id: self.blocks.len(),
+286            name: block_config.config.name(),
+287
+288            update_request,
+289            action_sender: None,
+290
+291            click_handler: block_config.common.click,
+292            default_actions: &[],
+293            signal: block_config.common.signal,
+294            shared_config,
+295
+296            error_format,
+297            error_fullscreen_format,
+298
+299            state: BlockState::None,
+300        };
+301
+302        block_config.config.spawn(api, &mut self.running_blocks);
+303
+304        self.blocks.push(block);
+305        self.blocks_render_cache.push(RenderedBlock {
+306            segments: Vec::new(),
+307            merge_with_next: block_config.common.merge_with_next,
+308        });
+309
+310        Ok(())
+311    }
+312
+313    fn process_request(&mut self, request: Request) {
+314        let block = &mut self.blocks[request.block_id];
+315        match request.cmd {
+316            RequestCmd::SetWidget(widget) => {
+317                block.state = BlockState::Normal { widget };
+318                if self.fullscreen_block == Some(request.block_id) {
+319                    self.fullscreen_block = None;
+320                }
+321            }
+322            RequestCmd::UnsetWidget => {
+323                block.state = BlockState::None;
+324                if self.fullscreen_block == Some(request.block_id) {
+325                    self.fullscreen_block = None;
+326                }
+327            }
+328            RequestCmd::SetError(error) => {
+329                block.set_error(self.fullscreen_block == Some(request.block_id), error);
+330            }
+331            RequestCmd::SetDefaultActions(actions) => {
+332                block.default_actions = actions;
+333            }
+334            RequestCmd::SubscribeToActions(action_sender) => {
+335                block.action_sender = Some(action_sender);
+336            }
+337        }
+338        block.notify_intervals(&self.widget_updates_sender);
+339    }
+340
+341    fn render_block(&mut self, id: usize) -> Result<(), BlockError> {
+342        let block = &mut self.blocks[id];
+343        let data = &mut self.blocks_render_cache[id].segments;
+344        match &block.state {
+345            BlockState::None => {
+346                data.clear();
+347            }
+348            BlockState::Normal { widget } | BlockState::Error { widget, .. } => {
+349                *data = widget
+350                    .get_data(&block.shared_config, id)
+351                    .map_err(|error| BlockError {
+352                        block_id: id,
+353                        block_name: block.name,
+354                        error,
+355                    })?;
+356            }
+357        }
+358        Ok(())
+359    }
+360
+361    fn render(&self) {
+362        if let Some(id) = self.fullscreen_block {
+363            protocol::print_blocks(&[&self.blocks_render_cache[id]], &self.config.shared);
+364        } else {
+365            protocol::print_blocks(&self.blocks_render_cache, &self.config.shared);
+366        }
+367    }
+368
+369    async fn process_event(&mut self, restart: fn() -> !) -> Result<(), BlockError> {
+370        tokio::select! {
+371            // Poll blocks
+372            Some(()) = self.running_blocks.next() => (),
+373            // Receive messages from blocks
+374            Some(request) = self.request_receiver.recv() => {
+375                let id = request.block_id;
+376                self.process_request(request);
+377                self.render_block(id)?;
+378                self.render();
+379            }
+380            // Handle scheduled updates
+381            Some(ids) = self.widget_updates_stream.next() => {
+382                for id in ids {
+383                    self.render_block(id)?;
+384                }
+385                self.render();
+386            }
+387            // Handle clicks
+388            Some(event) = self.events_stream.next() => {
+389                let block = self.blocks.get_mut(event.id).expect("Events receiver: ID out of bounds");
+390                match &mut block.state {
+391                    BlockState::None => (),
+392                    BlockState::Normal { .. } => {
+393                        let result = block.click_handler.handle(&event).await.map_err(|error| BlockError {
+394                            block_id: event.id,
+395                            block_name: block.name,
+396                            error,
+397                        })?;
+398                        match result {
+399                            Some(post_actions) => {
+400                                if let Some(action) = post_actions.action {
+401                                    block.send_action(Cow::Owned(action));
+402                                }
+403                                if post_actions.update {
+404                                    block.update_request.notify_one();
+405                                }
+406                            }
+407                            None => {
+408                                if let Some((_, _, action)) = block.default_actions
+409                                    .iter()
+410                                    .find(|(btn, widget, _)| *btn == event.button && *widget == event.instance.as_deref()) {
+411                                    block.send_action(Cow::Borrowed(action));
+412                                }
+413                            }
+414                        }
+415                    }
+416                    BlockState::Error { widget } => {
+417                        if self.fullscreen_block == Some(event.id) {
+418                            self.fullscreen_block = None;
+419                            widget.set_format(block.error_format.clone());
+420                        } else {
+421                            self.fullscreen_block = Some(event.id);
+422                            widget.set_format(block.error_fullscreen_format.clone());
+423                        }
+424                        block.notify_intervals(&self.widget_updates_sender);
+425                        self.render_block(event.id)?;
+426                        self.render();
+427                    }
+428                }
+429            }
+430            // Handle signals
+431            Some(signal) = self.signals_stream.next() => match signal {
+432                Signal::Usr1 => {
+433                    for block in &self.blocks {
+434                        block.update_request.notify_one();
+435                    }
+436                }
+437                Signal::Usr2 => restart(),
+438                Signal::Custom(signal) => {
+439                    for block in &self.blocks {
+440                        if block.signal == Some(signal) {
+441                            block.update_request.notify_one();
+442                        }
+443                    }
+444                }
+445            }
+446        }
+447        Ok(())
+448    }
+449
+450    pub async fn run_event_loop(mut self, restart: fn() -> !) -> Result<(), BlockError> {
+451        loop {
+452            if let Err(error) = self.process_event(restart).await {
+453                let block = &mut self.blocks[error.block_id];
+454
+455                if matches!(block.state, BlockState::Error { .. }) {
+456                    // This should never happen. If this code runs, it could mean that we
+457                    // got an error while trying to display and error. We better stop here.
+458                    return Err(error);
+459                }
+460
+461                block.set_error(self.fullscreen_block == Some(block.id), error.error);
+462                block.notify_intervals(&self.widget_updates_sender);
+463
+464                self.render_block(error.block_id)?;
+465                self.render();
+466            }
+467        }
+468    }
+469}
\ No newline at end of file diff --git a/src/i3status_rs/netlink.rs.html b/src/i3status_rs/netlink.rs.html new file mode 100644 index 0000000000..8cc9a691d8 --- /dev/null +++ b/src/i3status_rs/netlink.rs.html @@ -0,0 +1,497 @@ +netlink.rs - source

i3status_rs/
netlink.rs

1use neli::attr::Attribute as _;
+2use neli::consts::{nl::*, rtnl::*, socket::*};
+3use neli::nl::{NlPayload, Nlmsghdr};
+4use neli::rtnl::{Ifaddrmsg, Ifinfomsg, Rtmsg};
+5use neli::socket::{NlSocketHandle, tokio::NlSocket};
+6use neli::types::RtBuffer;
+7
+8use regex::Regex;
+9
+10use libc::c_uchar;
+11
+12use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+13use std::ops;
+14use std::path::Path;
+15
+16use crate::errors::*;
+17use crate::util;
+18
+19// From `linux/rtnetlink.h`
+20const RT_SCOPE_HOST: c_uchar = 254;
+21
+22#[derive(Debug)]
+23pub struct NetDevice {
+24    pub iface: Interface,
+25    pub wifi_info: Option<WifiInfo>,
+26    pub ip: Option<Ipv4Addr>,
+27    pub ipv6: Option<Ipv6Addr>,
+28    pub icon: &'static str,
+29    pub tun_wg_ppp: bool,
+30    pub nameservers: Vec<IpAddr>,
+31}
+32
+33#[derive(Debug, Default)]
+34pub struct WifiInfo {
+35    pub ssid: Option<String>,
+36    pub signal: Option<f64>,
+37    pub frequency: Option<f64>,
+38    pub bitrate: Option<f64>,
+39}
+40
+41impl NetDevice {
+42    pub async fn new(iface_re: Option<&Regex>) -> Result<Option<Self>> {
+43        let mut sock = NlSocket::new(
+44            NlSocketHandle::connect(NlFamily::Route, None, &[]).error("Socket error")?,
+45        )
+46        .error("Socket error")?;
+47
+48        let mut ifaces = get_interfaces(&mut sock, iface_re)
+49            .await
+50            .map_err(BoxErrorWrapper)
+51            .error("Failed to fetch interfaces")?;
+52        if ifaces.is_empty() {
+53            return Ok(None);
+54        }
+55
+56        let default_iface = get_default_interface(&mut sock)
+57            .await
+58            .map_err(BoxErrorWrapper)
+59            .error("Failed to get default interface")?;
+60
+61        let iface_position = ifaces
+62            .iter()
+63            .position(|i| i.index == default_iface)
+64            .or_else(|| ifaces.iter().position(|i| i.operstate == Operstate::Up))
+65            .unwrap_or(0);
+66
+67        let iface = ifaces.swap_remove(iface_position);
+68        let wifi_info = WifiInfo::new(iface.index).await?;
+69        let ip = ipv4(&mut sock, iface.index).await?;
+70        let ipv6 = ipv6(&mut sock, iface.index).await?;
+71        let nameservers = read_nameservers()
+72            .await
+73            .error("Failed to read nameservers")?;
+74
+75        // TODO: use netlink for the these too
+76        // I don't believe that this should ever change, so set it now:
+77        let path = Path::new("/sys/class/net").join(&iface.name);
+78        let tun = iface.name.starts_with("tun")
+79            || iface.name.starts_with("tap")
+80            || path.join("tun_flags").exists();
+81        let (wg, ppp) = util::read_file(path.join("uevent"))
+82            .await
+83            .map_or((false, false), |c| {
+84                (c.contains("wireguard"), c.contains("ppp"))
+85            });
+86
+87        let icon = if wifi_info.is_some() {
+88            "net_wireless"
+89        } else if tun || wg || ppp {
+90            "net_vpn"
+91        } else if iface.name == "lo" {
+92            "net_loopback"
+93        } else {
+94            "net_wired"
+95        };
+96
+97        Ok(Some(Self {
+98            iface,
+99            wifi_info,
+100            ip,
+101            ipv6,
+102            icon,
+103            tun_wg_ppp: tun | wg | ppp,
+104            nameservers,
+105        }))
+106    }
+107
+108    pub fn is_up(&self) -> bool {
+109        self.tun_wg_ppp
+110            || self.iface.operstate == Operstate::Up
+111            || (self.iface.operstate == Operstate::Unknown
+112                && (self.ip.is_some() || self.ipv6.is_some()))
+113    }
+114
+115    pub fn ssid(&self) -> Option<String> {
+116        self.wifi_info.as_ref()?.ssid.clone()
+117    }
+118
+119    pub fn frequency(&self) -> Option<f64> {
+120        self.wifi_info.as_ref()?.frequency
+121    }
+122
+123    pub fn bitrate(&self) -> Option<f64> {
+124        self.wifi_info.as_ref()?.bitrate
+125    }
+126
+127    pub fn signal(&self) -> Option<f64> {
+128        self.wifi_info.as_ref()?.signal
+129    }
+130}
+131
+132impl WifiInfo {
+133    async fn new(if_index: i32) -> Result<Option<Self>> {
+134        /// <https://github.com/torvalds/linux/blob/9ff9b0d392ea08090cd1780fb196f36dbb586529/drivers/net/wireless/intel/ipw2x00/ipw2200.c#L4322-L4334>
+135        fn signal_percents(raw: f64) -> f64 {
+136            const MAX_LEVEL: f64 = -20.;
+137            const MIN_LEVEL: f64 = -85.;
+138            const DIFF: f64 = MAX_LEVEL - MIN_LEVEL;
+139            (100. - (MAX_LEVEL - raw) * (15. * DIFF + 62. * (MAX_LEVEL - raw)) / (DIFF * DIFF))
+140                .clamp(0., 100.)
+141        }
+142
+143        fn ssid_from_bss_info_elements(mut bytes: &[u8]) -> Option<String> {
+144            while bytes.len() > 2 && bytes[0] != 0 {
+145                bytes = &bytes[(bytes[1] as usize + 2)..];
+146            }
+147
+148            if bytes.len() < 2 || bytes.len() < bytes[1] as usize + 2 {
+149                return None;
+150            };
+151
+152            let ssid_len = bytes[1] as usize;
+153            let raw_ssid = &bytes[2..][..ssid_len];
+154
+155            Some(String::from_utf8_lossy(raw_ssid).into_owned())
+156        }
+157
+158        // Ignore connection error because `nl80211` might not be enabled on the system.
+159        let Ok(mut socket) = neli_wifi::AsyncSocket::connect() else {
+160            return Ok(None);
+161        };
+162
+163        let interfaces = socket
+164            .get_interfaces_info()
+165            .await
+166            .error("Failed to get nl80211 interfaces")?;
+167
+168        for interface in interfaces {
+169            if let Some(index) = interface.index
+170                && index == if_index
+171                && let Ok(ap) = socket.get_station_info(index).await
+172            {
+173                // TODO: are there any situations when there is more than one station?
+174                let Some(ap) = ap.into_iter().next() else {
+175                    continue;
+176                };
+177
+178                let bss = socket
+179                    .get_bss_info(index)
+180                    .await
+181                    .unwrap_or_default()
+182                    .into_iter()
+183                    .find(|bss| bss.status == Some(1));
+184
+185                let raw_signal = match ap.signal {
+186                    Some(signal) => Some(signal),
+187                    None => bss
+188                        .as_ref()
+189                        .and_then(|bss| bss.signal)
+190                        .map(|s| (s / 100) as i8),
+191                };
+192
+193                let ssid = interface
+194                    .ssid
+195                    .as_deref()
+196                    .map(|ssid| String::from_utf8_lossy(ssid).into_owned())
+197                    .or_else(|| {
+198                        bss.as_ref()
+199                            .and_then(|bss| bss.information_elements.as_deref())
+200                            .and_then(ssid_from_bss_info_elements)
+201                    });
+202
+203                return Ok(Some(Self {
+204                    ssid,
+205                    frequency: interface.frequency.map(|f| f as f64 * 1e6),
+206                    signal: raw_signal.map(|s| signal_percents(s as f64)),
+207                    bitrate: ap.tx_bitrate.map(|b| b as f64 * 1e5), // 100kbit/s -> bit/s
+208                }));
+209            }
+210        }
+211        Ok(None)
+212    }
+213}
+214
+215#[derive(Debug, Default, Clone, Copy)]
+216pub struct InterfaceStats {
+217    pub rx_bytes: u64,
+218    pub tx_bytes: u64,
+219}
+220
+221impl ops::Sub for InterfaceStats {
+222    type Output = Self;
+223
+224    fn sub(mut self, rhs: Self) -> Self::Output {
+225        self.rx_bytes = self.rx_bytes.saturating_sub(rhs.rx_bytes);
+226        self.tx_bytes = self.tx_bytes.saturating_sub(rhs.tx_bytes);
+227        self
+228    }
+229}
+230
+231impl InterfaceStats {
+232    fn from_stats64(stats: &[u8]) -> Self {
+233        // stats looks something like that:
+234        //
+235        // #[repr(C)]
+236        // struct RtnlLinkStats64 {
+237        //     rx_packets: u64,
+238        //     tx_packets: u64,
+239        //     rx_bytes: u64,
+240        //     tx_bytes: u64,
+241        //     // the rest is omitted
+242        // }
+243        assert!(stats.len() >= 8 * 4);
+244        Self {
+245            rx_bytes: u64::from_ne_bytes(stats[16..24].try_into().unwrap()),
+246            tx_bytes: u64::from_ne_bytes(stats[24..32].try_into().unwrap()),
+247        }
+248    }
+249}
+250
+251#[derive(Debug)]
+252pub struct Interface {
+253    pub index: i32,
+254    pub operstate: Operstate,
+255    pub name: String,
+256    pub stats: Option<InterfaceStats>,
+257}
+258
+259macro_rules! recv_until_done {
+260    ($sock:ident, $payload:ident: $payload_type:ty => $($code:tt)*) => {
+261        let mut buf = Vec::new();
+262        'recv: loop {
+263            let msgs = $sock.recv::<u16, $payload_type>(&mut buf).await?;
+264            for msg in msgs {
+265                if msg.nl_type == libc::NLMSG_DONE as u16 {
+266                    break 'recv;
+267                }
+268                if let NlPayload::Payload($payload) = msg.nl_payload {
+269                    $($code)*
+270                }
+271            }
+272        }
+273    };
+274}
+275
+276async fn get_interfaces(
+277    sock: &mut NlSocket,
+278    filter: Option<&Regex>,
+279) -> Result<Vec<Interface>, Box<dyn StdError + Send + Sync + 'static>> {
+280    sock.send(&Nlmsghdr::new(
+281        None,
+282        Rtm::Getlink,
+283        NlmFFlags::new(&[NlmF::Dump, NlmF::Request]),
+284        None,
+285        None,
+286        NlPayload::Payload(Ifinfomsg::new(
+287            RtAddrFamily::Unspecified,
+288            Arphrd::None,
+289            0,
+290            IffFlags::empty(),
+291            IffFlags::empty(),
+292            RtBuffer::new(),
+293        )),
+294    ))
+295    .await?;
+296
+297    let mut interfaces = Vec::new();
+298
+299    recv_until_done!(sock, msg: Ifinfomsg => {
+300        let mut name = None;
+301        let mut stats = None;
+302        let mut operstate = Operstate::Unknown;
+303        for attr in msg.rtattrs.iter() {
+304            match attr.rta_type {
+305                Ifla::Ifname => name = Some(attr.get_payload_as_with_len()?),
+306                Ifla::Stats64 => stats = Some(InterfaceStats::from_stats64(attr.payload().as_ref())),
+307                Ifla::Operstate => operstate = attr.get_payload_as::<u8>()?.into(),
+308                _ => (),
+309            }
+310        }
+311        let name: String = name.unwrap();
+312        if filter.is_none_or(|f| f.is_match(&name)) {
+313            interfaces.push(Interface {
+314                index: msg.ifi_index,
+315                operstate,
+316                name,
+317                stats,
+318            });
+319        }
+320    });
+321
+322    Ok(interfaces)
+323}
+324
+325async fn get_default_interface(
+326    sock: &mut NlSocket,
+327) -> Result<i32, Box<dyn StdError + Send + Sync + 'static>> {
+328    sock.send(&Nlmsghdr::new(
+329        None,
+330        Rtm::Getroute,
+331        NlmFFlags::new(&[NlmF::Request, NlmF::Dump]),
+332        None,
+333        None,
+334        NlPayload::Payload(Rtmsg {
+335            rtm_family: RtAddrFamily::Inet,
+336            rtm_dst_len: 0,
+337            rtm_src_len: 0,
+338            rtm_tos: 0,
+339            rtm_table: RtTable::Unspec,
+340            rtm_protocol: Rtprot::Unspec,
+341            rtm_scope: RtScope::Universe,
+342            rtm_type: Rtn::Unspec,
+343            rtm_flags: RtmFFlags::empty(),
+344            rtattrs: RtBuffer::new(),
+345        }),
+346    ))
+347    .await?;
+348
+349    let mut best_index = 0;
+350    let mut best_metric = u32::MAX;
+351
+352    recv_until_done!(sock, msg: Rtmsg => {
+353        if msg.rtm_type != Rtn::Unicast {
+354            continue;
+355        }
+356        // Only check default routes (rtm_dst_len == 0)
+357        if msg.rtm_dst_len != 0 {
+358            continue;
+359        }
+360
+361        let mut index = None;
+362        let mut metric = 0u32;
+363        for attr in msg.rtattrs.iter() {
+364            match attr.rta_type {
+365                Rta::Oif => index = Some(attr.get_payload_as::<i32>()?),
+366                Rta::Priority => metric = attr.get_payload_as::<u32>()?,
+367                _ => (),
+368            }
+369        }
+370        if let Some(i) = index
+371            && metric < best_metric {
+372                best_metric = metric;
+373                best_index = i;
+374            }
+375    });
+376
+377    Ok(best_index)
+378}
+379
+380async fn ip_payload<const BYTES: usize>(
+381    sock: &mut NlSocket,
+382    ifa_family: RtAddrFamily,
+383    ifa_index: i32,
+384) -> Result<Option<[u8; BYTES]>, Box<dyn StdError + Send + Sync + 'static>> {
+385    sock.send(&Nlmsghdr::new(
+386        None,
+387        Rtm::Getaddr,
+388        NlmFFlags::new(&[NlmF::Dump, NlmF::Request]),
+389        None,
+390        None,
+391        NlPayload::Payload(Ifaddrmsg {
+392            ifa_family,
+393            ifa_prefixlen: 0,
+394            ifa_flags: IfaFFlags::empty(),
+395            ifa_scope: 0,
+396            ifa_index: 0,
+397            rtattrs: RtBuffer::new(),
+398        }),
+399    ))
+400    .await?;
+401
+402    let mut payload = None;
+403
+404    recv_until_done!(sock, msg: Ifaddrmsg => {
+405        if msg.ifa_index != ifa_index || msg.ifa_scope >= RT_SCOPE_HOST || payload.is_some() {
+406            continue;
+407        }
+408
+409        let attr_handle = msg.rtattrs.get_attr_handle();
+410
+411        if let Some(attr) = attr_handle.get_attribute(Ifa::Local)
+412            .or_else(|| attr_handle.get_attribute(Ifa::Address))
+413            && let Ok(p) = attr.rta_payload.as_ref().try_into()
+414        {
+415            payload = Some(p);
+416        }
+417    });
+418
+419    Ok(payload)
+420}
+421
+422async fn ipv4(sock: &mut NlSocket, ifa_index: i32) -> Result<Option<Ipv4Addr>> {
+423    Ok(ip_payload(sock, RtAddrFamily::Inet, ifa_index)
+424        .await
+425        .map_err(BoxErrorWrapper)
+426        .error("Failed to get IP address")?
+427        .map(Ipv4Addr::from))
+428}
+429
+430async fn ipv6(sock: &mut NlSocket, ifa_index: i32) -> Result<Option<Ipv6Addr>> {
+431    Ok(ip_payload(sock, RtAddrFamily::Inet6, ifa_index)
+432        .await
+433        .map_err(BoxErrorWrapper)
+434        .error("Failed to get IPv6 address")?
+435        .map(Ipv6Addr::from))
+436}
+437
+438async fn read_nameservers() -> Result<Vec<IpAddr>> {
+439    let file = util::read_file("/etc/resolv.conf")
+440        .await
+441        .error("Failed to read /etc/resolv.conf")?;
+442    let mut nameservers = Vec::new();
+443
+444    for line in file.lines() {
+445        let mut line_parts = line.split_whitespace();
+446        if line_parts.next() == Some("nameserver")
+447            && let Some(mut ip) = line_parts.next()
+448        {
+449            // TODO: use the zone id somehow?
+450            if let Some((without_zone_id, _zone_id)) = ip.split_once('%') {
+451                ip = without_zone_id;
+452            }
+453            nameservers.push(ip.parse().error("Unable to parse ip")?);
+454        }
+455    }
+456
+457    Ok(nameservers)
+458}
+459
+460// Source: https://www.kernel.org/doc/Documentation/networking/operstates.txt
+461#[derive(Debug, PartialEq, Eq)]
+462pub enum Operstate {
+463    /// Interface is in unknown state, neither driver nor userspace has set
+464    /// operational state. Interface must be considered for user data as
+465    /// setting operational state has not been implemented in every driver.
+466    Unknown,
+467    /// Unused in current kernel (notpresent interfaces normally disappear),
+468    /// just a numerical placeholder.
+469    Notpresent,
+470    /// Interface is unable to transfer data on L1, f.e. ethernet is not
+471    /// plugged or interface is ADMIN down.
+472    Down,
+473    /// Interfaces stacked on an interface that is IF_OPER_DOWN show this
+474    /// state (f.e. VLAN).
+475    Lowerlayerdown,
+476    /// Unused in current kernel.
+477    Testing,
+478    /// Interface is L1 up, but waiting for an external event, f.e. for a
+479    /// protocol to establish. (802.1X)
+480    Dormant,
+481    /// Interface is operational up and can be used.
+482    Up,
+483}
+484
+485impl From<u8> for Operstate {
+486    fn from(value: u8) -> Self {
+487        match value {
+488            1 => Self::Notpresent,
+489            2 => Self::Down,
+490            3 => Self::Lowerlayerdown,
+491            4 => Self::Testing,
+492            5 => Self::Dormant,
+493            6 => Self::Up,
+494            _ => Self::Unknown,
+495        }
+496    }
+497}
\ No newline at end of file diff --git a/src/i3status_rs/protocol.rs.html b/src/i3status_rs/protocol.rs.html new file mode 100644 index 0000000000..398335f625 --- /dev/null +++ b/src/i3status_rs/protocol.rs.html @@ -0,0 +1,140 @@ +protocol.rs - source

i3status_rs/
protocol.rs

1pub mod i3bar_block;
+2pub mod i3bar_event;
+3
+4use std::borrow::Borrow;
+5
+6use crate::RenderedBlock;
+7use crate::config::SharedConfig;
+8use crate::themes::color::Color;
+9use crate::themes::separator::Separator;
+10
+11use i3bar_block::I3BarBlock;
+12
+13pub fn init(never_pause: bool) {
+14    if never_pause {
+15        println!("{{\"version\": 1, \"click_events\": true, \"stop_signal\": 0}}\n[");
+16    } else {
+17        println!("{{\"version\": 1, \"click_events\": true}}\n[");
+18    }
+19}
+20
+21pub(crate) fn print_blocks<B>(blocks: &[B], config: &SharedConfig)
+22where
+23    B: Borrow<RenderedBlock>,
+24{
+25    let mut prev_last_bg = Color::None;
+26    let mut rendered_blocks = vec![];
+27
+28    // The right most block should never be alternated
+29    let mut alt = blocks
+30        .iter()
+31        .map(|x| x.borrow())
+32        .filter(|x| !x.segments.is_empty() && !x.merge_with_next)
+33        .count()
+34        % 2
+35        == 0;
+36
+37    let mut logical_block_i = 0;
+38
+39    let mut prev_merge_with_next = false;
+40
+41    for (i, widgets) in blocks
+42        .iter()
+43        .map(|x| x.borrow())
+44        .filter(|x| !x.segments.is_empty())
+45        .cloned()
+46        .enumerate()
+47    {
+48        let RenderedBlock {
+49            mut segments,
+50            merge_with_next,
+51        } = widgets;
+52
+53        for segment in &mut segments {
+54            segment.name = Some(logical_block_i.to_string());
+55
+56            // Apply tint for all widgets of every second block
+57            // TODO: Allow for other non-additive tints
+58            if alt {
+59                segment.background = segment.background + config.theme.alternating_tint_bg;
+60                segment.color = segment.color + config.theme.alternating_tint_fg;
+61            }
+62        }
+63
+64        if !merge_with_next {
+65            alt = !alt;
+66        }
+67
+68        let separator = match &config.theme.start_separator {
+69            Separator::Custom(_) if i == 0 => &config.theme.start_separator,
+70            _ => &config.theme.separator,
+71        };
+72
+73        if let Separator::Custom(separator) = separator {
+74            if !prev_merge_with_next {
+75                // The first widget's BG is used to get the FG color for the current separator
+76                let sep_fg = if config.theme.separator_fg == Color::Auto {
+77                    segments.first().unwrap().background
+78                } else {
+79                    config.theme.separator_fg
+80                };
+81
+82                // The separator's BG is the last block's last widget's BG
+83                let sep_bg = if config.theme.separator_bg == Color::Auto {
+84                    prev_last_bg
+85                } else {
+86                    config.theme.separator_bg
+87                };
+88
+89                let separator = I3BarBlock {
+90                    full_text: separator.clone(),
+91                    background: sep_bg,
+92                    color: sep_fg,
+93                    ..Default::default()
+94                };
+95
+96                rendered_blocks.push(separator);
+97            }
+98        } else if !merge_with_next {
+99            // Re-add native separator on last widget for native theme
+100            segments.last_mut().unwrap().separator = None;
+101            segments.last_mut().unwrap().separator_block_width = None;
+102        }
+103
+104        if !merge_with_next {
+105            logical_block_i += 1;
+106        }
+107
+108        prev_merge_with_next = merge_with_next;
+109        prev_last_bg = segments.last().unwrap().background;
+110
+111        rendered_blocks.extend(segments);
+112    }
+113
+114    if let Separator::Custom(end_separator) = &config.theme.end_separator {
+115        // The separator's FG is the last block's last widget's BG
+116        let sep_fg = if config.theme.separator_fg == Color::Auto {
+117            prev_last_bg
+118        } else {
+119            config.theme.separator_fg
+120        };
+121
+122        // The separator has no background color
+123        let sep_bg = if config.theme.separator_bg == Color::Auto {
+124            Color::None
+125        } else {
+126            config.theme.separator_bg
+127        };
+128
+129        let separator = I3BarBlock {
+130            full_text: end_separator.clone(),
+131            background: sep_bg,
+132            color: sep_fg,
+133            ..Default::default()
+134        };
+135
+136        rendered_blocks.push(separator);
+137    }
+138
+139    println!("{},", serde_json::to_string(&rendered_blocks).unwrap());
+140}
\ No newline at end of file diff --git a/src/i3status_rs/protocol/i3bar_block.rs.html b/src/i3status_rs/protocol/i3bar_block.rs.html new file mode 100644 index 0000000000..f5853a561c --- /dev/null +++ b/src/i3status_rs/protocol/i3bar_block.rs.html @@ -0,0 +1,92 @@ +i3bar_block.rs - source

i3status_rs/protocol/
i3bar_block.rs

1use crate::themes::color::Color;
+2use serde::Serialize;
+3
+4/// Represent block as described in <https://i3wm.org/docs/i3bar-protocol.html>
+5#[derive(Serialize, Debug, Clone)]
+6pub struct I3BarBlock {
+7    pub full_text: String,
+8    #[serde(skip_serializing_if = "String::is_empty")]
+9    pub short_text: String,
+10    #[serde(skip_serializing_if = "Color::skip_ser")]
+11    pub color: Color,
+12    #[serde(skip_serializing_if = "Color::skip_ser")]
+13    pub background: Color,
+14    #[serde(skip_serializing_if = "Option::is_none")]
+15    pub border: Option<String>,
+16    #[serde(skip_serializing_if = "Option::is_none")]
+17    pub border_top: Option<usize>,
+18    #[serde(skip_serializing_if = "Option::is_none")]
+19    pub border_right: Option<usize>,
+20    #[serde(skip_serializing_if = "Option::is_none")]
+21    pub border_bottom: Option<usize>,
+22    #[serde(skip_serializing_if = "Option::is_none")]
+23    pub border_left: Option<usize>,
+24    #[serde(skip_serializing_if = "Option::is_none")]
+25    pub min_width: Option<I3BarBlockMinWidth>,
+26    #[serde(skip_serializing_if = "Option::is_none")]
+27    pub align: Option<I3BarBlockAlign>,
+28    /// This project uses `name` field to uniquely identify each "logical block". For example two
+29    /// "config blocks" merged using `merge_with_next` will have the same `name`. This information
+30    /// could be used by some bar frontends (such as `i3bar-river`) and will be ignored by `i3bar`
+31    /// and `swaybar`.
+32    #[serde(skip_serializing_if = "Option::is_none")]
+33    pub name: Option<String>,
+34    /// This project uses `instance` field to uniquely identify each block and optionally a part
+35    /// of the block, e.g. a "button". The format is `{block_id}:{optional_widget_name}`. This info
+36    /// is used when dispatching click events.
+37    #[serde(skip_serializing_if = "String::is_empty")]
+38    pub instance: String,
+39    #[serde(skip_serializing_if = "Option::is_none")]
+40    pub urgent: Option<bool>,
+41    #[serde(skip_serializing_if = "Option::is_none")]
+42    pub separator: Option<bool>,
+43    #[serde(skip_serializing_if = "Option::is_none")]
+44    pub separator_block_width: Option<usize>,
+45    #[serde(skip_serializing_if = "Option::is_none")]
+46    pub markup: Option<String>,
+47}
+48
+49impl Default for I3BarBlock {
+50    fn default() -> Self {
+51        #[cfg(not(feature = "debug_borders"))]
+52        let border = None;
+53        #[cfg(feature = "debug_borders")]
+54        let border = Some("#ff0000".to_string());
+55        Self {
+56            full_text: String::new(),
+57            short_text: String::new(),
+58            color: Color::None,
+59            background: Color::None,
+60            border,
+61            border_top: None,
+62            border_right: None,
+63            border_bottom: None,
+64            border_left: None,
+65            min_width: None,
+66            align: None,
+67            name: None,
+68            instance: String::new(),
+69            urgent: None,
+70            separator: Some(false),
+71            separator_block_width: Some(0),
+72            markup: Some("pango".to_string()),
+73        }
+74    }
+75}
+76
+77#[derive(Serialize, Debug, Clone, Copy)]
+78#[allow(dead_code)]
+79#[serde(rename_all = "lowercase")]
+80pub enum I3BarBlockAlign {
+81    Center,
+82    Right,
+83    Left,
+84}
+85
+86#[derive(Serialize, Debug, Clone)]
+87#[allow(dead_code)]
+88#[serde(untagged)]
+89pub enum I3BarBlockMinWidth {
+90    Pixels(usize),
+91    Text(String),
+92}
\ No newline at end of file diff --git a/src/i3status_rs/protocol/i3bar_event.rs.html b/src/i3status_rs/protocol/i3bar_event.rs.html new file mode 100644 index 0000000000..0d5aec6be5 --- /dev/null +++ b/src/i3status_rs/protocol/i3bar_event.rs.html @@ -0,0 +1,112 @@ +i3bar_event.rs - source

i3status_rs/protocol/
i3bar_event.rs

1use std::os::unix::io::FromRawFd as _;
+2use std::time::Duration;
+3
+4use serde::Deserialize;
+5
+6use futures::StreamExt as _;
+7use tokio::fs::File;
+8use tokio::io::{AsyncBufReadExt as _, BufReader};
+9
+10use crate::BoxedStream;
+11use crate::click::MouseButton;
+12
+13#[derive(Debug, Clone, PartialEq, Eq)]
+14pub struct I3BarEvent {
+15    pub id: usize,
+16    pub instance: Option<String>,
+17    pub button: MouseButton,
+18}
+19
+20fn unprocessed_events_stream(invert_scrolling: bool) -> BoxedStream<I3BarEvent> {
+21    // Avoid spawning a blocking therad (why doesn't tokio do this too?)
+22    // This should be safe given that this function is called only once
+23    let stdin = unsafe { File::from_raw_fd(0) };
+24    let lines = BufReader::new(stdin).lines();
+25
+26    futures::stream::unfold(lines, move |mut lines| async move {
+27        loop {
+28            // Take only the valid JSON object between curly braces (cut off leading bracket, commas and whitespace)
+29            let line = lines.next_line().await.ok().flatten()?;
+30            let line = line.trim_start_matches(|c| c != '{');
+31            let line = line.trim_end_matches(|c| c != '}');
+32
+33            if line.is_empty() {
+34                continue;
+35            }
+36
+37            #[derive(Deserialize)]
+38            struct I3BarEventRaw {
+39                instance: Option<String>,
+40                button: MouseButton,
+41            }
+42
+43            let event: I3BarEventRaw = match serde_json::from_str(line) {
+44                Ok(event) => event,
+45                Err(err) => {
+46                    eprintln!("Failed to deserialize click event.\nData: {line}\nError: {err}");
+47                    continue;
+48                }
+49            };
+50
+51            let (id, instance) = match event.instance {
+52                Some(name) => {
+53                    let (id, instance) = name.split_once(':').unwrap();
+54                    let instance = if instance.is_empty() {
+55                        None
+56                    } else {
+57                        Some(instance.to_owned())
+58                    };
+59                    (id.parse().unwrap(), instance)
+60                }
+61                None => continue,
+62            };
+63
+64            use MouseButton::*;
+65            let button = match (event.button, invert_scrolling) {
+66                (WheelUp, false) | (WheelDown, true) => WheelUp,
+67                (WheelUp, true) | (WheelDown, false) => WheelDown,
+68                (other, _) => other,
+69            };
+70
+71            let event = I3BarEvent {
+72                id,
+73                instance,
+74                button,
+75            };
+76
+77            break Some((event, lines));
+78        }
+79    })
+80    .boxed_local()
+81}
+82
+83pub fn events_stream(
+84    invert_scrolling: bool,
+85    double_click_delay: Duration,
+86) -> BoxedStream<I3BarEvent> {
+87    let events = unprocessed_events_stream(invert_scrolling);
+88    futures::stream::unfold((events, None), move |(mut events, pending)| async move {
+89        if let Some(pending) = pending {
+90            return Some((pending, (events, None)));
+91        }
+92
+93        let mut event = events.next().await?;
+94
+95        // Handle double clicks (for now only left)
+96        if event.button == MouseButton::Left
+97            && !double_click_delay.is_zero()
+98            && let Ok(new_event) = tokio::time::timeout(double_click_delay, events.next()).await
+99        {
+100            let new_event = new_event?;
+101            if event == new_event {
+102                event.button = MouseButton::DoubleLeft;
+103            } else {
+104                return Some((event, (events, Some(new_event))));
+105            }
+106        }
+107
+108        Some((event, (events, None)))
+109    })
+110    .fuse()
+111    .boxed_local()
+112}
\ No newline at end of file diff --git a/src/i3status_rs/signals.rs.html b/src/i3status_rs/signals.rs.html new file mode 100644 index 0000000000..2f8f6e6fec --- /dev/null +++ b/src/i3status_rs/signals.rs.html @@ -0,0 +1,26 @@ +signals.rs - source

i3status_rs/
signals.rs

1use futures::stream::StreamExt as _;
+2use libc::{SIGRTMAX, SIGRTMIN};
+3use signal_hook::consts::{SIGUSR1, SIGUSR2};
+4use signal_hook_tokio::Signals;
+5
+6use crate::BoxedStream;
+7
+8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+9pub enum Signal {
+10    Usr1,
+11    Usr2,
+12    Custom(i32),
+13}
+14
+15/// Returns an infinite stream of `Signal`s
+16pub fn signals_stream() -> BoxedStream<Signal> {
+17    let (sigmin, sigmax) = (SIGRTMIN(), SIGRTMAX());
+18    let signals = Signals::new((sigmin..sigmax).chain([SIGUSR1, SIGUSR2])).unwrap();
+19    signals
+20        .map(move |signal| match signal {
+21            SIGUSR1 => Signal::Usr1,
+22            SIGUSR2 => Signal::Usr2,
+23            x => Signal::Custom(x - sigmin),
+24        })
+25        .boxed()
+26}
\ No newline at end of file diff --git a/src/i3status_rs/subprocess.rs.html b/src/i3status_rs/subprocess.rs.html new file mode 100644 index 0000000000..3a6afdad8d --- /dev/null +++ b/src/i3status_rs/subprocess.rs.html @@ -0,0 +1,36 @@ +subprocess.rs - source

i3status_rs/
subprocess.rs

1use std::io;
+2use std::os::unix::process::CommandExt as _;
+3use std::process::{Command, Stdio};
+4
+5/// Spawn a new detached process
+6pub fn spawn_process(cmd: &str, args: &[&str]) -> io::Result<()> {
+7    let mut proc = Command::new(cmd);
+8    proc.args(args);
+9    proc.stdin(Stdio::null());
+10    proc.stdout(Stdio::null());
+11    // Safety: libc::daemon() is async-signal-safe
+12    unsafe {
+13        proc.pre_exec(|| match libc::daemon(0, 0) {
+14            -1 => Err(io::Error::other("Failed to detach new process")),
+15            _ => Ok(()),
+16        });
+17    }
+18    proc.spawn()?.wait()?;
+19    Ok(())
+20}
+21
+22/// Spawn a new detached shell
+23pub fn spawn_shell(cmd: &str) -> io::Result<()> {
+24    spawn_process("sh", &["-c", cmd])
+25}
+26
+27pub async fn spawn_shell_sync(cmd: &str) -> io::Result<()> {
+28    tokio::process::Command::new("sh")
+29        .args(["-c", cmd])
+30        .stdin(Stdio::null())
+31        .stdout(Stdio::null())
+32        .spawn()?
+33        .wait()
+34        .await?;
+35    Ok(())
+36}
\ No newline at end of file diff --git a/src/i3status_rs/themes.rs.html b/src/i3status_rs/themes.rs.html new file mode 100644 index 0000000000..adb5a0b57f --- /dev/null +++ b/src/i3status_rs/themes.rs.html @@ -0,0 +1,221 @@ +themes.rs - source

i3status_rs/
themes.rs

1pub mod color;
+2pub mod separator;
+3pub mod xresources;
+4
+5use std::fmt;
+6use std::ops::{Deref, DerefMut};
+7
+8use serde::{Deserialize, de};
+9
+10use crate::errors::*;
+11use crate::util;
+12use crate::widget::State;
+13use color::Color;
+14use separator::Separator;
+15
+16#[derive(Debug, Clone)]
+17pub struct Theme(pub ThemeInner);
+18
+19impl Default for Theme {
+20    fn default() -> Self {
+21        ThemeUserConfig::default()
+22            .try_into()
+23            .unwrap_or_else(|_| Self(ThemeInner::default()))
+24    }
+25}
+26
+27impl Deref for Theme {
+28    type Target = ThemeInner;
+29    fn deref(&self) -> &Self::Target {
+30        &self.0
+31    }
+32}
+33impl DerefMut for Theme {
+34    fn deref_mut(&mut self) -> &mut Self::Target {
+35        &mut self.0
+36    }
+37}
+38
+39#[derive(Deserialize, Debug, Clone, Default)]
+40#[serde(deny_unknown_fields, default)]
+41pub struct ThemeInner {
+42    pub idle_bg: Color,
+43    pub idle_fg: Color,
+44    pub info_bg: Color,
+45    pub info_fg: Color,
+46    pub good_bg: Color,
+47    pub good_fg: Color,
+48    pub warning_bg: Color,
+49    pub warning_fg: Color,
+50    pub critical_bg: Color,
+51    pub critical_fg: Color,
+52    pub separator: Separator,
+53    pub separator_bg: Color,
+54    pub separator_fg: Color,
+55    pub alternating_tint_bg: Color,
+56    pub alternating_tint_fg: Color,
+57    pub end_separator: Separator,
+58    pub start_separator: Separator,
+59}
+60
+61impl Theme {
+62    pub fn get_colors(&self, state: State) -> (Color, Color) {
+63        match state {
+64            State::Idle => (self.idle_bg, self.idle_fg),
+65            State::Info => (self.info_bg, self.info_fg),
+66            State::Good => (self.good_bg, self.good_fg),
+67            State::Warning => (self.warning_bg, self.warning_fg),
+68            State::Critical => (self.critical_bg, self.critical_fg),
+69        }
+70    }
+71
+72    pub fn apply_overrides(&mut self, overrides: ThemeOverrides) -> Result<()> {
+73        let copy = self.clone();
+74
+75        if let Some(separator) = overrides.separator {
+76            self.separator = separator;
+77        }
+78        if let Some(end_separator) = overrides.end_separator {
+79            self.end_separator = end_separator;
+80        }
+81        if let Some(start_separator) = overrides.start_separator {
+82            self.start_separator = start_separator;
+83        }
+84
+85        macro_rules! apply {
+86            ($prop:tt) => {
+87                if let Some(color) = overrides.$prop {
+88                    self.$prop = color.eval(&copy)?;
+89                }
+90            };
+91        }
+92        apply!(idle_bg);
+93        apply!(idle_fg);
+94        apply!(info_bg);
+95        apply!(info_fg);
+96        apply!(good_bg);
+97        apply!(good_fg);
+98        apply!(warning_bg);
+99        apply!(warning_fg);
+100        apply!(critical_bg);
+101        apply!(critical_fg);
+102        apply!(separator_bg);
+103        apply!(separator_fg);
+104        apply!(alternating_tint_bg);
+105        apply!(alternating_tint_fg);
+106
+107        Ok(())
+108    }
+109}
+110
+111#[derive(Deserialize, Default)]
+112#[serde(deny_unknown_fields, default)]
+113pub struct ThemeUserConfig {
+114    pub theme: Option<String>,
+115    pub overrides: Option<ThemeOverrides>,
+116}
+117
+118#[derive(Deserialize, Debug, Clone, Default)]
+119pub struct ThemeOverrides {
+120    pub idle_bg: Option<ColorOrLink>,
+121    pub idle_fg: Option<ColorOrLink>,
+122    pub info_bg: Option<ColorOrLink>,
+123    pub info_fg: Option<ColorOrLink>,
+124    pub good_bg: Option<ColorOrLink>,
+125    pub good_fg: Option<ColorOrLink>,
+126    pub warning_bg: Option<ColorOrLink>,
+127    pub warning_fg: Option<ColorOrLink>,
+128    pub critical_bg: Option<ColorOrLink>,
+129    pub critical_fg: Option<ColorOrLink>,
+130    pub separator: Option<Separator>,
+131    pub separator_bg: Option<ColorOrLink>,
+132    pub separator_fg: Option<ColorOrLink>,
+133    pub alternating_tint_bg: Option<ColorOrLink>,
+134    pub alternating_tint_fg: Option<ColorOrLink>,
+135    pub end_separator: Option<Separator>,
+136    pub start_separator: Option<Separator>,
+137}
+138
+139impl TryFrom<ThemeUserConfig> for Theme {
+140    type Error = Error;
+141
+142    fn try_from(user_config: ThemeUserConfig) -> Result<Self, Self::Error> {
+143        let name = user_config.theme.as_deref().unwrap_or("plain");
+144        let file = util::find_file(name, Some("themes"), Some("toml"))
+145            .or_error(|| format!("Theme '{name}' not found"))?;
+146        let theme: ThemeInner = util::deserialize_toml_file(file)?;
+147        let mut theme = Theme(theme);
+148        if let Some(overrides) = user_config.overrides {
+149            theme.apply_overrides(overrides)?;
+150        }
+151        Ok(theme)
+152    }
+153}
+154
+155#[derive(Debug, Clone)]
+156pub enum ColorOrLink {
+157    Color(Color),
+158    Link { link: String },
+159}
+160
+161impl ColorOrLink {
+162    fn eval(self, theme: &Theme) -> Result<Color> {
+163        Ok(match self {
+164            Self::Color(c) => c,
+165            Self::Link { link } => match link.as_str() {
+166                "idle_bg" => theme.idle_bg,
+167                "idle_fg" => theme.idle_fg,
+168                "info_bg" => theme.info_bg,
+169                "info_fg" => theme.info_fg,
+170                "good_bg" => theme.good_bg,
+171                "good_fg" => theme.good_fg,
+172                "warning_bg" => theme.warning_bg,
+173                "warning_fg" => theme.warning_fg,
+174                "critical_bg" => theme.critical_bg,
+175                "critical_fg" => theme.critical_fg,
+176                "separator_bg" => theme.separator_bg,
+177                "separator_fg" => theme.separator_fg,
+178                "alternating_tint_bg" => theme.alternating_tint_bg,
+179                "alternating_tint_fg" => theme.alternating_tint_fg,
+180                _ => return Err(Error::new(format!("{link} is not a correct theme color"))),
+181            },
+182        })
+183    }
+184}
+185
+186impl<'de> Deserialize<'de> for ColorOrLink {
+187    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+188    where
+189        D: de::Deserializer<'de>,
+190    {
+191        struct Visitor;
+192        impl<'de> de::Visitor<'de> for Visitor {
+193            type Value = ColorOrLink;
+194
+195            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+196                formatter.write_str("color or link")
+197            }
+198
+199            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
+200            where
+201                A: de::MapAccess<'de>,
+202            {
+203                #[derive(Deserialize)]
+204                #[serde(deny_unknown_fields)]
+205                struct Link {
+206                    link: String,
+207                }
+208                Link::deserialize(de::value::MapAccessDeserializer::new(map))
+209                    .map(|link| ColorOrLink::Link { link: link.link })
+210            }
+211
+212            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+213            where
+214                E: de::Error,
+215            {
+216                v.parse::<Color>().serde_error().map(ColorOrLink::Color)
+217            }
+218        }
+219        deserializer.deserialize_any(Visitor)
+220    }
+221}
\ No newline at end of file diff --git a/src/i3status_rs/themes/color.rs.html b/src/i3status_rs/themes/color.rs.html new file mode 100644 index 0000000000..2052a4f18d --- /dev/null +++ b/src/i3status_rs/themes/color.rs.html @@ -0,0 +1,276 @@ +color.rs - source

i3status_rs/themes/
color.rs

1use crate::errors::*;
+2use serde::de::{self, Deserializer, Visitor};
+3use serde::{Deserialize, Serialize, Serializer};
+4use smart_default::SmartDefault;
+5use std::fmt;
+6use std::ops::Add;
+7use std::str::FromStr;
+8
+9/// An RGBA color (red, green, blue, alpha).
+10#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+11pub struct Rgba {
+12    pub r: u8,
+13    pub g: u8,
+14    pub b: u8,
+15    pub a: u8,
+16}
+17
+18impl Rgba {
+19    /// Create a new RGBA color.
+20    ///
+21    /// `r`: red component (0 to 255).
+22    ///
+23    /// `g`: green component (0 to 255).
+24    ///
+25    /// `b`: blue component (0 to 255).
+26    ///
+27    /// `a`: alpha component (0 to 255).
+28    pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
+29        Self { r, g, b, a }
+30    }
+31
+32    /// Create a new RGBA color from the `hex` value.
+33    ///
+34    /// ```let cyan = Rgba::from_hex(0xffffff);```
+35    pub fn from_hex(hex: u32) -> Self {
+36        let [r, g, b, a] = hex.to_be_bytes();
+37        Self { r, g, b, a }
+38    }
+39}
+40
+41impl Add for Rgba {
+42    type Output = Self;
+43    fn add(self, rhs: Self) -> Self::Output {
+44        Rgba::new(
+45            self.r.saturating_add(rhs.r),
+46            self.g.saturating_add(rhs.g),
+47            self.b.saturating_add(rhs.b),
+48            self.a.saturating_add(rhs.a),
+49        )
+50    }
+51}
+52
+53/// An HSVA color (hue, saturation, value, alpha).
+54#[derive(Copy, Clone, Debug, Default)]
+55pub struct Hsva {
+56    pub h: f64,
+57    pub s: f64,
+58    pub v: f64,
+59    pub a: u8,
+60}
+61
+62impl Hsva {
+63    /// Create a new HSVA color.
+64    ///
+65    /// `h`: hue component (0 to 360)
+66    ///
+67    /// `s`: saturation component (0 to 1)
+68    ///
+69    /// `v`: value component (0 to 1)
+70    ///
+71    /// `a`: alpha component (0 to 255).
+72    pub fn new(h: f64, s: f64, v: f64, a: u8) -> Self {
+73        Self { h, s, v, a }
+74    }
+75}
+76
+77impl PartialEq for Hsva {
+78    fn eq(&self, other: &Self) -> bool {
+79        approx(self.h, other.h)
+80            && approx(self.s, other.s)
+81            && approx(self.v, other.v)
+82            && self.a == other.a
+83    }
+84}
+85
+86impl From<Rgba> for Hsva {
+87    fn from(rgba: Rgba) -> Self {
+88        let r = rgba.r as f64 / 255.0;
+89        let g = rgba.g as f64 / 255.0;
+90        let b = rgba.b as f64 / 255.0;
+91
+92        let min = r.min(g.min(b));
+93        let max = r.max(g.max(b));
+94        let delta = max - min;
+95
+96        let v = max;
+97        let s = match max > 1e-3 {
+98            true => delta / max,
+99            false => 0.0,
+100        };
+101        let h = match delta == 0.0 {
+102            true => 0.0,
+103            false => {
+104                if r == max {
+105                    (g - b) / delta
+106                } else if g == max {
+107                    2.0 + (b - r) / delta
+108                } else {
+109                    4.0 + (r - g) / delta
+110                }
+111            }
+112        };
+113        let h2 = ((h * 60.0) + 360.0) % 360.0;
+114
+115        Self::new(h2, s, v, rgba.a)
+116    }
+117}
+118
+119impl From<Hsva> for Rgba {
+120    fn from(hsva: Hsva) -> Self {
+121        let range = (hsva.h / 60.0) as u8;
+122        let c = hsva.v * hsva.s;
+123        let x = c * (1.0 - (((hsva.h / 60.0) % 2.0) - 1.0).abs());
+124        let m = hsva.v - c;
+125
+126        let cm_scaled = ((c + m) * 255.0) as u8;
+127        let xm_scaled = ((x + m) * 255.0) as u8;
+128        let m_scaled = (m * 255.0) as u8;
+129
+130        match range {
+131            0 => Self::new(cm_scaled, xm_scaled, m_scaled, hsva.a),
+132            1 => Self::new(xm_scaled, cm_scaled, m_scaled, hsva.a),
+133            2 => Self::new(m_scaled, cm_scaled, xm_scaled, hsva.a),
+134            3 => Self::new(m_scaled, xm_scaled, cm_scaled, hsva.a),
+135            4 => Self::new(xm_scaled, m_scaled, cm_scaled, hsva.a),
+136            _ => Self::new(cm_scaled, m_scaled, xm_scaled, hsva.a),
+137        }
+138    }
+139}
+140
+141impl Add for Hsva {
+142    type Output = Self;
+143    fn add(self, rhs: Self) -> Self::Output {
+144        Hsva::new(
+145            (self.h + rhs.h) % 360.,
+146            (self.s + rhs.s).clamp(0., 1.),
+147            (self.v + rhs.v).clamp(0., 1.),
+148            self.a.saturating_add(rhs.a),
+149        )
+150    }
+151}
+152
+153pub fn approx(a: f64, b: f64) -> bool {
+154    if a == b {
+155        return true;
+156    }
+157    let eps = 1e-2;
+158    let abs_a = a.abs();
+159    let abs_b = b.abs();
+160    let diff = (abs_a - abs_b).abs();
+161    if a == 0.0 || b == 0.0 || abs_a + abs_b < f64::EPSILON {
+162        diff < eps * f64::EPSILON
+163    } else {
+164        diff / (abs_a + abs_b).min(f64::MAX) < eps
+165    }
+166}
+167
+168#[derive(Debug, Clone, Copy, PartialEq, SmartDefault)]
+169pub enum Color {
+170    #[default]
+171    None,
+172    Auto,
+173    Rgba(Rgba),
+174    Hsva(Hsva),
+175}
+176
+177impl Color {
+178    pub fn skip_ser(&self) -> bool {
+179        matches!(self, Self::None | Self::Auto)
+180    }
+181}
+182
+183impl Add for Color {
+184    type Output = Color;
+185    fn add(self, rhs: Self) -> Self::Output {
+186        match (self, rhs) {
+187            // Do nothing
+188            (x, Self::None | Self::Auto) | (Self::None | Self::Auto, x) => x,
+189            // Hsva + Hsva => Hsva
+190            (Color::Hsva(hsva1), Color::Hsva(hsva2)) => Color::Hsva(hsva1 + hsva2),
+191            // Rgba + Rgba => Rgba
+192            (Color::Rgba(rgba1), Color::Rgba(rgba2)) => Color::Rgba(rgba1 + rgba2),
+193            // Hsva + Rgba => Hsva
+194            // Rgba + Hsva => Hsva
+195            (Color::Hsva(hsva), Color::Rgba(rgba)) | (Color::Rgba(rgba), Color::Hsva(hsva)) => {
+196                Color::Hsva(hsva + rgba.into())
+197            }
+198        }
+199    }
+200}
+201
+202impl FromStr for Color {
+203    type Err = Error;
+204
+205    fn from_str(color: &str) -> Result<Self, Self::Err> {
+206        Ok(if color == "none" || color.is_empty() {
+207            Color::None
+208        } else if color == "auto" {
+209            Color::Auto
+210        } else if color.starts_with("hsv:") {
+211            let err_msg = || format!("'{color}' is not a valid HSVA color");
+212            let color = color.split_at(4).1;
+213            let mut components = color.split(':').map(|x| x.parse::<f64>().or_error(err_msg));
+214            let h = components.next().or_error(err_msg)??;
+215            let s = components.next().or_error(err_msg)??;
+216            let v = components.next().or_error(err_msg)??;
+217            let a = components.next().unwrap_or(Ok(100.))?;
+218            Color::Hsva(Hsva::new(h, s / 100., v / 100., (a / 100. * 255.) as u8))
+219        } else if color.starts_with("x:") {
+220            let name = color.split_at(2).1;
+221            super::xresources::get_color(name)?
+222                .or_error(|| format!("color '{name}' not defined in ~/.Xresources"))?
+223                .parse()
+224                .or_error(|| format!("invalid color definition '{name}'"))?
+225        } else {
+226            let err_msg = || format!("'{color}' is not a valid RGBA color");
+227            let rgb = color.get(1..7).or_error(err_msg)?;
+228            let a = color.get(7..9).unwrap_or("FF");
+229            Color::Rgba(Rgba::from_hex(
+230                (u32::from_str_radix(rgb, 16).or_error(err_msg)? << 8)
+231                    + u32::from_str_radix(a, 16).or_error(err_msg)?,
+232            ))
+233        })
+234    }
+235}
+236
+237impl Serialize for Color {
+238    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+239    where
+240        S: Serializer,
+241    {
+242        let format_rgba =
+243            |rgba: Rgba| format!("#{:02X}{:02X}{:02X}{:02X}", rgba.r, rgba.g, rgba.b, rgba.a);
+244        match *self {
+245            Self::None | Self::Auto => serializer.serialize_none(),
+246            Self::Rgba(rgba) => serializer.serialize_str(&format_rgba(rgba)),
+247            Self::Hsva(hsva) => serializer.serialize_str(&format_rgba(hsva.into())),
+248        }
+249    }
+250}
+251
+252impl<'de> Deserialize<'de> for Color {
+253    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+254    where
+255        D: Deserializer<'de>,
+256    {
+257        struct ColorVisitor;
+258
+259        impl Visitor<'_> for ColorVisitor {
+260            type Value = Color;
+261
+262            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+263                formatter.write_str("color")
+264            }
+265
+266            fn visit_str<E>(self, s: &str) -> Result<Color, E>
+267            where
+268                E: de::Error,
+269            {
+270                s.parse().serde_error()
+271            }
+272        }
+273
+274        deserializer.deserialize_any(ColorVisitor)
+275    }
+276}
\ No newline at end of file diff --git a/src/i3status_rs/themes/separator.rs.html b/src/i3status_rs/themes/separator.rs.html new file mode 100644 index 0000000000..f79cf7eb14 --- /dev/null +++ b/src/i3status_rs/themes/separator.rs.html @@ -0,0 +1,51 @@ +separator.rs - source

i3status_rs/themes/
separator.rs

1use crate::errors::*;
+2use serde::Deserialize;
+3use serde::de::{self, Deserializer, Visitor};
+4use smart_default::SmartDefault;
+5use std::fmt;
+6use std::str::FromStr;
+7
+8#[derive(Debug, Clone, PartialEq, Eq, SmartDefault)]
+9pub enum Separator {
+10    #[default]
+11    Native,
+12    Custom(String),
+13}
+14
+15impl FromStr for Separator {
+16    type Err = Error;
+17
+18    fn from_str(separator: &str) -> Result<Self, Self::Err> {
+19        Ok(if separator == "native" {
+20            Self::Native
+21        } else {
+22            Self::Custom(separator.into())
+23        })
+24    }
+25}
+26
+27impl<'de> Deserialize<'de> for Separator {
+28    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+29    where
+30        D: Deserializer<'de>,
+31    {
+32        struct SeparatorVisitor;
+33
+34        impl Visitor<'_> for SeparatorVisitor {
+35            type Value = Separator;
+36
+37            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+38                formatter.write_str("a separator string or 'native'")
+39            }
+40
+41            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
+42            where
+43                E: de::Error,
+44            {
+45                s.parse().serde_error()
+46            }
+47        }
+48
+49        deserializer.deserialize_any(SeparatorVisitor)
+50    }
+51}
\ No newline at end of file diff --git a/src/i3status_rs/themes/xresources.rs.html b/src/i3status_rs/themes/xresources.rs.html new file mode 100644 index 0000000000..6d841a269f --- /dev/null +++ b/src/i3status_rs/themes/xresources.rs.html @@ -0,0 +1,93 @@ +xresources.rs - source

i3status_rs/themes/
xresources.rs

1use log::debug;
+2use regex::Regex;
+3
+4use std::collections::HashMap;
+5use std::sync::LazyLock;
+6
+7use crate::errors::*;
+8
+9#[cfg(not(test))]
+10use std::{env, path::PathBuf};
+11
+12#[cfg(not(test))]
+13fn read_xresources() -> std::io::Result<String> {
+14    let home = env::var("HOME").map_err(|_| std::io::Error::other("HOME env var was not set"))?;
+15    let xresources = PathBuf::from(home + "/.Xresources");
+16    debug!(".Xresources @ {:?}", xresources);
+17    std::fs::read_to_string(xresources)
+18}
+19
+20#[cfg(test)]
+21use tests::read_xresources;
+22
+23static COLOR_REGEX: LazyLock<Regex> = LazyLock::new(|| {
+24    Regex::new(r"^\s*\*(?<name>[^: ]+)\s*:\s*(?<color>#[a-f0-9]{6,8}).*$").unwrap()
+25});
+26
+27static COLORS: LazyLock<Result<HashMap<String, String>, Error>> = LazyLock::new(|| {
+28    let content = read_xresources().error("could not read .Xresources")?;
+29    debug!(".Xresources content:\n{}", content);
+30    Ok(HashMap::from_iter(content.lines().filter_map(|line| {
+31        COLOR_REGEX
+32            .captures(line)
+33            .map(|caps| (caps["name"].to_string(), caps["color"].to_string()))
+34    })))
+35});
+36
+37pub fn get_color(name: &str) -> Result<Option<&String>, Error> {
+38    COLORS
+39        .as_ref()
+40        .map(|cmap| cmap.get(name))
+41        .map_err(Clone::clone)
+42}
+43
+44#[cfg(test)]
+45mod tests {
+46    use super::*;
+47    use std::io::Result;
+48
+49    #[allow(clippy::unnecessary_wraps)]
+50    pub(crate) fn read_xresources() -> Result<String> {
+51        static XRESOURCES: &str = "\
+52        ! this is a comment\n\
+53        \n\
+54        *color4 : #feedda\n\
+55    \n\
+56        *background: #ee33aa99\n\
+57        ";
+58        Ok(XRESOURCES.to_string())
+59    }
+60
+61    #[test]
+62    fn test_reading_colors() {
+63        let colors = COLORS.as_ref().unwrap();
+64        assert_eq!(colors.get("color4"), Some(&"#feedda".to_string()));
+65        assert_eq!(colors.get("background"), Some(&"#ee33aa99".to_string()));
+66        assert_eq!(2, colors.len());
+67    }
+68
+69    #[test]
+70    fn test_deserializing_xcolors() {
+71        use super::super::color::*;
+72        let mut parsed_color = "x:color4".parse::<Color>().unwrap();
+73        assert_eq!(
+74            parsed_color,
+75            Color::Rgba(Rgba {
+76                r: 254,
+77                g: 237,
+78                b: 218,
+79                a: 255
+80            })
+81        );
+82        parsed_color = "x:background".parse::<Color>().unwrap();
+83        assert_eq!(
+84            parsed_color,
+85            Color::Rgba(Rgba {
+86                r: 238,
+87                g: 51,
+88                b: 170,
+89                a: 153,
+90            })
+91        );
+92    }
+93}
\ No newline at end of file diff --git a/src/i3status_rs/util.rs.html b/src/i3status_rs/util.rs.html new file mode 100644 index 0000000000..478632c884 --- /dev/null +++ b/src/i3status_rs/util.rs.html @@ -0,0 +1,298 @@ +util.rs - source

i3status_rs/
util.rs

1use std::path::{Path, PathBuf};
+2
+3use dirs::{config_dir, data_dir};
+4use serde::de::DeserializeOwned;
+5use tokio::io::AsyncReadExt as _;
+6use tokio::process::Command;
+7
+8use crate::errors::*;
+9
+10/// Tries to find a file in standard locations:
+11/// - Fist try to find a file by full path (only if path is absolute)
+12/// - Then try XDG_CONFIG_HOME (e.g. `~/.config`)
+13/// - Then try XDG_DATA_HOME (e.g. `~/.local/share/`)
+14/// - Then try `/usr/share/`
+15///
+16/// Automatically append an extension if not presented.
+17pub fn find_file(file: &str, subdir: Option<&str>, extension: Option<&str>) -> Option<PathBuf> {
+18    let file = Path::new(file);
+19
+20    if file.is_absolute() && file.exists() {
+21        return Some(file.to_path_buf());
+22    }
+23
+24    // Try XDG_CONFIG_HOME (e.g. `~/.config`)
+25    if let Some(mut xdg_config) = config_dir() {
+26        xdg_config.push("i3status-rust");
+27        if let Some(subdir) = subdir {
+28            xdg_config.push(subdir);
+29        }
+30        xdg_config.push(file);
+31        if let Some(file) = exists_with_opt_extension(&xdg_config, extension) {
+32            return Some(file);
+33        }
+34    }
+35
+36    // Try XDG_DATA_HOME (e.g. `~/.local/share/`)
+37    if let Some(mut xdg_data) = data_dir() {
+38        xdg_data.push("i3status-rust");
+39        if let Some(subdir) = subdir {
+40            xdg_data.push(subdir);
+41        }
+42        xdg_data.push(file);
+43        if let Some(file) = exists_with_opt_extension(&xdg_data, extension) {
+44            return Some(file);
+45        }
+46    }
+47
+48    // Try `/usr/share/`
+49    let mut usr_share_path = PathBuf::from("/usr/share/i3status-rust");
+50    if let Some(subdir) = subdir {
+51        usr_share_path.push(subdir);
+52    }
+53    usr_share_path.push(file);
+54    if let Some(file) = exists_with_opt_extension(&usr_share_path, extension) {
+55        return Some(file);
+56    }
+57
+58    None
+59}
+60
+61fn exists_with_opt_extension(file: &Path, extension: Option<&str>) -> Option<PathBuf> {
+62    if file.exists() {
+63        return Some(file.into());
+64    }
+65    // If file has no extension, test with given extension
+66    if let (None, Some(extension)) = (file.extension(), extension) {
+67        let file = file.with_extension(extension);
+68        // Check again with extension added
+69        if file.exists() {
+70            return Some(file);
+71        }
+72    }
+73    None
+74}
+75
+76pub async fn new_dbus_connection() -> Result<zbus::Connection> {
+77    zbus::Connection::session()
+78        .await
+79        .error("Failed to open DBus session connection")
+80}
+81
+82pub async fn new_system_dbus_connection() -> Result<zbus::Connection> {
+83    zbus::Connection::system()
+84        .await
+85        .error("Failed to open DBus system connection")
+86}
+87
+88pub fn deserialize_toml_file<T, P>(path: P) -> Result<T>
+89where
+90    T: DeserializeOwned,
+91    P: AsRef<Path>,
+92{
+93    let path = path.as_ref();
+94
+95    let contents = std::fs::read_to_string(path)
+96        .or_error(|| format!("Failed to read file: {}", path.display()))?;
+97
+98    toml::from_str(&contents).map_err(|err| {
+99        let location_msg = err
+100            .span()
+101            .map(|span| {
+102                let line = 1 + contents.as_bytes()[..(span.start)]
+103                    .iter()
+104                    .filter(|b| **b == b'\n')
+105                    .count();
+106                format!(" at line {line}")
+107            })
+108            .unwrap_or_default();
+109        Error::new(format!(
+110            "Failed to deserialize TOML file {}{}: {}",
+111            path.display(),
+112            location_msg,
+113            err.message()
+114        ))
+115    })
+116}
+117
+118pub async fn read_file(path: impl AsRef<Path>) -> std::io::Result<String> {
+119    let mut file = tokio::fs::File::open(path).await?;
+120    let mut content = String::new();
+121    file.read_to_string(&mut content).await?;
+122    Ok(content.trim_end().to_string())
+123}
+124
+125pub async fn has_command(command: &str) -> Result<bool> {
+126    Command::new("sh")
+127        .args([
+128            "-c",
+129            format!("command -v {command} >/dev/null 2>&1").as_ref(),
+130        ])
+131        .status()
+132        .await
+133        .or_error(|| format!("Failed to check {command} presence"))
+134        .map(|status| status.success())
+135}
+136
+137/// # Example
+138///
+139/// ```ignore
+140/// let opt = Some(1);
+141/// let m: HashMap<&'static str, String> = map! {
+142///     "key" => "value",
+143///     [if true] "hello" => "world",
+144///     [if let Some(x) = opt] "opt" => x.to_string(),
+145/// };
+146/// map! { @extend m
+147///     "new key" => "new value",
+148///     "one" => "more",
+149/// }
+150/// ```
+151#[macro_export]
+152macro_rules! map {
+153    (@extend $map:ident $( $([$($cond_tokens:tt)*])? $key:literal => $value:expr ),* $(,)?) => {{
+154        $(
+155        map!(@insert $map, $key, $value $(,$($cond_tokens)*)?);
+156        )*
+157    }};
+158    (@extend $map:ident $( $key:expr => $value:expr ),* $(,)?) => {{
+159        $(
+160        map!(@insert $map, $key, $value);
+161        )*
+162    }};
+163    (@insert $map:ident, $key:expr, $value:expr) => {{
+164        $map.insert($key.into(), $value.into());
+165    }};
+166    (@insert $map:ident, $key:expr, $value:expr, if $cond:expr) => {{
+167        if $cond {
+168        $map.insert($key.into(), $value.into());
+169        }
+170    }};
+171    (@insert $map:ident, $key:expr, $value:expr, if let $pat:pat = $match_on:expr) => {{
+172        if let $pat = $match_on {
+173        $map.insert($key.into(), $value.into());
+174        }
+175    }};
+176    ($($tt:tt)*) => {{
+177        #[allow(unused_mut)]
+178        let mut m = ::std::collections::HashMap::new();
+179        map!(@extend m $($tt)*);
+180        m
+181    }};
+182}
+183
+184pub use map;
+185
+186macro_rules! regex {
+187    ($re:literal $(,)?) => {{
+188        static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
+189        RE.get_or_init(|| regex::Regex::new($re).unwrap())
+190    }};
+191}
+192
+193macro_rules! make_log_macro {
+194    (@wdoll $macro_name:ident, $block_name:literal, ($dol:tt)) => {
+195        #[allow(dead_code)]
+196        macro_rules! $macro_name {
+197            ($dol($args:tt)+) => {
+198                ::log::$macro_name!(target: $block_name, $dol($args)+);
+199            };
+200        }
+201    };
+202    ($macro_name:ident, $block_name:literal) => {
+203        make_log_macro!(@wdoll $macro_name, $block_name, ($));
+204    };
+205}
+206
+207pub fn format_bar_graph(content: &[f64]) -> String {
+208    // (x * one eighth block) https://en.wikipedia.org/wiki/Block_Elements
+209    static BARS: [char; 8] = [
+210        '\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}',
+211        '\u{2588}',
+212    ];
+213
+214    // Find min and max
+215    let mut min = f64::INFINITY;
+216    let mut max = f64::NEG_INFINITY;
+217    for &v in content {
+218        min = min.min(v);
+219        max = max.max(v);
+220    }
+221
+222    let range = max - min;
+223    content
+224        .iter()
+225        .map(|x| BARS[((x - min) / range * 7.).clamp(0., 7.) as usize])
+226        .collect()
+227}
+228
+229/// Convert 2 letter country code to Unicode
+230pub fn country_flag_from_iso_code(country_code: &str) -> String {
+231    let [mut b1, mut b2]: [u8; 2] = country_code.as_bytes().try_into().unwrap_or([0, 0]);
+232
+233    if !b1.is_ascii_uppercase() || !b2.is_ascii_uppercase() {
+234        return country_code.into();
+235    }
+236
+237    // Each char is encoded as 1F1E6 to 1F1FF for A-Z
+238    b1 += 0xa5;
+239    b2 += 0xa5;
+240    // The last byte will always start with 101 (0xa0) and then the 5 least
+241    // significant bits from the previous result
+242    b1 = 0xa0 | (b1 & 0x1f);
+243    b2 = 0xa0 | (b2 & 0x1f);
+244    // Get the flag string from the UTF-8 representation of our Unicode characters.
+245    String::from_utf8(vec![0xf0, 0x9f, 0x87, b1, 0xf0, 0x9f, 0x87, b2]).unwrap()
+246}
+247
+248/// A shortcut for `Default::default()`
+249/// See <https://github.com/rust-lang/rust/issues/73014>
+250#[inline]
+251pub fn default<T: Default>() -> T {
+252    Default::default()
+253}
+254
+255pub trait StreamExtDebounced: futures::StreamExt {
+256    fn next_debounced(&mut self) -> impl Future<Output = Option<Self::Item>>;
+257}
+258
+259impl<T: futures::StreamExt + Unpin> StreamExtDebounced for T {
+260    async fn next_debounced(&mut self) -> Option<Self::Item> {
+261        let mut result = self.next().await?;
+262        let mut noop_ctx = std::task::Context::from_waker(std::task::Waker::noop());
+263        loop {
+264            match self.poll_next_unpin(&mut noop_ctx) {
+265                std::task::Poll::Ready(Some(x)) => result = x,
+266                std::task::Poll::Ready(None) | std::task::Poll::Pending => return Some(result),
+267            }
+268        }
+269    }
+270}
+271
+272#[cfg(test)]
+273mod tests {
+274    use super::*;
+275
+276    #[tokio::test]
+277    async fn test_has_command_ok() {
+278        // we assume sh is always available
+279        assert!(has_command("sh").await.unwrap());
+280    }
+281
+282    #[tokio::test]
+283    async fn test_has_command_err() {
+284        // we assume thequickbrownfoxjumpsoverthelazydog command does not exist
+285        assert!(
+286            !has_command("thequickbrownfoxjumpsoverthelazydog")
+287                .await
+288                .unwrap()
+289        );
+290    }
+291
+292    #[test]
+293    fn test_flags() {
+294        assert!(country_flag_from_iso_code("ES") == "🇪🇸");
+295        assert!(country_flag_from_iso_code("US") == "🇺🇸");
+296        assert!(country_flag_from_iso_code("USA") == "USA");
+297    }
+298}
\ No newline at end of file diff --git a/src/i3status_rs/widget.rs.html b/src/i3status_rs/widget.rs.html new file mode 100644 index 0000000000..1031cf357b --- /dev/null +++ b/src/i3status_rs/widget.rs.html @@ -0,0 +1,156 @@ +widget.rs - source

i3status_rs/
widget.rs

1use crate::config::SharedConfig;
+2use crate::errors::*;
+3use crate::formatting::{Format, Fragment, Values};
+4use crate::protocol::i3bar_block::I3BarBlock;
+5use serde::Deserialize;
+6use smart_default::SmartDefault;
+7
+8#[derive(Debug, Clone, Default)]
+9pub struct Widget {
+10    pub state: State,
+11    source: Source,
+12    values: Values,
+13}
+14
+15impl Widget {
+16    pub fn new() -> Self {
+17        Self::default()
+18    }
+19
+20    /*
+21     * Builders
+22     */
+23
+24    pub fn with_text(mut self, text: String) -> Self {
+25        self.set_text(text);
+26        self
+27    }
+28
+29    pub fn with_state(mut self, state: State) -> Self {
+30        self.state = state;
+31        self
+32    }
+33
+34    pub fn with_format(mut self, format: Format) -> Self {
+35        self.set_format(format);
+36        self
+37    }
+38
+39    /*
+40     * Setters
+41     */
+42
+43    pub fn set_text(&mut self, text: String) {
+44        if text.is_empty() {
+45            self.source = Source::None;
+46        } else {
+47            self.source = Source::Text(text);
+48        }
+49    }
+50
+51    pub fn set_format(&mut self, format: Format) {
+52        self.source = Source::Format(format);
+53    }
+54
+55    pub fn set_values(&mut self, new_values: Values) {
+56        self.values = new_values;
+57    }
+58
+59    pub fn intervals(&self) -> Vec<u64> {
+60        match &self.source {
+61            Source::Format(f) => f.intervals(),
+62            _ => Vec::new(),
+63        }
+64    }
+65
+66    /// Construct `I3BarBlock` from this widget
+67    pub fn get_data(&self, shared_config: &SharedConfig, id: usize) -> Result<Vec<I3BarBlock>> {
+68        // Create a "template" block
+69        let (key_bg, key_fg) = shared_config.theme.get_colors(self.state);
+70        let (full, short) = self.source.render(shared_config, &self.values)?;
+71        let mut template = I3BarBlock {
+72            instance: format!("{id}:"),
+73            background: key_bg,
+74            color: key_fg,
+75            ..I3BarBlock::default()
+76        };
+77
+78        // Collect all the pieces into "parts"
+79        let mut parts = Vec::new();
+80
+81        if full.is_empty() {
+82            return Ok(parts);
+83        }
+84
+85        // If short text is available, it's necessary to hide all full blocks. `swaybar`/`i3bar`
+86        // will switch a block to "short mode" only if it's "short_text" is set to a non-empty
+87        // string "<span/>" is a non-empty string and it doesn't display anything. It's kinda hacky,
+88        // but it works.
+89        if !short.is_empty() {
+90            template.short_text = "<span/>".into();
+91        }
+92
+93        parts.extend(full.into_iter().map(|w| {
+94            let mut data = template.clone();
+95            data.full_text = w.formatted_text();
+96            if let Some(i) = &w.metadata.instance {
+97                data.instance.push_str(i);
+98            }
+99            data
+100        }));
+101
+102        template.full_text = "<span/>".into();
+103        parts.extend(short.into_iter().map(|w| {
+104            let mut data = template.clone();
+105            data.short_text = w.formatted_text();
+106            if let Some(i) = &w.metadata.instance {
+107                data.instance.push_str(i);
+108            }
+109            data
+110        }));
+111
+112        Ok(parts)
+113    }
+114}
+115
+116/// State of the widget. Affects the theming.
+117#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, SmartDefault)]
+118pub enum State {
+119    #[default]
+120    #[serde(alias = "idle")]
+121    Idle,
+122    #[serde(alias = "info")]
+123    Info,
+124    #[serde(alias = "good")]
+125    Good,
+126    #[serde(alias = "warning")]
+127    Warning,
+128    #[serde(alias = "critical")]
+129    Critical,
+130}
+131
+132/// The source of text for widget
+133#[derive(Debug, Clone, SmartDefault)]
+134enum Source {
+135    /// Collapsed widget (only icon will be displayed)
+136    #[default]
+137    None,
+138    /// Simple text
+139    Text(String),
+140    /// A format template
+141    Format(Format),
+142}
+143
+144impl Source {
+145    fn render(
+146        &self,
+147        config: &SharedConfig,
+148        values: &Values,
+149    ) -> Result<(Vec<Fragment>, Vec<Fragment>)> {
+150        match self {
+151            Self::Text(text) => Ok((vec![text.clone().into()], vec![])),
+152            Self::Format(format) => format.render(values, config),
+153            Self::None => Ok((vec![], vec![])),
+154        }
+155    }
+156}
\ No newline at end of file diff --git a/src/i3status_rs/wrappers.rs.html b/src/i3status_rs/wrappers.rs.html new file mode 100644 index 0000000000..b76e4ad1dd --- /dev/null +++ b/src/i3status_rs/wrappers.rs.html @@ -0,0 +1,253 @@ +wrappers.rs - source

i3status_rs/
wrappers.rs

1use crate::errors::*;
+2
+3use serde::de::{self, Deserialize, Deserializer};
+4use std::borrow::Cow;
+5use std::fmt::{self, Display};
+6use std::marker::PhantomData;
+7use std::ops::RangeInclusive;
+8use std::str::FromStr;
+9use std::time::Duration;
+10
+11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+12pub struct Seconds<const ALLOW_ONCE: bool = true>(pub Duration);
+13
+14impl<const ALLOW_ONCE: bool> From<u64> for Seconds<ALLOW_ONCE> {
+15    fn from(v: u64) -> Self {
+16        Self::new(v)
+17    }
+18}
+19
+20impl<const ALLOW_ONCE: bool> Seconds<ALLOW_ONCE> {
+21    pub fn new(value: u64) -> Self {
+22        Self(Duration::from_secs(value))
+23    }
+24
+25    pub fn timer(self) -> tokio::time::Interval {
+26        let mut timer = tokio::time::interval_at(tokio::time::Instant::now() + self.0, self.0);
+27        timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
+28        timer
+29    }
+30
+31    pub fn seconds(self) -> u64 {
+32        self.0.as_secs()
+33    }
+34}
+35
+36impl<'de, const ALLOW_ONCE: bool> Deserialize<'de> for Seconds<ALLOW_ONCE> {
+37    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+38    where
+39        D: Deserializer<'de>,
+40    {
+41        struct SecondsVisitor<const ALLOW_ONCE: bool>;
+42
+43        impl<const ALLOW_ONCE: bool> de::Visitor<'_> for SecondsVisitor<ALLOW_ONCE> {
+44            type Value = Seconds<ALLOW_ONCE>;
+45
+46            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+47                formatter.write_str("\"once\", i64 or f64")
+48            }
+49
+50            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+51            where
+52                E: de::Error,
+53            {
+54                if ALLOW_ONCE && v == "once" {
+55                    Ok(Seconds(Duration::from_secs(60 * 60 * 24 * 365)))
+56                } else {
+57                    Err(E::custom(format!("'{v}' is not a valid duration")))
+58                }
+59            }
+60
+61            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
+62            where
+63                E: de::Error,
+64            {
+65                Ok(Seconds(Duration::from_secs(v as u64)))
+66            }
+67
+68            fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
+69            where
+70                E: de::Error,
+71            {
+72                Ok(Seconds(Duration::from_secs_f64(v)))
+73            }
+74        }
+75
+76        deserializer.deserialize_any(SecondsVisitor)
+77    }
+78}
+79
+80#[derive(Debug, Clone)]
+81pub struct ShellString(pub Cow<'static, str>);
+82
+83impl<T> From<T> for ShellString
+84where
+85    T: Into<Cow<'static, str>>,
+86{
+87    fn from(v: T) -> Self {
+88        Self(v.into())
+89    }
+90}
+91
+92impl<'de> Deserialize<'de> for ShellString {
+93    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+94    where
+95        D: Deserializer<'de>,
+96    {
+97        struct Visitor;
+98
+99        impl de::Visitor<'_> for Visitor {
+100            type Value = ShellString;
+101
+102            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+103                formatter.write_str("text")
+104            }
+105
+106            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+107            where
+108                E: de::Error,
+109            {
+110                Ok(ShellString(v.to_string().into()))
+111            }
+112        }
+113
+114        deserializer.deserialize_any(Visitor)
+115    }
+116}
+117
+118impl ShellString {
+119    pub fn new<T: Into<Cow<'static, str>>>(value: T) -> Self {
+120        Self(value.into())
+121    }
+122
+123    pub fn expand(&self) -> Result<Cow<'_, str>> {
+124        shellexpand::full(&self.0).error("Failed to expand string")
+125    }
+126}
+127
+128/// A map with keys being ranges.
+129#[derive(Debug, Default, Clone)]
+130pub struct RangeMap<K, V>(Vec<(RangeInclusive<K>, V)>);
+131
+132impl<K, V> RangeMap<K, V> {
+133    pub fn get(&self, key: &K) -> Option<&V>
+134    where
+135        K: PartialOrd,
+136    {
+137        self.0
+138            .iter()
+139            .find_map(|(k, v)| k.contains(key).then_some(v))
+140    }
+141}
+142
+143impl<K, V> From<Vec<(RangeInclusive<K>, V)>> for RangeMap<K, V> {
+144    fn from(vec: Vec<(RangeInclusive<K>, V)>) -> Self {
+145        Self(vec)
+146    }
+147}
+148
+149impl<'de, K, V> Deserialize<'de> for RangeMap<K, V>
+150where
+151    K: FromStr,
+152    K::Err: Display,
+153    V: Deserialize<'de>,
+154{
+155    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+156    where
+157        D: Deserializer<'de>,
+158    {
+159        struct Visitor<K, V>(PhantomData<(K, V)>);
+160
+161        impl<'de, K, V> de::Visitor<'de> for Visitor<K, V>
+162        where
+163            K: FromStr,
+164            K::Err: Display,
+165            V: Deserialize<'de>,
+166        {
+167            type Value = RangeMap<K, V>;
+168
+169            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+170                formatter.write_str("range map")
+171            }
+172
+173            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
+174            where
+175                A: de::MapAccess<'de>,
+176            {
+177                let mut vec = Vec::with_capacity(map.size_hint().unwrap_or(2));
+178                while let Some((range, val)) = map.next_entry::<String, V>()? {
+179                    let (start, end) = range
+180                        .split_once("..")
+181                        .error("invalid range")
+182                        .serde_error()?;
+183                    let start: K = start.parse().serde_error()?;
+184                    let end: K = end.parse().serde_error()?;
+185                    vec.push((start..=end, val));
+186                }
+187                Ok(RangeMap(vec))
+188            }
+189        }
+190
+191        deserializer.deserialize_map(Visitor(PhantomData))
+192    }
+193}
+194
+195#[derive(Debug, Clone)]
+196pub struct SerdeRegex(pub regex::Regex);
+197
+198impl PartialEq for SerdeRegex {
+199    fn eq(&self, other: &Self) -> bool {
+200        self.0.as_str() == other.0.as_str()
+201    }
+202}
+203
+204impl Eq for SerdeRegex {}
+205
+206impl std::hash::Hash for SerdeRegex {
+207    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+208        self.0.as_str().hash(state);
+209    }
+210}
+211
+212impl<'de> Deserialize<'de> for SerdeRegex {
+213    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+214    where
+215        D: Deserializer<'de>,
+216    {
+217        struct Visitor;
+218
+219        impl de::Visitor<'_> for Visitor {
+220            type Value = SerdeRegex;
+221
+222            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+223                formatter.write_str("a regex")
+224            }
+225
+226            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+227            where
+228                E: de::Error,
+229            {
+230                regex::Regex::new(v).map(SerdeRegex).map_err(E::custom)
+231            }
+232        }
+233
+234        deserializer.deserialize_any(Visitor)
+235    }
+236}
+237
+238/// Display a slice. Similar to Debug impl for slice, but uses Display impl for elements.
+239pub struct DisplaySlice<'a, T>(pub &'a [T]);
+240
+241impl<T: Display> Display for DisplaySlice<'_, T> {
+242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+243        struct DisplayAsDebug<'a, T>(&'a T);
+244        impl<T: Display> fmt::Debug for DisplayAsDebug<'_, T> {
+245            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+246                fmt::Display::fmt(self.0, f)
+247            }
+248        }
+249        f.debug_list()
+250            .entries(self.0.iter().map(DisplayAsDebug))
+251            .finish()
+252    }
+253}
\ No newline at end of file diff --git a/src/icons.rs b/src/icons.rs deleted file mode 100644 index c9fcd45f35..0000000000 --- a/src/icons.rs +++ /dev/null @@ -1,167 +0,0 @@ -use crate::errors::*; -use crate::util; -use serde::Deserialize; -use std::collections::HashMap; - -#[derive(Deserialize, Debug, Clone)] -#[serde(try_from = "IconsConfigRaw")] -pub struct Icons(pub HashMap); - -#[derive(Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum Icon { - Single(String), - Progression(Vec), -} - -impl From<&'static str> for Icon { - fn from(value: &'static str) -> Self { - Self::Single(value.into()) - } -} - -impl From<[&str; N]> for Icon { - fn from(value: [&str; N]) -> Self { - Self::Progression(value.iter().map(|s| s.to_string()).collect()) - } -} - -impl Default for Icons { - fn default() -> Self { - // "none" icon set - Self(map! { - "backlight" => "BRIGHT", - "bat" => "BAT", - "bat_charging" => "CHG", - "bat_not_available" => "BAT N/A", - "bell" => "ON", - "bell-slash" => "OFF", - "bluetooth" => "BT", - "calendar" => "CAL", - "cogs" => "LOAD", - "cpu" => "CPU", - "cpu_boost_on" => "BOOST ON", - "cpu_boost_off" => "BOOST OFF", - "disk_drive" => "DISK", - "docker" => "DOCKER", - "github" => "GITHUB", - "gpu" => "GPU", - "headphones" => "HEAD", - "hueshift" => "HUE", - "joystick" => "JOY", - "keyboard" => "KBD", - "mail" => "MAIL", - "memory_mem" => "MEM", - "memory_swap" => "SWAP", - "mouse" => "MOUSE", - "music" => "MUSIC", - "music_next" => ">", - "music_pause" => "||", - "music_play" => ">", - "music_prev" => "<", - "net_bridge" => "BRIDGE", - "net_cellular" => [ - "NO SIGNAL", - "0 BARS", - "1 BAR", - "2 BARS", - "3 BARS", - "4 BARS", - ], - "net_down" => "DOWN", - "net_loopback" => "LO", - "net_modem" => "MODEM", - "net_up" => "UP ", - "net_vpn" => "VPN", - "net_wired" => "ETH", - "net_wireless" => "WLAN", - "notification" => "NOTIF", - "phone" => "PHONE", - "phone_disconnected" => "PHONE", - "ping" => "PING", - "pomodoro" => "POMODORO", - "pomodoro_break" => "BREAK", - "pomodoro_paused" => "PAUSED", - "pomodoro_started" => "STARTED", - "pomodoro_stopped" => "STOPPED", - "resolution" => "RES", - "scratchpad" => "[]", - "tasks" => "TSK", - "tea" => "TEA", - "thermometer" => "TEMP", - "time" => "TIME", - "toggle_off" => "OFF", - "toggle_on" => "ON", - "unknown" => "??", - "update" => "UPD", - "uptime" => "UP", - "volume" => "VOL", - "volume_muted" => "VOL MUTED", - "microphone" => "MIC", - "microphone_muted" => "MIC MUTED", - "weather_clouds_night" => "CLOUDY", - "weather_clouds" => "CLOUDY", - "weather_default" => "WEATHER", - "weather_fog_night" => "FOG", - "weather_fog" => "FOG", - "weather_moon" => "MOONY", - "weather_rain_night" => "RAIN", - "weather_rain" => "RAIN", - "weather_snow" => "SNOW", - "weather_sun" => "SUNNY", - "weather_thunder_night" => "STORM", - "weather_thunder" => "STORM", - "webcam" => "CAM", - "xrandr" => "SCREEN" - }) - } -} - -impl Icons { - pub fn from_file(file: &str) -> Result { - if file == "none" { - Ok(Icons::default()) - } else { - let file = util::find_file(file, Some("icons"), Some("toml")) - .or_error(|| format!("Icon set '{file}' not found"))?; - Ok(Icons(util::deserialize_toml_file(file)?)) - } - } - - pub fn apply_overrides(&mut self, overrides: HashMap) { - self.0.extend(overrides); - } - - pub fn get(&self, icon: &'_ str, value: Option) -> Option<&str> { - match (self.0.get(icon)?, value) { - (Icon::Single(icon), _) => Some(icon), - (Icon::Progression(prog), _) if prog.is_empty() => None, - (Icon::Progression(prog), None) => Some(prog.last().unwrap()), - (Icon::Progression(prog), Some(value)) => { - let index = ((value * prog.len() as f64) as usize).clamp(0, prog.len() - 1); - Some(prog[index].as_str()) - } - } - } -} - -#[derive(Deserialize, Default)] -#[serde(deny_unknown_fields, default)] -struct IconsConfigRaw { - icons: Option, - overrides: Option>, -} - -impl TryFrom for Icons { - type Error = Error; - - fn try_from(raw: IconsConfigRaw) -> Result { - let mut icons = Self::from_file(raw.icons.as_deref().unwrap_or("none"))?; - if let Some(overrides) = raw.overrides { - for icon in overrides { - icons.0.insert(icon.0, icon.1); - } - } - Ok(icons) - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 8ac8c1eae9..0000000000 --- a/src/lib.rs +++ /dev/null @@ -1,469 +0,0 @@ -#![warn(clippy::match_same_arms)] -#![warn(clippy::semicolon_if_nothing_returned)] -#![warn(clippy::unnecessary_wraps)] -#![warn(clippy::unused_trait_names)] -#![allow(clippy::single_match)] -#![cfg_attr(docsrs, feature(doc_cfg))] - -#[macro_use] -pub mod util; -pub mod blocks; -pub mod click; -pub mod config; -pub mod errors; -pub mod escape; -pub mod formatting; -pub mod geolocator; -pub mod icons; -mod netlink; -pub mod protocol; -mod signals; -mod subprocess; -pub mod themes; -pub mod widget; -mod wrappers; - -pub use env_logger; -pub use serde_json; -pub use tokio; - -use std::borrow::Cow; -use std::pin::Pin; -use std::sync::{Arc, LazyLock}; -use std::time::Duration; - -use futures::Stream; -use futures::stream::{FuturesUnordered, StreamExt as _}; -use tokio::process::Command; -use tokio::sync::{Notify, mpsc}; - -use crate::blocks::{BlockAction, BlockError, CommonApi}; -use crate::click::{ClickHandler, MouseButton}; -use crate::config::{BlockConfigEntry, Config, SharedConfig}; -use crate::errors::*; -use crate::formatting::Format; -use crate::formatting::value::Value; -use crate::protocol::i3bar_block::I3BarBlock; -use crate::protocol::i3bar_event::{self, I3BarEvent}; -use crate::signals::Signal; -use crate::widget::{State, Widget}; - -const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); -const REQWEST_TIMEOUT: Duration = Duration::from_secs(10); - -static REQWEST_CLIENT: LazyLock = LazyLock::new(|| { - reqwest::Client::builder() - .user_agent(APP_USER_AGENT) - .timeout(REQWEST_TIMEOUT) - .build() - .unwrap() -}); - -static REQWEST_CLIENT_IPV4: LazyLock = LazyLock::new(|| { - reqwest::Client::builder() - .user_agent(APP_USER_AGENT) - .local_address(Some(std::net::Ipv4Addr::UNSPECIFIED.into())) - .timeout(REQWEST_TIMEOUT) - .build() - .unwrap() -}); - -type BoxedFuture = Pin>>; - -type BoxedStream = Pin>>; - -type WidgetUpdatesSender = mpsc::UnboundedSender<(usize, Vec)>; - -/// A feature-rich and resource-friendly replacement for i3status(1), written in Rust. The -/// i3status-rs program writes a stream of configurable "blocks" of system information (time, -/// battery status, volume, etc.) to standard output in the JSON format understood by i3bar(1) and -/// sway-bar(5). -#[derive(Debug, clap::Parser)] -#[clap(author, about, long_about, version = env!("VERSION"))] -pub struct CliArgs { - /// Sets a TOML config file - /// - /// 1. If full absolute path given, then use it as is: `/home/foo/i3rs-config.toml` - /// - /// 2. If filename given, e.g. "custom_theme.toml", then first look in `$XDG_CONFIG_HOME/i3status-rust` - /// - /// 3. Then look for it in `$XDG_DATA_HOME/i3status-rust` - /// - /// 4. Otherwise look for it in `/usr/share/i3status-rust` - #[clap(default_value = "config.toml")] - pub config: String, - /// Ignore any attempts by i3 to pause the bar when hidden/fullscreen - #[clap(long = "never-pause")] - pub never_pause: bool, - /// Do not send the init sequence - #[clap(hide = true, long = "no-init")] - pub no_init: bool, - /// The maximum number of blocking threads spawned by tokio - #[clap(long = "threads", short = 'j', default_value = "2")] - pub blocking_threads: usize, -} - -pub struct BarState { - config: Config, - - blocks: Vec, - fullscreen_block: Option, - running_blocks: FuturesUnordered>, - - widget_updates_sender: WidgetUpdatesSender, - blocks_render_cache: Vec, - - request_sender: mpsc::UnboundedSender, - request_receiver: mpsc::UnboundedReceiver, - - widget_updates_stream: BoxedStream>, - signals_stream: BoxedStream, - events_stream: BoxedStream, -} - -#[derive(Debug)] -struct Request { - block_id: usize, - cmd: RequestCmd, -} - -#[derive(Debug)] -enum RequestCmd { - SetWidget(Widget), - UnsetWidget, - SetError(Error), - SetDefaultActions(&'static [(MouseButton, Option<&'static str>, &'static str)]), - SubscribeToActions(mpsc::UnboundedSender), -} - -#[derive(Debug, Clone)] -struct RenderedBlock { - pub segments: Vec, - pub merge_with_next: bool, -} - -#[derive(Debug)] -pub struct Block { - id: usize, - name: &'static str, - - update_request: Arc, - action_sender: Option>, - - click_handler: ClickHandler, - default_actions: &'static [(MouseButton, Option<&'static str>, &'static str)], - signal: Option, - shared_config: SharedConfig, - - error_format: Format, - error_fullscreen_format: Format, - - state: BlockState, -} - -#[derive(Debug)] -enum BlockState { - None, - Normal { widget: Widget }, - Error { widget: Widget }, -} - -impl Block { - fn notify_intervals(&self, tx: &WidgetUpdatesSender) { - let intervals = match &self.state { - BlockState::None => Vec::new(), - BlockState::Normal { widget } | BlockState::Error { widget } => widget.intervals(), - }; - let _ = tx.send((self.id, intervals)); - } - - fn send_action(&mut self, action: BlockAction) { - if let Some(sender) = &self.action_sender - && sender.send(action).is_err() - { - self.action_sender = None; - } - } - - fn set_error(&mut self, fullscreen: bool, error: Error) { - let error = BlockError { - block_id: self.id, - block_name: self.name, - error, - }; - - let mut widget = Widget::new() - .with_state(State::Critical) - .with_format(if fullscreen { - self.error_fullscreen_format.clone() - } else { - self.error_format.clone() - }); - widget.set_values(map! { - "full_error_message" => Value::text(error.to_string()), - [if let Some(v) = &error.error.message] "short_error_message" => Value::text(v.to_string()), - }); - self.state = BlockState::Error { widget }; - } -} - -impl BarState { - pub fn new(config: Config) -> Self { - let (request_sender, request_receiver) = mpsc::unbounded_channel(); - let (widget_updates_sender, widget_updates_stream) = - formatting::scheduling::manage_widgets_updates(); - Self { - blocks: Vec::new(), - fullscreen_block: None, - running_blocks: FuturesUnordered::new(), - - widget_updates_sender, - blocks_render_cache: Vec::new(), - - request_sender, - request_receiver, - - widget_updates_stream, - signals_stream: signals::signals_stream(), - events_stream: i3bar_event::events_stream( - config.invert_scrolling, - Duration::from_millis(config.double_click_delay), - ), - - config, - } - } - - pub async fn spawn_block(&mut self, block_config: BlockConfigEntry) -> Result<()> { - if let Some(cmd) = &block_config.common.if_command { - // TODO: async - if !Command::new("sh") - .args(["-c", cmd]) - .output() - .await - .error("failed to run if_command")? - .status - .success() - { - return Ok(()); - } - } - - let mut shared_config = self.config.shared.clone(); - - // Overrides - if let Some(icons_format) = block_config.common.icons_format { - shared_config.icons_format = Arc::new(icons_format); - } - if let Some(theme_overrides) = block_config.common.theme_overrides { - Arc::make_mut(&mut shared_config.theme).apply_overrides(theme_overrides)?; - } - if let Some(icons_overrides) = block_config.common.icons_overrides { - Arc::make_mut(&mut shared_config.icons).apply_overrides(icons_overrides); - } - - let update_request = Arc::new(Notify::new()); - - let api = CommonApi { - id: self.blocks.len(), - update_request: update_request.clone(), - request_sender: self.request_sender.clone(), - error_interval: Duration::from_secs(block_config.common.error_interval), - geolocator: self.config.geolocator.clone(), - }; - - let error_format = block_config - .common - .error_format - .with_default_config(&self.config.error_format); - let error_fullscreen_format = block_config - .common - .error_fullscreen_format - .with_default_config(&self.config.error_fullscreen_format); - - let block = Block { - id: self.blocks.len(), - name: block_config.config.name(), - - update_request, - action_sender: None, - - click_handler: block_config.common.click, - default_actions: &[], - signal: block_config.common.signal, - shared_config, - - error_format, - error_fullscreen_format, - - state: BlockState::None, - }; - - block_config.config.spawn(api, &mut self.running_blocks); - - self.blocks.push(block); - self.blocks_render_cache.push(RenderedBlock { - segments: Vec::new(), - merge_with_next: block_config.common.merge_with_next, - }); - - Ok(()) - } - - fn process_request(&mut self, request: Request) { - let block = &mut self.blocks[request.block_id]; - match request.cmd { - RequestCmd::SetWidget(widget) => { - block.state = BlockState::Normal { widget }; - if self.fullscreen_block == Some(request.block_id) { - self.fullscreen_block = None; - } - } - RequestCmd::UnsetWidget => { - block.state = BlockState::None; - if self.fullscreen_block == Some(request.block_id) { - self.fullscreen_block = None; - } - } - RequestCmd::SetError(error) => { - block.set_error(self.fullscreen_block == Some(request.block_id), error); - } - RequestCmd::SetDefaultActions(actions) => { - block.default_actions = actions; - } - RequestCmd::SubscribeToActions(action_sender) => { - block.action_sender = Some(action_sender); - } - } - block.notify_intervals(&self.widget_updates_sender); - } - - fn render_block(&mut self, id: usize) -> Result<(), BlockError> { - let block = &mut self.blocks[id]; - let data = &mut self.blocks_render_cache[id].segments; - match &block.state { - BlockState::None => { - data.clear(); - } - BlockState::Normal { widget } | BlockState::Error { widget, .. } => { - *data = widget - .get_data(&block.shared_config, id) - .map_err(|error| BlockError { - block_id: id, - block_name: block.name, - error, - })?; - } - } - Ok(()) - } - - fn render(&self) { - if let Some(id) = self.fullscreen_block { - protocol::print_blocks(&[&self.blocks_render_cache[id]], &self.config.shared); - } else { - protocol::print_blocks(&self.blocks_render_cache, &self.config.shared); - } - } - - async fn process_event(&mut self, restart: fn() -> !) -> Result<(), BlockError> { - tokio::select! { - // Poll blocks - Some(()) = self.running_blocks.next() => (), - // Receive messages from blocks - Some(request) = self.request_receiver.recv() => { - let id = request.block_id; - self.process_request(request); - self.render_block(id)?; - self.render(); - } - // Handle scheduled updates - Some(ids) = self.widget_updates_stream.next() => { - for id in ids { - self.render_block(id)?; - } - self.render(); - } - // Handle clicks - Some(event) = self.events_stream.next() => { - let block = self.blocks.get_mut(event.id).expect("Events receiver: ID out of bounds"); - match &mut block.state { - BlockState::None => (), - BlockState::Normal { .. } => { - let result = block.click_handler.handle(&event).await.map_err(|error| BlockError { - block_id: event.id, - block_name: block.name, - error, - })?; - match result { - Some(post_actions) => { - if let Some(action) = post_actions.action { - block.send_action(Cow::Owned(action)); - } - if post_actions.update { - block.update_request.notify_one(); - } - } - None => { - if let Some((_, _, action)) = block.default_actions - .iter() - .find(|(btn, widget, _)| *btn == event.button && *widget == event.instance.as_deref()) { - block.send_action(Cow::Borrowed(action)); - } - } - } - } - BlockState::Error { widget } => { - if self.fullscreen_block == Some(event.id) { - self.fullscreen_block = None; - widget.set_format(block.error_format.clone()); - } else { - self.fullscreen_block = Some(event.id); - widget.set_format(block.error_fullscreen_format.clone()); - } - block.notify_intervals(&self.widget_updates_sender); - self.render_block(event.id)?; - self.render(); - } - } - } - // Handle signals - Some(signal) = self.signals_stream.next() => match signal { - Signal::Usr1 => { - for block in &self.blocks { - block.update_request.notify_one(); - } - } - Signal::Usr2 => restart(), - Signal::Custom(signal) => { - for block in &self.blocks { - if block.signal == Some(signal) { - block.update_request.notify_one(); - } - } - } - } - } - Ok(()) - } - - pub async fn run_event_loop(mut self, restart: fn() -> !) -> Result<(), BlockError> { - loop { - if let Err(error) = self.process_event(restart).await { - let block = &mut self.blocks[error.block_id]; - - if matches!(block.state, BlockState::Error { .. }) { - // This should never happen. If this code runs, it could mean that we - // got an error while trying to display and error. We better stop here. - return Err(error); - } - - block.set_error(self.fullscreen_block == Some(block.id), error.error); - block.notify_intervals(&self.widget_updates_sender); - - self.render_block(error.block_id)?; - self.render(); - } - } - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 31330f6d04..0000000000 --- a/src/main.rs +++ /dev/null @@ -1,90 +0,0 @@ -use clap::Parser; - -use i3status_rs::blocks::BlockError; -use i3status_rs::config::Config; -use i3status_rs::errors::*; -use i3status_rs::escape::Escaped; -use i3status_rs::widget::{State, Widget}; -use i3status_rs::{BarState, protocol, util}; - -#[derive(Debug, thiserror::Error)] -enum ErrorMaybeInBlock { - #[error(transparent)] - InBlock(#[from] BlockError), - #[error(transparent)] - NotInBlock(#[from] Error), -} - -fn main() { - env_logger::init(); - - let args = i3status_rs::CliArgs::parse(); - let blocking_threads = args.blocking_threads; - - if !args.no_init { - protocol::init(args.never_pause); - } - - let result: Result<(), ErrorMaybeInBlock> = tokio::runtime::Builder::new_current_thread() - .max_blocking_threads(blocking_threads) - .enable_all() - .build() - .unwrap() - .block_on(async move { - let config_path = util::find_file(&args.config, None, Some("toml")) - .or_error(|| format!("Configuration file '{}' not found", args.config))?; - let mut config: Config = util::deserialize_toml_file(&config_path)?; - let blocks = std::mem::take(&mut config.blocks); - let mut bar = BarState::new(config); - for block_config in blocks { - bar.spawn_block(block_config).await?; - } - bar.run_event_loop(restart).await?; - Ok(()) - }); - if let Err(error) = result { - let error_widget = Widget::new() - .with_text(error.to_string().pango_escaped()) - .with_state(State::Critical); - - println!( - "{},", - serde_json::to_string(&error_widget.get_data(&Default::default(), 0).unwrap()).unwrap() - ); - eprintln!("\n\n{error}\n\n"); - dbg!(error); - - // Wait for USR2 signal to restart - signal_hook::iterator::Signals::new([signal_hook::consts::SIGUSR2]) - .unwrap() - .forever() - .next() - .unwrap(); - restart(); - } -} - -/// Restart in-place -fn restart() -> ! { - use std::env; - use std::ffi::CString; - use std::os::unix::ffi::OsStringExt; - - // On linux this line should be OK - let exe = CString::new(env::current_exe().unwrap().into_os_string().into_vec()).unwrap(); - - // Get current arguments - let mut arg: Vec = env::args_os() - .map(|a| CString::new(a.into_vec()).unwrap()) - .collect(); - - // Add "--no-init" argument if not already added - let no_init_arg = CString::new("--no-init").unwrap(); - if !arg.contains(&no_init_arg) { - arg.push(no_init_arg); - } - - // Restart - nix::unistd::execvp(&exe, &arg).unwrap(); - unreachable!(); -} diff --git a/src/netlink.rs b/src/netlink.rs deleted file mode 100644 index a69861dc52..0000000000 --- a/src/netlink.rs +++ /dev/null @@ -1,497 +0,0 @@ -use neli::attr::Attribute as _; -use neli::consts::{nl::*, rtnl::*, socket::*}; -use neli::nl::{NlPayload, Nlmsghdr}; -use neli::rtnl::{Ifaddrmsg, Ifinfomsg, Rtmsg}; -use neli::socket::{NlSocketHandle, tokio::NlSocket}; -use neli::types::RtBuffer; - -use regex::Regex; - -use libc::c_uchar; - -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use std::ops; -use std::path::Path; - -use crate::errors::*; -use crate::util; - -// From `linux/rtnetlink.h` -const RT_SCOPE_HOST: c_uchar = 254; - -#[derive(Debug)] -pub struct NetDevice { - pub iface: Interface, - pub wifi_info: Option, - pub ip: Option, - pub ipv6: Option, - pub icon: &'static str, - pub tun_wg_ppp: bool, - pub nameservers: Vec, -} - -#[derive(Debug, Default)] -pub struct WifiInfo { - pub ssid: Option, - pub signal: Option, - pub frequency: Option, - pub bitrate: Option, -} - -impl NetDevice { - pub async fn new(iface_re: Option<&Regex>) -> Result> { - let mut sock = NlSocket::new( - NlSocketHandle::connect(NlFamily::Route, None, &[]).error("Socket error")?, - ) - .error("Socket error")?; - - let mut ifaces = get_interfaces(&mut sock, iface_re) - .await - .map_err(BoxErrorWrapper) - .error("Failed to fetch interfaces")?; - if ifaces.is_empty() { - return Ok(None); - } - - let default_iface = get_default_interface(&mut sock) - .await - .map_err(BoxErrorWrapper) - .error("Failed to get default interface")?; - - let iface_position = ifaces - .iter() - .position(|i| i.index == default_iface) - .or_else(|| ifaces.iter().position(|i| i.operstate == Operstate::Up)) - .unwrap_or(0); - - let iface = ifaces.swap_remove(iface_position); - let wifi_info = WifiInfo::new(iface.index).await?; - let ip = ipv4(&mut sock, iface.index).await?; - let ipv6 = ipv6(&mut sock, iface.index).await?; - let nameservers = read_nameservers() - .await - .error("Failed to read nameservers")?; - - // TODO: use netlink for the these too - // I don't believe that this should ever change, so set it now: - let path = Path::new("/sys/class/net").join(&iface.name); - let tun = iface.name.starts_with("tun") - || iface.name.starts_with("tap") - || path.join("tun_flags").exists(); - let (wg, ppp) = util::read_file(path.join("uevent")) - .await - .map_or((false, false), |c| { - (c.contains("wireguard"), c.contains("ppp")) - }); - - let icon = if wifi_info.is_some() { - "net_wireless" - } else if tun || wg || ppp { - "net_vpn" - } else if iface.name == "lo" { - "net_loopback" - } else { - "net_wired" - }; - - Ok(Some(Self { - iface, - wifi_info, - ip, - ipv6, - icon, - tun_wg_ppp: tun | wg | ppp, - nameservers, - })) - } - - pub fn is_up(&self) -> bool { - self.tun_wg_ppp - || self.iface.operstate == Operstate::Up - || (self.iface.operstate == Operstate::Unknown - && (self.ip.is_some() || self.ipv6.is_some())) - } - - pub fn ssid(&self) -> Option { - self.wifi_info.as_ref()?.ssid.clone() - } - - pub fn frequency(&self) -> Option { - self.wifi_info.as_ref()?.frequency - } - - pub fn bitrate(&self) -> Option { - self.wifi_info.as_ref()?.bitrate - } - - pub fn signal(&self) -> Option { - self.wifi_info.as_ref()?.signal - } -} - -impl WifiInfo { - async fn new(if_index: i32) -> Result> { - /// - fn signal_percents(raw: f64) -> f64 { - const MAX_LEVEL: f64 = -20.; - const MIN_LEVEL: f64 = -85.; - const DIFF: f64 = MAX_LEVEL - MIN_LEVEL; - (100. - (MAX_LEVEL - raw) * (15. * DIFF + 62. * (MAX_LEVEL - raw)) / (DIFF * DIFF)) - .clamp(0., 100.) - } - - fn ssid_from_bss_info_elements(mut bytes: &[u8]) -> Option { - while bytes.len() > 2 && bytes[0] != 0 { - bytes = &bytes[(bytes[1] as usize + 2)..]; - } - - if bytes.len() < 2 || bytes.len() < bytes[1] as usize + 2 { - return None; - }; - - let ssid_len = bytes[1] as usize; - let raw_ssid = &bytes[2..][..ssid_len]; - - Some(String::from_utf8_lossy(raw_ssid).into_owned()) - } - - // Ignore connection error because `nl80211` might not be enabled on the system. - let Ok(mut socket) = neli_wifi::AsyncSocket::connect() else { - return Ok(None); - }; - - let interfaces = socket - .get_interfaces_info() - .await - .error("Failed to get nl80211 interfaces")?; - - for interface in interfaces { - if let Some(index) = interface.index - && index == if_index - && let Ok(ap) = socket.get_station_info(index).await - { - // TODO: are there any situations when there is more than one station? - let Some(ap) = ap.into_iter().next() else { - continue; - }; - - let bss = socket - .get_bss_info(index) - .await - .unwrap_or_default() - .into_iter() - .find(|bss| bss.status == Some(1)); - - let raw_signal = match ap.signal { - Some(signal) => Some(signal), - None => bss - .as_ref() - .and_then(|bss| bss.signal) - .map(|s| (s / 100) as i8), - }; - - let ssid = interface - .ssid - .as_deref() - .map(|ssid| String::from_utf8_lossy(ssid).into_owned()) - .or_else(|| { - bss.as_ref() - .and_then(|bss| bss.information_elements.as_deref()) - .and_then(ssid_from_bss_info_elements) - }); - - return Ok(Some(Self { - ssid, - frequency: interface.frequency.map(|f| f as f64 * 1e6), - signal: raw_signal.map(|s| signal_percents(s as f64)), - bitrate: ap.tx_bitrate.map(|b| b as f64 * 1e5), // 100kbit/s -> bit/s - })); - } - } - Ok(None) - } -} - -#[derive(Debug, Default, Clone, Copy)] -pub struct InterfaceStats { - pub rx_bytes: u64, - pub tx_bytes: u64, -} - -impl ops::Sub for InterfaceStats { - type Output = Self; - - fn sub(mut self, rhs: Self) -> Self::Output { - self.rx_bytes = self.rx_bytes.saturating_sub(rhs.rx_bytes); - self.tx_bytes = self.tx_bytes.saturating_sub(rhs.tx_bytes); - self - } -} - -impl InterfaceStats { - fn from_stats64(stats: &[u8]) -> Self { - // stats looks something like that: - // - // #[repr(C)] - // struct RtnlLinkStats64 { - // rx_packets: u64, - // tx_packets: u64, - // rx_bytes: u64, - // tx_bytes: u64, - // // the rest is omitted - // } - assert!(stats.len() >= 8 * 4); - Self { - rx_bytes: u64::from_ne_bytes(stats[16..24].try_into().unwrap()), - tx_bytes: u64::from_ne_bytes(stats[24..32].try_into().unwrap()), - } - } -} - -#[derive(Debug)] -pub struct Interface { - pub index: i32, - pub operstate: Operstate, - pub name: String, - pub stats: Option, -} - -macro_rules! recv_until_done { - ($sock:ident, $payload:ident: $payload_type:ty => $($code:tt)*) => { - let mut buf = Vec::new(); - 'recv: loop { - let msgs = $sock.recv::(&mut buf).await?; - for msg in msgs { - if msg.nl_type == libc::NLMSG_DONE as u16 { - break 'recv; - } - if let NlPayload::Payload($payload) = msg.nl_payload { - $($code)* - } - } - } - }; -} - -async fn get_interfaces( - sock: &mut NlSocket, - filter: Option<&Regex>, -) -> Result, Box> { - sock.send(&Nlmsghdr::new( - None, - Rtm::Getlink, - NlmFFlags::new(&[NlmF::Dump, NlmF::Request]), - None, - None, - NlPayload::Payload(Ifinfomsg::new( - RtAddrFamily::Unspecified, - Arphrd::None, - 0, - IffFlags::empty(), - IffFlags::empty(), - RtBuffer::new(), - )), - )) - .await?; - - let mut interfaces = Vec::new(); - - recv_until_done!(sock, msg: Ifinfomsg => { - let mut name = None; - let mut stats = None; - let mut operstate = Operstate::Unknown; - for attr in msg.rtattrs.iter() { - match attr.rta_type { - Ifla::Ifname => name = Some(attr.get_payload_as_with_len()?), - Ifla::Stats64 => stats = Some(InterfaceStats::from_stats64(attr.payload().as_ref())), - Ifla::Operstate => operstate = attr.get_payload_as::()?.into(), - _ => (), - } - } - let name: String = name.unwrap(); - if filter.is_none_or(|f| f.is_match(&name)) { - interfaces.push(Interface { - index: msg.ifi_index, - operstate, - name, - stats, - }); - } - }); - - Ok(interfaces) -} - -async fn get_default_interface( - sock: &mut NlSocket, -) -> Result> { - sock.send(&Nlmsghdr::new( - None, - Rtm::Getroute, - NlmFFlags::new(&[NlmF::Request, NlmF::Dump]), - None, - None, - NlPayload::Payload(Rtmsg { - rtm_family: RtAddrFamily::Inet, - rtm_dst_len: 0, - rtm_src_len: 0, - rtm_tos: 0, - rtm_table: RtTable::Unspec, - rtm_protocol: Rtprot::Unspec, - rtm_scope: RtScope::Universe, - rtm_type: Rtn::Unspec, - rtm_flags: RtmFFlags::empty(), - rtattrs: RtBuffer::new(), - }), - )) - .await?; - - let mut best_index = 0; - let mut best_metric = u32::MAX; - - recv_until_done!(sock, msg: Rtmsg => { - if msg.rtm_type != Rtn::Unicast { - continue; - } - // Only check default routes (rtm_dst_len == 0) - if msg.rtm_dst_len != 0 { - continue; - } - - let mut index = None; - let mut metric = 0u32; - for attr in msg.rtattrs.iter() { - match attr.rta_type { - Rta::Oif => index = Some(attr.get_payload_as::()?), - Rta::Priority => metric = attr.get_payload_as::()?, - _ => (), - } - } - if let Some(i) = index - && metric < best_metric { - best_metric = metric; - best_index = i; - } - }); - - Ok(best_index) -} - -async fn ip_payload( - sock: &mut NlSocket, - ifa_family: RtAddrFamily, - ifa_index: i32, -) -> Result, Box> { - sock.send(&Nlmsghdr::new( - None, - Rtm::Getaddr, - NlmFFlags::new(&[NlmF::Dump, NlmF::Request]), - None, - None, - NlPayload::Payload(Ifaddrmsg { - ifa_family, - ifa_prefixlen: 0, - ifa_flags: IfaFFlags::empty(), - ifa_scope: 0, - ifa_index: 0, - rtattrs: RtBuffer::new(), - }), - )) - .await?; - - let mut payload = None; - - recv_until_done!(sock, msg: Ifaddrmsg => { - if msg.ifa_index != ifa_index || msg.ifa_scope >= RT_SCOPE_HOST || payload.is_some() { - continue; - } - - let attr_handle = msg.rtattrs.get_attr_handle(); - - if let Some(attr) = attr_handle.get_attribute(Ifa::Local) - .or_else(|| attr_handle.get_attribute(Ifa::Address)) - && let Ok(p) = attr.rta_payload.as_ref().try_into() - { - payload = Some(p); - } - }); - - Ok(payload) -} - -async fn ipv4(sock: &mut NlSocket, ifa_index: i32) -> Result> { - Ok(ip_payload(sock, RtAddrFamily::Inet, ifa_index) - .await - .map_err(BoxErrorWrapper) - .error("Failed to get IP address")? - .map(Ipv4Addr::from)) -} - -async fn ipv6(sock: &mut NlSocket, ifa_index: i32) -> Result> { - Ok(ip_payload(sock, RtAddrFamily::Inet6, ifa_index) - .await - .map_err(BoxErrorWrapper) - .error("Failed to get IPv6 address")? - .map(Ipv6Addr::from)) -} - -async fn read_nameservers() -> Result> { - let file = util::read_file("/etc/resolv.conf") - .await - .error("Failed to read /etc/resolv.conf")?; - let mut nameservers = Vec::new(); - - for line in file.lines() { - let mut line_parts = line.split_whitespace(); - if line_parts.next() == Some("nameserver") - && let Some(mut ip) = line_parts.next() - { - // TODO: use the zone id somehow? - if let Some((without_zone_id, _zone_id)) = ip.split_once('%') { - ip = without_zone_id; - } - nameservers.push(ip.parse().error("Unable to parse ip")?); - } - } - - Ok(nameservers) -} - -// Source: https://www.kernel.org/doc/Documentation/networking/operstates.txt -#[derive(Debug, PartialEq, Eq)] -pub enum Operstate { - /// Interface is in unknown state, neither driver nor userspace has set - /// operational state. Interface must be considered for user data as - /// setting operational state has not been implemented in every driver. - Unknown, - /// Unused in current kernel (notpresent interfaces normally disappear), - /// just a numerical placeholder. - Notpresent, - /// Interface is unable to transfer data on L1, f.e. ethernet is not - /// plugged or interface is ADMIN down. - Down, - /// Interfaces stacked on an interface that is IF_OPER_DOWN show this - /// state (f.e. VLAN). - Lowerlayerdown, - /// Unused in current kernel. - Testing, - /// Interface is L1 up, but waiting for an external event, f.e. for a - /// protocol to establish. (802.1X) - Dormant, - /// Interface is operational up and can be used. - Up, -} - -impl From for Operstate { - fn from(value: u8) -> Self { - match value { - 1 => Self::Notpresent, - 2 => Self::Down, - 3 => Self::Lowerlayerdown, - 4 => Self::Testing, - 5 => Self::Dormant, - 6 => Self::Up, - _ => Self::Unknown, - } - } -} diff --git a/src/protocol.rs b/src/protocol.rs deleted file mode 100644 index 822fc6766b..0000000000 --- a/src/protocol.rs +++ /dev/null @@ -1,140 +0,0 @@ -pub mod i3bar_block; -pub mod i3bar_event; - -use std::borrow::Borrow; - -use crate::RenderedBlock; -use crate::config::SharedConfig; -use crate::themes::color::Color; -use crate::themes::separator::Separator; - -use i3bar_block::I3BarBlock; - -pub fn init(never_pause: bool) { - if never_pause { - println!("{{\"version\": 1, \"click_events\": true, \"stop_signal\": 0}}\n["); - } else { - println!("{{\"version\": 1, \"click_events\": true}}\n["); - } -} - -pub(crate) fn print_blocks(blocks: &[B], config: &SharedConfig) -where - B: Borrow, -{ - let mut prev_last_bg = Color::None; - let mut rendered_blocks = vec![]; - - // The right most block should never be alternated - let mut alt = blocks - .iter() - .map(|x| x.borrow()) - .filter(|x| !x.segments.is_empty() && !x.merge_with_next) - .count() - % 2 - == 0; - - let mut logical_block_i = 0; - - let mut prev_merge_with_next = false; - - for (i, widgets) in blocks - .iter() - .map(|x| x.borrow()) - .filter(|x| !x.segments.is_empty()) - .cloned() - .enumerate() - { - let RenderedBlock { - mut segments, - merge_with_next, - } = widgets; - - for segment in &mut segments { - segment.name = Some(logical_block_i.to_string()); - - // Apply tint for all widgets of every second block - // TODO: Allow for other non-additive tints - if alt { - segment.background = segment.background + config.theme.alternating_tint_bg; - segment.color = segment.color + config.theme.alternating_tint_fg; - } - } - - if !merge_with_next { - alt = !alt; - } - - let separator = match &config.theme.start_separator { - Separator::Custom(_) if i == 0 => &config.theme.start_separator, - _ => &config.theme.separator, - }; - - if let Separator::Custom(separator) = separator { - if !prev_merge_with_next { - // The first widget's BG is used to get the FG color for the current separator - let sep_fg = if config.theme.separator_fg == Color::Auto { - segments.first().unwrap().background - } else { - config.theme.separator_fg - }; - - // The separator's BG is the last block's last widget's BG - let sep_bg = if config.theme.separator_bg == Color::Auto { - prev_last_bg - } else { - config.theme.separator_bg - }; - - let separator = I3BarBlock { - full_text: separator.clone(), - background: sep_bg, - color: sep_fg, - ..Default::default() - }; - - rendered_blocks.push(separator); - } - } else if !merge_with_next { - // Re-add native separator on last widget for native theme - segments.last_mut().unwrap().separator = None; - segments.last_mut().unwrap().separator_block_width = None; - } - - if !merge_with_next { - logical_block_i += 1; - } - - prev_merge_with_next = merge_with_next; - prev_last_bg = segments.last().unwrap().background; - - rendered_blocks.extend(segments); - } - - if let Separator::Custom(end_separator) = &config.theme.end_separator { - // The separator's FG is the last block's last widget's BG - let sep_fg = if config.theme.separator_fg == Color::Auto { - prev_last_bg - } else { - config.theme.separator_fg - }; - - // The separator has no background color - let sep_bg = if config.theme.separator_bg == Color::Auto { - Color::None - } else { - config.theme.separator_bg - }; - - let separator = I3BarBlock { - full_text: end_separator.clone(), - background: sep_bg, - color: sep_fg, - ..Default::default() - }; - - rendered_blocks.push(separator); - } - - println!("{},", serde_json::to_string(&rendered_blocks).unwrap()); -} diff --git a/src/protocol/i3bar_block.rs b/src/protocol/i3bar_block.rs deleted file mode 100644 index eeb72917ab..0000000000 --- a/src/protocol/i3bar_block.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::themes::color::Color; -use serde::Serialize; - -/// Represent block as described in -#[derive(Serialize, Debug, Clone)] -pub struct I3BarBlock { - pub full_text: String, - #[serde(skip_serializing_if = "String::is_empty")] - pub short_text: String, - #[serde(skip_serializing_if = "Color::skip_ser")] - pub color: Color, - #[serde(skip_serializing_if = "Color::skip_ser")] - pub background: Color, - #[serde(skip_serializing_if = "Option::is_none")] - pub border: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub border_top: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub border_right: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub border_bottom: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub border_left: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub min_width: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub align: Option, - /// This project uses `name` field to uniquely identify each "logical block". For example two - /// "config blocks" merged using `merge_with_next` will have the same `name`. This information - /// could be used by some bar frontends (such as `i3bar-river`) and will be ignored by `i3bar` - /// and `swaybar`. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// This project uses `instance` field to uniquely identify each block and optionally a part - /// of the block, e.g. a "button". The format is `{block_id}:{optional_widget_name}`. This info - /// is used when dispatching click events. - #[serde(skip_serializing_if = "String::is_empty")] - pub instance: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub urgent: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub separator: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub separator_block_width: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub markup: Option, -} - -impl Default for I3BarBlock { - fn default() -> Self { - #[cfg(not(feature = "debug_borders"))] - let border = None; - #[cfg(feature = "debug_borders")] - let border = Some("#ff0000".to_string()); - Self { - full_text: String::new(), - short_text: String::new(), - color: Color::None, - background: Color::None, - border, - border_top: None, - border_right: None, - border_bottom: None, - border_left: None, - min_width: None, - align: None, - name: None, - instance: String::new(), - urgent: None, - separator: Some(false), - separator_block_width: Some(0), - markup: Some("pango".to_string()), - } - } -} - -#[derive(Serialize, Debug, Clone, Copy)] -#[allow(dead_code)] -#[serde(rename_all = "lowercase")] -pub enum I3BarBlockAlign { - Center, - Right, - Left, -} - -#[derive(Serialize, Debug, Clone)] -#[allow(dead_code)] -#[serde(untagged)] -pub enum I3BarBlockMinWidth { - Pixels(usize), - Text(String), -} diff --git a/src/protocol/i3bar_event.rs b/src/protocol/i3bar_event.rs deleted file mode 100644 index b50b6fe986..0000000000 --- a/src/protocol/i3bar_event.rs +++ /dev/null @@ -1,112 +0,0 @@ -use std::os::unix::io::FromRawFd as _; -use std::time::Duration; - -use serde::Deserialize; - -use futures::StreamExt as _; -use tokio::fs::File; -use tokio::io::{AsyncBufReadExt as _, BufReader}; - -use crate::BoxedStream; -use crate::click::MouseButton; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct I3BarEvent { - pub id: usize, - pub instance: Option, - pub button: MouseButton, -} - -fn unprocessed_events_stream(invert_scrolling: bool) -> BoxedStream { - // Avoid spawning a blocking therad (why doesn't tokio do this too?) - // This should be safe given that this function is called only once - let stdin = unsafe { File::from_raw_fd(0) }; - let lines = BufReader::new(stdin).lines(); - - futures::stream::unfold(lines, move |mut lines| async move { - loop { - // Take only the valid JSON object between curly braces (cut off leading bracket, commas and whitespace) - let line = lines.next_line().await.ok().flatten()?; - let line = line.trim_start_matches(|c| c != '{'); - let line = line.trim_end_matches(|c| c != '}'); - - if line.is_empty() { - continue; - } - - #[derive(Deserialize)] - struct I3BarEventRaw { - instance: Option, - button: MouseButton, - } - - let event: I3BarEventRaw = match serde_json::from_str(line) { - Ok(event) => event, - Err(err) => { - eprintln!("Failed to deserialize click event.\nData: {line}\nError: {err}"); - continue; - } - }; - - let (id, instance) = match event.instance { - Some(name) => { - let (id, instance) = name.split_once(':').unwrap(); - let instance = if instance.is_empty() { - None - } else { - Some(instance.to_owned()) - }; - (id.parse().unwrap(), instance) - } - None => continue, - }; - - use MouseButton::*; - let button = match (event.button, invert_scrolling) { - (WheelUp, false) | (WheelDown, true) => WheelUp, - (WheelUp, true) | (WheelDown, false) => WheelDown, - (other, _) => other, - }; - - let event = I3BarEvent { - id, - instance, - button, - }; - - break Some((event, lines)); - } - }) - .boxed_local() -} - -pub fn events_stream( - invert_scrolling: bool, - double_click_delay: Duration, -) -> BoxedStream { - let events = unprocessed_events_stream(invert_scrolling); - futures::stream::unfold((events, None), move |(mut events, pending)| async move { - if let Some(pending) = pending { - return Some((pending, (events, None))); - } - - let mut event = events.next().await?; - - // Handle double clicks (for now only left) - if event.button == MouseButton::Left - && !double_click_delay.is_zero() - && let Ok(new_event) = tokio::time::timeout(double_click_delay, events.next()).await - { - let new_event = new_event?; - if event == new_event { - event.button = MouseButton::DoubleLeft; - } else { - return Some((event, (events, Some(new_event)))); - } - } - - Some((event, (events, None))) - }) - .fuse() - .boxed_local() -} diff --git a/src/signals.rs b/src/signals.rs deleted file mode 100644 index eebff92ecd..0000000000 --- a/src/signals.rs +++ /dev/null @@ -1,26 +0,0 @@ -use futures::stream::StreamExt as _; -use libc::{SIGRTMAX, SIGRTMIN}; -use signal_hook::consts::{SIGUSR1, SIGUSR2}; -use signal_hook_tokio::Signals; - -use crate::BoxedStream; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Signal { - Usr1, - Usr2, - Custom(i32), -} - -/// Returns an infinite stream of `Signal`s -pub fn signals_stream() -> BoxedStream { - let (sigmin, sigmax) = (SIGRTMIN(), SIGRTMAX()); - let signals = Signals::new((sigmin..sigmax).chain([SIGUSR1, SIGUSR2])).unwrap(); - signals - .map(move |signal| match signal { - SIGUSR1 => Signal::Usr1, - SIGUSR2 => Signal::Usr2, - x => Signal::Custom(x - sigmin), - }) - .boxed() -} diff --git a/src/subprocess.rs b/src/subprocess.rs deleted file mode 100644 index f4f51ac103..0000000000 --- a/src/subprocess.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::io; -use std::os::unix::process::CommandExt as _; -use std::process::{Command, Stdio}; - -/// Spawn a new detached process -pub fn spawn_process(cmd: &str, args: &[&str]) -> io::Result<()> { - let mut proc = Command::new(cmd); - proc.args(args); - proc.stdin(Stdio::null()); - proc.stdout(Stdio::null()); - // Safety: libc::daemon() is async-signal-safe - unsafe { - proc.pre_exec(|| match libc::daemon(0, 0) { - -1 => Err(io::Error::other("Failed to detach new process")), - _ => Ok(()), - }); - } - proc.spawn()?.wait()?; - Ok(()) -} - -/// Spawn a new detached shell -pub fn spawn_shell(cmd: &str) -> io::Result<()> { - spawn_process("sh", &["-c", cmd]) -} - -pub async fn spawn_shell_sync(cmd: &str) -> io::Result<()> { - tokio::process::Command::new("sh") - .args(["-c", cmd]) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .spawn()? - .wait() - .await?; - Ok(()) -} diff --git a/src/themes.rs b/src/themes.rs deleted file mode 100644 index 750e295864..0000000000 --- a/src/themes.rs +++ /dev/null @@ -1,221 +0,0 @@ -pub mod color; -pub mod separator; -pub mod xresources; - -use std::fmt; -use std::ops::{Deref, DerefMut}; - -use serde::{Deserialize, de}; - -use crate::errors::*; -use crate::util; -use crate::widget::State; -use color::Color; -use separator::Separator; - -#[derive(Debug, Clone)] -pub struct Theme(pub ThemeInner); - -impl Default for Theme { - fn default() -> Self { - ThemeUserConfig::default() - .try_into() - .unwrap_or_else(|_| Self(ThemeInner::default())) - } -} - -impl Deref for Theme { - type Target = ThemeInner; - fn deref(&self) -> &Self::Target { - &self.0 - } -} -impl DerefMut for Theme { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -#[derive(Deserialize, Debug, Clone, Default)] -#[serde(deny_unknown_fields, default)] -pub struct ThemeInner { - pub idle_bg: Color, - pub idle_fg: Color, - pub info_bg: Color, - pub info_fg: Color, - pub good_bg: Color, - pub good_fg: Color, - pub warning_bg: Color, - pub warning_fg: Color, - pub critical_bg: Color, - pub critical_fg: Color, - pub separator: Separator, - pub separator_bg: Color, - pub separator_fg: Color, - pub alternating_tint_bg: Color, - pub alternating_tint_fg: Color, - pub end_separator: Separator, - pub start_separator: Separator, -} - -impl Theme { - pub fn get_colors(&self, state: State) -> (Color, Color) { - match state { - State::Idle => (self.idle_bg, self.idle_fg), - State::Info => (self.info_bg, self.info_fg), - State::Good => (self.good_bg, self.good_fg), - State::Warning => (self.warning_bg, self.warning_fg), - State::Critical => (self.critical_bg, self.critical_fg), - } - } - - pub fn apply_overrides(&mut self, overrides: ThemeOverrides) -> Result<()> { - let copy = self.clone(); - - if let Some(separator) = overrides.separator { - self.separator = separator; - } - if let Some(end_separator) = overrides.end_separator { - self.end_separator = end_separator; - } - if let Some(start_separator) = overrides.start_separator { - self.start_separator = start_separator; - } - - macro_rules! apply { - ($prop:tt) => { - if let Some(color) = overrides.$prop { - self.$prop = color.eval(©)?; - } - }; - } - apply!(idle_bg); - apply!(idle_fg); - apply!(info_bg); - apply!(info_fg); - apply!(good_bg); - apply!(good_fg); - apply!(warning_bg); - apply!(warning_fg); - apply!(critical_bg); - apply!(critical_fg); - apply!(separator_bg); - apply!(separator_fg); - apply!(alternating_tint_bg); - apply!(alternating_tint_fg); - - Ok(()) - } -} - -#[derive(Deserialize, Default)] -#[serde(deny_unknown_fields, default)] -pub struct ThemeUserConfig { - pub theme: Option, - pub overrides: Option, -} - -#[derive(Deserialize, Debug, Clone, Default)] -pub struct ThemeOverrides { - pub idle_bg: Option, - pub idle_fg: Option, - pub info_bg: Option, - pub info_fg: Option, - pub good_bg: Option, - pub good_fg: Option, - pub warning_bg: Option, - pub warning_fg: Option, - pub critical_bg: Option, - pub critical_fg: Option, - pub separator: Option, - pub separator_bg: Option, - pub separator_fg: Option, - pub alternating_tint_bg: Option, - pub alternating_tint_fg: Option, - pub end_separator: Option, - pub start_separator: Option, -} - -impl TryFrom for Theme { - type Error = Error; - - fn try_from(user_config: ThemeUserConfig) -> Result { - let name = user_config.theme.as_deref().unwrap_or("plain"); - let file = util::find_file(name, Some("themes"), Some("toml")) - .or_error(|| format!("Theme '{name}' not found"))?; - let theme: ThemeInner = util::deserialize_toml_file(file)?; - let mut theme = Theme(theme); - if let Some(overrides) = user_config.overrides { - theme.apply_overrides(overrides)?; - } - Ok(theme) - } -} - -#[derive(Debug, Clone)] -pub enum ColorOrLink { - Color(Color), - Link { link: String }, -} - -impl ColorOrLink { - fn eval(self, theme: &Theme) -> Result { - Ok(match self { - Self::Color(c) => c, - Self::Link { link } => match link.as_str() { - "idle_bg" => theme.idle_bg, - "idle_fg" => theme.idle_fg, - "info_bg" => theme.info_bg, - "info_fg" => theme.info_fg, - "good_bg" => theme.good_bg, - "good_fg" => theme.good_fg, - "warning_bg" => theme.warning_bg, - "warning_fg" => theme.warning_fg, - "critical_bg" => theme.critical_bg, - "critical_fg" => theme.critical_fg, - "separator_bg" => theme.separator_bg, - "separator_fg" => theme.separator_fg, - "alternating_tint_bg" => theme.alternating_tint_bg, - "alternating_tint_fg" => theme.alternating_tint_fg, - _ => return Err(Error::new(format!("{link} is not a correct theme color"))), - }, - }) - } -} - -impl<'de> Deserialize<'de> for ColorOrLink { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = ColorOrLink; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("color or link") - } - - fn visit_map(self, map: A) -> Result - where - A: de::MapAccess<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct Link { - link: String, - } - Link::deserialize(de::value::MapAccessDeserializer::new(map)) - .map(|link| ColorOrLink::Link { link: link.link }) - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - v.parse::().serde_error().map(ColorOrLink::Color) - } - } - deserializer.deserialize_any(Visitor) - } -} diff --git a/src/themes/color.rs b/src/themes/color.rs deleted file mode 100644 index 22f5171e88..0000000000 --- a/src/themes/color.rs +++ /dev/null @@ -1,276 +0,0 @@ -use crate::errors::*; -use serde::de::{self, Deserializer, Visitor}; -use serde::{Deserialize, Serialize, Serializer}; -use smart_default::SmartDefault; -use std::fmt; -use std::ops::Add; -use std::str::FromStr; - -/// An RGBA color (red, green, blue, alpha). -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -pub struct Rgba { - pub r: u8, - pub g: u8, - pub b: u8, - pub a: u8, -} - -impl Rgba { - /// Create a new RGBA color. - /// - /// `r`: red component (0 to 255). - /// - /// `g`: green component (0 to 255). - /// - /// `b`: blue component (0 to 255). - /// - /// `a`: alpha component (0 to 255). - pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { - Self { r, g, b, a } - } - - /// Create a new RGBA color from the `hex` value. - /// - /// ```let cyan = Rgba::from_hex(0xffffff);``` - pub fn from_hex(hex: u32) -> Self { - let [r, g, b, a] = hex.to_be_bytes(); - Self { r, g, b, a } - } -} - -impl Add for Rgba { - type Output = Self; - fn add(self, rhs: Self) -> Self::Output { - Rgba::new( - self.r.saturating_add(rhs.r), - self.g.saturating_add(rhs.g), - self.b.saturating_add(rhs.b), - self.a.saturating_add(rhs.a), - ) - } -} - -/// An HSVA color (hue, saturation, value, alpha). -#[derive(Copy, Clone, Debug, Default)] -pub struct Hsva { - pub h: f64, - pub s: f64, - pub v: f64, - pub a: u8, -} - -impl Hsva { - /// Create a new HSVA color. - /// - /// `h`: hue component (0 to 360) - /// - /// `s`: saturation component (0 to 1) - /// - /// `v`: value component (0 to 1) - /// - /// `a`: alpha component (0 to 255). - pub fn new(h: f64, s: f64, v: f64, a: u8) -> Self { - Self { h, s, v, a } - } -} - -impl PartialEq for Hsva { - fn eq(&self, other: &Self) -> bool { - approx(self.h, other.h) - && approx(self.s, other.s) - && approx(self.v, other.v) - && self.a == other.a - } -} - -impl From for Hsva { - fn from(rgba: Rgba) -> Self { - let r = rgba.r as f64 / 255.0; - let g = rgba.g as f64 / 255.0; - let b = rgba.b as f64 / 255.0; - - let min = r.min(g.min(b)); - let max = r.max(g.max(b)); - let delta = max - min; - - let v = max; - let s = match max > 1e-3 { - true => delta / max, - false => 0.0, - }; - let h = match delta == 0.0 { - true => 0.0, - false => { - if r == max { - (g - b) / delta - } else if g == max { - 2.0 + (b - r) / delta - } else { - 4.0 + (r - g) / delta - } - } - }; - let h2 = ((h * 60.0) + 360.0) % 360.0; - - Self::new(h2, s, v, rgba.a) - } -} - -impl From for Rgba { - fn from(hsva: Hsva) -> Self { - let range = (hsva.h / 60.0) as u8; - let c = hsva.v * hsva.s; - let x = c * (1.0 - (((hsva.h / 60.0) % 2.0) - 1.0).abs()); - let m = hsva.v - c; - - let cm_scaled = ((c + m) * 255.0) as u8; - let xm_scaled = ((x + m) * 255.0) as u8; - let m_scaled = (m * 255.0) as u8; - - match range { - 0 => Self::new(cm_scaled, xm_scaled, m_scaled, hsva.a), - 1 => Self::new(xm_scaled, cm_scaled, m_scaled, hsva.a), - 2 => Self::new(m_scaled, cm_scaled, xm_scaled, hsva.a), - 3 => Self::new(m_scaled, xm_scaled, cm_scaled, hsva.a), - 4 => Self::new(xm_scaled, m_scaled, cm_scaled, hsva.a), - _ => Self::new(cm_scaled, m_scaled, xm_scaled, hsva.a), - } - } -} - -impl Add for Hsva { - type Output = Self; - fn add(self, rhs: Self) -> Self::Output { - Hsva::new( - (self.h + rhs.h) % 360., - (self.s + rhs.s).clamp(0., 1.), - (self.v + rhs.v).clamp(0., 1.), - self.a.saturating_add(rhs.a), - ) - } -} - -pub fn approx(a: f64, b: f64) -> bool { - if a == b { - return true; - } - let eps = 1e-2; - let abs_a = a.abs(); - let abs_b = b.abs(); - let diff = (abs_a - abs_b).abs(); - if a == 0.0 || b == 0.0 || abs_a + abs_b < f64::EPSILON { - diff < eps * f64::EPSILON - } else { - diff / (abs_a + abs_b).min(f64::MAX) < eps - } -} - -#[derive(Debug, Clone, Copy, PartialEq, SmartDefault)] -pub enum Color { - #[default] - None, - Auto, - Rgba(Rgba), - Hsva(Hsva), -} - -impl Color { - pub fn skip_ser(&self) -> bool { - matches!(self, Self::None | Self::Auto) - } -} - -impl Add for Color { - type Output = Color; - fn add(self, rhs: Self) -> Self::Output { - match (self, rhs) { - // Do nothing - (x, Self::None | Self::Auto) | (Self::None | Self::Auto, x) => x, - // Hsva + Hsva => Hsva - (Color::Hsva(hsva1), Color::Hsva(hsva2)) => Color::Hsva(hsva1 + hsva2), - // Rgba + Rgba => Rgba - (Color::Rgba(rgba1), Color::Rgba(rgba2)) => Color::Rgba(rgba1 + rgba2), - // Hsva + Rgba => Hsva - // Rgba + Hsva => Hsva - (Color::Hsva(hsva), Color::Rgba(rgba)) | (Color::Rgba(rgba), Color::Hsva(hsva)) => { - Color::Hsva(hsva + rgba.into()) - } - } - } -} - -impl FromStr for Color { - type Err = Error; - - fn from_str(color: &str) -> Result { - Ok(if color == "none" || color.is_empty() { - Color::None - } else if color == "auto" { - Color::Auto - } else if color.starts_with("hsv:") { - let err_msg = || format!("'{color}' is not a valid HSVA color"); - let color = color.split_at(4).1; - let mut components = color.split(':').map(|x| x.parse::().or_error(err_msg)); - let h = components.next().or_error(err_msg)??; - let s = components.next().or_error(err_msg)??; - let v = components.next().or_error(err_msg)??; - let a = components.next().unwrap_or(Ok(100.))?; - Color::Hsva(Hsva::new(h, s / 100., v / 100., (a / 100. * 255.) as u8)) - } else if color.starts_with("x:") { - let name = color.split_at(2).1; - super::xresources::get_color(name)? - .or_error(|| format!("color '{name}' not defined in ~/.Xresources"))? - .parse() - .or_error(|| format!("invalid color definition '{name}'"))? - } else { - let err_msg = || format!("'{color}' is not a valid RGBA color"); - let rgb = color.get(1..7).or_error(err_msg)?; - let a = color.get(7..9).unwrap_or("FF"); - Color::Rgba(Rgba::from_hex( - (u32::from_str_radix(rgb, 16).or_error(err_msg)? << 8) - + u32::from_str_radix(a, 16).or_error(err_msg)?, - )) - }) - } -} - -impl Serialize for Color { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let format_rgba = - |rgba: Rgba| format!("#{:02X}{:02X}{:02X}{:02X}", rgba.r, rgba.g, rgba.b, rgba.a); - match *self { - Self::None | Self::Auto => serializer.serialize_none(), - Self::Rgba(rgba) => serializer.serialize_str(&format_rgba(rgba)), - Self::Hsva(hsva) => serializer.serialize_str(&format_rgba(hsva.into())), - } - } -} - -impl<'de> Deserialize<'de> for Color { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct ColorVisitor; - - impl Visitor<'_> for ColorVisitor { - type Value = Color; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("color") - } - - fn visit_str(self, s: &str) -> Result - where - E: de::Error, - { - s.parse().serde_error() - } - } - - deserializer.deserialize_any(ColorVisitor) - } -} diff --git a/src/themes/separator.rs b/src/themes/separator.rs deleted file mode 100644 index 0cf4740143..0000000000 --- a/src/themes/separator.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::errors::*; -use serde::Deserialize; -use serde::de::{self, Deserializer, Visitor}; -use smart_default::SmartDefault; -use std::fmt; -use std::str::FromStr; - -#[derive(Debug, Clone, PartialEq, Eq, SmartDefault)] -pub enum Separator { - #[default] - Native, - Custom(String), -} - -impl FromStr for Separator { - type Err = Error; - - fn from_str(separator: &str) -> Result { - Ok(if separator == "native" { - Self::Native - } else { - Self::Custom(separator.into()) - }) - } -} - -impl<'de> Deserialize<'de> for Separator { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct SeparatorVisitor; - - impl Visitor<'_> for SeparatorVisitor { - type Value = Separator; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a separator string or 'native'") - } - - fn visit_str(self, s: &str) -> Result - where - E: de::Error, - { - s.parse().serde_error() - } - } - - deserializer.deserialize_any(SeparatorVisitor) - } -} diff --git a/src/themes/xresources.rs b/src/themes/xresources.rs deleted file mode 100644 index a821b0db8a..0000000000 --- a/src/themes/xresources.rs +++ /dev/null @@ -1,93 +0,0 @@ -use log::debug; -use regex::Regex; - -use std::collections::HashMap; -use std::sync::LazyLock; - -use crate::errors::*; - -#[cfg(not(test))] -use std::{env, path::PathBuf}; - -#[cfg(not(test))] -fn read_xresources() -> std::io::Result { - let home = env::var("HOME").map_err(|_| std::io::Error::other("HOME env var was not set"))?; - let xresources = PathBuf::from(home + "/.Xresources"); - debug!(".Xresources @ {:?}", xresources); - std::fs::read_to_string(xresources) -} - -#[cfg(test)] -use tests::read_xresources; - -static COLOR_REGEX: LazyLock = LazyLock::new(|| { - Regex::new(r"^\s*\*(?[^: ]+)\s*:\s*(?#[a-f0-9]{6,8}).*$").unwrap() -}); - -static COLORS: LazyLock, Error>> = LazyLock::new(|| { - let content = read_xresources().error("could not read .Xresources")?; - debug!(".Xresources content:\n{}", content); - Ok(HashMap::from_iter(content.lines().filter_map(|line| { - COLOR_REGEX - .captures(line) - .map(|caps| (caps["name"].to_string(), caps["color"].to_string())) - }))) -}); - -pub fn get_color(name: &str) -> Result, Error> { - COLORS - .as_ref() - .map(|cmap| cmap.get(name)) - .map_err(Clone::clone) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::io::Result; - - #[allow(clippy::unnecessary_wraps)] - pub(crate) fn read_xresources() -> Result { - static XRESOURCES: &str = "\ - ! this is a comment\n\ - \n\ - *color4 : #feedda\n\ - \n\ - *background: #ee33aa99\n\ - "; - Ok(XRESOURCES.to_string()) - } - - #[test] - fn test_reading_colors() { - let colors = COLORS.as_ref().unwrap(); - assert_eq!(colors.get("color4"), Some(&"#feedda".to_string())); - assert_eq!(colors.get("background"), Some(&"#ee33aa99".to_string())); - assert_eq!(2, colors.len()); - } - - #[test] - fn test_deserializing_xcolors() { - use super::super::color::*; - let mut parsed_color = "x:color4".parse::().unwrap(); - assert_eq!( - parsed_color, - Color::Rgba(Rgba { - r: 254, - g: 237, - b: 218, - a: 255 - }) - ); - parsed_color = "x:background".parse::().unwrap(); - assert_eq!( - parsed_color, - Color::Rgba(Rgba { - r: 238, - g: 51, - b: 170, - a: 153, - }) - ); - } -} diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index eaadce60a8..0000000000 --- a/src/util.rs +++ /dev/null @@ -1,298 +0,0 @@ -use std::path::{Path, PathBuf}; - -use dirs::{config_dir, data_dir}; -use serde::de::DeserializeOwned; -use tokio::io::AsyncReadExt as _; -use tokio::process::Command; - -use crate::errors::*; - -/// Tries to find a file in standard locations: -/// - Fist try to find a file by full path (only if path is absolute) -/// - Then try XDG_CONFIG_HOME (e.g. `~/.config`) -/// - Then try XDG_DATA_HOME (e.g. `~/.local/share/`) -/// - Then try `/usr/share/` -/// -/// Automatically append an extension if not presented. -pub fn find_file(file: &str, subdir: Option<&str>, extension: Option<&str>) -> Option { - let file = Path::new(file); - - if file.is_absolute() && file.exists() { - return Some(file.to_path_buf()); - } - - // Try XDG_CONFIG_HOME (e.g. `~/.config`) - if let Some(mut xdg_config) = config_dir() { - xdg_config.push("i3status-rust"); - if let Some(subdir) = subdir { - xdg_config.push(subdir); - } - xdg_config.push(file); - if let Some(file) = exists_with_opt_extension(&xdg_config, extension) { - return Some(file); - } - } - - // Try XDG_DATA_HOME (e.g. `~/.local/share/`) - if let Some(mut xdg_data) = data_dir() { - xdg_data.push("i3status-rust"); - if let Some(subdir) = subdir { - xdg_data.push(subdir); - } - xdg_data.push(file); - if let Some(file) = exists_with_opt_extension(&xdg_data, extension) { - return Some(file); - } - } - - // Try `/usr/share/` - let mut usr_share_path = PathBuf::from("/usr/share/i3status-rust"); - if let Some(subdir) = subdir { - usr_share_path.push(subdir); - } - usr_share_path.push(file); - if let Some(file) = exists_with_opt_extension(&usr_share_path, extension) { - return Some(file); - } - - None -} - -fn exists_with_opt_extension(file: &Path, extension: Option<&str>) -> Option { - if file.exists() { - return Some(file.into()); - } - // If file has no extension, test with given extension - if let (None, Some(extension)) = (file.extension(), extension) { - let file = file.with_extension(extension); - // Check again with extension added - if file.exists() { - return Some(file); - } - } - None -} - -pub async fn new_dbus_connection() -> Result { - zbus::Connection::session() - .await - .error("Failed to open DBus session connection") -} - -pub async fn new_system_dbus_connection() -> Result { - zbus::Connection::system() - .await - .error("Failed to open DBus system connection") -} - -pub fn deserialize_toml_file(path: P) -> Result -where - T: DeserializeOwned, - P: AsRef, -{ - let path = path.as_ref(); - - let contents = std::fs::read_to_string(path) - .or_error(|| format!("Failed to read file: {}", path.display()))?; - - toml::from_str(&contents).map_err(|err| { - let location_msg = err - .span() - .map(|span| { - let line = 1 + contents.as_bytes()[..(span.start)] - .iter() - .filter(|b| **b == b'\n') - .count(); - format!(" at line {line}") - }) - .unwrap_or_default(); - Error::new(format!( - "Failed to deserialize TOML file {}{}: {}", - path.display(), - location_msg, - err.message() - )) - }) -} - -pub async fn read_file(path: impl AsRef) -> std::io::Result { - let mut file = tokio::fs::File::open(path).await?; - let mut content = String::new(); - file.read_to_string(&mut content).await?; - Ok(content.trim_end().to_string()) -} - -pub async fn has_command(command: &str) -> Result { - Command::new("sh") - .args([ - "-c", - format!("command -v {command} >/dev/null 2>&1").as_ref(), - ]) - .status() - .await - .or_error(|| format!("Failed to check {command} presence")) - .map(|status| status.success()) -} - -/// # Example -/// -/// ```ignore -/// let opt = Some(1); -/// let m: HashMap<&'static str, String> = map! { -/// "key" => "value", -/// [if true] "hello" => "world", -/// [if let Some(x) = opt] "opt" => x.to_string(), -/// }; -/// map! { @extend m -/// "new key" => "new value", -/// "one" => "more", -/// } -/// ``` -#[macro_export] -macro_rules! map { - (@extend $map:ident $( $([$($cond_tokens:tt)*])? $key:literal => $value:expr ),* $(,)?) => {{ - $( - map!(@insert $map, $key, $value $(,$($cond_tokens)*)?); - )* - }}; - (@extend $map:ident $( $key:expr => $value:expr ),* $(,)?) => {{ - $( - map!(@insert $map, $key, $value); - )* - }}; - (@insert $map:ident, $key:expr, $value:expr) => {{ - $map.insert($key.into(), $value.into()); - }}; - (@insert $map:ident, $key:expr, $value:expr, if $cond:expr) => {{ - if $cond { - $map.insert($key.into(), $value.into()); - } - }}; - (@insert $map:ident, $key:expr, $value:expr, if let $pat:pat = $match_on:expr) => {{ - if let $pat = $match_on { - $map.insert($key.into(), $value.into()); - } - }}; - ($($tt:tt)*) => {{ - #[allow(unused_mut)] - let mut m = ::std::collections::HashMap::new(); - map!(@extend m $($tt)*); - m - }}; -} - -pub use map; - -macro_rules! regex { - ($re:literal $(,)?) => {{ - static RE: std::sync::OnceLock = std::sync::OnceLock::new(); - RE.get_or_init(|| regex::Regex::new($re).unwrap()) - }}; -} - -macro_rules! make_log_macro { - (@wdoll $macro_name:ident, $block_name:literal, ($dol:tt)) => { - #[allow(dead_code)] - macro_rules! $macro_name { - ($dol($args:tt)+) => { - ::log::$macro_name!(target: $block_name, $dol($args)+); - }; - } - }; - ($macro_name:ident, $block_name:literal) => { - make_log_macro!(@wdoll $macro_name, $block_name, ($)); - }; -} - -pub fn format_bar_graph(content: &[f64]) -> String { - // (x * one eighth block) https://en.wikipedia.org/wiki/Block_Elements - static BARS: [char; 8] = [ - '\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}', - '\u{2588}', - ]; - - // Find min and max - let mut min = f64::INFINITY; - let mut max = f64::NEG_INFINITY; - for &v in content { - min = min.min(v); - max = max.max(v); - } - - let range = max - min; - content - .iter() - .map(|x| BARS[((x - min) / range * 7.).clamp(0., 7.) as usize]) - .collect() -} - -/// Convert 2 letter country code to Unicode -pub fn country_flag_from_iso_code(country_code: &str) -> String { - let [mut b1, mut b2]: [u8; 2] = country_code.as_bytes().try_into().unwrap_or([0, 0]); - - if !b1.is_ascii_uppercase() || !b2.is_ascii_uppercase() { - return country_code.into(); - } - - // Each char is encoded as 1F1E6 to 1F1FF for A-Z - b1 += 0xa5; - b2 += 0xa5; - // The last byte will always start with 101 (0xa0) and then the 5 least - // significant bits from the previous result - b1 = 0xa0 | (b1 & 0x1f); - b2 = 0xa0 | (b2 & 0x1f); - // Get the flag string from the UTF-8 representation of our Unicode characters. - String::from_utf8(vec![0xf0, 0x9f, 0x87, b1, 0xf0, 0x9f, 0x87, b2]).unwrap() -} - -/// A shortcut for `Default::default()` -/// See -#[inline] -pub fn default() -> T { - Default::default() -} - -pub trait StreamExtDebounced: futures::StreamExt { - fn next_debounced(&mut self) -> impl Future>; -} - -impl StreamExtDebounced for T { - async fn next_debounced(&mut self) -> Option { - let mut result = self.next().await?; - let mut noop_ctx = std::task::Context::from_waker(std::task::Waker::noop()); - loop { - match self.poll_next_unpin(&mut noop_ctx) { - std::task::Poll::Ready(Some(x)) => result = x, - std::task::Poll::Ready(None) | std::task::Poll::Pending => return Some(result), - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_has_command_ok() { - // we assume sh is always available - assert!(has_command("sh").await.unwrap()); - } - - #[tokio::test] - async fn test_has_command_err() { - // we assume thequickbrownfoxjumpsoverthelazydog command does not exist - assert!( - !has_command("thequickbrownfoxjumpsoverthelazydog") - .await - .unwrap() - ); - } - - #[test] - fn test_flags() { - assert!(country_flag_from_iso_code("ES") == "🇪🇸"); - assert!(country_flag_from_iso_code("US") == "🇺🇸"); - assert!(country_flag_from_iso_code("USA") == "USA"); - } -} diff --git a/src/widget.rs b/src/widget.rs deleted file mode 100644 index 0af2117eec..0000000000 --- a/src/widget.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::config::SharedConfig; -use crate::errors::*; -use crate::formatting::{Format, Fragment, Values}; -use crate::protocol::i3bar_block::I3BarBlock; -use serde::Deserialize; -use smart_default::SmartDefault; - -#[derive(Debug, Clone, Default)] -pub struct Widget { - pub state: State, - source: Source, - values: Values, -} - -impl Widget { - pub fn new() -> Self { - Self::default() - } - - /* - * Builders - */ - - pub fn with_text(mut self, text: String) -> Self { - self.set_text(text); - self - } - - pub fn with_state(mut self, state: State) -> Self { - self.state = state; - self - } - - pub fn with_format(mut self, format: Format) -> Self { - self.set_format(format); - self - } - - /* - * Setters - */ - - pub fn set_text(&mut self, text: String) { - if text.is_empty() { - self.source = Source::None; - } else { - self.source = Source::Text(text); - } - } - - pub fn set_format(&mut self, format: Format) { - self.source = Source::Format(format); - } - - pub fn set_values(&mut self, new_values: Values) { - self.values = new_values; - } - - pub fn intervals(&self) -> Vec { - match &self.source { - Source::Format(f) => f.intervals(), - _ => Vec::new(), - } - } - - /// Construct `I3BarBlock` from this widget - pub fn get_data(&self, shared_config: &SharedConfig, id: usize) -> Result> { - // Create a "template" block - let (key_bg, key_fg) = shared_config.theme.get_colors(self.state); - let (full, short) = self.source.render(shared_config, &self.values)?; - let mut template = I3BarBlock { - instance: format!("{id}:"), - background: key_bg, - color: key_fg, - ..I3BarBlock::default() - }; - - // Collect all the pieces into "parts" - let mut parts = Vec::new(); - - if full.is_empty() { - return Ok(parts); - } - - // If short text is available, it's necessary to hide all full blocks. `swaybar`/`i3bar` - // will switch a block to "short mode" only if it's "short_text" is set to a non-empty - // string "" is a non-empty string and it doesn't display anything. It's kinda hacky, - // but it works. - if !short.is_empty() { - template.short_text = "".into(); - } - - parts.extend(full.into_iter().map(|w| { - let mut data = template.clone(); - data.full_text = w.formatted_text(); - if let Some(i) = &w.metadata.instance { - data.instance.push_str(i); - } - data - })); - - template.full_text = "".into(); - parts.extend(short.into_iter().map(|w| { - let mut data = template.clone(); - data.short_text = w.formatted_text(); - if let Some(i) = &w.metadata.instance { - data.instance.push_str(i); - } - data - })); - - Ok(parts) - } -} - -/// State of the widget. Affects the theming. -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, SmartDefault)] -pub enum State { - #[default] - #[serde(alias = "idle")] - Idle, - #[serde(alias = "info")] - Info, - #[serde(alias = "good")] - Good, - #[serde(alias = "warning")] - Warning, - #[serde(alias = "critical")] - Critical, -} - -/// The source of text for widget -#[derive(Debug, Clone, SmartDefault)] -enum Source { - /// Collapsed widget (only icon will be displayed) - #[default] - None, - /// Simple text - Text(String), - /// A format template - Format(Format), -} - -impl Source { - fn render( - &self, - config: &SharedConfig, - values: &Values, - ) -> Result<(Vec, Vec)> { - match self { - Self::Text(text) => Ok((vec![text.clone().into()], vec![])), - Self::Format(format) => format.render(values, config), - Self::None => Ok((vec![], vec![])), - } - } -} diff --git a/src/wrappers.rs b/src/wrappers.rs deleted file mode 100644 index 2189c2d7da..0000000000 --- a/src/wrappers.rs +++ /dev/null @@ -1,253 +0,0 @@ -use crate::errors::*; - -use serde::de::{self, Deserialize, Deserializer}; -use std::borrow::Cow; -use std::fmt::{self, Display}; -use std::marker::PhantomData; -use std::ops::RangeInclusive; -use std::str::FromStr; -use std::time::Duration; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Seconds(pub Duration); - -impl From for Seconds { - fn from(v: u64) -> Self { - Self::new(v) - } -} - -impl Seconds { - pub fn new(value: u64) -> Self { - Self(Duration::from_secs(value)) - } - - pub fn timer(self) -> tokio::time::Interval { - let mut timer = tokio::time::interval_at(tokio::time::Instant::now() + self.0, self.0); - timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); - timer - } - - pub fn seconds(self) -> u64 { - self.0.as_secs() - } -} - -impl<'de, const ALLOW_ONCE: bool> Deserialize<'de> for Seconds { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct SecondsVisitor; - - impl de::Visitor<'_> for SecondsVisitor { - type Value = Seconds; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("\"once\", i64 or f64") - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - if ALLOW_ONCE && v == "once" { - Ok(Seconds(Duration::from_secs(60 * 60 * 24 * 365))) - } else { - Err(E::custom(format!("'{v}' is not a valid duration"))) - } - } - - fn visit_i64(self, v: i64) -> Result - where - E: de::Error, - { - Ok(Seconds(Duration::from_secs(v as u64))) - } - - fn visit_f64(self, v: f64) -> Result - where - E: de::Error, - { - Ok(Seconds(Duration::from_secs_f64(v))) - } - } - - deserializer.deserialize_any(SecondsVisitor) - } -} - -#[derive(Debug, Clone)] -pub struct ShellString(pub Cow<'static, str>); - -impl From for ShellString -where - T: Into>, -{ - fn from(v: T) -> Self { - Self(v.into()) - } -} - -impl<'de> Deserialize<'de> for ShellString { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct Visitor; - - impl de::Visitor<'_> for Visitor { - type Value = ShellString; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("text") - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - Ok(ShellString(v.to_string().into())) - } - } - - deserializer.deserialize_any(Visitor) - } -} - -impl ShellString { - pub fn new>>(value: T) -> Self { - Self(value.into()) - } - - pub fn expand(&self) -> Result> { - shellexpand::full(&self.0).error("Failed to expand string") - } -} - -/// A map with keys being ranges. -#[derive(Debug, Default, Clone)] -pub struct RangeMap(Vec<(RangeInclusive, V)>); - -impl RangeMap { - pub fn get(&self, key: &K) -> Option<&V> - where - K: PartialOrd, - { - self.0 - .iter() - .find_map(|(k, v)| k.contains(key).then_some(v)) - } -} - -impl From, V)>> for RangeMap { - fn from(vec: Vec<(RangeInclusive, V)>) -> Self { - Self(vec) - } -} - -impl<'de, K, V> Deserialize<'de> for RangeMap -where - K: FromStr, - K::Err: Display, - V: Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct Visitor(PhantomData<(K, V)>); - - impl<'de, K, V> de::Visitor<'de> for Visitor - where - K: FromStr, - K::Err: Display, - V: Deserialize<'de>, - { - type Value = RangeMap; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("range map") - } - - fn visit_map(self, mut map: A) -> Result - where - A: de::MapAccess<'de>, - { - let mut vec = Vec::with_capacity(map.size_hint().unwrap_or(2)); - while let Some((range, val)) = map.next_entry::()? { - let (start, end) = range - .split_once("..") - .error("invalid range") - .serde_error()?; - let start: K = start.parse().serde_error()?; - let end: K = end.parse().serde_error()?; - vec.push((start..=end, val)); - } - Ok(RangeMap(vec)) - } - } - - deserializer.deserialize_map(Visitor(PhantomData)) - } -} - -#[derive(Debug, Clone)] -pub struct SerdeRegex(pub regex::Regex); - -impl PartialEq for SerdeRegex { - fn eq(&self, other: &Self) -> bool { - self.0.as_str() == other.0.as_str() - } -} - -impl Eq for SerdeRegex {} - -impl std::hash::Hash for SerdeRegex { - fn hash(&self, state: &mut H) { - self.0.as_str().hash(state); - } -} - -impl<'de> Deserialize<'de> for SerdeRegex { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct Visitor; - - impl de::Visitor<'_> for Visitor { - type Value = SerdeRegex; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a regex") - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - regex::Regex::new(v).map(SerdeRegex).map_err(E::custom) - } - } - - deserializer.deserialize_any(Visitor) - } -} - -/// Display a slice. Similar to Debug impl for slice, but uses Display impl for elements. -pub struct DisplaySlice<'a, T>(pub &'a [T]); - -impl Display for DisplaySlice<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - struct DisplayAsDebug<'a, T>(&'a T); - impl fmt::Debug for DisplayAsDebug<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self.0, f) - } - } - f.debug_list() - .entries(self.0.iter().map(DisplayAsDebug)) - .finish() - } -} diff --git a/static.files/COPYRIGHT-7fb11f4e.txt b/static.files/COPYRIGHT-7fb11f4e.txt new file mode 100644 index 0000000000..752dab0a34 --- /dev/null +++ b/static.files/COPYRIGHT-7fb11f4e.txt @@ -0,0 +1,71 @@ +# REUSE-IgnoreStart + +These documentation pages include resources by third parties. This copyright +file applies only to those resources. The following third party resources are +included, and carry their own copyright notices and license terms: + +* Fira Sans (FiraSans-Regular.woff2, FiraSans-Medium.woff2): + + Copyright (c) 2014, Mozilla Foundation https://mozilla.org/ + with Reserved Font Name Fira Sans. + + Copyright (c) 2014, Telefonica S.A. + + Licensed under the SIL Open Font License, Version 1.1. + See FiraSans-LICENSE.txt. + +* rustdoc.css, main.js, and playpen.js: + + Copyright 2015 The Rust Developers. + Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE.txt) or + the MIT license (LICENSE-MIT.txt) at your option. + +* normalize.css: + + Copyright (c) Nicolas Gallagher and Jonathan Neal. + Licensed under the MIT license (see LICENSE-MIT.txt). + +* Source Code Pro (SourceCodePro-Regular.ttf.woff2, + SourceCodePro-Semibold.ttf.woff2, SourceCodePro-It.ttf.woff2): + + Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), + with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark + of Adobe Systems Incorporated in the United States and/or other countries. + + Licensed under the SIL Open Font License, Version 1.1. + See SourceCodePro-LICENSE.txt. + +* Source Serif 4 (SourceSerif4-Regular.ttf.woff2, SourceSerif4-Bold.ttf.woff2, + SourceSerif4-It.ttf.woff2, SourceSerif4-Semibold.ttf.woff2): + + Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name + 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United + States and/or other countries. + + Licensed under the SIL Open Font License, Version 1.1. + See SourceSerif4-LICENSE.md. + +* Nanum Barun Gothic Font (NanumBarunGothic.woff2) + + Copyright 2010, NAVER Corporation (http://www.nhncorp.com) + with Reserved Font Name Nanum, Naver Nanum, NanumGothic, Naver NanumGothic, + NanumMyeongjo, Naver NanumMyeongjo, NanumBrush, Naver NanumBrush, NanumPen, + Naver NanumPen, Naver NanumGothicEco, NanumGothicEco, + Naver NanumMyeongjoEco, NanumMyeongjoEco, Naver NanumGothicLight, + NanumGothicLight, NanumBarunGothic, Naver NanumBarunGothic. + + https://hangeul.naver.com/2017/nanum + https://github.com/hiun/NanumBarunGothic + + Licensed under the SIL Open Font License, Version 1.1. + See NanumBarunGothic-LICENSE.txt. + +* Rust logos (rust-logo.svg, favicon.svg, favicon-32x32.png) + + Copyright 2025 Rust Foundation. + Licensed under the Creative Commons Attribution license (CC-BY). + https://rustfoundation.org/policy/rust-trademark-policy/ + +This copyright file is intended to be distributed with rustdoc output. + +# REUSE-IgnoreEnd diff --git a/static.files/FiraMono-Medium-86f75c8c.woff2 b/static.files/FiraMono-Medium-86f75c8c.woff2 new file mode 100644 index 0000000000..610e9b2071 Binary files /dev/null and b/static.files/FiraMono-Medium-86f75c8c.woff2 differ diff --git a/static.files/FiraMono-Regular-87c26294.woff2 b/static.files/FiraMono-Regular-87c26294.woff2 new file mode 100644 index 0000000000..9fa44b7cc2 Binary files /dev/null and b/static.files/FiraMono-Regular-87c26294.woff2 differ diff --git a/static.files/FiraSans-Italic-81dc35de.woff2 b/static.files/FiraSans-Italic-81dc35de.woff2 new file mode 100644 index 0000000000..3f63664fee Binary files /dev/null and b/static.files/FiraSans-Italic-81dc35de.woff2 differ diff --git a/static.files/FiraSans-LICENSE-05ab6dbd.txt b/static.files/FiraSans-LICENSE-05ab6dbd.txt new file mode 100644 index 0000000000..d7e9c149b7 --- /dev/null +++ b/static.files/FiraSans-LICENSE-05ab6dbd.txt @@ -0,0 +1,98 @@ +// REUSE-IgnoreStart + +Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. +with Reserved Font Name < Fira >, + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +// REUSE-IgnoreEnd diff --git a/static.files/FiraSans-Medium-e1aa3f0a.woff2 b/static.files/FiraSans-Medium-e1aa3f0a.woff2 new file mode 100644 index 0000000000..7a1e5fc548 Binary files /dev/null and b/static.files/FiraSans-Medium-e1aa3f0a.woff2 differ diff --git a/static.files/FiraSans-MediumItalic-ccf7e434.woff2 b/static.files/FiraSans-MediumItalic-ccf7e434.woff2 new file mode 100644 index 0000000000..2d08f9f7d4 Binary files /dev/null and b/static.files/FiraSans-MediumItalic-ccf7e434.woff2 differ diff --git a/static.files/FiraSans-Regular-0fe48ade.woff2 b/static.files/FiraSans-Regular-0fe48ade.woff2 new file mode 100644 index 0000000000..e766e06ccb Binary files /dev/null and b/static.files/FiraSans-Regular-0fe48ade.woff2 differ diff --git a/static.files/LICENSE-APACHE-a60eea81.txt b/static.files/LICENSE-APACHE-a60eea81.txt new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/static.files/LICENSE-APACHE-a60eea81.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/static.files/LICENSE-MIT-23f18e03.txt b/static.files/LICENSE-MIT-23f18e03.txt new file mode 100644 index 0000000000..31aa79387f --- /dev/null +++ b/static.files/LICENSE-MIT-23f18e03.txt @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/static.files/NanumBarunGothic-13b3dcba.ttf.woff2 b/static.files/NanumBarunGothic-13b3dcba.ttf.woff2 new file mode 100644 index 0000000000..1866ad4bce Binary files /dev/null and b/static.files/NanumBarunGothic-13b3dcba.ttf.woff2 differ diff --git a/static.files/NanumBarunGothic-LICENSE-a37d393b.txt b/static.files/NanumBarunGothic-LICENSE-a37d393b.txt new file mode 100644 index 0000000000..4b3edc29eb --- /dev/null +++ b/static.files/NanumBarunGothic-LICENSE-a37d393b.txt @@ -0,0 +1,103 @@ +// REUSE-IgnoreStart + +Copyright (c) 2010, NAVER Corporation (https://www.navercorp.com/), + +with Reserved Font Name Nanum, Naver Nanum, NanumGothic, Naver NanumGothic, +NanumMyeongjo, Naver NanumMyeongjo, NanumBrush, Naver NanumBrush, NanumPen, +Naver NanumPen, Naver NanumGothicEco, NanumGothicEco, Naver NanumMyeongjoEco, +NanumMyeongjoEco, Naver NanumGothicLight, NanumGothicLight, NanumBarunGothic, +Naver NanumBarunGothic, NanumSquareRound, NanumBarunPen, MaruBuri + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +// REUSE-IgnoreEnd diff --git a/static.files/SourceCodePro-It-fc8b9304.ttf.woff2 b/static.files/SourceCodePro-It-fc8b9304.ttf.woff2 new file mode 100644 index 0000000000..462c34efcd Binary files /dev/null and b/static.files/SourceCodePro-It-fc8b9304.ttf.woff2 differ diff --git a/static.files/SourceCodePro-LICENSE-67f54ca7.txt b/static.files/SourceCodePro-LICENSE-67f54ca7.txt new file mode 100644 index 0000000000..0d2941e148 --- /dev/null +++ b/static.files/SourceCodePro-LICENSE-67f54ca7.txt @@ -0,0 +1,97 @@ +// REUSE-IgnoreStart + +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +// REUSE-IgnoreEnd diff --git a/static.files/SourceCodePro-Regular-8badfe75.ttf.woff2 b/static.files/SourceCodePro-Regular-8badfe75.ttf.woff2 new file mode 100644 index 0000000000..10b558e0b6 Binary files /dev/null and b/static.files/SourceCodePro-Regular-8badfe75.ttf.woff2 differ diff --git a/static.files/SourceCodePro-Semibold-aa29a496.ttf.woff2 b/static.files/SourceCodePro-Semibold-aa29a496.ttf.woff2 new file mode 100644 index 0000000000..5ec64eef0e Binary files /dev/null and b/static.files/SourceCodePro-Semibold-aa29a496.ttf.woff2 differ diff --git a/static.files/SourceSerif4-Bold-6d4fd4c0.ttf.woff2 b/static.files/SourceSerif4-Bold-6d4fd4c0.ttf.woff2 new file mode 100644 index 0000000000..181a07f63b Binary files /dev/null and b/static.files/SourceSerif4-Bold-6d4fd4c0.ttf.woff2 differ diff --git a/static.files/SourceSerif4-It-ca3b17ed.ttf.woff2 b/static.files/SourceSerif4-It-ca3b17ed.ttf.woff2 new file mode 100644 index 0000000000..2ae08a7bed Binary files /dev/null and b/static.files/SourceSerif4-It-ca3b17ed.ttf.woff2 differ diff --git a/static.files/SourceSerif4-LICENSE-a2cfd9d5.md b/static.files/SourceSerif4-LICENSE-a2cfd9d5.md new file mode 100644 index 0000000000..175fa4f47a --- /dev/null +++ b/static.files/SourceSerif4-LICENSE-a2cfd9d5.md @@ -0,0 +1,98 @@ + + +Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. +Copyright 2014 - 2023 Adobe (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + + diff --git a/static.files/SourceSerif4-Regular-6b053e98.ttf.woff2 b/static.files/SourceSerif4-Regular-6b053e98.ttf.woff2 new file mode 100644 index 0000000000..0263fc3042 Binary files /dev/null and b/static.files/SourceSerif4-Regular-6b053e98.ttf.woff2 differ diff --git a/static.files/SourceSerif4-Semibold-457a13ac.ttf.woff2 b/static.files/SourceSerif4-Semibold-457a13ac.ttf.woff2 new file mode 100644 index 0000000000..dd55f4e95e Binary files /dev/null and b/static.files/SourceSerif4-Semibold-457a13ac.ttf.woff2 differ diff --git a/static.files/favicon-044be391.svg b/static.files/favicon-044be391.svg new file mode 100644 index 0000000000..8b34b51198 --- /dev/null +++ b/static.files/favicon-044be391.svg @@ -0,0 +1,24 @@ + + + + + diff --git a/static.files/favicon-32x32-6580c154.png b/static.files/favicon-32x32-6580c154.png new file mode 100644 index 0000000000..69b8613ce1 Binary files /dev/null and b/static.files/favicon-32x32-6580c154.png differ diff --git a/static.files/main-eebb9057.js b/static.files/main-eebb9057.js new file mode 100644 index 0000000000..750010f4cc --- /dev/null +++ b/static.files/main-eebb9057.js @@ -0,0 +1,11 @@ +"use strict";window.RUSTDOC_TOOLTIP_HOVER_MS=300;window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS=450;function resourcePath(basename,extension){return getVar("root-path")+basename+getVar("resource-suffix")+extension;}function hideMain(){addClass(document.getElementById(MAIN_ID),"hidden");const toggle=document.getElementById("toggle-all-docs");if(toggle){toggle.setAttribute("disabled","disabled");}}function showMain(){const main=document.getElementById(MAIN_ID);if(!main){return;}removeClass(main,"hidden");const mainHeading=main.querySelector(".main-heading");if(mainHeading&&window.searchState.rustdocToolbar){if(window.searchState.rustdocToolbar.parentElement){window.searchState.rustdocToolbar.parentElement.removeChild(window.searchState.rustdocToolbar,);}mainHeading.appendChild(window.searchState.rustdocToolbar);}const toggle=document.getElementById("toggle-all-docs");if(toggle){toggle.removeAttribute("disabled");}}window.rootPath=getVar("root-path");window.currentCrate=getVar("current-crate");function setMobileTopbar(){const mobileTopbar=document.querySelector(".mobile-topbar");const locationTitle=document.querySelector(".sidebar h2.location");if(mobileTopbar){const mobileTitle=document.createElement("h2");mobileTitle.className="location";if(hasClass(document.querySelector(".rustdoc"),"crate")){mobileTitle.innerHTML=`Crate ${window.currentCrate}`;}else if(locationTitle){mobileTitle.innerHTML=locationTitle.innerHTML;}mobileTopbar.appendChild(mobileTitle);}}function getVirtualKey(ev){if("key"in ev&&typeof ev.key!=="undefined"){return ev.key;}const c=ev.charCode||ev.keyCode;if(c===27){return"Escape";}return String.fromCharCode(c);}const MAIN_ID="main-content";const SETTINGS_BUTTON_ID="settings-menu";const ALTERNATIVE_DISPLAY_ID="alternative-display";const NOT_DISPLAYED_ID="not-displayed";const HELP_BUTTON_ID="help-button";function getSettingsButton(){return document.getElementById(SETTINGS_BUTTON_ID);}function getHelpButton(){return document.getElementById(HELP_BUTTON_ID);}function getNakedUrl(){return window.location.href.split("?")[0].split("#")[0];}function insertAfter(newNode,referenceNode){referenceNode.parentNode.insertBefore(newNode,referenceNode.nextSibling);}function getOrCreateSection(id,classes){let el=document.getElementById(id);if(!el){el=document.createElement("section");el.id=id;el.className=classes;insertAfter(el,document.getElementById(MAIN_ID));}return el;}function getAlternativeDisplayElem(){return getOrCreateSection(ALTERNATIVE_DISPLAY_ID,"content hidden");}function getNotDisplayedElem(){return getOrCreateSection(NOT_DISPLAYED_ID,"hidden");}function switchDisplayedElement(elemToDisplay){const el=getAlternativeDisplayElem();if(el.children.length>0){getNotDisplayedElem().appendChild(el.firstElementChild);}if(elemToDisplay===null){addClass(el,"hidden");showMain();return;}el.appendChild(elemToDisplay);hideMain();removeClass(el,"hidden");const mainHeading=elemToDisplay.querySelector(".main-heading");if(mainHeading&&window.searchState.rustdocToolbar){if(window.searchState.rustdocToolbar.parentElement){window.searchState.rustdocToolbar.parentElement.removeChild(window.searchState.rustdocToolbar,);}mainHeading.appendChild(window.searchState.rustdocToolbar);}}function browserSupportsHistoryApi(){return window.history&&typeof window.history.pushState==="function";}function preLoadCss(cssUrl){const link=document.createElement("link");link.href=cssUrl;link.rel="preload";link.as="style";document.getElementsByTagName("head")[0].appendChild(link);}(function(){const isHelpPage=window.location.pathname.endsWith("/help.html");function loadScript(url,errorCallback){const script=document.createElement("script");script.src=url;if(errorCallback!==undefined){script.onerror=errorCallback;}document.head.append(script);}const settingsButton=getSettingsButton();if(settingsButton){settingsButton.onclick=event=>{if(event.ctrlKey||event.altKey||event.metaKey){return;}window.hideAllModals(false);addClass(getSettingsButton(),"rotate");event.preventDefault();loadScript(getVar("static-root-path")+getVar("settings-js"));setTimeout(()=>{const themes=getVar("themes").split(",");for(const theme of themes){if(theme!==""){preLoadCss(getVar("root-path")+theme+".css");}}},0);};}window.searchState={rustdocToolbar:document.querySelector("rustdoc-toolbar"),loadingText:"Loading search results...",input:document.getElementsByClassName("search-input")[0],outputElement:()=>{let el=document.getElementById("search");if(!el){el=document.createElement("section");el.id="search";getNotDisplayedElem().appendChild(el);}return el;},title:document.title,titleBeforeSearch:document.title,timeout:null,currentTab:0,focusedByTab:[null,null,null],clearInputTimeout:()=>{if(window.searchState.timeout!==null){clearTimeout(window.searchState.timeout);window.searchState.timeout=null;}},isDisplayed:()=>{const outputElement=window.searchState.outputElement();return!!outputElement&&!!outputElement.parentElement&&outputElement.parentElement.id===ALTERNATIVE_DISPLAY_ID;},focus:()=>{window.searchState.input&&window.searchState.input.focus();},defocus:()=>{window.searchState.input&&window.searchState.input.blur();},showResults:search=>{if(search===null||typeof search==="undefined"){search=window.searchState.outputElement();}switchDisplayedElement(search);document.title=window.searchState.title;},removeQueryParameters:()=>{document.title=window.searchState.titleBeforeSearch;if(browserSupportsHistoryApi()){history.replaceState(null,"",getNakedUrl()+window.location.hash);}},hideResults:()=>{switchDisplayedElement(null);window.searchState.removeQueryParameters();},getQueryStringParams:()=>{const params={};window.location.search.substring(1).split("&").map(s=>{const pair=s.split("=").map(x=>x.replace(/\+/g," "));params[decodeURIComponent(pair[0])]=typeof pair[1]==="undefined"?null:decodeURIComponent(pair[1]);});return params;},setup:()=>{const search_input=window.searchState.input;if(!search_input){return;}let searchLoaded=false;function sendSearchForm(){document.getElementsByClassName("search-form")[0].submit();}function loadSearch(){if(!searchLoaded){searchLoaded=true;loadScript(getVar("static-root-path")+getVar("search-js"),sendSearchForm);loadScript(resourcePath("search-index",".js"),sendSearchForm);}}search_input.addEventListener("focus",()=>{window.searchState.origPlaceholder=search_input.placeholder;search_input.placeholder="Type your search here.";loadSearch();});if(search_input.value!==""){loadSearch();}const params=window.searchState.getQueryStringParams();if(params.search!==undefined){window.searchState.setLoadingSearch();loadSearch();}},setLoadingSearch:()=>{const search=window.searchState.outputElement();if(!search){return;}search.innerHTML="

"+window.searchState.loadingText+"

";window.searchState.showResults(search);},descShards:new Map(),loadDesc:async function({descShard,descIndex}){if(descShard.promise===null){descShard.promise=new Promise((resolve,reject)=>{descShard.resolve=resolve;const ds=descShard;const fname=`${ds.crate}-desc-${ds.shard}-`;const url=resourcePath(`search.desc/${descShard.crate}/${fname}`,".js",);loadScript(url,reject);});}const list=await descShard.promise;return list[descIndex];},loadedDescShard:function(crate,shard,data){this.descShards.get(crate)[shard].resolve(data.split("\n"));},};const toggleAllDocsId="toggle-all-docs";let savedHash="";function handleHashes(ev){if(ev!==null&&window.searchState.isDisplayed()&&ev.newURL){switchDisplayedElement(null);const hash=ev.newURL.slice(ev.newURL.indexOf("#")+1);if(browserSupportsHistoryApi()){history.replaceState(null,"",getNakedUrl()+window.location.search+"#"+hash);}const elem=document.getElementById(hash);if(elem){elem.scrollIntoView();}}const pageId=window.location.hash.replace(/^#/,"");if(savedHash!==pageId){savedHash=pageId;if(pageId!==""){expandSection(pageId);}}if(savedHash.startsWith("impl-")){const splitAt=savedHash.indexOf("/");if(splitAt!==-1){const implId=savedHash.slice(0,splitAt);const assocId=savedHash.slice(splitAt+1);const implElems=document.querySelectorAll(`details > summary > section[id^="${implId}"]`,);onEachLazy(implElems,implElem=>{const numbered=/^(.+?)-([0-9]+)$/.exec(implElem.id);if(implElem.id!==implId&&(!numbered||numbered[1]!==implId)){return false;}return onEachLazy(implElem.parentElement.parentElement.querySelectorAll(`[id^="${assocId}"]`),item=>{const numbered=/^(.+?)-([0-9]+)$/.exec(item.id);if(item.id===assocId||(numbered&&numbered[1]===assocId)){openParentDetails(item);item.scrollIntoView();setTimeout(()=>{window.location.replace("#"+item.id);},0);return true;}},);});}}}function onHashChange(ev){hideSidebar();handleHashes(ev);}function openParentDetails(elem){while(elem){if(elem.tagName==="DETAILS"){elem.open=true;}elem=elem.parentElement;}}function expandSection(id){openParentDetails(document.getElementById(id));}function handleEscape(ev){window.searchState.clearInputTimeout();window.searchState.hideResults();ev.preventDefault();window.searchState.defocus();window.hideAllModals(true);}function handleShortcut(ev){const disableShortcuts=getSettingValue("disable-shortcuts")==="true";if(ev.ctrlKey||ev.altKey||ev.metaKey||disableShortcuts){return;}if(document.activeElement&&document.activeElement.tagName==="INPUT"&&document.activeElement.type!=="checkbox"&&document.activeElement.type!=="radio"){switch(getVirtualKey(ev)){case"Escape":handleEscape(ev);break;}}else{switch(getVirtualKey(ev)){case"Escape":handleEscape(ev);break;case"s":case"S":case"/":ev.preventDefault();window.searchState.focus();break;case"+":ev.preventDefault();expandAllDocs();break;case"-":ev.preventDefault();collapseAllDocs(false);break;case"_":ev.preventDefault();collapseAllDocs(true);break;case"?":showHelp();break;default:break;}}}document.addEventListener("keypress",handleShortcut);document.addEventListener("keydown",handleShortcut);function addSidebarItems(){if(!window.SIDEBAR_ITEMS){return;}const sidebar=document.getElementById("rustdoc-modnav");function block(shortty,id,longty){const filtered=window.SIDEBAR_ITEMS[shortty];if(!filtered){return;}const modpath=hasClass(document.querySelector(".rustdoc"),"mod")?"../":"";const h3=document.createElement("h3");h3.innerHTML=`${longty}`;const ul=document.createElement("ul");ul.className="block "+shortty;for(const name of filtered){let path;if(shortty==="mod"){path=`${modpath}${name}/index.html`;}else{path=`${modpath}${shortty}.${name}.html`;}let current_page=document.location.href.toString();if(current_page.endsWith("/")){current_page+="index.html";}const link=document.createElement("a");link.href=path;link.textContent=name;const li=document.createElement("li");if(link.href===current_page){li.classList.add("current");}li.appendChild(link);ul.appendChild(li);}sidebar.appendChild(h3);sidebar.appendChild(ul);}if(sidebar){block("primitive","primitives","Primitive Types");block("mod","modules","Modules");block("macro","macros","Macros");block("struct","structs","Structs");block("enum","enums","Enums");block("constant","constants","Constants");block("static","static","Statics");block("trait","traits","Traits");block("fn","functions","Functions");block("type","types","Type Aliases");block("union","unions","Unions");block("foreigntype","foreign-types","Foreign Types");block("keyword","keywords","Keywords");block("attr","attributes","Attribute Macros");block("derive","derives","Derive Macros");block("traitalias","trait-aliases","Trait Aliases");}}window.register_implementors=imp=>{const implementors=document.getElementById("implementors-list");const synthetic_implementors=document.getElementById("synthetic-implementors-list");const inlined_types=new Set();const TEXT_IDX=0;const SYNTHETIC_IDX=1;const TYPES_IDX=2;if(synthetic_implementors){onEachLazy(synthetic_implementors.getElementsByClassName("impl"),el=>{const aliases=el.getAttribute("data-aliases");if(!aliases){return;}aliases.split(",").forEach(alias=>{inlined_types.add(alias);});});}let currentNbImpls=implementors.getElementsByClassName("impl").length;const traitName=document.querySelector(".main-heading h1 > .trait").textContent;const baseIdName="impl-"+traitName+"-";const libs=Object.getOwnPropertyNames(imp);const script=document.querySelector("script[data-ignore-extern-crates]");const ignoreExternCrates=new Set((script?script.getAttribute("data-ignore-extern-crates"):"").split(","),);for(const lib of libs){if(lib===window.currentCrate||ignoreExternCrates.has(lib)){continue;}const structs=imp[lib];struct_loop:for(const struct of structs){const list=struct[SYNTHETIC_IDX]?synthetic_implementors:implementors;if(struct[SYNTHETIC_IDX]){for(const struct_type of struct[TYPES_IDX]){if(inlined_types.has(struct_type)){continue struct_loop;}inlined_types.add(struct_type);}}const code=document.createElement("h3");code.innerHTML=struct[TEXT_IDX];addClass(code,"code-header");onEachLazy(code.getElementsByTagName("a"),elem=>{const href=elem.getAttribute("href");if(href&&!href.startsWith("#")&&!/^(?:[a-z+]+:)?\/\//.test(href)){elem.setAttribute("href",window.rootPath+href);}});const currentId=baseIdName+currentNbImpls;const anchor=document.createElement("a");anchor.href="#"+currentId;addClass(anchor,"anchor");const display=document.createElement("div");display.id=currentId;addClass(display,"impl");display.appendChild(anchor);display.appendChild(code);list.appendChild(display);currentNbImpls+=1;}}};if(window.pending_implementors){window.register_implementors(window.pending_implementors);}window.register_type_impls=imp=>{if(!imp||!imp[window.currentCrate]){return;}window.pending_type_impls=undefined;const idMap=new Map();let implementations=document.getElementById("implementations-list");let trait_implementations=document.getElementById("trait-implementations-list");let trait_implementations_header=document.getElementById("trait-implementations");const script=document.querySelector("script[data-self-path]");const selfPath=script?script.getAttribute("data-self-path"):null;const mainContent=document.querySelector("#main-content");const sidebarSection=document.querySelector(".sidebar section");let methods=document.querySelector(".sidebar .block.method");let associatedTypes=document.querySelector(".sidebar .block.associatedtype");let associatedConstants=document.querySelector(".sidebar .block.associatedconstant");let sidebarTraitList=document.querySelector(".sidebar .block.trait-implementation");for(const impList of imp[window.currentCrate]){const types=impList.slice(2);const text=impList[0];const isTrait=impList[1]!==0;const traitName=impList[1];if(types.indexOf(selfPath)===-1){continue;}let outputList=isTrait?trait_implementations:implementations;if(outputList===null){const outputListName=isTrait?"Trait Implementations":"Implementations";const outputListId=isTrait?"trait-implementations-list":"implementations-list";const outputListHeaderId=isTrait?"trait-implementations":"implementations";const outputListHeader=document.createElement("h2");outputListHeader.id=outputListHeaderId;outputListHeader.innerText=outputListName;outputList=document.createElement("div");outputList.id=outputListId;if(isTrait){const link=document.createElement("a");link.href=`#${outputListHeaderId}`;link.innerText="Trait Implementations";const h=document.createElement("h3");h.appendChild(link);trait_implementations=outputList;trait_implementations_header=outputListHeader;sidebarSection.appendChild(h);sidebarTraitList=document.createElement("ul");sidebarTraitList.className="block trait-implementation";sidebarSection.appendChild(sidebarTraitList);mainContent.appendChild(outputListHeader);mainContent.appendChild(outputList);}else{implementations=outputList;if(trait_implementations){mainContent.insertBefore(outputListHeader,trait_implementations_header);mainContent.insertBefore(outputList,trait_implementations_header);}else{const mainContent=document.querySelector("#main-content");mainContent.appendChild(outputListHeader);mainContent.appendChild(outputList);}}}const template=document.createElement("template");template.innerHTML=text;onEachLazy(template.content.querySelectorAll("a"),elem=>{const href=elem.getAttribute("href");if(href&&!href.startsWith("#")&&!/^(?:[a-z+]+:)?\/\//.test(href)){elem.setAttribute("href",window.rootPath+href);}});onEachLazy(template.content.querySelectorAll("[id]"),el=>{let i=0;if(idMap.has(el.id)){i=idMap.get(el.id);}else if(document.getElementById(el.id)){i=1;while(document.getElementById(`${el.id}-${2 * i}`)){i=2*i;}while(document.getElementById(`${el.id}-${i}`)){i+=1;}}if(i!==0){const oldHref=`#${el.id}`;const newHref=`#${el.id}-${i}`;el.id=`${el.id}-${i}`;onEachLazy(template.content.querySelectorAll("a[href]"),link=>{if(link.getAttribute("href")===oldHref){link.href=newHref;}});}idMap.set(el.id,i+1);});const templateAssocItems=template.content.querySelectorAll("section.tymethod, "+"section.method, section.associatedtype, section.associatedconstant");if(isTrait){const li=document.createElement("li");const a=document.createElement("a");a.href=`#${template.content.querySelector(".impl").id}`;a.textContent=traitName;li.appendChild(a);sidebarTraitList.append(li);}else{onEachLazy(templateAssocItems,item=>{let block=hasClass(item,"associatedtype")?associatedTypes:(hasClass(item,"associatedconstant")?associatedConstants:(methods));if(!block){const blockTitle=hasClass(item,"associatedtype")?"Associated Types":(hasClass(item,"associatedconstant")?"Associated Constants":("Methods"));const blockClass=hasClass(item,"associatedtype")?"associatedtype":(hasClass(item,"associatedconstant")?"associatedconstant":("method"));const blockHeader=document.createElement("h3");const blockLink=document.createElement("a");blockLink.href="#implementations";blockLink.innerText=blockTitle;blockHeader.appendChild(blockLink);block=document.createElement("ul");block.className=`block ${blockClass}`;const insertionReference=methods||sidebarTraitList;if(insertionReference){const insertionReferenceH=insertionReference.previousElementSibling;sidebarSection.insertBefore(blockHeader,insertionReferenceH);sidebarSection.insertBefore(block,insertionReferenceH);}else{sidebarSection.appendChild(blockHeader);sidebarSection.appendChild(block);}if(hasClass(item,"associatedtype")){associatedTypes=block;}else if(hasClass(item,"associatedconstant")){associatedConstants=block;}else{methods=block;}}const li=document.createElement("li");const a=document.createElement("a");a.innerText=item.id.split("-")[0].split(".")[1];a.href=`#${item.id}`;li.appendChild(a);block.appendChild(li);});}outputList.appendChild(template.content);}for(const list of[methods,associatedTypes,associatedConstants,sidebarTraitList]){if(!list){continue;}const newChildren=Array.prototype.slice.call(list.children);newChildren.sort((a,b)=>{const aI=a.innerText;const bI=b.innerText;return aIbI?1:0;});list.replaceChildren(...newChildren);}};if(window.pending_type_impls){window.register_type_impls(window.pending_type_impls);}function addSidebarCrates(){if(!window.ALL_CRATES){return;}const sidebarElems=document.getElementById("rustdoc-modnav");if(!sidebarElems){return;}const h3=document.createElement("h3");h3.innerHTML="Crates";const ul=document.createElement("ul");ul.className="block crate";for(const crate of window.ALL_CRATES){const link=document.createElement("a");link.href=window.rootPath+crate+"/index.html";link.textContent=crate;const li=document.createElement("li");if(window.rootPath!=="./"&&crate===window.currentCrate){li.className="current";}li.appendChild(link);ul.appendChild(li);}sidebarElems.appendChild(h3);sidebarElems.appendChild(ul);}function expandAllDocs(){const innerToggle=document.getElementById(toggleAllDocsId);removeClass(innerToggle,"will-expand");onEachLazy(document.getElementsByClassName("toggle"),e=>{if(!hasClass(e,"type-contents-toggle")&&!hasClass(e,"more-examples-toggle")){e.open=true;}});innerToggle.children[0].innerText="Summary";}function collapseAllDocs(collapseImpls){const innerToggle=document.getElementById(toggleAllDocsId);addClass(innerToggle,"will-expand");onEachLazy(document.getElementsByClassName("toggle"),e=>{if((collapseImpls||e.parentNode.id!=="implementations-list")||(!hasClass(e,"implementors-toggle")&&!hasClass(e,"type-contents-toggle"))){e.open=false;}});innerToggle.children[0].innerText="Show all";}function toggleAllDocs(ev){const innerToggle=document.getElementById(toggleAllDocsId);if(!innerToggle){return;}if(hasClass(innerToggle,"will-expand")){expandAllDocs();}else{collapseAllDocs(ev!==undefined&&ev.shiftKey);}}(function(){const toggles=document.getElementById(toggleAllDocsId);if(toggles){toggles.onclick=toggleAllDocs;}const hideMethodDocs=getSettingValue("auto-hide-method-docs")==="true";const hideImplementations=getSettingValue("auto-hide-trait-implementations")==="true";const hideLargeItemContents=getSettingValue("auto-hide-large-items")!=="false";function setImplementorsTogglesOpen(id,open){const list=document.getElementById(id);if(list!==null){onEachLazy(list.getElementsByClassName("implementors-toggle"),e=>{e.open=open;});}}if(hideImplementations){setImplementorsTogglesOpen("trait-implementations-list",false);setImplementorsTogglesOpen("blanket-implementations-list",false);}onEachLazy(document.getElementsByClassName("toggle"),e=>{if(!hideLargeItemContents&&hasClass(e,"type-contents-toggle")){e.open=true;}if(hideMethodDocs&&hasClass(e,"method-toggle")){e.open=false;}});}());window.rustdoc_add_line_numbers_to_examples=()=>{function generateLine(nb){return`${nb}`;}onEachLazy(document.querySelectorAll(".rustdoc:not(.src) :not(.scraped-example) > .example-wrap > pre > code",),code=>{if(hasClass(code.parentElement.parentElement,"hide-lines")){removeClass(code.parentElement.parentElement,"hide-lines");return;}const lines=code.innerHTML.split("\n");const digits=(lines.length+"").length;code.innerHTML=lines.map((line,index)=>generateLine(index+1)+line).join("\n");addClass(code.parentElement.parentElement,`digits-${digits}`);});};window.rustdoc_remove_line_numbers_from_examples=()=>{onEachLazy(document.querySelectorAll(".rustdoc:not(.src) :not(.scraped-example) > .example-wrap"),x=>addClass(x,"hide-lines"),);};if(getSettingValue("line-numbers")==="true"){window.rustdoc_add_line_numbers_to_examples();}function showSidebar(){window.hideAllModals(false);const sidebar=document.getElementsByClassName("sidebar")[0];addClass(sidebar,"shown");}function hideSidebar(){const sidebar=document.getElementsByClassName("sidebar")[0];removeClass(sidebar,"shown");}window.addEventListener("resize",()=>{if(window.CURRENT_TOOLTIP_ELEMENT){const base=window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE;const force_visible=base.TOOLTIP_FORCE_VISIBLE;hideTooltip(false);if(force_visible){showTooltip(base);base.TOOLTIP_FORCE_VISIBLE=true;}}});const mainElem=document.getElementById(MAIN_ID);if(mainElem){mainElem.addEventListener("click",hideSidebar);}onEachLazy(document.querySelectorAll("a[href^='#']"),el=>{el.addEventListener("click",()=>{expandSection(el.hash.slice(1));hideSidebar();});});onEachLazy(document.querySelectorAll(".toggle > summary:not(.hideme)"),el=>{el.addEventListener("click",e=>{if(!e.target.matches("summary, a, a *")){e.preventDefault();}});});function showTooltip(e){const notable_ty=e.getAttribute("data-notable-ty");if(!window.NOTABLE_TRAITS&¬able_ty){const data=document.getElementById("notable-traits-data");if(data){window.NOTABLE_TRAITS=JSON.parse(data.innerText);}else{throw new Error("showTooltip() called with notable without any notable traits!");}}if(window.CURRENT_TOOLTIP_ELEMENT&&window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE===e){clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);return;}window.hideAllModals(false);const wrapper=document.createElement("div");if(notable_ty){wrapper.innerHTML="
"+window.NOTABLE_TRAITS[notable_ty]+"
";}else{const ttl=e.getAttribute("title");if(ttl!==null){e.setAttribute("data-title",ttl);e.removeAttribute("title");}const dttl=e.getAttribute("data-title");if(dttl!==null){const titleContent=document.createElement("div");titleContent.className="content";titleContent.appendChild(document.createTextNode(dttl));wrapper.appendChild(titleContent);}}wrapper.className="tooltip popover";const focusCatcher=document.createElement("div");focusCatcher.setAttribute("tabindex","0");focusCatcher.onfocus=hideTooltip;wrapper.appendChild(focusCatcher);const pos=e.getBoundingClientRect();wrapper.style.top=(pos.top+window.scrollY+pos.height)+"px";wrapper.style.left=0;wrapper.style.right="auto";wrapper.style.visibility="hidden";document.body.appendChild(wrapper);const wrapperPos=wrapper.getBoundingClientRect();const finalPos=pos.left+window.scrollX-wrapperPos.width+24;if(finalPos>0){wrapper.style.left=finalPos+"px";}else{wrapper.style.setProperty("--popover-arrow-offset",(wrapperPos.right-pos.right+4)+"px",);}wrapper.style.visibility="";window.CURRENT_TOOLTIP_ELEMENT=wrapper;window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE=e;clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);wrapper.onpointerenter=ev=>{if(ev.pointerType!=="mouse"){return;}clearTooltipHoverTimeout(e);};wrapper.onpointerleave=ev=>{if(ev.pointerType!=="mouse"||!(ev.relatedTarget instanceof HTMLElement)){return;}if(!e.TOOLTIP_FORCE_VISIBLE&&!e.contains(ev.relatedTarget)){setTooltipHoverTimeout(e,false);addClass(wrapper,"fade-out");}};}function setTooltipHoverTimeout(element,show){clearTooltipHoverTimeout(element);if(!show&&!window.CURRENT_TOOLTIP_ELEMENT){return;}if(show&&window.CURRENT_TOOLTIP_ELEMENT){return;}if(window.CURRENT_TOOLTIP_ELEMENT&&window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE!==element){return;}element.TOOLTIP_HOVER_TIMEOUT=setTimeout(()=>{if(show){showTooltip(element);}else if(!element.TOOLTIP_FORCE_VISIBLE){hideTooltip(false);}},show?window.RUSTDOC_TOOLTIP_HOVER_MS:window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS);}function clearTooltipHoverTimeout(element){if(element.TOOLTIP_HOVER_TIMEOUT!==undefined){removeClass(window.CURRENT_TOOLTIP_ELEMENT,"fade-out");clearTimeout(element.TOOLTIP_HOVER_TIMEOUT);delete element.TOOLTIP_HOVER_TIMEOUT;}}function tooltipBlurHandler(event){if(window.CURRENT_TOOLTIP_ELEMENT&&!window.CURRENT_TOOLTIP_ELEMENT.contains(document.activeElement)&&!window.CURRENT_TOOLTIP_ELEMENT.contains(event.relatedTarget)&&!window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(document.activeElement)&&!window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(event.relatedTarget)){setTimeout(()=>hideTooltip(false),0);}}function hideTooltip(focus){if(window.CURRENT_TOOLTIP_ELEMENT){if(window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE){if(focus){window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.focus();}window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE=false;}document.body.removeChild(window.CURRENT_TOOLTIP_ELEMENT);clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);window.CURRENT_TOOLTIP_ELEMENT=null;}}onEachLazy(document.getElementsByClassName("tooltip"),e=>{e.onclick=()=>{e.TOOLTIP_FORCE_VISIBLE=e.TOOLTIP_FORCE_VISIBLE?false:true;if(window.CURRENT_TOOLTIP_ELEMENT&&!e.TOOLTIP_FORCE_VISIBLE){hideTooltip(true);}else{showTooltip(e);window.CURRENT_TOOLTIP_ELEMENT.setAttribute("tabindex","0");window.CURRENT_TOOLTIP_ELEMENT.focus();window.CURRENT_TOOLTIP_ELEMENT.onblur=tooltipBlurHandler;}return false;};e.onpointerenter=ev=>{if(ev.pointerType!=="mouse"){return;}setTooltipHoverTimeout(e,true);};e.onpointermove=ev=>{if(ev.pointerType!=="mouse"){return;}setTooltipHoverTimeout(e,true);};e.onpointerleave=ev=>{if(ev.pointerType!=="mouse"){return;}if(!e.TOOLTIP_FORCE_VISIBLE&&window.CURRENT_TOOLTIP_ELEMENT&&!window.CURRENT_TOOLTIP_ELEMENT.contains(ev.relatedTarget)){setTooltipHoverTimeout(e,false);addClass(window.CURRENT_TOOLTIP_ELEMENT,"fade-out");}};});const sidebar_menu_toggle=document.getElementsByClassName("sidebar-menu-toggle")[0];if(sidebar_menu_toggle){sidebar_menu_toggle.addEventListener("click",()=>{const sidebar=document.getElementsByClassName("sidebar")[0];if(!hasClass(sidebar,"shown")){showSidebar();}else{hideSidebar();}});}function helpBlurHandler(event){if(!getHelpButton().contains(document.activeElement)&&!getHelpButton().contains(event.relatedTarget)&&!getSettingsButton().contains(document.activeElement)&&!getSettingsButton().contains(event.relatedTarget)){window.hidePopoverMenus();}}function buildHelpMenu(){const book_info=document.createElement("span");const drloChannel=`https://doc.rust-lang.org/${getVar("channel")}`;book_info.className="top";book_info.innerHTML=`You can find more information in \ +the rustdoc book.`;const shortcuts=[["?","Show this help dialog"],["S / /","Focus the search field"],["↑","Move up in search results"],["↓","Move down in search results"],["← / →","Switch result tab (when results focused)"],["⏎","Go to active search result"],["+","Expand all sections"],["-","Collapse all sections"],["_","Collapse all sections, including impl blocks"],].map(x=>"
"+x[0].split(" ").map((y,index)=>((index&1)===0?""+y+"":" "+y+" ")).join("")+"
"+x[1]+"
").join("");const div_shortcuts=document.createElement("div");addClass(div_shortcuts,"shortcuts");div_shortcuts.innerHTML="

Keyboard Shortcuts

"+shortcuts+"
";const infos=[`For a full list of all search features, take a look \ + here.`,"Prefix searches with a type followed by a colon (e.g., fn:) to \ + restrict the search to a given item kind.","Accepted kinds are: fn, mod, struct, \ + enum, trait, type, macro, \ + and const.","Search functions by type signature (e.g., vec -> usize or \ + -> vec or String, enum:Cow -> bool)","You can look for items with an exact name by putting double quotes around \ + your request: \"string\"",`Look for functions that accept or return \ + slices and \ + arrays by writing square \ + brackets (e.g., -> [u8] or [] -> Option)`,"Look for items inside another one by searching for a path: vec::Vec",].map(x=>"

"+x+"

").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="

Search Tricks

"+infos;const rustdoc_version=document.createElement("span");rustdoc_version.className="bottom";const rustdoc_version_code=document.createElement("code");rustdoc_version_code.innerText="rustdoc "+getVar("rustdoc-version");rustdoc_version.appendChild(rustdoc_version_code);const container=document.createElement("div");if(!isHelpPage){container.className="popover";}container.id="help";container.style.display="none";const side_by_side=document.createElement("div");side_by_side.className="side-by-side";side_by_side.appendChild(div_shortcuts);side_by_side.appendChild(div_infos);container.appendChild(book_info);container.appendChild(side_by_side);container.appendChild(rustdoc_version);if(isHelpPage){const help_section=document.createElement("section");help_section.appendChild(container);document.getElementById("main-content").appendChild(help_section);container.style.display="block";}else{const help_button=getHelpButton();help_button.appendChild(container);container.onblur=helpBlurHandler;help_button.onblur=helpBlurHandler;help_button.children[0].onblur=helpBlurHandler;}return container;}window.hideAllModals=switchFocus=>{hideSidebar();window.hidePopoverMenus();hideTooltip(switchFocus);};window.hidePopoverMenus=()=>{onEachLazy(document.querySelectorAll("rustdoc-toolbar .popover"),elem=>{elem.style.display="none";});const button=getHelpButton();if(button){removeClass(button,"help-open");}};function getHelpMenu(buildNeeded){let menu=getHelpButton().querySelector(".popover");if(!menu&&buildNeeded){menu=buildHelpMenu();}return menu;}function showHelp(){const button=getHelpButton();addClass(button,"help-open");button.querySelector("a").focus();const menu=getHelpMenu(true);if(menu.style.display==="none"){window.hideAllModals();menu.style.display="";}}const helpLink=document.querySelector(`#${HELP_BUTTON_ID} > a`);if(isHelpPage){buildHelpMenu();}else if(helpLink){helpLink.addEventListener("click",event=>{if(!helpLink.contains(helpLink)||event.ctrlKey||event.altKey||event.metaKey){return;}event.preventDefault();const menu=getHelpMenu(true);const shouldShowHelp=menu.style.display==="none";if(shouldShowHelp){showHelp();}else{window.hidePopoverMenus();}});}setMobileTopbar();addSidebarItems();addSidebarCrates();onHashChange(null);window.addEventListener("hashchange",onHashChange);window.searchState.setup();}());(function(){const SIDEBAR_MIN=100;const SIDEBAR_MAX=500;const RUSTDOC_MOBILE_BREAKPOINT=700;const BODY_MIN=400;const SIDEBAR_VANISH_THRESHOLD=SIDEBAR_MIN/2;const sidebarButton=document.getElementById("sidebar-button");if(sidebarButton){sidebarButton.addEventListener("click",e=>{removeClass(document.documentElement,"hide-sidebar");updateLocalStorage("hide-sidebar","false");if(document.querySelector(".rustdoc.src")){window.rustdocToggleSrcSidebar();}e.preventDefault();});}let currentPointerId=null;let desiredSidebarSize=null;let pendingSidebarResizingFrame=false;const resizer=document.querySelector(".sidebar-resizer");const sidebar=document.querySelector(".sidebar");if(!resizer||!sidebar){return;}const isSrcPage=hasClass(document.body,"src");const hideSidebar=function(){if(isSrcPage){window.rustdocCloseSourceSidebar();updateLocalStorage("src-sidebar-width",null);document.documentElement.style.removeProperty("--src-sidebar-width");sidebar.style.removeProperty("--src-sidebar-width");resizer.style.removeProperty("--src-sidebar-width");}else{addClass(document.documentElement,"hide-sidebar");updateLocalStorage("hide-sidebar","true");updateLocalStorage("desktop-sidebar-width",null);document.documentElement.style.removeProperty("--desktop-sidebar-width");sidebar.style.removeProperty("--desktop-sidebar-width");resizer.style.removeProperty("--desktop-sidebar-width");}};const showSidebar=function(){if(isSrcPage){window.rustdocShowSourceSidebar();}else{removeClass(document.documentElement,"hide-sidebar");updateLocalStorage("hide-sidebar","false");}};const changeSidebarSize=function(size){if(isSrcPage){updateLocalStorage("src-sidebar-width",size.toString());sidebar.style.setProperty("--src-sidebar-width",size+"px");resizer.style.setProperty("--src-sidebar-width",size+"px");}else{updateLocalStorage("desktop-sidebar-width",size.toString());sidebar.style.setProperty("--desktop-sidebar-width",size+"px");resizer.style.setProperty("--desktop-sidebar-width",size+"px");}};const isSidebarHidden=function(){return isSrcPage?!hasClass(document.documentElement,"src-sidebar-expanded"):hasClass(document.documentElement,"hide-sidebar");};const resize=function(e){if(currentPointerId===null||currentPointerId!==e.pointerId){return;}e.preventDefault();const pos=e.clientX-3;if(pos=SIDEBAR_MIN){if(isSidebarHidden()){showSidebar();}const constrainedPos=Math.min(pos,window.innerWidth-BODY_MIN,SIDEBAR_MAX);changeSidebarSize(constrainedPos);desiredSidebarSize=constrainedPos;if(pendingSidebarResizingFrame!==false){clearTimeout(pendingSidebarResizingFrame);}pendingSidebarResizingFrame=setTimeout(()=>{if(currentPointerId===null||pendingSidebarResizingFrame===false){return;}pendingSidebarResizingFrame=false;document.documentElement.style.setProperty("--resizing-sidebar-width",desiredSidebarSize+"px",);},100);}};window.addEventListener("resize",()=>{if(window.innerWidth=(window.innerWidth-BODY_MIN)){changeSidebarSize(window.innerWidth-BODY_MIN);}else if(desiredSidebarSize!==null&&desiredSidebarSize>SIDEBAR_MIN){changeSidebarSize(desiredSidebarSize);}});const stopResize=function(e){if(currentPointerId===null){return;}if(e){e.preventDefault();}desiredSidebarSize=sidebar.getBoundingClientRect().width;removeClass(resizer,"active");window.removeEventListener("pointermove",resize,false);window.removeEventListener("pointerup",stopResize,false);removeClass(document.documentElement,"sidebar-resizing");document.documentElement.style.removeProperty("--resizing-sidebar-width");if(resizer.releasePointerCapture){resizer.releasePointerCapture(currentPointerId);currentPointerId=null;}};const initResize=function(e){if(currentPointerId!==null||e.altKey||e.ctrlKey||e.metaKey||e.button!==0){return;}if(resizer.setPointerCapture){resizer.setPointerCapture(e.pointerId);if(!resizer.hasPointerCapture(e.pointerId)){resizer.releasePointerCapture(e.pointerId);return;}currentPointerId=e.pointerId;}window.hideAllModals(false);e.preventDefault();window.addEventListener("pointermove",resize,false);window.addEventListener("pointercancel",stopResize,false);window.addEventListener("pointerup",stopResize,false);addClass(resizer,"active");addClass(document.documentElement,"sidebar-resizing");const pos=e.clientX-sidebar.offsetLeft-3;document.documentElement.style.setProperty("--resizing-sidebar-width",pos+"px");desiredSidebarSize=null;};resizer.addEventListener("pointerdown",initResize,false);}());(function(){function copyContentToClipboard(content){if(content===null){return;}const el=document.createElement("textarea");el.value=content;el.setAttribute("readonly","");el.style.position="absolute";el.style.left="-9999px";document.body.appendChild(el);el.select();document.execCommand("copy");document.body.removeChild(el);}function copyButtonAnimation(button){button.classList.add("clicked");if(button.reset_button_timeout!==undefined){clearTimeout(button.reset_button_timeout);}button.reset_button_timeout=setTimeout(()=>{button.reset_button_timeout=undefined;button.classList.remove("clicked");},1000);}const but=document.getElementById("copy-path");if(!but){return;}but.onclick=()=>{const titleElement=document.querySelector("title");const title=titleElement&&titleElement.textContent?titleElement.textContent.replace(" - Rust",""):"";const[item,module]=title.split(" in ");const path=[item];if(module!==undefined){path.unshift(module);}copyContentToClipboard(path.join("::"));copyButtonAnimation(but);};function copyCode(codeElem){if(!codeElem){return;}copyContentToClipboard(codeElem.textContent);}function getExampleWrap(event){const target=event.target;if(target instanceof HTMLElement){let elem=target;while(elem!==null&&!hasClass(elem,"example-wrap")){if(elem===document.body||elem.tagName==="A"||elem.tagName==="BUTTON"||hasClass(elem,"docblock")){return null;}elem=elem.parentElement;}return elem;}else{return null;}}function addCopyButton(event){const elem=getExampleWrap(event);if(elem===null){return;}elem.removeEventListener("mouseover",addCopyButton);const parent=document.createElement("div");parent.className="button-holder";const runButton=elem.querySelector(".test-arrow");if(runButton!==null){parent.appendChild(runButton);}elem.appendChild(parent);const copyButton=document.createElement("button");copyButton.className="copy-button";copyButton.title="Copy code to clipboard";copyButton.addEventListener("click",()=>{copyCode(elem.querySelector("pre > code"));copyButtonAnimation(copyButton);});parent.appendChild(copyButton);if(!elem.parentElement||!elem.parentElement.classList.contains("scraped-example")||!window.updateScrapedExample){return;}const scrapedWrapped=elem.parentElement;window.updateScrapedExample(scrapedWrapped,parent);}function showHideCodeExampleButtons(event){const elem=getExampleWrap(event);if(elem===null){return;}let buttons=elem.querySelector(".button-holder");if(buttons===null){addCopyButton(event);buttons=elem.querySelector(".button-holder");if(buttons===null){return;}}buttons.classList.toggle("keep-visible");}onEachLazy(document.querySelectorAll(".docblock .example-wrap"),elem=>{elem.addEventListener("mouseover",addCopyButton);elem.addEventListener("click",showHideCodeExampleButtons);});}());(function(){document.body.addEventListener("copy",event=>{let target=nonnull(event.target);let isInsideCode=false;while(target&&target!==document.body){if(target.tagName==="CODE"){isInsideCode=true;break;}target=target.parentElement;}if(!isInsideCode){return;}const selection=document.getSelection();nonnull(event.clipboardData).setData("text/plain",selection.toString());event.preventDefault();});}()); \ No newline at end of file diff --git a/static.files/normalize-9960930a.css b/static.files/normalize-9960930a.css new file mode 100644 index 0000000000..469959f137 --- /dev/null +++ b/static.files/normalize-9960930a.css @@ -0,0 +1,2 @@ + /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ +html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:0.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type="button"],[type="reset"],[type="submit"],button{-webkit-appearance:button}[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none} \ No newline at end of file diff --git a/static.files/noscript-32bb7600.css b/static.files/noscript-32bb7600.css new file mode 100644 index 0000000000..c228ec49bd --- /dev/null +++ b/static.files/noscript-32bb7600.css @@ -0,0 +1 @@ + #main-content .attributes{margin-left:0 !important;}#copy-path,#sidebar-button,.sidebar-resizer{display:none !important;}nav.sub{display:none;}.src .sidebar{display:none;}.notable-traits{display:none;}:root,:root:not([data-theme]){--main-background-color:white;--main-color:black;--settings-input-color:#2196f3;--settings-input-border-color:#717171;--settings-button-color:#000;--settings-button-border-focus:#717171;--sidebar-background-color:#f5f5f5;--sidebar-background-color-hover:#e0e0e0;--sidebar-border-color:#ddd;--code-block-background-color:#f5f5f5;--scrollbar-track-background-color:#dcdcdc;--scrollbar-thumb-background-color:rgba(36,37,39,0.6);--scrollbar-color:rgba(36,37,39,0.6) #d9d9d9;--headings-border-bottom-color:#ddd;--border-color:#e0e0e0;--button-background-color:#fff;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--toggle-filter:none;--mobile-sidebar-menu-filter:none;--search-input-focused-border-color:#66afe9;--copy-path-button-color:#999;--copy-path-img-filter:invert(50%);--copy-path-img-hover-filter:invert(35%);--code-example-button-color:#7f7f7f;--code-example-button-hover-color:#595959;--settings-menu-filter:invert(50%);--settings-menu-hover-filter:invert(35%);--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--warning-border-color:#ff8e00;--type-link-color:#ad378a;--trait-link-color:#6e4fc9;--assoc-item-link-color:#3873ad;--function-link-color:#ad7c37;--macro-link-color:#068000;--keyword-link-color:#3873ad;--mod-link-color:#3873ad;--link-color:#3873ad;--sidebar-link-color:#356da4;--sidebar-current-link-background-color:#fff;--search-result-link-focus-background-color:#ccc;--search-result-border-color:#aaa3;--search-color:#000;--search-error-code-background-color:#d0cccc;--search-results-alias-color:#000;--search-results-grey-color:#999;--search-tab-title-count-color:#888;--search-tab-button-not-selected-border-top-color:#e6e6e6;--search-tab-button-not-selected-background:#e6e6e6;--search-tab-button-selected-border-top-color:#0089ff;--search-tab-button-selected-background:#fff;--stab-background-color:#fff5d6;--stab-code-color:#000;--code-highlight-kw-color:#8959a8;--code-highlight-kw-2-color:#4271ae;--code-highlight-lifetime-color:#b76514;--code-highlight-prelude-color:#4271ae;--code-highlight-prelude-val-color:#c82829;--code-highlight-number-color:#718c00;--code-highlight-string-color:#718c00;--code-highlight-literal-color:#c82829;--code-highlight-attribute-color:#c82829;--code-highlight-self-color:#c82829;--code-highlight-macro-color:#3e999f;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#8e908c;--code-highlight-doc-comment-color:#4d4d4c;--src-line-numbers-span-color:#c67e2d;--src-line-number-highlighted-background-color:#fdffd3;--target-background-color:#fdffd3;--target-border-color:#ad7c37;--kbd-color:#000;--kbd-background:#fafbfc;--kbd-box-shadow-color:#c6cbd1;--rust-logo-filter:initial;--crate-search-div-filter:invert(100%) sepia(0%) saturate(4223%) hue-rotate(289deg) brightness(114%) contrast(76%);--crate-search-div-hover-filter:invert(44%) sepia(18%) saturate(23%) hue-rotate(317deg) brightness(96%) contrast(93%);--crate-search-hover-border:#717171;--src-sidebar-background-selected:#fff;--src-sidebar-background-hover:#e0e0e0;--table-alt-row-background-color:#f5f5f5;--codeblock-link-background:#eee;--scrape-example-toggle-line-background:#ccc;--scrape-example-toggle-line-hover-background:#999;--scrape-example-code-line-highlight:#fcffd6;--scrape-example-code-line-highlight-focus:#f6fdb0;--scrape-example-help-border-color:#555;--scrape-example-help-color:#333;--scrape-example-help-hover-border-color:#000;--scrape-example-help-hover-color:#000;--scrape-example-code-wrapper-background-start:rgba(255,255,255,1);--scrape-example-code-wrapper-background-end:rgba(255,255,255,0);--sidebar-resizer-hover:hsl(207,90%,66%);--sidebar-resizer-active:hsl(207,90%,54%);}@media (prefers-color-scheme:dark){:root,:root:not([data-theme]){--main-background-color:#353535;--main-color:#ddd;--settings-input-color:#2196f3;--settings-input-border-color:#999;--settings-button-color:#000;--settings-button-border-focus:#ffb900;--sidebar-background-color:#505050;--sidebar-background-color-hover:#676767;--sidebar-border-color:#2A2A2A;--code-block-background-color:#2A2A2A;--scrollbar-track-background-color:#717171;--scrollbar-thumb-background-color:rgba(32,34,37,.6);--scrollbar-color:rgba(32,34,37,.6) #5a5a5a;--headings-border-bottom-color:#d2d2d2;--border-color:#e0e0e0;--button-background-color:#f0f0f0;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--toggle-filter:invert(100%);--mobile-sidebar-menu-filter:invert(100%);--search-input-focused-border-color:#008dfd;--copy-path-button-color:#999;--copy-path-img-filter:invert(50%);--copy-path-img-hover-filter:invert(65%);--code-example-button-color:#7f7f7f;--code-example-button-hover-color:#a5a5a5;--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--warning-border-color:#ff8e00;--type-link-color:#2dbfb8;--trait-link-color:#b78cf2;--assoc-item-link-color:#d2991d;--function-link-color:#2bab63;--macro-link-color:#09bd00;--keyword-link-color:#d2991d;--mod-link-color:#d2991d;--link-color:#d2991d;--sidebar-link-color:#fdbf35;--sidebar-current-link-background-color:#444;--search-result-link-focus-background-color:#616161;--search-result-border-color:#aaa3;--search-color:#111;--search-error-code-background-color:#484848;--search-results-alias-color:#fff;--search-results-grey-color:#ccc;--search-tab-title-count-color:#888;--search-tab-button-not-selected-border-top-color:#252525;--search-tab-button-not-selected-background:#252525;--search-tab-button-selected-border-top-color:#0089ff;--search-tab-button-selected-background:#353535;--settings-menu-filter:invert(50%);--settings-menu-hover-filter:invert(65%);--stab-background-color:#314559;--stab-code-color:#e6e1cf;--code-highlight-kw-color:#ab8ac1;--code-highlight-kw-2-color:#769acb;--code-highlight-lifetime-color:#d97f26;--code-highlight-prelude-color:#769acb;--code-highlight-prelude-val-color:#ee6868;--code-highlight-number-color:#83a300;--code-highlight-string-color:#83a300;--code-highlight-literal-color:#ee6868;--code-highlight-attribute-color:#ee6868;--code-highlight-self-color:#ee6868;--code-highlight-macro-color:#3e999f;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#8d8d8b;--code-highlight-doc-comment-color:#8ca375;--src-line-numbers-span-color:#3b91e2;--src-line-number-highlighted-background-color:#0a042f;--target-background-color:#494a3d;--target-border-color:#bb7410;--kbd-color:#000;--kbd-background:#fafbfc;--kbd-box-shadow-color:#c6cbd1;--rust-logo-filter:drop-shadow(1px 0 0px #fff) drop-shadow(0 1px 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 -1px 0 #fff);--crate-search-div-filter:invert(94%) sepia(0%) saturate(721%) hue-rotate(255deg) brightness(90%) contrast(90%);--crate-search-div-hover-filter:invert(69%) sepia(60%) saturate(6613%) hue-rotate(184deg) brightness(100%) contrast(91%);--crate-search-hover-border:#2196f3;--src-sidebar-background-selected:#333;--src-sidebar-background-hover:#444;--table-alt-row-background-color:#2a2a2a;--codeblock-link-background:#333;--scrape-example-toggle-line-background:#999;--scrape-example-toggle-line-hover-background:#c5c5c5;--scrape-example-code-line-highlight:#5b3b01;--scrape-example-code-line-highlight-focus:#7c4b0f;--scrape-example-help-border-color:#aaa;--scrape-example-help-color:#eee;--scrape-example-help-hover-border-color:#fff;--scrape-example-help-hover-color:#fff;--scrape-example-code-wrapper-background-start:rgba(53,53,53,1);--scrape-example-code-wrapper-background-end:rgba(53,53,53,0);--sidebar-resizer-hover:hsl(207,30%,54%);--sidebar-resizer-active:hsl(207,90%,54%);}} \ No newline at end of file diff --git a/static.files/rust-logo-9a9549ea.svg b/static.files/rust-logo-9a9549ea.svg new file mode 100644 index 0000000000..62424d8ffd --- /dev/null +++ b/static.files/rust-logo-9a9549ea.svg @@ -0,0 +1,61 @@ + + + diff --git a/static.files/rustdoc-aa0817cf.css b/static.files/rustdoc-aa0817cf.css new file mode 100644 index 0000000000..5fa709d10c --- /dev/null +++ b/static.files/rustdoc-aa0817cf.css @@ -0,0 +1,59 @@ + :root{--nav-sub-mobile-padding:8px;--search-typename-width:6.75rem;--desktop-sidebar-width:200px;--src-sidebar-width:300px;--desktop-sidebar-z-index:100;--sidebar-elems-left-padding:24px;--clipboard-image:url('data:image/svg+xml,\ +\ +\ +');--copy-path-height:34px;--copy-path-width:33px;--checkmark-image:url('data:image/svg+xml,\ +\ +');--button-left-margin:4px;--button-border-radius:2px;--toolbar-button-border-radius:6px;--code-block-border-radius:6px;--impl-items-indent:0.3em;--docblock-indent:24px;--font-family:"Source Serif 4",NanumBarunGothic,serif;--font-family-code:"Source Code Pro",monospace;--line-number-padding:4px;--line-number-right-margin:20px;--prev-arrow-image:url('data:image/svg+xml,');--next-arrow-image:url('data:image/svg+xml,');--expand-arrow-image:url('data:image/svg+xml,');--collapse-arrow-image:url('data:image/svg+xml,');--hamburger-image:url('data:image/svg+xml,\ + ');}:root.sans-serif{--font-family:"Fira Sans",sans-serif;--font-family-code:"Fira Mono",monospace;}@font-face {font-family:'Fira Sans';font-style:normal;font-weight:400;src:local('Fira Sans'),url("FiraSans-Regular-0fe48ade.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Fira Sans';font-style:italic;font-weight:400;src:local('Fira Sans Italic'),url("FiraSans-Italic-81dc35de.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Fira Sans';font-style:normal;font-weight:500;src:local('Fira Sans Medium'),url("FiraSans-Medium-e1aa3f0a.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Fira Sans';font-style:italic;font-weight:500;src:local('Fira Sans Medium Italic'),url("FiraSans-MediumItalic-ccf7e434.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Fira Mono';font-style:normal;font-weight:400;src:local('Fira Mono'),url("FiraMono-Regular-87c26294.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Fira Mono';font-style:normal;font-weight:500;src:local('Fira Mono Medium'),url("FiraMono-Medium-86f75c8c.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:normal;font-weight:400;src:local('Source Serif 4'),url("SourceSerif4-Regular-6b053e98.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:italic;font-weight:400;src:local('Source Serif 4 Italic'),url("SourceSerif4-It-ca3b17ed.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:normal;font-weight:500;src:local('Source Serif 4 Semibold'),url("SourceSerif4-Semibold-457a13ac.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:normal;font-weight:700;src:local('Source Serif 4 Bold'),url("SourceSerif4-Bold-6d4fd4c0.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Code Pro';font-style:normal;font-weight:400;src:url("SourceCodePro-Regular-8badfe75.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Code Pro';font-style:italic;font-weight:400;src:url("SourceCodePro-It-fc8b9304.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Code Pro';font-style:normal;font-weight:600;src:url("SourceCodePro-Semibold-aa29a496.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'NanumBarunGothic';src:url("NanumBarunGothic-13b3dcba.ttf.woff2") format("woff2");font-display:swap;unicode-range:U+AC00-D7AF,U+1100-11FF,U+3130-318F,U+A960-A97F,U+D7B0-D7FF;}*{box-sizing:border-box;}body{font:1rem/1.5 var(--font-family);margin:0;position:relative;overflow-wrap:break-word;overflow-wrap:anywhere;font-feature-settings:"kern","liga";background-color:var(--main-background-color);color:var(--main-color);}h1{font-size:1.5rem;}h2{font-size:1.375rem;}h3{font-size:1.25rem;}h1,h2,h3,h4,h5,h6{font-weight:500;}h1,h2,h3,h4{margin:25px 0 15px 0;padding-bottom:6px;}.docblock h3,.docblock h4,h5,h6{margin:15px 0 5px 0;}.docblock>h2:first-child,.docblock>h3:first-child,.docblock>h4:first-child,.docblock>h5:first-child,.docblock>h6:first-child{margin-top:0;}.main-heading h1{margin:0;padding:0;grid-area:main-heading-h1;overflow-wrap:break-word;overflow-wrap:anywhere;}.main-heading{position:relative;display:grid;grid-template-areas:"main-heading-breadcrumbs main-heading-breadcrumbs" "main-heading-h1 main-heading-toolbar" "main-heading-sub-heading main-heading-toolbar";grid-template-columns:minmax(105px,1fr) minmax(0,max-content);grid-template-rows:minmax(25px,min-content) min-content min-content;padding-bottom:6px;margin-bottom:15px;}.rustdoc-breadcrumbs{grid-area:main-heading-breadcrumbs;line-height:1.25;padding-top:5px;position:relative;z-index:1;}.rustdoc-breadcrumbs a{padding:5px 0 7px;}.content h2,.top-doc .docblock>h3,.top-doc .docblock>h4{border-bottom:1px solid var(--headings-border-bottom-color);}h1,h2{line-height:1.25;padding-top:3px;padding-bottom:9px;}h3.code-header{font-size:1.125rem;}h4.code-header{font-size:1rem;}.code-header{font-weight:600;margin:0;padding:0;white-space:pre-wrap;}.structfield,.sub-variant-field{margin:0.6em 0;}#crate-search,h1,h2,h3,h4,h5,h6,.sidebar,.mobile-topbar,.search-input,.search-results .result-name,.item-table dt>a,.out-of-band,.sub-heading,span.since,a.src,rustdoc-toolbar,summary.hideme,.scraped-example-list,.rustdoc-breadcrumbs,ul.all-items{font-family:"Fira Sans",Arial,NanumBarunGothic,sans-serif;}#toggle-all-docs,a.anchor,.section-header a,#src-sidebar a,.rust a,.sidebar h2 a,.sidebar h3 a,.mobile-topbar h2 a,h1 a,.search-results a,.search-results li,.stab,.result-name i{color:var(--main-color);}span.enum,a.enum,span.struct,a.struct,span.union,a.union,span.primitive,a.primitive,span.type,a.type,span.foreigntype,a.foreigntype{color:var(--type-link-color);}span.trait,a.trait,span.traitalias,a.traitalias{color:var(--trait-link-color);}span.associatedtype,a.associatedtype,span.constant,a.constant,span.static,a.static{color:var(--assoc-item-link-color);}span.fn,a.fn,span.method,a.method,span.tymethod,a.tymethod{color:var(--function-link-color);}span.attr,a.attr,span.derive,a.derive,span.macro,a.macro{color:var(--macro-link-color);}span.mod,a.mod{color:var(--mod-link-color);}span.keyword,a.keyword{color:var(--keyword-link-color);}a{color:var(--link-color);text-decoration:none;}ol,ul{padding-left:24px;}ul ul,ol ul,ul ol,ol ol{margin-bottom:.625em;}p,.docblock>.warning{margin:0 0 .75em 0;}p:last-child,.docblock>.warning:last-child{margin:0;}button{padding:1px 6px;cursor:pointer;}button#toggle-all-docs{padding:0;background:none;border:none;-webkit-appearance:none;opacity:1;}.rustdoc{display:flex;flex-direction:row;flex-wrap:nowrap;}main{position:relative;flex-grow:1;padding:10px 15px 40px 45px;min-width:0;}.src main{padding:15px;}.width-limiter{max-width:960px;margin-right:auto;}details:not(.toggle) summary{margin-bottom:.6em;}code,pre,.code-header,.type-signature{font-family:var(--font-family-code);}.docblock code,.item-table dd code{border-radius:3px;padding:0 0.125em;}.docblock pre code,.item-table dd pre code{padding:0;}pre{padding:14px;line-height:1.5;}pre.item-decl{overflow-x:auto;}.item-decl .type-contents-toggle{contain:initial;}.src .content pre{padding:20px;padding-left:16px;}img{max-width:100%;}.logo-container{line-height:0;display:block;}.rust-logo{filter:var(--rust-logo-filter);}.sidebar{font-size:0.875rem;flex:0 0 var(--desktop-sidebar-width);width:var(--desktop-sidebar-width);overflow-y:scroll;overscroll-behavior:contain;position:sticky;height:100vh;top:0;left:0;z-index:var(--desktop-sidebar-z-index);border-right:solid 1px var(--sidebar-border-color);}.rustdoc.src .sidebar{flex-basis:50px;width:50px;overflow-x:hidden;overflow-y:hidden;}.hide-sidebar .sidebar,.hide-sidebar .sidebar-resizer{display:none;}.sidebar-resizer{touch-action:none;width:9px;cursor:ew-resize;z-index:calc(var(--desktop-sidebar-z-index) + 1);position:fixed;height:100%;left:var(--desktop-sidebar-width);display:flex;align-items:center;justify-content:flex-start;color:var(--right-side-color);}.sidebar-resizer::before{content:"";border-right:dotted 2px currentColor;width:2px;height:12px;}.sidebar-resizer::after{content:"";border-right:dotted 2px currentColor;width:2px;height:16px;}.rustdoc.src .sidebar-resizer{left:49px;}.src-sidebar-expanded .src .sidebar-resizer{left:var(--src-sidebar-width);}.sidebar-resizing{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;}.sidebar-resizing *{cursor:ew-resize !important;}.sidebar-resizing .sidebar{position:fixed;border-right:solid 2px var(--sidebar-resizer-active);}.sidebar-resizing>body{padding-left:var(--resizing-sidebar-width);}.sidebar-resizer:hover,.sidebar-resizer:active,.sidebar-resizer:focus,.sidebar-resizer.active{width:10px;margin:0;left:calc(var(--desktop-sidebar-width) - 1px);border-left:solid 1px var(--sidebar-resizer-hover);color:var(--sidebar-resizer-hover);}.src-sidebar-expanded .rustdoc.src .sidebar-resizer:hover,.src-sidebar-expanded .rustdoc.src .sidebar-resizer:active,.src-sidebar-expanded .rustdoc.src .sidebar-resizer:focus,.src-sidebar-expanded .rustdoc.src .sidebar-resizer.active{left:calc(var(--src-sidebar-width) - 1px);}@media (pointer:coarse){.sidebar-resizer{display:none !important;}.sidebar{border-right:none;}}.sidebar-resizer.active{padding:0 140px;width:calc(140px + 140px + 9px + 2px);margin-left:-140px;border-left:none;color:var(--sidebar-resizer-active);}.sidebar,.mobile-topbar,.sidebar-menu-toggle,#src-sidebar{background-color:var(--sidebar-background-color);}.src .sidebar>*{visibility:hidden;}.src-sidebar-expanded .src .sidebar{overflow-y:auto;flex-basis:var(--src-sidebar-width);width:var(--src-sidebar-width);}.src-sidebar-expanded .src .sidebar>*{visibility:visible;}#all-types{margin-top:1em;}*{scrollbar-width:initial;scrollbar-color:var(--scrollbar-color);}.sidebar{scrollbar-width:thin;scrollbar-color:var(--scrollbar-color);}::-webkit-scrollbar{width:12px;}.sidebar::-webkit-scrollbar{width:8px;}::-webkit-scrollbar-track{-webkit-box-shadow:inset 0;background-color:var(--scrollbar-track-background-color);}.sidebar::-webkit-scrollbar-track{background-color:var(--scrollbar-track-background-color);}::-webkit-scrollbar-thumb,.sidebar::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-background-color);}.hidden{display:none !important;}.logo-container>img{height:48px;width:48px;}ul.block,.block li,.block ul{padding:0;margin:0;list-style:none;}.block ul a{padding-left:1rem;}.sidebar-elems a,.sidebar>h2 a{display:block;padding:0.25rem;margin-right:0.25rem;border-left:solid var(--sidebar-elems-left-padding) transparent;margin-left:calc(-0.25rem - var(--sidebar-elems-left-padding));background-clip:border-box;}.hide-toc #rustdoc-toc,.hide-toc .in-crate{display:none;}.hide-modnav #rustdoc-modnav{display:none;}.sidebar h2{text-wrap:balance;overflow-wrap:anywhere;padding:0;margin:0.7rem 0;}.sidebar h3{text-wrap:balance;overflow-wrap:anywhere;font-size:1.125rem;padding:0;margin:0;}.sidebar-elems,.sidebar>.version,.sidebar>h2{padding-left:var(--sidebar-elems-left-padding);}.sidebar a{color:var(--sidebar-link-color);}.sidebar .current,.sidebar .current a,.sidebar-crate a.logo-container:hover+h2 a,.sidebar a:hover:not(.logo-container){background-color:var(--sidebar-current-link-background-color);}.sidebar-elems .block{margin-bottom:2em;}.sidebar-elems .block li a{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;}.sidebar-crate{display:flex;align-items:center;justify-content:center;margin:14px 32px 1rem;row-gap:10px;column-gap:32px;flex-wrap:wrap;}.sidebar-crate h2{flex-grow:1;margin:0 -8px;align-self:start;}.sidebar-crate .logo-container{margin:0 calc(-16px - var(--sidebar-elems-left-padding));padding:0 var(--sidebar-elems-left-padding);text-align:center;}.sidebar-crate .logo-container img{margin-top:-16px;border-top:solid 16px transparent;box-sizing:content-box;position:relative;background-clip:border-box;z-index:1;}.sidebar-crate h2 a{display:block;border-left:solid var(--sidebar-elems-left-padding) transparent;background-clip:border-box;margin:0 calc(-24px + 0.25rem) 0 calc(-0.2rem - var(--sidebar-elems-left-padding));padding:calc((16px - 0.57rem ) / 2 ) 0.25rem;padding-left:0.2rem;}.sidebar-crate h2 .version{display:block;font-weight:normal;font-size:1rem;overflow-wrap:break-word;}.sidebar-crate+.version{margin-top:-1rem;margin-bottom:1rem;}.mobile-topbar{display:none;}.rustdoc .example-wrap{display:flex;position:relative;margin-bottom:10px;}.rustdoc .example-wrap>pre,.rustdoc .scraped-example .src-line-numbers,.rustdoc .scraped-example .src-line-numbers>pre{border-radius:6px;}.rustdoc .scraped-example{position:relative;}.rustdoc .example-wrap:last-child{margin-bottom:0px;}.rustdoc .example-wrap pre{margin:0;flex-grow:1;}.scraped-example:not(.expanded) .example-wrap{max-height:calc(1.5em * 5 + 10px);}.more-scraped-examples .scraped-example:not(.expanded) .example-wrap{max-height:calc(1.5em * 10 + 10px);}.rustdoc:not(.src) .scraped-example:not(.expanded) .src-line-numbers,.rustdoc:not(.src) .scraped-example:not(.expanded) .src-line-numbers>pre,.rustdoc:not(.src) .scraped-example:not(.expanded) pre.rust{padding-bottom:0;overflow:auto hidden;}.rustdoc:not(.src) .scraped-example .src-line-numbers{padding-top:0;}.rustdoc:not(.src) .scraped-example.expanded .src-line-numbers{padding-bottom:0;}.rustdoc:not(.src) .example-wrap pre{overflow:auto;}.example-wrap code{position:relative;}.example-wrap pre code span{display:inline;}.example-wrap.digits-1{--example-wrap-digits-count:1ch;}.example-wrap.digits-2{--example-wrap-digits-count:2ch;}.example-wrap.digits-3{--example-wrap-digits-count:3ch;}.example-wrap.digits-4{--example-wrap-digits-count:4ch;}.example-wrap.digits-5{--example-wrap-digits-count:5ch;}.example-wrap.digits-6{--example-wrap-digits-count:6ch;}.example-wrap.digits-7{--example-wrap-digits-count:7ch;}.example-wrap.digits-8{--example-wrap-digits-count:8ch;}.example-wrap.digits-9{--example-wrap-digits-count:9ch;}.example-wrap [data-nosnippet]{width:calc(var(--example-wrap-digits-count) + var(--line-number-padding) * 2);}.example-wrap pre>code{padding-left:calc(var(--example-wrap-digits-count) + var(--line-number-padding) * 2 + var(--line-number-right-margin));}.example-wrap [data-nosnippet]{color:var(--src-line-numbers-span-color);text-align:right;display:inline-block;margin-right:var(--line-number-right-margin);-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;padding:0 var(--line-number-padding);position:absolute;left:0;}.example-wrap .line-highlighted[data-nosnippet]{background-color:var(--src-line-number-highlighted-background-color);}.example-wrap pre>code{position:relative;display:block;}:root.word-wrap-source-code .example-wrap pre>code{word-break:break-all;white-space:pre-wrap;}:root.word-wrap-source-code .example-wrap pre>code *{word-break:break-all;}.example-wrap [data-nosnippet]:target{border-right:none;}.example-wrap.hide-lines [data-nosnippet]{display:none;}.search-loading{text-align:center;}.item-table dd{overflow-wrap:break-word;overflow-wrap:anywhere;}.docblock :not(pre)>code,.item-table dd code{white-space:pre-wrap;}.top-doc .docblock h2{font-size:1.375rem;}.top-doc .docblock h3{font-size:1.25rem;}.top-doc .docblock h4,.top-doc .docblock h5{font-size:1.125rem;}.top-doc .docblock h6{font-size:1rem;}.docblock h5{font-size:1rem;}.docblock h6{font-size:0.875rem;}.docblock{margin-left:var(--docblock-indent);position:relative;}.docblock>:not(.more-examples-toggle):not(.example-wrap){max-width:100%;overflow-x:auto;}.sub-heading{font-size:1rem;flex-grow:0;grid-area:main-heading-sub-heading;line-height:1.25;padding-bottom:4px;}.main-heading rustdoc-toolbar,.main-heading .out-of-band{grid-area:main-heading-toolbar;}rustdoc-toolbar{display:flex;flex-direction:row;flex-wrap:nowrap;min-height:60px;}.docblock code,.item-table dd code,pre,.rustdoc.src .example-wrap,.example-wrap .src-line-numbers{background-color:var(--code-block-background-color);border-radius:var(--code-block-border-radius);text-decoration:inherit;}#main-content{position:relative;}.docblock table{margin:.5em 0;border-collapse:collapse;}.docblock table td,.docblock table th{padding:.5em;border:1px solid var(--border-color);}.docblock table tbody tr:nth-child(2n){background:var(--table-alt-row-background-color);}.docblock .stab,.item-table dd .stab,.docblock p code{display:inline-block;}.docblock li{margin-bottom:.4em;}.docblock li p:not(:last-child){margin-bottom:.3em;}div.where{white-space:pre-wrap;font-size:0.875rem;}.item-info{display:block;margin-left:var(--docblock-indent);}.impl-items>.item-info{margin-left:calc(var(--docblock-indent) + var(--impl-items-indent));}.item-info code{font-size:0.875rem;}#main-content>.item-info{margin-left:0;}nav.sub{flex-grow:1;flex-flow:row nowrap;margin:4px 0 0 0;display:flex;align-items:center;}.search-form{position:relative;display:flex;height:34px;flex-grow:1;margin-bottom:4px;}.src nav.sub{margin:0 0 -10px 0;}.section-header{display:block;position:relative;}.section-header:hover>.anchor,.impl:hover>.anchor,.trait-impl:hover>.anchor,.variant:hover>.anchor{display:initial;}.anchor{display:none;position:absolute;left:-0.5em;background:none !important;}.anchor.field{left:-5px;}.section-header>.anchor{left:-15px;padding-right:8px;}h2.section-header>.anchor{padding-right:6px;}a.doc-anchor{color:var(--main-color);display:none;position:absolute;left:-17px;padding-right:10px;padding-left:3px;}*:hover>.doc-anchor{display:block;}.top-doc>.docblock>*:first-child>.doc-anchor{display:none !important;}.main-heading a:hover,.example-wrap .rust a:hover:not([data-nosnippet]),.all-items a:hover,.docblock a:not(.scrape-help):not(.tooltip):hover:not(.doc-anchor),.item-table dd a:not(.scrape-help):not(.tooltip):hover,.item-info a{text-decoration:underline;}.crate.block li.current a{font-weight:500;}table,.item-table{overflow-wrap:break-word;}.item-table{padding:0;margin:0;width:100%;}.item-table>dt{padding-right:1.25rem;}.item-table>dd{margin-inline-start:0;margin-left:0;}.search-results-title{margin-top:0;white-space:nowrap;display:flex;align-items:baseline;}.search-results-title+.sub-heading{color:var(--main-color);display:flex;align-items:baseline;white-space:nowrap;}#crate-search-div{position:relative;min-width:0;}#crate-search{padding:0 23px 0 4px;max-width:100%;text-overflow:ellipsis;border:1px solid var(--border-color);border-radius:4px;outline:none;cursor:pointer;-moz-appearance:none;-webkit-appearance:none;text-indent:0.01px;background-color:var(--main-background-color);color:inherit;line-height:1.5;font-weight:500;}#crate-search:hover,#crate-search:focus{border-color:var(--crate-search-hover-border);}#crate-search-div::after{pointer-events:none;width:100%;height:100%;position:absolute;top:0;left:0;content:"";background-repeat:no-repeat;background-size:20px;background-position:calc(100% - 2px) 56%;background-image:url('data:image/svg+xml, \ + ');filter:var(--crate-search-div-filter);}#crate-search-div:hover::after,#crate-search-div:focus-within::after{filter:var(--crate-search-div-hover-filter);}#crate-search>option{font-size:1rem;}.search-input{-webkit-appearance:none;outline:none;border:1px solid var(--border-color);border-radius:2px;padding:8px;font-size:1rem;flex-grow:1;background-color:var(--button-background-color);color:var(--search-color);}.search-input:focus{border-color:var(--search-input-focused-border-color);}.search-results{display:none;}.search-results.active{display:block;margin:0;padding:0;}.search-results>a{display:grid;grid-template-areas:"search-result-name search-result-desc" "search-result-type-signature search-result-type-signature";grid-template-columns:.6fr .4fr;margin-left:2px;margin-right:2px;border-bottom:1px solid var(--search-result-border-color);column-gap:1em;}.search-results>a>div.desc{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;grid-area:search-result-desc;}.search-results a:hover,.search-results a:focus{background-color:var(--search-result-link-focus-background-color);}.search-results .result-name{display:flex;align-items:center;justify-content:start;grid-area:search-result-name;}.search-results .result-name .alias{color:var(--search-results-alias-color);}.search-results .result-name .grey{color:var(--search-results-grey-color);}.search-results .result-name .typename{color:var(--search-results-grey-color);font-size:0.875rem;width:var(--search-typename-width);}.search-results .result-name .path{word-break:break-all;max-width:calc(100% - var(--search-typename-width));display:inline-block;}.search-results .result-name .path>*{display:inline;}.search-results .type-signature{grid-area:search-result-type-signature;white-space:pre-wrap;}.popover{position:absolute;top:100%;right:0;z-index:calc(var(--desktop-sidebar-z-index) + 1);margin-top:7px;border-radius:3px;border:1px solid var(--border-color);background-color:var(--main-background-color);color:var(--main-color);--popover-arrow-offset:11px;}.popover::before{content:'';position:absolute;right:var(--popover-arrow-offset);border:solid var(--border-color);border-width:1px 1px 0 0;background-color:var(--main-background-color);padding:4px;transform:rotate(-45deg);top:-5px;}.setting-line{margin:1.2em 0.6em;}.setting-radio input,.setting-check input{margin-right:0.3em;height:1.2rem;width:1.2rem;border:2px solid var(--settings-input-border-color);outline:none;-webkit-appearance:none;cursor:pointer;}.setting-radio input{border-radius:50%;}.setting-radio span,.setting-check span{padding-bottom:1px;}.setting-radio{margin-top:0.1em;margin-bottom:0.1em;min-width:3.8em;padding:0.3em;display:inline-flex;align-items:center;cursor:pointer;}.setting-radio+.setting-radio{margin-left:0.5em;}.setting-check{margin-right:20px;display:flex;align-items:center;cursor:pointer;}.setting-check input{flex-shrink:0;}.setting-radio input:checked{box-shadow:inset 0 0 0 3px var(--main-background-color);background-color:var(--settings-input-color);}.setting-check input:checked{background-color:var(--settings-input-color);border-width:1px;content:url('data:image/svg+xml,\ + \ + ');}.setting-radio input:focus,.setting-check input:focus{box-shadow:0 0 1px 1px var(--settings-input-color);}.setting-radio input:checked:focus{box-shadow:inset 0 0 0 3px var(--main-background-color),0 0 2px 2px var(--settings-input-color);}.setting-radio input:hover,.setting-check input:hover{border-color:var(--settings-input-color) !important;}#settings.popover{--popover-arrow-offset:202px;top:calc(100% - 16px);}#help.popover{max-width:600px;--popover-arrow-offset:118px;top:calc(100% - 16px);}#help dt{float:left;clear:left;margin-right:0.5rem;}#help dd{margin-bottom:0.5rem;}#help span.top,#help span.bottom{text-align:center;display:block;font-size:1.125rem;padding:0 0.5rem;text-wrap-style:balance;}#help span.top{margin:10px 0;border-bottom:1px solid var(--border-color);padding-bottom:4px;margin-bottom:6px;}#help span.bottom{clear:both;border-top:1px solid var(--border-color);}.side-by-side{display:flex;margin-bottom:20px;}.side-by-side>div{width:50%;padding:0 20px 0 17px;}.item-info .stab{display:block;padding:3px;margin-bottom:5px;}.item-table dt .stab{margin-left:0.3125em;}.stab{padding:0 2px;font-size:0.875rem;font-weight:normal;color:var(--main-color);background-color:var(--stab-background-color);width:fit-content;white-space:pre-wrap;border-radius:3px;display:inline;vertical-align:baseline;}.stab.portability>code{background:none;color:var(--stab-code-color);}.stab .emoji,.item-info .stab::before{font-size:1.25rem;}.stab .emoji{margin-right:0.3rem;}.item-info .stab::before{content:"\0";width:0;display:inline-block;color:transparent;}.emoji{text-shadow:1px 0 0 black,-1px 0 0 black,0 1px 0 black,0 -1px 0 black;}.since{font-weight:normal;font-size:initial;}.rightside{padding-left:12px;float:right;}.rightside:not(a),.out-of-band,.sub-heading,rustdoc-toolbar{color:var(--right-side-color);}pre.rust{tab-size:4;-moz-tab-size:4;}pre.rust .kw{color:var(--code-highlight-kw-color);}pre.rust .kw-2{color:var(--code-highlight-kw-2-color);}pre.rust .lifetime{color:var(--code-highlight-lifetime-color);}pre.rust .prelude-ty{color:var(--code-highlight-prelude-color);}pre.rust .prelude-val{color:var(--code-highlight-prelude-val-color);}pre.rust .string{color:var(--code-highlight-string-color);}pre.rust .number{color:var(--code-highlight-number-color);}pre.rust .bool-val{color:var(--code-highlight-literal-color);}pre.rust .self{color:var(--code-highlight-self-color);}pre.rust .attr{color:var(--code-highlight-attribute-color);}pre.rust .macro,pre.rust .macro-nonterminal{color:var(--code-highlight-macro-color);}pre.rust .question-mark{font-weight:bold;color:var(--code-highlight-question-mark-color);}pre.rust .comment{color:var(--code-highlight-comment-color);}pre.rust .doccomment{color:var(--code-highlight-doc-comment-color);}.rustdoc.src .example-wrap pre.rust a:not([data-nosnippet]){background:var(--codeblock-link-background);}.example-wrap.compile_fail,.example-wrap.should_panic{border-left:2px solid var(--codeblock-error-color);}.ignore.example-wrap{border-left:2px solid var(--codeblock-ignore-color);}.example-wrap.compile_fail:hover,.example-wrap.should_panic:hover{border-left:2px solid var(--codeblock-error-hover-color);}.example-wrap.ignore:hover{border-left:2px solid var(--codeblock-ignore-hover-color);}.example-wrap.compile_fail .tooltip,.example-wrap.should_panic .tooltip{color:var(--codeblock-error-color);}.example-wrap.ignore .tooltip{color:var(--codeblock-ignore-color);}.example-wrap.compile_fail:hover .tooltip,.example-wrap.should_panic:hover .tooltip{color:var(--codeblock-error-hover-color);}.example-wrap.ignore:hover .tooltip{color:var(--codeblock-ignore-hover-color);}.example-wrap .tooltip{position:absolute;display:block;left:-25px;top:5px;margin:0;line-height:1;}.example-wrap.compile_fail .tooltip,.example-wrap.should_panic .tooltip,.example-wrap.ignore .tooltip{font-weight:bold;font-size:1.25rem;}.content .docblock .warning{border-left:2px solid var(--warning-border-color);padding:14px;position:relative;overflow-x:visible !important;}.content .docblock .warning::before{color:var(--warning-border-color);content:"ⓘ";position:absolute;left:-25px;top:5px;font-weight:bold;font-size:1.25rem;}.top-doc>.docblock>.warning:first-child::before{top:20px;}.example-wrap>a.test-arrow,.example-wrap .button-holder{visibility:hidden;position:absolute;top:4px;right:4px;z-index:1;}a.test-arrow{height:var(--copy-path-height);padding:6px 4px 0 11px;}a.test-arrow::before{content:url('data:image/svg+xml,');}.example-wrap .button-holder{display:flex;}@media not (pointer:coarse){.example-wrap:hover>a.test-arrow,.example-wrap:hover>.button-holder{visibility:visible;}}.example-wrap .button-holder.keep-visible{visibility:visible;}.example-wrap .button-holder>*{background:var(--main-background-color);cursor:pointer;border-radius:var(--button-border-radius);height:var(--copy-path-height);width:var(--copy-path-width);border:0;color:var(--code-example-button-color);}.example-wrap .button-holder>*:hover{color:var(--code-example-button-hover-color);}.example-wrap .button-holder>*:not(:first-child){margin-left:var(--button-left-margin);}.example-wrap .button-holder .copy-button{padding:2px 0 0 4px;}.example-wrap .button-holder .copy-button::before,.example-wrap .test-arrow::before,.example-wrap .button-holder .prev::before,.example-wrap .button-holder .next::before,.example-wrap .button-holder .expand::before{filter:var(--copy-path-img-filter);}.example-wrap .button-holder .copy-button::before{content:var(--clipboard-image);}.example-wrap .button-holder .copy-button:hover::before,.example-wrap .test-arrow:hover::before{filter:var(--copy-path-img-hover-filter);}.example-wrap .button-holder .copy-button.clicked::before{content:var(--checkmark-image);padding-right:5px;}.example-wrap .button-holder .prev,.example-wrap .button-holder .next,.example-wrap .button-holder .expand{line-height:0px;}.example-wrap .button-holder .prev::before{content:var(--prev-arrow-image);}.example-wrap .button-holder .next::before{content:var(--next-arrow-image);}.example-wrap .button-holder .expand::before{content:var(--expand-arrow-image);}.example-wrap .button-holder .expand.collapse::before{content:var(--collapse-arrow-image);}.code-attribute{font-weight:300;color:var(--code-attribute-color);}.item-spacer{width:100%;height:12px;display:block;}.main-heading span.since::before{content:"Since ";}.sub-variant h4{font-size:1rem;font-weight:400;margin-top:0;margin-bottom:0;}.sub-variant{margin-left:24px;margin-bottom:40px;}.sub-variant>.sub-variant-field{margin-left:24px;}@keyframes targetfadein{from{background-color:var(--main-background-color);}10%{background-color:var(--target-border-color);}to{background-color:var(--target-background-color);}}:target:not([data-nosnippet]){background-color:var(--target-background-color);border-right:3px solid var(--target-border-color);}.code-header a.tooltip{color:inherit;margin-right:15px;position:relative;}.code-header a.tooltip:hover{color:var(--link-color);}a.tooltip:hover::after{position:absolute;top:calc(100% - 10px);left:-15px;right:-15px;height:20px;content:"\00a0";}@media not (prefers-reduced-motion){:target{animation:0.65s cubic-bezier(0,0,0.1,1.0) 0.1s targetfadein;}.fade-out{opacity:0;transition:opacity 0.45s cubic-bezier(0,0,0.1,1.0);}}.popover.tooltip .content{margin:0.25em 0.5em;}.popover.tooltip .content pre,.popover.tooltip .content code{background:transparent;margin:0;padding:0;font-size:1.25rem;white-space:pre-wrap;}.popover.tooltip .content>h3:first-child{margin:0 0 5px 0;}.search-failed{text-align:center;margin-top:20px;display:none;}.search-failed.active{display:block;}.search-failed>ul{text-align:left;max-width:570px;margin-left:auto;margin-right:auto;}#search-tabs{margin-top:0.25rem;display:flex;flex-direction:row;gap:1px;margin-bottom:4px;}#search-tabs button{text-align:center;font-size:1.125rem;border:0;border-top:2px solid;flex:1;line-height:1.5;color:inherit;}#search-tabs button:not(.selected){background-color:var(--search-tab-button-not-selected-background);border-top-color:var(--search-tab-button-not-selected-border-top-color);}#search-tabs button:hover,#search-tabs button.selected{background-color:var(--search-tab-button-selected-background);border-top-color:var(--search-tab-button-selected-border-top-color);}#search-tabs .count{font-size:1rem;font-variant-numeric:tabular-nums;color:var(--search-tab-title-count-color);}#search .error code{border-radius:3px;background-color:var(--search-error-code-background-color);}.search-corrections{font-weight:normal;}#src-sidebar{width:100%;overflow:auto;}#src-sidebar div.files>a:hover,details.dir-entry summary:hover,#src-sidebar div.files>a:focus,details.dir-entry summary:focus{background-color:var(--src-sidebar-background-hover);}#src-sidebar div.files>a.selected{background-color:var(--src-sidebar-background-selected);}.src-sidebar-title{position:sticky;top:0;display:flex;padding:8px 8px 0 48px;margin-bottom:7px;background:var(--sidebar-background-color);border-bottom:1px solid var(--border-color);}#settings-menu,#help-button,button#toggle-all-docs{margin-left:var(--button-left-margin);display:flex;line-height:1.25;min-width:14px;}#sidebar-button{display:none;line-height:0;}.hide-sidebar #sidebar-button,.src #sidebar-button{display:flex;margin-right:4px;position:fixed;height:34px;width:34px;}.hide-sidebar #sidebar-button{left:6px;background-color:var(--main-background-color);z-index:1;}.src #sidebar-button{left:8px;z-index:calc(var(--desktop-sidebar-z-index) + 1);}.hide-sidebar .src #sidebar-button{position:static;}#settings-menu>a,#help-button>a,#sidebar-button>a,button#toggle-all-docs{display:flex;align-items:center;justify-content:center;flex-direction:column;}#settings-menu>a,#help-button>a,button#toggle-all-docs{border:1px solid transparent;border-radius:var(--button-border-radius);color:var(--main-color);}#settings-menu>a,#help-button>a,button#toggle-all-docs{width:80px;border-radius:var(--toolbar-button-border-radius);}#settings-menu>a,#help-button>a{min-width:0;}#sidebar-button>a{background-color:var(--sidebar-background-color);width:33px;}#sidebar-button>a:hover,#sidebar-button>a:focus-visible{background-color:var(--main-background-color);}#settings-menu>a:hover,#settings-menu>a:focus-visible,#help-button>a:hover,#help-button>a:focus-visible,button#toggle-all-docs:hover,button#toggle-all-docs:focus-visible{border-color:var(--settings-button-border-focus);text-decoration:none;}#settings-menu>a::before{content:url('data:image/svg+xml,\ + ');width:18px;height:18px;filter:var(--settings-menu-filter);}button#toggle-all-docs::before{content:url('data:image/svg+xml,\ + ');width:18px;height:18px;filter:var(--settings-menu-filter);}button#toggle-all-docs.will-expand::before{content:url('data:image/svg+xml,\ + ');}#help-button>a::before{content:url('data:image/svg+xml,\ + \ + ?');width:18px;height:18px;filter:var(--settings-menu-filter);}button#toggle-all-docs::before,#help-button>a::before,#settings-menu>a::before{filter:var(--settings-menu-filter);margin:8px;}@media not (pointer:coarse){button#toggle-all-docs:hover::before,#help-button>a:hover::before,#settings-menu>a:hover::before{filter:var(--settings-menu-hover-filter);}}button[disabled]#toggle-all-docs{opacity:0.25;border:solid 1px var(--main-background-color);background-size:cover;}button[disabled]#toggle-all-docs:hover{border:solid 1px var(--main-background-color);cursor:not-allowed;}rustdoc-toolbar span.label{font-size:1rem;flex-grow:1;padding-bottom:4px;}#sidebar-button>a::before{content:url('data:image/svg+xml,\ + \ + \ + ');width:22px;height:22px;}#copy-path{color:var(--copy-path-button-color);background:var(--main-background-color);height:var(--copy-path-height);width:var(--copy-path-width);margin-left:10px;padding:0;padding-left:2px;border:0;font-size:0;}#copy-path::before{filter:var(--copy-path-img-filter);content:var(--clipboard-image);}#copy-path:hover::before{filter:var(--copy-path-img-hover-filter);}#copy-path.clicked::before{content:var(--checkmark-image);}@keyframes rotating{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}#settings-menu.rotate>a img{animation:rotating 2s linear infinite;}kbd{display:inline-block;padding:3px 5px;font:15px monospace;line-height:10px;vertical-align:middle;border:solid 1px var(--border-color);border-radius:3px;color:var(--kbd-color);background-color:var(--kbd-background);box-shadow:inset 0 -1px 0 var(--kbd-box-shadow-color);}ul.all-items>li{list-style:none;}details.dir-entry{padding-left:4px;}details.dir-entry>summary{margin:0 0 0 -4px;padding:0 0 0 4px;cursor:pointer;}details.dir-entry div.folders,details.dir-entry div.files{padding-left:23px;}details.dir-entry a{display:block;}details.toggle{contain:layout;position:relative;}details.big-toggle{contain:inline-size;}details.toggle>summary.hideme{cursor:pointer;font-size:1rem;}details.toggle>summary{list-style:none;outline:none;}details.toggle>summary::-webkit-details-marker,details.toggle>summary::marker{display:none;}details.toggle>summary.hideme>span{margin-left:9px;}details.toggle>summary::before{background:url('data:image/svg+xml,\ + ');content:"";cursor:pointer;width:16px;height:16px;display:inline-block;vertical-align:middle;opacity:.5;filter:var(--toggle-filter);}details.toggle>summary.hideme>span,.more-examples-toggle summary,.more-examples-toggle .hide-more{color:var(--toggles-color);}details.toggle>summary::after{content:"Expand";overflow:hidden;width:0;height:0;position:absolute;}details.toggle>summary.hideme::after{content:"";}details.toggle>summary:focus::before,details.toggle>summary:hover::before{opacity:1;}details.toggle>summary:focus-visible::before{outline:1px dotted #000;outline-offset:1px;}details.non-exhaustive{margin-bottom:8px;}details.toggle>summary.hideme::before{position:relative;}details.toggle>summary:not(.hideme)::before{position:absolute;left:-24px;top:4px;}.impl-items>details.toggle>summary:not(.hideme)::before,#main-content>.methods>details.toggle>summary:not(.hideme)::before{position:absolute;left:-24px;}.impl-items>*:not(.item-info),.implementors-toggle>.docblock,#main-content>.methods>:not(.item-info),.impl>.item-info,.impl>.docblock,.impl+.docblock{margin-left:var(--impl-items-indent);}details.big-toggle>summary:not(.hideme)::before{left:-34px;top:9px;}details.toggle[open] >summary.hideme{position:absolute;}details.toggle[open] >summary.hideme>span{display:none;}details.toggle[open] >summary::before{background:url('data:image/svg+xml,\ + ');}details.toggle[open] >summary::after{content:"Collapse";}details.toggle:not([open])>summary .docblock{max-height:calc(1.5em + 0.75em);overflow-y:hidden;}details.toggle:not([open])>summary .docblock>:first-child{max-width:100%;overflow:hidden;width:fit-content;white-space:nowrap;position:relative;padding-right:1em;}details.toggle:not([open])>summary .docblock>:first-child::after{content:"…";position:absolute;right:0;top:0;bottom:0;z-index:1;background-color:var(--main-background-color);font:1rem/1.5 "Source Serif 4",NanumBarunGothic,serif;padding-left:0.2em;}details.toggle:not([open])>summary .docblock>div:first-child::after{padding-top:calc(1.5em + 0.75em - 1.2rem);}details.toggle>summary .docblock{margin-top:0.75em;}.docblock summary>*{display:inline-block;}.docblock>.example-wrap:first-child .tooltip{margin-top:16px;}.src #sidebar-button>a::before,.sidebar-menu-toggle::before{content:var(--hamburger-image);opacity:0.75;filter:var(--mobile-sidebar-menu-filter);}.sidebar-menu-toggle:hover::before,.sidebar-menu-toggle:active::before,.sidebar-menu-toggle:focus::before{opacity:1;}@media (max-width:850px){#search-tabs .count{display:block;}.side-by-side{flex-direction:column-reverse;}.side-by-side>div{width:auto;}}@media (max-width:700px){:root{--impl-items-indent:0.7em;}*[id]{scroll-margin-top:45px;}#copy-path{width:0;visibility:hidden;}rustdoc-toolbar span.label{display:none;}#settings-menu>a,#help-button>a,button#toggle-all-docs{width:33px;}#settings.popover{--popover-arrow-offset:86px;}#help.popover{--popover-arrow-offset:48px;}.rustdoc{display:block;}main{padding-left:15px;padding-top:0px;}.sidebar .logo-container,.sidebar .location,.sidebar-resizer{display:none;}.sidebar{position:fixed;top:45px;left:-1000px;z-index:11;height:calc(100vh - 45px);border-right:none;width:100%;}.sidebar-elems .block li a{white-space:wrap;}.src main,.rustdoc.src .sidebar{top:0;padding:0;height:100vh;border:0;}.src .search-form{margin-left:40px;}.src .main-heading{margin-left:8px;}.hide-sidebar .search-form{margin-left:32px;}.hide-sidebar .src .search-form{margin-left:0;}.sidebar.shown,.src-sidebar-expanded .src .sidebar,.rustdoc:not(.src) .sidebar:focus-within{left:0;}.mobile-topbar h2{padding-bottom:0;margin:auto 0.5em auto auto;overflow:hidden;font-size:24px;white-space:nowrap;text-overflow:ellipsis;}.mobile-topbar .logo-container>img{max-width:35px;max-height:35px;margin:5px 0 5px 20px;}.mobile-topbar{display:flex;flex-direction:row;position:sticky;z-index:10;font-size:2rem;height:45px;width:100%;left:0;top:0;}.hide-sidebar .mobile-topbar{display:none;}.sidebar-menu-toggle{width:45px;border:none;line-height:0;}.hide-sidebar .sidebar-menu-toggle{display:none;}.sidebar-elems{margin-top:1em;}.anchor{display:none !important;}#main-content>details.toggle>summary::before,#main-content>div>details.toggle>summary::before{left:-11px;}#sidebar-button>a::before{content:url('data:image/svg+xml,\ + \ + \ + ');width:22px;height:22px;}.sidebar-menu-toggle:hover{background:var(--main-background-color);}.search-results>a,.search-results>a>div{display:block;}.search-results>a{padding:5px 0px;}.search-results>a>div.desc,.item-table dd{padding-left:2em;}.search-results .result-name{display:block;}.search-results .result-name .typename{width:initial;margin-right:0;}.search-results .result-name .typename,.search-results .result-name .path{display:inline;}.src-sidebar-expanded .src .sidebar{position:fixed;max-width:100vw;width:100vw;}.src .src-sidebar-title{padding-top:0;}details.implementors-toggle:not(.top-doc)>summary{margin-left:10px;}.impl-items>details.toggle>summary:not(.hideme)::before,#main-content>.methods>details.toggle>summary:not(.hideme)::before{left:-20px;}summary>.item-info{margin-left:10px;}.impl-items>.item-info{margin-left:calc(var(--impl-items-indent) + 10px);}.src nav.sub{margin:0 0 -25px 0;padding:var(--nav-sub-mobile-padding);}html:not(.src-sidebar-expanded) .src #sidebar-button>a{background-color:var(--main-background-color);}html:not(.src-sidebar-expanded) .src #sidebar-button>a:hover,html:not(.src-sidebar-expanded) .src #sidebar-button>a:focus-visible{background-color:var(--sidebar-background-color);}}@media (min-width:701px){.scraped-example-title{position:absolute;z-index:10;background:var(--main-background-color);bottom:8px;right:5px;padding:2px 4px;box-shadow:0 0 4px var(--main-background-color);}.item-table:not(.reexports){display:grid;grid-template-columns:33% 67%;}.item-table>dt,.item-table>dd{overflow-wrap:anywhere;}.item-table>dt{grid-column-start:1;}.item-table>dd{grid-column-start:2;}}@media print{:root{--docblock-indent:0;}nav.sidebar,nav.sub,.out-of-band,a.src,#copy-path,details.toggle[open] >summary::before,details.toggle>summary::before,details.toggle.top-doc>summary{display:none;}main{padding:10px;}}@media (max-width:464px){:root{--docblock-indent:12px;}.docblock code{overflow-wrap:break-word;overflow-wrap:anywhere;}nav.sub{flex-direction:column;}.search-form{align-self:stretch;}}.variant,.implementors-toggle>summary,.impl,#implementors-list>.docblock,.impl-items>section,.impl-items>.toggle>summary,.methods>section,.methods>.toggle>summary{margin-bottom:0.75em;}.variants>.docblock,.implementors-toggle>.docblock,.impl-items>.toggle[open]:not(:last-child),.methods>.toggle[open]:not(:last-child),.implementors-toggle[open]:not(:last-child){margin-bottom:2em;}#trait-implementations-list .impl-items>.toggle:not(:last-child),#synthetic-implementations-list .impl-items>.toggle:not(:last-child),#blanket-implementations-list .impl-items>.toggle:not(:last-child){margin-bottom:1em;}.scraped-example-list .scrape-help{margin-left:10px;padding:0 4px;font-weight:normal;font-size:12px;position:relative;bottom:1px;border:1px solid var(--scrape-example-help-border-color);border-radius:50px;color:var(--scrape-example-help-color);}.scraped-example-list .scrape-help:hover{border-color:var(--scrape-example-help-hover-border-color);color:var(--scrape-example-help-hover-color);}.scraped-example:not(.expanded) .example-wrap::before,.scraped-example:not(.expanded) .example-wrap::after{content:" ";width:100%;height:5px;position:absolute;z-index:1;}.scraped-example:not(.expanded) .example-wrap::before{top:0;background:linear-gradient(to bottom,var(--scrape-example-code-wrapper-background-start),var(--scrape-example-code-wrapper-background-end));}.scraped-example:not(.expanded) .example-wrap::after{bottom:0;background:linear-gradient(to top,var(--scrape-example-code-wrapper-background-start),var(--scrape-example-code-wrapper-background-end));}.scraped-example:not(.expanded){width:100%;overflow-y:hidden;margin-bottom:0;}.scraped-example:not(.expanded){overflow-x:hidden;}.scraped-example .rust span.highlight{background:var(--scrape-example-code-line-highlight);}.scraped-example .rust span.highlight.focus{background:var(--scrape-example-code-line-highlight-focus);}.more-examples-toggle{max-width:calc(100% + 25px);margin-top:10px;margin-left:-25px;}.more-examples-toggle .hide-more{margin-left:25px;cursor:pointer;}.more-scraped-examples{margin-left:25px;position:relative;}.toggle-line{position:absolute;top:5px;bottom:0;right:calc(100% + 10px);padding:0 4px;cursor:pointer;}.toggle-line-inner{min-width:2px;height:100%;background:var(--scrape-example-toggle-line-background);}.toggle-line:hover .toggle-line-inner{background:var(--scrape-example-toggle-line-hover-background);}.more-scraped-examples .scraped-example,.example-links{margin-top:20px;}.more-scraped-examples .scraped-example:first-child{margin-top:5px;}.example-links ul{margin-bottom:0;}:root[data-theme="light"],:root:not([data-theme]){--main-background-color:white;--main-color:black;--settings-input-color:#2196f3;--settings-input-border-color:#717171;--settings-button-color:#000;--settings-button-border-focus:#717171;--sidebar-background-color:#f5f5f5;--sidebar-background-color-hover:#e0e0e0;--sidebar-border-color:#ddd;--code-block-background-color:#f5f5f5;--scrollbar-track-background-color:#dcdcdc;--scrollbar-thumb-background-color:rgba(36,37,39,0.6);--scrollbar-color:rgba(36,37,39,0.6) #d9d9d9;--headings-border-bottom-color:#ddd;--border-color:#e0e0e0;--button-background-color:#fff;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--toggle-filter:none;--mobile-sidebar-menu-filter:none;--search-input-focused-border-color:#66afe9;--copy-path-button-color:#999;--copy-path-img-filter:invert(50%);--copy-path-img-hover-filter:invert(35%);--code-example-button-color:#7f7f7f;--code-example-button-hover-color:#595959;--settings-menu-filter:invert(50%);--settings-menu-hover-filter:invert(35%);--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--warning-border-color:#ff8e00;--type-link-color:#ad378a;--trait-link-color:#6e4fc9;--assoc-item-link-color:#3873ad;--function-link-color:#ad7c37;--macro-link-color:#068000;--keyword-link-color:#3873ad;--mod-link-color:#3873ad;--link-color:#3873ad;--sidebar-link-color:#356da4;--sidebar-current-link-background-color:#fff;--search-result-link-focus-background-color:#ccc;--search-result-border-color:#aaa3;--search-color:#000;--search-error-code-background-color:#d0cccc;--search-results-alias-color:#000;--search-results-grey-color:#999;--search-tab-title-count-color:#888;--search-tab-button-not-selected-border-top-color:#e6e6e6;--search-tab-button-not-selected-background:#e6e6e6;--search-tab-button-selected-border-top-color:#0089ff;--search-tab-button-selected-background:#fff;--stab-background-color:#fff5d6;--stab-code-color:#000;--code-highlight-kw-color:#8959a8;--code-highlight-kw-2-color:#4271ae;--code-highlight-lifetime-color:#b76514;--code-highlight-prelude-color:#4271ae;--code-highlight-prelude-val-color:#c82829;--code-highlight-number-color:#718c00;--code-highlight-string-color:#718c00;--code-highlight-literal-color:#c82829;--code-highlight-attribute-color:#c82829;--code-highlight-self-color:#c82829;--code-highlight-macro-color:#3e999f;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#8e908c;--code-highlight-doc-comment-color:#4d4d4c;--src-line-numbers-span-color:#c67e2d;--src-line-number-highlighted-background-color:#fdffd3;--target-background-color:#fdffd3;--target-border-color:#ad7c37;--kbd-color:#000;--kbd-background:#fafbfc;--kbd-box-shadow-color:#c6cbd1;--rust-logo-filter:initial;--crate-search-div-filter:invert(100%) sepia(0%) saturate(4223%) hue-rotate(289deg) brightness(114%) contrast(76%);--crate-search-div-hover-filter:invert(44%) sepia(18%) saturate(23%) hue-rotate(317deg) brightness(96%) contrast(93%);--crate-search-hover-border:#717171;--src-sidebar-background-selected:#fff;--src-sidebar-background-hover:#e0e0e0;--table-alt-row-background-color:#f5f5f5;--codeblock-link-background:#eee;--scrape-example-toggle-line-background:#ccc;--scrape-example-toggle-line-hover-background:#999;--scrape-example-code-line-highlight:#fcffd6;--scrape-example-code-line-highlight-focus:#f6fdb0;--scrape-example-help-border-color:#555;--scrape-example-help-color:#333;--scrape-example-help-hover-border-color:#000;--scrape-example-help-hover-color:#000;--scrape-example-code-wrapper-background-start:rgba(255,255,255,1);--scrape-example-code-wrapper-background-end:rgba(255,255,255,0);--sidebar-resizer-hover:hsl(207,90%,66%);--sidebar-resizer-active:hsl(207,90%,54%);}:root[data-theme="dark"]{--main-background-color:#353535;--main-color:#ddd;--settings-input-color:#2196f3;--settings-input-border-color:#999;--settings-button-color:#000;--settings-button-border-focus:#ffb900;--sidebar-background-color:#505050;--sidebar-background-color-hover:#676767;--sidebar-border-color:#999;--code-block-background-color:#2A2A2A;--scrollbar-track-background-color:#717171;--scrollbar-thumb-background-color:rgba(32,34,37,.6);--scrollbar-color:rgba(32,34,37,.6) #5a5a5a;--headings-border-bottom-color:#d2d2d2;--border-color:#e0e0e0;--button-background-color:#f0f0f0;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--toggle-filter:invert(100%);--mobile-sidebar-menu-filter:invert(100%);--search-input-focused-border-color:#008dfd;--copy-path-button-color:#999;--copy-path-img-filter:invert(50%);--copy-path-img-hover-filter:invert(65%);--code-example-button-color:#7f7f7f;--code-example-button-hover-color:#a5a5a5;--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--warning-border-color:#ff8e00;--type-link-color:#2dbfb8;--trait-link-color:#b78cf2;--assoc-item-link-color:#d2991d;--function-link-color:#2bab63;--macro-link-color:#09bd00;--keyword-link-color:#d2991d;--mod-link-color:#d2991d;--link-color:#d2991d;--sidebar-link-color:#fdbf35;--sidebar-current-link-background-color:#444;--search-result-link-focus-background-color:#616161;--search-result-border-color:#aaa3;--search-color:#111;--search-error-code-background-color:#484848;--search-results-alias-color:#fff;--search-results-grey-color:#ccc;--search-tab-title-count-color:#888;--search-tab-button-not-selected-border-top-color:#252525;--search-tab-button-not-selected-background:#252525;--search-tab-button-selected-border-top-color:#0089ff;--search-tab-button-selected-background:#353535;--settings-menu-filter:invert(50%);--settings-menu-hover-filter:invert(65%);--stab-background-color:#314559;--stab-code-color:#e6e1cf;--code-highlight-kw-color:#ab8ac1;--code-highlight-kw-2-color:#769acb;--code-highlight-lifetime-color:#d97f26;--code-highlight-prelude-color:#769acb;--code-highlight-prelude-val-color:#ee6868;--code-highlight-number-color:#83a300;--code-highlight-string-color:#83a300;--code-highlight-literal-color:#ee6868;--code-highlight-attribute-color:#ee6868;--code-highlight-self-color:#ee6868;--code-highlight-macro-color:#3e999f;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#8d8d8b;--code-highlight-doc-comment-color:#8ca375;--src-line-numbers-span-color:#3b91e2;--src-line-number-highlighted-background-color:#0a042f;--target-background-color:#494a3d;--target-border-color:#bb7410;--kbd-color:#000;--kbd-background:#fafbfc;--kbd-box-shadow-color:#c6cbd1;--rust-logo-filter:drop-shadow(1px 0 0px #fff) drop-shadow(0 1px 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 -1px 0 #fff);--crate-search-div-filter:invert(94%) sepia(0%) saturate(721%) hue-rotate(255deg) brightness(90%) contrast(90%);--crate-search-div-hover-filter:invert(69%) sepia(60%) saturate(6613%) hue-rotate(184deg) brightness(100%) contrast(91%);--crate-search-hover-border:#2196f3;--src-sidebar-background-selected:#333;--src-sidebar-background-hover:#444;--table-alt-row-background-color:#2a2a2a;--codeblock-link-background:#333;--scrape-example-toggle-line-background:#999;--scrape-example-toggle-line-hover-background:#c5c5c5;--scrape-example-code-line-highlight:#5b3b01;--scrape-example-code-line-highlight-focus:#7c4b0f;--scrape-example-help-border-color:#aaa;--scrape-example-help-color:#eee;--scrape-example-help-hover-border-color:#fff;--scrape-example-help-hover-color:#fff;--scrape-example-code-wrapper-background-start:rgba(53,53,53,1);--scrape-example-code-wrapper-background-end:rgba(53,53,53,0);--sidebar-resizer-hover:hsl(207,30%,54%);--sidebar-resizer-active:hsl(207,90%,54%);}:root[data-theme="ayu"]{--main-background-color:#0f1419;--main-color:#c5c5c5;--settings-input-color:#ffb454;--settings-input-border-color:#999;--settings-button-color:#fff;--settings-button-border-focus:#e0e0e0;--sidebar-background-color:#14191f;--sidebar-background-color-hover:rgba(70,70,70,0.33);--sidebar-border-color:#5c6773;--code-block-background-color:#191f26;--scrollbar-track-background-color:transparent;--scrollbar-thumb-background-color:#5c6773;--scrollbar-color:#5c6773 #24292f;--headings-border-bottom-color:#5c6773;--border-color:#5c6773;--button-background-color:#141920;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--toggle-filter:invert(100%);--mobile-sidebar-menu-filter:invert(100%);--search-input-focused-border-color:#5c6773;--copy-path-button-color:#fff;--copy-path-img-filter:invert(70%);--copy-path-img-hover-filter:invert(100%);--code-example-button-color:#b2b2b2;--code-example-button-hover-color:#fff;--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--warning-border-color:#ff8e00;--type-link-color:#ffa0a5;--trait-link-color:#39afd7;--assoc-item-link-color:#39afd7;--function-link-color:#fdd687;--macro-link-color:#a37acc;--keyword-link-color:#39afd7;--mod-link-color:#39afd7;--link-color:#39afd7;--sidebar-link-color:#53b1db;--sidebar-current-link-background-color:transparent;--search-result-link-focus-background-color:#3c3c3c;--search-result-border-color:#aaa3;--search-color:#fff;--search-error-code-background-color:#4f4c4c;--search-results-alias-color:#c5c5c5;--search-results-grey-color:#999;--search-tab-title-count-color:#888;--search-tab-button-not-selected-border-top-color:none;--search-tab-button-not-selected-background:transparent !important;--search-tab-button-selected-border-top-color:none;--search-tab-button-selected-background:#141920 !important;--settings-menu-filter:invert(70%);--settings-menu-hover-filter:invert(100%);--stab-background-color:#314559;--stab-code-color:#e6e1cf;--code-highlight-kw-color:#ff7733;--code-highlight-kw-2-color:#ff7733;--code-highlight-lifetime-color:#ff7733;--code-highlight-prelude-color:#69f2df;--code-highlight-prelude-val-color:#ff7733;--code-highlight-number-color:#b8cc52;--code-highlight-string-color:#b8cc52;--code-highlight-literal-color:#ff7733;--code-highlight-attribute-color:#e6e1cf;--code-highlight-self-color:#36a3d9;--code-highlight-macro-color:#a37acc;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#788797;--code-highlight-doc-comment-color:#a1ac88;--src-line-numbers-span-color:#5c6773;--src-line-number-highlighted-background-color:rgba(255,236,164,0.06);--target-background-color:rgba(255,236,164,0.06);--target-border-color:rgba(255,180,76,0.85);--kbd-color:#c5c5c5;--kbd-background:#314559;--kbd-box-shadow-color:#5c6773;--rust-logo-filter:drop-shadow(1px 0 0px #fff) drop-shadow(0 1px 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 -1px 0 #fff);--crate-search-div-filter:invert(41%) sepia(12%) saturate(487%) hue-rotate(171deg) brightness(94%) contrast(94%);--crate-search-div-hover-filter:invert(98%) sepia(12%) saturate(81%) hue-rotate(343deg) brightness(113%) contrast(76%);--crate-search-hover-border:#e0e0e0;--src-sidebar-background-selected:#14191f;--src-sidebar-background-hover:#14191f;--table-alt-row-background-color:#191f26;--codeblock-link-background:#333;--scrape-example-toggle-line-background:#999;--scrape-example-toggle-line-hover-background:#c5c5c5;--scrape-example-code-line-highlight:#5b3b01;--scrape-example-code-line-highlight-focus:#7c4b0f;--scrape-example-help-border-color:#aaa;--scrape-example-help-color:#eee;--scrape-example-help-hover-border-color:#fff;--scrape-example-help-hover-color:#fff;--scrape-example-code-wrapper-background-start:rgba(15,20,25,1);--scrape-example-code-wrapper-background-end:rgba(15,20,25,0);--sidebar-resizer-hover:hsl(34,50%,33%);--sidebar-resizer-active:hsl(34,100%,66%);}:root[data-theme="ayu"] h1,:root[data-theme="ayu"] h2,:root[data-theme="ayu"] h3,:root[data-theme="ayu"] h4,:where(:root[data-theme="ayu"]) h1 a,:root[data-theme="ayu"] .sidebar h2 a,:root[data-theme="ayu"] .sidebar h3 a{color:#fff;}:root[data-theme="ayu"] .docblock code{color:#ffb454;}:root[data-theme="ayu"] .docblock a>code{color:#39AFD7 !important;}:root[data-theme="ayu"] .code-header,:root[data-theme="ayu"] .docblock pre>code,:root[data-theme="ayu"] pre,:root[data-theme="ayu"] pre>code,:root[data-theme="ayu"] .item-info code,:root[data-theme="ayu"] .rustdoc.source .example-wrap{color:#e6e1cf;}:root[data-theme="ayu"] .sidebar .current,:root[data-theme="ayu"] .sidebar .current a,:root[data-theme="ayu"] .sidebar a:hover,:root[data-theme="ayu"] #src-sidebar div.files>a:hover,:root[data-theme="ayu"] details.dir-entry summary:hover,:root[data-theme="ayu"] #src-sidebar div.files>a:focus,:root[data-theme="ayu"] details.dir-entry summary:focus,:root[data-theme="ayu"] #src-sidebar div.files>a.selected{color:#ffb44c;}:root[data-theme="ayu"] .sidebar-elems .location{color:#ff7733;}:root[data-theme="ayu"] a[data-nosnippet].line-highlighted{color:#708090;padding-right:7px;border-right:1px solid #ffb44c;}:root[data-theme="ayu"] .search-results a:hover,:root[data-theme="ayu"] .search-results a:focus{color:#fff !important;background-color:#3c3c3c;}:root[data-theme="ayu"] .search-results a{color:#0096cf;}:root[data-theme="ayu"] .search-results a div.desc{color:#c5c5c5;}:root[data-theme="ayu"] .result-name .primitive>i,:root[data-theme="ayu"] .result-name .keyword>i{color:#788797;}:root[data-theme="ayu"] #search-tabs>button.selected{border-bottom:1px solid #ffb44c !important;border-top:none;}:root[data-theme="ayu"] #search-tabs>button:not(.selected){border:none;background-color:transparent !important;}:root[data-theme="ayu"] #search-tabs>button:hover{border-bottom:1px solid rgba(242,151,24,0.3);}:root[data-theme="ayu"] #settings-menu>a img,:root[data-theme="ayu"] #sidebar-button>a::before{filter:invert(100);} \ No newline at end of file diff --git a/static.files/scrape-examples-5e967b76.js b/static.files/scrape-examples-5e967b76.js new file mode 100644 index 0000000000..40dfe842fd --- /dev/null +++ b/static.files/scrape-examples-5e967b76.js @@ -0,0 +1 @@ +"use strict";(function(){const DEFAULT_MAX_LINES=5;const HIDDEN_MAX_LINES=10;function scrollToLoc(elt,loc,isHidden){const lines=elt.querySelectorAll("[data-nosnippet]");let scrollOffset;const maxLines=isHidden?HIDDEN_MAX_LINES:DEFAULT_MAX_LINES;if(loc[1]-loc[0]>maxLines){const line=Math.max(0,loc[0]-1);scrollOffset=lines[line].offsetTop;}else{const halfHeight=elt.offsetHeight/2;const offsetTop=lines[loc[0]].offsetTop;const lastLine=lines[loc[1]];const offsetBot=lastLine.offsetTop+lastLine.offsetHeight;const offsetMid=(offsetTop+offsetBot)/2;scrollOffset=offsetMid-halfHeight;}lines[0].parentElement.scrollTo(0,scrollOffset);elt.querySelector(".rust").scrollTo(0,scrollOffset);}function createScrapeButton(parent,className,content){const button=document.createElement("button");button.className=className;button.title=content;parent.insertBefore(button,parent.firstChild);return button;}window.updateScrapedExample=(example,buttonHolder)=>{let locIndex=0;const highlights=Array.prototype.slice.call(example.querySelectorAll(".highlight"));const link=example.querySelector(".scraped-example-title a");let expandButton=null;if(!example.classList.contains("expanded")){expandButton=createScrapeButton(buttonHolder,"expand","Show all");}const isHidden=example.parentElement.classList.contains("more-scraped-examples");const locs=example.locs;if(locs.length>1){const next=createScrapeButton(buttonHolder,"next","Next usage");const prev=createScrapeButton(buttonHolder,"prev","Previous usage");const onChangeLoc=changeIndex=>{removeClass(highlights[locIndex],"focus");changeIndex();scrollToLoc(example,locs[locIndex][0],isHidden);addClass(highlights[locIndex],"focus");const url=locs[locIndex][1];const title=locs[locIndex][2];link.href=url;link.innerHTML=title;};prev.addEventListener("click",()=>{onChangeLoc(()=>{locIndex=(locIndex-1+locs.length)%locs.length;});});next.addEventListener("click",()=>{onChangeLoc(()=>{locIndex=(locIndex+1)%locs.length;});});}if(expandButton){expandButton.addEventListener("click",()=>{if(hasClass(example,"expanded")){removeClass(example,"expanded");removeClass(expandButton,"collapse");expandButton.title="Show all";scrollToLoc(example,locs[0][0],isHidden);}else{addClass(example,"expanded");addClass(expandButton,"collapse");expandButton.title="Show single example";}});}};function setupLoc(example,isHidden){example.locs=JSON.parse(example.attributes.getNamedItem("data-locs").textContent);scrollToLoc(example,example.locs[0][0],isHidden);}const firstExamples=document.querySelectorAll(".scraped-example-list > .scraped-example");onEachLazy(firstExamples,el=>setupLoc(el,false));onEachLazy(document.querySelectorAll(".more-examples-toggle"),toggle=>{onEachLazy(toggle.querySelectorAll(".toggle-line, .hide-more"),button=>{button.addEventListener("click",()=>{toggle.open=false;});});const moreExamples=toggle.querySelectorAll(".scraped-example");toggle.querySelector("summary").addEventListener("click",()=>{setTimeout(()=>{onEachLazy(moreExamples,el=>setupLoc(el,true));});},{once:true});});})(); \ No newline at end of file diff --git a/static.files/search-fa3e91e5.js b/static.files/search-fa3e91e5.js new file mode 100644 index 0000000000..d4818b4fe6 --- /dev/null +++ b/static.files/search-fa3e91e5.js @@ -0,0 +1,6 @@ +"use strict";if(!Array.prototype.toSpliced){Array.prototype.toSpliced=function(){const me=this.slice();Array.prototype.splice.apply(me,arguments);return me;};}function onEachBtwn(arr,func,funcBtwn){let skipped=true;for(const value of arr){if(!skipped){funcBtwn(value);}skipped=func(value);}}function undef2null(x){if(x!==undefined){return x;}return null;}const itemTypes=["keyword","primitive","mod","externcrate","import","struct","enum","fn","type","static","trait","impl","tymethod","method","structfield","variant","macro","associatedtype","constant","associatedconstant","union","foreigntype","existential","attr","derive","traitalias","generic",];const TY_PRIMITIVE=itemTypes.indexOf("primitive");const TY_GENERIC=itemTypes.indexOf("generic");const TY_IMPORT=itemTypes.indexOf("import");const TY_TRAIT=itemTypes.indexOf("trait");const TY_FN=itemTypes.indexOf("fn");const TY_METHOD=itemTypes.indexOf("method");const TY_TYMETHOD=itemTypes.indexOf("tymethod");const ROOT_PATH=typeof window!=="undefined"?window.rootPath:"../";const UNBOXING_LIMIT=5;const REGEX_IDENT=/\p{ID_Start}\p{ID_Continue}*|_\p{ID_Continue}+/uy;const REGEX_INVALID_TYPE_FILTER=/[^a-z]/ui;const MAX_RESULTS=200;const NO_TYPE_FILTER=-1;const editDistanceState={current:[],prev:[],prevPrev:[],calculate:function calculate(a,b,limit){if(a.lengthlimit){return limit+1;}while(b.length>0&&b[0]===a[0]){a=a.substring(1);b=b.substring(1);}while(b.length>0&&b[b.length-1]===a[a.length-1]){a=a.substring(0,a.length-1);b=b.substring(0,b.length-1);}if(b.length===0){return minDist;}const aLength=a.length;const bLength=b.length;for(let i=0;i<=bLength;++i){this.current[i]=0;this.prev[i]=i;this.prevPrev[i]=Number.MAX_VALUE;}for(let i=1;i<=aLength;++i){this.current[0]=i;const aIdx=i-1;for(let j=1;j<=bLength;++j){const bIdx=j-1;const substitutionCost=a[aIdx]===b[bIdx]?0:1;this.current[j]=Math.min(this.prev[j]+1,this.current[j-1]+1,this.prev[j-1]+substitutionCost,);if((i>1)&&(j>1)&&(a[aIdx]===b[bIdx-1])&&(a[aIdx-1]===b[bIdx])){this.current[j]=Math.min(this.current[j],this.prevPrev[j-2]+1,);}}const prevPrevTmp=this.prevPrev;this.prevPrev=this.prev;this.prev=this.current;this.current=prevPrevTmp;}const distance=this.prev[bLength];return distance<=limit?distance:(limit+1);},};function editDistance(a,b,limit){return editDistanceState.calculate(a,b,limit);}function isEndCharacter(c){return"=,>-])".indexOf(c)!==-1;}function isFnLikeTy(ty){return ty===TY_FN||ty===TY_METHOD||ty===TY_TYMETHOD;}function isSeparatorCharacter(c){return c===","||c==="=";}function isReturnArrow(parserState){return parserState.userQuery.slice(parserState.pos,parserState.pos+2)==="->";}function skipWhitespace(parserState){while(parserState.pos0){const c=parserState.userQuery[pos-1];if(c===lookingFor){return true;}else if(c!==" "){break;}pos-=1;}return false;}function isLastElemGeneric(elems,parserState){return(elems.length>0&&elems[elems.length-1].generics.length>0)||prevIs(parserState,">");}function getFilteredNextElem(query,parserState,elems,isInGenerics){const start=parserState.pos;if(parserState.userQuery[parserState.pos]===":"&&!isPathStart(parserState)){throw["Expected type filter before ",":"];}getNextElem(query,parserState,elems,isInGenerics);if(parserState.userQuery[parserState.pos]===":"&&!isPathStart(parserState)){if(parserState.typeFilter!==null){throw["Unexpected ",":"," (expected path after type filter ",parserState.typeFilter+":",")",];}if(elems.length===0){throw["Expected type filter before ",":"];}else if(query.literalSearch){throw["Cannot use quotes on type filter"];}const typeFilterElem=elems.pop();checkExtraTypeFilterCharacters(start,parserState);parserState.typeFilter=typeFilterElem.normalizedPathLast;parserState.pos+=1;parserState.totalElems-=1;query.literalSearch=false;getNextElem(query,parserState,elems,isInGenerics);}}function getItemsBefore(query,parserState,elems,endChar){let foundStopChar=true;let foundSeparator=false;const oldTypeFilter=parserState.typeFilter;parserState.typeFilter=null;const oldIsInBinding=parserState.isInBinding;parserState.isInBinding=null;let hofParameters=null;let extra="";if(endChar===">"){extra="<";}else if(endChar==="]"){extra="[";}else if(endChar===")"){extra="(";}else if(endChar===""){extra="->";}else{extra=endChar;}while(parserState.pos"," after ","="];}hofParameters=[...elems];elems.length=0;parserState.pos+=2;foundStopChar=true;foundSeparator=false;continue;}else if(c===" "){parserState.pos+=1;continue;}else if(isSeparatorCharacter(c)){parserState.pos+=1;foundStopChar=true;foundSeparator=true;continue;}else if(c===":"&&isPathStart(parserState)){throw["Unexpected ","::",": paths cannot start with ","::"];}else if(isEndCharacter(c)){throw["Unexpected ",c," after ",extra];}if(!foundStopChar){let extra=[];if(isLastElemGeneric(query.elems,parserState)){extra=[" after ",">"];}else if(prevIs(parserState,"\"")){throw["Cannot have more than one element if you use quotes"];}if(endChar!==""){throw["Expected ",",",", ","=",", or ",endChar,...extra,", found ",c,];}throw["Expected ",","," or ","=",...extra,", found ",c,];}const posBefore=parserState.pos;getFilteredNextElem(query,parserState,elems,endChar!=="");if(endChar!==""&&parserState.pos>=parserState.length){throw["Unclosed ",extra];}if(posBefore===parserState.pos){parserState.pos+=1;}foundStopChar=false;}if(parserState.pos>=parserState.length&&endChar!==""){throw["Unclosed ",extra];}parserState.pos+=1;if(hofParameters){foundSeparator=false;if([...elems,...hofParameters].some(x=>x.bindingName)||parserState.isInBinding){throw["Unexpected ","="," within ","->"];}const hofElem=makePrimitiveElement("->",{generics:hofParameters,bindings:new Map([["output",[...elems]]]),typeFilter:null,});elems.length=0;elems[0]=hofElem;}parserState.typeFilter=oldTypeFilter;parserState.isInBinding=oldIsInBinding;return{foundSeparator};}function getNextElem(query,parserState,elems,isInGenerics){const generics=[];skipWhitespace(parserState);let start=parserState.pos;let end;if("[(".indexOf(parserState.userQuery[parserState.pos])!==-1){let endChar=")";let name="()";let friendlyName="tuple";if(parserState.userQuery[parserState.pos]==="["){endChar="]";name="[]";friendlyName="slice";}parserState.pos+=1;const{foundSeparator}=getItemsBefore(query,parserState,generics,endChar);const typeFilter=parserState.typeFilter;const bindingName=parserState.isInBinding;parserState.typeFilter=null;parserState.isInBinding=null;for(const gen of generics){if(gen.bindingName!==null){throw["Type parameter ","=",` cannot be within ${friendlyName} `,name];}}if(name==="()"&&!foundSeparator&&generics.length===1&&typeFilter===null){elems.push(generics[0]);}else if(name==="()"&&generics.length===1&&generics[0].name==="->"){generics[0].typeFilter=typeFilter;elems.push(generics[0]);}else{if(typeFilter!==null&&typeFilter!=="primitive"){throw["Invalid search type: primitive ",name," and ",typeFilter," both specified",];}parserState.totalElems+=1;if(isInGenerics){parserState.genericsElems+=1;}elems.push(makePrimitiveElement(name,{bindingName,generics}));}}else if(parserState.userQuery[parserState.pos]==="&"){if(parserState.typeFilter!==null&&parserState.typeFilter!=="primitive"){throw["Invalid search type: primitive ","&"," and ",parserState.typeFilter," both specified",];}parserState.typeFilter=null;parserState.pos+=1;let c=parserState.userQuery[parserState.pos];while(c===" "&&parserState.pos=end){throw["Found generics without a path"];}parserState.pos+=1;getItemsBefore(query,parserState,generics,">");}else if(parserState.pos=end){throw["Found generics without a path"];}if(parserState.isInBinding){throw["Unexpected ","("," after ","="];}parserState.pos+=1;const typeFilter=parserState.typeFilter;parserState.typeFilter=null;getItemsBefore(query,parserState,generics,")");skipWhitespace(parserState);if(isReturnArrow(parserState)){parserState.pos+=2;skipWhitespace(parserState);getFilteredNextElem(query,parserState,generics,isInGenerics);generics[generics.length-1].bindingName=makePrimitiveElement("output");}else{generics.push(makePrimitiveElement(null,{bindingName:makePrimitiveElement("output"),typeFilter:null,}));}parserState.typeFilter=typeFilter;}if(isStringElem){skipWhitespace(parserState);}if(start>=end&&generics.length===0){return;}if(parserState.userQuery[parserState.pos]==="="){if(parserState.isInBinding){throw["Cannot write ","="," twice in a binding"];}if(!isInGenerics){throw["Type parameter ","="," must be within generics list"];}const name=parserState.userQuery.slice(start,end).trim();if(name==="!"){throw["Type parameter ","="," key cannot be ","!"," never type"];}if(name.includes("!")){throw["Type parameter ","="," key cannot be ","!"," macro"];}if(name.includes("::")){throw["Type parameter ","="," key cannot contain ","::"," path"];}if(name.includes(":")){throw["Type parameter ","="," key cannot contain ",":"," type"];}parserState.isInBinding={name,generics};}else{elems.push(createQueryElement(query,parserState,parserState.userQuery.slice(start,end),generics,isInGenerics,),);}}}function checkExtraTypeFilterCharacters(start,parserState){const query=parserState.userQuery.slice(start,parserState.pos).trim();const match=query.match(REGEX_INVALID_TYPE_FILTER);if(match){throw["Unexpected ",match[0]," in type filter (before ",":",")",];}}function createQueryElement(query,parserState,name,generics,isInGenerics){const path=name.trim();if(path.length===0&&generics.length===0){throw["Unexpected ",parserState.userQuery[parserState.pos]];}if(query.literalSearch&&parserState.totalElems-parserState.genericsElems>0){throw["Cannot have more than one element if you use quotes"];}const typeFilter=parserState.typeFilter;parserState.typeFilter=null;if(name.trim()==="!"){if(typeFilter!==null&&typeFilter!=="primitive"){throw["Invalid search type: primitive never type ","!"," and ",typeFilter," both specified",];}if(generics.length!==0){throw["Never type ","!"," does not accept generic parameters",];}const bindingName=parserState.isInBinding;parserState.isInBinding=null;return makePrimitiveElement("never",{bindingName});}const quadcolon=/::\s*::/.exec(path);if(path.startsWith("::")){throw["Paths cannot start with ","::"];}else if(quadcolon!==null){throw["Unexpected ",quadcolon[0]];}const pathSegments=path.split(/(?:::\s*)|(?:\s+(?:::\s*)?)/).map(x=>x.toLowerCase());if(pathSegments.length===0||(pathSegments.length===1&&pathSegments[0]==="")){if(generics.length>0||prevIs(parserState,">")){throw["Found generics without a path"];}else{throw["Unexpected ",parserState.userQuery[parserState.pos]];}}for(const[i,pathSegment]of pathSegments.entries()){if(pathSegment==="!"){if(i!==0){throw["Never type ","!"," is not associated item"];}pathSegments[i]="never";}}parserState.totalElems+=1;if(isInGenerics){parserState.genericsElems+=1;}const bindingName=parserState.isInBinding;parserState.isInBinding=null;const bindings=new Map();const pathLast=pathSegments[pathSegments.length-1];return{name:name.trim(),id:null,fullPath:pathSegments,pathWithoutLast:pathSegments.slice(0,pathSegments.length-1),pathLast,normalizedPathLast:pathLast.replace(/_/g,""),generics:generics.filter(gen=>{if(gen.bindingName!==null&&gen.bindingName.name!==null){if(gen.name!==null){gen.bindingName.generics.unshift(gen);}bindings.set(gen.bindingName.name.toLowerCase().replace(/_/g,""),gen.bindingName.generics,);return false;}return true;}),bindings,typeFilter,bindingName,};}function makePrimitiveElement(name,extra){return Object.assign({name,id:null,fullPath:[name],pathWithoutLast:[],pathLast:name,normalizedPathLast:name,generics:[],bindings:new Map(),typeFilter:"primitive",bindingName:null,},extra);}function getStringElem(query,parserState,isInGenerics){if(isInGenerics){throw["Unexpected ","\""," in generics"];}else if(query.literalSearch){throw["Cannot have more than one literal search element"];}else if(parserState.totalElems-parserState.genericsElems>0){throw["Cannot use literal search when there is more than one element"];}parserState.pos+=1;const start=parserState.pos;const end=getIdentEndPosition(parserState);if(parserState.pos>=parserState.length){throw["Unclosed ","\""];}else if(parserState.userQuery[end]!=="\""){throw["Unexpected ",parserState.userQuery[end]," in a string element"];}else if(start===end){throw["Cannot have empty string element"];}parserState.pos+=1;query.literalSearch=true;}function getIdentEndPosition(parserState){let afterIdent=consumeIdent(parserState);let end=parserState.pos;let macroExclamation=-1;while(parserState.pos0){throw["Unexpected ",c," after ",parserState.userQuery[parserState.pos-1]," (not a valid identifier)"];}else{throw["Unexpected ",c," (not a valid identifier)"];}parserState.pos+=1;afterIdent=consumeIdent(parserState);end=parserState.pos;}if(macroExclamation!==-1){if(parserState.typeFilter===null){parserState.typeFilter="macro";}else if(parserState.typeFilter!=="macro"){throw["Invalid search type: macro ","!"," and ",parserState.typeFilter," both specified",];}end=macroExclamation;}return end;}function isSpecialStartCharacter(c){return"<\"".indexOf(c)!==-1;}function isPathStart(parserState){return parserState.userQuery.slice(parserState.pos,parserState.pos+2)==="::";}function consumeIdent(parserState){REGEX_IDENT.lastIndex=parserState.pos;const match=parserState.userQuery.match(REGEX_IDENT);if(match){parserState.pos+=match[0].length;return true;}return false;}function isPathSeparator(c){return c===":"||c===" ";}class VlqHexDecoder{constructor(string,cons){this.string=string;this.cons=cons;this.offset=0;this.backrefQueue=[];}decodeList(){let c=this.string.charCodeAt(this.offset);const ret=[];while(c!==125){ret.push(this.decode());c=this.string.charCodeAt(this.offset);}this.offset+=1;return ret;}decode(){let n=0;let c=this.string.charCodeAt(this.offset);if(c===123){this.offset+=1;return this.decodeList();}while(c<96){n=(n<<4)|(c&0xF);this.offset+=1;c=this.string.charCodeAt(this.offset);}n=(n<<4)|(c&0xF);const[sign,value]=[n&1,n>>1];this.offset+=1;return sign?-value:value;}next(){const c=this.string.charCodeAt(this.offset);if(c>=48&&c<64){this.offset+=1;return this.backrefQueue[c-48];}if(c===96){this.offset+=1;return this.cons(0);}const result=this.cons(this.decode());this.backrefQueue.unshift(result);if(this.backrefQueue.length>16){this.backrefQueue.pop();}return result;}}class RoaringBitmap{constructor(str){const strdecoded=atob(str);const u8array=new Uint8Array(strdecoded.length);for(let j=0;j=4){offsets=[];for(let j=0;j>3]&(1<<(j&0x7))){const runcount=(u8array[i]|(u8array[i+1]<<8));i+=2;this.containers.push(new RoaringBitmapRun(runcount,u8array.slice(i,i+(runcount*4)),));i+=runcount*4;}else if(this.cardinalities[j]>=4096){this.containers.push(new RoaringBitmapBits(u8array.slice(i,i+8192)));i+=8192;}else{const end=this.cardinalities[j]*2;this.containers.push(new RoaringBitmapArray(this.cardinalities[j],u8array.slice(i,i+end),));i+=end;}}}contains(keyvalue){const key=keyvalue>>16;const value=keyvalue&0xFFFF;let left=0;let right=this.keys.length-1;while(left<=right){const mid=Math.floor((left+right)/2);const x=this.keys[mid];if(xkey){right=mid-1;}else{return this.containers[mid].contains(value);}}return false;}}class RoaringBitmapRun{constructor(runcount,array){this.runcount=runcount;this.array=array;}contains(value){let left=0;let right=this.runcount-1;while(left<=right){const mid=Math.floor((left+right)/2);const i=mid*4;const start=this.array[i]|(this.array[i+1]<<8);const lenm1=this.array[i+2]|(this.array[i+3]<<8);if((start+lenm1)value){right=mid-1;}else{return true;}}return false;}}class RoaringBitmapArray{constructor(cardinality,array){this.cardinality=cardinality;this.array=array;}contains(value){let left=0;let right=this.cardinality-1;while(left<=right){const mid=Math.floor((left+right)/2);const i=mid*2;const x=this.array[i]|(this.array[i+1]<<8);if(xvalue){right=mid-1;}else{return true;}}return false;}}class RoaringBitmapBits{constructor(array){this.array=array;}contains(value){return!!(this.array[value>>3]&(1<<(value&7)));}}class NameTrie{constructor(){this.children=[];this.matches=[];}insert(name,id,tailTable){this.insertSubstring(name,0,id,tailTable);}insertSubstring(name,substart,id,tailTable){const l=name.length;if(substart===l){this.matches.push(id);}else{const sb=name.charCodeAt(substart);let child;if(this.children[sb]!==undefined){child=this.children[sb];}else{child=new NameTrie();this.children[sb]=child;let sste;if(substart>=2){const tail=name.substring(substart-2,substart+1);const entry=tailTable.get(tail);if(entry!==undefined){sste=entry;}else{sste=[];tailTable.set(tail,sste);}sste.push(child);}}child.insertSubstring(name,substart+1,id,tailTable);}}search(name,tailTable){const results=new Set();this.searchSubstringPrefix(name,0,results);if(results.size=3){const levParams=name.length>=6?new Lev2TParametricDescription(name.length):new Lev1TParametricDescription(name.length);this.searchLev(name,0,levParams,results);const tail=name.substring(0,3);const list=tailTable.get(tail);if(list!==undefined){for(const entry of list){entry.searchSubstringPrefix(name,3,results);}}}return[...results];}searchSubstringPrefix(name,substart,results){const l=name.length;if(substart===l){for(const match of this.matches){results.add(match);}let unprocessedChildren=[];for(const child of this.children){if(child){unprocessedChildren.push(child);}}let nextSet=[];while(unprocessedChildren.length!==0){const next=unprocessedChildren.pop();for(const child of next.children){if(child){nextSet.push(child);}}for(const match of next.matches){results.add(match);}if(unprocessedChildren.length===0){const tmp=unprocessedChildren;unprocessedChildren=nextSet;nextSet=tmp;}}}else{const sb=name.charCodeAt(substart);if(this.children[sb]!==undefined){this.children[sb].searchSubstringPrefix(name,substart+1,results);}}}searchLev(name,substart,levParams,results){const stack=[[this,0]];const n=levParams.n;while(stack.length!==0){const[trie,levState]=stack.pop();for(const[charCode,child]of trie.children.entries()){if(!child){continue;}const levPos=levParams.getPosition(levState);const vector=levParams.getVector(name,charCode,levPos,Math.min(name.length,levPos+(2*n)+1),);const newLevState=levParams.transition(levState,levPos,vector,);if(newLevState>=0){stack.push([child,newLevState]);if(levParams.isAccept(newLevState)){for(const match of child.matches){results.add(match);}}}}}}}class DocSearch{constructor(rawSearchIndex,rootPath,searchState){this.searchIndexDeprecated=new Map();this.searchIndexEmptyDesc=new Map();this.functionTypeFingerprint=new Uint32Array(0);this.typeNameIdMap=new Map();this.assocTypeIdNameMap=new Map();this.ALIASES=new Map();this.FOUND_ALIASES=new Set();this.rootPath=rootPath;this.searchState=searchState;this.typeNameIdOfArray=this.buildTypeMapIndex("array");this.typeNameIdOfSlice=this.buildTypeMapIndex("slice");this.typeNameIdOfArrayOrSlice=this.buildTypeMapIndex("[]");this.typeNameIdOfTuple=this.buildTypeMapIndex("tuple");this.typeNameIdOfUnit=this.buildTypeMapIndex("unit");this.typeNameIdOfTupleOrUnit=this.buildTypeMapIndex("()");this.typeNameIdOfFn=this.buildTypeMapIndex("fn");this.typeNameIdOfFnMut=this.buildTypeMapIndex("fnmut");this.typeNameIdOfFnOnce=this.buildTypeMapIndex("fnonce");this.typeNameIdOfHof=this.buildTypeMapIndex("->");this.typeNameIdOfOutput=this.buildTypeMapIndex("output",true);this.typeNameIdOfReference=this.buildTypeMapIndex("reference");this.EMPTY_BINDINGS_MAP=new Map();this.EMPTY_GENERICS_ARRAY=[];this.TYPES_POOL=new Map();this.nameTrie=new NameTrie();this.tailTable=new Map();this.searchIndex=this.buildIndex(rawSearchIndex);}buildTypeMapIndex(name,isAssocType){if(name===""||name===null){return null;}const obj=this.typeNameIdMap.get(name);if(obj!==undefined){obj.assocOnly=!!(isAssocType&&obj.assocOnly);return obj.id;}else{const id=this.typeNameIdMap.size;this.typeNameIdMap.set(name,{id,assocOnly:!!isAssocType});return id;}}buildItemSearchTypeAll(types,paths,lowercasePaths){return types&&types.length>0?types.map(type=>this.buildItemSearchType(type,paths,lowercasePaths)):this.EMPTY_GENERICS_ARRAY;}buildItemSearchType(type,paths,lowercasePaths,isAssocType){const PATH_INDEX_DATA=0;const GENERICS_DATA=1;const BINDINGS_DATA=2;let pathIndex,generics,bindings;if(typeof type==="number"){pathIndex=type;generics=this.EMPTY_GENERICS_ARRAY;bindings=this.EMPTY_BINDINGS_MAP;}else{pathIndex=type[PATH_INDEX_DATA];generics=this.buildItemSearchTypeAll(type[GENERICS_DATA],paths,lowercasePaths,);if(type.length>BINDINGS_DATA&&type[BINDINGS_DATA].length>0){bindings=new Map(type[BINDINGS_DATA].map(binding=>{const[assocType,constraints]=binding;return[this.buildItemSearchType(assocType,paths,lowercasePaths,true).id,this.buildItemSearchTypeAll(constraints,paths,lowercasePaths),];}));}else{bindings=this.EMPTY_BINDINGS_MAP;}}let result;if(pathIndex<0){result={id:pathIndex,name:"",ty:TY_GENERIC,path:null,exactPath:null,generics,bindings,unboxFlag:true,};}else if(pathIndex===0){result={id:null,name:"",ty:null,path:null,exactPath:null,generics,bindings,unboxFlag:true,};}else{const item=lowercasePaths[pathIndex-1];const id=this.buildTypeMapIndex(item.name,isAssocType);if(isAssocType&&id!==null){this.assocTypeIdNameMap.set(id,paths[pathIndex-1].name);}result={id,name:paths[pathIndex-1].name,ty:item.ty,path:item.path,exactPath:item.exactPath,generics,bindings,unboxFlag:item.unboxFlag,};}const cr=this.TYPES_POOL.get(result.id);if(cr){if(cr.generics.length===result.generics.length&&cr.generics!==result.generics&&cr.generics.every((x,i)=>result.generics[i]===x)){result.generics=cr.generics;}if(cr.bindings.size===result.bindings.size&&cr.bindings!==result.bindings){let ok=true;for(const[k,v]of cr.bindings.entries()){const v2=result.bindings.get(v);if(!v2){ok=false;break;}if(v!==v2&&v.length===v2.length&&v.every((x,i)=>v2[i]===x)){result.bindings.set(k,v);}else if(v!==v2){ok=false;break;}}if(ok){result.bindings=cr.bindings;}}if(cr.ty===result.ty&&cr.path===result.path&&cr.bindings===result.bindings&&cr.generics===result.generics&&cr.ty===result.ty&&cr.name===result.name&&cr.unboxFlag===result.unboxFlag){return cr;}}this.TYPES_POOL.set(result.id,result);return result;}buildFunctionTypeFingerprint(type,output){let input=type.id;if(input===this.typeNameIdOfArray||input===this.typeNameIdOfSlice){input=this.typeNameIdOfArrayOrSlice;}if(input===this.typeNameIdOfTuple||input===this.typeNameIdOfUnit){input=this.typeNameIdOfTupleOrUnit;}if(input===this.typeNameIdOfFn||input===this.typeNameIdOfFnMut||input===this.typeNameIdOfFnOnce){input=this.typeNameIdOfHof;}const hashint1=k=>{k=(~~k+0x7ed55d16)+(k<<12);k=(k ^ 0xc761c23c)^(k>>>19);k=(~~k+0x165667b1)+(k<<5);k=(~~k+0xd3a2646c)^(k<<9);k=(~~k+0xfd7046c5)+(k<<3);return(k ^ 0xb55a4f09)^(k>>>16);};const hashint2=k=>{k=~k+(k<<15);k ^=k>>>12;k+=k<<2;k ^=k>>>4;k=Math.imul(k,2057);return k ^(k>>16);};if(input!==null){const h0a=hashint1(input);const h0b=hashint2(input);const h1a=~~(h0a+Math.imul(h0b,2));const h1b=~~(h0a+Math.imul(h0b,3));const h2a=~~(h0a+Math.imul(h0b,4));const h2b=~~(h0a+Math.imul(h0b,5));output[0]|=(1<<(h0a%32))|(1<<(h1b%32));output[1]|=(1<<(h1a%32))|(1<<(h2b%32));output[2]|=(1<<(h2a%32))|(1<<(h0b%32));output[3]+=1;}for(const g of type.generics){this.buildFunctionTypeFingerprint(g,output);}const fb={id:null,ty:0,generics:this.EMPTY_GENERICS_ARRAY,bindings:this.EMPTY_BINDINGS_MAP,};for(const[k,v]of type.bindings.entries()){fb.id=k;fb.generics=v;this.buildFunctionTypeFingerprint(fb,output);}}buildIndex(rawSearchIndex){const buildFunctionSearchTypeCallback=(paths,lowercasePaths)=>{const cb=functionSearchType=>{if(functionSearchType===0){return null;}const INPUTS_DATA=0;const OUTPUT_DATA=1;let inputs;let output;if(typeof functionSearchType[INPUTS_DATA]==="number"){inputs=[this.buildItemSearchType(functionSearchType[INPUTS_DATA],paths,lowercasePaths,),];}else{inputs=this.buildItemSearchTypeAll(functionSearchType[INPUTS_DATA],paths,lowercasePaths,);}if(functionSearchType.length>1){if(typeof functionSearchType[OUTPUT_DATA]==="number"){output=[this.buildItemSearchType(functionSearchType[OUTPUT_DATA],paths,lowercasePaths,),];}else{output=this.buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA],paths,lowercasePaths,);}}else{output=[];}const where_clause=[];const l=functionSearchType.length;for(let i=2;i{const n=noop;return n;});let descShard={crate,shard:0,start:0,len:itemDescShardDecoder.next(),promise:null,resolve:null,};const descShardList=[descShard];this.searchIndexDeprecated.set(crate,new RoaringBitmap(crateCorpus.c));this.searchIndexEmptyDesc.set(crate,new RoaringBitmap(crateCorpus.e));let descIndex=0;let lastParamNames=[];let normalizedName=crate.indexOf("_")===-1?crate:crate.replace(/_/g,"");const crateRow={crate,ty:3,name:crate,path:"",descShard,descIndex,exactPath:"",desc:crateCorpus.doc,parent:undefined,type:null,paramNames:lastParamNames,id,word:crate,normalizedName,bitIndex:0,implDisambiguator:null,};this.nameTrie.insert(normalizedName,id,this.tailTable);id+=1;searchIndex.push(crateRow);currentIndex+=1;if(!this.searchIndexEmptyDesc.get(crate).contains(0)){descIndex+=1;}const itemTypes=crateCorpus.t;const itemNames=crateCorpus.n;const itemPaths=new Map(crateCorpus.q);const itemReexports=new Map(crateCorpus.r);const itemParentIdxDecoder=new VlqHexDecoder(crateCorpus.i,noop=>noop);const implDisambiguator=new Map(crateCorpus.b);const rawPaths=crateCorpus.p;const aliases=crateCorpus.a;const itemParamNames=new Map(crateCorpus.P);const lowercasePaths=[];const paths=[];const itemFunctionDecoder=new VlqHexDecoder(crateCorpus.f,buildFunctionSearchTypeCallback(paths,lowercasePaths),);let len=rawPaths.length;let lastPath=undef2null(itemPaths.get(0));for(let i=0;i{if(elem.length>idx&&elem[idx]!==undefined){const p=itemPaths.get(elem[idx]);if(p!==undefined){return p;}return if_not_found;}return if_null;};const path=elemPath(2,lastPath,null);const exactPath=elemPath(3,path,path);const unboxFlag=elem.length>4&&!!elem[4];lowercasePaths.push({ty,name:name.toLowerCase(),path,exactPath,unboxFlag});paths[i]={ty,name,path,exactPath,unboxFlag};}lastPath="";len=itemTypes.length;let lastName="";let lastWord="";for(let i=0;i=descShard.len&&!this.searchIndexEmptyDesc.get(crate).contains(bitIndex)){descShard={crate,shard:descShard.shard+1,start:descShard.start+descShard.len,len:itemDescShardDecoder.next(),promise:null,resolve:null,};descIndex=0;descShardList.push(descShard);}const name=itemNames[i]===""?lastName:itemNames[i];const word=itemNames[i]===""?lastWord:itemNames[i].toLowerCase();const pathU=itemPaths.get(i);const path=pathU!==undefined?pathU:lastPath;const paramNameString=itemParamNames.get(i);const paramNames=paramNameString!==undefined?paramNameString.split(","):lastParamNames;const type=itemFunctionDecoder.next();if(type!==null){if(type){const fp=this.functionTypeFingerprint.subarray(id*4,(id+1)*4);for(const t of type.inputs){this.buildFunctionTypeFingerprint(t,fp);}for(const t of type.output){this.buildFunctionTypeFingerprint(t,fp);}for(const w of type.where_clause){for(const t of w){this.buildFunctionTypeFingerprint(t,fp);}}}}const itemParentIdx=itemParentIdxDecoder.next();normalizedName=word.indexOf("_")===-1?word:word.replace(/_/g,"");const row={crate,ty:itemTypes.charCodeAt(i)-65,name,path,descShard,descIndex,exactPath:itemReexports.has(i)?itemPaths.get(itemReexports.get(i)):path,parent:itemParentIdx>0?paths[itemParentIdx-1]:undefined,type,paramNames,id,word,normalizedName,bitIndex,implDisambiguator:undef2null(implDisambiguator.get(i)),};this.nameTrie.insert(normalizedName,id,this.tailTable);id+=1;searchIndex.push(row);lastPath=row.path;lastParamNames=row.paramNames;if(!this.searchIndexEmptyDesc.get(crate).contains(bitIndex)){descIndex+=1;}lastName=name;lastWord=word;}if(aliases){allAliases.push([crate,aliases,currentIndex]);}currentIndex+=itemTypes.length;this.searchState.descShards.set(crate,descShardList);}for(const[crate,aliases,index]of allAliases){for(const[alias_name,alias_refs]of Object.entries(aliases)){if(!this.ALIASES.has(crate)){this.ALIASES.set(crate,new Map());}const word=alias_name.toLowerCase();const crate_alias_map=this.ALIASES.get(crate);if(!crate_alias_map.has(word)){crate_alias_map.set(word,[]);}const aliases_map=crate_alias_map.get(word);const normalizedName=word.indexOf("_")===-1?word:word.replace(/_/g,"");for(const alias of alias_refs){const originalIndex=alias+index;const original=searchIndex[originalIndex];const row={crate,name:alias_name,normalizedName,is_alias:true,ty:original.ty,type:original.type,paramNames:[],word,id,parent:undefined,original,path:"",implDisambiguator:original.implDisambiguator,descShard:original.descShard,descIndex:original.descIndex,bitIndex:original.bitIndex,};aliases_map.push(row);this.nameTrie.insert(normalizedName,id,this.tailTable);id+=1;searchIndex.push(row);}}}this.TYPES_POOL=new Map();return searchIndex;}static parseQuery(userQuery){function itemTypeFromName(typename){const index=itemTypes.findIndex(i=>i===typename);if(index<0){throw["Unknown type filter ",typename];}return index;}function convertTypeFilterOnElem(elem){if(typeof elem.typeFilter==="string"){let typeFilter=elem.typeFilter;if(typeFilter==="const"){typeFilter="constant";}elem.typeFilter=itemTypeFromName(typeFilter);}else{elem.typeFilter=NO_TYPE_FILTER;}for(const elem2 of elem.generics){convertTypeFilterOnElem(elem2);}for(const constraints of elem.bindings.values()){for(const constraint of constraints){convertTypeFilterOnElem(constraint);}}}function newParsedQuery(userQuery){return{userQuery,elems:[],returned:[],foundElems:0,totalElems:0,literalSearch:false,hasReturnArrow:false,error:null,correction:null,proposeCorrectionFrom:null,proposeCorrectionTo:null,typeFingerprint:new Uint32Array(4),};}function parseInput(query,parserState){let foundStopChar=true;while(parserState.pos"){if(isReturnArrow(parserState)){query.hasReturnArrow=true;break;}throw["Unexpected ",c," (did you mean ","->","?)"];}else if(parserState.pos>0){throw["Unexpected ",c," after ",parserState.userQuery[parserState.pos-1]];}throw["Unexpected ",c];}else if(c===" "){skipWhitespace(parserState);continue;}if(!foundStopChar){let extra=[];if(isLastElemGeneric(query.elems,parserState)){extra=[" after ",">"];}else if(prevIs(parserState,"\"")){throw["Cannot have more than one element if you use quotes"];}if(parserState.typeFilter!==null){throw["Expected ",","," or ","->",...extra,", found ",c,];}throw["Expected ",",",", ",":"," or ","->",...extra,", found ",c,];}const before=query.elems.length;getFilteredNextElem(query,parserState,query.elems,false);if(query.elems.length===before){parserState.pos+=1;}foundStopChar=false;}if(parserState.typeFilter!==null){throw["Unexpected ",":"," (expected path after type filter ",parserState.typeFilter+":",")",];}while(parserState.postypeof elem==="string")){query.error=err;}else{throw err;}return query;}if(!query.literalSearch){query.literalSearch=parserState.totalElems>1;}query.foundElems=query.elems.length+query.returned.length;query.totalElems=parserState.totalElems;return query;}async execQuery(origParsedQuery,filterCrates,currentCrate){const results_others=new Map(),results_in_args=new Map(),results_returned=new Map();const parsedQuery=origParsedQuery;const queryLen=parsedQuery.elems.reduce((acc,next)=>acc+next.pathLast.length,0)+parsedQuery.returned.reduce((acc,next)=>acc+next.pathLast.length,0);const maxEditDistance=Math.floor(queryLen/3);this.FOUND_ALIASES.clear();const genericSymbols=new Map();const convertNameToId=(elem,isAssocType)=>{const loweredName=elem.pathLast.toLowerCase();if(this.typeNameIdMap.has(loweredName)&&(isAssocType||!this.typeNameIdMap.get(loweredName).assocOnly)){elem.id=this.typeNameIdMap.get(loweredName).id;}else if(!parsedQuery.literalSearch){let match=null;let matchDist=maxEditDistance+1;let matchName="";for(const[name,{id,assocOnly}]of this.typeNameIdMap){const dist=Math.min(editDistance(name,loweredName,maxEditDistance),editDistance(name,elem.normalizedPathLast,maxEditDistance),);if(dist<=matchDist&&dist<=maxEditDistance&&(isAssocType||!assocOnly)){if(dist===matchDist&&matchName>name){continue;}match=id;matchDist=dist;matchName=name;}}if(match!==null){parsedQuery.correction=matchName;}elem.id=match;}if((elem.id===null&&parsedQuery.totalElems>1&&elem.typeFilter===-1&&elem.generics.length===0&&elem.bindings.size===0)||elem.typeFilter===TY_GENERIC){const id=genericSymbols.get(elem.normalizedPathLast);if(id!==undefined){elem.id=id;}else{elem.id=-(genericSymbols.size+1);genericSymbols.set(elem.normalizedPathLast,elem.id);}if(elem.typeFilter===-1&&elem.normalizedPathLast.length>=3){const maxPartDistance=Math.floor(elem.normalizedPathLast.length/3);let matchDist=maxPartDistance+1;let matchName="";for(const name of this.typeNameIdMap.keys()){const dist=editDistance(name,elem.normalizedPathLast,maxPartDistance,);if(dist<=matchDist&&dist<=maxPartDistance){if(dist===matchDist&&matchName>name){continue;}matchDist=dist;matchName=name;}}if(matchName!==""){parsedQuery.proposeCorrectionFrom=elem.name;parsedQuery.proposeCorrectionTo=matchName;}}elem.typeFilter=TY_GENERIC;}if(elem.generics.length>0&&elem.typeFilter===TY_GENERIC){parsedQuery.error=["Generic type parameter ",elem.name," does not accept generic parameters",];}for(const elem2 of elem.generics){convertNameToId(elem2);}elem.bindings=new Map(Array.from(elem.bindings.entries()).map(entry=>{const[name,constraints]=entry;if(!this.typeNameIdMap.has(name)){parsedQuery.error=["Type parameter ",name," does not exist",];return[0,[]];}for(const elem2 of constraints){convertNameToId(elem2,false);}return[this.typeNameIdMap.get(name).id,constraints];}),);};for(const elem of parsedQuery.elems){convertNameToId(elem,false);this.buildFunctionTypeFingerprint(elem,parsedQuery.typeFingerprint);}for(const elem of parsedQuery.returned){convertNameToId(elem,false);this.buildFunctionTypeFingerprint(elem,parsedQuery.typeFingerprint);}function createQueryResults(results_in_args,results_returned,results_others,parsedQuery){return{"in_args":results_in_args,"returned":results_returned,"others":results_others,"query":parsedQuery,};}const buildHrefAndPath=item=>{let displayPath;let href;if(item.is_alias){this.FOUND_ALIASES.add(item.word);item=item.original;}const type=itemTypes[item.ty];const name=item.name;let path=item.path;let exactPath=item.exactPath;if(type==="mod"){displayPath=path+"::";href=this.rootPath+path.replace(/::/g,"/")+"/"+name+"/index.html";}else if(type==="import"){displayPath=item.path+"::";href=this.rootPath+item.path.replace(/::/g,"/")+"/index.html#reexport."+name;}else if(type==="primitive"||type==="keyword"){displayPath="";exactPath="";href=this.rootPath+path.replace(/::/g,"/")+"/"+type+"."+name+".html";}else if(type==="externcrate"){displayPath="";href=this.rootPath+name+"/index.html";}else if(item.parent!==undefined){const myparent=item.parent;let anchor=type+"."+name;const parentType=itemTypes[myparent.ty];let pageType=parentType;let pageName=myparent.name;exactPath=`${myparent.exactPath}::${myparent.name}`;if(parentType==="primitive"){displayPath=myparent.name+"::";exactPath=myparent.name;}else if(type==="structfield"&&parentType==="variant"){const enumNameIdx=item.path.lastIndexOf("::");const enumName=item.path.substr(enumNameIdx+2);path=item.path.substr(0,enumNameIdx);displayPath=path+"::"+enumName+"::"+myparent.name+"::";anchor="variant."+myparent.name+".field."+name;pageType="enum";pageName=enumName;}else{displayPath=path+"::"+myparent.name+"::";}if(item.implDisambiguator!==null){anchor=item.implDisambiguator+"/"+anchor;}href=this.rootPath+path.replace(/::/g,"/")+"/"+pageType+"."+pageName+".html#"+anchor;}else{displayPath=item.path+"::";href=this.rootPath+item.path.replace(/::/g,"/")+"/"+type+"."+name+".html";}return[displayPath,href,`${exactPath}::${name}`];};function pathSplitter(path){const tmp=""+path.replace(/::/g,"::");if(tmp.endsWith("")){return tmp.slice(0,tmp.length-6);}return tmp;}const transformResults=(results,typeInfo)=>{const duplicates=new Set();const out=[];for(const result of results){if(result.id!==-1){const res=buildHrefAndPath(this.searchIndex[result.id]);const obj=Object.assign({parent:result.parent,type:result.type,dist:result.dist,path_dist:result.path_dist,index:result.index,desc:result.desc,item:result.item,displayPath:pathSplitter(res[0]),fullPath:"",href:"",displayTypeSignature:null,},this.searchIndex[result.id]);obj.fullPath=res[2]+"|"+obj.ty;if(duplicates.has(obj.fullPath)){continue;}if(obj.ty===TY_IMPORT&&duplicates.has(res[2])){continue;}if(duplicates.has(res[2]+"|"+TY_IMPORT)){continue;}duplicates.add(obj.fullPath);duplicates.add(res[2]);if(typeInfo!==null){obj.displayTypeSignature=this.formatDisplayTypeSignature(obj,typeInfo);}obj.href=res[1];out.push(obj);if(out.length>=MAX_RESULTS){break;}}}return out;};this.formatDisplayTypeSignature=async(obj,typeInfo)=>{const objType=obj.type;if(!objType){return{type:[],mappedNames:new Map(),whereClause:new Map()};}let fnInputs=null;let fnOutput=null;let mgens=null;if(typeInfo!=="elems"&&typeInfo!=="returned"){fnInputs=unifyFunctionTypes(objType.inputs,parsedQuery.elems,objType.where_clause,null,mgensScratch=>{fnOutput=unifyFunctionTypes(objType.output,parsedQuery.returned,objType.where_clause,mgensScratch,mgensOut=>{mgens=mgensOut;return true;},0,);return!!fnOutput;},0,);}else{const arr=typeInfo==="elems"?objType.inputs:objType.output;const highlighted=unifyFunctionTypes(arr,parsedQuery.elems,objType.where_clause,null,mgensOut=>{mgens=mgensOut;return true;},0,);if(typeInfo==="elems"){fnInputs=highlighted;}else{fnOutput=highlighted;}}if(!fnInputs){fnInputs=objType.inputs;}if(!fnOutput){fnOutput=objType.output;}const mappedNames=new Map();const whereClause=new Map();const fnParamNames=obj.paramNames||[];const queryParamNames=[];const remapQuery=queryElem=>{if(queryElem.id!==null&&queryElem.id<0){queryParamNames[-1-queryElem.id]=queryElem.name;}if(queryElem.generics.length>0){queryElem.generics.forEach(remapQuery);}if(queryElem.bindings.size>0){[...queryElem.bindings.values()].flat().forEach(remapQuery);}};parsedQuery.elems.forEach(remapQuery);parsedQuery.returned.forEach(remapQuery);const pushText=(fnType,result)=>{if(!!(result.length%2)===!!fnType.highlighted){result.push("");}else if(result.length===0&&!!fnType.highlighted){result.push("");result.push("");}result[result.length-1]+=fnType.name;};const writeHof=(fnType,result)=>{const hofOutput=fnType.bindings.get(this.typeNameIdOfOutput)||[];const hofInputs=fnType.generics;pushText(fnType,result);pushText({name:" (",highlighted:false},result);let needsComma=false;for(const fnType of hofInputs){if(needsComma){pushText({name:", ",highlighted:false},result);}needsComma=true;writeFn(fnType,result);}pushText({name:hofOutput.length===0?")":") -> ",highlighted:false,},result);if(hofOutput.length>1){pushText({name:"(",highlighted:false},result);}needsComma=false;for(const fnType of hofOutput){if(needsComma){pushText({name:", ",highlighted:false},result);}needsComma=true;writeFn(fnType,result);}if(hofOutput.length>1){pushText({name:")",highlighted:false},result);}};const writeSpecialPrimitive=(fnType,result)=>{if(fnType.id===this.typeNameIdOfArray||fnType.id===this.typeNameIdOfSlice||fnType.id===this.typeNameIdOfTuple||fnType.id===this.typeNameIdOfUnit){const[ob,sb]=fnType.id===this.typeNameIdOfArray||fnType.id===this.typeNameIdOfSlice?["[","]"]:["(",")"];pushText({name:ob,highlighted:fnType.highlighted},result);onEachBtwn(fnType.generics,nested=>writeFn(nested,result),()=>pushText({name:", ",highlighted:false},result),);pushText({name:sb,highlighted:fnType.highlighted},result);return true;}else if(fnType.id===this.typeNameIdOfReference){pushText({name:"&",highlighted:fnType.highlighted},result);let prevHighlighted=false;onEachBtwn(fnType.generics,value=>{prevHighlighted=!!value.highlighted;writeFn(value,result);},value=>pushText({name:" ",highlighted:prevHighlighted&&value.highlighted,},result),);return true;}else if(fnType.id===this.typeNameIdOfFn){writeHof(fnType,result);return true;}return false;};const writeFn=(fnType,result)=>{if(fnType.id!==null&&fnType.id<0){if(fnParamNames[-1-fnType.id]===""){const generics=fnType.generics.length>0?fnType.generics:objType.where_clause[-1-fnType.id];for(const nested of generics){writeFn(nested,result);}return;}else if(mgens){for(const[queryId,fnId]of mgens){if(fnId===fnType.id){mappedNames.set(queryParamNames[-1-queryId],fnParamNames[-1-fnType.id],);}}}pushText({name:fnParamNames[-1-fnType.id],highlighted:!!fnType.highlighted,},result);const where=[];onEachBtwn(fnType.generics,nested=>writeFn(nested,where),()=>pushText({name:" + ",highlighted:false},where),);if(where.length>0){whereClause.set(fnParamNames[-1-fnType.id],where);}}else{if(fnType.ty===TY_PRIMITIVE){if(writeSpecialPrimitive(fnType,result)){return;}}else if(fnType.ty===TY_TRAIT&&(fnType.id===this.typeNameIdOfFn||fnType.id===this.typeNameIdOfFnMut||fnType.id===this.typeNameIdOfFnOnce)){writeHof(fnType,result);return;}pushText(fnType,result);let hasBindings=false;if(fnType.bindings.size>0){onEachBtwn(fnType.bindings,([key,values])=>{const name=this.assocTypeIdNameMap.get(key);if(values.length===1&&values[0].id<0&&`${fnType.name}::${name}`===fnParamNames[-1-values[0].id]){for(const value of values){writeFn(value,[]);}return true;}if(!hasBindings){hasBindings=true;pushText({name:"<",highlighted:false},result);}pushText({name,highlighted:false},result);pushText({name:values.length!==1?"=(":"=",highlighted:false,},result);onEachBtwn(values||[],value=>writeFn(value,result),()=>pushText({name:" + ",highlighted:false},result),);if(values.length!==1){pushText({name:")",highlighted:false},result);}},()=>pushText({name:", ",highlighted:false},result),);}if(fnType.generics.length>0){pushText({name:hasBindings?", ":"<",highlighted:false},result);}onEachBtwn(fnType.generics,value=>writeFn(value,result),()=>pushText({name:", ",highlighted:false},result),);if(hasBindings||fnType.generics.length>0){pushText({name:">",highlighted:false},result);}}};const type=[];onEachBtwn(fnInputs,fnType=>writeFn(fnType,type),()=>pushText({name:", ",highlighted:false},type),);pushText({name:" -> ",highlighted:false},type);onEachBtwn(fnOutput,fnType=>writeFn(fnType,type),()=>pushText({name:", ",highlighted:false},type),);return{type,mappedNames,whereClause};};const sortResults=async(results,typeInfo,preferredCrate)=>{const userQuery=parsedQuery.userQuery;const normalizedUserQuery=parsedQuery.userQuery.toLowerCase();const isMixedCase=normalizedUserQuery!==userQuery;const result_list=[];const isReturnTypeQuery=parsedQuery.elems.length===0||typeInfo==="returned";for(const result of results.values()){result.item=this.searchIndex[result.id];result.word=this.searchIndex[result.id].word;if(isReturnTypeQuery){const resultItemType=result.item&&result.item.type;if(!resultItemType){continue;}const inputs=resultItemType.inputs;const where_clause=resultItemType.where_clause;if(containsTypeFromQuery(inputs,where_clause)){result.path_dist*=100;result.dist*=100;}}result_list.push(result);}result_list.sort((aaa,bbb)=>{let a;let b;if(isMixedCase){a=Number(aaa.item.name!==userQuery);b=Number(bbb.item.name!==userQuery);if(a!==b){return a-b;}}a=Number(aaa.word!==normalizedUserQuery);b=Number(bbb.word!==normalizedUserQuery);if(a!==b){return a-b;}a=Number(aaa.index<0);b=Number(bbb.index<0);if(a!==b){return a-b;}if(parsedQuery.hasReturnArrow){a=Number(!isFnLikeTy(aaa.item.ty));b=Number(!isFnLikeTy(bbb.item.ty));if(a!==b){return a-b;}}a=Number(aaa.path_dist);b=Number(bbb.path_dist);if(a!==b){return a-b;}a=Number(aaa.index);b=Number(bbb.index);if(a!==b){return a-b;}a=Number(aaa.dist);b=Number(bbb.dist);if(a!==b){return a-b;}a=Number(this.searchIndexDeprecated.get(aaa.item.crate).contains(aaa.item.bitIndex),);b=Number(this.searchIndexDeprecated.get(bbb.item.crate).contains(bbb.item.bitIndex),);if(a!==b){return a-b;}a=Number(aaa.item.crate!==preferredCrate);b=Number(bbb.item.crate!==preferredCrate);if(a!==b){return a-b;}a=Number(aaa.word.length);b=Number(bbb.word.length);if(a!==b){return a-b;}let aw=aaa.word;let bw=bbb.word;if(aw!==bw){return(aw>bw?+1:-1);}a=Number(this.searchIndexEmptyDesc.get(aaa.item.crate).contains(aaa.item.bitIndex),);b=Number(this.searchIndexEmptyDesc.get(bbb.item.crate).contains(bbb.item.bitIndex),);if(a!==b){return a-b;}a=Number(aaa.item.ty);b=Number(bbb.item.ty);if(a!==b){return a-b;}aw=aaa.item.path;bw=bbb.item.path;if(aw!==bw){return(aw>bw?+1:-1);}return 0;});return transformResults(result_list,typeInfo);};function unifyFunctionTypes(fnTypesIn,queryElems,whereClause,mgensIn,solutionCb,unboxingDepth,){if(unboxingDepth>=UNBOXING_LIMIT){return null;}const mgens=mgensIn===null?null:new Map(mgensIn);if(queryElems.length===0){return solutionCb(mgens)?fnTypesIn:null;}if(!fnTypesIn||fnTypesIn.length===0){return null;}const ql=queryElems.length;const fl=fnTypesIn.length;if(ql===1&&queryElems[0].generics.length===0&&queryElems[0].bindings.size===0){const queryElem=queryElems[0];for(const[i,fnType]of fnTypesIn.entries()){if(!unifyFunctionTypeIsMatchCandidate(fnType,queryElem,mgens)){continue;}if(fnType.id!==null&&fnType.id<0&&queryElem.id!==null&&queryElem.id<0){if(mgens&&mgens.has(queryElem.id)&&mgens.get(queryElem.id)!==fnType.id){continue;}const mgensScratch=new Map(mgens);mgensScratch.set(queryElem.id,fnType.id);if(!solutionCb||solutionCb(mgensScratch)){const highlighted=[...fnTypesIn];highlighted[i]=Object.assign({highlighted:true,},fnType,{generics:whereClause[-1-fnType.id],});return highlighted;}}else if(solutionCb(mgens?new Map(mgens):null)){const highlighted=[...fnTypesIn];highlighted[i]=Object.assign({highlighted:true,},fnType,{generics:unifyGenericTypes(fnType.generics,queryElem.generics,whereClause,mgens?new Map(mgens):null,solutionCb,unboxingDepth,)||fnType.generics,});return highlighted;}}for(const[i,fnType]of fnTypesIn.entries()){if(!unifyFunctionTypeIsUnboxCandidate(fnType,queryElem,whereClause,mgens,unboxingDepth+1,)){continue;}if(fnType.id<0){const highlightedGenerics=unifyFunctionTypes(whereClause[(-fnType.id)-1],queryElems,whereClause,mgens,solutionCb,unboxingDepth+1,);if(highlightedGenerics){const highlighted=[...fnTypesIn];highlighted[i]=Object.assign({highlighted:true,},fnType,{generics:highlightedGenerics,});return highlighted;}}else{const highlightedGenerics=unifyFunctionTypes([...Array.from(fnType.bindings.values()).flat(),...fnType.generics],queryElems,whereClause,mgens?new Map(mgens):null,solutionCb,unboxingDepth+1,);if(highlightedGenerics){const highlighted=[...fnTypesIn];highlighted[i]=Object.assign({},fnType,{generics:highlightedGenerics,bindings:new Map([...fnType.bindings.entries()].map(([k,v])=>{return[k,highlightedGenerics.splice(0,v.length)];})),});return highlighted;}}}return null;}const fnTypes=fnTypesIn.slice();const flast=fl-1;const qlast=ql-1;const queryElem=queryElems[qlast];let queryElemsTmp=null;for(let i=flast;i>=0;i-=1){const fnType=fnTypes[i];if(!unifyFunctionTypeIsMatchCandidate(fnType,queryElem,mgens)){continue;}let mgensScratch;if(fnType.id!==null&&queryElem.id!==null&&fnType.id<0){mgensScratch=new Map(mgens);if(mgensScratch.has(queryElem.id)&&mgensScratch.get(queryElem.id)!==fnType.id){continue;}mgensScratch.set(queryElem.id,fnType.id);}else{mgensScratch=mgens;}fnTypes[i]=fnTypes[flast];fnTypes.length=flast;if(!queryElemsTmp){queryElemsTmp=queryElems.slice(0,qlast);}let unifiedGenerics=[];let unifiedGenericsMgens=null;const passesUnification=unifyFunctionTypes(fnTypes,queryElemsTmp,whereClause,mgensScratch,mgensScratch=>{if(fnType.generics.length===0&&queryElem.generics.length===0&&fnType.bindings.size===0&&queryElem.bindings.size===0){return solutionCb(mgensScratch);}const solution=unifyFunctionTypeCheckBindings(fnType,queryElem,whereClause,mgensScratch,unboxingDepth,);if(!solution){return false;}const simplifiedGenerics=solution.simplifiedGenerics;for(const simplifiedMgens of solution.mgens){unifiedGenerics=unifyGenericTypes(simplifiedGenerics,queryElem.generics,whereClause,simplifiedMgens,solutionCb,unboxingDepth,);if(unifiedGenerics!==null){unifiedGenericsMgens=simplifiedMgens;return true;}}return false;},unboxingDepth,);if(passesUnification){passesUnification.length=fl;passesUnification[flast]=passesUnification[i];passesUnification[i]=Object.assign({},fnType,{highlighted:true,generics:unifiedGenerics,bindings:new Map([...fnType.bindings.entries()].map(([k,v])=>{return[k,queryElem.bindings.has(k)?unifyFunctionTypes(v,queryElem.bindings.get(k),whereClause,unifiedGenericsMgens,solutionCb,unboxingDepth,):unifiedGenerics.splice(0,v.length)];})),});return passesUnification;}fnTypes[flast]=fnTypes[i];fnTypes[i]=fnType;fnTypes.length=fl;}for(let i=flast;i>=0;i-=1){const fnType=fnTypes[i];if(!unifyFunctionTypeIsUnboxCandidate(fnType,queryElem,whereClause,mgens,unboxingDepth+1,)){continue;}const generics=fnType.id!==null&&fnType.id<0?whereClause[(-fnType.id)-1]:fnType.generics;const bindings=fnType.bindings?Array.from(fnType.bindings.values()).flat():[];const passesUnification=unifyFunctionTypes(fnTypes.toSpliced(i,1,...bindings,...generics),queryElems,whereClause,mgens,solutionCb,unboxingDepth+1,);if(passesUnification){const highlightedGenerics=passesUnification.slice(i,i+generics.length+bindings.length,);const highlightedFnType=Object.assign({},fnType,{generics:highlightedGenerics,bindings:new Map([...fnType.bindings.entries()].map(([k,v])=>{return[k,highlightedGenerics.splice(0,v.length)];})),});return passesUnification.toSpliced(i,generics.length+bindings.length,highlightedFnType,);}}return null;}function unifyGenericTypes(fnTypesIn,queryElems,whereClause,mgensIn,solutionCb,unboxingDepth,){if(unboxingDepth>=UNBOXING_LIMIT){return null;}const mgens=mgensIn===null?null:new Map(mgensIn);if(queryElems.length===0){return solutionCb(mgens)?fnTypesIn:null;}if(!fnTypesIn||fnTypesIn.length===0){return null;}const fnType=fnTypesIn[0];const queryElem=queryElems[0];if(unifyFunctionTypeIsMatchCandidate(fnType,queryElem,mgens)){if(fnType.id!==null&&fnType.id<0&&queryElem.id!==null&&queryElem.id<0){if(!mgens||!mgens.has(queryElem.id)||mgens.get(queryElem.id)===fnType.id){const mgensScratch=new Map(mgens);mgensScratch.set(queryElem.id,fnType.id);const fnTypesRemaining=unifyGenericTypes(fnTypesIn.slice(1),queryElems.slice(1),whereClause,mgensScratch,solutionCb,unboxingDepth,);if(fnTypesRemaining){const highlighted=[fnType,...fnTypesRemaining];highlighted[0]=Object.assign({highlighted:true,},fnType,{generics:whereClause[-1-fnType.id],});return highlighted;}}}else{let unifiedGenerics;const fnTypesRemaining=unifyGenericTypes(fnTypesIn.slice(1),queryElems.slice(1),whereClause,mgens,mgensScratch=>{const solution=unifyFunctionTypeCheckBindings(fnType,queryElem,whereClause,mgensScratch,unboxingDepth,);if(!solution){return false;}const simplifiedGenerics=solution.simplifiedGenerics;for(const simplifiedMgens of solution.mgens){unifiedGenerics=unifyGenericTypes(simplifiedGenerics,queryElem.generics,whereClause,simplifiedMgens,solutionCb,unboxingDepth,);if(unifiedGenerics!==null){return true;}}},unboxingDepth,);if(fnTypesRemaining){const highlighted=[fnType,...fnTypesRemaining];highlighted[0]=Object.assign({highlighted:true,},fnType,{generics:unifiedGenerics||fnType.generics,});return highlighted;}}}if(unifyFunctionTypeIsUnboxCandidate(fnType,queryElem,whereClause,mgens,unboxingDepth+1,)){let highlightedRemaining;if(fnType.id!==null&&fnType.id<0){const highlightedGenerics=unifyFunctionTypes(whereClause[(-fnType.id)-1],[queryElem],whereClause,mgens,mgensScratch=>{const hl=unifyGenericTypes(fnTypesIn.slice(1),queryElems.slice(1),whereClause,mgensScratch,solutionCb,unboxingDepth,);if(hl){highlightedRemaining=hl;}return hl;},unboxingDepth+1,);if(highlightedGenerics){return[Object.assign({highlighted:true,},fnType,{generics:highlightedGenerics,}),...highlightedRemaining];}}else{const highlightedGenerics=unifyGenericTypes([...Array.from(fnType.bindings.values()).flat(),...fnType.generics,],[queryElem],whereClause,mgens,mgensScratch=>{const hl=unifyGenericTypes(fnTypesIn.slice(1),queryElems.slice(1),whereClause,mgensScratch,solutionCb,unboxingDepth,);if(hl){highlightedRemaining=hl;}return hl;},unboxingDepth+1,);if(highlightedGenerics){return[Object.assign({},fnType,{generics:highlightedGenerics,bindings:new Map([...fnType.bindings.entries()].map(([k,v])=>{return[k,highlightedGenerics.splice(0,v.length)];})),}),...highlightedRemaining];}}}return null;}const unifyFunctionTypeIsMatchCandidate=(fnType,queryElem,mgensIn)=>{if(!typePassesFilter(queryElem.typeFilter,fnType.ty)){return false;}if(fnType.id!==null&&fnType.id<0&&queryElem.id!==null&&queryElem.id<0){if(mgensIn&&mgensIn.has(queryElem.id)&&mgensIn.get(queryElem.id)!==fnType.id){return false;}return true;}else{if(queryElem.id===this.typeNameIdOfArrayOrSlice&&(fnType.id===this.typeNameIdOfSlice||fnType.id===this.typeNameIdOfArray)){}else if(queryElem.id===this.typeNameIdOfTupleOrUnit&&(fnType.id===this.typeNameIdOfTuple||fnType.id===this.typeNameIdOfUnit)){}else if(queryElem.id===this.typeNameIdOfHof&&(fnType.id===this.typeNameIdOfFn||fnType.id===this.typeNameIdOfFnMut||fnType.id===this.typeNameIdOfFnOnce)){}else if(fnType.id!==queryElem.id||queryElem.id===null){return false;}if((fnType.generics.length+fnType.bindings.size)===0&&queryElem.generics.length!==0){return false;}if(fnType.bindings.size0){const fnTypePath=fnType.path!==undefined&&fnType.path!==null?fnType.path.split("::"):[];if(queryElemPathLength>fnTypePath.length){return false;}let i=0;for(const path of fnTypePath){if(path===queryElem.pathWithoutLast[i]){i+=1;if(i>=queryElemPathLength){break;}}}if(i0){let mgensSolutionSet=[mgensIn];for(const[name,constraints]of queryElem.bindings.entries()){if(mgensSolutionSet.length===0){return false;}if(!fnType.bindings.has(name)){return false;}const fnTypeBindings=fnType.bindings.get(name);mgensSolutionSet=mgensSolutionSet.flatMap(mgens=>{const newSolutions=[];unifyFunctionTypes(fnTypeBindings,constraints,whereClause,mgens,newMgens=>{newSolutions.push(newMgens);return false;},unboxingDepth,);return newSolutions;});}if(mgensSolutionSet.length===0){return false;}const binds=Array.from(fnType.bindings.entries()).flatMap(entry=>{const[name,constraints]=entry;if(queryElem.bindings.has(name)){return[];}else{return constraints;}});if(simplifiedGenerics.length>0){simplifiedGenerics=[...binds,...simplifiedGenerics];}else{simplifiedGenerics=binds;}return{simplifiedGenerics,mgens:mgensSolutionSet};}return{simplifiedGenerics,mgens:[mgensIn]};}function unifyFunctionTypeIsUnboxCandidate(fnType,queryElem,whereClause,mgens,unboxingDepth,){if(unboxingDepth>=UNBOXING_LIMIT){return false;}if(fnType.id!==null&&fnType.id<0){if(!whereClause){return false;}return checkIfInList(whereClause[(-fnType.id)-1],queryElem,whereClause,mgens,unboxingDepth,);}else if(fnType.unboxFlag&&(fnType.generics.length>0||fnType.bindings.size>0)){const simplifiedGenerics=[...fnType.generics,...Array.from(fnType.bindings.values()).flat(),];return checkIfInList(simplifiedGenerics,queryElem,whereClause,mgens,unboxingDepth,);}return false;}function containsTypeFromQuery(list,where_clause){if(!list)return false;for(const ty of parsedQuery.returned){if(ty.id!==null&&ty.id<0){continue;}if(checkIfInList(list,ty,where_clause,null,0)){return true;}}for(const ty of parsedQuery.elems){if(ty.id!==null&&ty.id<0){continue;}if(checkIfInList(list,ty,where_clause,null,0)){return true;}}return false;}function checkIfInList(list,elem,whereClause,mgens,unboxingDepth){for(const entry of list){if(checkType(entry,elem,whereClause,mgens,unboxingDepth)){return true;}}return false;}const checkType=(row,elem,whereClause,mgens,unboxingDepth)=>{if(unboxingDepth>=UNBOXING_LIMIT){return false;}if(row.id!==null&&elem.id!==null&&row.id>0&&elem.id>0&&elem.pathWithoutLast.length===0&&row.generics.length===0&&elem.generics.length===0&&row.bindings.size===0&&elem.bindings.size===0&&elem.id!==this.typeNameIdOfArrayOrSlice&&elem.id!==this.typeNameIdOfHof&&elem.id!==this.typeNameIdOfTupleOrUnit){return row.id===elem.id&&typePassesFilter(elem.typeFilter,row.ty);}else{return unifyFunctionTypes([row],[elem],whereClause,mgens,()=>true,unboxingDepth,);}};const checkTypeMgensForConflict=mgens=>{if(!mgens){return true;}const fnTypes=new Set();for(const[_qid,fid]of mgens){if(fnTypes.has(fid)){return false;}fnTypes.add(fid);}return true;};function checkPath(contains,ty){if(contains.length===0){return 0;}const maxPathEditDistance=Math.floor(contains.reduce((acc,next)=>acc+next.length,0)/3,);let ret_dist=maxPathEditDistance+1;const path=ty.path.split("::");if(ty.parent&&ty.parent.name){path.push(ty.parent.name.toLowerCase());}const length=path.length;const clength=contains.length;pathiter:for(let i=length-clength;i>=0;i-=1){let dist_total=0;for(let x=0;xmaxPathEditDistance){continue pathiter;}dist_total+=dist;}}ret_dist=Math.min(ret_dist,Math.round(dist_total/clength));}return ret_dist>maxPathEditDistance?null:ret_dist;}function typePassesFilter(filter,type){if(filter<=NO_TYPE_FILTER||filter===type)return true;const name=itemTypes[type];switch(itemTypes[filter]){case"constant":return name==="associatedconstant";case"fn":return name==="method"||name==="tymethod";case"type":return name==="primitive"||name==="associatedtype";case"trait":return name==="traitalias";}return false;}const handleAliases=async(ret,query,filterCrates,currentCrate)=>{const lowerQuery=query.toLowerCase();if(this.FOUND_ALIASES.has(lowerQuery)){return;}this.FOUND_ALIASES.add(lowerQuery);const aliases=[];const crateAliases=[];if(filterCrates!==null){if(this.ALIASES.has(filterCrates)&&this.ALIASES.get(filterCrates).has(lowerQuery)){const query_aliases=this.ALIASES.get(filterCrates).get(lowerQuery);for(const alias of query_aliases){aliases.push(alias);}}}else{for(const[crate,crateAliasesIndex]of this.ALIASES){if(crateAliasesIndex.has(lowerQuery)){const pushTo=crate===currentCrate?crateAliases:aliases;const query_aliases=crateAliasesIndex.get(lowerQuery);for(const alias of query_aliases){pushTo.push(alias);}}}}const sortFunc=(aaa,bbb)=>{if(aaa.original.path{alias={...alias};const res=buildHrefAndPath(alias);alias.displayPath=pathSplitter(res[0]);alias.fullPath=alias.displayPath+alias.name;alias.href=res[1];ret.others.unshift(alias);if(ret.others.length>MAX_RESULTS){ret.others.pop();}};aliases.forEach(pushFunc);crateAliases.forEach(pushFunc);};function addIntoResults(results,fullId,id,index,dist,path_dist,maxEditDistance){if(dist<=maxEditDistance||index!==-1){if(results.has(fullId)){const result=results.get(fullId);if(result===undefined||result.dontValidate||result.dist<=dist){return;}}results.set(fullId,{id:id,index:index,dontValidate:parsedQuery.literalSearch,dist:dist,path_dist:path_dist,});}}function handleArgs(row,pos,results){if(!row||(filterCrates!==null&&row.crate!==filterCrates)){return;}const rowType=row.type;if(!rowType){return;}const tfpDist=compareTypeFingerprints(row.id,parsedQuery.typeFingerprint,);if(tfpDist===null){return;}if(results.size>=MAX_RESULTS&&tfpDist>results.max_dist){return;}if(!unifyFunctionTypes(rowType.inputs,parsedQuery.elems,rowType.where_clause,null,mgens=>{return unifyFunctionTypes(rowType.output,parsedQuery.returned,rowType.where_clause,mgens,checkTypeMgensForConflict,0,);},0,)){return;}results.max_dist=Math.max(results.max_dist||0,tfpDist);addIntoResults(results,row.id,pos,0,tfpDist,0,Number.MAX_VALUE);}const compareTypeFingerprints=(fullId,queryFingerprint)=>{const fh0=this.functionTypeFingerprint[fullId*4];const fh1=this.functionTypeFingerprint[(fullId*4)+1];const fh2=this.functionTypeFingerprint[(fullId*4)+2];const[qh0,qh1,qh2]=queryFingerprint;const[in0,in1,in2]=[fh0&qh0,fh1&qh1,fh2&qh2];if((in0 ^ qh0)||(in1 ^ qh1)||(in2 ^ qh2)){return null;}return this.functionTypeFingerprint[(fullId*4)+3];};const innerRunQuery=()=>{if(parsedQuery.foundElems===1&&!parsedQuery.hasReturnArrow){const elem=parsedQuery.elems[0];const handleNameSearch=id=>{const row=this.searchIndex[id];if(!typePassesFilter(elem.typeFilter,row.ty)||(filterCrates!==null&&row.crate!==filterCrates)){return;}let pathDist=0;if(elem.fullPath.length>1){const maybePathDist=checkPath(elem.pathWithoutLast,row);if(maybePathDist===null){return;}pathDist=maybePathDist;}if(parsedQuery.literalSearch){if(row.word===elem.pathLast){addIntoResults(results_others,row.id,id,0,0,pathDist,0);}}else{addIntoResults(results_others,row.id,id,row.normalizedName.indexOf(elem.normalizedPathLast),editDistance(row.normalizedName,elem.normalizedPathLast,maxEditDistance,),pathDist,maxEditDistance,);}};if(elem.normalizedPathLast!==""){const last=elem.normalizedPathLast;for(const id of this.nameTrie.search(last,this.tailTable)){handleNameSearch(id);}}const length=this.searchIndex.length;for(let i=0,nSearchIndex=length;i0){const sortQ=(a,b)=>{const ag=a.generics.length===0&&a.bindings.size===0;const bg=b.generics.length===0&&b.bindings.size===0;if(ag!==bg){return+ag-+bg;}const ai=a.id!==null&&a.id>0;const bi=b.id!==null&&b.id>0;return+ai-+bi;};parsedQuery.elems.sort(sortQ);parsedQuery.returned.sort(sortQ);for(let i=0,nSearchIndex=this.searchIndex.length;i{const descs=await Promise.all(list.map(result=>{return this.searchIndexEmptyDesc.get(result.crate).contains(result.bitIndex)?"":this.searchState.loadDesc(result);}));for(const[i,result]of list.entries()){result.desc=descs[i];}}));if(parsedQuery.error!==null&&ret.others.length!==0){ret.query.error=null;}return ret;}}let rawSearchIndex;let docSearch;const longItemTypes=["keyword","primitive type","module","extern crate","re-export","struct","enum","function","type alias","static","trait","","trait method","method","struct field","enum variant","macro","assoc type","constant","assoc const","union","foreign type","existential type","attribute macro","derive macro","trait alias",];let currentResults;function printTab(nb){let iter=0;let foundCurrentTab=false;let foundCurrentResultSet=false;onEachLazy(document.getElementById("search-tabs").childNodes,elem=>{if(nb===iter){addClass(elem,"selected");foundCurrentTab=true;}else{removeClass(elem,"selected");}iter+=1;});const isTypeSearch=(nb>0||iter===1);iter=0;onEachLazy(document.getElementById("results").childNodes,elem=>{if(nb===iter){addClass(elem,"active");foundCurrentResultSet=true;}else{removeClass(elem,"active");}iter+=1;});if(foundCurrentTab&&foundCurrentResultSet){searchState.currentTab=nb;const correctionsElem=document.getElementsByClassName("search-corrections");if(isTypeSearch){removeClass(correctionsElem[0],"hidden");}else{addClass(correctionsElem[0],"hidden");}}else if(nb!==0){printTab(0);}}function buildUrl(search,filterCrates){let extra="?search="+encodeURIComponent(search);if(filterCrates!==null){extra+="&filter-crate="+encodeURIComponent(filterCrates);}return getNakedUrl()+extra+window.location.hash;}function getFilterCrates(){const elem=document.getElementById("crate-search");if(elem&&elem.value!=="all crates"&&window.searchIndex.has(elem.value)){return elem.value;}return null;}function nextTab(direction){const next=(searchState.currentTab+direction+3)%searchState.focusedByTab.length;searchState.focusedByTab[searchState.currentTab]=document.activeElement;printTab(next);focusSearchResult();}function focusSearchResult(){const target=searchState.focusedByTab[searchState.currentTab]||document.querySelectorAll(".search-results.active a").item(0)||document.querySelectorAll("#search-tabs button").item(searchState.currentTab);searchState.focusedByTab[searchState.currentTab]=null;if(target){target.focus();}}async function addTab(array,query,display){const extraClass=display?" active":"";const output=document.createElement(array.length===0&&query.error===null?"div":"ul",);if(array.length>0){output.className="search-results "+extraClass;const lis=Promise.all(array.map(async item=>{const name=item.is_alias?item.original.name:item.name;const type=itemTypes[item.ty];const longType=longItemTypes[item.ty];const typeName=longType.length!==0?`${longType}`:"?";const link=document.createElement("a");link.className="result-"+type;link.href=item.href;const resultName=document.createElement("span");resultName.className="result-name";resultName.insertAdjacentHTML("beforeend",`${typeName}`);link.appendChild(resultName);let alias=" ";if(item.is_alias){alias=`
\ +${item.name} - see \ +
`;}resultName.insertAdjacentHTML("beforeend",`
${alias}\ +${item.displayPath}${name}\ +
`);const description=document.createElement("div");description.className="desc";description.insertAdjacentHTML("beforeend",item.desc);if(item.displayTypeSignature){const{type,mappedNames,whereClause}=await item.displayTypeSignature;const displayType=document.createElement("div");type.forEach((value,index)=>{if(index%2!==0){const highlight=document.createElement("strong");highlight.appendChild(document.createTextNode(value));displayType.appendChild(highlight);}else{displayType.appendChild(document.createTextNode(value));}});if(mappedNames.size>0||whereClause.size>0){let addWhereLineFn=()=>{const line=document.createElement("div");line.className="where";line.appendChild(document.createTextNode("where"));displayType.appendChild(line);addWhereLineFn=()=>{};};for(const[qname,name]of mappedNames){if(name===qname){continue;}addWhereLineFn();const line=document.createElement("div");line.className="where";line.appendChild(document.createTextNode(` ${qname} matches `));const lineStrong=document.createElement("strong");lineStrong.appendChild(document.createTextNode(name));line.appendChild(lineStrong);displayType.appendChild(line);}for(const[name,innerType]of whereClause){if(innerType.length<=1){continue;}addWhereLineFn();const line=document.createElement("div");line.className="where";line.appendChild(document.createTextNode(` ${name}: `));innerType.forEach((value,index)=>{if(index%2!==0){const highlight=document.createElement("strong");highlight.appendChild(document.createTextNode(value));line.appendChild(highlight);}else{line.appendChild(document.createTextNode(value));}});displayType.appendChild(line);}}displayType.className="type-signature";link.appendChild(displayType);}link.appendChild(description);return link;}));lis.then(lis=>{for(const li of lis){output.appendChild(li);}});}else if(query.error===null){const dlroChannel=`https://doc.rust-lang.org/${getVar("channel")}`;output.className="search-failed"+extraClass;output.innerHTML="No results :(
"+"Try on DuckDuckGo?

"+"Or try looking in one of these:";}return output;}function makeTabHeader(tabNb,text,nbElems){const fmtNbElems=nbElems<10?`\u{2007}(${nbElems})\u{2007}\u{2007}`:nbElems<100?`\u{2007}(${nbElems})\u{2007}`:`\u{2007}(${nbElems})`;if(searchState.currentTab===tabNb){return"";}return"";}async function showResults(results,go_to_first,filterCrates){const search=searchState.outputElement();if(go_to_first||(results.others.length===1&&getSettingValue("go-to-only-result")==="true")){window.onunload=()=>{};searchState.removeQueryParameters();const elem=document.createElement("a");elem.href=results.others[0].href;removeClass(elem,"active");document.body.appendChild(elem);elem.click();return;}if(results.query===undefined){results.query=DocSearch.parseQuery(searchState.input.value);}currentResults=results.query.userQuery;let currentTab=searchState.currentTab;if((currentTab===0&&results.others.length===0)||(currentTab===1&&results.in_args.length===0)||(currentTab===2&&results.returned.length===0)){if(results.others.length!==0){currentTab=0;}else if(results.in_args.length){currentTab=1;}else if(results.returned.length){currentTab=2;}}let crates="";if(rawSearchIndex.size>1){crates="
in 
"+"
";}let output=`
\ +

Results

${crates}
`;if(results.query.error!==null){const error=results.query.error;error.forEach((value,index)=>{value=value.split("<").join("<").split(">").join(">");if(index%2!==0){error[index]=`${value.replaceAll(" ", " ")}`;}else{error[index]=value;}});output+=`

Query parser error: "${error.join("")}".

`;output+="
"+makeTabHeader(0,"In Names",results.others.length)+"
";currentTab=0;}else if(results.query.foundElems<=1&&results.query.returned.length===0){output+="
"+makeTabHeader(0,"In Names",results.others.length)+makeTabHeader(1,"In Parameters",results.in_args.length)+makeTabHeader(2,"In Return Types",results.returned.length)+"
";}else{const signatureTabTitle=results.query.elems.length===0?"In Function Return Types":results.query.returned.length===0?"In Function Parameters":"In Function Signatures";output+="
"+makeTabHeader(0,signatureTabTitle,results.others.length)+"
";currentTab=0;}if(results.query.correction!==null){const orig=results.query.returned.length>0?results.query.returned[0].name:results.query.elems[0].name;output+="

"+`Type "${orig}" not found. `+"Showing results for closest type name "+`"${results.query.correction}" instead.

`;}if(results.query.proposeCorrectionFrom!==null){const orig=results.query.proposeCorrectionFrom;const targ=results.query.proposeCorrectionTo;output+="

"+`Type "${orig}" not found and used as generic parameter. `+`Consider searching for "${targ}" instead.

`;}const[ret_others,ret_in_args,ret_returned]=await Promise.all([addTab(results.others,results.query,currentTab===0),addTab(results.in_args,results.query,currentTab===1),addTab(results.returned,results.query,currentTab===2),]);const resultsElem=document.createElement("div");resultsElem.id="results";resultsElem.appendChild(ret_others);resultsElem.appendChild(ret_in_args);resultsElem.appendChild(ret_returned);search.innerHTML=output;if(searchState.rustdocToolbar){search.querySelector(".main-heading").appendChild(searchState.rustdocToolbar);}const crateSearch=document.getElementById("crate-search");if(crateSearch){crateSearch.addEventListener("input",updateCrate);}search.appendChild(resultsElem);searchState.showResults(search);const elems=document.getElementById("search-tabs").childNodes;searchState.focusedByTab=[];let i=0;for(const elem of elems){const j=i;elem.onclick=()=>printTab(j);searchState.focusedByTab.push(null);i+=1;}printTab(currentTab);}function updateSearchHistory(url){if(!browserSupportsHistoryApi()){return;}const params=searchState.getQueryStringParams();if(!history.state&&!params.search){history.pushState(null,"",url);}else{history.replaceState(null,"",url);}}async function search(forced){const query=DocSearch.parseQuery(searchState.input.value.trim());let filterCrates=getFilterCrates();if(!forced&&query.userQuery===currentResults){if(query.userQuery.length>0){putBackSearch();}return;}searchState.setLoadingSearch();const params=searchState.getQueryStringParams();if(filterCrates===null&¶ms["filter-crate"]!==undefined){filterCrates=params["filter-crate"];}searchState.title="\""+query.userQuery+"\" Search - Rust";updateSearchHistory(buildUrl(query.userQuery,filterCrates));await showResults(await docSearch.execQuery(query,filterCrates,window.currentCrate),params.go_to_first,filterCrates);}function onSearchSubmit(e){e.preventDefault();searchState.clearInputTimeout();search();}function putBackSearch(){const search_input=searchState.input;if(!searchState.input){return;}if(search_input.value!==""&&!searchState.isDisplayed()){searchState.showResults();if(browserSupportsHistoryApi()){history.replaceState(null,"",buildUrl(search_input.value,getFilterCrates()));}document.title=searchState.title;}}function registerSearchEvents(){const params=searchState.getQueryStringParams();if(searchState.input.value===""){searchState.input.value=params.search||"";}const searchAfter500ms=()=>{searchState.clearInputTimeout();if(searchState.input.value.length===0){searchState.hideResults();}else{searchState.timeout=setTimeout(search,500);}};searchState.input.onkeyup=searchAfter500ms;searchState.input.oninput=searchAfter500ms;document.getElementsByClassName("search-form")[0].onsubmit=onSearchSubmit;searchState.input.onchange=e=>{if(e.target!==document.activeElement){return;}searchState.clearInputTimeout();setTimeout(search,0);};searchState.input.onpaste=searchState.input.onchange;searchState.outputElement().addEventListener("keydown",e=>{if(e.altKey||e.ctrlKey||e.shiftKey||e.metaKey){return;}if(e.which===38){const previous=document.activeElement.previousElementSibling;if(previous){previous.focus();}else{searchState.focus();}e.preventDefault();}else if(e.which===40){const next=document.activeElement.nextElementSibling;if(next){next.focus();}const rect=document.activeElement.getBoundingClientRect();if(window.innerHeight-rect.bottom{if(e.which===40){focusSearchResult();e.preventDefault();}});searchState.input.addEventListener("focus",()=>{putBackSearch();});searchState.input.addEventListener("blur",()=>{if(window.searchState.input){window.searchState.input.placeholder=window.searchState.origPlaceholder;}});if(browserSupportsHistoryApi()){const previousTitle=document.title;window.addEventListener("popstate",e=>{const params=searchState.getQueryStringParams();document.title=previousTitle;currentResults=null;if(params.search&¶ms.search.length>0){searchState.input.value=params.search;e.preventDefault();search();}else{searchState.input.value="";searchState.hideResults();}});}window.onpageshow=()=>{const qSearch=searchState.getQueryStringParams().search;if(searchState.input.value===""&&qSearch){searchState.input.value=qSearch;}search();};}function updateCrate(ev){if(ev.target.value==="all crates"){const query=searchState.input.value.trim();updateSearchHistory(buildUrl(query,null));}currentResults=null;search(true);}class ParametricDescription{constructor(w,n,minErrors){this.w=w;this.n=n;this.minErrors=minErrors;}isAccept(absState){const state=Math.floor(absState/(this.w+1));const offset=absState%(this.w+1);return this.w-offset+this.minErrors[state]<=this.n;}getPosition(absState){return absState%(this.w+1);}getVector(name,charCode,pos,end){let vector=0;for(let i=pos;i>5;const bitStart=bitLoc&31;if(bitStart+bitsPerValue<=32){return((data[dataLoc]>>bitStart)&this.MASKS[bitsPerValue-1]);}else{const part=32-bitStart;return ~~(((data[dataLoc]>>bitStart)&this.MASKS[part-1])+((data[1+dataLoc]&this.MASKS[bitsPerValue-part-1])<{const settingId=toggle.id;const settingValue=getSettingValue(settingId);if(settingValue!==null){toggle.checked=settingValue==="true";}toggle.onchange=()=>{changeSetting(toggle.id,toggle.checked);};});onEachLazy(settingsElement.querySelectorAll("input[type=\"radio\"]"),elem=>{const settingId=elem.name;let settingValue=getSettingValue(settingId);if(settingId==="theme"){const useSystem=getSettingValue("use-system-theme");if(useSystem==="true"||settingValue===null){settingValue=useSystem==="false"?"light":"system preference";}}if(settingValue!==null&&settingValue!=="null"){elem.checked=settingValue===elem.value;}elem.addEventListener("change",()=>{changeSetting(elem.name,elem.value);});},);}function buildSettingsPageSections(settings){let output="";for(const setting of settings){const js_data_name=setting["js_name"];const setting_name=setting["name"];if(setting["options"]!==undefined){output+=`\ +
+
${setting_name}
+
`;onEach(setting["options"],option=>{const checked=option===setting["default"]?" checked":"";const full=`${js_data_name}-${option.replace(/ /g,"-")}`;output+=`\ + `;});output+=`\ +
+
`;}else{const checked=setting["default"]===true?" checked":"";output+=`\ +
\ + \ +
`;}}return output;}function buildSettingsPage(){const theme_list=getVar("themes");const theme_names=(theme_list===null?"":theme_list).split(",").filter(t=>t);theme_names.push("light","dark","ayu");const settings=[{"name":"Theme","js_name":"theme","default":"system preference","options":theme_names.concat("system preference"),},{"name":"Preferred light theme","js_name":"preferred-light-theme","default":"light","options":theme_names,},{"name":"Preferred dark theme","js_name":"preferred-dark-theme","default":"dark","options":theme_names,},{"name":"Auto-hide item contents for large items","js_name":"auto-hide-large-items","default":true,},{"name":"Auto-hide item methods' documentation","js_name":"auto-hide-method-docs","default":false,},{"name":"Auto-hide trait implementation documentation","js_name":"auto-hide-trait-implementations","default":false,},{"name":"Directly go to item in search if there is only one result","js_name":"go-to-only-result","default":false,},{"name":"Show line numbers on code examples","js_name":"line-numbers","default":false,},{"name":"Hide persistent navigation bar","js_name":"hide-sidebar","default":false,},{"name":"Hide table of contents","js_name":"hide-toc","default":false,},{"name":"Hide module navigation","js_name":"hide-modnav","default":false,},{"name":"Disable keyboard shortcuts","js_name":"disable-shortcuts","default":false,},{"name":"Use sans serif fonts","js_name":"sans-serif-fonts","default":false,},{"name":"Word wrap source code","js_name":"word-wrap-source-code","default":false,},];const elementKind=isSettingsPage?"section":"div";const innerHTML=`
${buildSettingsPageSections(settings)}
`;const el=document.createElement(elementKind);el.id="settings";if(!isSettingsPage){el.className="popover";}el.innerHTML=innerHTML;if(isSettingsPage){const mainElem=document.getElementById(MAIN_ID);if(mainElem!==null){mainElem.appendChild(el);}}else{el.setAttribute("tabindex","-1");const settingsBtn=getSettingsButton();if(settingsBtn!==null){settingsBtn.appendChild(el);}}return el;}const settingsMenu=buildSettingsPage();function displaySettings(){settingsMenu.style.display="";onEachLazy(settingsMenu.querySelectorAll("input[type='checkbox']"),el=>{const val=getSettingValue(el.id);const checked=val==="true";if(checked!==el.checked&&val!==null){el.checked=checked;}});}function settingsBlurHandler(event){const helpBtn=getHelpButton();const settingsBtn=getSettingsButton();const helpUnfocused=helpBtn===null||(!helpBtn.contains(document.activeElement)&&!elemContainsTarget(helpBtn,event.relatedTarget));const settingsUnfocused=settingsBtn===null||(!settingsBtn.contains(document.activeElement)&&!elemContainsTarget(settingsBtn,event.relatedTarget));if(helpUnfocused&&settingsUnfocused){window.hidePopoverMenus();}}if(!isSettingsPage){const settingsButton=nonnull(getSettingsButton());const settingsMenu=nonnull(document.getElementById("settings"));settingsButton.onclick=event=>{if(elemContainsTarget(settingsMenu,event.target)){return;}event.preventDefault();const shouldDisplaySettings=settingsMenu.style.display==="none";window.hideAllModals(false);if(shouldDisplaySettings){displaySettings();}};settingsButton.onblur=settingsBlurHandler;nonnull(settingsButton.querySelector("a")).onblur=settingsBlurHandler;onEachLazy(settingsMenu.querySelectorAll("input"),el=>{el.onblur=settingsBlurHandler;});settingsMenu.onblur=settingsBlurHandler;}setTimeout(()=>{setEvents(settingsMenu);if(!isSettingsPage){displaySettings();}removeClass(getSettingsButton(),"rotate");},0);})(); \ No newline at end of file diff --git a/static.files/src-script-813739b1.js b/static.files/src-script-813739b1.js new file mode 100644 index 0000000000..bf546257ca --- /dev/null +++ b/static.files/src-script-813739b1.js @@ -0,0 +1 @@ +"use strict";(function(){const rootPath=getVar("root-path");const NAME_OFFSET=0;const DIRS_OFFSET=1;const FILES_OFFSET=2;const RUSTDOC_MOBILE_BREAKPOINT=700;function closeSidebarIfMobile(){if(window.innerWidth{removeClass(document.documentElement,"src-sidebar-expanded");updateLocalStorage("source-sidebar-show","false");};window.rustdocShowSourceSidebar=()=>{addClass(document.documentElement,"src-sidebar-expanded");updateLocalStorage("source-sidebar-show","true");};window.rustdocToggleSrcSidebar=()=>{if(document.documentElement.classList.contains("src-sidebar-expanded")){window.rustdocCloseSourceSidebar();}else{window.rustdocShowSourceSidebar();}};function createSrcSidebar(srcIndexStr){const container=nonnull(document.querySelector("nav.sidebar"));const sidebar=document.createElement("div");sidebar.id="src-sidebar";const srcIndex=new Map(JSON.parse(srcIndexStr));let hasFoundFile=false;for(const[key,source]of srcIndex){source[NAME_OFFSET]=key;hasFoundFile=createDirEntry(source,sidebar,"",hasFoundFile);}container.appendChild(sidebar);const selected_elem=sidebar.getElementsByClassName("selected")[0];if(typeof selected_elem!=="undefined"){selected_elem.focus();}}function highlightSrcLines(){const match=window.location.hash.match(/^#?(\d+)(?:-(\d+))?$/);if(!match){return;}let from=parseInt(match[1],10);let to=from;if(typeof match[2]!=="undefined"){to=parseInt(match[2],10);}if(to{removeClass(e,"line-highlighted");});for(let i=from;i<=to;++i){elem=document.getElementById(""+i);if(!elem){break;}addClass(elem,"line-highlighted");}}const handleSrcHighlight=(function(){let prev_line_id=0;const set_fragment=name=>{const x=window.scrollX,y=window.scrollY;if(browserSupportsHistoryApi()){history.replaceState(null,"","#"+name);highlightSrcLines();}else{location.replace("#"+name);}window.scrollTo(x,y);};return ev=>{let cur_line_id=parseInt(ev.target.id,10);if(isNaN(cur_line_id)||ev.ctrlKey||ev.altKey||ev.metaKey){return;}ev.preventDefault();if(ev.shiftKey&&prev_line_id){if(prev_line_id>cur_line_id){const tmp=prev_line_id;prev_line_id=cur_line_id;cur_line_id=tmp;}set_fragment(prev_line_id+"-"+cur_line_id);}else{prev_line_id=cur_line_id;set_fragment(""+cur_line_id);}};}());window.addEventListener("hashchange",highlightSrcLines);onEachLazy(document.querySelectorAll("a[data-nosnippet]"),el=>{el.addEventListener("click",handleSrcHighlight);});highlightSrcLines();window.createSrcSidebar=createSrcSidebar;})(); \ No newline at end of file diff --git a/static.files/storage-68b7e25d.js b/static.files/storage-68b7e25d.js new file mode 100644 index 0000000000..5b18baa723 --- /dev/null +++ b/static.files/storage-68b7e25d.js @@ -0,0 +1,25 @@ +"use strict";const builtinThemes=["light","dark","ayu"];const darkThemes=["dark","ayu"];window.currentTheme=(function(){const currentTheme=document.getElementById("themeStyle");return currentTheme instanceof HTMLLinkElement?currentTheme:null;})();const settingsDataset=(function(){const settingsElement=document.getElementById("default-settings");return settingsElement&&settingsElement.dataset?settingsElement.dataset:null;})();function nonnull(x,msg){if(x===null){throw(msg||"unexpected null value!");}else{return x;}}function nonundef(x,msg){if(x===undefined){throw(msg||"unexpected null value!");}else{return x;}}function getSettingValue(settingName){const current=getCurrentValue(settingName);if(current===null&&settingsDataset!==null){const def=settingsDataset[settingName.replace(/-/g,"_")];if(def!==undefined){return def;}}return current;}const localStoredTheme=getSettingValue("theme");function hasClass(elem,className){return!!elem&&!!elem.classList&&elem.classList.contains(className);}function addClass(elem,className){if(elem&&elem.classList){elem.classList.add(className);}}function removeClass(elem,className){if(elem&&elem.classList){elem.classList.remove(className);}}function onEach(arr,func){for(const elem of arr){if(func(elem)){return true;}}return false;}function onEachLazy(lazyArray,func){return onEach(Array.prototype.slice.call(lazyArray),func);}function updateLocalStorage(name,value){try{if(value===null){window.localStorage.removeItem("rustdoc-"+name);}else{window.localStorage.setItem("rustdoc-"+name,value);}}catch(e){}}function getCurrentValue(name){try{return window.localStorage.getItem("rustdoc-"+name);}catch(e){return null;}}function getVar(name){const el=document.querySelector("head > meta[name='rustdoc-vars']");return el?el.getAttribute("data-"+name):null;}function switchTheme(newThemeName,saveTheme){const themeNames=(getVar("themes")||"").split(",").filter(t=>t);themeNames.push(...builtinThemes);if(newThemeName===null||themeNames.indexOf(newThemeName)===-1){return;}if(saveTheme){updateLocalStorage("theme",newThemeName);}document.documentElement.setAttribute("data-theme",newThemeName);if(builtinThemes.indexOf(newThemeName)!==-1){if(window.currentTheme&&window.currentTheme.parentNode){window.currentTheme.parentNode.removeChild(window.currentTheme);window.currentTheme=null;}}else{const newHref=getVar("root-path")+encodeURIComponent(newThemeName)+getVar("resource-suffix")+".css";if(!window.currentTheme){if(document.readyState==="loading"){document.write(``);window.currentTheme=(function(){const currentTheme=document.getElementById("themeStyle");return currentTheme instanceof HTMLLinkElement?currentTheme:null;})();}else{window.currentTheme=document.createElement("link");window.currentTheme.rel="stylesheet";window.currentTheme.id="themeStyle";window.currentTheme.href=newHref;document.documentElement.appendChild(window.currentTheme);}}else if(newHref!==window.currentTheme.href){window.currentTheme.href=newHref;}}}const updateTheme=(function(){const mql=window.matchMedia("(prefers-color-scheme: dark)");function updateTheme(){if(getSettingValue("use-system-theme")!=="false"){const lightTheme=getSettingValue("preferred-light-theme")||"light";const darkTheme=getSettingValue("preferred-dark-theme")||"dark";updateLocalStorage("use-system-theme","true");switchTheme(mql.matches?darkTheme:lightTheme,true);}else{switchTheme(getSettingValue("theme"),false);}}mql.addEventListener("change",updateTheme);return updateTheme;})();if(getSettingValue("use-system-theme")!=="false"&&window.matchMedia){if(getSettingValue("use-system-theme")===null&&getSettingValue("preferred-dark-theme")===null&&localStoredTheme!==null&&darkThemes.indexOf(localStoredTheme)>=0){updateLocalStorage("preferred-dark-theme",localStoredTheme);}}updateTheme();if(getSettingValue("source-sidebar-show")==="true"){addClass(document.documentElement,"src-sidebar-expanded");}if(getSettingValue("hide-sidebar")==="true"){addClass(document.documentElement,"hide-sidebar");}if(getSettingValue("hide-toc")==="true"){addClass(document.documentElement,"hide-toc");}if(getSettingValue("hide-modnav")==="true"){addClass(document.documentElement,"hide-modnav");}if(getSettingValue("sans-serif-fonts")==="true"){addClass(document.documentElement,"sans-serif");}if(getSettingValue("word-wrap-source-code")==="true"){addClass(document.documentElement,"word-wrap-source-code");}function updateSidebarWidth(){const desktopSidebarWidth=getSettingValue("desktop-sidebar-width");if(desktopSidebarWidth&&desktopSidebarWidth!=="null"){document.documentElement.style.setProperty("--desktop-sidebar-width",desktopSidebarWidth+"px",);}const srcSidebarWidth=getSettingValue("src-sidebar-width");if(srcSidebarWidth&&srcSidebarWidth!=="null"){document.documentElement.style.setProperty("--src-sidebar-width",srcSidebarWidth+"px",);}}updateSidebarWidth();window.addEventListener("pageshow",ev=>{if(ev.persisted){setTimeout(updateTheme,0);setTimeout(updateSidebarWidth,0);}});class RustdocSearchElement extends HTMLElement{constructor(){super();}connectedCallback(){const rootPath=getVar("root-path");const currentCrate=getVar("current-crate");this.innerHTML=``;}}window.customElements.define("rustdoc-search",RustdocSearchElement);class RustdocToolbarElement extends HTMLElement{constructor(){super();}connectedCallback(){if(this.firstElementChild){return;}const rootPath=getVar("root-path");this.innerHTML=` +
+ Settings +
+
+ Help +
+ `;}}window.customElements.define("rustdoc-toolbar",RustdocToolbarElement); \ No newline at end of file diff --git a/trait.impl/clap_builder/derive/trait.Args.js b/trait.impl/clap_builder/derive/trait.Args.js new file mode 100644 index 0000000000..f4eec7c7e6 --- /dev/null +++ b/trait.impl/clap_builder/derive/trait.Args.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Args for CliArgs"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[146]} \ No newline at end of file diff --git a/trait.impl/clap_builder/derive/trait.CommandFactory.js b/trait.impl/clap_builder/derive/trait.CommandFactory.js new file mode 100644 index 0000000000..02628b2f95 --- /dev/null +++ b/trait.impl/clap_builder/derive/trait.CommandFactory.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl CommandFactory for CliArgs"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[156]} \ No newline at end of file diff --git a/trait.impl/clap_builder/derive/trait.FromArgMatches.js b/trait.impl/clap_builder/derive/trait.FromArgMatches.js new file mode 100644 index 0000000000..db97f5e3a7 --- /dev/null +++ b/trait.impl/clap_builder/derive/trait.FromArgMatches.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl FromArgMatches for CliArgs"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[156]} \ No newline at end of file diff --git a/trait.impl/clap_builder/derive/trait.Parser.js b/trait.impl/clap_builder/derive/trait.Parser.js new file mode 100644 index 0000000000..4fae686b10 --- /dev/null +++ b/trait.impl/clap_builder/derive/trait.Parser.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Parser for CliArgs"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[148]} \ No newline at end of file diff --git a/trait.impl/core/clone/trait.Clone.js b/trait.impl/core/clone/trait.Clone.js new file mode 100644 index 0000000000..bc525d5ec0 --- /dev/null +++ b/trait.impl/core/clone/trait.Clone.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Clone for AuthConfig"],["impl Clone for InfoType"],["impl Clone for HueShifter"],["impl Clone for KeyboardLayoutDriver"],["impl Clone for MailType"],["impl Clone for PlayerName"],["impl Clone for PackageManager"],["impl Clone for DeviceKind"],["impl Clone for SoundDriver"],["impl Clone for TemperatureScale"],["impl Clone for Timezone"],["impl Clone for MouseButton"],["impl Clone for Prefix"],["impl Clone for Unit"],["impl Clone for ValueInner"],["impl Clone for GeolocatorBackend"],["impl Clone for Icon"],["impl Clone for I3BarBlockAlign"],["impl Clone for I3BarBlockMinWidth"],["impl Clone for Color"],["impl Clone for ColorOrLink"],["impl Clone for Separator"],["impl Clone for State"],["impl Clone for BasicAuthConfig"],["impl Clone for BasicCredentials"],["impl Clone for OAuth2Config"],["impl Clone for OAuth2Credentials"],["impl Clone for SourceConfig"],["impl Clone for Item"],["impl Clone for Config"],["impl Clone for CommonApi"],["impl Clone for Filter"],["impl Clone for ClickConfigEntry"],["impl Clone for ClickHandler"],["impl Clone for PostActions"],["impl Clone for SharedConfig"],["impl Clone for Error"],["impl Clone for Config"],["impl Clone for Format"],["impl Clone for Fragment"],["impl Clone for Metadata"],["impl Clone for FormatTemplate"],["impl Clone for Value"],["impl Clone for IPAddressInfo"],["impl Clone for Icons"],["impl Clone for I3BarBlock"],["impl Clone for I3BarEvent"],["impl Clone for Hsva"],["impl Clone for Rgba"],["impl Clone for Theme"],["impl Clone for ThemeInner"],["impl Clone for ThemeOverrides"],["impl Clone for Widget"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[15491]} \ No newline at end of file diff --git a/trait.impl/core/cmp/trait.Eq.js b/trait.impl/core/cmp/trait.Eq.js new file mode 100644 index 0000000000..547414e08e --- /dev/null +++ b/trait.impl/core/cmp/trait.Eq.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Eq for DeviceKind"],["impl Eq for TemperatureScale"],["impl Eq for MouseButton"],["impl Eq for Prefix"],["impl Eq for Unit"],["impl Eq for Separator"],["impl Eq for State"],["impl Eq for Metadata"],["impl Eq for I3BarEvent"],["impl Eq for Rgba"],["impl<'a> Eq for Token<'a>"],["impl<'a> Eq for Arg<'a>"],["impl<'a> Eq for FormatTemplate<'a>"],["impl<'a> Eq for Formatter<'a>"],["impl<'a> Eq for Placeholder<'a>"],["impl<'a> Eq for TokenList<'a>"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[4590]} \ No newline at end of file diff --git a/trait.impl/core/cmp/trait.Ord.js b/trait.impl/core/cmp/trait.Ord.js new file mode 100644 index 0000000000..e1809c2e70 --- /dev/null +++ b/trait.impl/core/cmp/trait.Ord.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Ord for Prefix"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[294]} \ No newline at end of file diff --git a/trait.impl/core/cmp/trait.PartialEq.js b/trait.impl/core/cmp/trait.PartialEq.js new file mode 100644 index 0000000000..84ec6b00db --- /dev/null +++ b/trait.impl/core/cmp/trait.PartialEq.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl PartialEq for PackageManager"],["impl PartialEq for DeviceKind"],["impl PartialEq for TemperatureScale"],["impl PartialEq for MouseButton"],["impl PartialEq for Prefix"],["impl PartialEq for Unit"],["impl PartialEq for Color"],["impl PartialEq for Separator"],["impl PartialEq for State"],["impl PartialEq for Metadata"],["impl PartialEq for I3BarEvent"],["impl PartialEq for Hsva"],["impl PartialEq for Rgba"],["impl<'a> PartialEq for Token<'a>"],["impl<'a> PartialEq for Arg<'a>"],["impl<'a> PartialEq for FormatTemplate<'a>"],["impl<'a> PartialEq for Formatter<'a>"],["impl<'a> PartialEq for Placeholder<'a>"],["impl<'a> PartialEq for TokenList<'a>"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[5808]} \ No newline at end of file diff --git a/trait.impl/core/cmp/trait.PartialOrd.js b/trait.impl/core/cmp/trait.PartialOrd.js new file mode 100644 index 0000000000..9533bc3008 --- /dev/null +++ b/trait.impl/core/cmp/trait.PartialOrd.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl PartialOrd for Prefix"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[315]} \ No newline at end of file diff --git a/trait.impl/core/convert/trait.From.js b/trait.impl/core/convert/trait.From.js new file mode 100644 index 0000000000..bb5c30547f --- /dev/null +++ b/trait.impl/core/convert/trait.From.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl From<&'static str> for Icon"],["impl From<GeolocatorBackend> for Geolocator"],["impl From<String> for Fragment"],["impl From<Error> for CalendarError"],["impl From<Error> for CalendarError"],["impl From<Error> for FormatError"],["impl From<Error> for Error"],["impl From<Hsva> for Rgba"],["impl From<Rgba> for Hsva"],["impl From<DeError> for CalendarError"],["impl From<Error> for CalendarError"],["impl<const N: usize> From<[&str; N]> for Icon"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[4943]} \ No newline at end of file diff --git a/trait.impl/core/convert/trait.TryFrom.js b/trait.impl/core/convert/trait.TryFrom.js new file mode 100644 index 0000000000..7f1b02f52e --- /dev/null +++ b/trait.impl/core/convert/trait.TryFrom.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl TryFrom<Token<'_>> for Token"],["impl TryFrom<FormatTemplate<'_>> for FormatTemplate"],["impl TryFrom<TokenList<'_>> for TokenList"],["impl TryFrom<ThemeUserConfig> for Theme"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[1903]} \ No newline at end of file diff --git a/trait.impl/core/default/trait.Default.js b/trait.impl/core/default/trait.Default.js new file mode 100644 index 0000000000..a81685436d --- /dev/null +++ b/trait.impl/core/default/trait.Default.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Default for BatteryDriver"],["impl Default for AuthConfig"],["impl Default for InfoType"],["impl Default for Driver"],["impl Default for KeyboardLayoutDriver"],["impl Default for PlayerName"],["impl Default for DriverType"],["impl Default for DriverType"],["impl Default for DeviceKind"],["impl Default for SoundDriver"],["impl Default for TemperatureScale"],["impl Default for DriverType"],["impl Default for GeolocatorBackend"],["impl Default for Color"],["impl Default for Separator"],["impl Default for State"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for BasicCredentials"],["impl Default for Config"],["impl Default for OAuth2Config"],["impl Default for OAuth2Credentials"],["impl Default for SourceConfig"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Apt"],["impl Default for Dnf"],["impl Default for Config"],["impl Default for Xbps"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Filter"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for Config"],["impl Default for ClickHandler"],["impl Default for CommonBlockConfig"],["impl Default for SharedConfig"],["impl Default for Config"],["impl Default for DurationFormatter"],["impl Default for Fragment"],["impl Default for Metadata"],["impl Default for FormatTemplate"],["impl Default for Geolocator"],["impl Default for IPAddressInfo"],["impl Default for Icons"],["impl Default for I3BarBlock"],["impl Default for Hsva"],["impl Default for Rgba"],["impl Default for Theme"],["impl Default for ThemeInner"],["impl Default for ThemeOverrides"],["impl Default for ThemeUserConfig"],["impl Default for Widget"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[25423]} \ No newline at end of file diff --git a/trait.impl/core/error/trait.Error.js b/trait.impl/core/error/trait.Error.js new file mode 100644 index 0000000000..e3001cc42e --- /dev/null +++ b/trait.impl/core/error/trait.Error.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Error for CalendarError"],["impl Error for FormatError"],["impl Error for BlockError"],["impl Error for BoxErrorWrapper"],["impl Error for Error"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[1399]} \ No newline at end of file diff --git a/trait.impl/core/fmt/trait.Debug.js b/trait.impl/core/fmt/trait.Debug.js new file mode 100644 index 0000000000..524b772c34 --- /dev/null +++ b/trait.impl/core/fmt/trait.Debug.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Debug for BatteryDriver"],["impl Debug for AuthConfig"],["impl Debug for CalendarError"],["impl Debug for InfoType"],["impl Debug for BlockConfig"],["impl Debug for Driver"],["impl Debug for HueShifter"],["impl Debug for KeyboardLayoutDriver"],["impl Debug for MailType"],["impl Debug for PlayerName"],["impl Debug for DriverType"],["impl Debug for PackageManager"],["impl Debug for PrivacyDriver"],["impl Debug for DriverType"],["impl Debug for DeviceKind"],["impl Debug for SoundDriver"],["impl Debug for TemperatureScale"],["impl Debug for Timezone"],["impl Debug for DriverType"],["impl Debug for WeatherService"],["impl Debug for MouseButton"],["impl Debug for FormatError"],["impl Debug for DatetimeFormatter"],["impl Debug for Prefix"],["impl Debug for Token"],["impl Debug for Unit"],["impl Debug for ValueInner"],["impl Debug for GeolocatorBackend"],["impl Debug for Icon"],["impl Debug for I3BarBlockAlign"],["impl Debug for I3BarBlockMinWidth"],["impl Debug for Color"],["impl Debug for ColorOrLink"],["impl Debug for Separator"],["impl Debug for State"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for BasicAuthConfig"],["impl Debug for BasicCredentials"],["impl Debug for Config"],["impl Debug for OAuth2Config"],["impl Debug for OAuth2Credentials"],["impl Debug for SourceConfig"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Item"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for BlockError"],["impl Debug for Config"],["impl Debug for Filter"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for Config"],["impl Debug for ClickConfigEntry"],["impl Debug for ClickHandler"],["impl Debug for PostActions"],["impl Debug for BlockConfigEntry"],["impl Debug for CommonBlockConfig"],["impl Debug for Config"],["impl Debug for SharedConfig"],["impl Debug for BoxErrorWrapper"],["impl Debug for Error"],["impl Debug for Config"],["impl Debug for BarFormatter"],["impl Debug for DurationFormatter"],["impl Debug for EngFormatter"],["impl Debug for FlagFormatter"],["impl Debug for PangoStrFormatter"],["impl Debug for StrFormatter"],["impl Debug for TallyFormatter"],["impl Debug for Format"],["impl Debug for Fragment"],["impl Debug for Metadata"],["impl Debug for FormatTemplate"],["impl Debug for TokenList"],["impl Debug for Value"],["impl Debug for Geolocator"],["impl Debug for IPAddressInfo"],["impl Debug for Icons"],["impl Debug for I3BarBlock"],["impl Debug for I3BarEvent"],["impl Debug for Block"],["impl Debug for CliArgs"],["impl Debug for Hsva"],["impl Debug for Rgba"],["impl Debug for Theme"],["impl Debug for ThemeInner"],["impl Debug for ThemeOverrides"],["impl Debug for Widget"],["impl<'a> Debug for Token<'a>"],["impl<'a> Debug for Arg<'a>"],["impl<'a> Debug for FormatTemplate<'a>"],["impl<'a> Debug for Formatter<'a>"],["impl<'a> Debug for Placeholder<'a>"],["impl<'a> Debug for TokenList<'a>"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[38307]} \ No newline at end of file diff --git a/trait.impl/core/fmt/trait.Display.js b/trait.impl/core/fmt/trait.Display.js new file mode 100644 index 0000000000..e12cc484b6 --- /dev/null +++ b/trait.impl/core/fmt/trait.Display.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Display for CalendarError"],["impl Display for FormatError"],["impl Display for Prefix"],["impl Display for Unit"],["impl Display for BlockError"],["impl Display for BoxErrorWrapper"],["impl Display for Error"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[2032]} \ No newline at end of file diff --git a/trait.impl/core/hash/trait.Hash.js b/trait.impl/core/hash/trait.Hash.js new file mode 100644 index 0000000000..c0f58d5d6e --- /dev/null +++ b/trait.impl/core/hash/trait.Hash.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Hash for DeviceKind"],["impl Hash for MouseButton"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[573]} \ No newline at end of file diff --git a/trait.impl/core/marker/trait.Copy.js b/trait.impl/core/marker/trait.Copy.js new file mode 100644 index 0000000000..73abccbefc --- /dev/null +++ b/trait.impl/core/marker/trait.Copy.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Copy for InfoType"],["impl Copy for HueShifter"],["impl Copy for KeyboardLayoutDriver"],["impl Copy for PackageManager"],["impl Copy for DeviceKind"],["impl Copy for SoundDriver"],["impl Copy for TemperatureScale"],["impl Copy for MouseButton"],["impl Copy for Prefix"],["impl Copy for Unit"],["impl Copy for I3BarBlockAlign"],["impl Copy for Color"],["impl Copy for State"],["impl Copy for Metadata"],["impl Copy for Hsva"],["impl Copy for Rgba"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[4669]} \ No newline at end of file diff --git a/trait.impl/core/marker/trait.Freeze.js b/trait.impl/core/marker/trait.Freeze.js new file mode 100644 index 0000000000..79eadcb981 --- /dev/null +++ b/trait.impl/core/marker/trait.Freeze.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl !Freeze for Geolocator",1,["i3status_rs::geolocator::Geolocator"]],["impl !Freeze for BarState",1,["i3status_rs::BarState"]],["impl Freeze for BatteryDriver",1,["i3status_rs::blocks::battery::BatteryDriver"]],["impl Freeze for AuthConfig",1,["i3status_rs::blocks::calendar::AuthConfig"]],["impl Freeze for CalendarError",1,["i3status_rs::blocks::calendar::CalendarError"]],["impl Freeze for InfoType",1,["i3status_rs::blocks::disk_space::InfoType"]],["impl Freeze for BlockConfig",1,["i3status_rs::blocks::BlockConfig"]],["impl Freeze for Driver",1,["i3status_rs::blocks::focused_window::Driver"]],["impl Freeze for HueShifter",1,["i3status_rs::blocks::hueshift::HueShifter"]],["impl Freeze for KeyboardLayoutDriver",1,["i3status_rs::blocks::keyboard_layout::KeyboardLayoutDriver"]],["impl Freeze for MailType",1,["i3status_rs::blocks::maildir::MailType"]],["impl Freeze for PlayerName",1,["i3status_rs::blocks::music::PlayerName"]],["impl Freeze for DriverType",1,["i3status_rs::blocks::notify::DriverType"]],["impl Freeze for PackageManager",1,["i3status_rs::blocks::packages::PackageManager"]],["impl Freeze for PrivacyDriver",1,["i3status_rs::blocks::privacy::PrivacyDriver"]],["impl Freeze for DriverType",1,["i3status_rs::blocks::service_status::DriverType"]],["impl Freeze for DeviceKind",1,["i3status_rs::blocks::sound::DeviceKind"]],["impl Freeze for SoundDriver",1,["i3status_rs::blocks::sound::SoundDriver"]],["impl Freeze for TemperatureScale",1,["i3status_rs::blocks::temperature::TemperatureScale"]],["impl Freeze for Timezone",1,["i3status_rs::blocks::time::Timezone"]],["impl Freeze for DriverType",1,["i3status_rs::blocks::vpn::DriverType"]],["impl Freeze for WeatherService",1,["i3status_rs::blocks::weather::WeatherService"]],["impl Freeze for MouseButton",1,["i3status_rs::click::MouseButton"]],["impl Freeze for FormatError",1,["i3status_rs::formatting::FormatError"]],["impl Freeze for DatetimeFormatter",1,["i3status_rs::formatting::formatter::datetime::DatetimeFormatter"]],["impl Freeze for Prefix",1,["i3status_rs::formatting::prefix::Prefix"]],["impl Freeze for Token",1,["i3status_rs::formatting::template::Token"]],["impl Freeze for Unit",1,["i3status_rs::formatting::unit::Unit"]],["impl Freeze for ValueInner",1,["i3status_rs::formatting::value::ValueInner"]],["impl Freeze for GeolocatorBackend",1,["i3status_rs::geolocator::GeolocatorBackend"]],["impl Freeze for Icon",1,["i3status_rs::icons::Icon"]],["impl Freeze for I3BarBlockAlign",1,["i3status_rs::protocol::i3bar_block::I3BarBlockAlign"]],["impl Freeze for I3BarBlockMinWidth",1,["i3status_rs::protocol::i3bar_block::I3BarBlockMinWidth"]],["impl Freeze for Color",1,["i3status_rs::themes::color::Color"]],["impl Freeze for ColorOrLink",1,["i3status_rs::themes::ColorOrLink"]],["impl Freeze for Separator",1,["i3status_rs::themes::separator::Separator"]],["impl Freeze for State",1,["i3status_rs::widget::State"]],["impl Freeze for Config",1,["i3status_rs::blocks::amd_gpu::Config"]],["impl Freeze for Device",1,["i3status_rs::blocks::amd_gpu::Device"]],["impl Freeze for Config",1,["i3status_rs::blocks::backlight::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::battery::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::bluetooth::Config"]],["impl Freeze for BasicAuthConfig",1,["i3status_rs::blocks::calendar::BasicAuthConfig"]],["impl Freeze for BasicCredentials",1,["i3status_rs::blocks::calendar::BasicCredentials"]],["impl Freeze for Config",1,["i3status_rs::blocks::calendar::Config"]],["impl Freeze for OAuth2Config",1,["i3status_rs::blocks::calendar::OAuth2Config"]],["impl Freeze for OAuth2Credentials",1,["i3status_rs::blocks::calendar::OAuth2Credentials"]],["impl Freeze for SourceConfig",1,["i3status_rs::blocks::calendar::SourceConfig"]],["impl Freeze for Config",1,["i3status_rs::blocks::cpu::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::custom::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::custom_dbus::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::disk_iostats::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::disk_space::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::docker::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::external_ip::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::focused_window::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::github::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::hueshift::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::kdeconnect::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::keyboard_layout::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::load::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::maildir::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::memory::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::menu::Config"]],["impl Freeze for Item",1,["i3status_rs::blocks::menu::Item"]],["impl Freeze for Config",1,["i3status_rs::blocks::music::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::net::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::notify::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::notmuch::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::nvidia_gpu::Config"]],["impl Freeze for Apt",1,["i3status_rs::blocks::packages::apt::Apt"]],["impl Freeze for Dnf",1,["i3status_rs::blocks::packages::dnf::Dnf"]],["impl Freeze for Aur",1,["i3status_rs::blocks::packages::pacman::Aur"]],["impl Freeze for Pacman",1,["i3status_rs::blocks::packages::pacman::Pacman"]],["impl Freeze for Config",1,["i3status_rs::blocks::packages::Config"]],["impl Freeze for Xbps",1,["i3status_rs::blocks::packages::xbps::Xbps"]],["impl Freeze for Config",1,["i3status_rs::blocks::pomodoro::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::privacy::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::rofication::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::scratchpad::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::service_status::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::sound::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::speedtest::Config"]],["impl Freeze for BlockError",1,["i3status_rs::blocks::BlockError"]],["impl Freeze for CommonApi",1,["i3status_rs::blocks::CommonApi"]],["impl Freeze for Config",1,["i3status_rs::blocks::taskwarrior::Config"]],["impl Freeze for Filter",1,["i3status_rs::blocks::taskwarrior::Filter"]],["impl Freeze for Config",1,["i3status_rs::blocks::tea_timer::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::temperature::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::time::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::toggle::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::uptime::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::vpn::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::watson::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::weather::met_no::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::weather::nws::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::weather::open_weather_map::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::weather::Config"]],["impl Freeze for Config",1,["i3status_rs::blocks::xrandr::Config"]],["impl Freeze for ClickConfigEntry",1,["i3status_rs::click::ClickConfigEntry"]],["impl Freeze for ClickHandler",1,["i3status_rs::click::ClickHandler"]],["impl Freeze for PostActions",1,["i3status_rs::click::PostActions"]],["impl Freeze for BlockConfigEntry",1,["i3status_rs::config::BlockConfigEntry"]],["impl Freeze for CommonBlockConfig",1,["i3status_rs::config::CommonBlockConfig"]],["impl Freeze for Config",1,["i3status_rs::config::Config"]],["impl Freeze for SharedConfig",1,["i3status_rs::config::SharedConfig"]],["impl Freeze for BoxErrorWrapper",1,["i3status_rs::errors::BoxErrorWrapper"]],["impl Freeze for Error",1,["i3status_rs::errors::Error"]],["impl Freeze for Config",1,["i3status_rs::formatting::config::Config"]],["impl Freeze for BarFormatter",1,["i3status_rs::formatting::formatter::bar::BarFormatter"]],["impl Freeze for DurationFormatter",1,["i3status_rs::formatting::formatter::duration::DurationFormatter"]],["impl Freeze for EngFormatter",1,["i3status_rs::formatting::formatter::eng::EngFormatter"]],["impl Freeze for FlagFormatter",1,["i3status_rs::formatting::formatter::flag::FlagFormatter"]],["impl Freeze for PangoStrFormatter",1,["i3status_rs::formatting::formatter::pango::PangoStrFormatter"]],["impl Freeze for StrFormatter",1,["i3status_rs::formatting::formatter::str::StrFormatter"]],["impl Freeze for TallyFormatter",1,["i3status_rs::formatting::formatter::tally::TallyFormatter"]],["impl Freeze for Format",1,["i3status_rs::formatting::Format"]],["impl Freeze for Fragment",1,["i3status_rs::formatting::Fragment"]],["impl Freeze for Metadata",1,["i3status_rs::formatting::Metadata"]],["impl Freeze for FormatTemplate",1,["i3status_rs::formatting::template::FormatTemplate"]],["impl Freeze for TokenList",1,["i3status_rs::formatting::template::TokenList"]],["impl Freeze for Value",1,["i3status_rs::formatting::value::Value"]],["impl Freeze for IPAddressInfo",1,["i3status_rs::geolocator::IPAddressInfo"]],["impl Freeze for Icons",1,["i3status_rs::icons::Icons"]],["impl Freeze for I3BarBlock",1,["i3status_rs::protocol::i3bar_block::I3BarBlock"]],["impl Freeze for I3BarEvent",1,["i3status_rs::protocol::i3bar_event::I3BarEvent"]],["impl Freeze for Block",1,["i3status_rs::Block"]],["impl Freeze for CliArgs",1,["i3status_rs::CliArgs"]],["impl Freeze for Hsva",1,["i3status_rs::themes::color::Hsva"]],["impl Freeze for Rgba",1,["i3status_rs::themes::color::Rgba"]],["impl Freeze for Theme",1,["i3status_rs::themes::Theme"]],["impl Freeze for ThemeInner",1,["i3status_rs::themes::ThemeInner"]],["impl Freeze for ThemeOverrides",1,["i3status_rs::themes::ThemeOverrides"]],["impl Freeze for ThemeUserConfig",1,["i3status_rs::themes::ThemeUserConfig"]],["impl Freeze for Widget",1,["i3status_rs::widget::Widget"]],["impl<'a> Freeze for Token<'a>",1,["i3status_rs::formatting::parse::Token"]],["impl<'a> Freeze for Arg<'a>",1,["i3status_rs::formatting::parse::Arg"]],["impl<'a> Freeze for FormatTemplate<'a>",1,["i3status_rs::formatting::parse::FormatTemplate"]],["impl<'a> Freeze for Formatter<'a>",1,["i3status_rs::formatting::parse::Formatter"]],["impl<'a> Freeze for Placeholder<'a>",1,["i3status_rs::formatting::parse::Placeholder"]],["impl<'a> Freeze for TokenList<'a>",1,["i3status_rs::formatting::parse::TokenList"]]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[48575]} \ No newline at end of file diff --git a/trait.impl/core/marker/trait.Send.js b/trait.impl/core/marker/trait.Send.js new file mode 100644 index 0000000000..62e3d231f9 --- /dev/null +++ b/trait.impl/core/marker/trait.Send.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl !Send for BarState",1,["i3status_rs::BarState"]],["impl Send for BatteryDriver",1,["i3status_rs::blocks::battery::BatteryDriver"]],["impl Send for AuthConfig",1,["i3status_rs::blocks::calendar::AuthConfig"]],["impl Send for CalendarError",1,["i3status_rs::blocks::calendar::CalendarError"]],["impl Send for InfoType",1,["i3status_rs::blocks::disk_space::InfoType"]],["impl Send for BlockConfig",1,["i3status_rs::blocks::BlockConfig"]],["impl Send for Driver",1,["i3status_rs::blocks::focused_window::Driver"]],["impl Send for HueShifter",1,["i3status_rs::blocks::hueshift::HueShifter"]],["impl Send for KeyboardLayoutDriver",1,["i3status_rs::blocks::keyboard_layout::KeyboardLayoutDriver"]],["impl Send for MailType",1,["i3status_rs::blocks::maildir::MailType"]],["impl Send for PlayerName",1,["i3status_rs::blocks::music::PlayerName"]],["impl Send for DriverType",1,["i3status_rs::blocks::notify::DriverType"]],["impl Send for PackageManager",1,["i3status_rs::blocks::packages::PackageManager"]],["impl Send for PrivacyDriver",1,["i3status_rs::blocks::privacy::PrivacyDriver"]],["impl Send for DriverType",1,["i3status_rs::blocks::service_status::DriverType"]],["impl Send for DeviceKind",1,["i3status_rs::blocks::sound::DeviceKind"]],["impl Send for SoundDriver",1,["i3status_rs::blocks::sound::SoundDriver"]],["impl Send for TemperatureScale",1,["i3status_rs::blocks::temperature::TemperatureScale"]],["impl Send for Timezone",1,["i3status_rs::blocks::time::Timezone"]],["impl Send for DriverType",1,["i3status_rs::blocks::vpn::DriverType"]],["impl Send for WeatherService",1,["i3status_rs::blocks::weather::WeatherService"]],["impl Send for MouseButton",1,["i3status_rs::click::MouseButton"]],["impl Send for FormatError",1,["i3status_rs::formatting::FormatError"]],["impl Send for DatetimeFormatter",1,["i3status_rs::formatting::formatter::datetime::DatetimeFormatter"]],["impl Send for Prefix",1,["i3status_rs::formatting::prefix::Prefix"]],["impl Send for Token",1,["i3status_rs::formatting::template::Token"]],["impl Send for Unit",1,["i3status_rs::formatting::unit::Unit"]],["impl Send for ValueInner",1,["i3status_rs::formatting::value::ValueInner"]],["impl Send for GeolocatorBackend",1,["i3status_rs::geolocator::GeolocatorBackend"]],["impl Send for Icon",1,["i3status_rs::icons::Icon"]],["impl Send for I3BarBlockAlign",1,["i3status_rs::protocol::i3bar_block::I3BarBlockAlign"]],["impl Send for I3BarBlockMinWidth",1,["i3status_rs::protocol::i3bar_block::I3BarBlockMinWidth"]],["impl Send for Color",1,["i3status_rs::themes::color::Color"]],["impl Send for ColorOrLink",1,["i3status_rs::themes::ColorOrLink"]],["impl Send for Separator",1,["i3status_rs::themes::separator::Separator"]],["impl Send for State",1,["i3status_rs::widget::State"]],["impl Send for Config",1,["i3status_rs::blocks::amd_gpu::Config"]],["impl Send for Device",1,["i3status_rs::blocks::amd_gpu::Device"]],["impl Send for Config",1,["i3status_rs::blocks::backlight::Config"]],["impl Send for Config",1,["i3status_rs::blocks::battery::Config"]],["impl Send for Config",1,["i3status_rs::blocks::bluetooth::Config"]],["impl Send for BasicAuthConfig",1,["i3status_rs::blocks::calendar::BasicAuthConfig"]],["impl Send for BasicCredentials",1,["i3status_rs::blocks::calendar::BasicCredentials"]],["impl Send for Config",1,["i3status_rs::blocks::calendar::Config"]],["impl Send for OAuth2Config",1,["i3status_rs::blocks::calendar::OAuth2Config"]],["impl Send for OAuth2Credentials",1,["i3status_rs::blocks::calendar::OAuth2Credentials"]],["impl Send for SourceConfig",1,["i3status_rs::blocks::calendar::SourceConfig"]],["impl Send for Config",1,["i3status_rs::blocks::cpu::Config"]],["impl Send for Config",1,["i3status_rs::blocks::custom::Config"]],["impl Send for Config",1,["i3status_rs::blocks::custom_dbus::Config"]],["impl Send for Config",1,["i3status_rs::blocks::disk_iostats::Config"]],["impl Send for Config",1,["i3status_rs::blocks::disk_space::Config"]],["impl Send for Config",1,["i3status_rs::blocks::docker::Config"]],["impl Send for Config",1,["i3status_rs::blocks::external_ip::Config"]],["impl Send for Config",1,["i3status_rs::blocks::focused_window::Config"]],["impl Send for Config",1,["i3status_rs::blocks::github::Config"]],["impl Send for Config",1,["i3status_rs::blocks::hueshift::Config"]],["impl Send for Config",1,["i3status_rs::blocks::kdeconnect::Config"]],["impl Send for Config",1,["i3status_rs::blocks::keyboard_layout::Config"]],["impl Send for Config",1,["i3status_rs::blocks::load::Config"]],["impl Send for Config",1,["i3status_rs::blocks::maildir::Config"]],["impl Send for Config",1,["i3status_rs::blocks::memory::Config"]],["impl Send for Config",1,["i3status_rs::blocks::menu::Config"]],["impl Send for Item",1,["i3status_rs::blocks::menu::Item"]],["impl Send for Config",1,["i3status_rs::blocks::music::Config"]],["impl Send for Config",1,["i3status_rs::blocks::net::Config"]],["impl Send for Config",1,["i3status_rs::blocks::notify::Config"]],["impl Send for Config",1,["i3status_rs::blocks::notmuch::Config"]],["impl Send for Config",1,["i3status_rs::blocks::nvidia_gpu::Config"]],["impl Send for Apt",1,["i3status_rs::blocks::packages::apt::Apt"]],["impl Send for Dnf",1,["i3status_rs::blocks::packages::dnf::Dnf"]],["impl Send for Aur",1,["i3status_rs::blocks::packages::pacman::Aur"]],["impl Send for Pacman",1,["i3status_rs::blocks::packages::pacman::Pacman"]],["impl Send for Config",1,["i3status_rs::blocks::packages::Config"]],["impl Send for Xbps",1,["i3status_rs::blocks::packages::xbps::Xbps"]],["impl Send for Config",1,["i3status_rs::blocks::pomodoro::Config"]],["impl Send for Config",1,["i3status_rs::blocks::privacy::Config"]],["impl Send for Config",1,["i3status_rs::blocks::rofication::Config"]],["impl Send for Config",1,["i3status_rs::blocks::scratchpad::Config"]],["impl Send for Config",1,["i3status_rs::blocks::service_status::Config"]],["impl Send for Config",1,["i3status_rs::blocks::sound::Config"]],["impl Send for Config",1,["i3status_rs::blocks::speedtest::Config"]],["impl Send for BlockError",1,["i3status_rs::blocks::BlockError"]],["impl Send for CommonApi",1,["i3status_rs::blocks::CommonApi"]],["impl Send for Config",1,["i3status_rs::blocks::taskwarrior::Config"]],["impl Send for Filter",1,["i3status_rs::blocks::taskwarrior::Filter"]],["impl Send for Config",1,["i3status_rs::blocks::tea_timer::Config"]],["impl Send for Config",1,["i3status_rs::blocks::temperature::Config"]],["impl Send for Config",1,["i3status_rs::blocks::time::Config"]],["impl Send for Config",1,["i3status_rs::blocks::toggle::Config"]],["impl Send for Config",1,["i3status_rs::blocks::uptime::Config"]],["impl Send for Config",1,["i3status_rs::blocks::vpn::Config"]],["impl Send for Config",1,["i3status_rs::blocks::watson::Config"]],["impl Send for Config",1,["i3status_rs::blocks::weather::met_no::Config"]],["impl Send for Config",1,["i3status_rs::blocks::weather::nws::Config"]],["impl Send for Config",1,["i3status_rs::blocks::weather::open_weather_map::Config"]],["impl Send for Config",1,["i3status_rs::blocks::weather::Config"]],["impl Send for Config",1,["i3status_rs::blocks::xrandr::Config"]],["impl Send for ClickConfigEntry",1,["i3status_rs::click::ClickConfigEntry"]],["impl Send for ClickHandler",1,["i3status_rs::click::ClickHandler"]],["impl Send for PostActions",1,["i3status_rs::click::PostActions"]],["impl Send for BlockConfigEntry",1,["i3status_rs::config::BlockConfigEntry"]],["impl Send for CommonBlockConfig",1,["i3status_rs::config::CommonBlockConfig"]],["impl Send for Config",1,["i3status_rs::config::Config"]],["impl Send for SharedConfig",1,["i3status_rs::config::SharedConfig"]],["impl Send for BoxErrorWrapper",1,["i3status_rs::errors::BoxErrorWrapper"]],["impl Send for Error",1,["i3status_rs::errors::Error"]],["impl Send for Config",1,["i3status_rs::formatting::config::Config"]],["impl Send for BarFormatter",1,["i3status_rs::formatting::formatter::bar::BarFormatter"]],["impl Send for DurationFormatter",1,["i3status_rs::formatting::formatter::duration::DurationFormatter"]],["impl Send for EngFormatter",1,["i3status_rs::formatting::formatter::eng::EngFormatter"]],["impl Send for FlagFormatter",1,["i3status_rs::formatting::formatter::flag::FlagFormatter"]],["impl Send for PangoStrFormatter",1,["i3status_rs::formatting::formatter::pango::PangoStrFormatter"]],["impl Send for StrFormatter",1,["i3status_rs::formatting::formatter::str::StrFormatter"]],["impl Send for TallyFormatter",1,["i3status_rs::formatting::formatter::tally::TallyFormatter"]],["impl Send for Format",1,["i3status_rs::formatting::Format"]],["impl Send for Fragment",1,["i3status_rs::formatting::Fragment"]],["impl Send for Metadata",1,["i3status_rs::formatting::Metadata"]],["impl Send for FormatTemplate",1,["i3status_rs::formatting::template::FormatTemplate"]],["impl Send for TokenList",1,["i3status_rs::formatting::template::TokenList"]],["impl Send for Value",1,["i3status_rs::formatting::value::Value"]],["impl Send for Geolocator",1,["i3status_rs::geolocator::Geolocator"]],["impl Send for IPAddressInfo",1,["i3status_rs::geolocator::IPAddressInfo"]],["impl Send for Icons",1,["i3status_rs::icons::Icons"]],["impl Send for I3BarBlock",1,["i3status_rs::protocol::i3bar_block::I3BarBlock"]],["impl Send for I3BarEvent",1,["i3status_rs::protocol::i3bar_event::I3BarEvent"]],["impl Send for Block",1,["i3status_rs::Block"]],["impl Send for CliArgs",1,["i3status_rs::CliArgs"]],["impl Send for Hsva",1,["i3status_rs::themes::color::Hsva"]],["impl Send for Rgba",1,["i3status_rs::themes::color::Rgba"]],["impl Send for Theme",1,["i3status_rs::themes::Theme"]],["impl Send for ThemeInner",1,["i3status_rs::themes::ThemeInner"]],["impl Send for ThemeOverrides",1,["i3status_rs::themes::ThemeOverrides"]],["impl Send for ThemeUserConfig",1,["i3status_rs::themes::ThemeUserConfig"]],["impl Send for Widget",1,["i3status_rs::widget::Widget"]],["impl<'a> Send for Token<'a>",1,["i3status_rs::formatting::parse::Token"]],["impl<'a> Send for Arg<'a>",1,["i3status_rs::formatting::parse::Arg"]],["impl<'a> Send for FormatTemplate<'a>",1,["i3status_rs::formatting::parse::FormatTemplate"]],["impl<'a> Send for Formatter<'a>",1,["i3status_rs::formatting::parse::Formatter"]],["impl<'a> Send for Placeholder<'a>",1,["i3status_rs::formatting::parse::Placeholder"]],["impl<'a> Send for TokenList<'a>",1,["i3status_rs::formatting::parse::TokenList"]]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[47728]} \ No newline at end of file diff --git a/trait.impl/core/marker/trait.StructuralPartialEq.js b/trait.impl/core/marker/trait.StructuralPartialEq.js new file mode 100644 index 0000000000..703d26f557 --- /dev/null +++ b/trait.impl/core/marker/trait.StructuralPartialEq.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl StructuralPartialEq for PackageManager"],["impl StructuralPartialEq for DeviceKind"],["impl StructuralPartialEq for TemperatureScale"],["impl StructuralPartialEq for MouseButton"],["impl StructuralPartialEq for Prefix"],["impl StructuralPartialEq for Unit"],["impl StructuralPartialEq for Color"],["impl StructuralPartialEq for Separator"],["impl StructuralPartialEq for State"],["impl StructuralPartialEq for Metadata"],["impl StructuralPartialEq for I3BarEvent"],["impl StructuralPartialEq for Rgba"],["impl<'a> StructuralPartialEq for Token<'a>"],["impl<'a> StructuralPartialEq for Arg<'a>"],["impl<'a> StructuralPartialEq for FormatTemplate<'a>"],["impl<'a> StructuralPartialEq for Formatter<'a>"],["impl<'a> StructuralPartialEq for Placeholder<'a>"],["impl<'a> StructuralPartialEq for TokenList<'a>"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[6171]} \ No newline at end of file diff --git a/trait.impl/core/marker/trait.Sync.js b/trait.impl/core/marker/trait.Sync.js new file mode 100644 index 0000000000..2cda960ed1 --- /dev/null +++ b/trait.impl/core/marker/trait.Sync.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl !Sync for BarState",1,["i3status_rs::BarState"]],["impl Sync for BatteryDriver",1,["i3status_rs::blocks::battery::BatteryDriver"]],["impl Sync for AuthConfig",1,["i3status_rs::blocks::calendar::AuthConfig"]],["impl Sync for CalendarError",1,["i3status_rs::blocks::calendar::CalendarError"]],["impl Sync for InfoType",1,["i3status_rs::blocks::disk_space::InfoType"]],["impl Sync for BlockConfig",1,["i3status_rs::blocks::BlockConfig"]],["impl Sync for Driver",1,["i3status_rs::blocks::focused_window::Driver"]],["impl Sync for HueShifter",1,["i3status_rs::blocks::hueshift::HueShifter"]],["impl Sync for KeyboardLayoutDriver",1,["i3status_rs::blocks::keyboard_layout::KeyboardLayoutDriver"]],["impl Sync for MailType",1,["i3status_rs::blocks::maildir::MailType"]],["impl Sync for PlayerName",1,["i3status_rs::blocks::music::PlayerName"]],["impl Sync for DriverType",1,["i3status_rs::blocks::notify::DriverType"]],["impl Sync for PackageManager",1,["i3status_rs::blocks::packages::PackageManager"]],["impl Sync for PrivacyDriver",1,["i3status_rs::blocks::privacy::PrivacyDriver"]],["impl Sync for DriverType",1,["i3status_rs::blocks::service_status::DriverType"]],["impl Sync for DeviceKind",1,["i3status_rs::blocks::sound::DeviceKind"]],["impl Sync for SoundDriver",1,["i3status_rs::blocks::sound::SoundDriver"]],["impl Sync for TemperatureScale",1,["i3status_rs::blocks::temperature::TemperatureScale"]],["impl Sync for Timezone",1,["i3status_rs::blocks::time::Timezone"]],["impl Sync for DriverType",1,["i3status_rs::blocks::vpn::DriverType"]],["impl Sync for WeatherService",1,["i3status_rs::blocks::weather::WeatherService"]],["impl Sync for MouseButton",1,["i3status_rs::click::MouseButton"]],["impl Sync for FormatError",1,["i3status_rs::formatting::FormatError"]],["impl Sync for DatetimeFormatter",1,["i3status_rs::formatting::formatter::datetime::DatetimeFormatter"]],["impl Sync for Prefix",1,["i3status_rs::formatting::prefix::Prefix"]],["impl Sync for Token",1,["i3status_rs::formatting::template::Token"]],["impl Sync for Unit",1,["i3status_rs::formatting::unit::Unit"]],["impl Sync for ValueInner",1,["i3status_rs::formatting::value::ValueInner"]],["impl Sync for GeolocatorBackend",1,["i3status_rs::geolocator::GeolocatorBackend"]],["impl Sync for Icon",1,["i3status_rs::icons::Icon"]],["impl Sync for I3BarBlockAlign",1,["i3status_rs::protocol::i3bar_block::I3BarBlockAlign"]],["impl Sync for I3BarBlockMinWidth",1,["i3status_rs::protocol::i3bar_block::I3BarBlockMinWidth"]],["impl Sync for Color",1,["i3status_rs::themes::color::Color"]],["impl Sync for ColorOrLink",1,["i3status_rs::themes::ColorOrLink"]],["impl Sync for Separator",1,["i3status_rs::themes::separator::Separator"]],["impl Sync for State",1,["i3status_rs::widget::State"]],["impl Sync for Config",1,["i3status_rs::blocks::amd_gpu::Config"]],["impl Sync for Device",1,["i3status_rs::blocks::amd_gpu::Device"]],["impl Sync for Config",1,["i3status_rs::blocks::backlight::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::battery::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::bluetooth::Config"]],["impl Sync for BasicAuthConfig",1,["i3status_rs::blocks::calendar::BasicAuthConfig"]],["impl Sync for BasicCredentials",1,["i3status_rs::blocks::calendar::BasicCredentials"]],["impl Sync for Config",1,["i3status_rs::blocks::calendar::Config"]],["impl Sync for OAuth2Config",1,["i3status_rs::blocks::calendar::OAuth2Config"]],["impl Sync for OAuth2Credentials",1,["i3status_rs::blocks::calendar::OAuth2Credentials"]],["impl Sync for SourceConfig",1,["i3status_rs::blocks::calendar::SourceConfig"]],["impl Sync for Config",1,["i3status_rs::blocks::cpu::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::custom::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::custom_dbus::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::disk_iostats::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::disk_space::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::docker::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::external_ip::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::focused_window::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::github::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::hueshift::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::kdeconnect::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::keyboard_layout::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::load::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::maildir::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::memory::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::menu::Config"]],["impl Sync for Item",1,["i3status_rs::blocks::menu::Item"]],["impl Sync for Config",1,["i3status_rs::blocks::music::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::net::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::notify::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::notmuch::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::nvidia_gpu::Config"]],["impl Sync for Apt",1,["i3status_rs::blocks::packages::apt::Apt"]],["impl Sync for Dnf",1,["i3status_rs::blocks::packages::dnf::Dnf"]],["impl Sync for Aur",1,["i3status_rs::blocks::packages::pacman::Aur"]],["impl Sync for Pacman",1,["i3status_rs::blocks::packages::pacman::Pacman"]],["impl Sync for Config",1,["i3status_rs::blocks::packages::Config"]],["impl Sync for Xbps",1,["i3status_rs::blocks::packages::xbps::Xbps"]],["impl Sync for Config",1,["i3status_rs::blocks::pomodoro::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::privacy::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::rofication::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::scratchpad::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::service_status::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::sound::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::speedtest::Config"]],["impl Sync for BlockError",1,["i3status_rs::blocks::BlockError"]],["impl Sync for CommonApi",1,["i3status_rs::blocks::CommonApi"]],["impl Sync for Config",1,["i3status_rs::blocks::taskwarrior::Config"]],["impl Sync for Filter",1,["i3status_rs::blocks::taskwarrior::Filter"]],["impl Sync for Config",1,["i3status_rs::blocks::tea_timer::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::temperature::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::time::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::toggle::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::uptime::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::vpn::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::watson::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::weather::met_no::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::weather::nws::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::weather::open_weather_map::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::weather::Config"]],["impl Sync for Config",1,["i3status_rs::blocks::xrandr::Config"]],["impl Sync for ClickConfigEntry",1,["i3status_rs::click::ClickConfigEntry"]],["impl Sync for ClickHandler",1,["i3status_rs::click::ClickHandler"]],["impl Sync for PostActions",1,["i3status_rs::click::PostActions"]],["impl Sync for BlockConfigEntry",1,["i3status_rs::config::BlockConfigEntry"]],["impl Sync for CommonBlockConfig",1,["i3status_rs::config::CommonBlockConfig"]],["impl Sync for Config",1,["i3status_rs::config::Config"]],["impl Sync for SharedConfig",1,["i3status_rs::config::SharedConfig"]],["impl Sync for BoxErrorWrapper",1,["i3status_rs::errors::BoxErrorWrapper"]],["impl Sync for Error",1,["i3status_rs::errors::Error"]],["impl Sync for Config",1,["i3status_rs::formatting::config::Config"]],["impl Sync for BarFormatter",1,["i3status_rs::formatting::formatter::bar::BarFormatter"]],["impl Sync for DurationFormatter",1,["i3status_rs::formatting::formatter::duration::DurationFormatter"]],["impl Sync for EngFormatter",1,["i3status_rs::formatting::formatter::eng::EngFormatter"]],["impl Sync for FlagFormatter",1,["i3status_rs::formatting::formatter::flag::FlagFormatter"]],["impl Sync for PangoStrFormatter",1,["i3status_rs::formatting::formatter::pango::PangoStrFormatter"]],["impl Sync for StrFormatter",1,["i3status_rs::formatting::formatter::str::StrFormatter"]],["impl Sync for TallyFormatter",1,["i3status_rs::formatting::formatter::tally::TallyFormatter"]],["impl Sync for Format",1,["i3status_rs::formatting::Format"]],["impl Sync for Fragment",1,["i3status_rs::formatting::Fragment"]],["impl Sync for Metadata",1,["i3status_rs::formatting::Metadata"]],["impl Sync for FormatTemplate",1,["i3status_rs::formatting::template::FormatTemplate"]],["impl Sync for TokenList",1,["i3status_rs::formatting::template::TokenList"]],["impl Sync for Value",1,["i3status_rs::formatting::value::Value"]],["impl Sync for Geolocator",1,["i3status_rs::geolocator::Geolocator"]],["impl Sync for IPAddressInfo",1,["i3status_rs::geolocator::IPAddressInfo"]],["impl Sync for Icons",1,["i3status_rs::icons::Icons"]],["impl Sync for I3BarBlock",1,["i3status_rs::protocol::i3bar_block::I3BarBlock"]],["impl Sync for I3BarEvent",1,["i3status_rs::protocol::i3bar_event::I3BarEvent"]],["impl Sync for Block",1,["i3status_rs::Block"]],["impl Sync for CliArgs",1,["i3status_rs::CliArgs"]],["impl Sync for Hsva",1,["i3status_rs::themes::color::Hsva"]],["impl Sync for Rgba",1,["i3status_rs::themes::color::Rgba"]],["impl Sync for Theme",1,["i3status_rs::themes::Theme"]],["impl Sync for ThemeInner",1,["i3status_rs::themes::ThemeInner"]],["impl Sync for ThemeOverrides",1,["i3status_rs::themes::ThemeOverrides"]],["impl Sync for ThemeUserConfig",1,["i3status_rs::themes::ThemeUserConfig"]],["impl Sync for Widget",1,["i3status_rs::widget::Widget"]],["impl<'a> Sync for Token<'a>",1,["i3status_rs::formatting::parse::Token"]],["impl<'a> Sync for Arg<'a>",1,["i3status_rs::formatting::parse::Arg"]],["impl<'a> Sync for FormatTemplate<'a>",1,["i3status_rs::formatting::parse::FormatTemplate"]],["impl<'a> Sync for Formatter<'a>",1,["i3status_rs::formatting::parse::Formatter"]],["impl<'a> Sync for Placeholder<'a>",1,["i3status_rs::formatting::parse::Placeholder"]],["impl<'a> Sync for TokenList<'a>",1,["i3status_rs::formatting::parse::TokenList"]]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[47728]} \ No newline at end of file diff --git a/trait.impl/core/marker/trait.Unpin.js b/trait.impl/core/marker/trait.Unpin.js new file mode 100644 index 0000000000..a4f7ae82ea --- /dev/null +++ b/trait.impl/core/marker/trait.Unpin.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Unpin for BatteryDriver",1,["i3status_rs::blocks::battery::BatteryDriver"]],["impl Unpin for AuthConfig",1,["i3status_rs::blocks::calendar::AuthConfig"]],["impl Unpin for CalendarError",1,["i3status_rs::blocks::calendar::CalendarError"]],["impl Unpin for InfoType",1,["i3status_rs::blocks::disk_space::InfoType"]],["impl Unpin for BlockConfig",1,["i3status_rs::blocks::BlockConfig"]],["impl Unpin for Driver",1,["i3status_rs::blocks::focused_window::Driver"]],["impl Unpin for HueShifter",1,["i3status_rs::blocks::hueshift::HueShifter"]],["impl Unpin for KeyboardLayoutDriver",1,["i3status_rs::blocks::keyboard_layout::KeyboardLayoutDriver"]],["impl Unpin for MailType",1,["i3status_rs::blocks::maildir::MailType"]],["impl Unpin for PlayerName",1,["i3status_rs::blocks::music::PlayerName"]],["impl Unpin for DriverType",1,["i3status_rs::blocks::notify::DriverType"]],["impl Unpin for PackageManager",1,["i3status_rs::blocks::packages::PackageManager"]],["impl Unpin for PrivacyDriver",1,["i3status_rs::blocks::privacy::PrivacyDriver"]],["impl Unpin for DriverType",1,["i3status_rs::blocks::service_status::DriverType"]],["impl Unpin for DeviceKind",1,["i3status_rs::blocks::sound::DeviceKind"]],["impl Unpin for SoundDriver",1,["i3status_rs::blocks::sound::SoundDriver"]],["impl Unpin for TemperatureScale",1,["i3status_rs::blocks::temperature::TemperatureScale"]],["impl Unpin for Timezone",1,["i3status_rs::blocks::time::Timezone"]],["impl Unpin for DriverType",1,["i3status_rs::blocks::vpn::DriverType"]],["impl Unpin for WeatherService",1,["i3status_rs::blocks::weather::WeatherService"]],["impl Unpin for MouseButton",1,["i3status_rs::click::MouseButton"]],["impl Unpin for FormatError",1,["i3status_rs::formatting::FormatError"]],["impl Unpin for DatetimeFormatter",1,["i3status_rs::formatting::formatter::datetime::DatetimeFormatter"]],["impl Unpin for Prefix",1,["i3status_rs::formatting::prefix::Prefix"]],["impl Unpin for Token",1,["i3status_rs::formatting::template::Token"]],["impl Unpin for Unit",1,["i3status_rs::formatting::unit::Unit"]],["impl Unpin for ValueInner",1,["i3status_rs::formatting::value::ValueInner"]],["impl Unpin for GeolocatorBackend",1,["i3status_rs::geolocator::GeolocatorBackend"]],["impl Unpin for Icon",1,["i3status_rs::icons::Icon"]],["impl Unpin for I3BarBlockAlign",1,["i3status_rs::protocol::i3bar_block::I3BarBlockAlign"]],["impl Unpin for I3BarBlockMinWidth",1,["i3status_rs::protocol::i3bar_block::I3BarBlockMinWidth"]],["impl Unpin for Color",1,["i3status_rs::themes::color::Color"]],["impl Unpin for ColorOrLink",1,["i3status_rs::themes::ColorOrLink"]],["impl Unpin for Separator",1,["i3status_rs::themes::separator::Separator"]],["impl Unpin for State",1,["i3status_rs::widget::State"]],["impl Unpin for Config",1,["i3status_rs::blocks::amd_gpu::Config"]],["impl Unpin for Device",1,["i3status_rs::blocks::amd_gpu::Device"]],["impl Unpin for Config",1,["i3status_rs::blocks::backlight::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::battery::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::bluetooth::Config"]],["impl Unpin for BasicAuthConfig",1,["i3status_rs::blocks::calendar::BasicAuthConfig"]],["impl Unpin for BasicCredentials",1,["i3status_rs::blocks::calendar::BasicCredentials"]],["impl Unpin for Config",1,["i3status_rs::blocks::calendar::Config"]],["impl Unpin for OAuth2Config",1,["i3status_rs::blocks::calendar::OAuth2Config"]],["impl Unpin for OAuth2Credentials",1,["i3status_rs::blocks::calendar::OAuth2Credentials"]],["impl Unpin for SourceConfig",1,["i3status_rs::blocks::calendar::SourceConfig"]],["impl Unpin for Config",1,["i3status_rs::blocks::cpu::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::custom::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::custom_dbus::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::disk_iostats::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::disk_space::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::docker::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::external_ip::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::focused_window::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::github::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::hueshift::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::kdeconnect::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::keyboard_layout::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::load::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::maildir::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::memory::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::menu::Config"]],["impl Unpin for Item",1,["i3status_rs::blocks::menu::Item"]],["impl Unpin for Config",1,["i3status_rs::blocks::music::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::net::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::notify::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::notmuch::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::nvidia_gpu::Config"]],["impl Unpin for Apt",1,["i3status_rs::blocks::packages::apt::Apt"]],["impl Unpin for Dnf",1,["i3status_rs::blocks::packages::dnf::Dnf"]],["impl Unpin for Aur",1,["i3status_rs::blocks::packages::pacman::Aur"]],["impl Unpin for Pacman",1,["i3status_rs::blocks::packages::pacman::Pacman"]],["impl Unpin for Config",1,["i3status_rs::blocks::packages::Config"]],["impl Unpin for Xbps",1,["i3status_rs::blocks::packages::xbps::Xbps"]],["impl Unpin for Config",1,["i3status_rs::blocks::pomodoro::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::privacy::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::rofication::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::scratchpad::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::service_status::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::sound::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::speedtest::Config"]],["impl Unpin for BlockError",1,["i3status_rs::blocks::BlockError"]],["impl Unpin for CommonApi",1,["i3status_rs::blocks::CommonApi"]],["impl Unpin for Config",1,["i3status_rs::blocks::taskwarrior::Config"]],["impl Unpin for Filter",1,["i3status_rs::blocks::taskwarrior::Filter"]],["impl Unpin for Config",1,["i3status_rs::blocks::tea_timer::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::temperature::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::time::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::toggle::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::uptime::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::vpn::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::watson::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::weather::met_no::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::weather::nws::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::weather::open_weather_map::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::weather::Config"]],["impl Unpin for Config",1,["i3status_rs::blocks::xrandr::Config"]],["impl Unpin for ClickConfigEntry",1,["i3status_rs::click::ClickConfigEntry"]],["impl Unpin for ClickHandler",1,["i3status_rs::click::ClickHandler"]],["impl Unpin for PostActions",1,["i3status_rs::click::PostActions"]],["impl Unpin for BlockConfigEntry",1,["i3status_rs::config::BlockConfigEntry"]],["impl Unpin for CommonBlockConfig",1,["i3status_rs::config::CommonBlockConfig"]],["impl Unpin for Config",1,["i3status_rs::config::Config"]],["impl Unpin for SharedConfig",1,["i3status_rs::config::SharedConfig"]],["impl Unpin for BoxErrorWrapper",1,["i3status_rs::errors::BoxErrorWrapper"]],["impl Unpin for Error",1,["i3status_rs::errors::Error"]],["impl Unpin for Config",1,["i3status_rs::formatting::config::Config"]],["impl Unpin for BarFormatter",1,["i3status_rs::formatting::formatter::bar::BarFormatter"]],["impl Unpin for DurationFormatter",1,["i3status_rs::formatting::formatter::duration::DurationFormatter"]],["impl Unpin for EngFormatter",1,["i3status_rs::formatting::formatter::eng::EngFormatter"]],["impl Unpin for FlagFormatter",1,["i3status_rs::formatting::formatter::flag::FlagFormatter"]],["impl Unpin for PangoStrFormatter",1,["i3status_rs::formatting::formatter::pango::PangoStrFormatter"]],["impl Unpin for StrFormatter",1,["i3status_rs::formatting::formatter::str::StrFormatter"]],["impl Unpin for TallyFormatter",1,["i3status_rs::formatting::formatter::tally::TallyFormatter"]],["impl Unpin for Format",1,["i3status_rs::formatting::Format"]],["impl Unpin for Fragment",1,["i3status_rs::formatting::Fragment"]],["impl Unpin for Metadata",1,["i3status_rs::formatting::Metadata"]],["impl Unpin for FormatTemplate",1,["i3status_rs::formatting::template::FormatTemplate"]],["impl Unpin for TokenList",1,["i3status_rs::formatting::template::TokenList"]],["impl Unpin for Value",1,["i3status_rs::formatting::value::Value"]],["impl Unpin for Geolocator",1,["i3status_rs::geolocator::Geolocator"]],["impl Unpin for IPAddressInfo",1,["i3status_rs::geolocator::IPAddressInfo"]],["impl Unpin for Icons",1,["i3status_rs::icons::Icons"]],["impl Unpin for I3BarBlock",1,["i3status_rs::protocol::i3bar_block::I3BarBlock"]],["impl Unpin for I3BarEvent",1,["i3status_rs::protocol::i3bar_event::I3BarEvent"]],["impl Unpin for BarState",1,["i3status_rs::BarState"]],["impl Unpin for Block",1,["i3status_rs::Block"]],["impl Unpin for CliArgs",1,["i3status_rs::CliArgs"]],["impl Unpin for Hsva",1,["i3status_rs::themes::color::Hsva"]],["impl Unpin for Rgba",1,["i3status_rs::themes::color::Rgba"]],["impl Unpin for Theme",1,["i3status_rs::themes::Theme"]],["impl Unpin for ThemeInner",1,["i3status_rs::themes::ThemeInner"]],["impl Unpin for ThemeOverrides",1,["i3status_rs::themes::ThemeOverrides"]],["impl Unpin for ThemeUserConfig",1,["i3status_rs::themes::ThemeUserConfig"]],["impl Unpin for Widget",1,["i3status_rs::widget::Widget"]],["impl<'a> Unpin for Token<'a>",1,["i3status_rs::formatting::parse::Token"]],["impl<'a> Unpin for Arg<'a>",1,["i3status_rs::formatting::parse::Arg"]],["impl<'a> Unpin for FormatTemplate<'a>",1,["i3status_rs::formatting::parse::FormatTemplate"]],["impl<'a> Unpin for Formatter<'a>",1,["i3status_rs::formatting::parse::Formatter"]],["impl<'a> Unpin for Placeholder<'a>",1,["i3status_rs::formatting::parse::Placeholder"]],["impl<'a> Unpin for TokenList<'a>",1,["i3status_rs::formatting::parse::TokenList"]]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[48150]} \ No newline at end of file diff --git a/trait.impl/core/marker/trait.UnsafeUnpin.js b/trait.impl/core/marker/trait.UnsafeUnpin.js new file mode 100644 index 0000000000..88d75e4ded --- /dev/null +++ b/trait.impl/core/marker/trait.UnsafeUnpin.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl UnsafeUnpin for BatteryDriver",1,["i3status_rs::blocks::battery::BatteryDriver"]],["impl UnsafeUnpin for AuthConfig",1,["i3status_rs::blocks::calendar::AuthConfig"]],["impl UnsafeUnpin for CalendarError",1,["i3status_rs::blocks::calendar::CalendarError"]],["impl UnsafeUnpin for InfoType",1,["i3status_rs::blocks::disk_space::InfoType"]],["impl UnsafeUnpin for BlockConfig",1,["i3status_rs::blocks::BlockConfig"]],["impl UnsafeUnpin for Driver",1,["i3status_rs::blocks::focused_window::Driver"]],["impl UnsafeUnpin for HueShifter",1,["i3status_rs::blocks::hueshift::HueShifter"]],["impl UnsafeUnpin for KeyboardLayoutDriver",1,["i3status_rs::blocks::keyboard_layout::KeyboardLayoutDriver"]],["impl UnsafeUnpin for MailType",1,["i3status_rs::blocks::maildir::MailType"]],["impl UnsafeUnpin for PlayerName",1,["i3status_rs::blocks::music::PlayerName"]],["impl UnsafeUnpin for DriverType",1,["i3status_rs::blocks::notify::DriverType"]],["impl UnsafeUnpin for PackageManager",1,["i3status_rs::blocks::packages::PackageManager"]],["impl UnsafeUnpin for PrivacyDriver",1,["i3status_rs::blocks::privacy::PrivacyDriver"]],["impl UnsafeUnpin for DriverType",1,["i3status_rs::blocks::service_status::DriverType"]],["impl UnsafeUnpin for DeviceKind",1,["i3status_rs::blocks::sound::DeviceKind"]],["impl UnsafeUnpin for SoundDriver",1,["i3status_rs::blocks::sound::SoundDriver"]],["impl UnsafeUnpin for TemperatureScale",1,["i3status_rs::blocks::temperature::TemperatureScale"]],["impl UnsafeUnpin for Timezone",1,["i3status_rs::blocks::time::Timezone"]],["impl UnsafeUnpin for DriverType",1,["i3status_rs::blocks::vpn::DriverType"]],["impl UnsafeUnpin for WeatherService",1,["i3status_rs::blocks::weather::WeatherService"]],["impl UnsafeUnpin for MouseButton",1,["i3status_rs::click::MouseButton"]],["impl UnsafeUnpin for FormatError",1,["i3status_rs::formatting::FormatError"]],["impl UnsafeUnpin for DatetimeFormatter",1,["i3status_rs::formatting::formatter::datetime::DatetimeFormatter"]],["impl UnsafeUnpin for Prefix",1,["i3status_rs::formatting::prefix::Prefix"]],["impl UnsafeUnpin for Token",1,["i3status_rs::formatting::template::Token"]],["impl UnsafeUnpin for Unit",1,["i3status_rs::formatting::unit::Unit"]],["impl UnsafeUnpin for ValueInner",1,["i3status_rs::formatting::value::ValueInner"]],["impl UnsafeUnpin for GeolocatorBackend",1,["i3status_rs::geolocator::GeolocatorBackend"]],["impl UnsafeUnpin for Icon",1,["i3status_rs::icons::Icon"]],["impl UnsafeUnpin for I3BarBlockAlign",1,["i3status_rs::protocol::i3bar_block::I3BarBlockAlign"]],["impl UnsafeUnpin for I3BarBlockMinWidth",1,["i3status_rs::protocol::i3bar_block::I3BarBlockMinWidth"]],["impl UnsafeUnpin for Color",1,["i3status_rs::themes::color::Color"]],["impl UnsafeUnpin for ColorOrLink",1,["i3status_rs::themes::ColorOrLink"]],["impl UnsafeUnpin for Separator",1,["i3status_rs::themes::separator::Separator"]],["impl UnsafeUnpin for State",1,["i3status_rs::widget::State"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::amd_gpu::Config"]],["impl UnsafeUnpin for Device",1,["i3status_rs::blocks::amd_gpu::Device"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::backlight::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::battery::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::bluetooth::Config"]],["impl UnsafeUnpin for BasicAuthConfig",1,["i3status_rs::blocks::calendar::BasicAuthConfig"]],["impl UnsafeUnpin for BasicCredentials",1,["i3status_rs::blocks::calendar::BasicCredentials"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::calendar::Config"]],["impl UnsafeUnpin for OAuth2Config",1,["i3status_rs::blocks::calendar::OAuth2Config"]],["impl UnsafeUnpin for OAuth2Credentials",1,["i3status_rs::blocks::calendar::OAuth2Credentials"]],["impl UnsafeUnpin for SourceConfig",1,["i3status_rs::blocks::calendar::SourceConfig"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::cpu::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::custom::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::custom_dbus::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::disk_iostats::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::disk_space::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::docker::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::external_ip::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::focused_window::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::github::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::hueshift::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::kdeconnect::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::keyboard_layout::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::load::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::maildir::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::memory::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::menu::Config"]],["impl UnsafeUnpin for Item",1,["i3status_rs::blocks::menu::Item"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::music::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::net::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::notify::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::notmuch::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::nvidia_gpu::Config"]],["impl UnsafeUnpin for Apt",1,["i3status_rs::blocks::packages::apt::Apt"]],["impl UnsafeUnpin for Dnf",1,["i3status_rs::blocks::packages::dnf::Dnf"]],["impl UnsafeUnpin for Aur",1,["i3status_rs::blocks::packages::pacman::Aur"]],["impl UnsafeUnpin for Pacman",1,["i3status_rs::blocks::packages::pacman::Pacman"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::packages::Config"]],["impl UnsafeUnpin for Xbps",1,["i3status_rs::blocks::packages::xbps::Xbps"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::pomodoro::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::privacy::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::rofication::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::scratchpad::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::service_status::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::sound::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::speedtest::Config"]],["impl UnsafeUnpin for BlockError",1,["i3status_rs::blocks::BlockError"]],["impl UnsafeUnpin for CommonApi",1,["i3status_rs::blocks::CommonApi"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::taskwarrior::Config"]],["impl UnsafeUnpin for Filter",1,["i3status_rs::blocks::taskwarrior::Filter"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::tea_timer::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::temperature::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::time::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::toggle::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::uptime::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::vpn::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::watson::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::weather::met_no::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::weather::nws::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::weather::open_weather_map::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::weather::Config"]],["impl UnsafeUnpin for Config",1,["i3status_rs::blocks::xrandr::Config"]],["impl UnsafeUnpin for ClickConfigEntry",1,["i3status_rs::click::ClickConfigEntry"]],["impl UnsafeUnpin for ClickHandler",1,["i3status_rs::click::ClickHandler"]],["impl UnsafeUnpin for PostActions",1,["i3status_rs::click::PostActions"]],["impl UnsafeUnpin for BlockConfigEntry",1,["i3status_rs::config::BlockConfigEntry"]],["impl UnsafeUnpin for CommonBlockConfig",1,["i3status_rs::config::CommonBlockConfig"]],["impl UnsafeUnpin for Config",1,["i3status_rs::config::Config"]],["impl UnsafeUnpin for SharedConfig",1,["i3status_rs::config::SharedConfig"]],["impl UnsafeUnpin for BoxErrorWrapper",1,["i3status_rs::errors::BoxErrorWrapper"]],["impl UnsafeUnpin for Error",1,["i3status_rs::errors::Error"]],["impl UnsafeUnpin for Config",1,["i3status_rs::formatting::config::Config"]],["impl UnsafeUnpin for BarFormatter",1,["i3status_rs::formatting::formatter::bar::BarFormatter"]],["impl UnsafeUnpin for DurationFormatter",1,["i3status_rs::formatting::formatter::duration::DurationFormatter"]],["impl UnsafeUnpin for EngFormatter",1,["i3status_rs::formatting::formatter::eng::EngFormatter"]],["impl UnsafeUnpin for FlagFormatter",1,["i3status_rs::formatting::formatter::flag::FlagFormatter"]],["impl UnsafeUnpin for PangoStrFormatter",1,["i3status_rs::formatting::formatter::pango::PangoStrFormatter"]],["impl UnsafeUnpin for StrFormatter",1,["i3status_rs::formatting::formatter::str::StrFormatter"]],["impl UnsafeUnpin for TallyFormatter",1,["i3status_rs::formatting::formatter::tally::TallyFormatter"]],["impl UnsafeUnpin for Format",1,["i3status_rs::formatting::Format"]],["impl UnsafeUnpin for Fragment",1,["i3status_rs::formatting::Fragment"]],["impl UnsafeUnpin for Metadata",1,["i3status_rs::formatting::Metadata"]],["impl UnsafeUnpin for FormatTemplate",1,["i3status_rs::formatting::template::FormatTemplate"]],["impl UnsafeUnpin for TokenList",1,["i3status_rs::formatting::template::TokenList"]],["impl UnsafeUnpin for Value",1,["i3status_rs::formatting::value::Value"]],["impl UnsafeUnpin for Geolocator",1,["i3status_rs::geolocator::Geolocator"]],["impl UnsafeUnpin for IPAddressInfo",1,["i3status_rs::geolocator::IPAddressInfo"]],["impl UnsafeUnpin for Icons",1,["i3status_rs::icons::Icons"]],["impl UnsafeUnpin for I3BarBlock",1,["i3status_rs::protocol::i3bar_block::I3BarBlock"]],["impl UnsafeUnpin for I3BarEvent",1,["i3status_rs::protocol::i3bar_event::I3BarEvent"]],["impl UnsafeUnpin for BarState",1,["i3status_rs::BarState"]],["impl UnsafeUnpin for Block",1,["i3status_rs::Block"]],["impl UnsafeUnpin for CliArgs",1,["i3status_rs::CliArgs"]],["impl UnsafeUnpin for Hsva",1,["i3status_rs::themes::color::Hsva"]],["impl UnsafeUnpin for Rgba",1,["i3status_rs::themes::color::Rgba"]],["impl UnsafeUnpin for Theme",1,["i3status_rs::themes::Theme"]],["impl UnsafeUnpin for ThemeInner",1,["i3status_rs::themes::ThemeInner"]],["impl UnsafeUnpin for ThemeOverrides",1,["i3status_rs::themes::ThemeOverrides"]],["impl UnsafeUnpin for ThemeUserConfig",1,["i3status_rs::themes::ThemeUserConfig"]],["impl UnsafeUnpin for Widget",1,["i3status_rs::widget::Widget"]],["impl<'a> UnsafeUnpin for Token<'a>",1,["i3status_rs::formatting::parse::Token"]],["impl<'a> UnsafeUnpin for Arg<'a>",1,["i3status_rs::formatting::parse::Arg"]],["impl<'a> UnsafeUnpin for FormatTemplate<'a>",1,["i3status_rs::formatting::parse::FormatTemplate"]],["impl<'a> UnsafeUnpin for Formatter<'a>",1,["i3status_rs::formatting::parse::Formatter"]],["impl<'a> UnsafeUnpin for Placeholder<'a>",1,["i3status_rs::formatting::parse::Placeholder"]],["impl<'a> UnsafeUnpin for TokenList<'a>",1,["i3status_rs::formatting::parse::TokenList"]]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[30666]} \ No newline at end of file diff --git a/trait.impl/core/ops/arith/trait.Add.js b/trait.impl/core/ops/arith/trait.Add.js new file mode 100644 index 0000000000..9e1c4b6621 --- /dev/null +++ b/trait.impl/core/ops/arith/trait.Add.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Add for Color"],["impl Add for Hsva"],["impl Add for Rgba"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[854]} \ No newline at end of file diff --git a/trait.impl/core/ops/deref/trait.Deref.js b/trait.impl/core/ops/deref/trait.Deref.js new file mode 100644 index 0000000000..e533a06421 --- /dev/null +++ b/trait.impl/core/ops/deref/trait.Deref.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Deref for Theme"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[293]} \ No newline at end of file diff --git a/trait.impl/core/ops/deref/trait.DerefMut.js b/trait.impl/core/ops/deref/trait.DerefMut.js new file mode 100644 index 0000000000..f5087b7de4 --- /dev/null +++ b/trait.impl/core/ops/deref/trait.DerefMut.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl DerefMut for Theme"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[302]} \ No newline at end of file diff --git a/trait.impl/core/panic/unwind_safe/trait.RefUnwindSafe.js b/trait.impl/core/panic/unwind_safe/trait.RefUnwindSafe.js new file mode 100644 index 0000000000..e84a14f8e8 --- /dev/null +++ b/trait.impl/core/panic/unwind_safe/trait.RefUnwindSafe.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl !RefUnwindSafe for CalendarError",1,["i3status_rs::blocks::calendar::CalendarError"]],["impl !RefUnwindSafe for BlockConfig",1,["i3status_rs::blocks::BlockConfig"]],["impl !RefUnwindSafe for FormatError",1,["i3status_rs::formatting::FormatError"]],["impl !RefUnwindSafe for Token",1,["i3status_rs::formatting::template::Token"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::amd_gpu::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::backlight::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::battery::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::bluetooth::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::calendar::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::cpu::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::custom::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::custom_dbus::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::disk_iostats::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::disk_space::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::docker::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::external_ip::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::focused_window::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::github::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::hueshift::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::kdeconnect::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::keyboard_layout::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::load::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::maildir::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::memory::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::music::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::net::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::notify::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::notmuch::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::nvidia_gpu::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::packages::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::pomodoro::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::privacy::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::rofication::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::scratchpad::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::service_status::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::sound::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::speedtest::Config"]],["impl !RefUnwindSafe for BlockError",1,["i3status_rs::blocks::BlockError"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::taskwarrior::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::tea_timer::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::temperature::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::time::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::toggle::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::uptime::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::vpn::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::watson::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::weather::Config"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::blocks::xrandr::Config"]],["impl !RefUnwindSafe for BlockConfigEntry",1,["i3status_rs::config::BlockConfigEntry"]],["impl !RefUnwindSafe for CommonBlockConfig",1,["i3status_rs::config::CommonBlockConfig"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::config::Config"]],["impl !RefUnwindSafe for BoxErrorWrapper",1,["i3status_rs::errors::BoxErrorWrapper"]],["impl !RefUnwindSafe for Error",1,["i3status_rs::errors::Error"]],["impl !RefUnwindSafe for Config",1,["i3status_rs::formatting::config::Config"]],["impl !RefUnwindSafe for Format",1,["i3status_rs::formatting::Format"]],["impl !RefUnwindSafe for FormatTemplate",1,["i3status_rs::formatting::template::FormatTemplate"]],["impl !RefUnwindSafe for TokenList",1,["i3status_rs::formatting::template::TokenList"]],["impl !RefUnwindSafe for BarState",1,["i3status_rs::BarState"]],["impl !RefUnwindSafe for Block",1,["i3status_rs::Block"]],["impl !RefUnwindSafe for Widget",1,["i3status_rs::widget::Widget"]],["impl RefUnwindSafe for BatteryDriver",1,["i3status_rs::blocks::battery::BatteryDriver"]],["impl RefUnwindSafe for AuthConfig",1,["i3status_rs::blocks::calendar::AuthConfig"]],["impl RefUnwindSafe for InfoType",1,["i3status_rs::blocks::disk_space::InfoType"]],["impl RefUnwindSafe for Driver",1,["i3status_rs::blocks::focused_window::Driver"]],["impl RefUnwindSafe for HueShifter",1,["i3status_rs::blocks::hueshift::HueShifter"]],["impl RefUnwindSafe for KeyboardLayoutDriver",1,["i3status_rs::blocks::keyboard_layout::KeyboardLayoutDriver"]],["impl RefUnwindSafe for MailType",1,["i3status_rs::blocks::maildir::MailType"]],["impl RefUnwindSafe for PlayerName",1,["i3status_rs::blocks::music::PlayerName"]],["impl RefUnwindSafe for DriverType",1,["i3status_rs::blocks::notify::DriverType"]],["impl RefUnwindSafe for PackageManager",1,["i3status_rs::blocks::packages::PackageManager"]],["impl RefUnwindSafe for PrivacyDriver",1,["i3status_rs::blocks::privacy::PrivacyDriver"]],["impl RefUnwindSafe for DriverType",1,["i3status_rs::blocks::service_status::DriverType"]],["impl RefUnwindSafe for DeviceKind",1,["i3status_rs::blocks::sound::DeviceKind"]],["impl RefUnwindSafe for SoundDriver",1,["i3status_rs::blocks::sound::SoundDriver"]],["impl RefUnwindSafe for TemperatureScale",1,["i3status_rs::blocks::temperature::TemperatureScale"]],["impl RefUnwindSafe for Timezone",1,["i3status_rs::blocks::time::Timezone"]],["impl RefUnwindSafe for DriverType",1,["i3status_rs::blocks::vpn::DriverType"]],["impl RefUnwindSafe for WeatherService",1,["i3status_rs::blocks::weather::WeatherService"]],["impl RefUnwindSafe for MouseButton",1,["i3status_rs::click::MouseButton"]],["impl RefUnwindSafe for DatetimeFormatter",1,["i3status_rs::formatting::formatter::datetime::DatetimeFormatter"]],["impl RefUnwindSafe for Prefix",1,["i3status_rs::formatting::prefix::Prefix"]],["impl RefUnwindSafe for Unit",1,["i3status_rs::formatting::unit::Unit"]],["impl RefUnwindSafe for ValueInner",1,["i3status_rs::formatting::value::ValueInner"]],["impl RefUnwindSafe for GeolocatorBackend",1,["i3status_rs::geolocator::GeolocatorBackend"]],["impl RefUnwindSafe for Icon",1,["i3status_rs::icons::Icon"]],["impl RefUnwindSafe for I3BarBlockAlign",1,["i3status_rs::protocol::i3bar_block::I3BarBlockAlign"]],["impl RefUnwindSafe for I3BarBlockMinWidth",1,["i3status_rs::protocol::i3bar_block::I3BarBlockMinWidth"]],["impl RefUnwindSafe for Color",1,["i3status_rs::themes::color::Color"]],["impl RefUnwindSafe for ColorOrLink",1,["i3status_rs::themes::ColorOrLink"]],["impl RefUnwindSafe for Separator",1,["i3status_rs::themes::separator::Separator"]],["impl RefUnwindSafe for State",1,["i3status_rs::widget::State"]],["impl RefUnwindSafe for Device",1,["i3status_rs::blocks::amd_gpu::Device"]],["impl RefUnwindSafe for BasicAuthConfig",1,["i3status_rs::blocks::calendar::BasicAuthConfig"]],["impl RefUnwindSafe for BasicCredentials",1,["i3status_rs::blocks::calendar::BasicCredentials"]],["impl RefUnwindSafe for OAuth2Config",1,["i3status_rs::blocks::calendar::OAuth2Config"]],["impl RefUnwindSafe for OAuth2Credentials",1,["i3status_rs::blocks::calendar::OAuth2Credentials"]],["impl RefUnwindSafe for SourceConfig",1,["i3status_rs::blocks::calendar::SourceConfig"]],["impl RefUnwindSafe for Config",1,["i3status_rs::blocks::menu::Config"]],["impl RefUnwindSafe for Item",1,["i3status_rs::blocks::menu::Item"]],["impl RefUnwindSafe for Apt",1,["i3status_rs::blocks::packages::apt::Apt"]],["impl RefUnwindSafe for Dnf",1,["i3status_rs::blocks::packages::dnf::Dnf"]],["impl RefUnwindSafe for Aur",1,["i3status_rs::blocks::packages::pacman::Aur"]],["impl RefUnwindSafe for Pacman",1,["i3status_rs::blocks::packages::pacman::Pacman"]],["impl RefUnwindSafe for Xbps",1,["i3status_rs::blocks::packages::xbps::Xbps"]],["impl RefUnwindSafe for CommonApi",1,["i3status_rs::blocks::CommonApi"]],["impl RefUnwindSafe for Filter",1,["i3status_rs::blocks::taskwarrior::Filter"]],["impl RefUnwindSafe for Config",1,["i3status_rs::blocks::weather::met_no::Config"]],["impl RefUnwindSafe for Config",1,["i3status_rs::blocks::weather::nws::Config"]],["impl RefUnwindSafe for Config",1,["i3status_rs::blocks::weather::open_weather_map::Config"]],["impl RefUnwindSafe for ClickConfigEntry",1,["i3status_rs::click::ClickConfigEntry"]],["impl RefUnwindSafe for ClickHandler",1,["i3status_rs::click::ClickHandler"]],["impl RefUnwindSafe for PostActions",1,["i3status_rs::click::PostActions"]],["impl RefUnwindSafe for SharedConfig",1,["i3status_rs::config::SharedConfig"]],["impl RefUnwindSafe for BarFormatter",1,["i3status_rs::formatting::formatter::bar::BarFormatter"]],["impl RefUnwindSafe for DurationFormatter",1,["i3status_rs::formatting::formatter::duration::DurationFormatter"]],["impl RefUnwindSafe for EngFormatter",1,["i3status_rs::formatting::formatter::eng::EngFormatter"]],["impl RefUnwindSafe for FlagFormatter",1,["i3status_rs::formatting::formatter::flag::FlagFormatter"]],["impl RefUnwindSafe for PangoStrFormatter",1,["i3status_rs::formatting::formatter::pango::PangoStrFormatter"]],["impl RefUnwindSafe for StrFormatter",1,["i3status_rs::formatting::formatter::str::StrFormatter"]],["impl RefUnwindSafe for TallyFormatter",1,["i3status_rs::formatting::formatter::tally::TallyFormatter"]],["impl RefUnwindSafe for Fragment",1,["i3status_rs::formatting::Fragment"]],["impl RefUnwindSafe for Metadata",1,["i3status_rs::formatting::Metadata"]],["impl RefUnwindSafe for Value",1,["i3status_rs::formatting::value::Value"]],["impl RefUnwindSafe for Geolocator",1,["i3status_rs::geolocator::Geolocator"]],["impl RefUnwindSafe for IPAddressInfo",1,["i3status_rs::geolocator::IPAddressInfo"]],["impl RefUnwindSafe for Icons",1,["i3status_rs::icons::Icons"]],["impl RefUnwindSafe for I3BarBlock",1,["i3status_rs::protocol::i3bar_block::I3BarBlock"]],["impl RefUnwindSafe for I3BarEvent",1,["i3status_rs::protocol::i3bar_event::I3BarEvent"]],["impl RefUnwindSafe for CliArgs",1,["i3status_rs::CliArgs"]],["impl RefUnwindSafe for Hsva",1,["i3status_rs::themes::color::Hsva"]],["impl RefUnwindSafe for Rgba",1,["i3status_rs::themes::color::Rgba"]],["impl RefUnwindSafe for Theme",1,["i3status_rs::themes::Theme"]],["impl RefUnwindSafe for ThemeInner",1,["i3status_rs::themes::ThemeInner"]],["impl RefUnwindSafe for ThemeOverrides",1,["i3status_rs::themes::ThemeOverrides"]],["impl RefUnwindSafe for ThemeUserConfig",1,["i3status_rs::themes::ThemeUserConfig"]],["impl<'a> RefUnwindSafe for Token<'a>",1,["i3status_rs::formatting::parse::Token"]],["impl<'a> RefUnwindSafe for Arg<'a>",1,["i3status_rs::formatting::parse::Arg"]],["impl<'a> RefUnwindSafe for FormatTemplate<'a>",1,["i3status_rs::formatting::parse::FormatTemplate"]],["impl<'a> RefUnwindSafe for Formatter<'a>",1,["i3status_rs::formatting::parse::Formatter"]],["impl<'a> RefUnwindSafe for Placeholder<'a>",1,["i3status_rs::formatting::parse::Placeholder"]],["impl<'a> RefUnwindSafe for TokenList<'a>",1,["i3status_rs::formatting::parse::TokenList"]]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[54837]} \ No newline at end of file diff --git a/trait.impl/core/panic/unwind_safe/trait.UnwindSafe.js b/trait.impl/core/panic/unwind_safe/trait.UnwindSafe.js new file mode 100644 index 0000000000..3e375fa1a0 --- /dev/null +++ b/trait.impl/core/panic/unwind_safe/trait.UnwindSafe.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl !UnwindSafe for CalendarError",1,["i3status_rs::blocks::calendar::CalendarError"]],["impl !UnwindSafe for BlockConfig",1,["i3status_rs::blocks::BlockConfig"]],["impl !UnwindSafe for FormatError",1,["i3status_rs::formatting::FormatError"]],["impl !UnwindSafe for Token",1,["i3status_rs::formatting::template::Token"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::amd_gpu::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::backlight::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::battery::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::bluetooth::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::calendar::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::cpu::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::custom::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::custom_dbus::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::disk_iostats::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::disk_space::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::docker::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::external_ip::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::focused_window::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::github::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::hueshift::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::kdeconnect::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::keyboard_layout::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::load::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::maildir::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::memory::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::music::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::net::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::notify::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::notmuch::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::nvidia_gpu::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::packages::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::pomodoro::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::privacy::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::rofication::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::scratchpad::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::service_status::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::sound::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::speedtest::Config"]],["impl !UnwindSafe for BlockError",1,["i3status_rs::blocks::BlockError"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::taskwarrior::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::tea_timer::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::temperature::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::time::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::toggle::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::uptime::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::vpn::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::watson::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::weather::Config"]],["impl !UnwindSafe for Config",1,["i3status_rs::blocks::xrandr::Config"]],["impl !UnwindSafe for BlockConfigEntry",1,["i3status_rs::config::BlockConfigEntry"]],["impl !UnwindSafe for CommonBlockConfig",1,["i3status_rs::config::CommonBlockConfig"]],["impl !UnwindSafe for Config",1,["i3status_rs::config::Config"]],["impl !UnwindSafe for BoxErrorWrapper",1,["i3status_rs::errors::BoxErrorWrapper"]],["impl !UnwindSafe for Error",1,["i3status_rs::errors::Error"]],["impl !UnwindSafe for Config",1,["i3status_rs::formatting::config::Config"]],["impl !UnwindSafe for Format",1,["i3status_rs::formatting::Format"]],["impl !UnwindSafe for FormatTemplate",1,["i3status_rs::formatting::template::FormatTemplate"]],["impl !UnwindSafe for TokenList",1,["i3status_rs::formatting::template::TokenList"]],["impl !UnwindSafe for BarState",1,["i3status_rs::BarState"]],["impl !UnwindSafe for Block",1,["i3status_rs::Block"]],["impl !UnwindSafe for Widget",1,["i3status_rs::widget::Widget"]],["impl UnwindSafe for BatteryDriver",1,["i3status_rs::blocks::battery::BatteryDriver"]],["impl UnwindSafe for AuthConfig",1,["i3status_rs::blocks::calendar::AuthConfig"]],["impl UnwindSafe for InfoType",1,["i3status_rs::blocks::disk_space::InfoType"]],["impl UnwindSafe for Driver",1,["i3status_rs::blocks::focused_window::Driver"]],["impl UnwindSafe for HueShifter",1,["i3status_rs::blocks::hueshift::HueShifter"]],["impl UnwindSafe for KeyboardLayoutDriver",1,["i3status_rs::blocks::keyboard_layout::KeyboardLayoutDriver"]],["impl UnwindSafe for MailType",1,["i3status_rs::blocks::maildir::MailType"]],["impl UnwindSafe for PlayerName",1,["i3status_rs::blocks::music::PlayerName"]],["impl UnwindSafe for DriverType",1,["i3status_rs::blocks::notify::DriverType"]],["impl UnwindSafe for PackageManager",1,["i3status_rs::blocks::packages::PackageManager"]],["impl UnwindSafe for PrivacyDriver",1,["i3status_rs::blocks::privacy::PrivacyDriver"]],["impl UnwindSafe for DriverType",1,["i3status_rs::blocks::service_status::DriverType"]],["impl UnwindSafe for DeviceKind",1,["i3status_rs::blocks::sound::DeviceKind"]],["impl UnwindSafe for SoundDriver",1,["i3status_rs::blocks::sound::SoundDriver"]],["impl UnwindSafe for TemperatureScale",1,["i3status_rs::blocks::temperature::TemperatureScale"]],["impl UnwindSafe for Timezone",1,["i3status_rs::blocks::time::Timezone"]],["impl UnwindSafe for DriverType",1,["i3status_rs::blocks::vpn::DriverType"]],["impl UnwindSafe for WeatherService",1,["i3status_rs::blocks::weather::WeatherService"]],["impl UnwindSafe for MouseButton",1,["i3status_rs::click::MouseButton"]],["impl UnwindSafe for DatetimeFormatter",1,["i3status_rs::formatting::formatter::datetime::DatetimeFormatter"]],["impl UnwindSafe for Prefix",1,["i3status_rs::formatting::prefix::Prefix"]],["impl UnwindSafe for Unit",1,["i3status_rs::formatting::unit::Unit"]],["impl UnwindSafe for ValueInner",1,["i3status_rs::formatting::value::ValueInner"]],["impl UnwindSafe for GeolocatorBackend",1,["i3status_rs::geolocator::GeolocatorBackend"]],["impl UnwindSafe for Icon",1,["i3status_rs::icons::Icon"]],["impl UnwindSafe for I3BarBlockAlign",1,["i3status_rs::protocol::i3bar_block::I3BarBlockAlign"]],["impl UnwindSafe for I3BarBlockMinWidth",1,["i3status_rs::protocol::i3bar_block::I3BarBlockMinWidth"]],["impl UnwindSafe for Color",1,["i3status_rs::themes::color::Color"]],["impl UnwindSafe for ColorOrLink",1,["i3status_rs::themes::ColorOrLink"]],["impl UnwindSafe for Separator",1,["i3status_rs::themes::separator::Separator"]],["impl UnwindSafe for State",1,["i3status_rs::widget::State"]],["impl UnwindSafe for Device",1,["i3status_rs::blocks::amd_gpu::Device"]],["impl UnwindSafe for BasicAuthConfig",1,["i3status_rs::blocks::calendar::BasicAuthConfig"]],["impl UnwindSafe for BasicCredentials",1,["i3status_rs::blocks::calendar::BasicCredentials"]],["impl UnwindSafe for OAuth2Config",1,["i3status_rs::blocks::calendar::OAuth2Config"]],["impl UnwindSafe for OAuth2Credentials",1,["i3status_rs::blocks::calendar::OAuth2Credentials"]],["impl UnwindSafe for SourceConfig",1,["i3status_rs::blocks::calendar::SourceConfig"]],["impl UnwindSafe for Config",1,["i3status_rs::blocks::menu::Config"]],["impl UnwindSafe for Item",1,["i3status_rs::blocks::menu::Item"]],["impl UnwindSafe for Apt",1,["i3status_rs::blocks::packages::apt::Apt"]],["impl UnwindSafe for Dnf",1,["i3status_rs::blocks::packages::dnf::Dnf"]],["impl UnwindSafe for Aur",1,["i3status_rs::blocks::packages::pacman::Aur"]],["impl UnwindSafe for Pacman",1,["i3status_rs::blocks::packages::pacman::Pacman"]],["impl UnwindSafe for Xbps",1,["i3status_rs::blocks::packages::xbps::Xbps"]],["impl UnwindSafe for CommonApi",1,["i3status_rs::blocks::CommonApi"]],["impl UnwindSafe for Filter",1,["i3status_rs::blocks::taskwarrior::Filter"]],["impl UnwindSafe for Config",1,["i3status_rs::blocks::weather::met_no::Config"]],["impl UnwindSafe for Config",1,["i3status_rs::blocks::weather::nws::Config"]],["impl UnwindSafe for Config",1,["i3status_rs::blocks::weather::open_weather_map::Config"]],["impl UnwindSafe for ClickConfigEntry",1,["i3status_rs::click::ClickConfigEntry"]],["impl UnwindSafe for ClickHandler",1,["i3status_rs::click::ClickHandler"]],["impl UnwindSafe for PostActions",1,["i3status_rs::click::PostActions"]],["impl UnwindSafe for SharedConfig",1,["i3status_rs::config::SharedConfig"]],["impl UnwindSafe for BarFormatter",1,["i3status_rs::formatting::formatter::bar::BarFormatter"]],["impl UnwindSafe for DurationFormatter",1,["i3status_rs::formatting::formatter::duration::DurationFormatter"]],["impl UnwindSafe for EngFormatter",1,["i3status_rs::formatting::formatter::eng::EngFormatter"]],["impl UnwindSafe for FlagFormatter",1,["i3status_rs::formatting::formatter::flag::FlagFormatter"]],["impl UnwindSafe for PangoStrFormatter",1,["i3status_rs::formatting::formatter::pango::PangoStrFormatter"]],["impl UnwindSafe for StrFormatter",1,["i3status_rs::formatting::formatter::str::StrFormatter"]],["impl UnwindSafe for TallyFormatter",1,["i3status_rs::formatting::formatter::tally::TallyFormatter"]],["impl UnwindSafe for Fragment",1,["i3status_rs::formatting::Fragment"]],["impl UnwindSafe for Metadata",1,["i3status_rs::formatting::Metadata"]],["impl UnwindSafe for Value",1,["i3status_rs::formatting::value::Value"]],["impl UnwindSafe for Geolocator",1,["i3status_rs::geolocator::Geolocator"]],["impl UnwindSafe for IPAddressInfo",1,["i3status_rs::geolocator::IPAddressInfo"]],["impl UnwindSafe for Icons",1,["i3status_rs::icons::Icons"]],["impl UnwindSafe for I3BarBlock",1,["i3status_rs::protocol::i3bar_block::I3BarBlock"]],["impl UnwindSafe for I3BarEvent",1,["i3status_rs::protocol::i3bar_event::I3BarEvent"]],["impl UnwindSafe for CliArgs",1,["i3status_rs::CliArgs"]],["impl UnwindSafe for Hsva",1,["i3status_rs::themes::color::Hsva"]],["impl UnwindSafe for Rgba",1,["i3status_rs::themes::color::Rgba"]],["impl UnwindSafe for Theme",1,["i3status_rs::themes::Theme"]],["impl UnwindSafe for ThemeInner",1,["i3status_rs::themes::ThemeInner"]],["impl UnwindSafe for ThemeOverrides",1,["i3status_rs::themes::ThemeOverrides"]],["impl UnwindSafe for ThemeUserConfig",1,["i3status_rs::themes::ThemeUserConfig"]],["impl<'a> UnwindSafe for Token<'a>",1,["i3status_rs::formatting::parse::Token"]],["impl<'a> UnwindSafe for Arg<'a>",1,["i3status_rs::formatting::parse::Arg"]],["impl<'a> UnwindSafe for FormatTemplate<'a>",1,["i3status_rs::formatting::parse::FormatTemplate"]],["impl<'a> UnwindSafe for Formatter<'a>",1,["i3status_rs::formatting::parse::Formatter"]],["impl<'a> UnwindSafe for Placeholder<'a>",1,["i3status_rs::formatting::parse::Placeholder"]],["impl<'a> UnwindSafe for TokenList<'a>",1,["i3status_rs::formatting::parse::TokenList"]]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[53568]} \ No newline at end of file diff --git a/trait.impl/core/str/traits/trait.FromStr.js b/trait.impl/core/str/traits/trait.FromStr.js new file mode 100644 index 0000000000..1db0212ae7 --- /dev/null +++ b/trait.impl/core/str/traits/trait.FromStr.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl FromStr for Prefix"],["impl FromStr for Unit"],["impl FromStr for Color"],["impl FromStr for Separator"],["impl FromStr for Config"],["impl FromStr for FormatTemplate"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[1865]} \ No newline at end of file diff --git a/trait.impl/i3status_rs/blocks/packages/trait.Backend.js b/trait.impl/i3status_rs/blocks/packages/trait.Backend.js new file mode 100644 index 0000000000..18cd5e57c9 --- /dev/null +++ b/trait.impl/i3status_rs/blocks/packages/trait.Backend.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[18]} \ No newline at end of file diff --git a/trait.impl/i3status_rs/errors/trait.ErrorContext.js b/trait.impl/i3status_rs/errors/trait.ErrorContext.js new file mode 100644 index 0000000000..18cd5e57c9 --- /dev/null +++ b/trait.impl/i3status_rs/errors/trait.ErrorContext.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[18]} \ No newline at end of file diff --git a/trait.impl/i3status_rs/errors/trait.ToSerdeError.js b/trait.impl/i3status_rs/errors/trait.ToSerdeError.js new file mode 100644 index 0000000000..18cd5e57c9 --- /dev/null +++ b/trait.impl/i3status_rs/errors/trait.ToSerdeError.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[18]} \ No newline at end of file diff --git a/trait.impl/i3status_rs/escape/trait.CollectEscaped.js b/trait.impl/i3status_rs/escape/trait.CollectEscaped.js new file mode 100644 index 0000000000..18cd5e57c9 --- /dev/null +++ b/trait.impl/i3status_rs/escape/trait.CollectEscaped.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[18]} \ No newline at end of file diff --git a/trait.impl/i3status_rs/escape/trait.Escaped.js b/trait.impl/i3status_rs/escape/trait.Escaped.js new file mode 100644 index 0000000000..18cd5e57c9 --- /dev/null +++ b/trait.impl/i3status_rs/escape/trait.Escaped.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[18]} \ No newline at end of file diff --git a/trait.impl/i3status_rs/formatting/formatter/trait.Formatter.js b/trait.impl/i3status_rs/formatting/formatter/trait.Formatter.js new file mode 100644 index 0000000000..18cd5e57c9 --- /dev/null +++ b/trait.impl/i3status_rs/formatting/formatter/trait.Formatter.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[18]} \ No newline at end of file diff --git a/trait.impl/i3status_rs/formatting/value/trait.IntoF64.js b/trait.impl/i3status_rs/formatting/value/trait.IntoF64.js new file mode 100644 index 0000000000..18cd5e57c9 --- /dev/null +++ b/trait.impl/i3status_rs/formatting/value/trait.IntoF64.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[18]} \ No newline at end of file diff --git a/trait.impl/i3status_rs/util/trait.StreamExtDebounced.js b/trait.impl/i3status_rs/util/trait.StreamExtDebounced.js new file mode 100644 index 0000000000..18cd5e57c9 --- /dev/null +++ b/trait.impl/i3status_rs/util/trait.StreamExtDebounced.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[18]} \ No newline at end of file diff --git a/trait.impl/serde/de/trait.Deserialize.js b/trait.impl/serde/de/trait.Deserialize.js new file mode 100644 index 0000000000..220d77629f --- /dev/null +++ b/trait.impl/serde/de/trait.Deserialize.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl<'de> Deserialize<'de> for BatteryDriver"],["impl<'de> Deserialize<'de> for AuthConfig"],["impl<'de> Deserialize<'de> for InfoType"],["impl<'de> Deserialize<'de> for BlockConfig"],["impl<'de> Deserialize<'de> for Driver"],["impl<'de> Deserialize<'de> for HueShifter"],["impl<'de> Deserialize<'de> for KeyboardLayoutDriver"],["impl<'de> Deserialize<'de> for MailType"],["impl<'de> Deserialize<'de> for PlayerName"],["impl<'de> Deserialize<'de> for DriverType"],["impl<'de> Deserialize<'de> for PackageManager"],["impl<'de> Deserialize<'de> for PrivacyDriver"],["impl<'de> Deserialize<'de> for DriverType"],["impl<'de> Deserialize<'de> for DeviceKind"],["impl<'de> Deserialize<'de> for SoundDriver"],["impl<'de> Deserialize<'de> for TemperatureScale"],["impl<'de> Deserialize<'de> for Timezone"],["impl<'de> Deserialize<'de> for DriverType"],["impl<'de> Deserialize<'de> for WeatherService"],["impl<'de> Deserialize<'de> for MouseButton"],["impl<'de> Deserialize<'de> for GeolocatorBackend"],["impl<'de> Deserialize<'de> for Icon"],["impl<'de> Deserialize<'de> for Color"],["impl<'de> Deserialize<'de> for ColorOrLink"],["impl<'de> Deserialize<'de> for Separator"],["impl<'de> Deserialize<'de> for State"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config"],["impl<'de> Deserialize<'de> for BasicAuthConfig"],["impl<'de> Deserialize<'de> for BasicCredentials"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for OAuth2Config"],["impl<'de> Deserialize<'de> for OAuth2Credentials"],["impl<'de> Deserialize<'de> for SourceConfig"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config"],["impl<'de> Deserialize<'de> for Item"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Filter"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for Config"],["impl<'de> Deserialize<'de> for Config
where\n Config: Default,
"],["impl<'de> Deserialize<'de> for ClickConfigEntry"],["impl<'de> Deserialize<'de> for ClickHandler"],["impl<'de> Deserialize<'de> for BlockConfigEntry"],["impl<'de> Deserialize<'de> for CommonBlockConfig"],["impl<'de> Deserialize<'de> for Config"],["impl<'de> Deserialize<'de> for SharedConfig"],["impl<'de> Deserialize<'de> for Config"],["impl<'de> Deserialize<'de> for Geolocator"],["impl<'de> Deserialize<'de> for IPAddressInfo"],["impl<'de> Deserialize<'de> for Icons"],["impl<'de> Deserialize<'de> for ThemeInner"],["impl<'de> Deserialize<'de> for ThemeOverrides"],["impl<'de> Deserialize<'de> for ThemeUserConfig"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[45969]} \ No newline at end of file diff --git a/trait.impl/serde/ser/trait.Serialize.js b/trait.impl/serde/ser/trait.Serialize.js new file mode 100644 index 0000000000..afcc945586 --- /dev/null +++ b/trait.impl/serde/ser/trait.Serialize.js @@ -0,0 +1,9 @@ +(function() { + var implementors = Object.fromEntries([["i3status_rs",[["impl Serialize for I3BarBlockAlign"],["impl Serialize for I3BarBlockMinWidth"],["impl Serialize for Color"],["impl Serialize for I3BarBlock"]]]]); + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } +})() +//{"start":57,"fragment_lengths":[1279]} \ No newline at end of file diff --git a/type.impl/alloc/borrow/enum.Cow.js b/type.impl/alloc/borrow/enum.Cow.js new file mode 100644 index 0000000000..0e2a7f70e5 --- /dev/null +++ b/type.impl/alloc/borrow/enum.Cow.js @@ -0,0 +1,9 @@ +(function() { + var type_impls = Object.fromEntries([["i3status_rs",[["
1.14.0 · Source§

impl<'a> Add<&'a str> for Cow<'a, str>

Source§

type Output = Cow<'a, str>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: &'a str) -> <Cow<'a, str> as Add<&'a str>>::Output

Performs the + operation. Read more
","Add<&'a str>","i3status_rs::blocks::BlockAction"],["
1.14.0 · Source§

impl<'a> Add for Cow<'a, str>

Source§

type Output = Cow<'a, str>

The resulting type after applying the + operator.
Source§

fn add(self, rhs: Cow<'a, str>) -> <Cow<'a, str> as Add>::Output

Performs the + operation. Read more
","Add","i3status_rs::blocks::BlockAction"],["
1.14.0 · Source§

impl<'a> AddAssign<&'a str> for Cow<'a, str>

Source§

fn add_assign(&mut self, rhs: &'a str)

Performs the += operation. Read more
","AddAssign<&'a str>","i3status_rs::blocks::BlockAction"],["
1.14.0 · Source§

impl<'a> AddAssign for Cow<'a, str>

Source§

fn add_assign(&mut self, rhs: Cow<'a, str>)

Performs the += operation. Read more
","AddAssign","i3status_rs::blocks::BlockAction"],["
§

impl<'a> Arg for Cow<'a, str>

§

fn as_str(&self) -> Result<&str, Errno>

Returns a view of this string as a string slice.
§

fn to_string_lossy(&self) -> Cow<'_, str>

Returns a potentially-lossy rendering of this string as a\nCow<'_, str>.
§

fn as_cow_c_str(&self) -> Result<Cow<'_, CStr>, Errno>

Returns a view of this string as a maybe-owned CStr.
§

fn into_c_str<'b>(self) -> Result<Cow<'b, CStr>, Errno>
where\n Cow<'a, str>: 'b,

Consumes self and returns a view of this string as a maybe-owned\nCStr.
§

fn into_with_c_str<T, F>(self, f: F) -> Result<T, Errno>
where\n Cow<'a, str>: Sized,\n F: FnOnce(&CStr) -> Result<T, Errno>,

Runs a closure with self passed in as a &CStr.
","Arg","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<T> AsRef<T> for Cow<'_, T>
where\n T: ToOwned + ?Sized,

Source§

fn as_ref(&self) -> &T

Converts this type into a shared reference of the (usually inferred) input type.
","AsRef","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<'a, B> Borrow<B> for Cow<'a, B>
where\n B: ToOwned + ?Sized,

Source§

fn borrow(&self) -> &B

Immutably borrows from an owned value. Read more
","Borrow","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<B> Clone for Cow<'_, B>
where\n B: ToOwned + ?Sized,

Source§

fn clone(&self) -> Cow<'_, B>

Returns a duplicate of the value. Read more
Source§

fn clone_from(&mut self, source: &Cow<'_, B>)

Performs copy-assignment from source. Read more
","Clone","i3status_rs::blocks::BlockAction"],["
§

impl<C> Connection for Cow<'_, C>
where\n C: Connection + ToOwned + ?Sized,

§

fn wait_for_event(&self) -> Result<Event, ConnectionError>

Wait for a new event from the X11 server.
§

fn wait_for_raw_event(\n &self,\n) -> Result<<Cow<'_, C> as RequestConnection>::Buf, ConnectionError>

Wait for a new raw/unparsed event from the X11 server.
§

fn wait_for_event_with_sequence(&self) -> Result<(Event, u64), ConnectionError>

Wait for a new event from the X11 server.
§

fn wait_for_raw_event_with_sequence(\n &self,\n) -> Result<(<Cow<'_, C> as RequestConnection>::Buf, u64), ConnectionError>

Wait for a new raw/unparsed event from the X11 server.
§

fn poll_for_event(&self) -> Result<Option<Event>, ConnectionError>

Poll for a new event from the X11 server.
§

fn poll_for_raw_event(\n &self,\n) -> Result<Option<<Cow<'_, C> as RequestConnection>::Buf>, ConnectionError>

Poll for a new raw/unparsed event from the X11 server.
§

fn poll_for_event_with_sequence(\n &self,\n) -> Result<Option<(Event, u64)>, ConnectionError>

Poll for a new event from the X11 server.
§

fn poll_for_raw_event_with_sequence(\n &self,\n) -> Result<Option<(<Cow<'_, C> as RequestConnection>::Buf, u64)>, ConnectionError>

Poll for a new unparsed/raw event from the X11 server.
§

fn flush(&self) -> Result<(), ConnectionError>

Send all pending requests to the server. Read more
§

fn setup(&self) -> &Setup

Get the setup information sent by the X11 server. Read more
§

fn generate_id(&self) -> Result<u32, ReplyOrIdError>

Generate a new X11 identifier. Read more
","Connection","i3status_rs::blocks::BlockAction"],["
Source§

impl<B> Cow<'_, B>
where\n B: ToOwned + ?Sized,

Source

pub const fn is_borrowed(&self) -> bool

🔬This is a nightly-only experimental API. (cow_is_borrowed)

Returns true if the data is borrowed, i.e. if to_mut would require additional work.

\n
§Examples
\n
#![feature(cow_is_borrowed)]\nuse std::borrow::Cow;\n\nlet cow = Cow::Borrowed(\"moo\");\nassert!(cow.is_borrowed());\n\nlet bull: Cow<'_, str> = Cow::Owned(\"...moo?\".to_string());\nassert!(!bull.is_borrowed());
\n
Source

pub const fn is_owned(&self) -> bool

🔬This is a nightly-only experimental API. (cow_is_borrowed)

Returns true if the data is owned, i.e. if to_mut would be a no-op.

\n
§Examples
\n
#![feature(cow_is_borrowed)]\nuse std::borrow::Cow;\n\nlet cow: Cow<'_, str> = Cow::Owned(\"moo\".to_string());\nassert!(cow.is_owned());\n\nlet bull = Cow::Borrowed(\"...moo?\");\nassert!(!bull.is_owned());
\n
1.0.0 · Source

pub fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned

Acquires a mutable reference to the owned form of the data.

\n

Clones the data if it is not already owned.

\n
§Examples
\n
use std::borrow::Cow;\n\nlet mut cow = Cow::Borrowed(\"foo\");\ncow.to_mut().make_ascii_uppercase();\n\nassert_eq!(\n  cow,\n  Cow::Owned(String::from(\"FOO\")) as Cow<'_, str>\n);
\n
1.0.0 · Source

pub fn into_owned(self) -> <B as ToOwned>::Owned

Extracts the owned data.

\n

Clones the data if it is not already owned.

\n
§Examples
\n

Calling into_owned on a Cow::Borrowed returns a clone of the borrowed data:

\n\n
use std::borrow::Cow;\n\nlet s = \"Hello world!\";\nlet cow = Cow::Borrowed(s);\n\nassert_eq!(\n  cow.into_owned(),\n  String::from(s)\n);
\n

Calling into_owned on a Cow::Owned returns the owned data. The data is moved out of the\nCow without being cloned.

\n\n
use std::borrow::Cow;\n\nlet s = \"Hello world!\";\nlet cow: Cow<'_, str> = Cow::Owned(String::from(s));\n\nassert_eq!(\n  cow.into_owned(),\n  String::from(s)\n);
\n
",0,"i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<B> Debug for Cow<'_, B>
where\n B: Debug + ToOwned + ?Sized,\n <B as ToOwned>::Owned: Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
","Debug","i3status_rs::blocks::BlockAction"],["
1.11.0 · Source§

impl<B> Default for Cow<'_, B>
where\n B: ToOwned + ?Sized,\n <B as ToOwned>::Owned: Default,

Source§

fn default() -> Cow<'_, B>

Creates an owned Cow<’a, B> with the default value for the contained owned value.

\n
","Default","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<B> Deref for Cow<'_, B>
where\n B: ToOwned + ?Sized,\n <B as ToOwned>::Owned: Borrow<B>,

Source§

type Target = B

The resulting type after dereferencing.
Source§

fn deref(&self) -> &B

Dereferences the value.
","Deref","i3status_rs::blocks::BlockAction"],["
Source§

impl<'de, 'a, T> Deserialize<'de> for Cow<'a, T>
where\n T: ToOwned + ?Sized,\n <T as ToOwned>::Owned: Deserialize<'de>,

Source§

fn deserialize<D>(\n deserializer: D,\n) -> Result<Cow<'a, T>, <D as Deserializer<'de>>::Error>
where\n D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
","Deserialize<'de>","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<B> Display for Cow<'_, B>
where\n B: Display + ToOwned + ?Sized,\n <B as ToOwned>::Owned: Display,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
","Display","i3status_rs::blocks::BlockAction"],["
§

impl<T> EncodeAsVarULE<T> for Cow<'_, T>
where\n T: VarULE + ToOwned + ?Sized,

§

fn encode_var_ule_as_slices<R>(&self, cb: impl FnOnce(&[&[u8]]) -> R) -> R

Calls cb with a piecewise list of byte slices that when concatenated\nproduce the memory pattern of the corresponding instance of T. Read more
§

fn encode_var_ule_len(&self) -> usize

Return the length, in bytes, of the corresponding [VarULE] type
§

fn encode_var_ule_write(&self, dst: &mut [u8])

Write the corresponding [VarULE] type to the dst buffer. dst should\nbe the size of [Self::encode_var_ule_len()]
","EncodeAsVarULE","i3status_rs::blocks::BlockAction"],["
1.28.0 · Source§

impl<'a> From<&'a String> for Cow<'a, str>

Source§

fn from(s: &'a String) -> Cow<'a, str>

Converts a String reference into a Borrowed variant.\nNo heap allocation is performed, and the string\nis not copied.

\n
§Example
\n
let s = \"eggplant\".to_string();\nassert_eq!(Cow::from(&s), Cow::Borrowed(\"eggplant\"));
\n
","From<&'a String>","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<'a> From<&'a str> for Cow<'a, str>

Source§

fn from(s: &'a str) -> Cow<'a, str>

Converts a string slice into a Borrowed variant.\nNo heap allocation is performed, and the string\nis not copied.

\n
§Example
\n
assert_eq!(Cow::from(\"eggplant\"), Cow::Borrowed(\"eggplant\"));
\n
","From<&'a str>","i3status_rs::blocks::BlockAction"],["
§

impl<'key> From<Key<'key>> for Cow<'static, str>

§

fn from(key: Key<'key>) -> Cow<'static, str>

Converts to this type from the input type.
","From>","i3status_rs::blocks::BlockAction"],["
§

impl<'a> From<PercentEncode<'a>> for Cow<'a, str>

§

fn from(iter: PercentEncode<'a>) -> Cow<'a, str>

Converts to this type from the input type.
","From>","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<'a> From<String> for Cow<'a, str>

Source§

fn from(s: String) -> Cow<'a, str>

Converts a String into an Owned variant.\nNo heap allocation is performed, and the string\nis not copied.

\n
§Example
\n
let s = \"eggplant\".to_string();\nlet s2 = \"eggplant\".to_string();\nassert_eq!(Cow::from(s), Cow::<'static, str>::Owned(s2));
\n
","From","i3status_rs::blocks::BlockAction"],["
1.12.0 · Source§

impl<'a, 'b> FromIterator<&'b str> for Cow<'a, str>

Source§

fn from_iter<I>(it: I) -> Cow<'a, str>
where\n I: IntoIterator<Item = &'b str>,

Creates a value from an iterator. Read more
","FromIterator<&'b str>","i3status_rs::blocks::BlockAction"],["
1.12.0 · Source§

impl<'a> FromIterator<String> for Cow<'a, str>

Source§

fn from_iter<I>(it: I) -> Cow<'a, str>
where\n I: IntoIterator<Item = String>,

Creates a value from an iterator. Read more
","FromIterator","i3status_rs::blocks::BlockAction"],["
1.12.0 · Source§

impl<'a> FromIterator<char> for Cow<'a, str>

Source§

fn from_iter<I>(it: I) -> Cow<'a, str>
where\n I: IntoIterator<Item = char>,

Creates a value from an iterator. Read more
","FromIterator","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<B> Hash for Cow<'_, B>
where\n B: Hash + ToOwned + ?Sized,

Source§

fn hash<H>(&self, state: &mut H)
where\n H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where\n H: Hasher,\n Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
","Hash","i3status_rs::blocks::BlockAction"],["
Source§

impl<T> IdentFragment for Cow<'_, T>
where\n T: IdentFragment + ToOwned + ?Sized,

Source§

fn span(&self) -> Option<Span>

Span associated with this IdentFragment. Read more
Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Format this value as an identifier fragment.
","IdentFragment","i3status_rs::blocks::BlockAction"],["
Source§

impl<'de, 'a, E> IntoDeserializer<'de, E> for Cow<'a, str>
where\n E: Error,

Source§

type Deserializer = CowStrDeserializer<'a, E>

The type of the deserializer being converted into.
Source§

fn into_deserializer(self) -> CowStrDeserializer<'a, E>

Convert this value into a deserializer.
","IntoDeserializer<'de, E>","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<B> Ord for Cow<'_, B>
where\n B: Ord + ToOwned + ?Sized,

Source§

fn cmp(&self, other: &Cow<'_, B>) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where\n Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where\n Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where\n Self: Sized,

Restrict a value to a certain interval. Read more
","Ord","i3status_rs::blocks::BlockAction"],["
Source§

impl<'a> PartialEq<&'a ByteStr> for Cow<'a, str>

Source§

fn eq(&self, other: &&'a ByteStr) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq<&'a ByteStr>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<&RiAbsoluteStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &&RiAbsoluteStr<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq<&RiAbsoluteStr>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<&RiFragmentStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &&RiFragmentStr<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq<&RiFragmentStr>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<&RiQueryStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &&RiQueryStr<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq<&RiQueryStr>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<&RiReferenceStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &&RiReferenceStr<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq<&RiReferenceStr>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<&RiRelativeStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &&RiRelativeStr<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq<&RiRelativeStr>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<&RiStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &&RiStr<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq<&RiStr>","i3status_rs::blocks::BlockAction"],["
§

impl PartialEq<&UriTemplateStr> for Cow<'_, str>

§

fn eq(&self, o: &&UriTemplateStr) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq<&UriTemplateStr>","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<'a, 'b> PartialEq<&'b str> for Cow<'a, str>

Source§

fn eq(&self, other: &&'b str) -> bool

Tests for self and other values to be equal, and is used by ==.
Source§

fn ne(&self, other: &&'b str) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq<&'b str>","i3status_rs::blocks::BlockAction"],["
Source§

impl<'a> PartialEq<ByteString> for Cow<'_, str>

Source§

fn eq(&self, other: &ByteString) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<'a, 'b, B, C> PartialEq<Cow<'b, C>> for Cow<'a, B>
where\n B: PartialEq<C> + ToOwned + ?Sized,\n C: ToOwned + ?Sized,

Source§

fn eq(&self, other: &Cow<'b, C>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<RiAbsoluteStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &RiAbsoluteStr<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<RiAbsoluteString<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &RiAbsoluteString<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<RiFragmentStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &RiFragmentStr<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<RiFragmentString<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &RiFragmentString<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<RiQueryStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &RiQueryStr<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<RiQueryString<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &RiQueryString<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<RiReferenceStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &RiReferenceStr<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<RiReferenceString<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &RiReferenceString<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<RiRelativeStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &RiRelativeStr<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<RiRelativeString<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &RiRelativeString<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<RiStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &RiStr<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialEq<RiString<S>> for Cow<'_, str>
where\n S: Spec,

§

fn eq(&self, o: &RiString<S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq>","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<'a, 'b> PartialEq<String> for Cow<'a, str>

Source§

fn eq(&self, other: &String) -> bool

Tests for self and other values to be equal, and is used by ==.
Source§

fn ne(&self, other: &String) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq","i3status_rs::blocks::BlockAction"],["
§

impl PartialEq<UriTemplateStr> for Cow<'_, str>

§

fn eq(&self, o: &UriTemplateStr) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq","i3status_rs::blocks::BlockAction"],["
§

impl PartialEq<UriTemplateString> for Cow<'_, str>

§

fn eq(&self, o: &UriTemplateString) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<'a, 'b> PartialEq<str> for Cow<'a, str>

Source§

fn eq(&self, other: &str) -> bool

Tests for self and other values to be equal, and is used by ==.
Source§

fn ne(&self, other: &str) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq","i3status_rs::blocks::BlockAction"],["
Source§

impl<'a> PartialOrd<&'a ByteStr> for Cow<'a, str>

Source§

fn partial_cmp(&self, other: &&'a ByteStr) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd<&'a ByteStr>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<&RiAbsoluteStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &&RiAbsoluteStr<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd<&RiAbsoluteStr>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<&RiFragmentStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &&RiFragmentStr<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd<&RiFragmentStr>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<&RiQueryStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &&RiQueryStr<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd<&RiQueryStr>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<&RiReferenceStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &&RiReferenceStr<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd<&RiReferenceStr>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<&RiRelativeStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &&RiRelativeStr<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd<&RiRelativeStr>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<&RiStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &&RiStr<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd<&RiStr>","i3status_rs::blocks::BlockAction"],["
§

impl PartialOrd<&UriTemplateStr> for Cow<'_, str>

§

fn partial_cmp(&self, o: &&UriTemplateStr) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd<&UriTemplateStr>","i3status_rs::blocks::BlockAction"],["
Source§

impl<'a> PartialOrd<ByteString> for Cow<'_, str>

Source§

fn partial_cmp(&self, other: &ByteString) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<RiAbsoluteStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &RiAbsoluteStr<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<RiAbsoluteString<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &RiAbsoluteString<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<RiFragmentStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &RiFragmentStr<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<RiFragmentString<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &RiFragmentString<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<RiQueryStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &RiQueryStr<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<RiQueryString<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &RiQueryString<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<RiReferenceStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &RiReferenceStr<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<RiReferenceString<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &RiReferenceString<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<RiRelativeStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &RiRelativeStr<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<RiRelativeString<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &RiRelativeString<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<RiStr<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &RiStr<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd>","i3status_rs::blocks::BlockAction"],["
§

impl<S> PartialOrd<RiString<S>> for Cow<'_, str>
where\n S: Spec,

§

fn partial_cmp(&self, o: &RiString<S>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd>","i3status_rs::blocks::BlockAction"],["
§

impl PartialOrd<UriTemplateStr> for Cow<'_, str>

§

fn partial_cmp(&self, o: &UriTemplateStr) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd","i3status_rs::blocks::BlockAction"],["
§

impl PartialOrd<UriTemplateString> for Cow<'_, str>

§

fn partial_cmp(&self, o: &UriTemplateString) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<'a, B> PartialOrd for Cow<'a, B>
where\n B: PartialOrd + ToOwned + ?Sized,

Source§

fn partial_cmp(&self, other: &Cow<'a, B>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd","i3status_rs::blocks::BlockAction"],["
§

impl<'a> Replacer for Cow<'a, str>

§

fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String)

Appends possibly empty data to dst to replace the current match. Read more
§

fn no_expansion(&mut self) -> Option<Cow<'_, str>>

Return a fixed unchanging replacement string. Read more
§

fn by_ref<'r>(&'r mut self) -> ReplacerRef<'r, Self>

Returns a type that implements Replacer, but that borrows and wraps\nthis Replacer. Read more
","Replacer","i3status_rs::blocks::BlockAction"],["
§

impl<C> RequestConnection for Cow<'_, C>
where\n C: RequestConnection + ToOwned + ?Sized,

§

type Buf = <C as RequestConnection>::Buf

Type used as buffer to store raw replies or events before\nthey are parsed.
§

fn send_request_with_reply<R>(\n &self,\n bufs: &[IoSlice<'_>],\n fds: Vec<OwnedFd>,\n) -> Result<Cookie<'_, Cow<'_, C>, R>, ConnectionError>
where\n R: TryParse,

Send a request with a reply to the server. Read more
§

fn send_trait_request_with_reply<R>(\n &self,\n request: R,\n) -> Result<Cookie<'_, Cow<'_, C>, <R as ReplyRequest>::Reply>, ConnectionError>
where\n R: ReplyRequest,

Send a request with a reply to the server. Read more
§

fn send_request_with_reply_with_fds<R>(\n &self,\n bufs: &[IoSlice<'_>],\n fds: Vec<OwnedFd>,\n) -> Result<CookieWithFds<'_, Cow<'_, C>, R>, ConnectionError>
where\n R: TryParseFd,

Send a request with a reply containing file descriptors to the server. Read more
§

fn send_trait_request_with_reply_with_fds<R>(\n &self,\n request: R,\n) -> Result<CookieWithFds<'_, Cow<'_, C>, <R as ReplyFDsRequest>::Reply>, ConnectionError>
where\n R: ReplyFDsRequest,

Send a request with a reply containing file descriptors to the server. Read more
§

fn send_request_without_reply(\n &self,\n bufs: &[IoSlice<'_>],\n fds: Vec<OwnedFd>,\n) -> Result<VoidCookie<'_, Cow<'_, C>>, ConnectionError>

Send a request without a reply to the server. Read more
§

fn send_trait_request_without_reply<R>(\n &self,\n request: R,\n) -> Result<VoidCookie<'_, Cow<'_, C>>, ConnectionError>
where\n R: VoidRequest,

Send a request without a reply to the server. Read more
§

fn discard_reply(&self, sequence: u64, kind: RequestKind, mode: DiscardMode)

A reply to an error should be discarded. Read more
§

fn prefetch_extension_information(\n &self,\n extension_name: &'static str,\n) -> Result<(), ConnectionError>

Prefetches information about an extension. Read more
§

fn extension_information(\n &self,\n extension_name: &'static str,\n) -> Result<Option<ExtensionInformation>, ConnectionError>

Get information about an extension. Read more
§

fn wait_for_reply_or_error(\n &self,\n sequence: u64,\n) -> Result<<Cow<'_, C> as RequestConnection>::Buf, ReplyError>

Wait for the reply to a request. Read more
§

fn wait_for_reply_or_raw_error(\n &self,\n sequence: u64,\n) -> Result<ReplyOrError<<Cow<'_, C> as RequestConnection>::Buf>, ConnectionError>

Wait for the reply to a request. Read more
§

fn wait_for_reply(\n &self,\n sequence: u64,\n) -> Result<Option<<Cow<'_, C> as RequestConnection>::Buf>, ConnectionError>

Wait for the reply to a request. Read more
§

fn wait_for_reply_with_fds(\n &self,\n sequence: u64,\n) -> Result<(<Cow<'_, C> as RequestConnection>::Buf, Vec<OwnedFd>), ReplyError>

Wait for the reply to a request that has FDs. Read more
§

fn wait_for_reply_with_fds_raw(\n &self,\n sequence: u64,\n) -> Result<ReplyOrError<(<Cow<'_, C> as RequestConnection>::Buf, Vec<OwnedFd>), <Cow<'_, C> as RequestConnection>::Buf>, ConnectionError>

Wait for the reply to a request that has FDs. Read more
§

fn check_for_error(&self, sequence: u64) -> Result<(), ReplyError>

Check whether a request that does not have a reply caused an X11 error. Read more
§

fn check_for_raw_error(\n &self,\n sequence: u64,\n) -> Result<Option<<Cow<'_, C> as RequestConnection>::Buf>, ConnectionError>

Check whether a request that does not have a reply caused an X11 error. Read more
§

fn prefetch_maximum_request_bytes(&self)

Prefetches the maximum request length. Read more
§

fn maximum_request_bytes(&self) -> usize

The maximum number of bytes that the X11 server accepts in a request.
§

fn parse_error(&self, error: &[u8]) -> Result<X11Error, ParseError>

Parse a generic error.
§

fn parse_event(&self, event: &[u8]) -> Result<Event, ParseError>

Parse a generic event.
","RequestConnection","i3status_rs::blocks::BlockAction"],["
Source§

impl<'a, T> Serialize for Cow<'a, T>
where\n T: Serialize + ToOwned + ?Sized,

Source§

fn serialize<S>(\n &self,\n serializer: S,\n) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where\n S: Serializer,

Serialize this value into the given Serde serializer. Read more
","Serialize","i3status_rs::blocks::BlockAction"],["
Source§

impl<T> ToTokens for Cow<'_, T>
where\n T: ToTokens + ToOwned + ?Sized,

Source§

fn to_tokens(&self, tokens: &mut TokenStream)

🔬This is a nightly-only experimental API. (proc_macro_totokens)
Write self to the given TokenStream. Read more
Source§

fn to_token_stream(&self) -> TokenStream

🔬This is a nightly-only experimental API. (proc_macro_totokens)
Convert self directly into a TokenStream object. Read more
Source§

fn into_token_stream(self) -> TokenStream
where\n Self: Sized,

🔬This is a nightly-only experimental API. (proc_macro_totokens)
Convert self directly into a TokenStream object. Read more
","ToTokens","i3status_rs::blocks::BlockAction"],["
Source§

impl<'a, T> ToTokens for Cow<'a, T>
where\n T: ToOwned + ToTokens + ?Sized,

Source§

fn to_tokens(&self, tokens: &mut TokenStream)

Write self to the given TokenStream. Read more
Source§

fn to_token_stream(&self) -> TokenStream

Convert self directly into a TokenStream object. Read more
Source§

fn into_token_stream(self) -> TokenStream
where\n Self: Sized,

Convert self directly into a TokenStream object. Read more
","ToTokens","i3status_rs::blocks::BlockAction"],["
§

impl<T> Type for Cow<'_, T>
where\n T: Type + ToOwned + ?Sized,

§

const SIGNATURE: &'static Signature = T::SIGNATURE

The signature for the implementing type, in parsed format. Read more
","Type","i3status_rs::blocks::BlockAction"],["
§

impl<'a, T> Writeable for Cow<'a, T>
where\n T: Writeable + ToOwned + ?Sized,

§

fn write_to<W>(&self, sink: &mut W) -> Result<(), Error>
where\n W: Write + ?Sized,

Writes a string to the given sink. Errors from the sink are bubbled up.\nThe default implementation delegates to write_to_parts, and discards any\nPart annotations.
§

fn write_to_parts<W>(&self, sink: &mut W) -> Result<(), Error>
where\n W: PartsWrite + ?Sized,

Write bytes and Part annotations to the given sink. Errors from the\nsink are bubbled up. The default implementation delegates to write_to,\nand doesn’t produce any Part annotations.
§

fn writeable_length_hint(&self) -> LengthHint

Returns a hint for the number of UTF-8 bytes that will be written to the sink. Read more
§

fn write_to_string(&self) -> Cow<'_, str>

Creates a new String with the data from this Writeable. Like ToString,\nbut smaller and faster. Read more
§

fn writeable_cmp_bytes(&self, other: &[u8]) -> Ordering

Compares the contents of this Writeable to the given bytes\nwithout allocating a String to hold the Writeable contents. Read more
","Writeable","i3status_rs::blocks::BlockAction"],["
§

impl<'a, T> Yokeable<'a> for Cow<'static, T>
where\n T: 'static + ToOwned + ?Sized,\n <T as ToOwned>::Owned: Sized,

§

type Output = Cow<'a, T>

This type MUST be Self with the 'static replaced with 'a, i.e. Self<'a>
§

fn transform(&'a self) -> &'a Cow<'a, T>

This method must cast self between &'a Self<'static> and &'a Self<'a>. Read more
§

fn transform_owned(self) -> Cow<'a, T>

This method must cast self between Self<'static> and Self<'a>. Read more
§

unsafe fn make(from: Cow<'a, T>) -> Cow<'static, T>

This method can be used to cast away Self<'a>’s lifetime. Read more
§

fn transform_mut<F>(&'a mut self, f: F)
where\n F: 'static + for<'b> FnOnce(&'b mut <Cow<'static, T> as Yokeable<'a>>::Output),

This method must cast self between &'a mut Self<'static> and &'a mut Self<'a>,\nand pass it to f. Read more
","Yokeable<'a>","i3status_rs::blocks::BlockAction"],["
§

impl<'zf, B> ZeroFrom<'zf, Cow<'_, B>> for Cow<'zf, B>
where\n B: ToOwned + ?Sized,

§

fn zero_from(other: &'zf Cow<'_, B>) -> Cow<'zf, B>

Clone the other C into a struct that may retain references into C.
","ZeroFrom<'zf, Cow<'_, B>>","i3status_rs::blocks::BlockAction"],["
§

impl<'zf> ZeroFrom<'zf, String> for Cow<'zf, str>

§

fn zero_from(other: &'zf String) -> Cow<'zf, str>

Clone the other C into a struct that may retain references into C.
","ZeroFrom<'zf, String>","i3status_rs::blocks::BlockAction"],["
§

impl<'zf> ZeroFrom<'zf, str> for Cow<'zf, str>

§

fn zero_from(other: &'zf str) -> Cow<'zf, str>

Clone the other C into a struct that may retain references into C.
","ZeroFrom<'zf, str>","i3status_rs::blocks::BlockAction"],["
Source§

impl<T> DerefPure for Cow<'_, T>
where\n T: Clone,

","DerefPure","i3status_rs::blocks::BlockAction"],["
Source§

impl DerefPure for Cow<'_, str>

","DerefPure","i3status_rs::blocks::BlockAction"],["
1.0.0 · Source§

impl<B> Eq for Cow<'_, B>
where\n B: Eq + ToOwned + ?Sized,

","Eq","i3status_rs::blocks::BlockAction"],["
§

impl<T> IoSafe for Cow<'_, T>
where\n T: Clone + IoSafe,

","IoSafe","i3status_rs::blocks::BlockAction"]]]]); + if (window.register_type_impls) { + window.register_type_impls(type_impls); + } else { + window.pending_type_impls = type_impls; + } +})() +//{"start":55,"fragment_lengths":[350830]} \ No newline at end of file diff --git a/type.impl/core/result/enum.Result.js b/type.impl/core/result/enum.Result.js new file mode 100644 index 0000000000..74bcace1ac --- /dev/null +++ b/type.impl/core/result/enum.Result.js @@ -0,0 +1,9 @@ +(function() { + var type_impls = Object.fromEntries([["i3status_rs",[["
1.0.0 · Source§

impl<T, E> Clone for Result<T, E>
where\n T: Clone,\n E: Clone,

Source§

fn clone(&self) -> Result<T, E>

Returns a duplicate of the value. Read more
Source§

fn clone_from(&mut self, source: &Result<T, E>)

Performs copy-assignment from source. Read more
","Clone","i3status_rs::errors::Result"],["
1.0.0 · Source§

impl<T, E> Debug for Result<T, E>
where\n T: Debug,\n E: Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
","Debug","i3status_rs::errors::Result"],["
Source§

impl<'de, T, E> Deserialize<'de> for Result<T, E>
where\n T: Deserialize<'de>,\n E: Deserialize<'de>,

Source§

fn deserialize<D>(\n deserializer: D,\n) -> Result<Result<T, E>, <D as Deserializer<'de>>::Error>
where\n D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
","Deserialize<'de>","i3status_rs::errors::Result"],["
§

impl<I, O, E> Finish<I, O, E> for Result<(I, O), Err<E>>

§

fn finish(self) -> Result<(I, O), E>

converts the parser’s result to a type that is more consumable by error\nmanagement libraries. It keeps the same Ok branch, and merges Err::Error\nand Err::Failure into the Err side. Read more
","Finish","i3status_rs::errors::Result"],["
§

impl From<Errors> for Result<(), Errors>

§

fn from(e: Errors) -> Result<(), Errors>

Converts to this type from the input type.
","From","i3status_rs::errors::Result"],["
1.0.0 · Source§

impl<A, E, V> FromIterator<Result<A, E>> for Result<V, E>
where\n V: FromIterator<A>,

Source§

fn from_iter<I>(iter: I) -> Result<V, E>
where\n I: IntoIterator<Item = Result<A, E>>,

Takes each element in the Iterator: if it is an Err, no further\nelements are taken, and the Err is returned. Should no Err occur, a\ncontainer with the values of each Result is returned.

\n

Here is an example which increments every integer in a vector,\nchecking for overflow:

\n\n
let v = vec![1, 2];\nlet res: Result<Vec<u32>, &'static str> = v.iter().map(|x: &u32|\n    x.checked_add(1).ok_or(\"Overflow!\")\n).collect();\nassert_eq!(res, Ok(vec![2, 3]));
\n

Here is another example that tries to subtract one from another list\nof integers, this time checking for underflow:

\n\n
let v = vec![1, 2, 0];\nlet res: Result<Vec<u32>, &'static str> = v.iter().map(|x: &u32|\n    x.checked_sub(1).ok_or(\"Underflow!\")\n).collect();\nassert_eq!(res, Err(\"Underflow!\"));
\n

Here is a variation on the previous example, showing that no\nfurther elements are taken from iter after the first Err.

\n\n
let v = vec![3, 2, 1, 10];\nlet mut shared = 0;\nlet res: Result<Vec<u32>, &'static str> = v.iter().map(|x: &u32| {\n    shared += x;\n    x.checked_sub(2).ok_or(\"Underflow!\")\n}).collect();\nassert_eq!(res, Err(\"Underflow!\"));\nassert_eq!(shared, 6);
\n

Since the third element caused an underflow, no further elements were taken,\nso the final value of shared is 6 (= 3 + 2 + 1), not 16.

\n
","FromIterator>","i3status_rs::errors::Result"],["
Source§

impl<T, E, F> FromResidual<Result<Infallible, E>> for Result<T, F>
where\n F: From<E>,

Source§

fn from_residual(residual: Result<Infallible, E>) -> Result<T, F>

🔬This is a nightly-only experimental API. (try_trait_v2)
Constructs the type from a compatible Residual type. Read more
","FromResidual>","i3status_rs::errors::Result"],["
Source§

impl<T, E, F> FromResidual<Yeet<E>> for Result<T, F>
where\n F: From<E>,

Source§

fn from_residual(_: Yeet<E>) -> Result<T, F>

🔬This is a nightly-only experimental API. (try_trait_v2)
Constructs the type from a compatible Residual type. Read more
","FromResidual>","i3status_rs::errors::Result"],["
1.0.0 · Source§

impl<T, E> Hash for Result<T, E>
where\n T: Hash,\n E: Hash,

Source§

fn hash<__H>(&self, state: &mut __H)
where\n __H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where\n H: Hasher,\n Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
","Hash","i3status_rs::errors::Result"],["
1.0.0 · Source§

impl<T, E> IntoIterator for Result<T, E>

Source§

fn into_iter(self) -> IntoIter<T>

Returns a consuming iterator over the possibly contained value.

\n

The iterator yields one value if the result is Result::Ok, otherwise none.

\n
§Examples
\n
let x: Result<u32, &str> = Ok(5);\nlet v: Vec<u32> = x.into_iter().collect();\nassert_eq!(v, [5]);\n\nlet x: Result<u32, &str> = Err(\"nothing!\");\nlet v: Vec<u32> = x.into_iter().collect();\nassert_eq!(v, []);
\n
Source§

type Item = T

The type of the elements being iterated over.
Source§

type IntoIter = IntoIter<T>

Which kind of iterator are we turning this into?
","IntoIterator","i3status_rs::errors::Result"],["
1.0.0 · Source§

impl<T, E> Ord for Result<T, E>
where\n T: Ord,\n E: Ord,

Source§

fn cmp(&self, other: &Result<T, E>) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where\n Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where\n Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where\n Self: Sized,

Restrict a value to a certain interval. Read more
","Ord","i3status_rs::errors::Result"],["
1.0.0 · Source§

impl<T, E> PartialEq for Result<T, E>
where\n T: PartialEq,\n E: PartialEq,

Source§

fn eq(&self, other: &Result<T, E>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq","i3status_rs::errors::Result"],["
1.0.0 · Source§

impl<T, E> PartialOrd for Result<T, E>
where\n T: PartialOrd,\n E: PartialOrd,

Source§

fn partial_cmp(&self, other: &Result<T, E>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the\n<= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the >\noperator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by\nthe >= operator. Read more
","PartialOrd","i3status_rs::errors::Result"],["
§

impl<B, E> Policy<B, E> for Result<Action, E>
where\n E: Clone,

§

fn redirect(&mut self, _: &Attempt<'_>) -> Result<Action, E>

Invoked when the service received a response with a redirection status code (3xx). Read more
§

fn on_request(&mut self, _request: &mut Request<B>)

Invoked right before the service makes a request, regardless of whether it is redirected\nor not. Read more
§

fn clone_body(&self, _body: &B) -> Option<B>

Try to clone a request body before the service makes a redirected request. Read more
","Policy","i3status_rs::errors::Result"],["
1.16.0 · Source§

impl<T, U, E> Product<Result<U, E>> for Result<T, E>
where\n T: Product<U>,

Source§

fn product<I>(iter: I) -> Result<T, E>
where\n I: Iterator<Item = Result<U, E>>,

Takes each element in the Iterator: if it is an Err, no further\nelements are taken, and the Err is returned. Should no Err\noccur, the product of all elements is returned.

\n
§Examples
\n

This multiplies each number in a vector of strings,\nif a string could not be parsed the operation returns Err:

\n\n
let nums = vec![\"5\", \"10\", \"1\", \"2\"];\nlet total: Result<usize, _> = nums.iter().map(|w| w.parse::<usize>()).product();\nassert_eq!(total, Ok(100));\nlet nums = vec![\"5\", \"10\", \"one\", \"2\"];\nlet total: Result<usize, _> = nums.iter().map(|w| w.parse::<usize>()).product();\nassert!(total.is_err());
\n
","Product>","i3status_rs::errors::Result"],["
Source§

impl<T, E> Residual<T> for Result<Infallible, E>

Source§

type TryType = Result<T, E>

🔬This is a nightly-only experimental API. (try_trait_v2_residual)
The “return” type of this meta-function.
","Residual","i3status_rs::errors::Result"],["
Source§

impl<T, E> Result<&T, E>

1.59.0 (const: 1.83.0) · Source

pub const fn copied(self) -> Result<T, E>
where\n T: Copy,

Maps a Result<&T, E> to a Result<T, E> by copying the contents of the\nOk part.

\n
§Examples
\n
let val = 12;\nlet x: Result<&i32, i32> = Ok(&val);\nassert_eq!(x, Ok(&12));\nlet copied = x.copied();\nassert_eq!(copied, Ok(12));
\n
1.59.0 · Source

pub fn cloned(self) -> Result<T, E>
where\n T: Clone,

Maps a Result<&T, E> to a Result<T, E> by cloning the contents of the\nOk part.

\n
§Examples
\n
let val = 12;\nlet x: Result<&i32, i32> = Ok(&val);\nassert_eq!(x, Ok(&12));\nlet cloned = x.cloned();\nassert_eq!(cloned, Ok(12));
\n
",0,"i3status_rs::errors::Result"],["
Source§

impl<T, E> Result<&mut T, E>

1.59.0 (const: 1.83.0) · Source

pub const fn copied(self) -> Result<T, E>
where\n T: Copy,

Maps a Result<&mut T, E> to a Result<T, E> by copying the contents of the\nOk part.

\n
§Examples
\n
let mut val = 12;\nlet x: Result<&mut i32, i32> = Ok(&mut val);\nassert_eq!(x, Ok(&mut 12));\nlet copied = x.copied();\nassert_eq!(copied, Ok(12));
\n
1.59.0 · Source

pub fn cloned(self) -> Result<T, E>
where\n T: Clone,

Maps a Result<&mut T, E> to a Result<T, E> by cloning the contents of the\nOk part.

\n
§Examples
\n
let mut val = 12;\nlet x: Result<&mut i32, i32> = Ok(&mut val);\nassert_eq!(x, Ok(&mut 12));\nlet cloned = x.cloned();\nassert_eq!(cloned, Ok(12));
\n
",0,"i3status_rs::errors::Result"],["
Source§

impl<T, E> Result<Option<T>, E>

1.33.0 (const: 1.83.0) · Source

pub const fn transpose(self) -> Option<Result<T, E>>

Transposes a Result of an Option into an Option of a Result.

\n

Ok(None) will be mapped to None.\nOk(Some(_)) and Err(_) will be mapped to Some(Ok(_)) and Some(Err(_)).

\n
§Examples
\n
#[derive(Debug, Eq, PartialEq)]\nstruct SomeErr;\n\nlet x: Result<Option<i32>, SomeErr> = Ok(Some(5));\nlet y: Option<Result<i32, SomeErr>> = Some(Ok(5));\nassert_eq!(x.transpose(), y);
\n
",0,"i3status_rs::errors::Result"],["
Source§

impl<T, E> Result<Result<T, E>, E>

1.89.0 (const: 1.89.0) · Source

pub const fn flatten(self) -> Result<T, E>

Converts from Result<Result<T, E>, E> to Result<T, E>

\n
§Examples
\n
let x: Result<Result<&'static str, u32>, u32> = Ok(Ok(\"hello\"));\nassert_eq!(Ok(\"hello\"), x.flatten());\n\nlet x: Result<Result<&'static str, u32>, u32> = Ok(Err(6));\nassert_eq!(Err(6), x.flatten());\n\nlet x: Result<Result<&'static str, u32>, u32> = Err(6);\nassert_eq!(Err(6), x.flatten());
\n

Flattening only removes one level of nesting at a time:

\n\n
let x: Result<Result<Result<&'static str, u32>, u32>, u32> = Ok(Ok(Ok(\"hello\")));\nassert_eq!(Ok(Ok(\"hello\")), x.flatten());\nassert_eq!(Ok(\"hello\"), x.flatten().flatten());
\n
",0,"i3status_rs::errors::Result"],["
Source§

impl<T, E> Result<T, E>

1.0.0 (const: 1.48.0) · Source

pub const fn is_ok(&self) -> bool

Returns true if the result is Ok.

\n
§Examples
\n
let x: Result<i32, &str> = Ok(-3);\nassert_eq!(x.is_ok(), true);\n\nlet x: Result<i32, &str> = Err(\"Some error message\");\nassert_eq!(x.is_ok(), false);
\n
1.70.0 · Source

pub fn is_ok_and(self, f: impl FnOnce(T) -> bool) -> bool

Returns true if the result is Ok and the value inside of it matches a predicate.

\n
§Examples
\n
let x: Result<u32, &str> = Ok(2);\nassert_eq!(x.is_ok_and(|x| x > 1), true);\n\nlet x: Result<u32, &str> = Ok(0);\nassert_eq!(x.is_ok_and(|x| x > 1), false);\n\nlet x: Result<u32, &str> = Err(\"hey\");\nassert_eq!(x.is_ok_and(|x| x > 1), false);\n\nlet x: Result<String, &str> = Ok(\"ownership\".to_string());\nassert_eq!(x.as_ref().is_ok_and(|x| x.len() > 1), true);\nprintln!(\"still alive {:?}\", x);
\n
1.0.0 (const: 1.48.0) · Source

pub const fn is_err(&self) -> bool

Returns true if the result is Err.

\n
§Examples
\n
let x: Result<i32, &str> = Ok(-3);\nassert_eq!(x.is_err(), false);\n\nlet x: Result<i32, &str> = Err(\"Some error message\");\nassert_eq!(x.is_err(), true);
\n
1.70.0 · Source

pub fn is_err_and(self, f: impl FnOnce(E) -> bool) -> bool

Returns true if the result is Err and the value inside of it matches a predicate.

\n
§Examples
\n
use std::io::{Error, ErrorKind};\n\nlet x: Result<u32, Error> = Err(Error::new(ErrorKind::NotFound, \"!\"));\nassert_eq!(x.is_err_and(|x| x.kind() == ErrorKind::NotFound), true);\n\nlet x: Result<u32, Error> = Err(Error::new(ErrorKind::PermissionDenied, \"!\"));\nassert_eq!(x.is_err_and(|x| x.kind() == ErrorKind::NotFound), false);\n\nlet x: Result<u32, Error> = Ok(123);\nassert_eq!(x.is_err_and(|x| x.kind() == ErrorKind::NotFound), false);\n\nlet x: Result<u32, String> = Err(\"ownership\".to_string());\nassert_eq!(x.as_ref().is_err_and(|x| x.len() > 1), true);\nprintln!(\"still alive {:?}\", x);
\n
1.0.0 · Source

pub fn ok(self) -> Option<T>

Converts from Result<T, E> to Option<T>.

\n

Converts self into an Option<T>, consuming self,\nand discarding the error, if any.

\n
§Examples
\n
let x: Result<u32, &str> = Ok(2);\nassert_eq!(x.ok(), Some(2));\n\nlet x: Result<u32, &str> = Err(\"Nothing here\");\nassert_eq!(x.ok(), None);
\n
1.0.0 · Source

pub fn err(self) -> Option<E>

Converts from Result<T, E> to Option<E>.

\n

Converts self into an Option<E>, consuming self,\nand discarding the success value, if any.

\n
§Examples
\n
let x: Result<u32, &str> = Ok(2);\nassert_eq!(x.err(), None);\n\nlet x: Result<u32, &str> = Err(\"Nothing here\");\nassert_eq!(x.err(), Some(\"Nothing here\"));
\n
1.0.0 (const: 1.48.0) · Source

pub const fn as_ref(&self) -> Result<&T, &E>

Converts from &Result<T, E> to Result<&T, &E>.

\n

Produces a new Result, containing a reference\ninto the original, leaving the original in place.

\n
§Examples
\n
let x: Result<u32, &str> = Ok(2);\nassert_eq!(x.as_ref(), Ok(&2));\n\nlet x: Result<u32, &str> = Err(\"Error\");\nassert_eq!(x.as_ref(), Err(&\"Error\"));
\n
1.0.0 (const: 1.83.0) · Source

pub const fn as_mut(&mut self) -> Result<&mut T, &mut E>

Converts from &mut Result<T, E> to Result<&mut T, &mut E>.

\n
§Examples
\n
fn mutate(r: &mut Result<i32, i32>) {\n    match r.as_mut() {\n        Ok(v) => *v = 42,\n        Err(e) => *e = 0,\n    }\n}\n\nlet mut x: Result<i32, i32> = Ok(2);\nmutate(&mut x);\nassert_eq!(x.unwrap(), 42);\n\nlet mut x: Result<i32, i32> = Err(13);\nmutate(&mut x);\nassert_eq!(x.unwrap_err(), 0);
\n
1.0.0 · Source

pub fn map<U, F>(self, op: F) -> Result<U, E>
where\n F: FnOnce(T) -> U,

Maps a Result<T, E> to Result<U, E> by applying a function to a\ncontained Ok value, leaving an Err value untouched.

\n

This function can be used to compose the results of two functions.

\n
§Examples
\n

Print the numbers on each line of a string multiplied by two.

\n\n
let line = \"1\\n2\\n3\\n4\\n\";\n\nfor num in line.lines() {\n    match num.parse::<i32>().map(|i| i * 2) {\n        Ok(n) => println!(\"{n}\"),\n        Err(..) => {}\n    }\n}
\n
1.41.0 · Source

pub fn map_or<U, F>(self, default: U, f: F) -> U
where\n F: FnOnce(T) -> U,

Returns the provided default (if Err), or\napplies a function to the contained value (if Ok).

\n

Arguments passed to map_or are eagerly evaluated; if you are passing\nthe result of a function call, it is recommended to use map_or_else,\nwhich is lazily evaluated.

\n
§Examples
\n
let x: Result<_, &str> = Ok(\"foo\");\nassert_eq!(x.map_or(42, |v| v.len()), 3);\n\nlet x: Result<&str, _> = Err(\"bar\");\nassert_eq!(x.map_or(42, |v| v.len()), 42);
\n
1.41.0 · Source

pub fn map_or_else<U, D, F>(self, default: D, f: F) -> U
where\n D: FnOnce(E) -> U,\n F: FnOnce(T) -> U,

Maps a Result<T, E> to U by applying fallback function default to\na contained Err value, or function f to a contained Ok value.

\n

This function can be used to unpack a successful result\nwhile handling an error.

\n
§Examples
\n
let k = 21;\n\nlet x : Result<_, &str> = Ok(\"foo\");\nassert_eq!(x.map_or_else(|e| k * 2, |v| v.len()), 3);\n\nlet x : Result<&str, _> = Err(\"bar\");\nassert_eq!(x.map_or_else(|e| k * 2, |v| v.len()), 42);
\n
Source

pub fn map_or_default<U, F>(self, f: F) -> U
where\n U: Default,\n F: FnOnce(T) -> U,

🔬This is a nightly-only experimental API. (result_option_map_or_default)

Maps a Result<T, E> to a U by applying function f to the contained\nvalue if the result is Ok, otherwise if Err, returns the\ndefault value for the type U.

\n
§Examples
\n
#![feature(result_option_map_or_default)]\n\nlet x: Result<_, &str> = Ok(\"foo\");\nlet y: Result<&str, _> = Err(\"bar\");\n\nassert_eq!(x.map_or_default(|x| x.len()), 3);\nassert_eq!(y.map_or_default(|y| y.len()), 0);
\n
1.0.0 · Source

pub fn map_err<F, O>(self, op: O) -> Result<T, F>
where\n O: FnOnce(E) -> F,

Maps a Result<T, E> to Result<T, F> by applying a function to a\ncontained Err value, leaving an Ok value untouched.

\n

This function can be used to pass through a successful result while handling\nan error.

\n
§Examples
\n
fn stringify(x: u32) -> String { format!(\"error code: {x}\") }\n\nlet x: Result<u32, u32> = Ok(2);\nassert_eq!(x.map_err(stringify), Ok(2));\n\nlet x: Result<u32, u32> = Err(13);\nassert_eq!(x.map_err(stringify), Err(\"error code: 13\".to_string()));
\n
1.76.0 · Source

pub fn inspect<F>(self, f: F) -> Result<T, E>
where\n F: FnOnce(&T),

Calls a function with a reference to the contained value if Ok.

\n

Returns the original result.

\n
§Examples
\n
let x: u8 = \"4\"\n    .parse::<u8>()\n    .inspect(|x| println!(\"original: {x}\"))\n    .map(|x| x.pow(3))\n    .expect(\"failed to parse number\");
\n
1.76.0 · Source

pub fn inspect_err<F>(self, f: F) -> Result<T, E>
where\n F: FnOnce(&E),

Calls a function with a reference to the contained value if Err.

\n

Returns the original result.

\n
§Examples
\n
use std::{fs, io};\n\nfn read() -> io::Result<String> {\n    fs::read_to_string(\"address.txt\")\n        .inspect_err(|e| eprintln!(\"failed to read file: {e}\"))\n}
\n
1.47.0 · Source

pub fn as_deref(&self) -> Result<&<T as Deref>::Target, &E>
where\n T: Deref,

Converts from Result<T, E> (or &Result<T, E>) to Result<&<T as Deref>::Target, &E>.

\n

Coerces the Ok variant of the original Result via Deref\nand returns the new Result.

\n
§Examples
\n
let x: Result<String, u32> = Ok(\"hello\".to_string());\nlet y: Result<&str, &u32> = Ok(\"hello\");\nassert_eq!(x.as_deref(), y);\n\nlet x: Result<String, u32> = Err(42);\nlet y: Result<&str, &u32> = Err(&42);\nassert_eq!(x.as_deref(), y);
\n
1.47.0 · Source

pub fn as_deref_mut(&mut self) -> Result<&mut <T as Deref>::Target, &mut E>
where\n T: DerefMut,

Converts from Result<T, E> (or &mut Result<T, E>) to Result<&mut <T as DerefMut>::Target, &mut E>.

\n

Coerces the Ok variant of the original Result via DerefMut\nand returns the new Result.

\n
§Examples
\n
let mut s = \"HELLO\".to_string();\nlet mut x: Result<String, u32> = Ok(\"hello\".to_string());\nlet y: Result<&mut str, &mut u32> = Ok(&mut s);\nassert_eq!(x.as_deref_mut().map(|x| { x.make_ascii_uppercase(); x }), y);\n\nlet mut i = 42;\nlet mut x: Result<String, u32> = Err(42);\nlet y: Result<&mut str, &mut u32> = Err(&mut i);\nassert_eq!(x.as_deref_mut().map(|x| { x.make_ascii_uppercase(); x }), y);
\n
1.0.0 · Source

pub fn iter(&self) -> Iter<'_, T>

Returns an iterator over the possibly contained value.

\n

The iterator yields one value if the result is Result::Ok, otherwise none.

\n
§Examples
\n
let x: Result<u32, &str> = Ok(7);\nassert_eq!(x.iter().next(), Some(&7));\n\nlet x: Result<u32, &str> = Err(\"nothing!\");\nassert_eq!(x.iter().next(), None);
\n
1.0.0 · Source

pub fn iter_mut(&mut self) -> IterMut<'_, T>

Returns a mutable iterator over the possibly contained value.

\n

The iterator yields one value if the result is Result::Ok, otherwise none.

\n
§Examples
\n
let mut x: Result<u32, &str> = Ok(7);\nmatch x.iter_mut().next() {\n    Some(v) => *v = 40,\n    None => {},\n}\nassert_eq!(x, Ok(40));\n\nlet mut x: Result<u32, &str> = Err(\"nothing!\");\nassert_eq!(x.iter_mut().next(), None);
\n
1.4.0 · Source

pub fn expect(self, msg: &str) -> T
where\n E: Debug,

Returns the contained Ok value, consuming the self value.

\n

Because this function may panic, its use is generally discouraged.\nInstead, prefer to use pattern matching and handle the Err\ncase explicitly, or call unwrap_or, unwrap_or_else, or\nunwrap_or_default.

\n
§Panics
\n

Panics if the value is an Err, with a panic message including the\npassed message, and the content of the Err.

\n
§Examples
\n
let x: Result<u32, &str> = Err(\"emergency failure\");\nx.expect(\"Testing expect\"); // panics with `Testing expect: emergency failure`
\n
§Recommended Message Style
\n

We recommend that expect messages are used to describe the reason you\nexpect the Result should be Ok.

\n\n
let path = std::env::var(\"IMPORTANT_PATH\")\n    .expect(\"env variable `IMPORTANT_PATH` should be set by `wrapper_script.sh`\");
\n

Hint: If you’re having trouble remembering how to phrase expect\nerror messages remember to focus on the word “should” as in “env\nvariable should be set by blah” or “the given binary should be available\nand executable by the current user”.

\n

For more detail on expect message styles and the reasoning behind our recommendation please\nrefer to the section on “Common Message\nStyles” in the\nstd::error module docs.

\n
1.0.0 · Source

pub fn unwrap(self) -> T
where\n E: Debug,

Returns the contained Ok value, consuming the self value.

\n

Because this function may panic, its use is generally discouraged.\nPanics are meant for unrecoverable errors, and\nmay abort the entire program.

\n

Instead, prefer to use the ? (try) operator, or pattern matching\nto handle the Err case explicitly, or call unwrap_or,\nunwrap_or_else, or unwrap_or_default.

\n
§Panics
\n

Panics if the value is an Err, with a panic message provided by the\nErr’s value.

\n
§Examples
\n

Basic usage:

\n\n
let x: Result<u32, &str> = Ok(2);\nassert_eq!(x.unwrap(), 2);
\n\n
let x: Result<u32, &str> = Err(\"emergency failure\");\nx.unwrap(); // panics with `emergency failure`
\n
1.16.0 · Source

pub fn unwrap_or_default(self) -> T
where\n T: Default,

Returns the contained Ok value or a default

\n

Consumes the self argument then, if Ok, returns the contained\nvalue, otherwise if Err, returns the default value for that\ntype.

\n
§Examples
\n

Converts a string to an integer, turning poorly-formed strings\ninto 0 (the default value for integers). parse converts\na string to any other type that implements FromStr, returning an\nErr on error.

\n\n
let good_year_from_input = \"1909\";\nlet bad_year_from_input = \"190blarg\";\nlet good_year = good_year_from_input.parse().unwrap_or_default();\nlet bad_year = bad_year_from_input.parse().unwrap_or_default();\n\nassert_eq!(1909, good_year);\nassert_eq!(0, bad_year);
\n
1.17.0 · Source

pub fn expect_err(self, msg: &str) -> E
where\n T: Debug,

Returns the contained Err value, consuming the self value.

\n
§Panics
\n

Panics if the value is an Ok, with a panic message including the\npassed message, and the content of the Ok.

\n
§Examples
\n
let x: Result<u32, &str> = Ok(10);\nx.expect_err(\"Testing expect_err\"); // panics with `Testing expect_err: 10`
\n
1.0.0 · Source

pub fn unwrap_err(self) -> E
where\n T: Debug,

Returns the contained Err value, consuming the self value.

\n
§Panics
\n

Panics if the value is an Ok, with a custom panic message provided\nby the Ok’s value.

\n
§Examples
\n
let x: Result<u32, &str> = Ok(2);\nx.unwrap_err(); // panics with `2`
\n\n
let x: Result<u32, &str> = Err(\"emergency failure\");\nassert_eq!(x.unwrap_err(), \"emergency failure\");
\n
Source

pub const fn into_ok(self) -> T
where\n E: Into<!>,

🔬This is a nightly-only experimental API. (unwrap_infallible)

Returns the contained Ok value, but never panics.

\n

Unlike unwrap, this method is known to never panic on the\nresult types it is implemented for. Therefore, it can be used\ninstead of unwrap as a maintainability safeguard that will fail\nto compile if the error type of the Result is later changed\nto an error that can actually occur.

\n
§Examples
\n
\nfn only_good_news() -> Result<String, !> {\n    Ok(\"this is fine\".into())\n}\n\nlet s: String = only_good_news().into_ok();\nprintln!(\"{s}\");
\n
Source

pub const fn into_err(self) -> E
where\n T: Into<!>,

🔬This is a nightly-only experimental API. (unwrap_infallible)

Returns the contained Err value, but never panics.

\n

Unlike unwrap_err, this method is known to never panic on the\nresult types it is implemented for. Therefore, it can be used\ninstead of unwrap_err as a maintainability safeguard that will fail\nto compile if the ok type of the Result is later changed\nto a type that can actually occur.

\n
§Examples
\n
\nfn only_bad_news() -> Result<!, String> {\n    Err(\"Oops, it failed\".into())\n}\n\nlet error: String = only_bad_news().into_err();\nprintln!(\"{error}\");
\n
1.0.0 · Source

pub fn and<U>(self, res: Result<U, E>) -> Result<U, E>

Returns res if the result is Ok, otherwise returns the Err value of self.

\n

Arguments passed to and are eagerly evaluated; if you are passing the\nresult of a function call, it is recommended to use and_then, which is\nlazily evaluated.

\n
§Examples
\n
let x: Result<u32, &str> = Ok(2);\nlet y: Result<&str, &str> = Err(\"late error\");\nassert_eq!(x.and(y), Err(\"late error\"));\n\nlet x: Result<u32, &str> = Err(\"early error\");\nlet y: Result<&str, &str> = Ok(\"foo\");\nassert_eq!(x.and(y), Err(\"early error\"));\n\nlet x: Result<u32, &str> = Err(\"not a 2\");\nlet y: Result<&str, &str> = Err(\"late error\");\nassert_eq!(x.and(y), Err(\"not a 2\"));\n\nlet x: Result<u32, &str> = Ok(2);\nlet y: Result<&str, &str> = Ok(\"different result type\");\nassert_eq!(x.and(y), Ok(\"different result type\"));
\n
1.0.0 · Source

pub fn and_then<U, F>(self, op: F) -> Result<U, E>
where\n F: FnOnce(T) -> Result<U, E>,

Calls op if the result is Ok, otherwise returns the Err value of self.

\n

This function can be used for control flow based on Result values.

\n
§Examples
\n
fn sq_then_to_string(x: u32) -> Result<String, &'static str> {\n    x.checked_mul(x).map(|sq| sq.to_string()).ok_or(\"overflowed\")\n}\n\nassert_eq!(Ok(2).and_then(sq_then_to_string), Ok(4.to_string()));\nassert_eq!(Ok(1_000_000).and_then(sq_then_to_string), Err(\"overflowed\"));\nassert_eq!(Err(\"not a number\").and_then(sq_then_to_string), Err(\"not a number\"));
\n

Often used to chain fallible operations that may return Err.

\n\n
use std::{io::ErrorKind, path::Path};\n\n// Note: on Windows \"/\" maps to \"C:\\\"\nlet root_modified_time = Path::new(\"/\").metadata().and_then(|md| md.modified());\nassert!(root_modified_time.is_ok());\n\nlet should_fail = Path::new(\"/bad/path\").metadata().and_then(|md| md.modified());\nassert!(should_fail.is_err());\nassert_eq!(should_fail.unwrap_err().kind(), ErrorKind::NotFound);
\n
1.0.0 · Source

pub fn or<F>(self, res: Result<T, F>) -> Result<T, F>

Returns res if the result is Err, otherwise returns the Ok value of self.

\n

Arguments passed to or are eagerly evaluated; if you are passing the\nresult of a function call, it is recommended to use or_else, which is\nlazily evaluated.

\n
§Examples
\n
let x: Result<u32, &str> = Ok(2);\nlet y: Result<u32, &str> = Err(\"late error\");\nassert_eq!(x.or(y), Ok(2));\n\nlet x: Result<u32, &str> = Err(\"early error\");\nlet y: Result<u32, &str> = Ok(2);\nassert_eq!(x.or(y), Ok(2));\n\nlet x: Result<u32, &str> = Err(\"not a 2\");\nlet y: Result<u32, &str> = Err(\"late error\");\nassert_eq!(x.or(y), Err(\"late error\"));\n\nlet x: Result<u32, &str> = Ok(2);\nlet y: Result<u32, &str> = Ok(100);\nassert_eq!(x.or(y), Ok(2));
\n
1.0.0 · Source

pub fn or_else<F, O>(self, op: O) -> Result<T, F>
where\n O: FnOnce(E) -> Result<T, F>,

Calls op if the result is Err, otherwise returns the Ok value of self.

\n

This function can be used for control flow based on result values.

\n
§Examples
\n
fn sq(x: u32) -> Result<u32, u32> { Ok(x * x) }\nfn err(x: u32) -> Result<u32, u32> { Err(x) }\n\nassert_eq!(Ok(2).or_else(sq).or_else(sq), Ok(2));\nassert_eq!(Ok(2).or_else(err).or_else(sq), Ok(2));\nassert_eq!(Err(3).or_else(sq).or_else(err), Ok(9));\nassert_eq!(Err(3).or_else(err).or_else(err), Err(3));
\n
1.0.0 · Source

pub fn unwrap_or(self, default: T) -> T

Returns the contained Ok value or a provided default.

\n

Arguments passed to unwrap_or are eagerly evaluated; if you are passing\nthe result of a function call, it is recommended to use unwrap_or_else,\nwhich is lazily evaluated.

\n
§Examples
\n
let default = 2;\nlet x: Result<u32, &str> = Ok(9);\nassert_eq!(x.unwrap_or(default), 9);\n\nlet x: Result<u32, &str> = Err(\"error\");\nassert_eq!(x.unwrap_or(default), default);
\n
1.0.0 · Source

pub fn unwrap_or_else<F>(self, op: F) -> T
where\n F: FnOnce(E) -> T,

Returns the contained Ok value or computes it from a closure.

\n
§Examples
\n
fn count(x: &str) -> usize { x.len() }\n\nassert_eq!(Ok(2).unwrap_or_else(count), 2);\nassert_eq!(Err(\"foo\").unwrap_or_else(count), 3);
\n
1.58.0 · Source

pub unsafe fn unwrap_unchecked(self) -> T

Returns the contained Ok value, consuming the self value,\nwithout checking that the value is not an Err.

\n
§Safety
\n

Calling this method on an Err is undefined behavior.

\n
§Examples
\n
let x: Result<u32, &str> = Ok(2);\nassert_eq!(unsafe { x.unwrap_unchecked() }, 2);
\n\n
let x: Result<u32, &str> = Err(\"emergency failure\");\nunsafe { x.unwrap_unchecked(); } // Undefined behavior!
\n
1.58.0 · Source

pub unsafe fn unwrap_err_unchecked(self) -> E

Returns the contained Err value, consuming the self value,\nwithout checking that the value is not an Ok.

\n
§Safety
\n

Calling this method on an Ok is undefined behavior.

\n
§Examples
\n
let x: Result<u32, &str> = Ok(2);\nunsafe { x.unwrap_err_unchecked() }; // Undefined behavior!
\n\n
let x: Result<u32, &str> = Err(\"emergency failure\");\nassert_eq!(unsafe { x.unwrap_err_unchecked() }, \"emergency failure\");
\n
",0,"i3status_rs::errors::Result"],["
Source§

impl<T, E> Serialize for Result<T, E>
where\n T: Serialize,\n E: Serialize,

Source§

fn serialize<S>(\n &self,\n serializer: S,\n) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where\n S: Serializer,

Serialize this value into the given Serde serializer. Read more
","Serialize","i3status_rs::errors::Result"],["
1.16.0 · Source§

impl<T, U, E> Sum<Result<U, E>> for Result<T, E>
where\n T: Sum<U>,

Source§

fn sum<I>(iter: I) -> Result<T, E>
where\n I: Iterator<Item = Result<U, E>>,

Takes each element in the Iterator: if it is an Err, no further\nelements are taken, and the Err is returned. Should no Err\noccur, the sum of all elements is returned.

\n
§Examples
\n

This sums up every integer in a vector, rejecting the sum if a negative\nelement is encountered:

\n\n
let f = |&x: &i32| if x < 0 { Err(\"Negative element found\") } else { Ok(x) };\nlet v = vec![1, 2];\nlet res: Result<i32, _> = v.iter().map(f).sum();\nassert_eq!(res, Ok(3));\nlet v = vec![1, -2];\nlet res: Result<i32, _> = v.iter().map(f).sum();\nassert_eq!(res, Err(\"Negative element found\"));
\n
","Sum>","i3status_rs::errors::Result"],["
1.61.0 · Source§

impl<T, E> Termination for Result<T, E>
where\n T: Termination,\n E: Debug,

Source§

fn report(self) -> ExitCode

Is called to get the representation of the value as status code.\nThis status code is returned to the operating system.
","Termination","i3status_rs::errors::Result"],["
Source§

impl<T, E> Try for Result<T, E>

Source§

type Output = T

🔬This is a nightly-only experimental API. (try_trait_v2)
The type of the value produced by ? when not short-circuiting.
Source§

type Residual = Result<Infallible, E>

🔬This is a nightly-only experimental API. (try_trait_v2)
The type of the value passed to FromResidual::from_residual\nas part of ? when short-circuiting. Read more
Source§

fn from_output(output: <Result<T, E> as Try>::Output) -> Result<T, E>

🔬This is a nightly-only experimental API. (try_trait_v2)
Constructs the type from its Output type. Read more
Source§

fn branch(\n self,\n) -> ControlFlow<<Result<T, E> as Try>::Residual, <Result<T, E> as Try>::Output>

🔬This is a nightly-only experimental API. (try_trait_v2)
Used in ? to decide whether the operator should produce a value\n(because this returned ControlFlow::Continue)\nor propagate a value back to the caller\n(because this returned ControlFlow::Break). Read more
","Try","i3status_rs::errors::Result"],["
§

impl<T, E> TryWriteable for Result<T, E>
where\n T: Writeable,\n E: Writeable + Clone,

§

type Error = E

§

fn try_write_to<W>(\n &self,\n sink: &mut W,\n) -> Result<Result<(), <Result<T, E> as TryWriteable>::Error>, Error>
where\n W: Write + ?Sized,

Writes the content of this writeable to a sink. Read more
§

fn try_write_to_parts<S>(\n &self,\n sink: &mut S,\n) -> Result<Result<(), <Result<T, E> as TryWriteable>::Error>, Error>
where\n S: PartsWrite + ?Sized,

Writes the content of this writeable to a sink with parts (annotations). Read more
§

fn writeable_length_hint(&self) -> LengthHint

Returns a hint for the number of UTF-8 bytes that will be written to the sink. Read more
§

fn try_write_to_string(\n &self,\n) -> Result<Cow<'_, str>, (<Result<T, E> as TryWriteable>::Error, Cow<'_, str>)>

Writes the content of this writeable to a string. Read more
§

fn writeable_cmp_bytes(&self, other: &[u8]) -> Ordering

Compares the content of this writeable to a byte slice. Read more
","TryWriteable","i3status_rs::errors::Result"],["
1.0.0 · Source§

impl<T, E> Copy for Result<T, E>
where\n T: Copy,\n E: Copy,

","Copy","i3status_rs::errors::Result"],["
1.0.0 · Source§

impl<T, E> Eq for Result<T, E>
where\n T: Eq,\n E: Eq,

","Eq","i3status_rs::errors::Result"],["
1.0.0 · Source§

impl<T, E> StructuralPartialEq for Result<T, E>

","StructuralPartialEq","i3status_rs::errors::Result"],["
Source§

impl<T, E> UseCloned for Result<T, E>
where\n T: UseCloned,\n E: UseCloned,

","UseCloned","i3status_rs::errors::Result"]]]]); + if (window.register_type_impls) { + window.register_type_impls(type_impls); + } else { + window.pending_type_impls = type_impls; + } +})() +//{"start":55,"fragment_lengths":[181182]} \ No newline at end of file diff --git a/type.impl/std/collections/hash/map/struct.HashMap.js b/type.impl/std/collections/hash/map/struct.HashMap.js new file mode 100644 index 0000000000..16b9949ab9 --- /dev/null +++ b/type.impl/std/collections/hash/map/struct.HashMap.js @@ -0,0 +1,9 @@ +(function() { + var type_impls = Object.fromEntries([["i3status_rs",[["
§

impl<K, V, S> Accumulate<(K, V)> for HashMap<K, V, S>
where\n K: Eq + Hash,\n S: BuildHasher + Default,

§

fn initial(capacity: Option<usize>) -> HashMap<K, V, S>

Create a new Extend of the correct type
§

fn accumulate(&mut self, _: (K, V))

Accumulate the input into an accumulator
","Accumulate<(K, V)>","i3status_rs::formatting::Values"],["
1.0.0 · Source§

impl<K, V, S> Clone for HashMap<K, V, S>
where\n K: Clone,\n V: Clone,\n S: Clone,

Source§

fn clone(&self) -> HashMap<K, V, S>

Returns a duplicate of the value. Read more
Source§

fn clone_from(&mut self, source: &HashMap<K, V, S>)

Performs copy-assignment from source. Read more
","Clone","i3status_rs::formatting::Values"],["
1.0.0 · Source§

impl<K, V, S> Debug for HashMap<K, V, S>
where\n K: Debug,\n V: Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
","Debug","i3status_rs::formatting::Values"],["
1.0.0 · Source§

impl<K, V, S> Default for HashMap<K, V, S>
where\n S: Default,

Source§

fn default() -> HashMap<K, V, S>

Creates an empty HashMap<K, V, S>, with the Default value for the hasher.

\n
","Default","i3status_rs::formatting::Values"],["
Source§

impl<'de, K, V, S> Deserialize<'de> for HashMap<K, V, S>
where\n K: Deserialize<'de> + Eq + Hash,\n V: Deserialize<'de>,\n S: BuildHasher + Default,

Source§

fn deserialize<D>(\n deserializer: D,\n) -> Result<HashMap<K, V, S>, <D as Deserializer<'de>>::Error>
where\n D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
","Deserialize<'de>","i3status_rs::formatting::Values"],["
1.4.0 · Source§

impl<'a, K, V, S> Extend<(&'a K, &'a V)> for HashMap<K, V, S>
where\n K: Eq + Hash + Copy,\n V: Copy,\n S: BuildHasher,

Source§

fn extend<T>(&mut self, iter: T)
where\n T: IntoIterator<Item = (&'a K, &'a V)>,

Extends a collection with the contents of an iterator. Read more
Source§

fn extend_one(&mut self, _: (&'a K, &'a V))

🔬This is a nightly-only experimental API. (extend_one)
Extends a collection with exactly one element.
Source§

fn extend_reserve(&mut self, additional: usize)

🔬This is a nightly-only experimental API. (extend_one)
Reserves capacity in a collection for the given number of additional elements. Read more
","Extend<(&'a K, &'a V)>","i3status_rs::formatting::Values"],["
1.0.0 · Source§

impl<K, V, S> Extend<(K, V)> for HashMap<K, V, S>
where\n K: Eq + Hash,\n S: BuildHasher,

Inserts all new key-values from the iterator and replaces values with existing\nkeys with new values returned from the iterator.

\n
Source§

fn extend<T>(&mut self, iter: T)
where\n T: IntoIterator<Item = (K, V)>,

Extends a collection with the contents of an iterator. Read more
Source§

fn extend_one(&mut self, _: (K, V))

🔬This is a nightly-only experimental API. (extend_one)
Extends a collection with exactly one element.
Source§

fn extend_reserve(&mut self, additional: usize)

🔬This is a nightly-only experimental API. (extend_one)
Reserves capacity in a collection for the given number of additional elements. Read more
","Extend<(K, V)>","i3status_rs::formatting::Values"],["
1.56.0 · Source§

impl<K, V, const N: usize> From<[(K, V); N]> for HashMap<K, V>
where\n K: Eq + Hash,

Source§

fn from(arr: [(K, V); N]) -> HashMap<K, V>

Converts a [(K, V); N] into a HashMap<K, V>.

\n

If any entries in the array have equal keys,\nall but one of the corresponding values will be dropped.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet map1 = HashMap::from([(1, 2), (3, 4)]);\nlet map2: HashMap<_, _> = [(1, 2), (3, 4)].into();\nassert_eq!(map1, map2);
\n
","From<[(K, V); N]>","i3status_rs::formatting::Values"],["
1.0.0 · Source§

impl<K, V, S> FromIterator<(K, V)> for HashMap<K, V, S>
where\n K: Eq + Hash,\n S: BuildHasher + Default,

Source§

fn from_iter<T>(iter: T) -> HashMap<K, V, S>
where\n T: IntoIterator<Item = (K, V)>,

Constructs a HashMap<K, V> from an iterator of key-value pairs.

\n

If the iterator produces any pairs with equal keys,\nall but one of the corresponding values will be dropped.

\n
","FromIterator<(K, V)>","i3status_rs::formatting::Values"],["
Source§

impl<K, V> HashMap<K, V>

1.0.0 · Source

pub fn new() -> HashMap<K, V>

Creates an empty HashMap.

\n

The hash map is initially created with a capacity of 0, so it will not allocate until it\nis first inserted into.

\n
§Examples
\n
use std::collections::HashMap;\nlet mut map: HashMap<&str, i32> = HashMap::new();
\n
1.0.0 · Source

pub fn with_capacity(capacity: usize) -> HashMap<K, V>

Creates an empty HashMap with at least the specified capacity.

\n

The hash map will be able to hold at least capacity elements without\nreallocating. This method is allowed to allocate for more elements than\ncapacity. If capacity is zero, the hash map will not allocate.

\n
§Examples
\n
use std::collections::HashMap;\nlet mut map: HashMap<&str, i32> = HashMap::with_capacity(10);
\n
",0,"i3status_rs::formatting::Values"],["
Source§

impl<K, V, S> HashMap<K, V, S>

1.7.0 (const: 1.85.0) · Source

pub const fn with_hasher(hash_builder: S) -> HashMap<K, V, S>

Creates an empty HashMap which will use the given hash builder to hash\nkeys.

\n

The created map has the default initial capacity.

\n

Warning: hash_builder is normally randomly generated, and\nis designed to allow HashMaps to be resistant to attacks that\ncause many collisions and very poor performance. Setting it\nmanually using this function can expose a DoS attack vector.

\n

The hash_builder passed should implement the BuildHasher trait for\nthe HashMap to be useful, see its documentation for details.

\n
§Examples
\n
use std::collections::HashMap;\nuse std::hash::RandomState;\n\nlet s = RandomState::new();\nlet mut map = HashMap::with_hasher(s);\nmap.insert(1, 2);
\n
1.7.0 · Source

pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> HashMap<K, V, S>

Creates an empty HashMap with at least the specified capacity, using\nhasher to hash the keys.

\n

The hash map will be able to hold at least capacity elements without\nreallocating. This method is allowed to allocate for more elements than\ncapacity. If capacity is zero, the hash map will not allocate.

\n

Warning: hasher is normally randomly generated, and\nis designed to allow HashMaps to be resistant to attacks that\ncause many collisions and very poor performance. Setting it\nmanually using this function can expose a DoS attack vector.

\n

The hasher passed should implement the BuildHasher trait for\nthe HashMap to be useful, see its documentation for details.

\n
§Examples
\n
use std::collections::HashMap;\nuse std::hash::RandomState;\n\nlet s = RandomState::new();\nlet mut map = HashMap::with_capacity_and_hasher(10, s);\nmap.insert(1, 2);
\n
1.0.0 · Source

pub fn capacity(&self) -> usize

Returns the number of elements the map can hold without reallocating.

\n

This number is a lower bound; the HashMap<K, V> might be able to hold\nmore, but is guaranteed to be able to hold at least this many.

\n
§Examples
\n
use std::collections::HashMap;\nlet map: HashMap<i32, i32> = HashMap::with_capacity(100);\nassert!(map.capacity() >= 100);
\n
1.0.0 · Source

pub fn keys(&self) -> Keys<'_, K, V>

An iterator visiting all keys in arbitrary order.\nThe iterator element type is &'a K.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet map = HashMap::from([\n    (\"a\", 1),\n    (\"b\", 2),\n    (\"c\", 3),\n]);\n\nfor key in map.keys() {\n    println!(\"{key}\");\n}
\n
§Performance
\n

In the current implementation, iterating over keys takes O(capacity) time\ninstead of O(len) because it internally visits empty buckets too.

\n
1.54.0 · Source

pub fn into_keys(self) -> IntoKeys<K, V>

Creates a consuming iterator visiting all the keys in arbitrary order.\nThe map cannot be used after calling this.\nThe iterator element type is K.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet map = HashMap::from([\n    (\"a\", 1),\n    (\"b\", 2),\n    (\"c\", 3),\n]);\n\nlet mut vec: Vec<&str> = map.into_keys().collect();\n// The `IntoKeys` iterator produces keys in arbitrary order, so the\n// keys must be sorted to test them against a sorted array.\nvec.sort_unstable();\nassert_eq!(vec, [\"a\", \"b\", \"c\"]);
\n
§Performance
\n

In the current implementation, iterating over keys takes O(capacity) time\ninstead of O(len) because it internally visits empty buckets too.

\n
1.0.0 · Source

pub fn values(&self) -> Values<'_, K, V>

An iterator visiting all values in arbitrary order.\nThe iterator element type is &'a V.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet map = HashMap::from([\n    (\"a\", 1),\n    (\"b\", 2),\n    (\"c\", 3),\n]);\n\nfor val in map.values() {\n    println!(\"{val}\");\n}
\n
§Performance
\n

In the current implementation, iterating over values takes O(capacity) time\ninstead of O(len) because it internally visits empty buckets too.

\n
1.10.0 · Source

pub fn values_mut(&mut self) -> ValuesMut<'_, K, V>

An iterator visiting all values mutably in arbitrary order.\nThe iterator element type is &'a mut V.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut map = HashMap::from([\n    (\"a\", 1),\n    (\"b\", 2),\n    (\"c\", 3),\n]);\n\nfor val in map.values_mut() {\n    *val = *val + 10;\n}\n\nfor val in map.values() {\n    println!(\"{val}\");\n}
\n
§Performance
\n

In the current implementation, iterating over values takes O(capacity) time\ninstead of O(len) because it internally visits empty buckets too.

\n
1.54.0 · Source

pub fn into_values(self) -> IntoValues<K, V>

Creates a consuming iterator visiting all the values in arbitrary order.\nThe map cannot be used after calling this.\nThe iterator element type is V.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet map = HashMap::from([\n    (\"a\", 1),\n    (\"b\", 2),\n    (\"c\", 3),\n]);\n\nlet mut vec: Vec<i32> = map.into_values().collect();\n// The `IntoValues` iterator produces values in arbitrary order, so\n// the values must be sorted to test them against a sorted array.\nvec.sort_unstable();\nassert_eq!(vec, [1, 2, 3]);
\n
§Performance
\n

In the current implementation, iterating over values takes O(capacity) time\ninstead of O(len) because it internally visits empty buckets too.

\n
1.0.0 · Source

pub fn iter(&self) -> Iter<'_, K, V>

An iterator visiting all key-value pairs in arbitrary order.\nThe iterator element type is (&'a K, &'a V).

\n
§Examples
\n
use std::collections::HashMap;\n\nlet map = HashMap::from([\n    (\"a\", 1),\n    (\"b\", 2),\n    (\"c\", 3),\n]);\n\nfor (key, val) in map.iter() {\n    println!(\"key: {key} val: {val}\");\n}
\n
§Performance
\n

In the current implementation, iterating over map takes O(capacity) time\ninstead of O(len) because it internally visits empty buckets too.

\n
1.0.0 · Source

pub fn iter_mut(&mut self) -> IterMut<'_, K, V>

An iterator visiting all key-value pairs in arbitrary order,\nwith mutable references to the values.\nThe iterator element type is (&'a K, &'a mut V).

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut map = HashMap::from([\n    (\"a\", 1),\n    (\"b\", 2),\n    (\"c\", 3),\n]);\n\n// Update all values\nfor (_, val) in map.iter_mut() {\n    *val *= 2;\n}\n\nfor (key, val) in &map {\n    println!(\"key: {key} val: {val}\");\n}
\n
§Performance
\n

In the current implementation, iterating over map takes O(capacity) time\ninstead of O(len) because it internally visits empty buckets too.

\n
1.0.0 · Source

pub fn len(&self) -> usize

Returns the number of elements in the map.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut a = HashMap::new();\nassert_eq!(a.len(), 0);\na.insert(1, \"a\");\nassert_eq!(a.len(), 1);
\n
1.0.0 · Source

pub fn is_empty(&self) -> bool

Returns true if the map contains no elements.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut a = HashMap::new();\nassert!(a.is_empty());\na.insert(1, \"a\");\nassert!(!a.is_empty());
\n
1.6.0 · Source

pub fn drain(&mut self) -> Drain<'_, K, V>

Clears the map, returning all key-value pairs as an iterator. Keeps the\nallocated memory for reuse.

\n

If the returned iterator is dropped before being fully consumed, it\ndrops the remaining key-value pairs. The returned iterator keeps a\nmutable borrow on the map to optimize its implementation.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut a = HashMap::new();\na.insert(1, \"a\");\na.insert(2, \"b\");\n\nfor (k, v) in a.drain().take(1) {\n    assert!(k == 1 || k == 2);\n    assert!(v == \"a\" || v == \"b\");\n}\n\nassert!(a.is_empty());
\n
1.88.0 · Source

pub fn extract_if<F>(&mut self, pred: F) -> ExtractIf<'_, K, V, F>
where\n F: FnMut(&K, &mut V) -> bool,

Creates an iterator which uses a closure to determine if an element (key-value pair) should be removed.

\n

If the closure returns true, the element is removed from the map and\nyielded. If the closure returns false, or panics, the element remains\nin the map and will not be yielded.

\n

The iterator also lets you mutate the value of each element in the\nclosure, regardless of whether you choose to keep or remove it.

\n

If the returned ExtractIf is not exhausted, e.g. because it is dropped without iterating\nor the iteration short-circuits, then the remaining elements will be retained.\nUse retain with a negated predicate if you do not need the returned iterator.

\n
§Examples
\n

Splitting a map into even and odd keys, reusing the original map:

\n\n
use std::collections::HashMap;\n\nlet mut map: HashMap<i32, i32> = (0..8).map(|x| (x, x)).collect();\nlet extracted: HashMap<i32, i32> = map.extract_if(|k, _v| k % 2 == 0).collect();\n\nlet mut evens = extracted.keys().copied().collect::<Vec<_>>();\nlet mut odds = map.keys().copied().collect::<Vec<_>>();\nevens.sort();\nodds.sort();\n\nassert_eq!(evens, vec![0, 2, 4, 6]);\nassert_eq!(odds, vec![1, 3, 5, 7]);
\n
1.18.0 · Source

pub fn retain<F>(&mut self, f: F)
where\n F: FnMut(&K, &mut V) -> bool,

Retains only the elements specified by the predicate.

\n

In other words, remove all pairs (k, v) for which f(&k, &mut v) returns false.\nThe elements are visited in unsorted (and unspecified) order.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut map: HashMap<i32, i32> = (0..8).map(|x| (x, x*10)).collect();\nmap.retain(|&k, _| k % 2 == 0);\nassert_eq!(map.len(), 4);
\n
§Performance
\n

In the current implementation, this operation takes O(capacity) time\ninstead of O(len) because it internally visits empty buckets too.

\n
1.0.0 · Source

pub fn clear(&mut self)

Clears the map, removing all key-value pairs. Keeps the allocated memory\nfor reuse.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut a = HashMap::new();\na.insert(1, \"a\");\na.clear();\nassert!(a.is_empty());
\n
1.9.0 · Source

pub fn hasher(&self) -> &S

Returns a reference to the map’s BuildHasher.

\n
§Examples
\n
use std::collections::HashMap;\nuse std::hash::RandomState;\n\nlet hasher = RandomState::new();\nlet map: HashMap<i32, i32> = HashMap::with_hasher(hasher);\nlet hasher: &RandomState = map.hasher();
\n
",0,"i3status_rs::formatting::Values"],["
Source§

impl<K, V, S> HashMap<K, V, S>
where\n K: Eq + Hash,\n S: BuildHasher,

1.0.0 · Source

pub fn reserve(&mut self, additional: usize)

Reserves capacity for at least additional more elements to be inserted\nin the HashMap. The collection may reserve more space to speculatively\navoid frequent reallocations. After calling reserve,\ncapacity will be greater than or equal to self.len() + additional.\nDoes nothing if capacity is already sufficient.

\n
§Panics
\n

Panics if the new allocation size overflows usize.

\n
§Examples
\n
use std::collections::HashMap;\nlet mut map: HashMap<&str, i32> = HashMap::new();\nmap.reserve(10);
\n
1.57.0 · Source

pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError>

Tries to reserve capacity for at least additional more elements to be inserted\nin the HashMap. The collection may reserve more space to speculatively\navoid frequent reallocations. After calling try_reserve,\ncapacity will be greater than or equal to self.len() + additional if\nit returns Ok(()).\nDoes nothing if capacity is already sufficient.

\n
§Errors
\n

If the capacity overflows, or the allocator reports a failure, then an error\nis returned.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut map: HashMap<&str, isize> = HashMap::new();\nmap.try_reserve(10).expect(\"why is the test harness OOMing on a handful of bytes?\");
\n
1.0.0 · Source

pub fn shrink_to_fit(&mut self)

Shrinks the capacity of the map as much as possible. It will drop\ndown as much as possible while maintaining the internal rules\nand possibly leaving some space in accordance with the resize policy.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut map: HashMap<i32, i32> = HashMap::with_capacity(100);\nmap.insert(1, 2);\nmap.insert(3, 4);\nassert!(map.capacity() >= 100);\nmap.shrink_to_fit();\nassert!(map.capacity() >= 2);
\n
1.56.0 · Source

pub fn shrink_to(&mut self, min_capacity: usize)

Shrinks the capacity of the map with a lower limit. It will drop\ndown no lower than the supplied limit while maintaining the internal rules\nand possibly leaving some space in accordance with the resize policy.

\n

If the current capacity is less than the lower limit, this is a no-op.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut map: HashMap<i32, i32> = HashMap::with_capacity(100);\nmap.insert(1, 2);\nmap.insert(3, 4);\nassert!(map.capacity() >= 100);\nmap.shrink_to(10);\nassert!(map.capacity() >= 10);\nmap.shrink_to(0);\nassert!(map.capacity() >= 2);
\n
1.0.0 · Source

pub fn entry(&mut self, key: K) -> Entry<'_, K, V>

Gets the given key’s corresponding entry in the map for in-place manipulation.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut letters = HashMap::new();\n\nfor ch in \"a short treatise on fungi\".chars() {\n    letters.entry(ch).and_modify(|counter| *counter += 1).or_insert(1);\n}\n\nassert_eq!(letters[&'s'], 2);\nassert_eq!(letters[&'t'], 3);\nassert_eq!(letters[&'u'], 1);\nassert_eq!(letters.get(&'y'), None);
\n
1.0.0 · Source

pub fn get<Q>(&self, k: &Q) -> Option<&V>
where\n K: Borrow<Q>,\n Q: Hash + Eq + ?Sized,

Returns a reference to the value corresponding to the key.

\n

The key may be any borrowed form of the map’s key type, but\nHash and Eq on the borrowed form must match those for\nthe key type.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut map = HashMap::new();\nmap.insert(1, \"a\");\nassert_eq!(map.get(&1), Some(&\"a\"));\nassert_eq!(map.get(&2), None);
\n
1.40.0 · Source

pub fn get_key_value<Q>(&self, k: &Q) -> Option<(&K, &V)>
where\n K: Borrow<Q>,\n Q: Hash + Eq + ?Sized,

Returns the key-value pair corresponding to the supplied key. This is\npotentially useful:

\n
    \n
  • for key types where non-identical keys can be considered equal;
  • \n
  • for getting the &K stored key value from a borrowed &Q lookup key; or
  • \n
  • for getting a reference to a key with the same lifetime as the collection.
  • \n
\n

The supplied key may be any borrowed form of the map’s key type, but\nHash and Eq on the borrowed form must match those for\nthe key type.

\n
§Examples
\n
use std::collections::HashMap;\nuse std::hash::{Hash, Hasher};\n\n#[derive(Clone, Copy, Debug)]\nstruct S {\n    id: u32,\n    name: &'static str, // ignored by equality and hashing operations\n}\n\nimpl PartialEq for S {\n    fn eq(&self, other: &S) -> bool {\n        self.id == other.id\n    }\n}\n\nimpl Eq for S {}\n\nimpl Hash for S {\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        self.id.hash(state);\n    }\n}\n\nlet j_a = S { id: 1, name: \"Jessica\" };\nlet j_b = S { id: 1, name: \"Jess\" };\nlet p = S { id: 2, name: \"Paul\" };\nassert_eq!(j_a, j_b);\n\nlet mut map = HashMap::new();\nmap.insert(j_a, \"Paris\");\nassert_eq!(map.get_key_value(&j_a), Some((&j_a, &\"Paris\")));\nassert_eq!(map.get_key_value(&j_b), Some((&j_a, &\"Paris\"))); // the notable case\nassert_eq!(map.get_key_value(&p), None);
\n
1.86.0 · Source

pub fn get_disjoint_mut<Q, const N: usize>(\n &mut self,\n ks: [&Q; N],\n) -> [Option<&mut V>; N]
where\n K: Borrow<Q>,\n Q: Hash + Eq + ?Sized,

Attempts to get mutable references to N values in the map at once.

\n

Returns an array of length N with the results of each query. For soundness, at most one\nmutable reference will be returned to any value. None will be used if the key is missing.

\n

This method performs a check to ensure there are no duplicate keys, which currently has a time-complexity of O(n^2),\nso be careful when passing many keys.

\n
§Panics
\n

Panics if any keys are overlapping.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut libraries = HashMap::new();\nlibraries.insert(\"Bodleian Library\".to_string(), 1602);\nlibraries.insert(\"Athenæum\".to_string(), 1807);\nlibraries.insert(\"Herzogin-Anna-Amalia-Bibliothek\".to_string(), 1691);\nlibraries.insert(\"Library of Congress\".to_string(), 1800);\n\n// Get Athenæum and Bodleian Library\nlet [Some(a), Some(b)] = libraries.get_disjoint_mut([\n    \"Athenæum\",\n    \"Bodleian Library\",\n]) else { panic!() };\n\n// Assert values of Athenæum and Library of Congress\nlet got = libraries.get_disjoint_mut([\n    \"Athenæum\",\n    \"Library of Congress\",\n]);\nassert_eq!(\n    got,\n    [\n        Some(&mut 1807),\n        Some(&mut 1800),\n    ],\n);\n\n// Missing keys result in None\nlet got = libraries.get_disjoint_mut([\n    \"Athenæum\",\n    \"New York Public Library\",\n]);\nassert_eq!(\n    got,\n    [\n        Some(&mut 1807),\n        None\n    ]\n);
\n\n
use std::collections::HashMap;\n\nlet mut libraries = HashMap::new();\nlibraries.insert(\"Athenæum\".to_string(), 1807);\n\n// Duplicate keys panic!\nlet got = libraries.get_disjoint_mut([\n    \"Athenæum\",\n    \"Athenæum\",\n]);
\n
1.86.0 · Source

pub unsafe fn get_disjoint_unchecked_mut<Q, const N: usize>(\n &mut self,\n ks: [&Q; N],\n) -> [Option<&mut V>; N]
where\n K: Borrow<Q>,\n Q: Hash + Eq + ?Sized,

Attempts to get mutable references to N values in the map at once, without validating that\nthe values are unique.

\n

Returns an array of length N with the results of each query. None will be used if\nthe key is missing.

\n

For a safe alternative see get_disjoint_mut.

\n
§Safety
\n

Calling this method with overlapping keys is undefined behavior even if the resulting\nreferences are not used.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut libraries = HashMap::new();\nlibraries.insert(\"Bodleian Library\".to_string(), 1602);\nlibraries.insert(\"Athenæum\".to_string(), 1807);\nlibraries.insert(\"Herzogin-Anna-Amalia-Bibliothek\".to_string(), 1691);\nlibraries.insert(\"Library of Congress\".to_string(), 1800);\n\n// SAFETY: The keys do not overlap.\nlet [Some(a), Some(b)] = (unsafe { libraries.get_disjoint_unchecked_mut([\n    \"Athenæum\",\n    \"Bodleian Library\",\n]) }) else { panic!() };\n\n// SAFETY: The keys do not overlap.\nlet got = unsafe { libraries.get_disjoint_unchecked_mut([\n    \"Athenæum\",\n    \"Library of Congress\",\n]) };\nassert_eq!(\n    got,\n    [\n        Some(&mut 1807),\n        Some(&mut 1800),\n    ],\n);\n\n// SAFETY: The keys do not overlap.\nlet got = unsafe { libraries.get_disjoint_unchecked_mut([\n    \"Athenæum\",\n    \"New York Public Library\",\n]) };\n// Missing keys result in None\nassert_eq!(got, [Some(&mut 1807), None]);
\n
1.0.0 · Source

pub fn contains_key<Q>(&self, k: &Q) -> bool
where\n K: Borrow<Q>,\n Q: Hash + Eq + ?Sized,

Returns true if the map contains a value for the specified key.

\n

The key may be any borrowed form of the map’s key type, but\nHash and Eq on the borrowed form must match those for\nthe key type.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut map = HashMap::new();\nmap.insert(1, \"a\");\nassert_eq!(map.contains_key(&1), true);\nassert_eq!(map.contains_key(&2), false);
\n
1.0.0 · Source

pub fn get_mut<Q>(&mut self, k: &Q) -> Option<&mut V>
where\n K: Borrow<Q>,\n Q: Hash + Eq + ?Sized,

Returns a mutable reference to the value corresponding to the key.

\n

The key may be any borrowed form of the map’s key type, but\nHash and Eq on the borrowed form must match those for\nthe key type.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut map = HashMap::new();\nmap.insert(1, \"a\");\nif let Some(x) = map.get_mut(&1) {\n    *x = \"b\";\n}\nassert_eq!(map[&1], \"b\");
\n
1.0.0 · Source

pub fn insert(&mut self, k: K, v: V) -> Option<V>

Inserts a key-value pair into the map.

\n

If the map did not have this key present, None is returned.

\n

If the map did have this key present, the value is updated, and the old\nvalue is returned. The key is not updated, though; this matters for\ntypes that can be == without being identical. See the module-level\ndocumentation for more.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut map = HashMap::new();\nassert_eq!(map.insert(37, \"a\"), None);\nassert_eq!(map.is_empty(), false);\n\nmap.insert(37, \"b\");\nassert_eq!(map.insert(37, \"c\"), Some(\"b\"));\nassert_eq!(map[&37], \"c\");
\n
Source

pub fn try_insert(\n &mut self,\n key: K,\n value: V,\n) -> Result<&mut V, OccupiedError<'_, K, V>>

🔬This is a nightly-only experimental API. (map_try_insert)

Tries to insert a key-value pair into the map, and returns\na mutable reference to the value in the entry.

\n

If the map already had this key present, nothing is updated, and\nan error containing the occupied entry and the value is returned.

\n
§Examples
\n

Basic usage:

\n\n
#![feature(map_try_insert)]\n\nuse std::collections::HashMap;\n\nlet mut map = HashMap::new();\nassert_eq!(map.try_insert(37, \"a\").unwrap(), &\"a\");\n\nlet err = map.try_insert(37, \"b\").unwrap_err();\nassert_eq!(err.entry.key(), &37);\nassert_eq!(err.entry.get(), &\"a\");\nassert_eq!(err.value, \"b\");
\n
1.0.0 · Source

pub fn remove<Q>(&mut self, k: &Q) -> Option<V>
where\n K: Borrow<Q>,\n Q: Hash + Eq + ?Sized,

Removes a key from the map, returning the value at the key if the key\nwas previously in the map.

\n

The key may be any borrowed form of the map’s key type, but\nHash and Eq on the borrowed form must match those for\nthe key type.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut map = HashMap::new();\nmap.insert(1, \"a\");\nassert_eq!(map.remove(&1), Some(\"a\"));\nassert_eq!(map.remove(&1), None);
\n
1.27.0 · Source

pub fn remove_entry<Q>(&mut self, k: &Q) -> Option<(K, V)>
where\n K: Borrow<Q>,\n Q: Hash + Eq + ?Sized,

Removes a key from the map, returning the stored key and value if the\nkey was previously in the map.

\n

The key may be any borrowed form of the map’s key type, but\nHash and Eq on the borrowed form must match those for\nthe key type.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet mut map = HashMap::new();\nmap.insert(1, \"a\");\nassert_eq!(map.remove_entry(&1), Some((1, \"a\")));\nassert_eq!(map.remove(&1), None);
\n
",0,"i3status_rs::formatting::Values"],["
1.0.0 · Source§

impl<K, Q, V, S> Index<&Q> for HashMap<K, V, S>
where\n K: Eq + Hash + Borrow<Q>,\n Q: Eq + Hash + ?Sized,\n S: BuildHasher,

Source§

fn index(&self, key: &Q) -> &V

Returns a reference to the value corresponding to the supplied key.

\n
§Panics
\n

Panics if the key is not present in the HashMap.

\n
Source§

type Output = V

The returned type after indexing.
","Index<&Q>","i3status_rs::formatting::Values"],["
Source§

impl<'de, K, V, S, E> IntoDeserializer<'de, E> for HashMap<K, V, S>
where\n K: IntoDeserializer<'de, E> + Eq + Hash,\n V: IntoDeserializer<'de, E>,\n S: BuildHasher,\n E: Error,

Source§

type Deserializer = MapDeserializer<'de, <HashMap<K, V, S> as IntoIterator>::IntoIter, E>

The type of the deserializer being converted into.
Source§

fn into_deserializer(\n self,\n) -> <HashMap<K, V, S> as IntoDeserializer<'de, E>>::Deserializer

Convert this value into a deserializer.
","IntoDeserializer<'de, E>","i3status_rs::formatting::Values"],["
1.0.0 · Source§

impl<K, V, S> IntoIterator for HashMap<K, V, S>

Source§

fn into_iter(self) -> IntoIter<K, V>

Creates a consuming iterator, that is, one that moves each key-value\npair out of the map in arbitrary order. The map cannot be used after\ncalling this.

\n
§Examples
\n
use std::collections::HashMap;\n\nlet map = HashMap::from([\n    (\"a\", 1),\n    (\"b\", 2),\n    (\"c\", 3),\n]);\n\n// Not possible with .iter()\nlet vec: Vec<(&str, i32)> = map.into_iter().collect();
\n
Source§

type Item = (K, V)

The type of the elements being iterated over.
Source§

type IntoIter = IntoIter<K, V>

Which kind of iterator are we turning this into?
","IntoIterator","i3status_rs::formatting::Values"],["
1.0.0 · Source§

impl<K, V, S> PartialEq for HashMap<K, V, S>
where\n K: Eq + Hash,\n V: PartialEq,\n S: BuildHasher,

Source§

fn eq(&self, other: &HashMap<K, V, S>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient,\nand should not be overridden without very good reason.
","PartialEq","i3status_rs::formatting::Values"],["
Source§

impl<K, V, H> Serialize for HashMap<K, V, H>
where\n K: Serialize,\n V: Serialize,

Source§

fn serialize<S>(\n &self,\n serializer: S,\n) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where\n S: Serializer,

Serialize this value into the given Serde serializer. Read more
","Serialize","i3status_rs::formatting::Values"],["
§

impl<'k, 'v, K, V, H> TryFrom<Dict<'k, 'v>> for HashMap<K, V, H>
where\n K: Basic + TryFrom<Value<'k>> + Eq + Hash,\n V: TryFrom<Value<'v>>,\n <K as TryFrom<Value<'k>>>::Error: Into<Error>,\n <V as TryFrom<Value<'v>>>::Error: Into<Error>,\n H: BuildHasher + Default,

§

type Error = Error

The type returned in the event of a conversion error.
§

fn try_from(\n v: Dict<'k, 'v>,\n) -> Result<HashMap<K, V, H>, <HashMap<K, V, H> as TryFrom<Dict<'k, 'v>>>::Error>

Performs the conversion.
","TryFrom>","i3status_rs::formatting::Values"],["
§

impl<'k, 'v, K, V, H> TryFrom<OwnedValue> for HashMap<K, V, H>
where\n K: Basic + TryFrom<Value<'k>> + Hash + Eq,\n V: TryFrom<Value<'v>>,\n H: BuildHasher + Default,\n <K as TryFrom<Value<'k>>>::Error: Into<Error>,\n <V as TryFrom<Value<'v>>>::Error: Into<Error>,

§

type Error = Error

The type returned in the event of a conversion error.
§

fn try_from(\n value: OwnedValue,\n) -> Result<HashMap<K, V, H>, <HashMap<K, V, H> as TryFrom<OwnedValue>>::Error>

Performs the conversion.
","TryFrom","i3status_rs::formatting::Values"],["
§

impl<'a, K, V, H> TryFrom<Value<'a>> for HashMap<K, V, H>
where\n K: Basic + TryFrom<Value<'a>> + Hash + Eq,\n V: TryFrom<Value<'a>>,\n H: BuildHasher + Default,\n <K as TryFrom<Value<'a>>>::Error: Into<Error>,\n <V as TryFrom<Value<'a>>>::Error: Into<Error>,

§

type Error = Error

The type returned in the event of a conversion error.
§

fn try_from(\n value: Value<'a>,\n) -> Result<HashMap<K, V, H>, <HashMap<K, V, H> as TryFrom<Value<'a>>>::Error>

Performs the conversion.
","TryFrom>","i3status_rs::formatting::Values"],["
§

impl<K, V, H> Type for HashMap<K, V, H>
where\n K: Type + Eq + Hash,\n V: Type,\n H: BuildHasher,

§

const SIGNATURE: &'static Signature

The signature for the implementing type, in parsed format. Read more
","Type","i3status_rs::formatting::Values"],["
1.0.0 · Source§

impl<K, V, S> Eq for HashMap<K, V, S>
where\n K: Eq + Hash,\n V: Eq,\n S: BuildHasher,

","Eq","i3status_rs::formatting::Values"],["
1.36.0 · Source§

impl<K, V, S> UnwindSafe for HashMap<K, V, S>
where\n K: UnwindSafe,\n V: UnwindSafe,\n S: UnwindSafe,

","UnwindSafe","i3status_rs::formatting::Values"]]]]); + if (window.register_type_impls) { + window.register_type_impls(type_impls); + } else { + window.pending_type_impls = type_impls; + } +})() +//{"start":55,"fragment_lengths":[147810]} \ No newline at end of file diff --git a/verify_icon_files.sh b/verify_icon_files.sh deleted file mode 100755 index 801a9690e2..0000000000 --- a/verify_icon_files.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -TMP="$(mktemp)" -trap 'rm -f -- "$TMP"' EXIT - -EXITCODE=0 - -for f in files/icons/*.toml; do - echo == Verifying $f == - comm -3 <(sed -n '/impl Default for Icons {/, /}/p' src/icons.rs | awk -F '"' '/=>/ {print $2}' | sort) <(awk '!/^#/ && /=/ {print $1}' $f | sort) > $TMP - if [ -s $TMP ]; then - echo "Found the following conflicts❗" - cat $TMP - EXITCODE=1 - else - echo "No conflicts found ✅" - fi -done - -exit $EXITCODE diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml deleted file mode 100644 index eec6599b37..0000000000 --- a/xtask/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "xtask" -version = "0.1.0" -edition = "2024" - -[dependencies] -anyhow = "1.0" -clap = "4.1" -clap_mangen = "0.2" -pandoc = "0.8" - -i3status-rs = { path = ".." } diff --git a/xtask/src/main.rs b/xtask/src/main.rs deleted file mode 100644 index 759d1472e8..0000000000 --- a/xtask/src/main.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::io::BufRead; -use std::io::Write; -use std::path::Path; -use std::{env, fs, io}; - -use clap::ArgMatches; -use clap::CommandFactory; - -use anyhow::{Context, Result}; - -use pandoc::PandocOutput; - -enum XTaskSubcommand { - GenerateManpage, -} - -impl TryFrom<&ArgMatches> for XTaskSubcommand { - type Error = clap::Error; - - fn try_from(value: &ArgMatches) -> Result { - if let Some(subcommand) = value.subcommand() { - match subcommand.0 { - "generate-manpage" => Ok(XTaskSubcommand::GenerateManpage), - _ => Err(clap::Error::new(clap::error::ErrorKind::InvalidSubcommand)), - } - } else { - Err(clap::Error::new(clap::error::ErrorKind::MissingSubcommand)) - } - } -} - -fn main() -> Result<()> { - let arguments_model = clap::Command::new("cargo xtask") - .version(env!("CARGO_PKG_VERSION")) - .author("AUTHOR") - .about("Automation using the cargo xtask convention.") - .arg_required_else_help(true) - .bin_name("cargo xtask") - .disable_version_flag(true) - .long_about( - " -Cargo xtask is a convention that allows easy integration of third party commands into the regular -cargo workflow. Xtask's are defined as a separate package and can be used for all kinds of -automation. - ", - ) - .subcommand( - clap::Command::new("generate-manpage") - .visible_alias("gm") - .about("Automatic man page generation. Saves the manpage to 'man/i3status-rs.1'"), - ); - - let program_parsed_arguments = arguments_model.get_matches(); - - let parsed_subcommand = XTaskSubcommand::try_from(&program_parsed_arguments)?; - - match parsed_subcommand { - XTaskSubcommand::GenerateManpage => generate_manpage(), - } -} - -fn generate_manpage() -> Result<()> { - let xtask_manifest_dir = env::var("CARGO_MANIFEST_DIR")?; - - let root_dir = Path::new(&xtask_manifest_dir) - .parent() - .context("invalid CARGO_MANIFEST_DIR")?; - - let src_dir = root_dir.join("src"); - let blocks_src_dir = src_dir.join("blocks"); - let man_dir = root_dir.join("man"); - let doc_dir = root_dir.join("doc"); - let man_out_path = man_dir.join("i3status-rs.1"); - - let mut result: Vec<_> = fs::read_dir(blocks_src_dir) - .unwrap() - .map(Result::unwrap) - .filter(|e| e.file_type().unwrap().is_file()) - .filter_map(|entry| { - let block_name = entry - .file_name() - .to_str() - .unwrap() - .rsplit_once('.') - .unwrap() - .0 - .to_string(); - - let file = fs::File::open(entry.path()).unwrap(); - let mut doc = String::new(); - - for line in io::BufReader::new(file) - .lines() - .map(Result::unwrap) - .take_while(|l| l.starts_with("//!")) - { - let mut line = &line[3..]; - if line.starts_with(' ') { - line = &line[1..]; - } - - if line.starts_with('#') { - doc.push_str("##") - } - doc.push_str(line); - doc.push('\n'); - } - - if !doc.is_empty() { - Some((block_name, doc)) - } else { - None - } - }) - .collect(); - - result.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - - let mut md_blocks = String::new(); - for (block, doc) in &result { - use std::fmt::Write; - writeln!(md_blocks, "## {block}\n{doc}").unwrap(); - } - - let man_blocks = { - // pandoc -o man/blocks.1 -t man man/blocks.md - let mut pandoc = pandoc::new(); - pandoc - .set_input_format( - pandoc::InputFormat::Other("markdown-tex_math_dollars".into()), - vec![], - ) - .set_input(pandoc::InputKind::Pipe(md_blocks)) - .set_output_format(pandoc::OutputFormat::Man, vec![]) - .set_output(pandoc::OutputKind::Pipe); - match pandoc.execute()? { - PandocOutput::ToBuffer(output) => output, - _ => unreachable!(), - } - }; - - let man_themes = { - let md_themes = fs::read_to_string(doc_dir.join("themes.md"))?; - // pandoc -o man/themes.1 -t man --base-header-level=2 doc/themes.md - let mut pandoc = pandoc::new(); - pandoc - .set_input_format(pandoc::InputFormat::Markdown, vec![]) - .set_input(pandoc::InputKind::Pipe(md_themes)) - .set_output_format(pandoc::OutputFormat::Man, vec![]) - .set_output(pandoc::OutputKind::Pipe) - .add_option(pandoc::PandocOption::ShiftHeadingLevelBy(2)); - match pandoc.execute()? { - PandocOutput::ToBuffer(output) => output, - _ => unreachable!(), - } - }; - - fs::create_dir_all(&man_dir).unwrap(); - let mut out = io::BufWriter::new(fs::File::create(man_out_path).unwrap()); - let man = clap_mangen::Man::new(i3status_rs::CliArgs::command()); - man.render_title(&mut out).unwrap(); - man.render_name_section(&mut out).unwrap(); - man.render_synopsis_section(&mut out).unwrap(); - man.render_description_section(&mut out).unwrap(); - man.render_options_section(&mut out).unwrap(); - - out.write_all(b".SH BLOCKS\n")?; - out.write_all(man_blocks.as_bytes())?; - out.write_all(b".SH THEMES\n")?; - out.write_all(man_themes.as_bytes())?; - - man.render_version_section(&mut out).unwrap(); - man.render_authors_section(&mut out).unwrap(); - - Ok(()) -}