Showing preview only (840K chars total). Download the full file or copy to clipboard to get everything.
Repository: elkowar/eww
Branch: master
Commit: 865cf631d5bb
Files: 282
Total size: 764.2 KB
Directory structure:
gitextract_tp854u_1/
├── .editorconfig
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── feature_request.yml
│ │ └── widget-request.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── build.yml
│ └── gh-pages.yml
├── .gitignore
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE
├── README.md
├── YUCK_MIGRATION.md
├── crates/
│ ├── eww/
│ │ ├── Cargo.toml
│ │ ├── build.rs
│ │ └── src/
│ │ ├── app.rs
│ │ ├── application_lifecycle.rs
│ │ ├── client.rs
│ │ ├── config/
│ │ │ ├── eww_config.rs
│ │ │ ├── inbuilt.rs
│ │ │ ├── mod.rs
│ │ │ ├── script_var.rs
│ │ │ ├── scss.rs
│ │ │ ├── system_stats.rs
│ │ │ └── window_definition.rs
│ │ ├── daemon_response.rs
│ │ ├── display_backend.rs
│ │ ├── error_handling_ctx.rs
│ │ ├── file_database.rs
│ │ ├── geometry.rs
│ │ ├── ipc_server.rs
│ │ ├── main.rs
│ │ ├── opts.rs
│ │ ├── paths.rs
│ │ ├── script_var_handler.rs
│ │ ├── server.rs
│ │ ├── state/
│ │ │ ├── mod.rs
│ │ │ ├── one_to_n_elements_map.rs
│ │ │ ├── scope.rs
│ │ │ ├── scope_graph.rs
│ │ │ └── test.rs
│ │ ├── util.rs
│ │ ├── widgets/
│ │ │ ├── build_widget.rs
│ │ │ ├── circular_progressbar.rs
│ │ │ ├── def_widget_macro.rs
│ │ │ ├── graph.rs
│ │ │ ├── mod.rs
│ │ │ ├── systray.rs
│ │ │ ├── transform.rs
│ │ │ ├── widget_definitions.rs
│ │ │ └── window.rs
│ │ ├── window_arguments.rs
│ │ └── window_initiator.rs
│ ├── eww_shared_util/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ ├── locale.rs
│ │ ├── span.rs
│ │ └── wrappers.rs
│ ├── notifier_host/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── host.rs
│ │ ├── icon.rs
│ │ ├── item.rs
│ │ ├── lib.rs
│ │ ├── proxy/
│ │ │ ├── dbus_menu.xml
│ │ │ ├── dbus_status_notifier_item.rs
│ │ │ ├── dbus_status_notifier_item.xml
│ │ │ ├── dbus_status_notifier_watcher.rs
│ │ │ ├── dbus_status_notifier_watcher.xml
│ │ │ └── mod.rs
│ │ └── watcher.rs
│ ├── simplexpr/
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ ├── build.rs
│ │ └── src/
│ │ ├── ast.rs
│ │ ├── dynval.rs
│ │ ├── error.rs
│ │ ├── eval.rs
│ │ ├── lib.rs
│ │ ├── parser/
│ │ │ ├── lalrpop_helpers.rs
│ │ │ ├── lexer.rs
│ │ │ ├── mod.rs
│ │ │ └── snapshots/
│ │ │ ├── simplexpr__parser__lexer__test__basic.snap
│ │ │ ├── simplexpr__parser__lexer__test__comments.snap
│ │ │ ├── simplexpr__parser__lexer__test__digit.snap
│ │ │ ├── simplexpr__parser__lexer__test__empty_interpolation.snap
│ │ │ ├── simplexpr__parser__lexer__test__escaping.snap
│ │ │ ├── simplexpr__parser__lexer__test__interpolation_1.snap
│ │ │ ├── simplexpr__parser__lexer__test__interpolation_nested.snap
│ │ │ ├── simplexpr__parser__lexer__test__json_in_interpolation.snap
│ │ │ ├── simplexpr__parser__lexer__test__number_in_ident.snap
│ │ │ ├── simplexpr__parser__lexer__test__quote_backslash_eof.snap
│ │ │ ├── simplexpr__parser__lexer__test__safe_interpolation.snap
│ │ │ ├── simplexpr__parser__lexer__test__simplexpr_lexer_basic.snap
│ │ │ ├── simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate-2.snap
│ │ │ ├── simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate-3.snap
│ │ │ ├── simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate.snap
│ │ │ ├── simplexpr__parser__lexer__test__symbol_spam.snap
│ │ │ ├── simplexpr__parser__lexer__test__weird_char_boundaries.snap
│ │ │ ├── simplexpr__parser__lexer__test__weird_nesting.snap
│ │ │ ├── simplexpr__parser__tests__test-10.snap
│ │ │ ├── simplexpr__parser__tests__test-11.snap
│ │ │ ├── simplexpr__parser__tests__test-12.snap
│ │ │ ├── simplexpr__parser__tests__test-13.snap
│ │ │ ├── simplexpr__parser__tests__test-14.snap
│ │ │ ├── simplexpr__parser__tests__test-15.snap
│ │ │ ├── simplexpr__parser__tests__test-16.snap
│ │ │ ├── simplexpr__parser__tests__test-2.snap
│ │ │ ├── simplexpr__parser__tests__test-3.snap
│ │ │ ├── simplexpr__parser__tests__test-4.snap
│ │ │ ├── simplexpr__parser__tests__test-5.snap
│ │ │ ├── simplexpr__parser__tests__test-6.snap
│ │ │ ├── simplexpr__parser__tests__test-7.snap
│ │ │ ├── simplexpr__parser__tests__test-8.snap
│ │ │ ├── simplexpr__parser__tests__test-9.snap
│ │ │ └── simplexpr__parser__tests__test.snap
│ │ ├── simplexpr_parser.lalrpop
│ │ └── snapshots/
│ │ ├── simplexpr__dynval__test__parse_duration-2.snap
│ │ ├── simplexpr__dynval__test__parse_duration-3.snap
│ │ ├── simplexpr__dynval__test__parse_duration-4.snap
│ │ ├── simplexpr__dynval__test__parse_duration-5.snap
│ │ ├── simplexpr__dynval__test__parse_duration-6.snap
│ │ ├── simplexpr__dynval__test__parse_duration-7.snap
│ │ ├── simplexpr__dynval__test__parse_duration-8.snap
│ │ ├── simplexpr__dynval__test__parse_duration.snap
│ │ ├── simplexpr__dynval__test__parse_vec-2.snap
│ │ ├── simplexpr__dynval__test__parse_vec-3.snap
│ │ ├── simplexpr__dynval__test__parse_vec-4.snap
│ │ ├── simplexpr__dynval__test__parse_vec-5.snap
│ │ ├── simplexpr__dynval__test__parse_vec-6.snap
│ │ ├── simplexpr__dynval__test__parse_vec-7.snap
│ │ ├── simplexpr__dynval__test__parse_vec-8.snap
│ │ ├── simplexpr__dynval__test__parse_vec.snap
│ │ ├── simplexpr__tests__test-10.snap
│ │ ├── simplexpr__tests__test-11.snap
│ │ ├── simplexpr__tests__test-12.snap
│ │ ├── simplexpr__tests__test-13.snap
│ │ ├── simplexpr__tests__test-14.snap
│ │ ├── simplexpr__tests__test-15.snap
│ │ ├── simplexpr__tests__test-16.snap
│ │ ├── simplexpr__tests__test-17.snap
│ │ ├── simplexpr__tests__test-18.snap
│ │ ├── simplexpr__tests__test-19.snap
│ │ ├── simplexpr__tests__test-2.snap
│ │ ├── simplexpr__tests__test-20.snap
│ │ ├── simplexpr__tests__test-21.snap
│ │ ├── simplexpr__tests__test-22.snap
│ │ ├── simplexpr__tests__test-23.snap
│ │ ├── simplexpr__tests__test-24.snap
│ │ ├── simplexpr__tests__test-25.snap
│ │ ├── simplexpr__tests__test-26.snap
│ │ ├── simplexpr__tests__test-3.snap
│ │ ├── simplexpr__tests__test-4.snap
│ │ ├── simplexpr__tests__test-5.snap
│ │ ├── simplexpr__tests__test-6.snap
│ │ ├── simplexpr__tests__test-7.snap
│ │ ├── simplexpr__tests__test-8.snap
│ │ ├── simplexpr__tests__test-9.snap
│ │ └── simplexpr__tests__test.snap
│ └── yuck/
│ ├── Cargo.toml
│ ├── build.rs
│ └── src/
│ ├── ast_error.rs
│ ├── config/
│ │ ├── attributes.rs
│ │ ├── backend_window_options.rs
│ │ ├── file_provider.rs
│ │ ├── mod.rs
│ │ ├── monitor.rs
│ │ ├── script_var_definition.rs
│ │ ├── snapshots/
│ │ │ ├── eww_config__config__test__config.snap
│ │ │ └── yuck__config__test__config.snap
│ │ ├── toplevel.rs
│ │ ├── validate.rs
│ │ ├── var_definition.rs
│ │ ├── widget_definition.rs
│ │ ├── widget_use.rs
│ │ ├── window_definition.rs
│ │ └── window_geometry.rs
│ ├── error.rs
│ ├── format_diagnostic.rs
│ ├── lib.rs
│ ├── parser/
│ │ ├── ast.rs
│ │ ├── ast_iterator.rs
│ │ ├── from_ast.rs
│ │ ├── lexer.rs
│ │ ├── mod.rs
│ │ ├── parse_error.rs
│ │ ├── parser.lalrpop
│ │ └── snapshots/
│ │ ├── eww_config__parser__element__test__test.snap
│ │ ├── eww_config__parser__test-10.snap
│ │ ├── eww_config__parser__test-11.snap
│ │ ├── eww_config__parser__test-12.snap
│ │ ├── eww_config__parser__test-13.snap
│ │ ├── eww_config__parser__test-14.snap
│ │ ├── eww_config__parser__test-15.snap
│ │ ├── eww_config__parser__test-16.snap
│ │ ├── eww_config__parser__test-17.snap
│ │ ├── eww_config__parser__test-2.snap
│ │ ├── eww_config__parser__test-3.snap
│ │ ├── eww_config__parser__test-4.snap
│ │ ├── eww_config__parser__test-5.snap
│ │ ├── eww_config__parser__test-6.snap
│ │ ├── eww_config__parser__test-7.snap
│ │ ├── eww_config__parser__test-8.snap
│ │ ├── eww_config__parser__test-9.snap
│ │ ├── eww_config__parser__test.snap
│ │ ├── yuck__parser__lexer__test__basic.snap
│ │ ├── yuck__parser__lexer__test__basic_simplexpr.snap
│ │ ├── yuck__parser__lexer__test__char_boundary.snap
│ │ ├── yuck__parser__lexer__test__end_with_string_interpolation.snap
│ │ ├── yuck__parser__lexer__test__escaped_quote.snap
│ │ ├── yuck__parser__lexer__test__escaped_strings.snap
│ │ ├── yuck__parser__lexer__test__quotes_in_quotes.snap
│ │ ├── yuck__parser__lexer__yuck_lexer-2.snap
│ │ ├── yuck__parser__lexer__yuck_lexer-3.snap
│ │ ├── yuck__parser__lexer__yuck_lexer-4.snap
│ │ ├── yuck__parser__lexer__yuck_lexer.snap
│ │ ├── yuck__parser__test-10.snap
│ │ ├── yuck__parser__test-11.snap
│ │ ├── yuck__parser__test-12.snap
│ │ ├── yuck__parser__test-13.snap
│ │ ├── yuck__parser__test-14.snap
│ │ ├── yuck__parser__test-15.snap
│ │ ├── yuck__parser__test-16.snap
│ │ ├── yuck__parser__test-17.snap
│ │ ├── yuck__parser__test-2.snap
│ │ ├── yuck__parser__test-3.snap
│ │ ├── yuck__parser__test-4.snap
│ │ ├── yuck__parser__test-5.snap
│ │ ├── yuck__parser__test-6.snap
│ │ ├── yuck__parser__test-7.snap
│ │ ├── yuck__parser__test-8.snap
│ │ ├── yuck__parser__test-9.snap
│ │ ├── yuck__parser__test.snap
│ │ ├── yuck__parser__test__test-10.snap
│ │ ├── yuck__parser__test__test-11.snap
│ │ ├── yuck__parser__test__test-12.snap
│ │ ├── yuck__parser__test__test-13.snap
│ │ ├── yuck__parser__test__test-14.snap
│ │ ├── yuck__parser__test__test-15.snap
│ │ ├── yuck__parser__test__test-16.snap
│ │ ├── yuck__parser__test__test-17.snap
│ │ ├── yuck__parser__test__test-2.snap
│ │ ├── yuck__parser__test__test-3.snap
│ │ ├── yuck__parser__test__test-4.snap
│ │ ├── yuck__parser__test__test-5.snap
│ │ ├── yuck__parser__test__test-6.snap
│ │ ├── yuck__parser__test__test-7.snap
│ │ ├── yuck__parser__test__test-8.snap
│ │ ├── yuck__parser__test__test-9.snap
│ │ └── yuck__parser__test__test.snap
│ └── value/
│ ├── coords.rs
│ └── mod.rs
├── default.nix
├── docs/
│ ├── .gitignore
│ ├── book.toml
│ ├── src/
│ │ ├── SUMMARY.md
│ │ ├── configuration.md
│ │ ├── eww.md
│ │ ├── examples.md
│ │ ├── expression_language.md
│ │ ├── magic-vars.md
│ │ ├── troubleshooting.md
│ │ ├── widgets.md
│ │ └── working_with_gtk.md
│ └── theme/
│ ├── book.js
│ ├── css/
│ │ ├── chrome.css
│ │ ├── general.css
│ │ ├── print.css
│ │ └── variables.css
│ ├── highlight.css
│ ├── highlight.js
│ └── index.hbs
├── examples/
│ ├── data-structures/
│ │ ├── eww.scss
│ │ └── eww.yuck
│ └── eww-bar/
│ ├── eww.scss
│ ├── eww.yuck
│ └── scripts/
│ └── getvol
├── flake.nix
├── gen-docs.ts
├── rust-toolchain.toml
├── rustfmt.toml
└── shell.nix
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [elkowar]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: elkowar
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report
description: Report a bug you have encountered
title: "[BUG] "
labels: bug
body:
- type: checkboxes
attributes:
label: Checklist before submitting an issue
options:
- label: I have searched through the existing [closed and open issues](https://github.com/elkowar/eww/issues?q=is%3Aissue) for eww and made sure this is not a duplicate
required: true
- label: I have specifically verified that this bug is not a common [user error](https://github.com/elkowar/eww/issues?q=is%3Aissue+label%3Ano-actual-bug+is%3Aclosed)
required: true
- label: I am providing as much relevant information as I am able to in this bug report (Minimal config to reproduce the issue for example, if applicable)
required: true
- type: textarea
attributes:
label: "Description of the bug"
description: "A clear an concise description of what the bug is."
validations:
required: true
- type: textarea
attributes:
label: "Reproducing the issue"
description: "Please provide a clear and and minimal description of how to reproduce the bug. If possible, provide a simple example configuration that shows the issue."
validations:
required: false
- type: textarea
attributes:
label: "Expected behaviour"
description: "A clear and concise description of what you expected to happen."
validations:
required: false
- type: textarea
attributes:
label: "Additional context"
description: "If applicable, provide additional context or screenshots here."
validations:
required: false
- type: textarea
attributes:
label: "Platform and environment"
description: "Does this happen on wayland, X11, or on both? What WM/Compositor are you using? Which version of eww are you using? (when using a git version, optimally provide the exact commit ref)."
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
description: Suggest an idea for this project
title: "[FEATURE] "
labels: [enhancement]
body:
- type: textarea
attributes:
label: "Description of the requested feature"
description: "Give a clear and concise description of the feature you are proposing, including examples for when it would be useful."
validations:
required: true
- type: textarea
attributes:
label: "Proposed configuration syntax"
description: "If the feature you are requesting would add or change something to the Eww configuration, please provide an example for how the feature could be used."
validations:
required: false
- type: textarea
attributes:
label: "Additional context"
description: "If applicable, provide additional context or screenshots here."
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/widget-request.yml
================================================
name: Widget request
description: Suggest an new Widget
title: "[WIDGET] "
labels: [widget-request]
body:
- type: textarea
attributes:
label: "Description of the widget"
description: "Provide an explanation of the widget."
validations:
required: true
- type: textarea
attributes:
label: "Implementation proposal"
description: "If applicable, describe which GTK-widgets this widget would be based on. A gallery of GTK widgets can be found at https://docs.gtk.org/gtk3. Please include links to the respective GTK documentation pages."
validations:
required: false
- type: textarea
attributes:
label: "Example usage"
description: "Provide an example snippet of configuration showcasing how the widget could be used, including the attributes the widget should support. For anything non-obvious, include an explanation of how the properties should behave."
validations:
required: false
- type: textarea
attributes:
label: "Additional context"
description: "Provide any additional context if applicable."
validations:
required: false
================================================
FILE: .github/pull_request_template.md
================================================
Please follow this template, if applicable.
## Description
Provide a short description of the changes your PR introduces.
This includes the actual feature you are adding,
as well as any other relevant additions that were necessary to implement your feature.
## Usage
When adding a widget or anything else that affects the configuration,
please provide a minimal example configuration snippet showcasing how to use it and
### Showcase
When adding widgets, please provide screenshots showcasing how your widget looks.
This is not strictly required, but strongly appreciated.
## Additional Notes
Anything else you want to add, such as remaining questions or explanations.
## Checklist
Please make sure you can check all the boxes that apply to this PR.
- [ ] All widgets I've added are correctly documented.
- [ ] I added my changes to CHANGELOG.md, if appropriate.
- [ ] The documentation in the `docs/content/main` directory has been adjusted to reflect my changes.
- [ ] I used `cargo fmt` to automatically format all code before committing
================================================
FILE: .github/workflows/build.yml
================================================
name: build
on:
push:
branches: [master]
pull_request:
branches: [master]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libdbusmenu-gtk3-dev
- uses: actions/checkout@v4
- name: Setup rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy,rustfmt
- name: Load rust cache
uses: Swatinem/rust-cache@v2
- name: Setup problem matchers
uses: r7kamura/rust-problem-matchers@v1
- name: Check formatting
run: cargo fmt -- --check
- name: Check with default features
run: cargo check
- name: Run tests
run: cargo test
- name: Check x11 only
run: cargo check --no-default-features --features=x11
- name: Check wayland only
run: cargo check --no-default-features --features=wayland
- name: Check no-backend
run: cargo check --no-default-features
================================================
FILE: .github/workflows/gh-pages.yml
================================================
name: Build and deploy Github pages
on:
push:
branches:
- master
paths:
- "docs/**"
- "gen-docs.ts"
- "crates/eww/src/widgets/widget_definitions.rs"
- "crates/eww/src/config/inbuilt.rs"
- ".github/workflows/**"
jobs:
build:
name: Build mdBook
runs-on: ubuntu-latest
steps:
# Checkout
- uses: actions/checkout@master
# Build widget documentation
- name: Use deno to build widget documentation
uses: denoland/setup-deno@main
with:
deno-version: "v1.x"
- run: deno run --allow-read --allow-write gen-docs.ts ./crates/eww/src/widgets/widget_definitions.rs ./crates/eww/src/config/inbuilt.rs
# Build & deploy
- name: build mdBook page
uses: peaceiris/actions-mdbook@v1
with:
mdbook-version: '0.4.8'
- run: mdbook build docs
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/book/
================================================
FILE: .gitignore
================================================
/target
/**/target
/result
/result-*
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to eww will be listed here, starting at changes since version 0.2.0.
## Unreleased
### BREAKING CHANGES
- [#1176](https://github.com/elkowar/eww/pull/1176) changed safe access (`?.`) behavior:
Attempting to index in an empty JSON string (`'""'`) is now an error.
### Fixes
- Fix crash on invalid `formattime` format string (By: luca3s)
- Fix crash on NaN or infinite graph value (By: luca3s)
- Re-enable some scss features (By: w-lfchen)
- Fix and refactor nix flake (By: w-lfchen)
- Fix remove items from systray (By: vnva)
- Fix the gtk `stack` widget (By: ovalkonia)
- Fix values in the `EWW_NET` variable (By: mario-kr)
- Fix the gtk `expander` widget (By: ovalkonia)
- Fix wayland monitor names support (By: dragonnn)
- Load systray items that are registered without a path (By: Kage-Yami)
- `get_locale` now follows POSIX standard for locale selection (By: mirhahn, w-lfchen)
- Improve multi-monitor handling under wayland (By: bkueng)
### Features
- Add warning and docs for incompatible `:anchor` and `:exclusive` options
- Add `eww poll` subcommand to force-poll a variable (By: kiana-S)
- Add OnDemand support for focusable on wayland (By: GallowsDove)
- Add jq `raw-output` support (By: RomanHargrave)
- Update rust toolchain to 1.81.0 (By: w-lfchen)
- Add `:fill-svg` and `:preserve-aspect-ratio` properties to images (By: hypernova7, w-lfchen)
- Add `:truncate` property to labels, disabled by default (except in cases where truncation would be enabled in version `0.5.0` and before) (By: Rayzeq).
- Add support for `:hover` css selectors for tray items (By: zeapoz)
- Add scss support for the `:style` widget property (By: ovalkonia)
- Add `min` and `max` function calls to simplexpr (By: ovalkonia)
- Add `flip-x`, `flip-y`, `vertical` options to the graph widget to determine its direction
- Add `transform-origin-x`/`transform-origin-y` properties to transform widget (By: mario-kr)
- Add keyboard support for button presses (By: julianschuler)
- Support empty string for safe access operator (By: ModProg)
- Add `log` function calls to simplexpr (By: topongo)
- Add `:lines` and `:wrap-mode` properties to label widget (By: vaporii)
- Add `value-pos` to scale widget (By: ipsvn)
- Add `floor` and `ceil` function calls to simplexpr (By: wsbankenstein)
- Add `formatbytes` function calls to simplexpr (By: topongo)
## [0.6.0] (21.04.2024)
### Fixes
- The `shell-completions` subcommand is now run before anything is set up
- Fix nix flake
- Fix `jq` (By: w-lfchen)
- Labels now use gtk's truncation system (By: Rayzeq).
### Features
- Add `systray` widget (By: ralismark)
- Add `:checked` property to checkbox (By: levnikmyskin)
## [0.5.0] (17.02.2024)
### BREAKING CHANGES
- Remove `eww windows` command, replace with `eww active-windows` and `eww list-windows`
### Features
- Add `:icon` and `:icon-size` to the image widget (By: Adrian Perez de Castro)
- Add `get_env` function (By: RegenJacob)
- Add `:namespace` window option
- Default to building with x11 and wayland support simultaneously
- Add `truncate-left` property on `label` widgets (By: kawaki-san)
- Add `gravity` property on `label` widgets (By: Elekrisk)
- Add support for safe access (`?.`) in simplexpr (By: oldwomanjosiah)
- Allow floating-point numbers in percentages for window-geometry
- Add support for safe access with index (`?.[n]`) (By: ModProg)
- Made `and`, `or` and `?:` lazily evaluated in simplexpr (By: ModProg)
- Add Vanilla CSS support (By: Ezequiel Ramis)
- Add `jq` function, offering jq-style json processing
- Add support for the `EWW_BATTERY` magic variable in FreeBSD, OpenBSD, and NetBSD (By: dangerdyke)
- Add `justify` property to the label widget, allowing text justification (By: n3oney)
- Add `EWW_TIME` magic variable (By: Erenoit)
- Add trigonometric functions (`sin`, `cos`, `tan`, `cot`) and degree/radian conversions (`degtorad`, `radtodeg`) (By: end-4)
- Add `substring` function to simplexpr
- Add `--duration` flag to `eww open`
- Add support for referring to monitor with `<primary>`
- Add support for multiple matchers in `monitor` field
- Add `stack` widget (By: vladaviedov)
- Add `unindent` property to the label widget, allowing to disable removal of leading spaces (By: nrv)
- Switch to stable rust toolchain (1.76)
- Add `tooltip` widget, which allows setting a custom tooltip (not only text), to a widget (By: Rayzeq)
- Add `eww shell-completions` command, generating completion scripts for different shells
### Fixes
- Fix wrong values in `EWW_NET`
- Fix logfiles growing indefinitely
## [0.4.0] (04.09.2022)
### BREAKING CHANGES
- Change `calendar`-widget to index months starting at 1 rather than indexed from 0
### Features
- Add support for output names in X11 to select `:monitor`.
- Add support for `:active`-pseudoselector on eventbox (By: viandoxdev)
- Add support for `:password` on input (By: viandoxdev)
### Notable fixes and other changes
- Scale now only runs the onchange command on changes caused by user-interaction
- Improve CSS error reporting
- Fix deflisten scripts not always getting cleaned up properly
- Add `:round-digits` to scale widget (By: gavynriebau)
- Fix cirular-progress not properly displaying 100% values when clockwise is false
- Fix temperatures inside `EWW_TEMPS` not being accessible if at least one value is `NaN`
## 0.3.0 (26.05.2022)
### BREAKING CHANGES
- Change the onclick command API to support multiple placeholders.
This changes. the behaviour of the calendar widget's onclick as well as the onhover and onhoverlost
events. Instead of providing the entire date (or, respecively, the x and y mouse coordinates) in
a single value (`day.month.year`, `x y`), the values are now provided as separate placeholders.
The day can now be accessed with `{0}`, the month with `{1}`, and the year with `{2}`, and
similarly x and y are accessed with `{0}` and `{1}`.
### Features
- Add `eww inspector` command
- Add `--no-daemonize` flag
- Add support for displaying marks on `scale`-widget (By: druskus20)
- Add `children`-widget that allows custom widgets to make use of children
- Add support for `:hover` css selectors for eventbox (By: druskus20)
- Add `eww get` subcommand (By: druskus20)
- Add circular progress widget (By: druskus20)
- Add `:xalign` and `:yalign` to labels (By: alecsferra)
- Add `graph` widget (By: druskus20)
- Add `>=` and `<=` operators to simplexpr (By: viandoxdev)
- Add `desktop` window type (By: Alvaro Lopez)
- Add `scroll` widget (By: viandoxdev)
- Add `notification` window type
- Add drag and drop functionality to eventbox
- Add `search`, `captures`, `stringlength`, `arraylength` and `objectlength` functions for expressions (By: MartinJM, ElKowar)
- Add `matches` function
- Add `transform` widget (By: druskus20)
- Add `:onaccept` to input field, add `:onclick` to eventbox
- Add `EWW_CMD`, `EWW_CONFIG_DIR`, `EWW_EXECUTABLE` magic variables
- Add `overlay` widget (By: viandoxdev)
- Add arguments option to `defwindow` (By: WilfSilver)
### Notable Internal changes
- Rework state management completely, now making local state and dynamic widget hierarchy changes possible.
### Notable fixes and other changes
- Fix `onscroll` gtk-bug where the first event is emitted incorrectly (By: druskus20)
- Allow windows to get moved when windowtype is `normal`
- Added more examples
- List system-level dependencies in documentation
- Document structure of magic variables (By: legendofmiracles)
- Updated dependencies
================================================
FILE: Cargo.toml
================================================
[workspace]
members = ["crates/*"]
resolver = "2"
[workspace.dependencies]
simplexpr = { version = "0.1.0", path = "crates/simplexpr" }
eww_shared_util = { version = "0.1.0", path = "crates/eww_shared_util" }
yuck = { version = "0.1.0", path = "crates/yuck", default-features = false }
notifier_host = { version = "0.1.0", path = "crates/notifier_host" }
anyhow = "1.0.86"
bincode = "1.3.3"
bytesize = "2.0.1"
cached = "0.53.1"
chrono = "0.4.38"
chrono-tz = "0.10.0"
clap = { version = "4.5.1", features = ["derive"] }
clap_complete = "4.5.12"
codespan-reporting = "0.11"
derive_more = { version = "1", features = [
"as_ref",
"debug",
"display",
"from",
"from_str",
] }
extend = "1.2"
futures = "0.3.30"
grass = "0.13.4"
gtk = "0.18.1"
insta = "1.7"
itertools = "0.13.0"
jaq-core = "1.5.1"
jaq-parse = "1.0.3"
jaq-std = "1.6.0"
jaq-interpret = "1.5.0"
jaq-syn = "1.6.0"
lalrpop = { version = "0.21", features = ["unicode"] }
lalrpop-util = { version = "0.21", features = ["unicode"] }
libc = "0.2"
log = "0.4"
maplit = "1"
nix = "0.29.0"
notify = "6.1.1"
once_cell = "1.19"
pretty_assertions = "1.4.0"
pretty_env_logger = "0.5.0"
ref-cast = "1.0.22"
regex = "1.10.5"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
simple-signal = "1.1"
smart-default = "0.7.1"
static_assertions = "1.1.0"
strsim = "0.11"
strum = { version = "0.26", features = ["derive"] }
sysinfo = "0.31.2"
thiserror = "1.0"
tokio-util = "0.7.11"
tokio = { version = "1.39.2", features = ["full"] }
unescape = "0.1"
wait-timeout = "0.2"
zbus = { version = "3.15.2", default-features = false, features = ["tokio"] }
[profile.dev]
split-debuginfo = "unpacked"
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2020 ElKowar
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.
================================================
FILE: README.md
================================================
[](https://deps.rs/repo/github/elkowar/eww)
# Eww
<img src="./.github/EwwLogo.svg" height="100" align="left"/>
Elkowars Wacky Widgets is a standalone widget system made in Rust that allows you to implement
your own, custom widgets in any window manager.
Documentation **and instructions on how to install** can be found [here](https://elkowar.github.io/eww).
Dharmx also wrote a nice, beginner friendly introductory guide for eww [here](https://dharmx.is-a.dev/eww-powermenu/).
## Check out another cool project by me
<img src="https://raw.githubusercontent.com/elkowar/yolk/refs/heads/main/.github/images/yolk_logo.svg" height="100" align="right"/>
I'm currently busy working [yolk](https://github.com/elkowar/yolk),
which is a dotfile management solution that supports a unique spin on templating: *templating without template files*.
To find out more, check out the [website and documentation](https://elkowar.github.io/yolk)!
## Examples
(Note that some of these still make use of the old configuration syntax.)
* A basic bar, see [examples](./examples/eww-bar)

* [Some setups by Druskus20](https://github.com/druskus20/eugh)

* [My own vertical bar](https://github.com/elkowar/dots-of-war/tree/master/eww-bar/.config/eww-bar)
<img src="https://raw.githubusercontent.com/elkowar/dots-of-war/master/eww-bar/.config/eww-bar/showcase.png" height="400" width="auto"/>
* [Vertical Bar by Rxyhn](https://github.com/rxyhn/bspdots)
<div align="left">

</div>
* [Setup by Axarva](https://github.com/Axarva/dotfiles-2.0)

* [Setup by adi1090x](https://github.com/adi1090x/widgets)

* [i3 Bar replacement by owenrumney](https://github.com/owenrumney/eww-bar)


* [Setups by iSparsh](https://github.com/iSparsh/gross)

* [topbar by saimoomedits](https://github.com/Saimoomedits/eww-widgets)
<div align="center">

</div>
* [Activate Linux by Nycta](https://github.com/Nycta-b424b3c7/eww_activate-linux)
<div align="left">

</div>
## Contribewwting
If you want to contribute anything, like adding new widgets, features, or subcommands (including sample configs), you should definitely do so.
### Steps
1. Fork this repository
2. Install dependencies
3. Smash your head against the keyboard from frustration (coding is hard)
4. Write down your changes in CHANGELOG.md
5. Open a pull request once you're finished
## Widget
https://en.wikipedia.org/wiki/Wikipedia:Widget
================================================
FILE: YUCK_MIGRATION.md
================================================
# Migrating to yuck
Yuck is the new configuration syntax used by eww.
While the syntax has changed dramatically, the general structure of the configuration
has stayed mostly the same.
Most notably, the top-level blocks are now gone.
This means that `defvar`, `defwidget`, etc blocks no longer need to be in separate
sections of the file, but instead can be put wherever you need them.
Explaining the exact syntax of yuck would be significantly less effective than just
looking at an example, as the general syntax is very simple.
Thus, to get a feel for yuck, read through the [example configuration](./examples/eww-bar/eww.yuck).
Additionally, a couple smaller things have been changed.
The fields and structure of the `defwindow` block as been adjusted to better reflect
the options provided by the displayserver that is being used.
The major changes are:
- The `screen` field is now called `monitor`
- `reserve` and `geometry` are now structured slightly differently (see [here](./docs/src/configuration.md#creating-your-first-window))
To see how exactly the configuration now looks, check the [respective documentation](./docs/src/configuration.md#creating-your-first-window)
## Automatically converting your configuration
A couple _amazing_ people have started to work on an [automatic converter](https://github.com/undefinedDarkness/ewwxml) that can turn your
old eww.xml into the new yuck format!
================================================
FILE: crates/eww/Cargo.toml
================================================
[package]
name = "eww"
version = "0.6.0"
authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"]
description = "Widgets for everyone!"
license = "MIT"
repository = "https://github.com/elkowar/eww"
homepage = "https://github.com/elkowar/eww"
edition = "2021"
[features]
default = ["x11", "wayland"]
x11 = ["gdkx11", "x11rb"]
wayland = ["gtk-layer-shell"]
[dependencies]
simplexpr.workspace = true
eww_shared_util.workspace = true
yuck.workspace = true
notifier_host.workspace = true
gtk-layer-shell = { version = "0.8.1", optional = true, features=["v0_6"] }
gdkx11 = { version = "0.18", optional = true }
x11rb = { version = "0.13.1", features = ["randr"], optional = true }
gdk-sys = "0.18.0"
ordered-stream = "0.2.0"
grass.workspace = true
anyhow.workspace = true
bincode.workspace = true
chrono.workspace = true
clap = { workspace = true, features = ["derive"] }
clap_complete.workspace = true
codespan-reporting.workspace = true
derive_more.workspace = true
extend.workspace = true
futures.workspace = true
gtk.workspace = true
itertools.workspace = true
libc.workspace = true
log.workspace = true
maplit.workspace = true
nix = { workspace = true, features = ["process", "fs", "signal"] }
notify.workspace = true
once_cell.workspace = true
pretty_env_logger.workspace = true
regex.workspace = true
serde_json.workspace = true
serde = { workspace = true, features = ["derive"] }
simple-signal.workspace = true
sysinfo = { workspace = true }
tokio-util.workspace = true
tokio = { workspace = true, features = ["full"] }
unescape.workspace = true
wait-timeout.workspace = true
zbus = { workspace = true, default-features = false, features = ["tokio"] }
================================================
FILE: crates/eww/build.rs
================================================
use std::process::Command;
fn main() {
let output = Command::new("git").args(["rev-parse", "HEAD"]).output();
if let Ok(output) = output {
if let Ok(hash) = String::from_utf8(output.stdout) {
println!("cargo:rustc-env=GIT_HASH={}", hash);
println!("cargo:rustc-env=CARGO_PKG_VERSION={} {}", env!("CARGO_PKG_VERSION"), hash);
}
}
let output = Command::new("git").args(["show", "-s", "--format=%ci"]).output();
if let Ok(output) = output {
if let Ok(date) = String::from_utf8(output.stdout) {
println!("cargo:rustc-env=GIT_COMMIT_DATE={}", date);
}
}
}
================================================
FILE: crates/eww/src/app.rs
================================================
use crate::{
daemon_response::DaemonResponseSender,
display_backend::DisplayBackend,
error_handling_ctx,
gtk::prelude::{ContainerExt, CssProviderExt, GtkWindowExt, MonitorExt, StyleContextExt, WidgetExt},
paths::EwwPaths,
script_var_handler::ScriptVarHandlerHandle,
state::scope_graph::{ScopeGraph, ScopeIndex},
widgets::window::Window,
window_arguments::WindowArguments,
window_initiator::WindowInitiator,
*,
};
use anyhow::anyhow;
use codespan_reporting::files::Files;
use eww_shared_util::{Span, VarName};
use gdk::Monitor;
use glib::ObjectExt;
use gtk::{gdk, glib};
use itertools::Itertools;
use once_cell::sync::Lazy;
use simplexpr::{dynval::DynVal, SimplExpr};
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
marker::PhantomData,
rc::Rc,
};
use tokio::sync::mpsc::UnboundedSender;
use yuck::{
config::{
monitor::MonitorIdentifier,
script_var_definition::ScriptVarDefinition,
window_geometry::{AnchorPoint, WindowGeometry},
},
error::DiagError,
gen_diagnostic,
value::Coords,
};
/// A command for the eww daemon.
/// While these are mostly generated from eww CLI commands (see [`opts::ActionWithServer`]),
/// they may also be generated from other places internally.
#[derive(Debug)]
pub enum DaemonCommand {
NoOp,
UpdateVars(Vec<(VarName, DynVal)>),
PollVars(Vec<VarName>),
ReloadConfigAndCss(DaemonResponseSender),
OpenInspector,
OpenMany {
windows: Vec<(String, String)>,
args: Vec<(String, VarName, DynVal)>,
should_toggle: bool,
sender: DaemonResponseSender,
},
OpenWindow {
window_name: String,
instance_id: Option<String>,
pos: Option<Coords>,
size: Option<Coords>,
anchor: Option<AnchorPoint>,
screen: Option<MonitorIdentifier>,
should_toggle: bool,
duration: Option<std::time::Duration>,
sender: DaemonResponseSender,
args: Option<Vec<(VarName, DynVal)>>,
},
CloseWindows {
windows: Vec<String>,
auto_reopen: bool,
sender: DaemonResponseSender,
},
KillServer,
CloseAll,
PrintState {
all: bool,
sender: DaemonResponseSender,
},
GetVar {
name: String,
sender: DaemonResponseSender,
},
PrintDebug(DaemonResponseSender),
PrintGraph(DaemonResponseSender),
ListWindows(DaemonResponseSender),
ListActiveWindows(DaemonResponseSender),
}
/// An opened window.
#[derive(Debug)]
pub struct EwwWindow {
pub name: String,
pub scope_index: ScopeIndex,
pub gtk_window: Window,
pub destroy_event_handler_id: Option<glib::SignalHandlerId>,
}
impl EwwWindow {
/// Close the GTK window and disconnect the destroy event-handler.
///
/// You need to make sure that the scope get's properly cleaned from the state graph
/// and that script-vars get cleaned up properly
pub fn close(self) {
log::info!("Closing gtk window {}", self.name);
self.gtk_window.close();
if let Some(handler_id) = self.destroy_event_handler_id {
self.gtk_window.disconnect(handler_id);
}
}
}
pub struct App<B: DisplayBackend> {
pub scope_graph: Rc<RefCell<ScopeGraph>>,
pub eww_config: config::EwwConfig,
/// Map of all currently open windows to their unique IDs
/// If no specific ID was specified whilst starting the window,
/// it will be the same as the window name.
/// Therefore, only one window of a given name can exist when not using IDs.
pub open_windows: HashMap<String, EwwWindow>,
pub instance_id_to_args: HashMap<String, WindowArguments>,
/// Window names that are supposed to be open, but failed.
/// When reloading the config, these should be opened again.
pub failed_windows: HashSet<String>,
pub css_provider: gtk::CssProvider,
/// Sender to send [`DaemonCommand`]s
pub app_evt_send: UnboundedSender<DaemonCommand>,
pub script_var_handler: ScriptVarHandlerHandle,
/// Senders that will cancel a windows auto-close timer when started with --duration.
pub window_close_timer_abort_senders: HashMap<String, futures::channel::oneshot::Sender<()>>,
pub paths: EwwPaths,
pub phantom: PhantomData<B>,
}
impl<B: DisplayBackend> std::fmt::Debug for App<B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("App")
.field("scope_graph", &*self.scope_graph.borrow())
.field("eww_config", &self.eww_config)
.field("open_windows", &self.open_windows)
.field("failed_windows", &self.failed_windows)
.field("window_arguments", &self.instance_id_to_args)
.field("paths", &self.paths)
.finish()
}
}
/// Wait until the .model() is available for all monitors (or there is a timeout)
async fn wait_for_monitor_model() {
let display = gdk::Display::default().expect("could not get default display");
let start = std::time::Instant::now();
loop {
let all_monitors_set =
(0..display.n_monitors()).all(|i| display.monitor(i).and_then(|monitor| monitor.model()).is_some());
if all_monitors_set {
break;
}
tokio::time::sleep(Duration::from_millis(10)).await;
if std::time::Instant::now() - start > Duration::from_millis(500) {
log::warn!("Timed out waiting for monitor model to be set");
break;
}
}
}
impl<B: DisplayBackend> App<B> {
/// Handle a [`DaemonCommand`] event, logging any errors that occur.
pub async fn handle_command(&mut self, event: DaemonCommand) {
if let Err(err) = self.try_handle_command(event).await {
error_handling_ctx::print_error(err);
}
}
/// Try to handle a [`DaemonCommand`] event.
async fn try_handle_command(&mut self, event: DaemonCommand) -> Result<()> {
log::debug!("Handling event: {:?}", &event);
match event {
DaemonCommand::NoOp => {}
DaemonCommand::OpenInspector => {
gtk::Window::set_interactive_debugging(true);
}
DaemonCommand::UpdateVars(mappings) => {
for (var_name, new_value) in mappings {
self.update_global_variable(var_name, new_value);
}
}
DaemonCommand::PollVars(names) => {
for var_name in names {
self.force_poll_variable(var_name);
}
}
DaemonCommand::ReloadConfigAndCss(sender) => {
// Wait for all monitor models to be set. When a new monitor gets added, this
// might not immediately be the case. And if we were to wait inside the
// connect_monitor_added callback, model() never gets set. So instead we wait here.
wait_for_monitor_model().await;
let mut errors = Vec::new();
let config_result = config::read_from_eww_paths(&self.paths);
if let Err(e) = config_result.and_then(|new_config| self.load_config(new_config)) {
errors.push(e)
}
match crate::config::scss::parse_scss_from_config(self.paths.get_config_dir()) {
Ok((file_id, css)) => {
if let Err(e) = self.load_css(file_id, &css) {
errors.push(anyhow!(e));
}
}
Err(e) => {
errors.push(e);
}
}
sender.respond_with_error_list(errors)?;
}
DaemonCommand::KillServer => {
log::info!("Received kill command, stopping server!");
self.stop_application();
}
DaemonCommand::CloseAll => {
log::info!("Received close command, closing all windows");
for window_name in self.open_windows.keys().cloned().collect::<Vec<String>>() {
self.close_window(&window_name, false)?;
}
}
DaemonCommand::OpenMany { windows, args, should_toggle, sender } => {
let errors = windows
.iter()
.map(|w| {
let (config_name, id) = w;
if should_toggle && self.open_windows.contains_key(id) {
self.close_window(id, false)
} else {
log::debug!("Config: {}, id: {}", config_name, id);
let window_args = args
.iter()
.filter(|(win_id, ..)| win_id.is_empty() || win_id == id)
.map(|(_, n, v)| (n.clone(), v.clone()))
.collect();
self.open_window(&WindowArguments::new_from_args(id.to_string(), config_name.clone(), window_args)?)
}
})
.filter_map(Result::err);
sender.respond_with_error_list(errors)?;
}
DaemonCommand::OpenWindow {
window_name,
instance_id,
pos,
size,
anchor,
screen: monitor,
should_toggle,
duration,
sender,
args,
} => {
let instance_id = instance_id.unwrap_or_else(|| window_name.clone());
let is_open = self.open_windows.contains_key(&instance_id);
let result = if should_toggle && is_open {
self.close_window(&instance_id, false)
} else {
self.open_window(&WindowArguments {
instance_id,
window_name,
pos,
size,
monitor,
anchor,
duration,
args: args.unwrap_or_default().into_iter().collect(),
})
};
sender.respond_with_result(result)?;
}
DaemonCommand::CloseWindows { windows, auto_reopen, sender } => {
let errors = windows.iter().map(|window| self.close_window(window, auto_reopen)).filter_map(Result::err);
// Ignore sending errors, as the channel might already be closed
let _ = sender.respond_with_error_list(errors);
}
DaemonCommand::PrintState { all, sender } => {
let scope_graph = self.scope_graph.borrow();
let used_globals_names = scope_graph.currently_used_globals();
let output = scope_graph
.global_scope()
.data
.iter()
.filter(|(key, _)| all || used_globals_names.contains(*key))
.map(|(key, value)| format!("{}: {}", key, value))
.join("\n");
sender.send_success(output)?
}
DaemonCommand::GetVar { name, sender } => {
let scope_graph = &*self.scope_graph.borrow();
let vars = &scope_graph.global_scope().data;
match vars.get(name.as_str()) {
Some(x) => sender.send_success(x.to_string())?,
None => sender.send_failure(format!("Variable not found \"{}\"", name))?,
}
}
DaemonCommand::ListWindows(sender) => {
let output = self.eww_config.get_windows().keys().join("\n");
sender.send_success(output)?
}
DaemonCommand::ListActiveWindows(sender) => {
let output = self.open_windows.iter().map(|(id, window)| format!("{id}: {}", window.name)).join("\n");
sender.send_success(output)?
}
DaemonCommand::PrintDebug(sender) => {
let output = format!("{:#?}", &self);
sender.send_success(output)?
}
DaemonCommand::PrintGraph(sender) => sender.send_success(self.scope_graph.borrow().visualize())?,
}
Ok(())
}
/// Fully stop eww:
/// close all windows, stop the script_var_handler, quit the gtk appliaction and send the exit instruction to the lifecycle manager
fn stop_application(&mut self) {
self.script_var_handler.stop_all();
for (_, window) in self.open_windows.drain() {
window.close();
}
gtk::main_quit();
let _ = crate::application_lifecycle::send_exit();
}
fn update_global_variable(&mut self, name: VarName, value: DynVal) {
let result = self.scope_graph.borrow_mut().update_global_value(&name, value);
if let Err(err) = result {
error_handling_ctx::print_error(err);
}
self.apply_run_while_expressions_mentioning(&name);
}
/// Variables may be referenced in defpoll :run-while expressions.
/// Thus, when a variable changes, the run-while conditions of all variables
/// that mention the changed variable need to be reevaluated and reapplied.
fn apply_run_while_expressions_mentioning(&mut self, name: &VarName) {
let mentioning_vars = match self.eww_config.get_run_while_mentions_of(name) {
Some(x) => x,
None => return,
};
let mentioning_vars = mentioning_vars.iter().filter_map(|name| self.eww_config.get_script_var(name).ok());
for var in mentioning_vars {
if let ScriptVarDefinition::Poll(poll_var) = var {
let scope_graph = self.scope_graph.borrow();
let run_while_result = scope_graph
.evaluate_simplexpr_in_scope(scope_graph.root_index, &poll_var.run_while_expr)
.map(|v| v.as_bool());
match run_while_result {
Ok(Ok(true)) => self.script_var_handler.add(var.clone()),
Ok(Ok(false)) => self.script_var_handler.stop_for_variable(poll_var.name.clone()),
Ok(Err(err)) => error_handling_ctx::print_error(anyhow!(err)),
Err(err) => error_handling_ctx::print_error(anyhow!(err)),
};
}
}
}
fn force_poll_variable(&mut self, name: VarName) {
match self.eww_config.get_script_var(&name) {
Err(err) => error_handling_ctx::print_error(err),
Ok(var) => {
if let ScriptVarDefinition::Poll(poll_var) = var {
log::debug!("force-polling var {}", &name);
match script_var_handler::run_poll_once(&poll_var) {
Err(err) => error_handling_ctx::print_error(err),
Ok(value) => self.update_global_variable(name, value),
}
} else {
error_handling_ctx::print_error(anyhow!("Script var '{}' is not polling", name))
}
}
}
}
/// Close a window and do all the required cleanups in the scope_graph and script_var_handler
fn close_window(&mut self, instance_id: &str, auto_reopen: bool) -> Result<()> {
if let Some(old_abort_send) = self.window_close_timer_abort_senders.remove(instance_id) {
_ = old_abort_send.send(());
}
let eww_window = self
.open_windows
.remove(instance_id)
.with_context(|| format!("Tried to close window with id '{instance_id}', but no such window was open"))?;
let scope_index = eww_window.scope_index;
eww_window.close();
self.scope_graph.borrow_mut().remove_scope(scope_index);
let unused_variables = self.scope_graph.borrow().currently_unused_globals();
for unused_var in unused_variables {
log::debug!("stopping script-var {}", &unused_var);
self.script_var_handler.stop_for_variable(unused_var.clone());
}
if auto_reopen {
self.failed_windows.insert(instance_id.to_string());
// There might be an alternative monitor available already, so try to re-open it immediately.
// This can happen for example when a monitor gets disconnected and another connected,
// and the connection event happens before the disconnect.
if let Some(window_arguments) = self.instance_id_to_args.get(instance_id) {
let _ = self.open_window(&window_arguments.clone());
}
} else {
self.instance_id_to_args.remove(instance_id);
}
Ok(())
}
fn open_window(&mut self, window_args: &WindowArguments) -> Result<()> {
let instance_id = &window_args.instance_id;
self.failed_windows.remove(instance_id);
log::info!("Opening window {} as '{}'", window_args.window_name, instance_id);
// if an instance of this is already running, close it
if self.open_windows.contains_key(instance_id) {
self.close_window(instance_id, false)?;
}
self.instance_id_to_args.insert(instance_id.to_string(), window_args.clone());
let open_result: Result<_> = (|| {
let window_name: &str = &window_args.window_name;
let window_def = self.eww_config.get_window(window_name)?.clone();
assert_eq!(window_def.name, window_name, "window definition name did not equal the called window");
let initiator = WindowInitiator::new(&window_def, window_args)?;
let root_index = self.scope_graph.borrow().root_index;
let scoped_vars_literal = initiator.get_scoped_vars().into_iter().map(|(k, v)| (k, SimplExpr::Literal(v))).collect();
let window_scope = self.scope_graph.borrow_mut().register_new_scope(
instance_id.to_string(),
Some(root_index),
root_index,
scoped_vars_literal,
)?;
let root_widget = crate::widgets::build_widget::build_gtk_widget(
&mut self.scope_graph.borrow_mut(),
Rc::new(self.eww_config.get_widget_definitions().clone()),
window_scope,
window_def.widget,
None,
)?;
root_widget.style_context().add_class(window_name);
let monitor = get_gdk_monitor(initiator.monitor.clone())?;
let mut eww_window = initialize_window::<B>(&initiator, monitor, root_widget, window_scope)?;
eww_window.gtk_window.style_context().add_class(window_name);
// initialize script var handlers for variables. As starting a scriptvar with the script_var_handler is idempodent,
// we can just start script vars that are already running without causing issues
// TODO maybe this could be handled by having a track_newly_used_variables function in the scope tree?
for used_var in self.scope_graph.borrow().variables_used_in_self_or_subscopes_of(eww_window.scope_index) {
if let Ok(script_var) = self.eww_config.get_script_var(&used_var) {
self.script_var_handler.add(script_var.clone());
}
}
eww_window.destroy_event_handler_id = Some(eww_window.gtk_window.connect_destroy({
let app_evt_sender = self.app_evt_send.clone();
let instance_id = instance_id.to_string();
move |_| {
// we don't care about the actual error response from the daemon as this is mostly just a fallback.
// Generally, this should get disconnected before the gtk window gets destroyed.
// This callback is triggered in 2 cases:
// - When the monitor of this window gets disconnected
// - When the window is closed manually.
// We don't distinguish here and assume the window should be reopened once a monitor
// becomes available again
let (response_sender, _) = daemon_response::create_pair();
let command = DaemonCommand::CloseWindows {
windows: vec![instance_id.clone()],
auto_reopen: true,
sender: response_sender,
};
if let Err(err) = app_evt_sender.send(command) {
log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err);
}
}
}));
let duration = window_args.duration;
if let Some(duration) = duration {
let app_evt_sender = self.app_evt_send.clone();
let (abort_send, abort_recv) = futures::channel::oneshot::channel();
glib::MainContext::default().spawn_local({
let instance_id = instance_id.to_string();
async move {
tokio::select! {
_ = glib::timeout_future(duration) => {
let (response_sender, mut response_recv) = daemon_response::create_pair();
let command = DaemonCommand::CloseWindows { windows: vec![instance_id.clone()], auto_reopen: false, sender: response_sender };
if let Err(err) = app_evt_sender.send(command) {
log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err);
}
_ = response_recv.recv().await;
}
_ = abort_recv => {}
}
}
});
if let Some(old_abort_send) = self.window_close_timer_abort_senders.insert(instance_id.to_string(), abort_send) {
_ = old_abort_send.send(());
}
}
self.open_windows.insert(instance_id.to_string(), eww_window);
Ok(())
})();
if let Err(err) = open_result {
self.failed_windows.insert(instance_id.to_string());
Err(err).with_context(|| format!("failed to open window `{}`", instance_id))
} else {
Ok(())
}
}
/// Load the given configuration, reloading all script-vars and attempting to reopen all windows that where opened.
pub fn load_config(&mut self, config: config::EwwConfig) -> Result<()> {
log::info!("Reloading windows");
self.script_var_handler.stop_all();
let old_handler = std::mem::replace(&mut self.script_var_handler, script_var_handler::init(self.app_evt_send.clone()));
old_handler.join_thread();
log::trace!("loading config: {:#?}", config);
self.eww_config = config;
self.scope_graph.borrow_mut().clear(self.eww_config.generate_initial_state()?);
let open_window_ids: Vec<String> =
self.open_windows.keys().cloned().chain(self.failed_windows.iter().cloned()).dedup().collect();
for instance_id in &open_window_ids {
let window_arguments = self.instance_id_to_args.get(instance_id).with_context(|| {
format!("Cannot reopen window, initial parameters were not saved correctly for {instance_id}")
})?;
self.open_window(&window_arguments.clone())?;
}
Ok(())
}
/// Load a given CSS string into the gtk css provider, returning a nicely formatted [`DiagError`] when GTK errors out
pub fn load_css(&mut self, file_id: usize, css: &str) -> Result<()> {
if let Err(err) = self.css_provider.load_from_data(css.as_bytes()) {
static PATTERN: Lazy<regex::Regex> = Lazy::new(|| regex::Regex::new(r"[^:]*:(\d+):(\d+)(.*)$").unwrap());
let nice_error_option: Option<_> = (|| {
let captures = PATTERN.captures(err.message())?;
let line = captures.get(1).unwrap().as_str().parse::<usize>().ok()?;
let msg = captures.get(3).unwrap().as_str();
let db = error_handling_ctx::FILE_DATABASE.read().ok()?;
let line_range = db.line_range(file_id, line - 1).ok()?;
let span = Span(line_range.start, line_range.end - 1, file_id);
Some(DiagError(gen_diagnostic!(msg, span)))
})();
match nice_error_option {
Some(error) => Err(anyhow!(error)),
None => Err(anyhow!("CSS error: {}", err.message())),
}
} else {
Ok(())
}
}
}
fn initialize_window<B: DisplayBackend>(
window_init: &WindowInitiator,
monitor: Monitor,
root_widget: gtk::Widget,
window_scope: ScopeIndex,
) -> Result<EwwWindow> {
let monitor_geometry = monitor.geometry();
let (actual_window_rect, x, y) = match window_init.geometry {
Some(geometry) => {
let rect = get_window_rectangle(geometry, monitor_geometry);
(Some(rect), rect.x(), rect.y())
}
_ => (None, 0, 0),
};
let window = B::initialize_window(window_init, monitor_geometry, x, y)
.with_context(|| format!("monitor {} is unavailable", window_init.monitor.clone().unwrap()))?;
window.set_title(&format!("Eww - {}", window_init.name));
window.set_position(gtk::WindowPosition::None);
window.set_gravity(gdk::Gravity::Center);
if let Some(actual_window_rect) = actual_window_rect {
window.set_size_request(actual_window_rect.width(), actual_window_rect.height());
window.set_default_size(actual_window_rect.width(), actual_window_rect.height());
}
window.set_decorated(false);
window.set_skip_taskbar_hint(true);
window.set_skip_pager_hint(true);
// run on_screen_changed to set the visual correctly initially.
on_screen_changed(&window, None);
window.connect_screen_changed(on_screen_changed);
window.add(&root_widget);
window.realize();
#[cfg(feature = "x11")]
if B::IS_X11 {
if let Some(geometry) = window_init.geometry {
let _ = apply_window_position(geometry, monitor_geometry, &window);
if window_init.backend_options.x11.window_type != yuck::config::backend_window_options::X11WindowType::Normal {
window.connect_configure_event(move |window, _| {
let _ = apply_window_position(geometry, monitor_geometry, window);
false
});
}
}
display_backend::set_xprops(&window, monitor, window_init)?;
}
window.show_all();
Ok(EwwWindow {
name: window_init.name.clone(),
gtk_window: window,
scope_index: window_scope,
destroy_event_handler_id: None,
})
}
/// Apply the provided window-positioning rules to the window.
#[cfg(feature = "x11")]
fn apply_window_position(mut window_geometry: WindowGeometry, monitor_geometry: gdk::Rectangle, window: &Window) -> Result<()> {
let gdk_window = window.window().context("Failed to get gdk window from gtk window")?;
window_geometry.size = Coords::from_pixels(window.size());
let actual_window_rect = get_window_rectangle(window_geometry, monitor_geometry);
let gdk_origin = gdk_window.origin();
if actual_window_rect.x() != gdk_origin.1 || actual_window_rect.y() != gdk_origin.2 {
gdk_window.move_(actual_window_rect.x(), actual_window_rect.y());
}
Ok(())
}
fn on_screen_changed(window: &Window, _old_screen: Option<&gdk::Screen>) {
let visual = gtk::prelude::GtkWindowExt::screen(window)
.and_then(|screen| screen.rgba_visual().filter(|_| screen.is_composited()).or_else(|| screen.system_visual()));
window.set_visual(visual.as_ref());
}
/// Get the monitor geometry of a given monitor, or the default if none is given
fn get_gdk_monitor(identifier: Option<MonitorIdentifier>) -> Result<Monitor> {
let display = gdk::Display::default().expect("could not get default display");
let monitor = match identifier {
Some(ident) => {
let mon = get_monitor_from_display(&display, &ident);
mon.with_context(|| {
let head = format!("Failed to get monitor {}\nThe available monitors are:", ident);
let mut body = String::new();
for m in 0..display.n_monitors() {
if let Some(model) = display.monitor(m).and_then(|x| x.model()) {
body.push_str(format!("\n\t[{}] {}", m, model).as_str());
}
}
format!("{}{}", head, body)
})?
}
None => display
.primary_monitor()
.context("Failed to get primary monitor from GTK. Try explicitly specifying the monitor on your window.")?,
};
Ok(monitor)
}
/// Get the name of monitor plug for given monitor number
/// workaround gdk not providing this information on wayland in regular calls
/// gdk_screen_get_monitor_plug_name is deprecated but works fine for that case
fn get_monitor_plug_name(display: &gdk::Display, monitor_num: i32) -> Option<&str> {
unsafe {
use glib::translate::ToGlibPtr;
let plug_name_pointer = gdk_sys::gdk_screen_get_monitor_plug_name(display.default_screen().to_glib_none().0, monitor_num);
use std::ffi::CStr;
CStr::from_ptr(plug_name_pointer).to_str().ok()
}
}
/// Returns the [Monitor][gdk::Monitor] structure corresponding to the identifer.
/// Outside of x11, only [MonitorIdentifier::Numeric] is supported
pub fn get_monitor_from_display(display: &gdk::Display, identifier: &MonitorIdentifier) -> Option<gdk::Monitor> {
match identifier {
MonitorIdentifier::List(list) => {
for ident in list {
if let Some(monitor) = get_monitor_from_display(display, ident) {
return Some(monitor);
}
}
None
}
MonitorIdentifier::Primary => display.primary_monitor(),
MonitorIdentifier::Numeric(num) => display.monitor(*num),
MonitorIdentifier::Name(name) => {
for m in 0..display.n_monitors() {
if let Some(model) = display.monitor(m).and_then(|x| x.model()) {
if model == *name || Some(name.as_str()) == get_monitor_plug_name(display, m) {
return display.monitor(m);
}
}
}
None
}
}
}
pub fn get_window_rectangle(geometry: WindowGeometry, screen_rect: gdk::Rectangle) -> gdk::Rectangle {
let (offset_x, offset_y) = geometry.offset.relative_to(screen_rect.width(), screen_rect.height());
let (width, height) = geometry.size.relative_to(screen_rect.width(), screen_rect.height());
let x = screen_rect.x() + offset_x + geometry.anchor_point.x.alignment_to_coordinate(width, screen_rect.width());
let y = screen_rect.y() + offset_y + geometry.anchor_point.y.alignment_to_coordinate(height, screen_rect.height());
gdk::Rectangle::new(x, y, width, height)
}
================================================
FILE: crates/eww/src/application_lifecycle.rs
================================================
//! Module concerned with handling the global application lifecycle of eww.
//! Currently, this only means handling application exit by providing a global
//! `recv_exit()` function which can be awaited to receive an event in case of application termination.
use anyhow::{Context, Result};
use once_cell::sync::Lazy;
use tokio::sync::broadcast;
pub static APPLICATION_EXIT_SENDER: Lazy<broadcast::Sender<()>> = Lazy::new(|| broadcast::channel(2).0);
/// Notify all listening tasks of the termination of the eww application process.
pub fn send_exit() -> Result<()> {
(APPLICATION_EXIT_SENDER).send(()).context("Failed to send exit lifecycle event")?;
Ok(())
}
/// Yields Ok(()) on application termination. Await on this in all long-running tasks
/// and perform any cleanup if necessary.
pub async fn recv_exit() -> Result<()> {
(APPLICATION_EXIT_SENDER).subscribe().recv().await.context("Failed to receive lifecycle event")
}
/// Select in a loop, breaking once a application termination event (see `crate::application_lifecycle`) is received.
#[macro_export]
macro_rules! loop_select_exiting {
($($content:tt)*) => {
loop {
tokio::select! {
Ok(()) = $crate::application_lifecycle::recv_exit() => {
break;
}
$($content)*
}
}
};
}
================================================
FILE: crates/eww/src/client.rs
================================================
use std::process::Stdio;
use crate::{
daemon_response::DaemonResponse,
opts::{self, ActionClientOnly},
paths::EwwPaths,
};
use anyhow::{Context, Result};
use std::{
io::{Read, Write},
os::unix::net::UnixStream,
};
pub fn handle_client_only_action(paths: &EwwPaths, action: ActionClientOnly) -> Result<()> {
match action {
ActionClientOnly::Logs => {
std::process::Command::new("tail")
.args(["-f", paths.get_log_file().to_string_lossy().as_ref()].iter())
.stdin(Stdio::null())
.spawn()?
.wait()?;
}
}
Ok(())
}
/// Connect to the daemon and send the given request.
/// Returns the response from the daemon, or None if the daemon did not provide any useful response. An Ok(None) response does _not_ indicate failure.
pub fn do_server_call(stream: &mut UnixStream, action: &opts::ActionWithServer) -> Result<Option<DaemonResponse>> {
log::debug!("Forwarding options to server");
stream.set_nonblocking(false).context("Failed to set stream to non-blocking")?;
let message_bytes = bincode::serialize(&action)?;
stream.write(&(message_bytes.len() as u32).to_be_bytes()).context("Failed to send command size header to IPC stream")?;
stream.write_all(&message_bytes).context("Failed to write command to IPC stream")?;
let mut buf = Vec::new();
stream.set_read_timeout(Some(std::time::Duration::from_millis(100))).context("Failed to set read timeout")?;
stream.read_to_end(&mut buf).context("Error reading response from server")?;
Ok(if buf.is_empty() {
None
} else {
let buf = bincode::deserialize(&buf)?;
Some(buf)
})
}
================================================
FILE: crates/eww/src/config/eww_config.rs
================================================
use anyhow::{bail, Context, Result};
use eww_shared_util::VarName;
use std::collections::HashMap;
use yuck::{
config::{
script_var_definition::ScriptVarDefinition, validate::ValidationError, widget_definition::WidgetDefinition,
window_definition::WindowDefinition, Config,
},
error::DiagError,
format_diagnostic::ToDiagnostic,
};
use simplexpr::dynval::DynVal;
use crate::{config::inbuilt, error_handling_ctx, file_database::FileDatabase, paths::EwwPaths, widgets::widget_definitions};
use super::script_var;
/// Load an [`EwwConfig`] from the config dir of the given [`crate::EwwPaths`],
/// resetting and applying the global YuckFiles object in [`crate::error_handling_ctx`].
pub fn read_from_eww_paths(eww_paths: &EwwPaths) -> Result<EwwConfig> {
error_handling_ctx::clear_files();
EwwConfig::read_from_dir(&mut error_handling_ctx::FILE_DATABASE.write().unwrap(), eww_paths)
}
/// Eww configuration structure.
#[derive(Debug, Clone, Default)]
pub struct EwwConfig {
widgets: HashMap<String, WidgetDefinition>,
windows: HashMap<String, WindowDefinition>,
initial_variables: HashMap<VarName, DynVal>,
script_vars: HashMap<VarName, ScriptVarDefinition>,
// map of variables to all pollvars which refer to them in their run-while-expression
run_while_mentions: HashMap<VarName, Vec<VarName>>,
}
impl EwwConfig {
/// Load an [`EwwConfig`] from the config dir of the given [`crate::EwwPaths`], reading the main config file.
pub fn read_from_dir(files: &mut FileDatabase, eww_paths: &EwwPaths) -> Result<Self> {
let yuck_path = eww_paths.get_yuck_path();
if !yuck_path.exists() {
bail!("The configuration file `{}` does not exist", yuck_path.display());
}
let config = Config::generate_from_main_file(files, yuck_path)?;
// run some validations on the configuration
let magic_globals: Vec<_> =
inbuilt::INBUILT_VAR_NAMES.iter().chain(inbuilt::MAGIC_CONSTANT_NAMES).map(|x| VarName::from(*x)).collect();
yuck::config::validate::validate(&config, magic_globals)?;
for (name, def) in &config.widget_definitions {
if widget_definitions::BUILTIN_WIDGET_NAMES.contains(&name.as_str()) {
return Err(
DiagError(ValidationError::AccidentalBuiltinOverride(def.span, name.to_string()).to_diagnostic()).into()
);
}
}
let Config { widget_definitions, window_definitions, mut var_definitions, mut script_vars } = config;
script_vars.extend(inbuilt::get_inbuilt_vars());
var_definitions.extend(inbuilt::get_magic_constants(eww_paths));
let mut run_while_mentions = HashMap::<VarName, Vec<VarName>>::new();
for var in script_vars.values() {
if let ScriptVarDefinition::Poll(var) = var {
for name in var.run_while_expr.collect_var_refs() {
run_while_mentions.entry(name.clone()).or_default().push(var.name.clone())
}
}
}
Ok(EwwConfig {
windows: window_definitions,
widgets: widget_definitions,
initial_variables: var_definitions.into_iter().map(|(k, v)| (k, v.initial_value)).collect(),
script_vars,
run_while_mentions,
})
}
// TODO this is kinda ugly
pub fn generate_initial_state(&self) -> Result<HashMap<VarName, DynVal>> {
let mut vars = self
.script_vars
.iter()
.map(|(name, var)| Ok((name.clone(), script_var::initial_value(var)?)))
.collect::<Result<HashMap<_, _>>>()?;
vars.extend(self.initial_variables.clone());
Ok(vars)
}
pub fn get_windows(&self) -> &HashMap<String, WindowDefinition> {
&self.windows
}
pub fn get_window(&self, name: &str) -> Result<&WindowDefinition> {
self.windows.get(name).with_context(|| {
format!(
"No window named '{}' exists in config.\nThis may also be caused by your config failing to load properly, \
please check for any other errors in that case.",
name
)
})
}
pub fn get_script_var(&self, name: &VarName) -> Result<&ScriptVarDefinition> {
self.script_vars.get(name).with_context(|| format!("No script var named '{}' exists", name))
}
pub fn get_widget_definitions(&self) -> &HashMap<String, WidgetDefinition> {
&self.widgets
}
/// Given a variable name, get the names of all variables that reference that variable in their run-while (active/inactive) state
pub fn get_run_while_mentions_of(&self, name: &VarName) -> Option<&Vec<VarName>> {
self.run_while_mentions.get(name)
}
}
================================================
FILE: crates/eww/src/config/inbuilt.rs
================================================
use std::collections::HashMap;
use simplexpr::{dynval::DynVal, SimplExpr};
use yuck::config::{
script_var_definition::{PollScriptVar, ScriptVarDefinition, VarSource},
var_definition::VarDefinition,
};
use crate::{config::system_stats::*, paths::EwwPaths};
use eww_shared_util::VarName;
macro_rules! define_builtin_vars {
($($name:literal [$interval:literal] => $fun:expr),*$(,)?) => {
pub static INBUILT_VAR_NAMES: &[&'static str] = &[$($name),*];
pub fn get_inbuilt_vars() -> HashMap<VarName, ScriptVarDefinition> {
maplit::hashmap! {
$(
VarName::from($name) => ScriptVarDefinition::Poll(PollScriptVar {
name: VarName::from($name),
run_while_expr: SimplExpr::Literal(DynVal::from(true)),
command: VarSource::Function($fun),
initial_value: None,
interval: std::time::Duration::from_secs($interval),
name_span: eww_shared_util::span::Span::DUMMY,
})
),*
}
}
}
}
define_builtin_vars! {
// @desc EWW_TEMPS - Heat of the components in degree Celsius
// @prop { <name>: temperature }
"EWW_TEMPS" [2] => || Ok(DynVal::from(get_temperatures())),
// @desc EWW_RAM - Information on ram and swap usage in bytes.
// @prop { total_mem, free_mem, total_swap, free_swap, available_mem, used_mem, used_mem_perc }
"EWW_RAM" [2] => || Ok(DynVal::from(get_ram())),
// @desc EWW_DISK - Information on on all mounted partitions (Might report inaccurately on some filesystems, like btrfs and zfs) Example: `{EWW_DISK["/"]}`
// @prop { <mount_point>: { name, total, free, used, used_perc } }
"EWW_DISK" [2] => || Ok(DynVal::from(get_disks())),
// @desc EWW_BATTERY - Battery capacity in percent of the main battery
// @prop { <name>: { capacity, status } }
"EWW_BATTERY" [2] => || Ok(DynVal::from(
match get_battery_capacity() {
Err(e) => {
log::error!("Couldn't get the battery capacity: {:?}", e);
"Error: Check `eww log` for more details".to_string()
}
Ok(o) => o,
}
)),
// @desc EWW_CPU - Information on the CPU cores: frequency and usage (No MacOS support)
// @prop { cores: [{ core, freq, usage }], avg }
"EWW_CPU" [2] => || Ok(DynVal::from(get_cpus())) ,
// @desc EWW_NET - Bytes up/down on all interfaces
// @prop { <name>: { up, down } }
"EWW_NET" [2] => || Ok(DynVal::from(net())) ,
// @desc EWW_TIME - the current UNIX timestamp
"EWW_TIME" [1] => || Ok(DynVal::from(get_time())) ,
}
macro_rules! define_magic_constants {
($eww_paths:ident, $($name:literal => $value:expr),*$(,)?) => {
pub static MAGIC_CONSTANT_NAMES: &[&'static str] = &[$($name),*];
pub fn get_magic_constants($eww_paths: &EwwPaths) -> HashMap<VarName, VarDefinition> {
maplit::hashmap! {
$(VarName::from($name) => VarDefinition {
name: VarName::from($name),
initial_value: $value,
span: eww_shared_util::span::Span::DUMMY
}),*
}
}
}
}
define_magic_constants! { eww_paths,
// @desc EWW_CONFIG_DIR - Path to the eww configuration of the current process
"EWW_CONFIG_DIR" => DynVal::from_string(eww_paths.get_config_dir().to_string_lossy().into_owned()),
// @desc EWW_CMD - eww command running in the current configuration, useful in event handlers. I.e.: `:onclick "${EWW_CMD} update foo=bar"`
"EWW_CMD" => DynVal::from_string(
format!("\"{}\" --config \"{}\"",
std::env::current_exe().map(|x| x.to_string_lossy().into_owned()).unwrap_or_else(|_| "eww".to_string()),
eww_paths.get_config_dir().to_string_lossy().into_owned()
)
),
// @desc EWW_EXECUTABLE - Full path of the eww executable
"EWW_EXECUTABLE" => DynVal::from_string(
std::env::current_exe().map(|x| x.to_string_lossy().into_owned()).unwrap_or_else(|_| "eww".to_string()),
),
}
================================================
FILE: crates/eww/src/config/mod.rs
================================================
pub mod eww_config;
pub mod inbuilt;
pub mod script_var;
pub mod scss;
pub mod system_stats;
pub mod window_definition;
pub use eww_config::*;
pub use script_var::*;
================================================
FILE: crates/eww/src/config/script_var.rs
================================================
use std::process::Command;
use anyhow::{anyhow, bail, Context, Result};
use codespan_reporting::diagnostic::Severity;
use eww_shared_util::{Span, VarName};
use simplexpr::dynval::DynVal;
use yuck::{
config::script_var_definition::{ScriptVarDefinition, VarSource},
error::DiagError,
gen_diagnostic,
};
pub fn create_script_var_failed_warn(span: Span, var_name: &VarName, error_output: &str) -> DiagError {
DiagError(gen_diagnostic! {
kind = Severity::Warning,
msg = format!("The script for the `{}`-variable exited unsuccessfully", var_name),
label = span => "Defined here",
note = error_output,
})
}
pub fn initial_value(var: &ScriptVarDefinition) -> Result<DynVal> {
match var {
ScriptVarDefinition::Poll(x) => match &x.initial_value {
Some(value) => Ok(value.clone()),
None => match &x.command {
VarSource::Function(f) => f()
.map_err(|err| anyhow!(err))
.with_context(|| format!("Failed to compute initial value for {}", &var.name())),
VarSource::Shell(span, command) => {
run_command(command).map_err(|e| anyhow!(create_script_var_failed_warn(*span, var.name(), &e.to_string())))
}
},
},
ScriptVarDefinition::Listen(var) => Ok(var.initial_value.clone()),
}
}
/// Run a command and get the output
pub fn run_command(cmd: &str) -> Result<DynVal> {
log::debug!("Running command: {}", cmd);
let command = Command::new("/bin/sh").arg("-c").arg(cmd).output()?;
if !command.status.success() {
bail!("Failed with output:\n{}", String::from_utf8(command.stderr)?);
}
let output = String::from_utf8(command.stdout)?;
let output = output.trim_matches('\n');
Ok(DynVal::from(output))
}
================================================
FILE: crates/eww/src/config/scss.rs
================================================
use std::path::Path;
use anyhow::{anyhow, Context};
use crate::{error_handling_ctx, util::replace_env_var_references};
/// read an (s)css file, replace all environment variable references within it and
/// then parse it into css.
/// Also adds the CSS to the [`crate::file_database::FileDatabase`]
pub fn parse_scss_from_config(path: &Path) -> anyhow::Result<(usize, String)> {
let css_file = path.join("eww.css");
let scss_file = path.join("eww.scss");
if css_file.exists() && scss_file.exists() {
return Err(anyhow!("Encountered both an SCSS and CSS file. Only one of these may exist at a time"));
}
let (s_css_path, css) = if css_file.exists() {
let css_file_content = std::fs::read_to_string(&css_file)
.with_context(|| format!("Given CSS file doesn't exist: {}", css_file.display()))?;
let css = replace_env_var_references(css_file_content);
(css_file, css)
} else {
let scss_file_content =
std::fs::read_to_string(&scss_file).with_context(|| format!("Given SCSS file doesn't exist! {}", path.display()))?;
let file_content = replace_env_var_references(scss_file_content);
let grass_config = grass::Options::default().load_path(path);
let css = grass::from_string(file_content, &grass_config).map_err(|err| anyhow!("SCSS parsing error: {}", err))?;
(scss_file, css)
};
let mut file_db = error_handling_ctx::FILE_DATABASE.write().unwrap();
let file_id = file_db.insert_string(s_css_path.display().to_string(), css.clone())?;
Ok((file_id, css))
}
================================================
FILE: crates/eww/src/config/system_stats.rs
================================================
use crate::util::IterAverage;
use anyhow::{Context, Result};
use once_cell::sync::Lazy;
use std::{fs::read_to_string, sync::Mutex};
use sysinfo::System;
struct RefreshTime(std::time::Instant);
impl RefreshTime {
pub fn new() -> Self {
Self(std::time::Instant::now())
}
pub fn next_refresh(&mut self) -> std::time::Duration {
let now = std::time::Instant::now();
let duration = now.duration_since(self.0);
self.0 = now;
duration
}
}
static SYSTEM: Lazy<Mutex<System>> = Lazy::new(|| Mutex::new(System::new()));
static DISKS: Lazy<Mutex<sysinfo::Disks>> = Lazy::new(|| Mutex::new(sysinfo::Disks::new_with_refreshed_list()));
static COMPONENTS: Lazy<Mutex<sysinfo::Components>> = Lazy::new(|| Mutex::new(sysinfo::Components::new_with_refreshed_list()));
static NETWORKS: Lazy<Mutex<(RefreshTime, sysinfo::Networks)>> =
Lazy::new(|| Mutex::new((RefreshTime::new(), sysinfo::Networks::new_with_refreshed_list())));
pub fn get_disks() -> String {
let mut disks = DISKS.lock().unwrap();
disks.refresh_list();
disks.refresh();
disks
.iter()
.map(|c| {
let total_space = c.total_space();
let available_space = c.available_space();
let used_space = total_space - available_space;
(
c.mount_point().display().to_string(),
serde_json::json!({
"name": c.name(),
"total": total_space,
"free": available_space,
"used": used_space,
"used_perc": (used_space as f32 / total_space as f32) * 100f32
}),
)
})
.collect::<serde_json::Value>()
.to_string()
}
pub fn get_ram() -> String {
let mut system = SYSTEM.lock().unwrap();
system.refresh_memory();
let total_memory = system.total_memory();
let available_memory = system.available_memory();
let used_memory = total_memory as f32 - available_memory as f32;
serde_json::json!({
"total_mem": total_memory,
"free_mem": system.free_memory(),
"total_swap": system.total_swap(),
"free_swap": system.free_swap(),
"available_mem": available_memory,
"used_mem": used_memory,
"used_mem_perc": (used_memory / total_memory as f32) * 100f32,
})
.to_string()
}
pub fn get_temperatures() -> String {
let mut components = COMPONENTS.lock().unwrap();
components.refresh_list();
components.refresh();
components
.iter()
.map(|c| {
(
c.label().to_uppercase().replace(' ', "_"),
// It is common for temperatures to report a non-numeric value.
// Tolerate it by serializing it as the string "null"
c.temperature().to_string().replace("NaN", "\"null\""),
)
})
.collect::<serde_json::Value>()
.to_string()
}
pub fn get_cpus() -> String {
let mut system = SYSTEM.lock().unwrap();
system.refresh_cpu_specifics(sysinfo::CpuRefreshKind::everything());
let cpus = system.cpus();
serde_json::json!({
"cores": cpus.iter()
.map(|a| {
serde_json::json!({
"core": a.name(),
"freq": a.frequency(),
"usage": a.cpu_usage() as i64
})
}).collect::<Vec<_>>(),
"avg": cpus.iter().map(|a| a.cpu_usage()).avg()
})
.to_string()
}
#[cfg(target_os = "macos")]
pub fn get_battery_capacity() -> Result<String> {
let capacity = String::from_utf8(
std::process::Command::new("pmset")
.args(&["-g", "batt"])
.output()
.context("\nError while getting the battery value on macos, with `pmset`: ")?
.stdout,
)?;
// Example output of that command:
// Now drawing from 'Battery Power'
//-InternalBattery-0 (id=11403363) 100%; discharging; (no estimate) present: true
let regex = regex!(r"[0-9]*%");
let mut number = regex.captures(&capacity).unwrap().get(0).unwrap().as_str().to_string();
// Removes the % at the end
number.pop();
Ok(format!(
"{{ \"BAT0\": {{ \"capacity\": \"{}\", \"status\": \"{}\" }}}}",
number,
capacity.split(";").collect::<Vec<&str>>()[1]
))
}
#[cfg(target_os = "linux")]
pub fn get_battery_capacity() -> Result<String> {
use std::{collections::HashMap, sync::atomic::AtomicBool};
#[derive(serde::Serialize)]
struct BatteryData {
capacity: i64,
status: String,
}
#[derive(serde::Serialize)]
struct Data {
#[serde(flatten)]
batteries: HashMap<String, BatteryData>,
total_avg: f64,
}
let mut current = 0_f64;
let mut total = 0_f64;
let mut batteries = HashMap::new();
let power_supply_dir = std::path::Path::new("/sys/class/power_supply");
let power_supply_entries = power_supply_dir.read_dir().context("Couldn't read /sys/class/power_supply directory")?;
for entry in power_supply_entries {
let entry = entry?.path();
if !entry.is_dir() {
continue;
}
if let (Ok(capacity), Ok(status)) = (read_to_string(entry.join("capacity")), read_to_string(entry.join("status"))) {
batteries.insert(
entry.file_name().context("Couldn't get filename")?.to_string_lossy().to_string(),
BatteryData {
status: status.trim_end_matches('\n').to_string(),
capacity: capacity.trim_end_matches('\n').parse::<f64>()?.round() as i64,
},
);
if let (Ok(charge_full), Ok(charge_now), Ok(voltage_now)) = (
read_to_string(entry.join("charge_full")),
read_to_string(entry.join("charge_now")),
read_to_string(entry.join("voltage_now")),
) {
// (uAh / 1000000) * U = p and that / one million so that we have microwatt
current += ((charge_now.trim_end_matches('\n').parse::<f64>()? / 1000000_f64)
* voltage_now.trim_end_matches('\n').parse::<f64>()?)
/ 1000000_f64;
total += ((charge_full.trim_end_matches('\n').parse::<f64>()? / 1000000_f64)
* voltage_now.trim_end_matches('\n').parse::<f64>()?)
/ 1000000_f64;
} else if let (Ok(energy_full), Ok(energy_now)) =
(read_to_string(entry.join("energy_full")), read_to_string(entry.join("energy_now")))
{
current += energy_now.trim_end_matches('\n').parse::<f64>()?;
total += energy_full.trim_end_matches('\n').parse::<f64>()?;
} else {
static WARNED: AtomicBool = AtomicBool::new(false);
if !WARNED.load(std::sync::atomic::Ordering::Relaxed) {
WARNED.store(true, std::sync::atomic::Ordering::Relaxed);
log::warn!(
"Failed to get/calculate uWh: the total_avg value of the battery magic var will probably be a garbage \
value that can not be trusted."
);
}
}
}
}
if total == 0_f64 {
return Ok(String::from(""));
}
Ok(serde_json::to_string(&(Data { batteries, total_avg: (current / total) * 100_f64 })).unwrap())
}
#[cfg(any(target_os = "netbsd", target_os = "freebsd", target_os = "openbsd"))]
pub fn get_battery_capacity() -> Result<String> {
let batteries = String::from_utf8(
// I have only tested `apm` on FreeBSD, but it *should* work on all of the listed targets,
// based on what I can tell from their online man pages.
std::process::Command::new("apm")
.output()
.context("\nError while getting the battery values on bsd, with `apm`: ")?
.stdout,
)?;
// `apm` output should look something like this:
// $ apm
// ...
// Remaining battery life: 87%
// Remaining battery time: unknown
// Number of batteries: 1
// Battery 0
// Battery Status: charging
// Remaining battery life: 87%
// Remaining battery time: unknown
// ...
// last 4 lines are repeated for each battery.
// see also:
// https://www.freebsd.org/cgi/man.cgi?query=apm&manpath=FreeBSD+13.1-RELEASE+and+Ports
// https://man.openbsd.org/amd64/apm.8
// https://man.netbsd.org/apm.8
let mut json = String::from('{');
let re_total = regex!(r"(?m)^Remaining battery life: (\d+)%");
let re_single = regex!(r"(?sm)^Battery (\d+):.*?Status: (\w+).*?(\d+)%");
for bat in re_single.captures_iter(&batteries) {
json.push_str(&format!(
r#""BAT{}": {{ "status": "{}", "capacity": {} }}, "#,
bat.get(1).unwrap().as_str(),
bat.get(2).unwrap().as_str(),
bat.get(3).unwrap().as_str(),
))
}
json.push_str(&format!(r#""total_avg": {}}}"#, re_total.captures(&batteries).unwrap().get(1).unwrap().as_str()));
Ok(json)
}
#[cfg(not(target_os = "macos"))]
#[cfg(not(target_os = "linux"))]
#[cfg(not(target_os = "netbsd"))]
#[cfg(not(target_os = "freebsd"))]
#[cfg(not(target_os = "openbsd"))]
pub fn get_battery_capacity() -> Result<String> {
Err(anyhow::anyhow!("Eww doesn't support your OS for getting the battery capacity"))
}
pub fn net() -> String {
let (ref mut last_refresh, ref mut networks) = &mut *NETWORKS.lock().unwrap();
networks.refresh_list();
let elapsed = last_refresh.next_refresh();
networks
.iter()
.map(|(name, data)| {
let transmitted = data.transmitted() as f64 / elapsed.as_secs_f64();
let received = data.received() as f64 / elapsed.as_secs_f64();
(name, serde_json::json!({ "NET_UP": transmitted, "NET_DOWN": received }))
})
.collect::<serde_json::Value>()
.to_string()
}
pub fn get_time() -> String {
chrono::offset::Utc::now().timestamp().to_string()
}
================================================
FILE: crates/eww/src/config/window_definition.rs
================================================
================================================
FILE: crates/eww/src/daemon_response.rs
================================================
//! Types to manage messages that notify the eww client over the result of a command
//!
//! Communcation between the daemon and eww client happens via IPC.
//! If the daemon needs to send messages back to the client as a response to a command (mostly for CLI output),
//! this happens via the DaemonResponse types
use anyhow::{Context, Result};
use itertools::Itertools;
use tokio::sync::mpsc;
use crate::error_handling_ctx;
/// Response that the app may send as a response to a event.
/// This is used in `DaemonCommand`s that contain a response sender.
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, derive_more::Display)]
pub enum DaemonResponse {
Success(String),
Failure(String),
}
#[derive(Debug)]
pub struct DaemonResponseSender(mpsc::UnboundedSender<DaemonResponse>);
pub fn create_pair() -> (DaemonResponseSender, mpsc::UnboundedReceiver<DaemonResponse>) {
let (sender, recv) = mpsc::unbounded_channel();
(DaemonResponseSender(sender), recv)
}
impl DaemonResponseSender {
pub fn send_success(&self, s: String) -> Result<()> {
self.0.send(DaemonResponse::Success(s)).context("Failed to send success response from application thread")
}
pub fn send_failure(&self, s: String) -> Result<()> {
self.0.send(DaemonResponse::Failure(s)).context("Failed to send failure response from application thread")
}
/// Given a list of errors, respond with an error value if there are any errors, and respond with success otherwise.
pub fn respond_with_error_list(&self, errors: impl IntoIterator<Item = anyhow::Error>) -> Result<()> {
let errors = errors.into_iter().map(|e| error_handling_ctx::format_error(&e)).join("\n");
if errors.is_empty() {
self.send_success(String::new())
} else {
self.respond_with_error_msg(errors)
}
}
/// In case of an Err, send the error message to a sender.
pub fn respond_with_result<T>(&self, result: Result<T>) -> Result<()> {
match result {
Ok(_) => self.send_success(String::new()),
Err(e) => {
let formatted = error_handling_ctx::format_error(&e);
self.respond_with_error_msg(formatted)
}
}
.context("sending response from main thread")
}
fn respond_with_error_msg(&self, msg: String) -> Result<()> {
println!("Action failed with error: {}", msg);
self.send_failure(msg)
}
}
pub type DaemonResponseReceiver = mpsc::UnboundedReceiver<DaemonResponse>;
================================================
FILE: crates/eww/src/display_backend.rs
================================================
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
use gtk::gdk;
#[cfg(feature = "wayland")]
pub use platform_wayland::WaylandBackend;
#[cfg(feature = "x11")]
pub use platform_x11::{set_xprops, X11Backend};
pub trait DisplayBackend: Send + Sync + 'static {
const IS_X11: bool;
const IS_WAYLAND: bool;
fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window>;
}
pub struct NoBackend;
impl DisplayBackend for NoBackend {
const IS_X11: bool = false;
const IS_WAYLAND: bool = false;
fn initialize_window(_window_init: &WindowInitiator, _monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {
Some(Window::new(gtk::WindowType::Toplevel, x, y))
}
}
#[cfg(feature = "wayland")]
mod platform_wayland {
use super::DisplayBackend;
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
use gtk::gdk;
use gtk::prelude::*;
use gtk_layer_shell::{KeyboardMode, LayerShell};
use yuck::config::backend_window_options::WlWindowFocusable;
use yuck::config::{window_definition::WindowStacking, window_geometry::AnchorAlignment};
pub struct WaylandBackend;
impl DisplayBackend for WaylandBackend {
const IS_X11: bool = false;
const IS_WAYLAND: bool = true;
fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {
let window = Window::new(gtk::WindowType::Toplevel, x, y);
// Initialising a layer shell surface
window.init_layer_shell();
// Sets the monitor where the surface is shown
if let Some(ident) = window_init.monitor.clone() {
let display = gdk::Display::default().expect("could not get default display");
if let Some(monitor) = crate::app::get_monitor_from_display(&display, &ident) {
window.set_monitor(&monitor);
} else {
return None;
}
};
window.set_resizable(window_init.resizable);
// Sets the layer where the layer shell surface will spawn
match window_init.stacking {
WindowStacking::Foreground => window.set_layer(gtk_layer_shell::Layer::Top),
WindowStacking::Background => window.set_layer(gtk_layer_shell::Layer::Background),
WindowStacking::Bottom => window.set_layer(gtk_layer_shell::Layer::Bottom),
WindowStacking::Overlay => window.set_layer(gtk_layer_shell::Layer::Overlay),
}
if let Some(namespace) = &window_init.backend_options.wayland.namespace {
window.set_namespace(namespace);
}
// Sets the keyboard interactivity
match window_init.backend_options.wayland.focusable {
WlWindowFocusable::None => window.set_keyboard_mode(KeyboardMode::None),
WlWindowFocusable::Exclusive => window.set_keyboard_mode(KeyboardMode::Exclusive),
WlWindowFocusable::OnDemand => window.set_keyboard_mode(KeyboardMode::OnDemand),
}
if let Some(geometry) = window_init.geometry {
// Positioning surface
let mut top = false;
let mut left = false;
let mut right = false;
let mut bottom = false;
match geometry.anchor_point.x {
AnchorAlignment::START => left = true,
AnchorAlignment::CENTER => {}
AnchorAlignment::END => right = true,
}
match geometry.anchor_point.y {
AnchorAlignment::START => top = true,
AnchorAlignment::CENTER => {}
AnchorAlignment::END => bottom = true,
}
window.set_anchor(gtk_layer_shell::Edge::Left, left);
window.set_anchor(gtk_layer_shell::Edge::Right, right);
window.set_anchor(gtk_layer_shell::Edge::Top, top);
window.set_anchor(gtk_layer_shell::Edge::Bottom, bottom);
let xoffset = geometry.offset.x.pixels_relative_to(monitor.width());
let yoffset = geometry.offset.y.pixels_relative_to(monitor.height());
if left {
window.set_layer_shell_margin(gtk_layer_shell::Edge::Left, xoffset);
} else {
window.set_layer_shell_margin(gtk_layer_shell::Edge::Right, xoffset);
}
if bottom {
window.set_layer_shell_margin(gtk_layer_shell::Edge::Bottom, yoffset);
} else {
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, yoffset);
}
// https://github.com/elkowar/eww/issues/296
if window_init.backend_options.wayland.exclusive
&& geometry.anchor_point.x != AnchorAlignment::CENTER
&& geometry.anchor_point.y != AnchorAlignment::CENTER
{
log::warn!("When ':exclusive true' the anchor has to include 'center', otherwise exlcusive won't work")
}
}
if window_init.backend_options.wayland.exclusive {
window.auto_exclusive_zone_enable();
}
Some(window)
}
}
}
#[cfg(feature = "x11")]
mod platform_x11 {
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
use anyhow::{Context, Result};
use gdk::Monitor;
use gtk::gdk;
use gtk::{self, prelude::*};
use x11rb::protocol::xproto::ConnectionExt;
use x11rb::{
self,
connection::Connection,
protocol::xproto::*,
rust_connection::{DefaultStream, RustConnection},
};
use yuck::config::{
backend_window_options::{Side, X11WindowType},
window_definition::WindowStacking,
};
use super::DisplayBackend;
pub struct X11Backend;
impl DisplayBackend for X11Backend {
const IS_X11: bool = true;
const IS_WAYLAND: bool = false;
fn initialize_window(window_init: &WindowInitiator, _monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {
let window_type =
if window_init.backend_options.x11.wm_ignore { gtk::WindowType::Popup } else { gtk::WindowType::Toplevel };
let window = Window::new(window_type, x, y);
window.set_resizable(window_init.resizable);
window.set_keep_above(window_init.stacking == WindowStacking::Foreground);
window.set_keep_below(window_init.stacking == WindowStacking::Background);
if window_init.backend_options.x11.sticky {
window.stick();
} else {
window.unstick();
}
Some(window)
}
}
pub fn set_xprops(window: &Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
let backend = X11BackendConnection::new()?;
backend.set_xprops_for(window, monitor, window_init)?;
Ok(())
}
struct X11BackendConnection {
conn: RustConnection<DefaultStream>,
root_window: u32,
atoms: AtomCollection,
}
impl X11BackendConnection {
fn new() -> Result<Self> {
let (conn, screen_num) = RustConnection::connect(None)?;
let screen = conn.setup().roots[screen_num].clone();
let atoms = AtomCollection::new(&conn)?.reply()?;
Ok(X11BackendConnection { conn, root_window: screen.root, atoms })
}
fn set_xprops_for(&self, window: &Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
let monitor_rect = monitor.geometry();
let scale_factor = monitor.scale_factor() as u32;
let gdk_window = window.window().context("Couldn't get gdk window from gtk window")?;
let win_id =
gdk_window.downcast_ref::<gdkx11::X11Window>().context("Failed to get x11 window for gtk window")?.xid() as u32;
let strut_def = window_init.backend_options.x11.struts;
let root_window_geometry = self.conn.get_geometry(self.root_window)?.reply()?;
let mon_x = scale_factor * monitor_rect.x() as u32;
let mon_y = scale_factor * monitor_rect.y() as u32;
let mon_end_x = scale_factor * (monitor_rect.x() + monitor_rect.width()) as u32 - 1u32;
let mon_end_y = scale_factor * (monitor_rect.y() + monitor_rect.height()) as u32 - 1u32;
let dist = match strut_def.side {
Side::Left | Side::Right => strut_def.distance.pixels_relative_to(monitor_rect.width()) as u32,
Side::Top | Side::Bottom => strut_def.distance.pixels_relative_to(monitor_rect.height()) as u32,
};
// don't question it,.....
// it's how the X gods want it to be.
// left, right, top, bottom, left_start_y, left_end_y, right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x, bottom_end_x
#[rustfmt::skip]
let strut_list: Vec<u8> = match strut_def.side {
Side::Left => vec![dist + mon_x, 0, 0, 0, mon_x, mon_end_y, 0, 0, 0, 0, 0, 0],
Side::Right => vec![0, root_window_geometry.width as u32 - mon_end_x + dist, 0, 0, 0, 0, mon_x, mon_end_y, 0, 0, 0, 0],
Side::Top => vec![0, 0, dist + mon_y, 0, 0, 0, 0, 0, mon_x, mon_end_x, 0, 0],
Side::Bottom => vec![0, 0, 0, root_window_geometry.height as u32 - mon_end_y + dist, 0, 0, 0, 0, 0, 0, mon_x, mon_end_x],
// This should never happen but if it does the window will be anchored on the
// right of the screen
}.iter().flat_map(|x| x.to_le_bytes().to_vec()).collect();
self.conn
.change_property(
PropMode::REPLACE,
win_id,
self.atoms._NET_WM_STRUT,
self.atoms.CARDINAL,
32,
4,
&strut_list[0..16],
)?
.check()?;
self.conn
.change_property(
PropMode::REPLACE,
win_id,
self.atoms._NET_WM_STRUT_PARTIAL,
self.atoms.CARDINAL,
32,
12,
&strut_list,
)?
.check()?;
// TODO possibly support setting multiple window types
x11rb::wrapper::ConnectionExt::change_property32(
&self.conn,
PropMode::REPLACE,
win_id,
self.atoms._NET_WM_WINDOW_TYPE,
self.atoms.ATOM,
&[match window_init.backend_options.x11.window_type {
X11WindowType::Dock => self.atoms._NET_WM_WINDOW_TYPE_DOCK,
X11WindowType::Normal => self.atoms._NET_WM_WINDOW_TYPE_NORMAL,
X11WindowType::Dialog => self.atoms._NET_WM_WINDOW_TYPE_DIALOG,
X11WindowType::Toolbar => self.atoms._NET_WM_WINDOW_TYPE_TOOLBAR,
X11WindowType::Utility => self.atoms._NET_WM_WINDOW_TYPE_UTILITY,
X11WindowType::Desktop => self.atoms._NET_WM_WINDOW_TYPE_DESKTOP,
X11WindowType::Notification => self.atoms._NET_WM_WINDOW_TYPE_NOTIFICATION,
}],
)?
.check()?;
self.conn.flush().context("Failed to send requests to X server")
}
}
x11rb::atom_manager! {
pub AtomCollection: AtomCollectionCookie {
_NET_WM_WINDOW_TYPE,
_NET_WM_WINDOW_TYPE_NORMAL,
_NET_WM_WINDOW_TYPE_DOCK,
_NET_WM_WINDOW_TYPE_DIALOG,
_NET_WM_WINDOW_TYPE_TOOLBAR,
_NET_WM_WINDOW_TYPE_UTILITY,
_NET_WM_WINDOW_TYPE_DESKTOP,
_NET_WM_WINDOW_TYPE_NOTIFICATION,
_NET_WM_STATE,
_NET_WM_STATE_STICKY,
_NET_WM_STATE_ABOVE,
_NET_WM_STATE_BELOW,
_NET_WM_NAME,
_NET_WM_STRUT,
_NET_WM_STRUT_PARTIAL,
WM_NAME,
UTF8_STRING,
COMPOUND_TEXT,
CARDINAL,
ATOM,
WM_CLASS,
STRING,
}
}
}
================================================
FILE: crates/eww/src/error_handling_ctx.rs
================================================
//! Disgusting global state.
//! I hate this, but [buffet](https://github.com/buffet) told me that this is what I should do for peak maintainability!
use std::sync::{Arc, RwLock};
use codespan_reporting::{
diagnostic::Diagnostic,
term::{self, Chars},
};
use eww_shared_util::Span;
use once_cell::sync::Lazy;
use simplexpr::{dynval::ConversionError, eval::EvalError};
use yuck::{config::validate::ValidationError, error::DiagError, format_diagnostic::ToDiagnostic};
use crate::file_database::FileDatabase;
pub static FILE_DATABASE: Lazy<Arc<RwLock<FileDatabase>>> = Lazy::new(|| Arc::new(RwLock::new(FileDatabase::new())));
pub fn clear_files() {
*FILE_DATABASE.write().unwrap() = FileDatabase::new();
}
pub fn print_error(err: anyhow::Error) {
match anyhow_err_to_diagnostic(&err) {
Some(diag) => match stringify_diagnostic(diag) {
Ok(diag) => eprintln!("{}", diag),
Err(_) => log::error!("{:?}", err),
},
None => log::error!("{:?}", err),
}
}
pub fn format_error(err: &anyhow::Error) -> String {
anyhow_err_to_diagnostic(err).and_then(|diag| stringify_diagnostic(diag).ok()).unwrap_or_else(|| format!("{:?}", err))
}
pub fn anyhow_err_to_diagnostic(err: &anyhow::Error) -> Option<Diagnostic<usize>> {
#[allow(clippy::manual_map)]
if let Some(err) = err.downcast_ref::<DiagError>() {
Some(err.0.clone())
} else if let Some(err) = err.downcast_ref::<ConversionError>() {
Some(err.to_diagnostic())
} else if let Some(err) = err.downcast_ref::<ValidationError>() {
Some(err.to_diagnostic())
} else if let Some(err) = err.downcast_ref::<EvalError>() {
Some(err.to_diagnostic())
} else {
None
}
}
pub fn stringify_diagnostic(mut diagnostic: codespan_reporting::diagnostic::Diagnostic<usize>) -> anyhow::Result<String> {
diagnostic.labels.retain(|label| !Span(label.range.start, label.range.end, label.file_id).is_dummy());
let mut config = term::Config::default();
let mut chars = Chars::box_drawing();
chars.single_primary_caret = '─';
config.chars = chars;
config.chars.note_bullet = '→';
let mut buf = Vec::new();
let mut writer = term::termcolor::Ansi::new(&mut buf);
let files = FILE_DATABASE.read().unwrap();
term::emit(&mut writer, &config, &*files, &diagnostic)?;
Ok(String::from_utf8(buf)?)
}
================================================
FILE: crates/eww/src/file_database.rs
================================================
use std::collections::HashMap;
use codespan_reporting::files::Files;
use eww_shared_util::Span;
use yuck::{
config::file_provider::{FilesError, YuckFileProvider},
error::DiagError,
parser::ast::Ast,
};
#[derive(Debug, Clone, Default)]
pub struct FileDatabase {
files: HashMap<usize, CodeFile>,
latest_id: usize,
}
impl FileDatabase {
pub fn new() -> Self {
Self::default()
}
fn get_file(&self, id: usize) -> Result<&CodeFile, codespan_reporting::files::Error> {
self.files.get(&id).ok_or(codespan_reporting::files::Error::FileMissing)
}
fn insert_code_file(&mut self, file: CodeFile) -> usize {
let file_id = self.latest_id;
self.files.insert(file_id, file);
self.latest_id += 1;
file_id
}
pub fn insert_string(&mut self, name: String, content: String) -> Result<usize, DiagError> {
let line_starts = codespan_reporting::files::line_starts(&content).collect();
let code_file = CodeFile { name, line_starts, source_len_bytes: content.len(), source: CodeSource::Literal(content) };
let file_id = self.insert_code_file(code_file);
Ok(file_id)
}
}
impl YuckFileProvider for FileDatabase {
fn load_yuck_file(&mut self, path: std::path::PathBuf) -> Result<(Span, Vec<Ast>), FilesError> {
let file_content = std::fs::read_to_string(&path)?;
let line_starts = codespan_reporting::files::line_starts(&file_content).collect();
let code_file = CodeFile {
name: path.display().to_string(),
line_starts,
source_len_bytes: file_content.len(),
source: CodeSource::File(path),
};
let file_id = self.insert_code_file(code_file);
Ok(yuck::parser::parse_toplevel(file_id, file_content)?)
}
fn load_yuck_str(&mut self, name: String, content: String) -> Result<(Span, Vec<Ast>), DiagError> {
let file_id = self.insert_string(name, content.clone())?;
yuck::parser::parse_toplevel(file_id, content)
}
fn unload(&mut self, id: usize) {
self.files.remove(&id);
}
}
impl<'a> Files<'a> for FileDatabase {
type FileId = usize;
type Name = &'a str;
type Source = String;
fn name(&'a self, id: Self::FileId) -> Result<Self::Name, codespan_reporting::files::Error> {
Ok(&self.get_file(id)?.name)
}
fn source(&'a self, id: Self::FileId) -> Result<Self::Source, codespan_reporting::files::Error> {
self.get_file(id)?.source.read_content().map_err(codespan_reporting::files::Error::Io)
}
fn line_index(&self, id: Self::FileId, byte_index: usize) -> Result<usize, codespan_reporting::files::Error> {
Ok(self.get_file(id)?.line_starts.binary_search(&byte_index).unwrap_or_else(|next_line| next_line - 1))
}
fn line_range(
&self,
id: Self::FileId,
line_index: usize,
) -> Result<std::ops::Range<usize>, codespan_reporting::files::Error> {
let file = self.get_file(id)?;
let line_start = file.line_start(line_index)?;
let next_line_start = file.line_start(line_index + 1)?;
Ok(line_start..next_line_start)
}
}
#[derive(Clone, Debug)]
struct CodeFile {
name: String,
line_starts: Vec<usize>,
source: CodeSource,
source_len_bytes: usize,
}
impl CodeFile {
/// Return the starting byte index of the line with the specified line index.
/// Convenience method that already generates errors if necessary.
fn line_start(&self, line_index: usize) -> Result<usize, codespan_reporting::files::Error> {
use std::cmp::Ordering;
match line_index.cmp(&self.line_starts.len()) {
Ordering::Less => Ok(self.line_starts.get(line_index).cloned().expect("failed despite previous check")),
Ordering::Equal => Ok(self.source_len_bytes),
Ordering::Greater => {
Err(codespan_reporting::files::Error::LineTooLarge { given: line_index, max: self.line_starts.len() - 1 })
}
}
}
}
#[derive(Clone, Debug)]
enum CodeSource {
File(std::path::PathBuf),
Literal(String),
}
impl CodeSource {
fn read_content(&self) -> std::io::Result<String> {
match self {
CodeSource::File(path) => Ok(std::fs::read_to_string(path)?),
CodeSource::Literal(x) => Ok(x.to_string()),
}
}
}
================================================
FILE: crates/eww/src/geometry.rs
================================================
use derive_more::{Debug, *};
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display)]
#[display(".x*.y:.width*.height")]
pub struct Rect {
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
}
================================================
FILE: crates/eww/src/ipc_server.rs
================================================
use crate::{app, opts};
use anyhow::{Context, Result};
use std::time::Duration;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
sync::mpsc::*,
};
pub async fn run_server<P: AsRef<std::path::Path>>(evt_send: UnboundedSender<app::DaemonCommand>, socket_path: P) -> Result<()> {
let socket_path = socket_path.as_ref();
let listener = { tokio::net::UnixListener::bind(socket_path)? };
log::info!("IPC server initialized");
crate::loop_select_exiting! {
connection = listener.accept() => match connection {
Ok((stream, _addr)) => {
let evt_send = evt_send.clone();
tokio::spawn(async move {
let result = handle_connection(stream, evt_send.clone()).await;
crate::print_result_err!("while handling IPC connection with client", result);
});
},
Err(e) => eprintln!("Failed to connect to client: {:?}", e),
}
}
Ok(())
}
/// Handle a single IPC connection from start to end.
async fn handle_connection(mut stream: tokio::net::UnixStream, evt_send: UnboundedSender<app::DaemonCommand>) -> Result<()> {
let (mut stream_read, mut stream_write) = stream.split();
let action: opts::ActionWithServer = read_action_from_stream(&mut stream_read).await?;
log::debug!("received command from IPC: {:?}", &action);
let (command, maybe_response_recv) = action.into_daemon_command();
evt_send.send(command)?;
if let Some(mut response_recv) = maybe_response_recv {
log::debug!("Waiting for response for IPC client");
if let Ok(Some(response)) = tokio::time::timeout(Duration::from_millis(100), response_recv.recv()).await {
let response = bincode::serialize(&response)?;
let result = &stream_write.write_all(&response).await;
crate::print_result_err!("sending text response to ipc client", &result);
}
}
stream_write.shutdown().await?;
Ok(())
}
/// Read a single message from a unix stream, and parses it into a `ActionWithServer`
/// The format here requires the first 4 bytes to be the size of the rest of the message (in big-endian), followed by the rest of the message.
async fn read_action_from_stream(stream_read: &'_ mut tokio::net::unix::ReadHalf<'_>) -> Result<opts::ActionWithServer> {
let mut message_byte_length = [0u8; 4];
stream_read.read_exact(&mut message_byte_length).await.context("Failed to read message size header in IPC message")?;
let message_byte_length = u32::from_be_bytes(message_byte_length);
let mut raw_message = Vec::<u8>::with_capacity(message_byte_length as usize);
while raw_message.len() < message_byte_length as usize {
stream_read.read_buf(&mut raw_message).await.context("Failed to read actual IPC message")?;
}
bincode::deserialize(&raw_message).context("Failed to parse client message")
}
================================================
FILE: crates/eww/src/main.rs
================================================
#![allow(rustdoc::private_intra_doc_links)]
extern crate gtk;
#[cfg(feature = "wayland")]
extern crate gtk_layer_shell as gtk_layer_shell;
use anyhow::{Context, Result};
use clap::CommandFactory as _;
use daemon_response::{DaemonResponse, DaemonResponseReceiver};
use display_backend::DisplayBackend;
use opts::ActionWithServer;
use paths::EwwPaths;
use std::{os::unix::net, path::Path, time::Duration};
use crate::server::ForkResult;
mod app;
mod application_lifecycle;
mod client;
mod config;
mod daemon_response;
mod display_backend;
mod error_handling_ctx;
mod file_database;
mod geometry;
mod ipc_server;
mod opts;
mod paths;
mod script_var_handler;
mod server;
mod state;
mod util;
mod widgets;
mod window_arguments;
mod window_initiator;
fn main() {
let eww_binary_name = std::env::args().next().unwrap();
let opts: opts::Opt = opts::Opt::from_env();
let log_level_filter = if opts.log_debug { log::LevelFilter::Debug } else { log::LevelFilter::Info };
if std::env::var("RUST_LOG").is_ok() {
pretty_env_logger::init_timed();
} else {
pretty_env_logger::formatted_timed_builder()
.filter(Some("eww"), log_level_filter)
.filter(Some("notifier_host"), log_level_filter)
.init();
}
if let opts::Action::ShellCompletions { shell } = opts.action {
clap_complete::generate(shell, &mut opts::RawOpt::command(), "eww", &mut std::io::stdout());
return;
}
let detected_wayland = detect_wayland();
#[allow(unused)]
let use_wayland = opts.force_wayland || detected_wayland;
#[cfg(all(feature = "wayland", feature = "x11"))]
let result = if use_wayland {
log::debug!("Running on wayland. force_wayland={}, detected_wayland={}", opts.force_wayland, detected_wayland);
run::<display_backend::WaylandBackend>(opts, eww_binary_name)
} else {
log::debug!("Running on X11. force_wayland={}, detected_wayland={}", opts.force_wayland, detected_wayland);
run::<display_backend::X11Backend>(opts, eww_binary_name)
};
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
let result = {
if use_wayland {
log::warn!("Eww compiled without wayland support. Falling back to X11, eventhough wayland was requested.");
}
run::<display_backend::X11Backend>(opts, eww_binary_name)
};
#[cfg(all(feature = "wayland", not(feature = "x11")))]
let result = run::<display_backend::WaylandBackend>(opts, eww_binary_name);
#[cfg(not(any(feature = "wayland", feature = "x11")))]
let result = run::<display_backend::NoBackend>(opts, eww_binary_name);
if let Err(err) = result {
error_handling_ctx::print_error(err);
std::process::exit(1);
}
}
fn detect_wayland() -> bool {
let session_type = std::env::var("XDG_SESSION_TYPE").unwrap_or_default();
let wayland_display = std::env::var("WAYLAND_DISPLAY").unwrap_or_default();
session_type.contains("wayland") || (!wayland_display.is_empty() && !session_type.contains("x11"))
}
fn run<B: DisplayBackend>(opts: opts::Opt, eww_binary_name: String) -> Result<()> {
let paths = opts
.config_path
.map(EwwPaths::from_config_dir)
.unwrap_or_else(EwwPaths::default)
.context("Failed to initialize eww paths")?;
let should_restart = match &opts.action {
opts::Action::ShellCompletions { .. } => unreachable!(),
opts::Action::Daemon => opts.restart,
opts::Action::WithServer(action) => opts.restart && action.can_start_daemon(),
opts::Action::ClientOnly(_) => false,
};
if should_restart {
let response = handle_server_command(&paths, &ActionWithServer::KillServer, 1);
if let Ok(Some(response)) = response {
handle_daemon_response(response);
}
std::thread::sleep(std::time::Duration::from_millis(200));
}
let would_show_logs = match opts.action {
opts::Action::ShellCompletions { .. } => unreachable!(),
opts::Action::ClientOnly(action) => {
client::handle_client_only_action(&paths, action)?;
false
}
// make sure that there isn't already a Eww daemon running.
opts::Action::Daemon if check_server_running(paths.get_ipc_socket_file()) => {
eprintln!("Eww server already running.");
true
}
opts::Action::Daemon => {
log::info!("Initializing Eww server. ({})", paths.get_ipc_socket_file().display());
let _ = std::fs::remove_file(paths.get_ipc_socket_file());
if !opts.show_logs {
println!("Run `{} logs` to see any errors while editing your configuration.", eww_binary_name);
}
let fork_result = server::initialize_server::<B>(paths.clone(), None, !opts.no_daemonize)?;
opts.no_daemonize || fork_result == ForkResult::Parent
}
opts::Action::WithServer(ActionWithServer::KillServer) => {
if let Some(response) = handle_server_command(&paths, &ActionWithServer::KillServer, 1)? {
handle_daemon_response(response);
}
false
}
// a running daemon is necessary for this command
opts::Action::WithServer(action) => {
// attempt to just send the command to a running daemon
match handle_server_command(&paths, &action, 5) {
Ok(Some(response)) => {
handle_daemon_response(response);
true
}
Ok(None) => true,
Err(err) if action.can_start_daemon() && !opts.no_daemonize => {
// connecting to the daemon failed. Thus, start the daemon here!
log::warn!("Failed to connect to daemon: {}", err);
log::info!("Initializing eww server. ({})", paths.get_ipc_socket_file().display());
let _ = std::fs::remove_file(paths.get_ipc_socket_file());
if !opts.show_logs {
println!("Run `{} logs` to see any errors while editing your configuration.", eww_binary_name);
}
let (command, response_recv) = action.into_daemon_command();
// start the daemon and give it the command
let fork_result = server::initialize_server::<B>(paths.clone(), Some(command), true)?;
let is_parent = fork_result == ForkResult::Parent;
if let (Some(recv), true) = (response_recv, is_parent) {
listen_for_daemon_response(recv);
}
is_parent
}
Err(err) => Err(err)?,
}
}
};
if would_show_logs && opts.show_logs {
client::handle_client_only_action(&paths, opts::ActionClientOnly::Logs)?;
}
Ok(())
}
fn listen_for_daemon_response(mut recv: DaemonResponseReceiver) {
let rt = tokio::runtime::Builder::new_current_thread()
.thread_name("listen-for-daemon-response")
.enable_all()
.build()
.expect("Failed to initialize tokio runtime");
rt.block_on(async {
if let Ok(Some(response)) = tokio::time::timeout(Duration::from_millis(100), recv.recv()).await {
println!("{}", response);
}
})
}
/// attempt to send a command to the daemon and send it the given action repeatedly.
fn handle_server_command(paths: &EwwPaths, action: &ActionWithServer, connect_attempts: usize) -> Result<Option<DaemonResponse>> {
log::debug!("Trying to find server process at socket {}", paths.get_ipc_socket_file().display());
let mut stream = attempt_connect(paths.get_ipc_socket_file(), connect_attempts).context("Failed to connect to daemon")?;
log::debug!("Connected to Eww server ({}).", &paths.get_ipc_socket_file().display());
client::do_server_call(&mut stream, action).context("Error while forwarding command to server")
}
fn handle_daemon_response(res: DaemonResponse) {
match res {
DaemonResponse::Success(x) => println!("{}", x),
DaemonResponse::Failure(x) => {
eprintln!("{}", x);
std::process::exit(1);
}
}
}
fn attempt_connect(socket_path: impl AsRef<Path>, attempts: usize) -> Option<net::UnixStream> {
for _ in 0..attempts {
if let Ok(mut con) = net::UnixStream::connect(&socket_path) {
if client::do_server_call(&mut con, &opts::ActionWithServer::Ping).is_ok() {
return net::UnixStream::connect(&socket_path).ok();
}
}
std::thread::sleep(Duration::from_millis(200));
}
None
}
/// Check if a eww server is currently running by trying to send a ping message to it.
fn check_server_running(socket_path: impl AsRef<Path>) -> bool {
let response = net::UnixStream::connect(socket_path)
.ok()
.and_then(|mut stream| client::do_server_call(&mut stream, &opts::ActionWithServer::Ping).ok());
response.is_some()
}
================================================
FILE: crates/eww/src/opts.rs
================================================
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use eww_shared_util::VarName;
use serde::{Deserialize, Serialize};
use simplexpr::dynval::DynVal;
use yuck::{
config::{monitor::MonitorIdentifier, window_geometry::AnchorPoint},
value::Coords,
};
use crate::{
app,
daemon_response::{self, DaemonResponse, DaemonResponseSender},
};
/// Struct that gets generated from `RawOpt`.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Opt {
pub force_wayland: bool,
pub log_debug: bool,
pub show_logs: bool,
pub restart: bool,
pub config_path: Option<std::path::PathBuf>,
pub action: Action,
pub no_daemonize: bool,
}
#[derive(Parser, Debug, Serialize, Deserialize, PartialEq)]
#[clap(author = "ElKowar")]
#[clap(version, about)]
pub(super) struct RawOpt {
/// Write out debug logs. (To read the logs, run `eww logs`).
#[arg(long = "debug", global = true)]
log_debug: bool,
/// Force eww to use wayland. This is a no-op if eww was compiled without wayland support.
#[arg(long = "force-wayland", global = true)]
force_wayland: bool,
/// override path to configuration directory (directory that contains eww.yuck and eww.(s)css)
#[arg(short, long, global = true)]
config: Option<std::path::PathBuf>,
/// Watch the log output after executing the command
#[arg(long = "logs", global = true)]
show_logs: bool,
/// Avoid daemonizing eww.
#[arg(long = "no-daemonize", global = true)]
no_daemonize: bool,
/// Restart the daemon completely before running the command
#[arg(long = "restart", global = true)]
restart: bool,
#[command(subcommand)]
action: Action,
}
#[derive(Subcommand, Debug, Serialize, Deserialize, PartialEq)]
pub enum Action {
/// Generate a shell completion script
ShellCompletions {
#[arg(short, long)]
#[serde(with = "serde_shell")]
shell: clap_complete::shells::Shell,
},
/// Start the Eww daemon.
#[command(name = "daemon", alias = "d")]
Daemon,
#[command(flatten)]
ClientOnly(ActionClientOnly),
#[command(flatten)]
WithServer(ActionWithServer),
}
#[derive(Subcommand, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum ActionClientOnly {
/// Print and watch the eww logs
#[command(name = "logs")]
Logs,
}
#[derive(Subcommand, Debug, Serialize, Deserialize, PartialEq)]
pub enum ActionWithServer {
/// Ping the eww server, checking if it is reachable.
#[clap(name = "ping")]
Ping,
/// Update the value of a variable, in a running eww instance
#[clap(name = "update", alias = "u")]
Update {
/// variable_name="new_value"-pairs that will be updated
#[arg(value_parser = parse_var_update_arg)]
mappings: Vec<(VarName, DynVal)>,
},
/// Update a polling variable using its script.
///
/// This will force the variable to be updated even if its
/// automatic polling is disabled.
#[command(name = "poll")]
Poll {
/// Variables to be polled
names: Vec<VarName>,
},
/// Open the GTK debugger
#[command(name = "inspector", alias = "debugger")]
OpenInspector,
/// Open a window
#[clap(name = "open", alias = "o")]
OpenWindow {
/// Name of the window you want to open.
window_name: String,
// The id of the window instance
#[arg(long)]
id: Option<String>,
/// The identifier of the monitor the window should open on
#[arg(long)]
screen: Option<MonitorIdentifier>,
/// The position of the window, where it should open. (i.e.: 200x100)
#[arg(short, long)]
pos: Option<Coords>,
/// The size of the window to open (i.e.: 200x100)
#[arg(short, long)]
size: Option<Coords>,
/// Sidepoint of the window, formatted like "top right"
#[arg(short, long)]
anchor: Option<AnchorPoint>,
/// If the window is already open, close it instead
#[arg(long = "toggle")]
should_toggle: bool,
/// Automatically close the window after a specified amount of time, i.e.: 1s
#[arg(long, value_parser=parse_duration)]
duration: Option<std::time::Duration>,
/// Define a variable for the window, i.e.: `--arg "var_name=value"`
#[arg(long = "arg", value_parser = parse_var_update_arg)]
args: Option<Vec<(VarName, DynVal)>>,
},
/// Open multiple windows at once.
/// NOTE: This will in the future be part of eww open, and will then be removed.
#[command(name = "open-many")]
OpenMany {
/// List the windows to open, optionally including their id, i.e.: `--window "window_name:window_id"`
#[arg(value_parser = parse_window_config_and_id)]
windows: Vec<(String, String)>,
/// Define a variable for the window, i.e.: `--arg "window_id:var_name=value"`
#[arg(long = "arg", value_parser = parse_window_id_args)]
args: Vec<(String, VarName, DynVal)>,
/// If a window is already open, close it instead
#[arg(long = "toggle")]
should_toggle: bool,
},
/// Close the given windows
#[command(name = "close", alias = "c")]
CloseWindows { windows: Vec<String> },
/// Reload the configuration
#[command(name = "reload", alias = "r")]
Reload,
/// Kill the eww daemon
#[command(name = "kill", alias = "k")]
KillServer,
/// Close all windows, without killing the daemon
#[command(name = "close-all", alias = "ca")]
CloseAll,
/// Prints the variables used in all currently open window
#[command(name = "state")]
ShowState {
/// Shows all variables, including not currently used ones
#[arg(short, long)]
all: bool,
},
/// Get the value of a variable if defined
#[command(name = "get")]
GetVar { name: String },
/// List the names of active windows
#[command(name = "list-windows")]
ListWindows,
/// Show active window IDs, formatted linewise `<window_id>: <window_name>`
#[command(name = "active-windows")]
ListActiveWindows,
/// Print out the widget structure as seen by eww.
///
/// This may be useful if you are facing issues with how eww is interpreting your configuration,
/// and to provide additional context to the eww developers if you are filing a bug.
#[command(name = "debug")]
ShowDebug,
/// Print out the scope graph structure in graphviz dot format.
#[command(name = "graph")]
ShowGraph,
}
impl Opt {
pub fn from_env() -> Self {
let raw: RawOpt = RawOpt::parse();
raw.into()
}
}
impl From<RawOpt> for Opt {
fn from(other: RawOpt) -> Self {
let RawOpt { log_debug, force_wayland, config, show_logs, no_daemonize, restart, action } = other;
Opt { log_debug, force_wayland, show_logs, restart, config_path: config, action, no_daemonize }
}
}
/// Parse a window-name:window-id pair of the form `name:id` or `name` into a tuple of `(name, id)`.
fn parse_window_config_and_id(s: &str) -> Result<(String, String)> {
let (name, id) = s.split_once(':').unwrap_or((s, s));
Ok((name.to_string(), id.to_string()))
}
/// Parse a window-id specific variable value declaration with the syntax `window-id:variable_name="new_value"`
/// into a tuple of `(id, variable_name, new_value)`.
fn parse_window_id_args(s: &str) -> Result<(String, VarName, DynVal)> {
// Parse the = first so we know if an id has not been given
let (name, value) = parse_var_update_arg(s)?;
let (id, var_name) = name.0.split_once(':').unwrap_or(("", &name.0));
Ok((id.to_string(), var_name.into(), value))
}
/// Split the input string at `=`, parsing the value into a [`DynVal`].
fn parse_var_update_arg(s: &str) -> Result<(VarName, DynVal)> {
let (name, value) = s
.split_once('=')
.with_context(|| format!("arguments must be in the shape `variable_name=\"new_value\"`, but got: {}", s))?;
Ok((name.into(), DynVal::from_string(value.to_owned())))
}
impl ActionWithServer {
pub fn can_start_daemon(&self) -> bool {
matches!(self, ActionWithServer::OpenWindow { .. } | ActionWithServer::OpenMany { .. })
}
pub fn into_daemon_command(self) -> (app::DaemonCommand, Option<daemon_response::DaemonResponseReceiver>) {
let command = match self {
ActionWithServer::Update { mappings } => app::DaemonCommand::UpdateVars(mappings),
ActionWithServer::Poll { names } => app::DaemonCommand::PollVars(names),
ActionWithServer::OpenInspector => app::DaemonCommand::OpenInspector,
ActionWithServer::KillServer => app::DaemonCommand::KillServer,
ActionWithServer::CloseAll => app::DaemonCommand::CloseAll,
ActionWithServer::Ping => {
let (send, recv) = tokio::sync::mpsc::unbounded_channel();
let _ = send.send(DaemonResponse::Success("pong".to_owned()));
return (app::DaemonCommand::NoOp, Some(recv));
}
ActionWithServer::OpenMany { windows, args, should_toggle } => {
return with_response_channel(|sender| app::DaemonCommand::OpenMany { windows, args, should_toggle, sender });
}
ActionWithServer::OpenWindow { window_name, id, pos, size, screen, anchor, should_toggle, duration, args } => {
return with_response_channel(|sender| app::DaemonCommand::OpenWindow {
window_name,
instance_id: id,
pos,
size,
anchor,
screen,
should_toggle,
duration,
sender,
args,
})
}
ActionWithServer::CloseWindows { windows } => {
return with_response_channel(|sender| app::DaemonCommand::CloseWindows { windows, auto_reopen: false, sender });
}
ActionWithServer::Reload => return with_response_channel(app::DaemonCommand::ReloadConfigAndCss),
ActionWithServer::ListWindows => return with_response_channel(app::DaemonCommand::ListWindows),
ActionWithServer::ListActiveWindows => return with_response_channel(app::DaemonCommand::ListActiveWindows),
ActionWithServer::ShowState { all } => {
return with_response_channel(|sender| app::DaemonCommand::PrintState { all, sender })
}
ActionWithServer::GetVar { name } => {
return with_response_channel(|sender| app::DaemonCommand::GetVar { name, sender })
}
ActionWithServer::ShowDebug => return with_response_channel(app::DaemonCommand::PrintDebug),
ActionWithServer::ShowGraph => return with_response_channel(app::DaemonCommand::PrintGraph),
};
(command, None)
}
}
fn with_response_channel<O, F>(f: F) -> (O, Option<tokio::sync::mpsc::UnboundedReceiver<DaemonResponse>>)
where
F: FnOnce(DaemonResponseSender) -> O,
{
let (sender, recv) = daemon_response::create_pair();
(f(sender), Some(recv))
}
fn parse_duration(s: &str) -> Result<std::time::Duration, simplexpr::dynval::ConversionError> {
DynVal::from_string(s.to_owned()).as_duration()
}
mod serde_shell {
use std::str::FromStr as _;
use clap_complete::Shell;
use serde::{Deserialize as _, Deserializer, Serialize as _, Serializer};
pub fn serialize<S: Serializer>(shell: &Shell, serializer: S) -> Result<S::Ok, S::Error> {
shell.to_string().serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Shell, D::Error> {
let s = String::deserialize(deserializer)?;
Shell::from_str(&s).map_err(serde::de::Error::custom)
}
}
================================================
FILE: crates/eww/src/paths.rs
================================================
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
path::{Path, PathBuf},
};
use anyhow::{bail, Result};
/// Stores references to all the paths relevant to eww, and abstracts access to these files and directories
#[derive(Debug, Clone)]
pub struct EwwPaths {
pub log_file: PathBuf,
pub log_dir: PathBuf,
pub ipc_socket_file: PathBuf,
pub config_dir: PathBuf,
}
impl EwwPaths {
pub fn from_config_dir<P: AsRef<Path>>(config_dir: P) -> Result<Self> {
let config_dir = config_dir.as_ref();
if config_dir.is_file() {
bail!("Please provide the path to the config directory, not a file within it")
}
if !config_dir.exists() {
bail!("Configuration directory {} does not exist", config_dir.display());
}
let config_dir = config_dir.canonicalize()?;
let mut hasher = DefaultHasher::new();
format!("{}", config_dir.display()).hash(&mut hasher);
// daemon_id is a hash of the config dir path to ensure that, given a normal XDG_RUNTIME_DIR,
// the absolute path to the socket stays under the 108 bytes limit. (see #387, man 7 unix)
let daemon_id = format!("{:x}", hasher.finish());
let ipc_socket_file = std::env::var("XDG_RUNTIME_DIR")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| std::path::PathBuf::from("/tmp"))
.join(format!("eww-server_{}", daemon_id));
// 100 as the limit isn't quite 108 everywhere (i.e 104 on BSD or mac)
if format!("{}", ipc_socket_file.display()).len() > 100 {
log::warn!("The IPC socket file's absolute path exceeds 100 bytes, the socket may fail to create.");
}
let log_dir = std::env::var("XDG_CACHE_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from(std::env::var("HOME").unwrap()).join(".cache"))
.join("eww");
if !log_dir.exists() {
log::info!("Creating log dir");
std::fs::create_dir_all(&log_dir)?;
}
Ok(EwwPaths { config_dir, log_file: log_dir.join(format!("eww_{}.log", daemon_id)), log_dir, ipc_socket_file })
}
pub fn default() -> Result<Self> {
let config_dir = std::env::var("XDG_CONFIG_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from(std::env::var("HOME").unwrap()).join(".config"))
.join("eww");
Self::from_config_dir(config_dir)
}
pub fn get_log_file(&self) -> &Path {
self.log_file.as_path()
}
pub fn get_log_dir(&self) -> &Path {
self.log_dir.as_path()
}
pub fn get_ipc_socket_file(&self) -> &Path {
self.ipc_socket_file.as_path()
}
pub fn get_config_dir(&self) -> &Path {
self.config_dir.as_path()
}
pub fn get_yuck_path(&self) -> PathBuf {
self.config_dir.join("eww.yuck")
}
}
impl std::fmt::Display for EwwPaths {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"config-dir: {}, ipc-socket: {}, log-file: {}",
self.config_dir.display(),
self.ipc_socket_file.display(),
self.log_file.display()
)
}
}
================================================
FILE: crates/eww/src/script_var_handler.rs
================================================
use std::collections::HashMap;
use crate::{
app,
config::{create_script_var_failed_warn, script_var},
};
use anyhow::{anyhow, Result};
use app::DaemonCommand;
use eww_shared_util::VarName;
use nix::{
sys::signal,
unistd::{setpgid, Pid},
};
use simplexpr::dynval::DynVal;
use tokio::{
io::{AsyncBufReadExt, BufReader},
sync::mpsc::UnboundedSender,
};
use tokio_util::sync::CancellationToken;
use yuck::config::script_var_definition::{ListenScriptVar, PollScriptVar, ScriptVarDefinition, VarSource};
/// Initialize the script var handler, and return a handle to that handler, which can be used to control
/// the script var execution.
pub fn init(evt_send: UnboundedSender<DaemonCommand>) -> ScriptVarHandlerHandle {
let (msg_send, mut msg_recv) = tokio::sync::mpsc::unbounded_channel();
let thread_handle = std::thread::Builder::new()
.name("outer-script-var-handler".to_string())
.spawn(move || {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_name("script-var-handler")
.build()
.expect("Failed to initialize tokio runtime for script var handlers");
rt.block_on(async {
let _: Result<_> = async {
let mut handler = ScriptVarHandler {
listen_handler: ListenVarHandler::new(evt_send.clone())?,
poll_handler: PollVarHandler::new(evt_send)?,
};
crate::loop_select_exiting! {
Some(msg) = msg_recv.recv() => match msg {
ScriptVarHandlerMsg::AddVar(var) => {
handler.add(var).await;
}
ScriptVarHandlerMsg::Stop(name) => {
handler.stop_for_variable(&name).await?;
}
ScriptVarHandlerMsg::StopAll => {
handler.stop_all().await;
break;
}
},
else => break,
};
Ok(())
}
.await;
})
})
.expect("Failed to start script-var-handler thread");
ScriptVarHandlerHandle { msg_send, thread_handle }
}
/// Handle to the script-var handling system.
pub struct ScriptVarHandlerHandle {
msg_send: UnboundedSender<ScriptVarHandlerMsg>,
thread_handle: std::thread::JoinHandle<()>,
}
impl ScriptVarHandlerHandle {
/// Add a new script-var that should be executed.
/// This is idempodent, meaning that running a definition that already has a script_var attached which is running
/// won't do anything.
pub fn add(&self, script_var: ScriptVarDefinition) {
crate::print_result_err!(
"while forwarding instruction to script-var handler",
self.msg_send.send(ScriptVarHandlerMsg::AddVar(script_var))
);
}
/// Stop the execution of a specific script-var.
pub fn stop_for_variable(&self, name: VarName) {
crate::print_result_err!(
"while forwarding instruction to script-var handler",
self.msg_send.send(ScriptVarHandlerMsg::Stop(name)),
);
}
/// Stop the execution of all script-vars.
pub fn stop_all(&self) {
crate::print_result_err!(
"while forwarding instruction to script-var handler",
self.msg_send.send(ScriptVarHandlerMsg::StopAll)
);
}
pub fn join_thread(self) {
let _ = self.thread_handle.join();
}
}
/// Message enum used by the ScriptVarHandlerHandle to communicate to the ScriptVarHandler
#[derive(Debug, Eq, PartialEq)]
#[allow(clippy::large_enum_variant)]
enum ScriptVarHandlerMsg {
AddVar(ScriptVarDefinition),
Stop(VarName),
StopAll,
}
/// Handler that manages running and updating [ScriptVarDefinition]s
struct ScriptVarHandler {
listen_handler: ListenVarHandler,
poll_handler: PollVarHandler,
}
impl ScriptVarHandler {
async fn add(&mut self, script_var: ScriptVarDefinition) {
match script_var {
ScriptVarDefinition::Poll(var) => self.poll_handler.start(var).await,
ScriptVarDefinition::Listen(var) => self.listen_handler.start(var).await,
};
}
/// Stop the handler that is responsible for a given variable.
async fn stop_for_variable(&mut self, name: &VarName) -> Result<()> {
log::debug!("Stopping script var process for variable {}", name);
self.listen_handler.stop_for_variable(name).await;
self.poll_handler.stop_for_variable(name);
Ok(())
}
/// stop all running scripts and schedules
async fn stop_all(&mut self) {
log::debug!("Stopping script-var-handlers");
self.listen_handler.stop_all().await;
self.poll_handler.stop_all();
}
}
struct PollVarHandler {
evt_send: UnboundedSender<DaemonCommand>,
poll_handles: HashMap<VarName, CancellationToken>,
}
impl PollVarHandler {
fn new(evt_send: UnboundedSender<DaemonCommand>) -> Result<Self> {
let handler = PollVarHandler { evt_send, poll_handles: HashMap::new() };
Ok(handler)
}
async fn start(&mut self, var: PollScriptVar) {
if self.poll_handles.contains_key(&var.name) {
return;
}
log::debug!("starting poll var {}", &var.name);
let cancellation_token = CancellationToken::new();
self.poll_handles.insert(var.name.clone(), cancellation_token.clone());
let evt_send = self.evt_send.clone();
tokio::spawn(async move {
let result: Result<_> = (|| {
evt_send.send(app::DaemonCommand::UpdateVars(vec![(var.name.clone(), run_poll_once(&var)?)]))?;
Ok(())
})();
if let Err(err) = result {
crate::error_handling_ctx::print_error(err);
}
crate::loop_select_exiting! {
_ = cancellation_token.cancelled() => break,
_ = tokio::time::sleep(var.interval) => {
let result: Result<_> = (|| {
evt_send.send(app::DaemonCommand::UpdateVars(vec![(var.name.clone(), run_poll_once(&var)?)]))?;
Ok(())
})();
if let Err(err) = result {
crate::error_handling_ctx::print_error(err);
}
}
}
});
}
fn stop_for_variable(&mut self, name: &VarName) {
if let Some(token) = self.poll_handles.remove(name) {
log::debug!("stopped poll var {}", name);
token.cancel()
}
}
fn stop_all(&mut self) {
self.poll_handles.drain().for_each(|(_, token)| token.cancel());
}
}
pub fn run_poll_once(var: &PollScriptVar) -> Result<DynVal> {
match &var.command {
VarSource::Shell(span, command) => {
script_var::run_command(command).map_err(|e| anyhow!(create_script_var_failed_warn(*span, &var.name, &e.to_string())))
}
VarSource::Function(x) => x().map_err(|e| anyhow!(e)),
}
}
impl Drop for PollVarHandler {
fn drop(&mut self) {
self.stop_all();
}
}
struct ListenVarHandler {
evt_send: UnboundedSender<DaemonCommand>,
listen_process_handles: HashMap<VarName, cancellation::AwaitableCancelationSender>,
}
impl ListenVarHandler {
fn new(evt_send: UnboundedSender<DaemonCommand>) -> Result<Self> {
let handler = ListenVarHandler { evt_send, listen_process_handles: HashMap::new() };
Ok(handler)
}
/// Start a listen-var. Starting a variable that is already running will not do anything.
async fn start(&mut self, var: ListenScriptVar) {
log::debug!("starting listen-var {}", &var.name);
// Make sure the same listenvar is never started twice,
// as that would cause eww to not clean up the older listenvar on window close.
if self.listen_process_handles.contains_key(&var.name) {
return;
}
let (cancel_send, mut cancel_recv) = cancellation::create();
self.listen_process_handles.insert(var.name.clone(), cancel_send);
let evt_send = self.evt_send.clone();
tokio::spawn(async move {
let result: Result<_> = async {
let mut handle = unsafe {
tokio::process::Command::new("sh")
.args(["-c", &var.command])
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.stdin(std::process::Stdio::null())
.pre_exec(|| {
let _ = setpgid(Pid::from_raw(0), Pid::from_raw(0));
Ok(())
})
.spawn()?
};
let mut stdout_lines = BufReader::new(handle.stdout.take().unwrap()).lines();
let mut stderr_lines = BufReader::new(handle.stderr.take().unwrap()).lines();
let mut completion_notify = None;
crate::loop_select_exiting! {
_ = handle.wait() => break,
notify = cancel_recv.wait_for_cancel() => {
completion_notify = notify;
break;
}
Ok(Some(line)) = stdout_lines.next_line() => {
let new_value = DynVal::from_string(line.to_owned());
evt_send.send(DaemonCommand::UpdateVars(vec![(var.name.to_owned(), new_value)]))?;
}
Ok(Some(line)) = stderr_lines.next_line() => {
log::warn!("stderr of `{}`: {}", var.name, line);
}
else => break,
};
terminate_handle(handle).await;
if let Some(completion_notify) = completion_notify {
completion_notify.completed().await;
}
Ok(())
}
.await;
if let Err(err) = result {
log::error!(
"[{}:{}] Error while executing listen-var command {}: {:?}",
::std::file!(),
::std::line!(),
&var.command,
err
);
}
});
}
async fn stop_for_variable(&mut self, name: &VarName) {
if let Some(token) = self.listen_process_handles.remove(name) {
log::debug!("stopped listen-var {}", name);
token.cancel().await;
}
}
async fn stop_all(&mut self) {
for (_, token) in self.listen_process_handles.drain() {
token.cancel().await;
}
}
}
impl Drop for ListenVarHandler {
fn drop(&mut self) {
if !self.listen_process_handles.is_empty() {
std::thread::scope(|s| {
s.spawn(|| {
let rt = tokio::runtime::Builder::new_current_thread()
.thread_name("listen-var-drop-stop-all")
.build()
.expect("Failed to initialize tokio runtime for script var handlers");
rt.block_on(async {
self.stop_all().await;
});
});
})
}
}
}
async fn terminate_handle(mut child: tokio::process::Child) {
if let Some(id) = child.id() {
log::debug!("Killing process with id {}", id);
let _ = signal::killpg(Pid::from_raw(id as i32), signal::SIGTERM);
tokio::select! {
_ = child.wait() => { },
_ = tokio::time::sleep(std::time::Duration::from_secs(10)) => {
let _ = child.kill().await;
}
};
} else {
let _ = child.kill().await;
}
}
// Especially for listenvars, we want to make sure that the scripts are actually
// cancelled before we kill the tokio task that they run in.
// for that, we need to wait for the completion of the cancel itself
/// Provides a CancellationToken-like object that allows to wait for completion of the cancellation.
mod cancellation {
pub(super) struct CancelCompletionNotifier(tokio::sync::mpsc::Sender<()>);
impl CancelCompletionNotifier {
pub async fn completed(self) {
crate::print_result_err!("Sending cancellation completion", self.0.send(()).await);
}
}
pub(super) struct AwaitableCancelationReceiver(tokio::sync::mpsc::Receiver<CancelCompletionNotifier>);
impl AwaitableCancelationReceiver {
pub(super) async fn wait_for_cancel(&mut self) -> Option<CancelCompletionNotifier> {
self.0.recv().await
}
}
#[derive(Clone)]
pub(super) struct AwaitableCancelationSender(tokio::sync::mpsc::Sender<CancelCompletionNotifier>);
impl AwaitableCancelationSender {
pub(super) async fn cancel(&self) {
let (send, mut recv) = tokio::sync::mpsc::channel(1);
if self.0.send(CancelCompletionNotifier(send)).await.is_ok() {
let _ = recv.recv().await;
}
}
}
pub(super) fn create() -> (AwaitableCancelationSender, AwaitableCancelationReceiver) {
let (send, recv) = tokio::sync::mpsc::channel(1);
(AwaitableCancelationSender(send), AwaitableCancelationReceiver(recv))
}
}
================================================
FILE: crates/eww/src/server.rs
================================================
use crate::{
app::{self, App, DaemonCommand},
config, daemon_response,
display_backend::DisplayBackend,
error_handling_ctx, ipc_server, script_var_handler,
state::scope_graph::ScopeGraph,
EwwPaths,
};
use anyhow::{Context, Result};
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
io::Write,
marker::PhantomData,
os::unix::io::AsRawFd,
path::Path,
rc::Rc,
sync::{atomic::Ordering, Arc},
};
use tokio::sync::mpsc::*;
pub fn initialize_server<B: DisplayBackend>(
paths: EwwPaths,
action: Option<DaemonCommand>,
should_daemonize: bool,
) -> Result<ForkResult> {
let (ui_send, mut ui_recv) = tokio::sync::mpsc::unbounded_channel();
std::env::set_current_dir(paths.get_config_dir())
.with_context(|| format!("Failed to change working directory to {}", paths.get_config_dir().display()))?;
log::info!("Loading paths: {}", &paths);
let read_config = config::read_from_eww_paths(&paths);
let eww_config = match read_config {
Ok(config) => config,
Err(err) => {
error_handling_ctx::print_error(err);
config::EwwConfig::default()
}
};
cleanup_log_dir(paths.get_log_dir())?;
if should_daemonize {
let fork_result = do_detach(paths.get_log_file())?;
if fork_result == ForkResult::Parent {
return Ok(ForkResult::Parent);
}
}
println!(
r#"
┏━━━━━━━━━━━━━━━━━━━━━━━┓
┃Initializing eww daemon┃
┗━━━━━━━━━━━━━━━━━━━━━━━┛
"#
);
simple_signal::set_handler(&[simple_signal::Signal::Int, simple_signal::Signal::Term], move |_| {
log::info!("Shutting down eww daemon...");
if let Err(e) = crate::application_lifecycle::send_exit() {
log::error!("Failed to send application shutdown event to workers: {:?}", e);
std::process::exit(1);
}
});
if B::IS_WAYLAND {
std::env::set_var("GDK_BACKEND", "wayland")
}
gtk::init()?;
log::debug!("Initializing script var handler");
let script_var_handler = script_var_handler::init(ui_send.clone());
let (scope_graph_evt_send, mut scope_graph_evt_recv) = tokio::sync::mpsc::unbounded_channel();
let mut app: App<B> = app::App {
scope_graph: Rc::new(RefCell::new(ScopeGraph::from_global_vars(
eww_config.generate_initial_state()?,
scope_graph_evt_send,
))),
eww_config,
open_windows: HashMap::new(),
failed_windows: HashSet::new(),
instance_id_to_args: HashMap::new(),
css_provider: gtk::CssProvider::new(),
script_var_handler,
app_evt_send: ui_send.clone(),
window_close_timer_abort_senders: HashMap::new(),
paths,
phantom: PhantomData,
};
if let Some(screen) = gtk::gdk::Screen::default() {
gtk::StyleContext::add_provider_for_screen(&screen, &app.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
}
if let Ok((file_id, css)) = config::scss::parse_scss_from_config(app.paths.get_config_dir()) {
if let Err(e) = app.load_css(file_id, &css) {
error_handling_ctx::print_error(e);
}
}
connect_monitor_added(ui_send.clone());
// initialize all the handlers and tasks running asyncronously
let tokio_handle = init_async_part(app.paths.clone(), ui_send);
gtk::glib::MainContext::default().spawn_local(async move {
// if an action was given to the daemon initially, execute it first.
if let Some(action) = action {
app.handle_command(action).await;
}
loop {
tokio::select! {
Some(scope_graph_evt) = scope_graph_evt_recv.recv() => {
app.scope_graph.borrow_mut().handle_scope_graph_event(scope_graph_evt);
},
Some(ui_event) = ui_recv.recv() => {
app.handle_command(ui_event).await;
}
else => break,
}
}
});
// allow the GTK main thread to do tokio things
let _g = tokio_handle.enter();
gtk::main();
log::info!("main application thread finished");
Ok(ForkResult::Child)
}
fn connect_monitor_added(ui_send: UnboundedSender<DaemonCommand>) {
let display = gtk::gdk::Display::default().expect("could not get default display");
display.connect_monitor_added({
move |_display: >k::gdk::Display, _monitor: >k::gdk::Monitor| {
log::info!("New monitor connected, reloading configuration");
let _ = reload_config_and_css(&ui_send);
}
});
}
fn reload_config_and_css(ui_send: &UnboundedSender<DaemonCommand>) -> Result<()> {
let (daemon_resp_sender, mut daemon_resp_response) = daemon_response::create_pair();
ui_send.send(DaemonCommand::ReloadConfigAndCss(daemon_resp_sender))?;
tokio::spawn(async move {
match daemon_resp_response.recv().await {
Some(daemon_response::DaemonResponse::Success(_)) => log::info!("Reloaded config successfully"),
Some(daemon_response::DaemonResponse::Failure(e)) => eprintln!("{}", e),
None => log::error!("No response to reload configuration-reload request"),
}
});
Ok(())
}
fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::DaemonCommand>) -> tokio::runtime::Handle {
let rt = tokio::runtime::Builder::new_multi_thread()
.thread_name("main-async-runtime")
.enable_all()
.build()
.expect("Failed to initialize tokio runtime");
let handle = rt.handle().clone();
std::thread::Builder::new()
.name("outer-main-async-runtime".to_string())
.spawn(move || {
rt.block_on(async {
let filewatch_join_handle = {
let ui_send = ui_send.clone();
let paths = paths.clone();
tokio::spawn(async move { run_filewatch(paths.config_dir, ui_send).await })
};
let ipc_server_join_handle = {
let ui_send = ui_send.clone();
tokio::spawn(async move { ipc_server::run_server(ui_send, paths.get_ipc_socket_file()).await })
};
let forward_exit_to_app_handle = {
let ui_send = ui_send.clone();
tokio::spawn(async move {
// Wait for application exit event
let _ = crate::application_lifecycle::recv_exit().await;
log::debug!("Forward task received exit event");
// Then forward that to the application
let _ = ui_send.send(app::DaemonCommand::KillServer);
})
};
let result = tokio::try_join!(filewatch_join_handle, ipc_server_join_handle, forward_exit_to_app_handle);
if let Err(e) = result {
log::error!("Eww exiting with error: {:?}", e);
}
})
})
.expect("Failed to start outer-main-async-runtime thread");
handle
}
/// Watch configuration files for changes, sending reload events to the eww app when the files change.
async fn run_filewatch<P: AsRef<Path>>(config_dir: P, evt_send: UnboundedSender<app::DaemonCommand>) -> Result<()> {
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let mut watcher: RecommendedWatcher = notify::recommended_watcher(move |res: notify::Result<notify::Event>| match res {
Ok(notify::Event { kind: notify::EventKind::Modify(_), paths, .. }) => {
let relevant_files_changed = paths.iter().any(|path| {
let ext = path.extension().unwrap_or_default();
ext == "yuck" || ext == "scss" || ext == "css"
});
if relevant_files_changed {
if let Err(err) = tx.send(()) {
log::warn!("Error forwarding file update event: {:?}", err);
}
}
}
Ok(_) => {}
Err(e) => log::error!("Encountered Error While Watching Files: {}", e),
})?;
watcher.watch(config_dir.as_ref(), RecursiveMode::Recursive)?;
// make sure to not trigger reloads too much by only accepting one reload every 500ms.
let debounce_done = Arc::new(std::sync::atomic::AtomicBool::new(true));
crate::loop_select_exiting! {
Some(()) = rx.recv() => {
let debounce_done = debounce_done.clone();
if debounce_done.swap(false, Ordering::SeqCst) {
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
debounce_done.store(true, Ordering::SeqCst);
});
// without this sleep, reading the config file sometimes gives an empty file.
// This is probably a result of editors not locking the file correctly,
// and eww being too fast, thus reading the file while it's empty.
// There should be some cleaner solution for this, but this will do for now.
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
reload_config_and_css(&evt_send)?;
}
},
else => break
};
Ok(())
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ForkResult {
Parent,
Child,
}
/// detach the process from the terminal, also redirecting stdout and stderr to LOG_FILE
fn do_detach(log_file_path: impl AsRef<Path>) -> Result<ForkResult> {
// detach from terminal
match unsafe { nix::unistd::fork()? } {
nix::unistd::ForkResult::Child => {
nix::unistd::setsid()?;
match unsafe { nix::unistd::fork()? } {
nix::unistd::ForkResult::Parent { .. } => std::process::exit(0),
nix::unistd::ForkResult::Child => {}
}
}
nix::unistd::ForkResult::Parent { .. } => {
return Ok(ForkResult::Parent);
}
}
let file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&log_file_path)
.unwrap_or_else(|_| panic!("Error opening log file ({}), for writing", log_file_path.as_ref().to_string_lossy()));
let fd = file.as_raw_fd();
if nix::unistd::isatty(1)? {
nix::unistd::dup2(fd, std::io::stdout().as_raw_fd())?;
}
if nix::unistd::isatty(2)? {
nix::unistd::dup2(fd, std::io::stderr().as_raw_fd())?;
}
Ok(ForkResult::Child)
}
/// Ensure the log directory never grows larger than 100MB by deleting files older than 7 days,
/// and truncating all other logfiles to 100MB.
fn cleanup_log_dir(log_dir: impl AsRef<Path>) -> Result<()> {
// Find all files named "eww_*.log" in the log directory
let log_files = std::fs::read_dir(&log_dir)?
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if let Some(file_name) = path.file_name() {
if file_name.to_string_lossy().starts_with("eww_") && file_name.to_string_lossy().ends_with(".log") {
Some(path)
} else {
None
}
} else {
None
}
})
.collect::<Vec<_>>();
for log_file in log_files {
// if the file is older than a week, delete it
if let Ok(metadata) = log_file.metadata() {
if metadata.modified()?.elapsed()?.as_secs() > 60 * 60 * 24 * 7 {
log::info!("Deleting old log file: {}", log_file.display());
std::fs::remove_file(&log_file)?;
} else {
// If the file is larger than 200MB, delete the start of it until it's 100MB or less.
let mut file = std::fs::OpenOptions::new().append(true).open(&log_file)?;
let file_size = file.metadata()?.len();
if file_size > 200_000_000 {
let mut file_content = std::fs::read(&log_file)?;
let bytes_to_remove = file_content.len().saturating_sub(100_000_000);
file_content.drain(0..bytes_to_remove);
file.set_len(0)?;
file.write_all(&file_content)?;
}
}
}
}
Ok(())
}
================================================
FILE: crates/eww/src/state/mod.rs
================================================
mod one_to_n_elements_map;
pub mod scope;
pub mod scope_graph;
#[cfg(test)]
mod test;
================================================
FILE: crates/eww/src/state/one_to_n_elements_map.rs
================================================
use anyhow::{bail, Result};
use std::collections::{HashMap, HashSet};
/// A map that represents a structure of a 1-n relationship with edges that contain data.
#[derive(Debug)]
pub struct OneToNElementsMap<I, T> {
pub(super) child_to_parent: HashMap<I, (I, T)>,
pub(super) parent_to_children: HashMap<I, HashSet<I>>,
}
impl<I: Copy + std::hash::Hash + std::cmp::Eq + std::fmt::Debug, T> OneToNElementsMap<I, T> {
pub fn new() -> Self {
OneToNElementsMap { child_to_parent: HashMap::new(), parent_to_children: HashMap::new() }
}
pub fn clear(&mut self) {
self.child_to_parent.clear();
self.parent_to_children.clear()
}
pub fn insert(&mut self, child: I, parent: I, edge: T) -> Result<()> {
if self.child_to_parent.contains_key(&child) {
bail!("this child already has a parent");
}
self.child_to_parent.insert(child, (parent, edge));
self.parent_to_children.entry(parent).or_default().insert(child);
Ok(())
}
pub fn remove(&mut self, scope: I) {
if let Some(children) = self.parent_to_children.remove(&scope) {
for child in &children {
self.child_to_parent.remove(child);
}
}
if let Some((parent, _)) = self.child_to_parent.remove(&scope) {
if let Some(children_of_parent) = self.parent_to_children.get_mut(&parent) {
children_of_parent.remove(&scope);
}
}
}
pub fn get_parent_of(&self, index: I) -> Option<I> {
self.child_to_parent.get(&index).map(|(parent, _)| *parent)
}
pub fn get_parent_edge_of(&self, index: I) -> Option<&(I, T)> {
self.child_to_parent.get(&index)
}
pub fn get_parent_edge_mut(&mut self, index: I) -> Option<&mut (I, T)> {
self.child_to_parent.get_mut(&index)
}
#[allow(unused)]
pub fn get_children_of(&self, index: I) -> HashSet<I> {
self.parent_to_children.get(&index).cloned().unwrap_or_default()
}
/// Return the children and edges to those children of a given scope
pub fn get_children_edges_of(&self, index: I) -> Vec<(I, &T)> {
let mut result = Vec::new();
if let Some(children) = self.parent_to_children.get(&index) {
for child_scope in children {
let (_, edge) = self.child_to_parent.get(child_scope).expect("OneToNElementsMap got into inconsistent state");
result.push((*child_scope, edge));
}
}
result
}
#[cfg_attr(not(debug_assertions), allow(dead_code))]
pub fn validate(&self) -> Result<()> {
for (parent, children) in &self.parent_to_children {
for child in children {
if let Some((parent_2, _)) = self.child_to_parent.get(child) {
if parent_2 != parent {
bail!(
"parent_to_child stored mapping from {:?} to {:?}, but child_to_parent contained mapping to {:?} \
instead",
parent,
child,
parent_2
);
}
} else {
bail!(
"parent_to_child stored mapping from {:?} to {:?}, which was not found in child_to_parent",
parent,
child
);
}
}
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn test_add_scope() {
let mut map = OneToNElementsMap::new();
map.insert(1, 2, "a".to_string()).unwrap();
map.insert(2, 3, "b".to_string()).unwrap();
map.insert(3, 4, "c".to_string()).unwrap();
map.insert(5, 4, "d".to_string()).unwrap();
assert_eq!(map.get_parent_of(1), Some(2));
assert_eq!(map.get_parent_of(2), Some(3));
assert_eq!(map.get_parent_of(3), Some(4));
assert_eq!(map.get_parent_of(4), None);
assert_eq!(map.get_parent_of(5), Some(4));
assert_eq!(map.get_children_of(4), HashSet::from_iter(vec![3, 5]));
assert_eq!(map.get_children_of(3), HashSet::from_iter(vec![2]));
assert_eq!(map.get_children_of(2), HashSet::from_iter(vec![1]));
assert_eq!(map.get_children_of(1), HashSet::new());
}
#[test]
pub fn test_remove_scope() {
let mut map = OneToNElementsMap::new();
map.insert(1, 2, "a".to_string()).unwrap();
map.insert(2, 3, "b".to_string()).unwrap();
map.insert(3, 4, "c".to_string()).unwrap();
map.insert(5, 4, "d".to_string()).unwrap();
map.remove(4);
assert_eq!(map.get_parent_of(1), Some(2));
assert_eq!(map.get_parent_of(2), Some(3));
assert_eq!(map.get_parent_of(3), None);
assert_eq!(map.get_parent_of(4), None);
assert_eq!(map.get_parent_of(5), None);
assert_eq!(map.get_children_of(3), HashSet::from_iter(vec![2]));
assert_eq!(map.get_children_of(2), HashSet::from_iter(vec![1]));
assert_eq!(map.get_children_of(1), HashSet::new());
}
}
================================================
FILE: crates/eww/src/state/scope.rs
================================================
use anyhow::Result;
use std::{collections::HashMap, rc::Rc};
use eww_shared_util::VarName;
use simplexpr::dynval::DynVal;
use super::scope_graph::{ScopeGraph, ScopeIndex};
#[derive(Debug)]
pub struct Scope {
pub name: String,
pub ancestor: Option<ScopeIndex>,
pub data: HashMap<VarName, DynVal>,
/// The listeners that react to value changes in this scope.
/// **Note** that there might be VarNames referenced here that are not defined in this scope.
/// In those cases it is necessary to look into the scopes this scope is inheriting from.
pub listeners: HashMap<VarName, Vec<Rc<Listener>>>,
pub node_index: ScopeIndex,
}
impl Scope {
/// Initializes a scope **incompletely**. The [`Self::node_index`] is not set correctly, and needs to be
/// set to the index of the node in the scope graph that connects to this scope.
pub(super) fn new(name: String, created_by: Option<ScopeIndex>, data: HashMap<VarName, DynVal>) -> Self {
Self { name, ancestor: created_by, data, listeners: HashMap::new(), node_index: ScopeIndex(0) }
}
}
pub type ListenerFn = Box<dyn Fn(&mut ScopeGraph, HashMap<VarName, DynVal>) -> Result<()>>;
pub struct Listener {
pub needed_variables: Vec<VarName>,
pub f: ListenerFn,
}
impl std::fmt::Debug for Listener {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Listener").field("needed_variables", &self.needed_variables).field("f", &"function").finish()
}
}
================================================
FILE: crates/eww/src/state/scope_graph.rs
================================================
use std::{
collections::{HashMap, HashSet},
rc::Rc,
};
use anyhow::{anyhow, bail, Context, Result};
use eww_shared_util::{AttrName, VarName};
use simplexpr::{dynval::DynVal, SimplExpr};
use tokio::sync::mpsc::UnboundedSender;
use crate::error_handling_ctx;
use super::scope::{Listener, Scope};
#[derive(Hash, Eq, PartialEq, Copy, Clone)]
pub struct ScopeIndex(pub usize);
impl std::fmt::Debug for ScopeIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ScopeIndex({})", self.0)
}
}
impl ScopeIndex {
fn advance(&mut self) {
self.0 += 1;
}
}
pub enum ScopeGraphEvent {
RemoveScope(ScopeIndex),
}
/// A graph structure of scopes where each scope may inherit from another scope,
/// and can provide attributes to arbitrarily many descendant scopes.
///
/// ## Some terminology
/// **Subscope / Superscope**: Subscopes are scopes that _inherit_ from their superscope.
/// This means that they have access to all the variables defined in that scope as well.
/// The variables a subscope references from it's superscope are listed in the [`internal::Inherits`].
/// In most cases, scopes inherit from the global scope.
///
/// **Descendant / Ancestor**: Descendants of a scope are the scopes that are used
/// _within_ that ancestor scope. This means that a descendant scope's widgets will aways be
/// used as children of the ancestors widgets.
/// Any scope can have 0 or 1 ancestor, and any arbitrary amount of descendants.
/// An ancestor scope can provide attributes to it's descendants, which will be
/// listed in the respective [`internal::ProvidedAttr`]s.
///
/// Invariants:
/// - every scope inherits from exactly 0 or 1 scopes.
/// - any scope may provide 0-n attributes to 0-n descendants.
/// - There must not be inheritance loops
/// - Inheritance is transitive - if a is subscope of b, and b is subscope of c, a has access to variables from c.
/// - In case of transitive inheritance, all steps need to explicitly store the referenced variables. This means that
/// if A is subscope of B, and B is subscope of C, and A references a variable "foo" from C, then this reference
/// needs to be stored in both the inheritance connection A -> B and B -> C
#[derive(Debug)]
pub struct ScopeGraph {
pub(self) graph: internal::ScopeGraphInternal,
pub root_index: ScopeIndex,
// TODO this should be factored out, it doesn't really belong into this module / struct.
pub event_sender: UnboundedSender<ScopeGraphEvent>,
}
impl ScopeGraph {
pub fn from_global_vars(vars: HashMap<VarName, DynVal>, event_sender: UnboundedSender<ScopeGraphEvent>) -> Self {
let mut graph = internal::ScopeGraphInternal::new();
let root_index = graph.add_scope(Scope {
name: "global".to_string(),
ancestor: None,
data: vars,
listeners: HashMap::new(),
node_index: ScopeIndex(0),
});
if let Some(scope) = graph.scope_at_mut(root_index) {
scope.node_index = root_index;
}
Self { graph, root_index, event_sender }
}
pub fn update_global_value(&mut self, var_name: &VarName, value: DynVal) -> Result<()> {
self.update_value(self.root_index, var_name, value)
}
pub fn handle_scope_graph_event(&mut self, evt: ScopeGraphEvent) {
match evt {
ScopeGraphEvent::RemoveScope(scope_index) => {
self.remove_scope(scope_index);
}
}
}
/// Fully reinitialize the scope graph. Completely removes all state, and resets the ScopeIndex uniqueness.
pub fn clear(&mut self, vars: HashMap<VarName, DynVal>) {
self.graph.clear();
let root_index = self.graph.add_scope(Scope {
name: "global".to_string(),
ancestor: None,
data: vars,
listeners: HashMap::new(),
node_index: ScopeIndex(0),
});
if let Some(scope) = self.graph.scope_at_mut(root_index) {
scope.node_index = root_index;
}
self.root_index = root_index;
}
pub fn remove_scope(&mut self, scope_index: ScopeIndex) {
self.graph.remove_scope(scope_index);
}
#[cfg_attr(not(debug_assertions), allow(dead_code))]
pub fn validate(&self) -> Result<()> {
self.graph.validate()
}
pub fn visualize(&self) -> String {
self.graph.visualize()
}
pub fn currently_used_globals(&self) -> HashSet<VarName> {
self.variables_used_in_self_or_subscopes_of(self.root_index)
}
pub fn currently_unused_globals(&self) -> HashSet<VarName> {
let used_variables = self.currently_used_globals();
self.global_scope().data.keys().cloned().collect::<HashSet<_>>().difference(&used_variables).cloned().collect()
}
pub fn scope_at(&self, index: ScopeIndex) -> Option<&Scope> {
self.graph.scope_at(index)
}
pub fn global_scope(&self) -> &Scope {
self.graph.scope_at(self.root_index).expect("No root scope in graph")
}
/// Evaluate a [SimplExpr] in a given scope. This will return `Err` if any referenced variables
/// are not available in the scope. If evaluation fails for other reasons (bad types, etc)
/// this will print a warning and return an empty string instead.
pub fn evaluate_simplexpr_in_scope(&self, index: ScopeIndex, expr: &SimplExpr) -> Result<DynVal> {
let needed_vars = self.lookup_variables_in_scope(index, &expr.collect_var_refs())?;
// TODORW
// TODO allowing it to fail here is painfully ugly
match expr.eval(&needed_vars) {
Ok(value) => Ok(value),
Err(err) => {
error_handling_ctx::print_error(anyhow!(err));
Ok(DynVal::from(""))
}
}
}
/// Register a new scope in the graph.
/// This will look up and resolve variable references in attributes to set up the correct [`internal::ProvidedAttr`] relationships.
pub fn register_new_scope(
&mut self,
name: String,
superscope: Option<ScopeIndex>,
calling_scope: ScopeIndex,
attributes: HashMap<AttrName, SimplExpr>,
) -> Result<ScopeIndex> {
let mut scope_variables = HashMap::new();
// First get the current values. If nothing here fails, we know that everything is in scope.
for (attr_name, attr_value) in &attributes {
let current_value = self.evaluate_simplexpr_in_scope(calling_scope, attr_value)?;
scope_variables.insert(attr_name.clone().into(), current_value);
}
// Now that we're sure that we have all of the values, we can make changes to the scopegraph without
// risking getting it into an inconsistent state by adding a scope that can't get fully instantiated
// and aborting that operation prematurely.
let new_scope = Scope::new(name, Some(calling_scope), scope_variables);
let new_scope_index = self.graph.add_scope(new_scope);
if let Some(superscope) = superscope {
self.graph.add_inheritance_relation(new_scope_index, superscope);
}
if let Some(scope) = self.graph.scope_at_mut(new_scope_index) {
scope.node_index = new_scope_index;
}
for (attr_name, expression) in attributes {
let expression_var_refs = expression.collect_var_refs();
if !expression_var_refs.is_empty() {
self.graph.register_scope_provides_attr(
calling_scope,
new_scope_index,
internal::ProvidedAttr { attr_name, expression },
);
for used_variable in expression_var_refs {
self.register_scope_referencing_variable(calling_scope, used_variable)?;
}
}
}
#[cfg(debug_assertions)]
self.validate()?;
Ok(new_scope_index)
}
/// Register a listener. This listener will get called when any of the required variables change.
/// If there are no required_variables in the listener, nothing gets registered, but the listener
/// gets called once.
/// This should be used to update the gtk widgets that are in a scope.
/// This also calls the listener initially.
pub fn register_listener(&mut self, scope_index: ScopeIndex, listener: Listener) -> Result<()> {
if listener.needed_variables.is_empty() {
if let Err(err) = (*listener.f)(self, HashMap::new()).context("Error while updating UI after state change") {
error_handling_ctx::print_error(err);
}
} else {
for required_var in &listener.needed_variables {
self.register_scope_referencing_variable(scope_index, required_var.clone())?;
}
let scope = self.graph.scope_at_mut(scope_index).context("Scope not in graph")?;
let listener = Rc::new(listener);
for required_var in &listener.needed_variables {
scope.listeners.entry(required_var.clone()).or_default().push(listener.clone());
}
let required_variables = self.lookup_variables_in_scope(scope_index, &listener.needed_variables)?;
if let Err(err) = (*listener.f)(self, required_variables).context("Error while updating UI after state change") {
error_handling_ctx::print_error(err);
}
#[cfg(debug_assertions)]
self.validate()?;
}
Ok(())
}
/// Register the fact that a scope is referencing a given variable.
/// If the scope contains the variable itself, this is a No-op. Otherwise, will add that reference to the inherited scope relation.
pub fn register_scope_referencing_variable(&mut self, scope_index: ScopeIndex, var_name: VarName) -> Result<()> {
if !self.graph.scope_at(scope_index).context("scope not in graph")?.data.contains_key(&var_name) {
let superscope =
self.graph.superscope_of(scope_index).with_context(|| format!("Variable {} not in scope", var_name))?;
self.graph.add_reference_to_inherits_edge(scope_index, var_name.clone())?;
self.register_scope_referencing_variable(superscope, var_name)?;
}
Ok(())
}
pub fn update_value(&mut self, original_scope_index: ScopeIndex, updated_var: &VarName, new_value: DynVal) -> Result<()> {
let scope_index = self
.find_scope_with_variable(original_scope_index, updated_var)
.with_context(|| format!("Variable {} not scope", updated_var))?;
if let Some(entry) = self.graph.scope_at_mut(scope_index).and_then(|scope| scope.data.get_mut(updated_var)) {
*entry = new_value;
}
self.notify_value_changed(scope_index, updated_var)?;
#[cfg(debug_assertions)]
self.graph.validate()?;
Ok(())
}
/// Notify a scope that a value has been changed. This triggers the listeners and notifies further subscopes scopes recursively.
pub fn notify_value_changed(&mut self, scope_index: ScopeIndex, updated_var: &VarName) -> Result<()> {
// Update scopes that reference the changed variable in their attribute expressions.
let edges: Vec<(ScopeIndex, internal::ProvidedAttr)> =
self.graph.scopes_getting_attr_using(scope_index, updated_var).into_iter().map(|(a, b)| (a, b.clone())).collect();
for (referencing_scope, edge) in edges {
if let Err(err) = self.evaluate_simplexpr_in_scope(scope_index, &edge.expression).and_then(|updated_attr_value| {
self.update_value(referencing_scope, edge.attr_name.to_var_name_ref(), updated_attr_value)
}) {
error_handling_ctx::print_error(err);
}
}
// Trigger the listeners from this scope
self.call_listeners_in_scope(scope_index, updated_var)?;
// Now find subscopes that reference this variable
let affected_subscopes = self.graph.subscopes_referencing(scope_index, updated_var);
for affected_subscope in affected_subscopes {
self.notify_value_changed(affected_subscope, updated_var)?;
}
Ok(())
}
/// Call all of the listeners in a given `scope_index` that are affected by a change to the `updated_var`.
fn call_listeners_in_scope(&mut self, scope_index: ScopeIndex, updated_var: &VarName) -> Result<()> {
let scope = self.graph.scope_at(scope_index).context("Scope not in graph")?;
if let Some(triggered_listeners) = scope.listeners.get(updated_var) {
for listener in triggered_listeners.clone() {
let required_variables = self.lookup_variables_in_scope(scope_index, &listener.needed_variables)?;
if let Err(err) = (*listener.f)(self, required_variables).context("Error while updating UI after state change") {
error_handling_ctx::print_error(err);
}
}
}
Ok(())
}
/// Find the closest available scope that contains variable with the given name.
pub fn find_scope_with_variable(&self, index: ScopeIndex, var_name: &VarName) -> Option<ScopeIndex> {
let scope = self.graph.scope_at(index)?;
if scope.data.contains_key(var_name) {
Some(index)
} else {
self.find_scope_with_variable(self.graph.superscope_of(index)?, var_name)
}
}
/// Find the value of a variable in the closest available scope that contains a variable with that name.
pub fn lookup_variable_in_scope(&self, index: ScopeIndex, var_name: &VarName) -> Option<&DynVal> {
self.find_scope_with_variable(index, var_name)
.and_then(|scope| self.graph.scope_at(scope))
.map(|x| x.data.get(var_name).unwrap())
}
/// Get all variables that are used in the given scope or in any descendants of that scope.
/// If called with an index not in the tree, will return an empty set of variables.
pub fn variables_used_in_self_or_subscopes_of(&self, index: ScopeIndex) -> HashSet<VarName> {
if let Some(scope) = self.scope_at(index) {
let mut variables: HashSet<VarName> = scope.listeners.keys().cloned().collect();
for (_, provided_attrs) in self.graph.descendant_edges_of(index) {
for attr in provided_attrs {
variables.extend(attr.expression.collect_var_refs());
}
}
for (_, edge) in self.graph.subscope_edges_of(index) {
variables.extend(edge.references.clone());
}
// get all the variables that the current scope references from it's superscope
if let Some((_, edge)) = self.graph.superscope_edge_of(index) {
variables.extend(edge.references.clone())
}
// look through all descendants of this scope
for (descendant, _) in self.graph.descendant_edges_of(index) {
let used_in_descendant = self.variables_used_in_self_or_subscopes_of(descendant);
// only include those variables that are not shadowed by the descendant itself
let descendant_scope = self.scope_at(descendant).unwrap();
variables.extend(used_in_descendant.difference(&descendant_scope.data.keys().cloned().collect()).cloned());
}
variables
} else {
HashSet::new()
}
}
/// like [Self::lookup_variable_in_scope], but looks up a set of variables and stores them in a HashMap.
pub fn lookup_variables_in_scope(&self, scope_index: ScopeIndex, vars: &[VarName]) -> Result<HashMap<VarName, DynVal>> {
vars.iter()
.map(|required_var_name| {
let value = self
.lookup_variable_in_scope(scope_index, required_var_name)
.with_context(|| format!("Variable {} neither in scope nor any superscope", required_var_name))?;
Ok((required_var_name.clone(), value.clone()))
})
.collect::<Result<_>>()
}
}
mod internal {
use super::{super::one_to_n_elements_map::OneToNElementsMap, *};
/// a --provides attribute [`Self::attr_name`] calculated via [`Self::expression`] to--> b
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ProvidedAttr {
pub attr_name: AttrName,
pub expression: SimplExpr,
}
/// a -- inherits scope of --> b
/// If a inherits from b, and references variable V, V may either be available in b or in scopes that b inherits from.
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Inherits {
/// The variable names the subscope references from the superscope
pub references: HashSet<VarName>,
}
/// The internal graph representation of the [`ScopeGraph`].
/// Unlike the public ScopeGraph, this may temporarily be in an inconsistent state while changes are being made.
#[derive(Debug)]
pub struct ScopeGraphInternal {
last_index: ScopeIndex,
scopes: HashMap<ScopeIndex, Scope>,
/// Edges from ancestors to descendants
pub(super) hierarchy_relations: OneToNElementsMap<ScopeIndex, Vec<ProvidedAttr>>,
/// Edges from superscopes to subscopes.
pub(super) inheritance_relations: OneToNElementsMap<ScopeIndex, Inherits>,
}
impl ScopeGraphInternal {
pub fn new() -> Self {
Self {
last_index: ScopeIndex(0),
scopes: HashMap::new(),
inheritance_relations: OneToNElementsMap::new(),
hierarchy_relations: OneToNElementsMap::new(),
}
}
pub fn clear(&mut self) {
self.scopes.clear();
self.inheritance_relations.clear();
self.hierarchy_relations.clear();
}
pub fn add_scope(&mut self, scope: Scope) -> ScopeIndex {
let idx = self.last_index;
if let Some(ancestor) = scope.ancestor {
let _ = self.hierarchy_relations.insert(idx, ancestor, Vec::new());
}
self.scopes.insert(idx, scope);
self.last_index.advance();
idx
}
pub fn descendant_edges_of(&self, index: ScopeIndex) -> Vec<(ScopeIndex, &Vec<ProvidedAttr>)> {
self.hierarchy_relations.get_children_edges_of(index)
}
pub fn subscope_edges_of(&self, index: ScopeIndex) -> Vec<(ScopeIndex, &Inherits)> {
self.inheritance_relations.get_children_edges_of(index)
}
pub fn superscope_edge_of(&self, index: ScopeIndex) -> Option<&(ScopeIndex, Inherits)> {
self.inheritance_relations.get_parent_edge_of(index)
}
pub fn remove_scope(&mut self, index: ScopeIndex) {
self.scopes.remove(&index);
if let Some(descendants) = self.hierarchy_relations.parent_to_children.get(&index).cloned() {
for descendant in descendants {
self.remove_scope(descendant);
}
}
self.hierarchy_relations.remove(index);
self.inheritance_relations.remove(index);
}
pub fn add_inheritance_relation(&mut self, a: ScopeIndex, b: ScopeIndex) {
self.inheritance_relations.insert(a, b, Inherits { references: HashSet::new() }).unwrap();
}
/// Register that a given scope `a` provides an attribute to it's descendant `b`.
pub fn register_scope_provides_attr(&mut self, a: ScopeIndex, b: ScopeIndex, edge: ProvidedAttr) {
if let Some((superscope, edges)) = self.hierarchy_relations.get_parent_edge_mut(b) {
assert_eq!(*superscope, a, "Hierarchy map had a different superscope for a given scope than what was given here");
edges.push(edge);
} else {
log::error!(
"Tried to register a provided attribute edge between two scopes that are not connected in the hierarchy map"
);
}
}
pub fn scope_at(&self, index: ScopeIndex) -> Option<&Scope> {
self.scopes.get(&index)
}
pub fn scope_at_mut(&mut self, index: ScopeIndex) -> Option<&mut Scope> {
self.scopes.get_mut(&index)
}
/// List all subscopes that reference a given variable directly (-> the variable is in the [Inherits::references])
pub fn subscopes_referencing(&self, index: ScopeIndex, var_name: &VarName) -> Vec<ScopeIndex> {
self.inheritance_relations
.get_children_edges_of(index)
.iter()
.filter(|(_, edge)| edge.references.contains(var_name))
.map(|(scope, _)| *scope)
.collect()
}
pub fn superscope_of(&self, index: ScopeIndex) -> Option<ScopeIndex> {
self.inheritance_relations.get_parent_of(index)
}
/// List the scopes that are provided some attribute referencing `var_name` by the given scope `index`.
pub fn scopes_getting_attr_using(&self, index: ScopeIndex, var_name: &VarName) -> Vec<(ScopeIndex, &ProvidedAttr)> {
let edge_mappings = self.hierarchy_relations.get_children_edges_of(index);
edge_mappings
.iter()
.flat_map(|(k, v)| v.iter().map(move |edge| (*k, edge)))
.filter(|(_, edge)| edge.expression.references_var(var_name))
.collect()
}
/// Register that a given scope references a variable from it's direct superscope.
/// If the given scope does not have a superscope, this will return an `Err`.
pub fn add_reference_to_inherits_edge(&mut self, subscope: ScopeIndex, var_name: VarName) -> Result<()> {
let (_, edge) = self
.inheritance_relations
.get_parent_edge_mut(subscope)
.with_context(|| format!("Given scope {:?} does not have any superscope", subscope))?;
edge.references.insert(var_name);
Ok(())
}
#[cfg_attr(not(debug_assertions), allow(dead_code))]
pub fn validate(&self) -> Result<()> {
for (child_scope, (parent_scope, _edge)) in &self.hierarchy_relations.child_to_parent {
if !self.scopes.contains_key(child_scope) {
bail!("hierarchy_relations lists key that is not in graph");
}
if !self.scopes.contains_key(parent_scope) {
bail!("hierarchy_relations values lists scope that is not in graph");
}
}
for (child_scope, (parent_scope_idx, edge)) in &self.inheritance_relations.child_to_parent {
if !self.scopes.contains_key(child_scope) {
bail!("inheritance_relations lists key that is not in graph");
}
if let Some(parent_scope) = self.scopes.get(parent_scope_idx) {
// check that everything the scope references from it's parent is actually
// accessible by the parent, meaning it either stores it directly or
// inherits it itself
for var in &edge.references {
let parent_has_access_to_var = parent_scope.data.contains_key(var)
|| self
.inheritance_relations
.child_to_parent
.get(parent_scope_idx)
.map_or(false, |(_, e)| e.references.contains(var));
if !parent_has_access_to_var {
bail!("scope inherited variable that parent scope doesn't have access to");
}
}
} else {
bail!("inheritance_relations values lists scope that is not in graph");
}
}
self.hierarchy_relations.validate()?;
self.inheritance_relations.validate()?;
Ok(())
}
pub fn visualize(&self) -> String {
let mut output = String::new();
output.push_str("digraph {\n");
for (scope_index, scope) in &self.scopes {
output.push_str(&format!(
" \"{:?}\"[label=\"{}\\n{}\"]\n",
scope_index,
scope.name,
format!(
"data: {:?}, listeners: {:?}",
scope.data.iter().filter(|(k, _v)| !k.0.starts_with("EWW")).collect::<Vec<_>>(),
scope
.listeners
.iter()
.map(|(k, v)| format!(
"on {}: {:?}",
k.0,
v.iter()
.map(|l| format!("{:?}", l.needed_variables.iter().map(|x| x.0.clone()).collect::<Vec<_>>()))
.collect::<Vec<_>>()
))
.collect::<Vec<_>>()
)
.replace('\"', "'")
));
if let Some(created_by) = scope.ancestor {
output.push_str(&format!(" \"{:?}\" -> \"{:?}\"[label=\"ancestor\"]\n", created_by, scope_index));
}
}
for (child, (parent, edges)) in &self.hierarchy_relations.child_to_parent {
for edge in edges {
output.push_str(&format!(
" \"{:?}\" -> \"{:?}\" [color = \"red\", label = \"{}\"]\n",
parent,
child,
format!(":{} `{:?}`", edge.attr_name, edge.expression).replace('\"', "'")
));
}
}
for (child, (parent, edge)) in &self.inheritance_relations.child_to_parent {
output.push_str(&format!(
" \"{:?}\" -> \"{:?}\" [color = \"blue\", label = \"{}\"]\n",
child,
parent,
format!("inherits({:?})", edge.references).replace('\"', "'")
));
}
output.push('}');
output
}
}
}
#[cfg(test)]
mod test {
use maplit::{hashmap, hashset};
use super::*;
#[test]
fn test_nested_inheritance() {
let globals = hashmap! {
"global".into() => "hi".into(),
};
let (send, _recv) = tokio::sync::mpsc::unbounded_channel();
let mut scope_graph = ScopeGraph::from_global_vars(globals, send);
let root_scope = scope_graph.root_index;
let widget1_scope = scope_graph.register_new_scope("1".into(), Some(root_scope), root_scope, hashmap! {}).unwrap();
let widget2_scope = scope_graph.register_new_scope("2".into(), Some(widget1_scope), widget1_scope, hashmap! {}).unwrap();
scope_graph.register_scope_referencing_variable(widget2_scope, "global".into()).unwrap();
let inheritance_child_to_parent = scope_graph.graph.inheritance_relations.child_to_parent;
assert!(inheritance_child_to_parent.get(&widget2_scope).unwrap().1.references.contains("global"));
assert!(inheritance_child_to_parent.get(&widget1_scope).unwrap().1.references.contains("global"));
}
#[test]
fn test_lookup_variable_in_scope() {
let globals = hashmap! {
"global".into() => "hi".into(),
};
let (send, _recv) = tokio::sync::mpsc::unbounded_channel();
let mut scope_graph = ScopeGraph::from_global_vars(globals, send);
let widget_1_scope = scope_graph
.register_new_scope("1".to_string(), Some(scope_graph.root_index), scope_graph.root_index, hashmap! {})
.unwrap();
let widget_2_scope =
scope_graph.register_new_scope("2".to_string(), Some(widget_1_scope), widget_1_scope, hashmap! {}).unwrap();
let widget_no_parent_scope = scope_graph.register_new_scope("2".to_string(), None, widget_1_sco
gitextract_tp854u_1/ ├── .editorconfig ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── feature_request.yml │ │ └── widget-request.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── build.yml │ └── gh-pages.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── YUCK_MIGRATION.md ├── crates/ │ ├── eww/ │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ ├── app.rs │ │ ├── application_lifecycle.rs │ │ ├── client.rs │ │ ├── config/ │ │ │ ├── eww_config.rs │ │ │ ├── inbuilt.rs │ │ │ ├── mod.rs │ │ │ ├── script_var.rs │ │ │ ├── scss.rs │ │ │ ├── system_stats.rs │ │ │ └── window_definition.rs │ │ ├── daemon_response.rs │ │ ├── display_backend.rs │ │ ├── error_handling_ctx.rs │ │ ├── file_database.rs │ │ ├── geometry.rs │ │ ├── ipc_server.rs │ │ ├── main.rs │ │ ├── opts.rs │ │ ├── paths.rs │ │ ├── script_var_handler.rs │ │ ├── server.rs │ │ ├── state/ │ │ │ ├── mod.rs │ │ │ ├── one_to_n_elements_map.rs │ │ │ ├── scope.rs │ │ │ ├── scope_graph.rs │ │ │ └── test.rs │ │ ├── util.rs │ │ ├── widgets/ │ │ │ ├── build_widget.rs │ │ │ ├── circular_progressbar.rs │ │ │ ├── def_widget_macro.rs │ │ │ ├── graph.rs │ │ │ ├── mod.rs │ │ │ ├── systray.rs │ │ │ ├── transform.rs │ │ │ ├── widget_definitions.rs │ │ │ └── window.rs │ │ ├── window_arguments.rs │ │ └── window_initiator.rs │ ├── eww_shared_util/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── locale.rs │ │ ├── span.rs │ │ └── wrappers.rs │ ├── notifier_host/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── host.rs │ │ ├── icon.rs │ │ ├── item.rs │ │ ├── lib.rs │ │ ├── proxy/ │ │ │ ├── dbus_menu.xml │ │ │ ├── dbus_status_notifier_item.rs │ │ │ ├── dbus_status_notifier_item.xml │ │ │ ├── dbus_status_notifier_watcher.rs │ │ │ ├── dbus_status_notifier_watcher.xml │ │ │ └── mod.rs │ │ └── watcher.rs │ ├── simplexpr/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ └── src/ │ │ ├── ast.rs │ │ ├── dynval.rs │ │ ├── error.rs │ │ ├── eval.rs │ │ ├── lib.rs │ │ ├── parser/ │ │ │ ├── lalrpop_helpers.rs │ │ │ ├── lexer.rs │ │ │ ├── mod.rs │ │ │ └── snapshots/ │ │ │ ├── simplexpr__parser__lexer__test__basic.snap │ │ │ ├── simplexpr__parser__lexer__test__comments.snap │ │ │ ├── simplexpr__parser__lexer__test__digit.snap │ │ │ ├── simplexpr__parser__lexer__test__empty_interpolation.snap │ │ │ ├── simplexpr__parser__lexer__test__escaping.snap │ │ │ ├── simplexpr__parser__lexer__test__interpolation_1.snap │ │ │ ├── simplexpr__parser__lexer__test__interpolation_nested.snap │ │ │ ├── simplexpr__parser__lexer__test__json_in_interpolation.snap │ │ │ ├── simplexpr__parser__lexer__test__number_in_ident.snap │ │ │ ├── simplexpr__parser__lexer__test__quote_backslash_eof.snap │ │ │ ├── simplexpr__parser__lexer__test__safe_interpolation.snap │ │ │ ├── simplexpr__parser__lexer__test__simplexpr_lexer_basic.snap │ │ │ ├── simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate-2.snap │ │ │ ├── simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate-3.snap │ │ │ ├── simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate.snap │ │ │ ├── simplexpr__parser__lexer__test__symbol_spam.snap │ │ │ ├── simplexpr__parser__lexer__test__weird_char_boundaries.snap │ │ │ ├── simplexpr__parser__lexer__test__weird_nesting.snap │ │ │ ├── simplexpr__parser__tests__test-10.snap │ │ │ ├── simplexpr__parser__tests__test-11.snap │ │ │ ├── simplexpr__parser__tests__test-12.snap │ │ │ ├── simplexpr__parser__tests__test-13.snap │ │ │ ├── simplexpr__parser__tests__test-14.snap │ │ │ ├── simplexpr__parser__tests__test-15.snap │ │ │ ├── simplexpr__parser__tests__test-16.snap │ │ │ ├── simplexpr__parser__tests__test-2.snap │ │ │ ├── simplexpr__parser__tests__test-3.snap │ │ │ ├── simplexpr__parser__tests__test-4.snap │ │ │ ├── simplexpr__parser__tests__test-5.snap │ │ │ ├── simplexpr__parser__tests__test-6.snap │ │ │ ├── simplexpr__parser__tests__test-7.snap │ │ │ ├── simplexpr__parser__tests__test-8.snap │ │ │ ├── simplexpr__parser__tests__test-9.snap │ │ │ └── simplexpr__parser__tests__test.snap │ │ ├── simplexpr_parser.lalrpop │ │ └── snapshots/ │ │ ├── simplexpr__dynval__test__parse_duration-2.snap │ │ ├── simplexpr__dynval__test__parse_duration-3.snap │ │ ├── simplexpr__dynval__test__parse_duration-4.snap │ │ ├── simplexpr__dynval__test__parse_duration-5.snap │ │ ├── simplexpr__dynval__test__parse_duration-6.snap │ │ ├── simplexpr__dynval__test__parse_duration-7.snap │ │ ├── simplexpr__dynval__test__parse_duration-8.snap │ │ ├── simplexpr__dynval__test__parse_duration.snap │ │ ├── simplexpr__dynval__test__parse_vec-2.snap │ │ ├── simplexpr__dynval__test__parse_vec-3.snap │ │ ├── simplexpr__dynval__test__parse_vec-4.snap │ │ ├── simplexpr__dynval__test__parse_vec-5.snap │ │ ├── simplexpr__dynval__test__parse_vec-6.snap │ │ ├── simplexpr__dynval__test__parse_vec-7.snap │ │ ├── simplexpr__dynval__test__parse_vec-8.snap │ │ ├── simplexpr__dynval__test__parse_vec.snap │ │ ├── simplexpr__tests__test-10.snap │ │ ├── simplexpr__tests__test-11.snap │ │ ├── simplexpr__tests__test-12.snap │ │ ├── simplexpr__tests__test-13.snap │ │ ├── simplexpr__tests__test-14.snap │ │ ├── simplexpr__tests__test-15.snap │ │ ├── simplexpr__tests__test-16.snap │ │ ├── simplexpr__tests__test-17.snap │ │ ├── simplexpr__tests__test-18.snap │ │ ├── simplexpr__tests__test-19.snap │ │ ├── simplexpr__tests__test-2.snap │ │ ├── simplexpr__tests__test-20.snap │ │ ├── simplexpr__tests__test-21.snap │ │ ├── simplexpr__tests__test-22.snap │ │ ├── simplexpr__tests__test-23.snap │ │ ├── simplexpr__tests__test-24.snap │ │ ├── simplexpr__tests__test-25.snap │ │ ├── simplexpr__tests__test-26.snap │ │ ├── simplexpr__tests__test-3.snap │ │ ├── simplexpr__tests__test-4.snap │ │ ├── simplexpr__tests__test-5.snap │ │ ├── simplexpr__tests__test-6.snap │ │ ├── simplexpr__tests__test-7.snap │ │ ├── simplexpr__tests__test-8.snap │ │ ├── simplexpr__tests__test-9.snap │ │ └── simplexpr__tests__test.snap │ └── yuck/ │ ├── Cargo.toml │ ├── build.rs │ └── src/ │ ├── ast_error.rs │ ├── config/ │ │ ├── attributes.rs │ │ ├── backend_window_options.rs │ │ ├── file_provider.rs │ │ ├── mod.rs │ │ ├── monitor.rs │ │ ├── script_var_definition.rs │ │ ├── snapshots/ │ │ │ ├── eww_config__config__test__config.snap │ │ │ └── yuck__config__test__config.snap │ │ ├── toplevel.rs │ │ ├── validate.rs │ │ ├── var_definition.rs │ │ ├── widget_definition.rs │ │ ├── widget_use.rs │ │ ├── window_definition.rs │ │ └── window_geometry.rs │ ├── error.rs │ ├── format_diagnostic.rs │ ├── lib.rs │ ├── parser/ │ │ ├── ast.rs │ │ ├── ast_iterator.rs │ │ ├── from_ast.rs │ │ ├── lexer.rs │ │ ├── mod.rs │ │ ├── parse_error.rs │ │ ├── parser.lalrpop │ │ └── snapshots/ │ │ ├── eww_config__parser__element__test__test.snap │ │ ├── eww_config__parser__test-10.snap │ │ ├── eww_config__parser__test-11.snap │ │ ├── eww_config__parser__test-12.snap │ │ ├── eww_config__parser__test-13.snap │ │ ├── eww_config__parser__test-14.snap │ │ ├── eww_config__parser__test-15.snap │ │ ├── eww_config__parser__test-16.snap │ │ ├── eww_config__parser__test-17.snap │ │ ├── eww_config__parser__test-2.snap │ │ ├── eww_config__parser__test-3.snap │ │ ├── eww_config__parser__test-4.snap │ │ ├── eww_config__parser__test-5.snap │ │ ├── eww_config__parser__test-6.snap │ │ ├── eww_config__parser__test-7.snap │ │ ├── eww_config__parser__test-8.snap │ │ ├── eww_config__parser__test-9.snap │ │ ├── eww_config__parser__test.snap │ │ ├── yuck__parser__lexer__test__basic.snap │ │ ├── yuck__parser__lexer__test__basic_simplexpr.snap │ │ ├── yuck__parser__lexer__test__char_boundary.snap │ │ ├── yuck__parser__lexer__test__end_with_string_interpolation.snap │ │ ├── yuck__parser__lexer__test__escaped_quote.snap │ │ ├── yuck__parser__lexer__test__escaped_strings.snap │ │ ├── yuck__parser__lexer__test__quotes_in_quotes.snap │ │ ├── yuck__parser__lexer__yuck_lexer-2.snap │ │ ├── yuck__parser__lexer__yuck_lexer-3.snap │ │ ├── yuck__parser__lexer__yuck_lexer-4.snap │ │ ├── yuck__parser__lexer__yuck_lexer.snap │ │ ├── yuck__parser__test-10.snap │ │ ├── yuck__parser__test-11.snap │ │ ├── yuck__parser__test-12.snap │ │ ├── yuck__parser__test-13.snap │ │ ├── yuck__parser__test-14.snap │ │ ├── yuck__parser__test-15.snap │ │ ├── yuck__parser__test-16.snap │ │ ├── yuck__parser__test-17.snap │ │ ├── yuck__parser__test-2.snap │ │ ├── yuck__parser__test-3.snap │ │ ├── yuck__parser__test-4.snap │ │ ├── yuck__parser__test-5.snap │ │ ├── yuck__parser__test-6.snap │ │ ├── yuck__parser__test-7.snap │ │ ├── yuck__parser__test-8.snap │ │ ├── yuck__parser__test-9.snap │ │ ├── yuck__parser__test.snap │ │ ├── yuck__parser__test__test-10.snap │ │ ├── yuck__parser__test__test-11.snap │ │ ├── yuck__parser__test__test-12.snap │ │ ├── yuck__parser__test__test-13.snap │ │ ├── yuck__parser__test__test-14.snap │ │ ├── yuck__parser__test__test-15.snap │ │ ├── yuck__parser__test__test-16.snap │ │ ├── yuck__parser__test__test-17.snap │ │ ├── yuck__parser__test__test-2.snap │ │ ├── yuck__parser__test__test-3.snap │ │ ├── yuck__parser__test__test-4.snap │ │ ├── yuck__parser__test__test-5.snap │ │ ├── yuck__parser__test__test-6.snap │ │ ├── yuck__parser__test__test-7.snap │ │ ├── yuck__parser__test__test-8.snap │ │ ├── yuck__parser__test__test-9.snap │ │ └── yuck__parser__test__test.snap │ └── value/ │ ├── coords.rs │ └── mod.rs ├── default.nix ├── docs/ │ ├── .gitignore │ ├── book.toml │ ├── src/ │ │ ├── SUMMARY.md │ │ ├── configuration.md │ │ ├── eww.md │ │ ├── examples.md │ │ ├── expression_language.md │ │ ├── magic-vars.md │ │ ├── troubleshooting.md │ │ ├── widgets.md │ │ └── working_with_gtk.md │ └── theme/ │ ├── book.js │ ├── css/ │ │ ├── chrome.css │ │ ├── general.css │ │ ├── print.css │ │ └── variables.css │ ├── highlight.css │ ├── highlight.js │ └── index.hbs ├── examples/ │ ├── data-structures/ │ │ ├── eww.scss │ │ └── eww.yuck │ └── eww-bar/ │ ├── eww.scss │ ├── eww.yuck │ └── scripts/ │ └── getvol ├── flake.nix ├── gen-docs.ts ├── rust-toolchain.toml ├── rustfmt.toml └── shell.nix
SYMBOL INDEX (942 symbols across 78 files)
FILE: crates/eww/build.rs
function main (line 2) | fn main() {
FILE: crates/eww/src/app.rs
type DaemonCommand (line 45) | pub enum DaemonCommand {
type EwwWindow (line 92) | pub struct EwwWindow {
method close (line 104) | pub fn close(self) {
type App (line 113) | pub struct App<B: DisplayBackend> {
function fmt (line 139) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
function wait_for_monitor_model (line 152) | async fn wait_for_monitor_model() {
function handle_command (line 171) | pub async fn handle_command(&mut self, event: DaemonCommand) {
function try_handle_command (line 178) | async fn try_handle_command(&mut self, event: DaemonCommand) -> Result<(...
function stop_application (line 326) | fn stop_application(&mut self) {
function update_global_variable (line 335) | fn update_global_variable(&mut self, name: VarName, value: DynVal) {
function apply_run_while_expressions_mentioning (line 347) | fn apply_run_while_expressions_mentioning(&mut self, name: &VarName) {
function force_poll_variable (line 369) | fn force_poll_variable(&mut self, name: VarName) {
function close_window (line 387) | fn close_window(&mut self, instance_id: &str, auto_reopen: bool) -> Resu...
function open_window (line 422) | fn open_window(&mut self, window_args: &WindowArguments) -> Result<()> {
function load_config (line 540) | pub fn load_config(&mut self, config: config::EwwConfig) -> Result<()> {
function load_css (line 564) | pub fn load_css(&mut self, file_id: usize, css: &str) -> Result<()> {
function initialize_window (line 586) | fn initialize_window<B: DisplayBackend>(
function apply_window_position (line 649) | fn apply_window_position(mut window_geometry: WindowGeometry, monitor_ge...
function on_screen_changed (line 663) | fn on_screen_changed(window: &Window, _old_screen: Option<&gdk::Screen>) {
function get_gdk_monitor (line 670) | fn get_gdk_monitor(identifier: Option<MonitorIdentifier>) -> Result<Moni...
function get_monitor_plug_name (line 696) | fn get_monitor_plug_name(display: &gdk::Display, monitor_num: i32) -> Op...
function get_monitor_from_display (line 707) | pub fn get_monitor_from_display(display: &gdk::Display, identifier: &Mon...
function get_window_rectangle (line 732) | pub fn get_window_rectangle(geometry: WindowGeometry, screen_rect: gdk::...
FILE: crates/eww/src/application_lifecycle.rs
function send_exit (line 12) | pub fn send_exit() -> Result<()> {
function recv_exit (line 19) | pub async fn recv_exit() -> Result<()> {
FILE: crates/eww/src/client.rs
function handle_client_only_action (line 14) | pub fn handle_client_only_action(paths: &EwwPaths, action: ActionClientO...
function do_server_call (line 29) | pub fn do_server_call(stream: &mut UnixStream, action: &opts::ActionWith...
FILE: crates/eww/src/config/eww_config.rs
function read_from_eww_paths (line 21) | pub fn read_from_eww_paths(eww_paths: &EwwPaths) -> Result<EwwConfig> {
type EwwConfig (line 28) | pub struct EwwConfig {
method read_from_dir (line 40) | pub fn read_from_dir(files: &mut FileDatabase, eww_paths: &EwwPaths) -...
method generate_initial_state (line 83) | pub fn generate_initial_state(&self) -> Result<HashMap<VarName, DynVal...
method get_windows (line 93) | pub fn get_windows(&self) -> &HashMap<String, WindowDefinition> {
method get_window (line 97) | pub fn get_window(&self, name: &str) -> Result<&WindowDefinition> {
method get_script_var (line 107) | pub fn get_script_var(&self, name: &VarName) -> Result<&ScriptVarDefin...
method get_widget_definitions (line 111) | pub fn get_widget_definitions(&self) -> &HashMap<String, WidgetDefinit...
method get_run_while_mentions_of (line 116) | pub fn get_run_while_mentions_of(&self, name: &VarName) -> Option<&Vec...
FILE: crates/eww/src/config/script_var.rs
function create_script_var_failed_warn (line 13) | pub fn create_script_var_failed_warn(span: Span, var_name: &VarName, err...
function initial_value (line 22) | pub fn initial_value(var: &ScriptVarDefinition) -> Result<DynVal> {
function run_command (line 41) | pub fn run_command(cmd: &str) -> Result<DynVal> {
FILE: crates/eww/src/config/scss.rs
function parse_scss_from_config (line 10) | pub fn parse_scss_from_config(path: &Path) -> anyhow::Result<(usize, Str...
FILE: crates/eww/src/config/system_stats.rs
type RefreshTime (line 7) | struct RefreshTime(std::time::Instant);
method new (line 9) | pub fn new() -> Self {
method next_refresh (line 13) | pub fn next_refresh(&mut self) -> std::time::Duration {
function get_disks (line 27) | pub fn get_disks() -> String {
function get_ram (line 54) | pub fn get_ram() -> String {
function get_temperatures (line 73) | pub fn get_temperatures() -> String {
function get_cpus (line 91) | pub fn get_cpus() -> String {
function get_battery_capacity (line 110) | pub fn get_battery_capacity() -> Result<String> {
function get_battery_capacity (line 135) | pub fn get_battery_capacity() -> Result<String> {
function get_battery_capacity (line 206) | pub fn get_battery_capacity() -> Result<String> {
function get_battery_capacity (line 253) | pub fn get_battery_capacity() -> Result<String> {
function net (line 257) | pub fn net() -> String {
function get_time (line 274) | pub fn get_time() -> String {
FILE: crates/eww/src/daemon_response.rs
type DaemonResponse (line 16) | pub enum DaemonResponse {
type DaemonResponseSender (line 22) | pub struct DaemonResponseSender(mpsc::UnboundedSender<DaemonResponse>);
method send_success (line 30) | pub fn send_success(&self, s: String) -> Result<()> {
method send_failure (line 34) | pub fn send_failure(&self, s: String) -> Result<()> {
method respond_with_error_list (line 39) | pub fn respond_with_error_list(&self, errors: impl IntoIterator<Item =...
method respond_with_result (line 49) | pub fn respond_with_result<T>(&self, result: Result<T>) -> Result<()> {
method respond_with_error_msg (line 60) | fn respond_with_error_msg(&self, msg: String) -> Result<()> {
function create_pair (line 24) | pub fn create_pair() -> (DaemonResponseSender, mpsc::UnboundedReceiver<D...
type DaemonResponseReceiver (line 66) | pub type DaemonResponseReceiver = mpsc::UnboundedReceiver<DaemonResponse>;
FILE: crates/eww/src/display_backend.rs
type DisplayBackend (line 11) | pub trait DisplayBackend: Send + Sync + 'static {
constant IS_X11 (line 12) | const IS_X11: bool;
constant IS_WAYLAND (line 13) | const IS_WAYLAND: bool;
method initialize_window (line 15) | fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rect...
constant IS_X11 (line 21) | const IS_X11: bool = false;
constant IS_WAYLAND (line 22) | const IS_WAYLAND: bool = false;
method initialize_window (line 24) | fn initialize_window(_window_init: &WindowInitiator, _monitor: gdk::Re...
constant IS_X11 (line 42) | const IS_X11: bool = false;
constant IS_WAYLAND (line 43) | const IS_WAYLAND: bool = true;
method initialize_window (line 45) | fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rect...
constant IS_X11 (line 155) | const IS_X11: bool = true;
constant IS_WAYLAND (line 156) | const IS_WAYLAND: bool = false;
method initialize_window (line 158) | fn initialize_window(window_init: &WindowInitiator, _monitor: gdk::Rec...
type NoBackend (line 18) | pub struct NoBackend;
type WaylandBackend (line 39) | pub struct WaylandBackend;
type X11Backend (line 153) | pub struct X11Backend;
function set_xprops (line 174) | pub fn set_xprops(window: &Window, monitor: Monitor, window_init: &Windo...
type X11BackendConnection (line 180) | struct X11BackendConnection {
method new (line 187) | fn new() -> Result<Self> {
method set_xprops_for (line 194) | fn set_xprops_for(&self, window: &Window, monitor: Monitor, window_ini...
FILE: crates/eww/src/error_handling_ctx.rs
function clear_files (line 19) | pub fn clear_files() {
function print_error (line 23) | pub fn print_error(err: anyhow::Error) {
function format_error (line 33) | pub fn format_error(err: &anyhow::Error) -> String {
function anyhow_err_to_diagnostic (line 37) | pub fn anyhow_err_to_diagnostic(err: &anyhow::Error) -> Option<Diagnosti...
function stringify_diagnostic (line 52) | pub fn stringify_diagnostic(mut diagnostic: codespan_reporting::diagnost...
FILE: crates/eww/src/file_database.rs
type FileDatabase (line 12) | pub struct FileDatabase {
method new (line 18) | pub fn new() -> Self {
method get_file (line 22) | fn get_file(&self, id: usize) -> Result<&CodeFile, codespan_reporting:...
method insert_code_file (line 26) | fn insert_code_file(&mut self, file: CodeFile) -> usize {
method insert_string (line 33) | pub fn insert_string(&mut self, name: String, content: String) -> Resu...
type FileId (line 66) | type FileId = usize;
type Name (line 67) | type Name = &'a str;
type Source (line 68) | type Source = String;
method name (line 70) | fn name(&'a self, id: Self::FileId) -> Result<Self::Name, codespan_rep...
method source (line 74) | fn source(&'a self, id: Self::FileId) -> Result<Self::Source, codespan...
method line_index (line 78) | fn line_index(&self, id: Self::FileId, byte_index: usize) -> Result<us...
method line_range (line 82) | fn line_range(
method load_yuck_file (line 42) | fn load_yuck_file(&mut self, path: std::path::PathBuf) -> Result<(Span, ...
method load_yuck_str (line 55) | fn load_yuck_str(&mut self, name: String, content: String) -> Result<(Sp...
method unload (line 60) | fn unload(&mut self, id: usize) {
type CodeFile (line 95) | struct CodeFile {
method line_start (line 105) | fn line_start(&self, line_index: usize) -> Result<usize, codespan_repo...
type CodeSource (line 119) | enum CodeSource {
method read_content (line 125) | fn read_content(&self) -> std::io::Result<String> {
FILE: crates/eww/src/geometry.rs
type Rect (line 5) | pub struct Rect {
FILE: crates/eww/src/ipc_server.rs
function run_server (line 9) | pub async fn run_server<P: AsRef<std::path::Path>>(evt_send: UnboundedSe...
function handle_connection (line 29) | async fn handle_connection(mut stream: tokio::net::UnixStream, evt_send:...
function read_action_from_stream (line 54) | async fn read_action_from_stream(stream_read: &'_ mut tokio::net::unix::...
FILE: crates/eww/src/main.rs
function main (line 37) | fn main() {
function detect_wayland (line 88) | fn detect_wayland() -> bool {
function run (line 94) | fn run<B: DisplayBackend>(opts: opts::Opt, eww_binary_name: String) -> R...
function listen_for_daemon_response (line 184) | fn listen_for_daemon_response(mut recv: DaemonResponseReceiver) {
function handle_server_command (line 198) | fn handle_server_command(paths: &EwwPaths, action: &ActionWithServer, co...
function handle_daemon_response (line 205) | fn handle_daemon_response(res: DaemonResponse) {
function attempt_connect (line 215) | fn attempt_connect(socket_path: impl AsRef<Path>, attempts: usize) -> Op...
function check_server_running (line 228) | fn check_server_running(socket_path: impl AsRef<Path>) -> bool {
FILE: crates/eww/src/opts.rs
type Opt (line 18) | pub struct Opt {
method from_env (line 220) | pub fn from_env() -> Self {
method from (line 227) | fn from(other: RawOpt) -> Self {
type RawOpt (line 31) | pub(super) struct RawOpt {
type Action (line 61) | pub enum Action {
type ActionClientOnly (line 81) | pub enum ActionClientOnly {
type ActionWithServer (line 88) | pub enum ActionWithServer {
method can_start_daemon (line 260) | pub fn can_start_daemon(&self) -> bool {
method into_daemon_command (line 264) | pub fn into_daemon_command(self) -> (app::DaemonCommand, Option<daemon...
function parse_window_config_and_id (line 234) | fn parse_window_config_and_id(s: &str) -> Result<(String, String)> {
function parse_window_id_args (line 242) | fn parse_window_id_args(s: &str) -> Result<(String, VarName, DynVal)> {
function parse_var_update_arg (line 252) | fn parse_var_update_arg(s: &str) -> Result<(VarName, DynVal)> {
function with_response_channel (line 313) | fn with_response_channel<O, F>(f: F) -> (O, Option<tokio::sync::mpsc::Un...
function parse_duration (line 321) | fn parse_duration(s: &str) -> Result<std::time::Duration, simplexpr::dyn...
function serialize (line 331) | pub fn serialize<S: Serializer>(shell: &Shell, serializer: S) -> Result<...
function deserialize (line 335) | pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result...
FILE: crates/eww/src/paths.rs
type EwwPaths (line 11) | pub struct EwwPaths {
method from_config_dir (line 19) | pub fn from_config_dir<P: AsRef<Path>>(config_dir: P) -> Result<Self> {
method default (line 60) | pub fn default() -> Result<Self> {
method get_log_file (line 69) | pub fn get_log_file(&self) -> &Path {
method get_log_dir (line 73) | pub fn get_log_dir(&self) -> &Path {
method get_ipc_socket_file (line 77) | pub fn get_ipc_socket_file(&self) -> &Path {
method get_config_dir (line 81) | pub fn get_config_dir(&self) -> &Path {
method get_yuck_path (line 85) | pub fn get_yuck_path(&self) -> PathBuf {
method fmt (line 91) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: crates/eww/src/script_var_handler.rs
function init (line 25) | pub fn init(evt_send: UnboundedSender<DaemonCommand>) -> ScriptVarHandle...
type ScriptVarHandlerHandle (line 66) | pub struct ScriptVarHandlerHandle {
method add (line 75) | pub fn add(&self, script_var: ScriptVarDefinition) {
method stop_for_variable (line 83) | pub fn stop_for_variable(&self, name: VarName) {
method stop_all (line 91) | pub fn stop_all(&self) {
method join_thread (line 98) | pub fn join_thread(self) {
type ScriptVarHandlerMsg (line 106) | enum ScriptVarHandlerMsg {
type ScriptVarHandler (line 113) | struct ScriptVarHandler {
method add (line 119) | async fn add(&mut self, script_var: ScriptVarDefinition) {
method stop_for_variable (line 127) | async fn stop_for_variable(&mut self, name: &VarName) -> Result<()> {
method stop_all (line 135) | async fn stop_all(&mut self) {
type PollVarHandler (line 142) | struct PollVarHandler {
method new (line 148) | fn new(evt_send: UnboundedSender<DaemonCommand>) -> Result<Self> {
method start (line 153) | async fn start(&mut self, var: PollScriptVar) {
method stop_for_variable (line 187) | fn stop_for_variable(&mut self, name: &VarName) {
method stop_all (line 194) | fn stop_all(&mut self) {
function run_poll_once (line 199) | pub fn run_poll_once(var: &PollScriptVar) -> Result<DynVal> {
method drop (line 209) | fn drop(&mut self) {
type ListenVarHandler (line 214) | struct ListenVarHandler {
method new (line 220) | fn new(evt_send: UnboundedSender<DaemonCommand>) -> Result<Self> {
method start (line 226) | async fn start(&mut self, var: ListenScriptVar) {
method stop_for_variable (line 292) | async fn stop_for_variable(&mut self, name: &VarName) {
method stop_all (line 299) | async fn stop_all(&mut self) {
method drop (line 307) | fn drop(&mut self) {
function terminate_handle (line 324) | async fn terminate_handle(mut child: tokio::process::Child) {
type CancelCompletionNotifier (line 344) | pub(super) struct CancelCompletionNotifier(tokio::sync::mpsc::Sender<()>);
method completed (line 346) | pub async fn completed(self) {
type AwaitableCancelationReceiver (line 351) | pub(super) struct AwaitableCancelationReceiver(tokio::sync::mpsc::Receiv...
method wait_for_cancel (line 354) | pub(super) async fn wait_for_cancel(&mut self) -> Option<CancelComplet...
type AwaitableCancelationSender (line 360) | pub(super) struct AwaitableCancelationSender(tokio::sync::mpsc::Sender<C...
method cancel (line 362) | pub(super) async fn cancel(&self) {
function create (line 370) | pub(super) fn create() -> (AwaitableCancelationSender, AwaitableCancelat...
FILE: crates/eww/src/server.rs
function initialize_server (line 23) | pub fn initialize_server<B: DisplayBackend>(
function connect_monitor_added (line 141) | fn connect_monitor_added(ui_send: UnboundedSender<DaemonCommand>) {
function reload_config_and_css (line 151) | fn reload_config_and_css(ui_send: &UnboundedSender<DaemonCommand>) -> Re...
function init_async_part (line 164) | fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::Daemon...
function run_filewatch (line 211) | async fn run_filewatch<P: AsRef<Path>>(config_dir: P, evt_send: Unbounde...
type ForkResult (line 258) | pub enum ForkResult {
function do_detach (line 264) | fn do_detach(log_file_path: impl AsRef<Path>) -> Result<ForkResult> {
function cleanup_log_dir (line 298) | fn cleanup_log_dir(log_dir: impl AsRef<Path>) -> Result<()> {
FILE: crates/eww/src/state/one_to_n_elements_map.rs
type OneToNElementsMap (line 6) | pub struct OneToNElementsMap<I, T> {
function new (line 12) | pub fn new() -> Self {
function clear (line 16) | pub fn clear(&mut self) {
function insert (line 21) | pub fn insert(&mut self, child: I, parent: I, edge: T) -> Result<()> {
function remove (line 30) | pub fn remove(&mut self, scope: I) {
function get_parent_of (line 43) | pub fn get_parent_of(&self, index: I) -> Option<I> {
function get_parent_edge_of (line 47) | pub fn get_parent_edge_of(&self, index: I) -> Option<&(I, T)> {
function get_parent_edge_mut (line 51) | pub fn get_parent_edge_mut(&mut self, index: I) -> Option<&mut (I, T)> {
function get_children_of (line 56) | pub fn get_children_of(&self, index: I) -> HashSet<I> {
function get_children_edges_of (line 61) | pub fn get_children_edges_of(&self, index: I) -> Vec<(I, &T)> {
function validate (line 73) | pub fn validate(&self) -> Result<()> {
function test_add_scope (line 104) | pub fn test_add_scope() {
function test_remove_scope (line 124) | pub fn test_remove_scope() {
FILE: crates/eww/src/state/scope.rs
type Scope (line 10) | pub struct Scope {
method new (line 24) | pub(super) fn new(name: String, created_by: Option<ScopeIndex>, data: ...
type ListenerFn (line 29) | pub type ListenerFn = Box<dyn Fn(&mut ScopeGraph, HashMap<VarName, DynVa...
type Listener (line 31) | pub struct Listener {
method fmt (line 36) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: crates/eww/src/state/scope_graph.rs
type ScopeIndex (line 16) | pub struct ScopeIndex(pub usize);
method fmt (line 19) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
method advance (line 24) | fn advance(&mut self) {
type ScopeGraphEvent (line 29) | pub enum ScopeGraphEvent {
type ScopeGraph (line 58) | pub struct ScopeGraph {
method from_global_vars (line 66) | pub fn from_global_vars(vars: HashMap<VarName, DynVal>, event_sender: ...
method update_global_value (line 81) | pub fn update_global_value(&mut self, var_name: &VarName, value: DynVa...
method handle_scope_graph_event (line 85) | pub fn handle_scope_graph_event(&mut self, evt: ScopeGraphEvent) {
method clear (line 94) | pub fn clear(&mut self, vars: HashMap<VarName, DynVal>) {
method remove_scope (line 109) | pub fn remove_scope(&mut self, scope_index: ScopeIndex) {
method validate (line 114) | pub fn validate(&self) -> Result<()> {
method visualize (line 118) | pub fn visualize(&self) -> String {
method currently_used_globals (line 122) | pub fn currently_used_globals(&self) -> HashSet<VarName> {
method currently_unused_globals (line 126) | pub fn currently_unused_globals(&self) -> HashSet<VarName> {
method scope_at (line 131) | pub fn scope_at(&self, index: ScopeIndex) -> Option<&Scope> {
method global_scope (line 135) | pub fn global_scope(&self) -> &Scope {
method evaluate_simplexpr_in_scope (line 142) | pub fn evaluate_simplexpr_in_scope(&self, index: ScopeIndex, expr: &Si...
method register_new_scope (line 157) | pub fn register_new_scope(
method register_listener (line 210) | pub fn register_listener(&mut self, scope_index: ScopeIndex, listener:...
method register_scope_referencing_variable (line 239) | pub fn register_scope_referencing_variable(&mut self, scope_index: Sco...
method update_value (line 249) | pub fn update_value(&mut self, original_scope_index: ScopeIndex, updat...
method notify_value_changed (line 267) | pub fn notify_value_changed(&mut self, scope_index: ScopeIndex, update...
method call_listeners_in_scope (line 291) | fn call_listeners_in_scope(&mut self, scope_index: ScopeIndex, updated...
method find_scope_with_variable (line 305) | pub fn find_scope_with_variable(&self, index: ScopeIndex, var_name: &V...
method lookup_variable_in_scope (line 315) | pub fn lookup_variable_in_scope(&self, index: ScopeIndex, var_name: &V...
method variables_used_in_self_or_subscopes_of (line 323) | pub fn variables_used_in_self_or_subscopes_of(&self, index: ScopeIndex...
method lookup_variables_in_scope (line 356) | pub fn lookup_variables_in_scope(&self, scope_index: ScopeIndex, vars:...
type ProvidedAttr (line 374) | pub struct ProvidedAttr {
type Inherits (line 382) | pub struct Inherits {
type ScopeGraphInternal (line 390) | pub struct ScopeGraphInternal {
method new (line 402) | pub fn new() -> Self {
method clear (line 411) | pub fn clear(&mut self) {
method add_scope (line 417) | pub fn add_scope(&mut self, scope: Scope) -> ScopeIndex {
method descendant_edges_of (line 427) | pub fn descendant_edges_of(&self, index: ScopeIndex) -> Vec<(ScopeInde...
method subscope_edges_of (line 431) | pub fn subscope_edges_of(&self, index: ScopeIndex) -> Vec<(ScopeIndex,...
method superscope_edge_of (line 435) | pub fn superscope_edge_of(&self, index: ScopeIndex) -> Option<&(ScopeI...
method remove_scope (line 439) | pub fn remove_scope(&mut self, index: ScopeIndex) {
method add_inheritance_relation (line 450) | pub fn add_inheritance_relation(&mut self, a: ScopeIndex, b: ScopeInde...
method register_scope_provides_attr (line 455) | pub fn register_scope_provides_attr(&mut self, a: ScopeIndex, b: Scope...
method scope_at (line 466) | pub fn scope_at(&self, index: ScopeIndex) -> Option<&Scope> {
method scope_at_mut (line 470) | pub fn scope_at_mut(&mut self, index: ScopeIndex) -> Option<&mut Scope> {
method subscopes_referencing (line 475) | pub fn subscopes_referencing(&self, index: ScopeIndex, var_name: &VarN...
method superscope_of (line 484) | pub fn superscope_of(&self, index: ScopeIndex) -> Option<ScopeIndex> {
method scopes_getting_attr_using (line 489) | pub fn scopes_getting_attr_using(&self, index: ScopeIndex, var_name: &...
method add_reference_to_inherits_edge (line 500) | pub fn add_reference_to_inherits_edge(&mut self, subscope: ScopeIndex,...
method validate (line 510) | pub fn validate(&self) -> Result<()> {
method visualize (line 549) | pub fn visualize(&self) -> String {
function test_nested_inheritance (line 612) | fn test_nested_inheritance() {
function test_lookup_variable_in_scope (line 632) | fn test_lookup_variable_in_scope() {
function test_variables_used_in_self_or_subscopes_of (line 686) | fn test_variables_used_in_self_or_subscopes_of() {
FILE: crates/eww/src/state/test.rs
function create_fn_verificator (line 13) | pub fn create_fn_verificator() -> (Arc<AtomicBool>, Box<dyn Fn()>) {
function test_delete_scope (line 40) | pub fn test_delete_scope() {
function test_state_updates (line 78) | fn test_state_updates() {
FILE: crates/eww/src/util.rs
function list_difference (line 58) | pub fn list_difference<'a, 'b, T: PartialEq>(a: &'a [T], b: &'b [T]) -> ...
method is_blank (line 79) | fn is_blank(self) -> bool {
method trim_lines (line 84) | fn trim_lines(self) -> String {
type IterAverage (line 89) | pub trait IterAverage {
method avg (line 90) | fn avg(self) -> f32;
method avg (line 94) | fn avg(self) -> f32 {
function replace_env_var_references (line 108) | pub fn replace_env_var_references(input: String) -> String {
function unindent (line 114) | pub fn unindent(text: &str) -> String {
function test_replace_env_var_references (line 139) | fn test_replace_env_var_references() {
function test_unindent (line 149) | fn test_unindent() {
FILE: crates/eww/src/widgets/build_widget.rs
type BuilderArgs (line 34) | pub struct BuilderArgs<'a> {
function build_gtk_widget (line 49) | pub fn build_gtk_widget(
function build_basic_gtk_widget (line 68) | fn build_basic_gtk_widget(
function build_builtin_gtk_widget (line 123) | fn build_builtin_gtk_widget(
function populate_widget_children (line 178) | fn populate_widget_children(
function build_loop_special_widget (line 218) | fn build_loop_special_widget(
function build_children_special_widget (line 283) | fn build_children_special_widget(
type CustomWidgetInvocation (line 344) | pub struct CustomWidgetInvocation {
function validate_container_children_count (line 352) | fn validate_container_children_count(container: >k::Container, widget_...
FILE: crates/eww/src/widgets/circular_progressbar.rs
type CircProgPriv (line 15) | pub struct CircProgPriv {
method default (line 33) | fn default() -> Self {
method properties (line 45) | fn properties() -> &'static [glib::ParamSpec] {
method set_property (line 49) | fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::Pa...
method property (line 68) | fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
type ParentType (line 75) | type ParentType = gtk::Bin;
type Type (line 76) | type Type = CircProg;
constant NAME (line 78) | const NAME: &'static str = "CircProg";
method class_init (line 80) | fn class_init(klass: &mut Self::Class) {
method default (line 86) | fn default() -> Self {
method new (line 92) | pub fn new() -> Self {
method add (line 98) | fn add(&self, widget: >k::Widget) {
function calc_widget_lowest_preferred_dimension (line 109) | fn calc_widget_lowest_preferred_dimension(widget: >k::Widget) -> (i32,...
method preferred_width (line 122) | fn preferred_width(&self) -> (i32, i32) {
method preferred_width_for_height (line 135) | fn preferred_width_for_height(&self, _height: i32) -> (i32, i32) {
method preferred_height (line 139) | fn preferred_height(&self) -> (i32, i32) {
method preferred_height_for_width (line 152) | fn preferred_height_for_width(&self, _width: i32) -> (i32, i32) {
method draw (line 156) | fn draw(&self, cr: &cairo::Context) -> glib::Propagation {
function perc_to_rad (line 232) | fn perc_to_rad(n: f64) -> f64 {
FILE: crates/eww/src/widgets/graph.rs
type GraphPriv (line 19) | pub struct GraphPriv {
method update_history (line 75) | fn update_history(&self, v: (std::time::Instant, f64)) {
method value_to_point (line 94) | fn value_to_point(&self, width: f64, height: f64, x: f64, y: f64) -> (...
method default (line 54) | fn default() -> Self {
method properties (line 103) | fn properties() -> &'static [glib::ParamSpec] {
method set_property (line 107) | fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::Pa...
method property (line 146) | fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
type ParentType (line 153) | type ParentType = gtk::Bin;
type Type (line 154) | type Type = Graph;
constant NAME (line 156) | const NAME: &'static str = "Graph";
method class_init (line 158) | fn class_init(klass: &mut Self::Class) {
method default (line 164) | fn default() -> Self {
method new (line 170) | pub fn new() -> Self {
method add (line 176) | fn add(&self, _widget: >k::Widget) {
method preferred_width (line 183) | fn preferred_width(&self) -> (i32, i32) {
method preferred_width_for_height (line 188) | fn preferred_width_for_height(&self, height: i32) -> (i32, i32) {
method preferred_height (line 192) | fn preferred_height(&self) -> (i32, i32) {
method preferred_height_for_width (line 197) | fn preferred_height_for_width(&self, width: i32) -> (i32, i32) {
method draw (line 201) | fn draw(&self, cr: &cairo::Context) -> glib::Propagation {
function apply_line_style (line 308) | fn apply_line_style(style: &str, cr: &cairo::Context) -> Result<()> {
FILE: crates/eww/src/widgets/mod.rs
function run_command (line 16) | fn run_command<T>(timeout: std::time::Duration, cmd: &str, args: &[T])
function replace_placeholders (line 44) | fn replace_placeholders<T>(cmd: &str, args: &[T]) -> String
function test_replace_placeholders (line 60) | fn test_replace_placeholders() {
FILE: crates/eww/src/widgets/systray.rs
type DBusSession (line 12) | struct DBusSession {
function dbus_session (line 16) | async fn dbus_session() -> zbus::Result<&'static DBusSession> {
function run_async_task (line 32) | fn run_async_task<F: Future>(f: F) -> F::Output {
type Props (line 37) | pub struct Props {
method new (line 43) | pub fn new() -> Self {
method icon_size (line 48) | pub fn icon_size(&self, value: i32) {
type Tray (line 60) | struct Tray {
method add_item (line 97) | fn add_item(&mut self, id: &str, item: notifier_host::Item) {
method remove_item (line 109) | fn remove_item(&mut self, id: &str) {
function spawn_systray (line 68) | pub fn spawn_systray(container: >k::Box, props: &Props) {
type Item (line 120) | struct Item {
method new (line 137) | fn new(id: String, item: notifier_host::Item, icon_size: tokio::sync::...
method maintain (line 166) | async fn maintain(
method drop (line 129) | fn drop(&mut self) {
function load_icon_for_item (line 271) | async fn load_icon_for_item(icon: >k::Image, item: ¬ifier_host::Ite...
FILE: crates/eww/src/widgets/transform.rs
type TransformPriv (line 16) | pub struct TransformPriv {
method default (line 43) | fn default() -> Self {
method properties (line 58) | fn properties() -> &'static [glib::ParamSpec] {
method set_property (line 62) | fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::Pa...
method property (line 96) | fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
type ParentType (line 103) | type ParentType = gtk::Bin;
type Type (line 104) | type Type = Transform;
constant NAME (line 106) | const NAME: &'static str = "Transform";
method class_init (line 108) | fn class_init(klass: &mut Self::Class) {
method default (line 114) | fn default() -> Self {
method new (line 120) | pub fn new() -> Self {
method add (line 126) | fn add(&self, widget: >k::Widget) {
method draw (line 139) | fn draw(&self, cr: >k::cairo::Context) -> glib::Propagation {
function perc_to_rad (line 198) | fn perc_to_rad(n: f64) -> f64 {
FILE: crates/eww/src/widgets/widget_definitions.rs
constant BUILTIN_WIDGET_NAMES (line 61) | pub const BUILTIN_WIDGET_NAMES: &[&str] = &[
function widget_use_to_gtk_widget (line 90) | pub(super) fn widget_use_to_gtk_widget(bargs: &mut BuilderArgs) -> Resul...
function resolve_widget_attrs (line 135) | pub(super) fn resolve_widget_attrs(bargs: &mut BuilderArgs, gtk_widget: ...
function resolve_range_attrs (line 229) | pub(super) fn resolve_range_attrs(bargs: &mut BuilderArgs, gtk_widget: &...
function resolve_orientable_attrs (line 279) | pub(super) fn resolve_orientable_attrs(bargs: &mut BuilderArgs, gtk_widg...
constant WIDGET_NAME_COMBO_BOX_TEXT (line 289) | const WIDGET_NAME_COMBO_BOX_TEXT: &str = "combo-box-text";
function build_gtk_combo_box_text (line 292) | fn build_gtk_combo_box_text(bargs: &mut BuilderArgs) -> Result<gtk::Comb...
constant WIDGET_NAME_EXPANDER (line 313) | const WIDGET_NAME_EXPANDER: &str = "expander";
function build_gtk_expander (line 317) | fn build_gtk_expander(bargs: &mut BuilderArgs) -> Result<gtk::Expander> {
constant WIDGET_NAME_REVEALER (line 362) | const WIDGET_NAME_REVEALER: &str = "revealer";
function build_gtk_revealer (line 365) | fn build_gtk_revealer(bargs: &mut BuilderArgs) -> Result<gtk::Revealer> {
constant WIDGET_NAME_CHECKBOX (line 378) | const WIDGET_NAME_CHECKBOX: &str = "checkbox";
function build_gtk_checkbox (line 381) | fn build_gtk_checkbox(bargs: &mut BuilderArgs) -> Result<gtk::CheckButto...
constant WIDGET_NAME_COLOR_BUTTON (line 399) | const WIDGET_NAME_COLOR_BUTTON: &str = "color-button";
function build_gtk_color_button (line 402) | fn build_gtk_color_button(bargs: &mut BuilderArgs) -> Result<gtk::ColorB...
constant WIDGET_NAME_COLOR_CHOOSER (line 420) | const WIDGET_NAME_COLOR_CHOOSER: &str = "color-chooser";
function build_gtk_color_chooser (line 423) | fn build_gtk_color_chooser(bargs: &mut BuilderArgs) -> Result<gtk::Color...
constant WIDGET_NAME_SCALE (line 441) | const WIDGET_NAME_SCALE: &str = "scale";
function build_gtk_scale (line 444) | fn build_gtk_scale(bargs: &mut BuilderArgs) -> Result<gtk::Scale> {
constant WIDGET_NAME_PROGRESS (line 472) | const WIDGET_NAME_PROGRESS: &str = "progress";
function build_gtk_progress (line 475) | fn build_gtk_progress(bargs: &mut BuilderArgs) -> Result<gtk::ProgressBa...
constant WIDGET_NAME_INPUT (line 491) | const WIDGET_NAME_INPUT: &str = "input";
function build_gtk_input (line 494) | fn build_gtk_input(bargs: &mut BuilderArgs) -> Result<gtk::Entry> {
constant WIDGET_NAME_BUTTON (line 523) | const WIDGET_NAME_BUTTON: &str = "button";
function build_gtk_button (line 526) | fn build_gtk_button(bargs: &mut BuilderArgs) -> Result<gtk::Button> {
function parse_icon_size (line 574) | fn parse_icon_size(o: &str) -> Result<gtk::IconSize> {
constant WIDGET_NAME_IMAGE (line 585) | const WIDGET_NAME_IMAGE: &str = "image";
function build_gtk_image (line 588) | fn build_gtk_image(bargs: &mut BuilderArgs) -> Result<gtk::Image> {
constant WIDGET_NAME_BOX (line 635) | const WIDGET_NAME_BOX: &str = "box";
function build_gtk_box (line 638) | fn build_gtk_box(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
constant WIDGET_NAME_OVERLAY (line 651) | const WIDGET_NAME_OVERLAY: &str = "overlay";
function build_gtk_overlay (line 654) | fn build_gtk_overlay(bargs: &mut BuilderArgs) -> Result<gtk::Overlay> {
constant WIDGET_NAME_TOOLTIP (line 688) | const WIDGET_NAME_TOOLTIP: &str = "tooltip";
function build_tooltip (line 691) | fn build_tooltip(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
constant WIDGET_NAME_CENTERBOX (line 732) | const WIDGET_NAME_CENTERBOX: &str = "centerbox";
function build_center_box (line 735) | fn build_center_box(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
constant WIDGET_NAME_SCROLL (line 778) | const WIDGET_NAME_SCROLL: &str = "scroll";
function build_gtk_scrolledwindow (line 781) | fn build_gtk_scrolledwindow(bargs: &mut BuilderArgs) -> Result<gtk::Scro...
constant WIDGET_NAME_EVENTBOX (line 799) | const WIDGET_NAME_EVENTBOX: &str = "eventbox";
function build_gtk_event_box (line 802) | fn build_gtk_event_box(bargs: &mut BuilderArgs) -> Result<gtk::EventBox> {
constant WIDGET_NAME_LABEL (line 963) | const WIDGET_NAME_LABEL: &str = "label";
function build_gtk_label (line 966) | fn build_gtk_label(bargs: &mut BuilderArgs) -> Result<gtk::Label> {
constant WIDGET_NAME_LITERAL (line 1068) | const WIDGET_NAME_LITERAL: &str = "literal";
function build_gtk_literal (line 1071) | fn build_gtk_literal(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
constant WIDGET_NAME_CALENDAR (line 1119) | const WIDGET_NAME_CALENDAR: &str = "calendar";
function build_gtk_calendar (line 1122) | fn build_gtk_calendar(bargs: &mut BuilderArgs) -> Result<gtk::Calendar> {
constant WIDGET_NAME_STACK (line 1168) | const WIDGET_NAME_STACK: &str = "stack";
function build_gtk_stack (line 1171) | fn build_gtk_stack(bargs: &mut BuilderArgs) -> Result<gtk::Stack> {
constant WIDGET_NAME_TRANSFORM (line 1206) | const WIDGET_NAME_TRANSFORM: &str = "transform";
function build_transform (line 1209) | fn build_transform(bargs: &mut BuilderArgs) -> Result<Transform> {
constant WIDGET_NAME_CIRCULAR_PROGRESS (line 1230) | const WIDGET_NAME_CIRCULAR_PROGRESS: &str = "circular-progress";
function build_circular_progress_bar (line 1233) | fn build_circular_progress_bar(bargs: &mut BuilderArgs) -> Result<CircPr...
constant WIDGET_NAME_GRAPH (line 1248) | const WIDGET_NAME_GRAPH: &str = "graph";
function build_graph (line 1251) | fn build_graph(bargs: &mut BuilderArgs) -> Result<super::graph::Graph> {
constant WIDGET_NAME_SYSTRAY (line 1293) | const WIDGET_NAME_SYSTRAY: &str = "systray";
function build_systray (line 1296) | fn build_systray(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
function parse_orientation (line 1329) | fn parse_orientation(o: &str) -> Result<gtk::Orientation> {
type DragEntryType (line 1336) | enum DragEntryType {
function parse_dragtype (line 1342) | fn parse_dragtype(o: &str) -> Result<DragEntryType> {
function parse_revealer_transition (line 1350) | fn parse_revealer_transition(t: &str) -> Result<gtk::RevealerTransitionT...
function parse_stack_transition (line 1362) | fn parse_stack_transition(t: &str) -> Result<gtk::StackTransitionType> {
function parse_align (line 1374) | fn parse_align(o: &str) -> Result<gtk::Align> {
function parse_justification (line 1385) | fn parse_justification(j: &str) -> Result<gtk::Justification> {
function parse_position_type (line 1395) | fn parse_position_type(g: &str) -> Result<gtk::PositionType> {
function parse_gravity (line 1405) | fn parse_gravity(g: &str) -> Result<gtk::pango::Gravity> {
function parse_wrap_mode (line 1416) | fn parse_wrap_mode(w: &str) -> Result<gtk::pango::WrapMode> {
function connect_first_map (line 1425) | fn connect_first_map<W: IsA<gtk::Widget>, F: Fn(&W) + 'static>(widget: &...
FILE: crates/eww/src/widgets/window.rs
type WindowPriv (line 12) | pub struct WindowPriv {
method default (line 22) | fn default() -> Self {
type ParentType (line 29) | type ParentType = gtk::Window;
type Type (line 30) | type Type = Window;
constant NAME (line 32) | const NAME: &'static str = "WindowEww";
method default (line 36) | fn default() -> Self {
method new (line 42) | pub fn new(type_: gtk::WindowType, x_: i32, y_: i32) -> Self {
method properties (line 52) | fn properties() -> &'static [glib::ParamSpec] {
method property (line 56) | fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
FILE: crates/eww/src/window_arguments.rs
function parse_value_from_args (line 13) | fn parse_value_from_args<T: FromStr>(name: &str, args: &mut HashMap<VarN...
type WindowArguments (line 22) | pub struct WindowArguments {
method new_from_args (line 36) | pub fn new_from_args(id: String, config_name: String, mut args: HashMa...
method get_local_window_variables (line 56) | pub fn get_local_window_variables(&self, window_def: &WindowDefinition...
FILE: crates/eww/src/window_initiator.rs
type WindowInitiator (line 17) | pub struct WindowInitiator {
method new (line 28) | pub fn new(window_def: &WindowDefinition, args: &WindowArguments) -> R...
method get_scoped_vars (line 47) | pub fn get_scoped_vars(&self) -> HashMap<AttrName, DynVal> {
FILE: crates/eww_shared_util/src/locale.rs
function get_locale (line 7) | pub fn get_locale() -> Locale {
FILE: crates/eww_shared_util/src/span.rs
type Span (line 6) | pub struct Span(pub usize, pub usize, pub usize);
constant DUMMY (line 9) | pub const DUMMY: Span = Span(usize::MAX, usize::MAX, usize::MAX);
method point (line 11) | pub fn point(loc: usize, file_id: usize) -> Self {
method to (line 17) | pub fn to(mut self, other: Span) -> Self {
method ending_at (line 23) | pub fn ending_at(mut self, end: usize) -> Self {
method point_span (line 29) | pub fn point_span(mut self) -> Self {
method point_span_at_end (line 35) | pub fn point_span_at_end(mut self) -> Self {
method shifted (line 40) | pub fn shifted(mut self, n: isize) -> Self {
method new_relative (line 46) | pub fn new_relative(mut self, other_start: usize, other_end: usize) ->...
method is_dummy (line 52) | pub fn is_dummy(&self) -> bool {
method fmt (line 58) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
method fmt (line 68) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type Spanned (line 73) | pub trait Spanned {
method span (line 74) | fn span(&self) -> Span;
FILE: crates/eww_shared_util/src/wrappers.rs
type VarName (line 9) | pub struct VarName(pub String);
method borrow (line 12) | fn borrow(&self) -> &str {
method from (line 24) | fn from(s: &str) -> Self {
method from (line 30) | fn from(x: AttrName) -> Self {
type AttrName (line 39) | pub struct AttrName(pub String);
method to_attr_name_ref (line 18) | pub fn to_attr_name_ref(&self) -> &AttrName {
method to_var_name_ref (line 42) | pub fn to_var_name_ref(&self) -> &VarName {
method borrow (line 48) | fn borrow(&self) -> &str {
method from (line 54) | fn from(s: &str) -> Self {
method from (line 60) | fn from(x: VarName) -> Self {
FILE: crates/notifier_host/src/host.rs
type Host (line 6) | pub trait Host {
method add_item (line 9) | fn add_item(&mut self, id: &str, item: Item);
method remove_item (line 12) | fn remove_item(&mut self, id: &str);
function register_as_host (line 29) | pub async fn register_as_host(
function run_host (line 67) | pub async fn run_host(host: &mut dyn Host, snw: &proxy::StatusNotifierWa...
FILE: crates/notifier_host/src/icon.rs
type IconError (line 6) | enum IconError {
function fallback_icon (line 31) | async fn fallback_icon(size: i32, scale: i32) -> Option<gtk::gdk_pixbuf:...
function icon_from_pixmap (line 45) | fn icon_from_pixmap(width: i32, height: i32, mut data: Vec<u8>) -> gtk::...
function icon_from_pixmaps (line 73) | fn icon_from_pixmaps(pixmaps: Vec<(i32, i32, Vec<u8>)>, size: i32) -> Op...
function icon_from_name (line 100) | fn icon_from_name(
function load_icon_from_sni (line 124) | pub async fn load_icon_from_sni(
FILE: crates/notifier_host/src/item.rs
type Status (line 11) | pub enum Status {
type Err (line 28) | type Err = ParseStatusError;
method from_str (line 30) | fn from_str(s: &str) -> std::result::Result<Self, ParseStatusError> {
type ParseStatusError (line 25) | pub struct ParseStatusError;
type Item (line 44) | pub struct Item {
method from_address (line 58) | pub async fn from_address(con: &zbus::Connection, service: &str) -> zb...
method status (line 83) | pub async fn status(&self) -> zbus::Result<Status> {
method set_menu (line 91) | pub async fn set_menu(&mut self, widget: >k::EventBox) -> zbus::Resu...
method popup_menu (line 98) | pub async fn popup_menu(&self, event: >k::gdk::EventButton, x: i32, ...
method icon (line 108) | pub async fn icon(&self, size: i32, scale: i32) -> Option<gtk::gdk_pix...
type DBusNode (line 117) | struct DBusNode {
type DBusInterface (line 129) | struct DBusInterface {
function resolve_pathless_address (line 134) | async fn resolve_pathless_address(con: &zbus::Connection, service: &str,...
function join_to_path (line 167) | fn join_to_path(path: &str, name: String) -> String {
FILE: crates/notifier_host/src/lib.rs
constant WATCHER_BUS (line 48) | pub const WATCHER_BUS: &str = "org.kde.StatusNotifierWatcher";
constant WATCHER_OBJECT (line 49) | pub const WATCHER_OBJECT: &str = "/StatusNotifierWatcher";
constant ITEM_OBJECT (line 51) | pub const ITEM_OBJECT: &str = "/StatusNotifierItem";
FILE: crates/notifier_host/src/proxy/dbus_status_notifier_item.rs
type StatusNotifierItem (line 18) | trait StatusNotifierItem {
method activate (line 20) | fn activate(&self, x: i32, y: i32) -> zbus::Result<()>;
method context_menu (line 23) | fn context_menu(&self, x: i32, y: i32) -> zbus::Result<()>;
method scroll (line 26) | fn scroll(&self, delta: i32, orientation: &str) -> zbus::Result<()>;
method secondary_activate (line 29) | fn secondary_activate(&self, x: i32, y: i32) -> zbus::Result<()>;
method new_attention_icon (line 33) | fn new_attention_icon(&self) -> zbus::Result<()>;
method new_icon (line 37) | fn new_icon(&self) -> zbus::Result<()>;
method new_overlay_icon (line 41) | fn new_overlay_icon(&self) -> zbus::Result<()>;
method new_status (line 45) | fn new_status(&self, status: &str) -> zbus::Result<()>;
method new_title (line 49) | fn new_title(&self) -> zbus::Result<()>;
method new_tool_tip (line 53) | fn new_tool_tip(&self) -> zbus::Result<()>;
method attention_icon_name (line 57) | fn attention_icon_name(&self) -> zbus::Result<String>;
method attention_icon_pixmap (line 61) | fn attention_icon_pixmap(&self) -> zbus::Result<Vec<(i32, i32, Vec<u8>...
method attention_movie_name (line 65) | fn attention_movie_name(&self) -> zbus::Result<String>;
method category (line 69) | fn category(&self) -> zbus::Result<String>;
method icon_name (line 73) | fn icon_name(&self) -> zbus::Result<String>;
method icon_pixmap (line 77) | fn icon_pixmap(&self) -> zbus::Result<Vec<(i32, i32, Vec<u8>)>>;
method icon_theme_path (line 81) | fn icon_theme_path(&self) -> zbus::Result<String>;
method id (line 85) | fn id(&self) -> zbus::Result<String>;
method item_is_menu (line 89) | fn item_is_menu(&self) -> zbus::Result<bool>;
method menu (line 93) | fn menu(&self) -> zbus::Result<zbus::zvariant::OwnedObjectPath>;
method overlay_icon_name (line 97) | fn overlay_icon_name(&self) -> zbus::Result<String>;
method overlay_icon_pixmap (line 101) | fn overlay_icon_pixmap(&self) -> zbus::Result<Vec<(i32, i32, Vec<u8>)>>;
method status (line 105) | fn status(&self) -> zbus::Result<String>;
method title (line 109) | fn title(&self) -> zbus::Result<String>;
method tool_tip (line 113) | fn tool_tip(&self) -> zbus::Result<(String, Vec<(i32, i32, Vec<u8>)>)>;
FILE: crates/notifier_host/src/proxy/dbus_status_notifier_watcher.rs
type StatusNotifierWatcher (line 19) | trait StatusNotifierWatcher {
method register_status_notifier_host (line 21) | fn register_status_notifier_host(&self, service: &str) -> zbus::Result...
method register_status_notifier_item (line 24) | fn register_status_notifier_item(&self, service: &str) -> zbus::Result...
method status_notifier_host_registered (line 28) | fn status_notifier_host_registered(&self) -> zbus::Result<()>;
method status_notifier_host_unregistered (line 32) | fn status_notifier_host_unregistered(&self) -> zbus::Result<()>;
method status_notifier_item_registered (line 36) | fn status_notifier_item_registered(&self, service: &str) -> zbus::Resu...
method status_notifier_item_unregistered (line 40) | fn status_notifier_item_unregistered(&self, service: &str) -> zbus::Re...
method is_status_notifier_host_registered (line 44) | fn is_status_notifier_host_registered(&self) -> zbus::Result<bool>;
method protocol_version (line 48) | fn protocol_version(&self) -> zbus::Result<i32>;
method registered_status_notifier_items (line 52) | fn registered_status_notifier_items(&self) -> zbus::Result<Vec<String>>;
FILE: crates/notifier_host/src/watcher.rs
type Watcher (line 11) | pub struct Watcher {
method register_status_notifier_host (line 29) | async fn register_status_notifier_host(
method status_notifier_host_registered (line 93) | async fn status_notifier_host_registered(ctxt: &zbus::SignalContext<'_...
method status_notifier_host_unregistered (line 97) | async fn status_notifier_host_unregistered(ctxt: &zbus::SignalContext<...
method is_status_notifier_host_registered (line 101) | async fn is_status_notifier_host_registered(&self) -> bool {
method register_status_notifier_item (line 109) | async fn register_status_notifier_item(
method status_notifier_item_registered (line 163) | async fn status_notifier_item_registered(ctxt: &zbus::SignalContext<'_...
method status_notifier_item_unregistered (line 167) | async fn status_notifier_item_unregistered(ctxt: &zbus::SignalContext<...
method registered_status_notifier_items (line 171) | async fn registered_status_notifier_items(&self) -> Vec<String> {
method protocol_version (line 180) | fn protocol_version(&self) -> i32 {
method new (line 187) | pub fn new() -> Watcher {
method attach_to (line 192) | pub async fn attach_to(self, con: &zbus::Connection) -> zbus::Result<(...
method is_status_notifier_host_registered_refresh (line 211) | async fn is_status_notifier_host_registered_refresh(ctxt: &zbus::Signa...
method registered_status_notifier_items_refresh (line 222) | async fn registered_status_notifier_items_refresh(ctxt: &zbus::SignalC...
function parse_service (line 240) | async fn parse_service<'a>(
function wait_for_service_exit (line 282) | async fn wait_for_service_exit(con: &zbus::Connection, service: zbus::na...
FILE: crates/simplexpr/build.rs
function main (line 2) | fn main() {
FILE: crates/simplexpr/src/ast.rs
type BinOp (line 10) | pub enum BinOp {
type UnaryOp (line 29) | pub enum UnaryOp {
type AccessType (line 38) | pub enum AccessType {
type SimplExpr (line 44) | pub enum SimplExpr {
method fmt (line 58) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
method literal (line 88) | pub fn literal(span: Span, s: String) -> Self {
method synth_string (line 93) | pub fn synth_string(s: impl Into<String>) -> Self {
method synth_literal (line 98) | pub fn synth_literal<T: Into<DynVal>>(s: T) -> Self {
method var_ref (line 102) | pub fn var_ref(span: Span, n: impl Into<VarName>) -> Self {
method references_var (line 106) | pub fn references_var(&self, var: &VarName) -> bool {
method collect_var_refs_into (line 119) | pub fn collect_var_refs_into(&self, dest: &mut Vec<VarName>) {
method collect_var_refs (line 142) | pub fn collect_var_refs(&self) -> Vec<VarName> {
method fmt (line 167) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
method span (line 150) | fn span(&self) -> Span {
FILE: crates/simplexpr/src/dynval.rs
type Result (line 6) | pub type Result<T> = std::result::Result<T, ConversionError>;
type ConversionError (line 10) | pub struct ConversionError {
method new (line 21) | pub fn new(value: DynVal, target_type: &'static str, source: impl std:...
type DurationParseError (line 18) | pub struct DurationParseError;
method span (line 26) | fn span(&self) -> Span {
type DynVal (line 32) | pub struct DynVal(pub String, pub Span);
method from (line 35) | fn from(s: String) -> Self {
method fmt (line 41) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
method fmt (line 46) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
method eq (line 53) | fn eq(&self, other: &Self) -> bool {
method from_iter (line 63) | fn from_iter<T: IntoIterator<Item = DynVal>>(iter: T) -> Self {
type Err (line 69) | type Err = ConversionError;
method from_str (line 73) | fn from_str(s: &str) -> Result<Self> {
type Error (line 102) | type Error = serde_json::Error;
method try_from (line 104) | fn try_from(value: serde_json::Value) -> std::result::Result<Self, Sel...
method from (line 110) | fn from(v: Vec<DynVal>) -> Self {
method from (line 118) | fn from(d: std::time::Duration) -> Self {
method from (line 124) | fn from(v: &serde_json::Value) -> Self {
method at (line 142) | pub fn at(mut self, span: Span) -> Self {
method at_if_dummy (line 147) | pub fn at_if_dummy(mut self, span: Span) -> Self {
method from_string (line 154) | pub fn from_string(s: String) -> Self {
method read_as (line 158) | pub fn read_as<E, T: FromDynVal<Err = E>>(&self) -> std::result::Resul...
method into_inner (line 162) | pub fn into_inner(self) -> String {
method as_string (line 167) | pub fn as_string(&self) -> Result<String> {
method as_f64 (line 171) | pub fn as_f64(&self) -> Result<f64> {
method as_i32 (line 175) | pub fn as_i32(&self) -> Result<i32> {
method as_i64 (line 179) | pub fn as_i64(&self) -> Result<i64> {
method as_bool (line 183) | pub fn as_bool(&self) -> Result<bool> {
method as_duration (line 187) | pub fn as_duration(&self) -> Result<std::time::Duration> {
method as_vec (line 215) | pub fn as_vec(&self) -> Result<Vec<String>> {
method as_json_value (line 240) | pub fn as_json_value(&self) -> Result<serde_json::Value> {
method as_json_array (line 245) | pub fn as_json_array(&self) -> Result<Vec<serde_json::Value>> {
method as_json_object (line 253) | pub fn as_json_object(&self) -> Result<serde_json::Map<String, serde_j...
type FromDynVal (line 78) | pub trait FromDynVal: Sized {
method from_dynval (line 80) | fn from_dynval(x: &DynVal) -> std::result::Result<Self, Self::Err>;
type Err (line 84) | type Err = E;
method from_dynval (line 86) | fn from_dynval(x: &DynVal) -> std::result::Result<Self, Self::Err> {
method span (line 136) | fn span(&self) -> Span {
function test_parse_vec (line 266) | fn test_parse_vec() {
function test_parse_duration (line 278) | fn test_parse_duration() {
FILE: crates/simplexpr/src/error.rs
type ParseError (line 6) | pub struct ParseError {
method from_parse_error (line 12) | pub fn from_parse_error(file_id: usize, err: lalrpop_util::ParseError<...
method span (line 18) | fn span(&self) -> Span {
FILE: crates/simplexpr/src/eval.rs
type JaqParseError (line 20) | pub struct JaqParseError(pub Option<jaq_parse::Error>);
method fmt (line 22) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type EvalError (line 31) | pub enum EvalError {
method at (line 75) | pub fn at(self, span: Span) -> Self {
method map_in_span (line 82) | pub fn map_in_span(self, f: impl FnOnce(Self) -> Self) -> Self {
method span (line 91) | fn span(&self) -> Span {
method try_map_var_refs (line 103) | pub fn try_map_var_refs<E, F: Fn(Span, VarName) -> Result<SimplExpr, E> ...
method map_var_refs (line 133) | pub fn map_var_refs(self, f: impl Fn(Span, VarName) -> SimplExpr) -> Self {
method resolve_one_level (line 140) | pub fn resolve_one_level(self, variables: &HashMap<VarName, SimplExpr>) ...
method resolve_refs (line 145) | pub fn resolve_refs(self, variables: &HashMap<VarName, DynVal>) -> Resul...
method var_refs_with_span (line 156) | pub fn var_refs_with_span(&self) -> Vec<(Span, &VarName)> {
method eval_no_vars (line 182) | pub fn eval_no_vars(&self) -> Result<DynVal, EvalError> {
method eval (line 192) | pub fn eval(&self, values: &HashMap<VarName, DynVal>) -> Result<DynVal, ...
function call_expr_function (line 319) | fn call_expr_function(name: &str, args: Vec<DynVal>) -> Result<DynVal, E...
function prepare_jaq_filter (line 575) | fn prepare_jaq_filter(code: String) -> Result<Arc<jaq_interpret::Filter>...
function run_jaq_function (line 593) | fn run_jaq_function(json: serde_json::Value, code: String, args: &str) -...
FILE: crates/simplexpr/src/parser/lalrpop_helpers.rs
function b (line 7) | pub fn b<T>(x: T) -> Box<T> {
function parse_stringlit (line 11) | pub fn parse_stringlit(
FILE: crates/simplexpr/src/parser/lexer.rs
type Sp (line 5) | pub type Sp<T> = (usize, T, usize);
type StrLitSegment (line 8) | pub enum StrLitSegment {
type Token (line 14) | pub enum Token {
type Lexer (line 117) | pub struct Lexer<'s> {
function new (line 126) | pub fn new(file_id: usize, span_offset: usize, source: &'s str) -> Self {
function remaining (line 130) | fn remaining(&self) -> &'s str {
function next_token (line 134) | pub fn next_token(&mut self) -> Option<Result<Sp<Token>, LexicalError>> {
function advance_by (line 176) | fn advance_by(&mut self, n: usize) -> Option<()> {
function advance_until_one_of (line 187) | fn advance_until_one_of<'a>(&mut self, pat: &[&'a str]) -> Option<&'a st...
function advance_until_unescaped_one_of (line 201) | fn advance_until_unescaped_one_of<'a>(&mut self, pat: &[&'a str]) -> Opt...
function string_lit (line 213) | pub fn string_lit(&mut self) -> Option<Result<Sp<Vec<Sp<StrLitSegment>>>...
type Item (line 269) | type Item = Result<Sp<Token>, LexicalError>;
method next (line 271) | fn next(&mut self) -> Option<Self::Item> {
type LexicalError (line 277) | pub struct LexicalError(pub Span);
method fmt (line 286) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
method span (line 280) | fn span(&self) -> Span {
FILE: crates/simplexpr/src/parser/mod.rs
function parse_string (line 6) | pub fn parse_string(byte_offset: usize, file_id: usize, s: &str) -> Resu...
function test (line 27) | fn test() {
FILE: crates/yuck/build.rs
function main (line 2) | fn main() {
FILE: crates/yuck/src/ast_error.rs
type AstError (line 7) | pub enum AstError {
method span (line 48) | fn span(&self) -> Span {
method to_diagnostic (line 26) | fn to_diagnostic(&self) -> codespan_reporting::diagnostic::Diagnostic<us...
FILE: crates/yuck/src/config/attributes.rs
type AttrError (line 12) | pub enum AttrError {
method span (line 24) | fn span(&self) -> Span {
type AttrEntry (line 34) | pub struct AttrEntry {
method new (line 40) | pub fn new(key_span: Span, value: Ast) -> AttrEntry {
type Attributes (line 47) | pub struct Attributes {
method new (line 53) | pub fn new(span: Span, attrs: HashMap<AttrName, AttrEntry>) -> Self {
method ast_required (line 57) | pub fn ast_required<T: FromAst>(&mut self, key: &str) -> Result<T, Dia...
method ast_optional (line 65) | pub fn ast_optional<T: FromAst>(&mut self, key: &str) -> Result<Option...
method primitive_required (line 74) | pub fn primitive_required<T, E>(&mut self, key: &str) -> Result<T, Dia...
method primitive_optional (line 89) | pub fn primitive_optional<T, E>(&mut self, key: &str) -> Result<Option...
method get_unused (line 108) | pub fn get_unused(self) -> impl Iterator<Item = (Span, AttrName)> {
type AttrSpec (line 115) | pub struct AttrSpec {
method from_ast (line 122) | fn from_ast(e: Ast) -> DiagResult<Self> {
FILE: crates/yuck/src/config/backend_window_options.rs
type Error (line 23) | pub enum Error {
type BackendWindowOptionsDef (line 37) | pub struct BackendWindowOptionsDef {
method eval (line 43) | pub fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Resu...
method from_attrs (line 47) | pub fn from_attrs(attrs: &mut Attributes) -> DiagResult<Self> {
type BackendWindowOptions (line 69) | pub struct BackendWindowOptions {
type X11BackendWindowOptions (line 75) | pub struct X11BackendWindowOptions {
type X11BackendWindowOptionsDef (line 84) | pub struct X11BackendWindowOptionsDef {
method eval (line 92) | fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<X...
type WlBackendWindowOptions (line 113) | pub struct WlBackendWindowOptions {
type WlBackendWindowOptionsDef (line 121) | pub struct WlBackendWindowOptionsDef {
method eval (line 128) | fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<W...
function eval_opt_expr_as_bool (line 143) | fn eval_opt_expr_as_bool(
type WlWindowFocusable (line 155) | pub enum WlWindowFocusable {
type Err (line 162) | type Err = EnumParseError;
method from_str (line 164) | fn from_str(s: &str) -> Result<Self, Self::Err> {
type X11WindowType (line 178) | pub enum X11WindowType {
type Err (line 189) | type Err = EnumParseError;
method from_str (line 191) | fn from_str(s: &str) -> Result<Self, Self::Err> {
type Side (line 205) | pub enum Side {
type Err (line 214) | type Err = EnumParseError;
method from_str (line 216) | fn from_str(s: &str) -> Result<Side, Self::Err> {
type X11StrutDefinitionExpr (line 228) | pub struct X11StrutDefinitionExpr {
method eval (line 234) | fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<X...
constant ELEMENT_NAME (line 246) | const ELEMENT_NAME: &'static str = "struts";
method from_tail (line 248) | fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator...
type X11StrutDefinition (line 256) | pub struct X11StrutDefinition {
FILE: crates/yuck/src/config/file_provider.rs
type FilesError (line 6) | pub enum FilesError {
type YuckFileProvider (line 14) | pub trait YuckFileProvider {
method load_yuck_file (line 15) | fn load_yuck_file(&mut self, path: std::path::PathBuf) -> Result<(Span...
method load_yuck_str (line 16) | fn load_yuck_str(&mut self, name: String, content: String) -> Result<(...
method unload (line 17) | fn unload(&mut self, id: usize);
FILE: crates/yuck/src/config/monitor.rs
type MonitorIdentifier (line 12) | pub enum MonitorIdentifier {
method from_dynval (line 20) | pub fn from_dynval(val: &DynVal) -> Result<Self, ConversionError> {
method is_numeric (line 32) | pub fn is_numeric(&self) -> bool {
method fmt (line 49) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
type Err (line 60) | type Err = Infallible;
method from_str (line 62) | fn from_str(s: &str) -> Result<Self, Self::Err> {
method from (line 38) | fn from(val: &MonitorIdentifier) -> Self {
FILE: crates/yuck/src/config/script_var_definition.rs
type ScriptVarDefinition (line 11) | pub enum ScriptVarDefinition {
method name_span (line 17) | pub fn name_span(&self) -> Span {
method name (line 24) | pub fn name(&self) -> &VarName {
method command_span (line 31) | pub fn command_span(&self) -> Option<Span> {
type VarSource (line 43) | pub enum VarSource {
type PollScriptVar (line 51) | pub struct PollScriptVar {
constant ELEMENT_NAME (line 61) | const ELEMENT_NAME: &'static str = "defpoll";
method from_tail (line 63) | fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator...
type ListenScriptVar (line 90) | pub struct ListenScriptVar {
constant ELEMENT_NAME (line 98) | const ELEMENT_NAME: &'static str = "deflisten";
method from_tail (line 100) | fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator...
FILE: crates/yuck/src/config/toplevel.rs
type Include (line 37) | pub struct Include {
constant ELEMENT_NAME (line 43) | const ELEMENT_NAME: &'static str = "include";
method from_tail (line 45) | fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator...
type TopLevel (line 52) | pub enum TopLevel {
method from_ast (line 61) | fn from_ast(e: Ast) -> DiagResult<Self> {
type Config (line 88) | pub struct Config {
method append_toplevel (line 96) | fn append_toplevel(&mut self, files: &mut impl YuckFileProvider, tople...
method generate (line 140) | pub fn generate(files: &mut impl YuckFileProvider, elements: Vec<Ast>)...
method generate_from_main_file (line 153) | pub fn generate_from_main_file(files: &mut impl YuckFileProvider, path...
FILE: crates/yuck/src/config/validate.rs
type ValidationError (line 9) | pub enum ValidationError {
method span (line 26) | fn span(&self) -> Span {
function validate (line 35) | pub fn validate(config: &Config, additional_globals: Vec<VarName>) -> Re...
function validate_widget_definition (line 54) | pub fn validate_widget_definition(
function validate_variables_in_widget_use (line 67) | pub fn validate_variables_in_widget_use(
FILE: crates/yuck/src/config/var_definition.rs
type VarDefinition (line 10) | pub struct VarDefinition {
constant ELEMENT_NAME (line 17) | const ELEMENT_NAME: &'static str = "defvar";
method from_tail (line 19) | fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<...
FILE: crates/yuck/src/config/widget_definition.rs
type WidgetDefinition (line 16) | pub struct WidgetDefinition {
constant ELEMENT_NAME (line 25) | const ELEMENT_NAME: &'static str = "defwidget";
method from_tail (line 27) | fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<...
FILE: crates/yuck/src/config/widget_use.rs
type WidgetUse (line 18) | pub enum WidgetUse {
type LoopWidgetUse (line 25) | pub struct LoopWidgetUse {
type ChildrenWidgetUse (line 34) | pub struct ChildrenWidgetUse {
type BasicWidgetUse (line 40) | pub struct BasicWidgetUse {
method children_span (line 49) | pub fn children_span(&self) -> Span {
method from_iter (line 57) | fn from_iter<I: Iterator<Item = Ast>>(
constant ELEMENT_NAME (line 70) | const ELEMENT_NAME: &'static str = "for";
method from_tail (line 72) | fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<...
constant ELEMENT_NAME (line 95) | const ELEMENT_NAME: &'static str = "children";
method from_tail (line 97) | fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<...
method from_ast (line 106) | fn from_ast(e: Ast) -> DiagResult<Self> {
function label_from_simplexpr (line 122) | fn label_from_simplexpr(value: SimplExpr, span: Span) -> BasicWidgetUse {
FILE: crates/yuck/src/config/window_definition.rs
type WindowStackingConversionError (line 25) | pub enum WindowStackingConversionError {
type WindowDefinition (line 33) | pub struct WindowDefinition {
method eval_monitor (line 47) | pub fn eval_monitor(&self, local_variables: &HashMap<VarName, DynVal>)...
method eval_resizable (line 55) | pub fn eval_resizable(&self, local_variables: &HashMap<VarName, DynVal...
method eval_stacking (line 63) | pub fn eval_stacking(
constant ELEMENT_NAME (line 78) | const ELEMENT_NAME: &'static str = "defwindow";
method from_tail (line 80) | fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator...
type EnumParseError (line 97) | pub struct EnumParseError {
method fmt (line 102) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type WindowStacking (line 131) | pub enum WindowStacking {
type Err (line 140) | type Err = EnumParseError;
method from_str (line 142) | fn from_str(s: &str) -> Result<Self, Self::Err> {
FILE: crates/yuck/src/config/window_geometry.rs
type AnchorAlignment (line 21) | pub enum AnchorAlignment {
method from_x_alignment (line 32) | pub fn from_x_alignment(s: &str) -> Result<AnchorAlignment, EnumParseE...
method from_y_alignment (line 40) | pub fn from_y_alignment(s: &str) -> Result<AnchorAlignment, EnumParseE...
method alignment_to_coordinate (line 48) | pub fn alignment_to_coordinate(&self, size_inner: i32, size_container:...
type AnchorPoint (line 58) | pub struct AnchorPoint {
method fmt (line 64) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type Err (line 95) | type Err = AnchorPointParseError;
method from_str (line 97) | fn from_str(s: &str) -> Result<Self, Self::Err> {
type AnchorPointParseError (line 87) | pub enum AnchorPointParseError {
type CoordsDef (line 114) | pub struct CoordsDef {
method eval (line 130) | pub fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Resu...
type Error (line 120) | pub enum Error {
function convert_to_num_with_unit (line 138) | fn convert_to_num_with_unit(
type WindowGeometryDef (line 150) | pub struct WindowGeometryDef {
method eval (line 173) | pub fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Resu...
constant ELEMENT_NAME (line 157) | const ELEMENT_NAME: &'static str = "geometry";
method from_tail (line 159) | fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator...
type WindowGeometry (line 186) | pub struct WindowGeometry {
method override_if_given (line 193) | pub fn override_if_given(&self, anchor_point: Option<AnchorPoint>, off...
method fmt (line 203) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: crates/yuck/src/error.rs
type DiagResult (line 10) | pub type DiagResult<T> = Result<T, DiagError>;
type DiagError (line 14) | pub struct DiagError(pub diagnostic::Diagnostic<usize>);
method from (line 21) | fn from(x: T) -> Self {
method note (line 27) | pub fn note(self, note: &str) -> Self {
method from_parse_error (line 31) | pub fn from_parse_error(
function get_parse_error_span (line 39) | pub fn get_parse_error_span<T, E: Spanned>(file_id: usize, err: &lalrpop...
type DiagResultExt (line 50) | pub trait DiagResultExt<T> {
method note (line 51) | fn note(self, note: &str) -> DiagResult<T>;
function note (line 55) | fn note(self, note: &str) -> DiagResult<T> {
FILE: crates/yuck/src/format_diagnostic.rs
function span_to_primary_label (line 12) | pub fn span_to_primary_label(span: Span) -> Label<usize> {
function span_to_secondary_label (line 15) | pub fn span_to_secondary_label(span: Span) -> Label<usize> {
type DiagnosticExt (line 60) | pub trait DiagnosticExt: Sized {
method with_label (line 61) | fn with_label(self, label: Label<usize>) -> Self;
method with_note (line 62) | fn with_note(self, note: String) -> Self;
method with_label (line 66) | fn with_label(self, label: Label<usize>) -> Self {
method with_note (line 70) | fn with_note(self, note: String) -> Self {
type ToDiagnostic (line 75) | pub trait ToDiagnostic: std::fmt::Debug {
method to_diagnostic (line 76) | fn to_diagnostic(&self) -> Diagnostic<usize>;
method to_message (line 77) | fn to_message(&self) -> String {
method to_diagnostic (line 83) | fn to_diagnostic(&self) -> Diagnostic<usize> {
method to_diagnostic (line 89) | fn to_diagnostic(&self) -> Diagnostic<usize> {
method to_diagnostic (line 98) | fn to_diagnostic(&self) -> Diagnostic<usize> {
method to_diagnostic (line 110) | fn to_diagnostic(&self) -> Diagnostic<usize> {
method to_diagnostic (line 180) | fn to_diagnostic(&self) -> Diagnostic<usize> {
method to_diagnostic (line 186) | fn to_diagnostic(&self) -> Diagnostic<usize> {
method to_diagnostic (line 228) | fn to_diagnostic(&self) -> Diagnostic<usize> {
function variable_deprecation_note (line 154) | fn variable_deprecation_note(var_name: String) -> Option<String> {
function lalrpop_error_to_diagnostic (line 159) | pub fn lalrpop_error_to_diagnostic<T: std::fmt::Display, E: Spanned + To...
function generate_lexical_error_diagnostic (line 237) | fn generate_lexical_error_diagnostic(span: Span) -> Diagnostic<usize> {
FILE: crates/yuck/src/parser/ast.rs
type AstType (line 11) | pub enum AstType {
method fmt (line 25) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type Ast (line 34) | pub enum Ast {
method expr_type (line 76) | pub fn expr_type(&self) -> AstType {
method as_simplexpr (line 87) | pub fn as_simplexpr(&self) -> Result<SimplExpr, AstError> {
method try_ast_iter (line 97) | pub fn try_ast_iter(self) -> Result<AstIterator<impl Iterator<Item = A...
method fmt (line 105) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
method fmt (line 119) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
method span (line 125) | fn span(&self) -> Span {
FILE: crates/yuck/src/parser/ast_iterator.rs
type AstIterator (line 12) | pub struct AstIterator<I: Iterator<Item = Ast>> {
function expect_literal (line 44) | pub fn expect_literal(&mut self) -> Result<(Span, DynVal), AstError> {
function new (line 58) | pub fn new(span: Span, iter: I) -> Self {
function expect_any (line 62) | pub fn expect_any(&mut self) -> Result<Ast, AstError> {
function expect_simplexpr (line 66) | pub fn expect_simplexpr(&mut self) -> Result<(Span, SimplExpr), AstError> {
function expect_done (line 80) | pub fn expect_done(&mut self) -> Result<(), AstError> {
function expect_key_values (line 89) | pub fn expect_key_values(&mut self) -> Result<Attributes, AstError> {
function put_back (line 93) | pub fn put_back(&mut self, ast: Ast) -> Option<Ast> {
type Item (line 100) | type Item = Ast;
method next (line 102) | fn next(&mut self) -> Option<Self::Item> {
function parse_key_values (line 110) | fn parse_key_values(
FILE: crates/yuck/src/parser/from_ast.rs
type FromAst (line 7) | pub trait FromAst: Sized {
method from_ast (line 8) | fn from_ast(e: Ast) -> DiagResult<Self>;
method from_ast (line 12) | fn from_ast(e: Ast) -> DiagResult<Self> {
method from_ast (line 18) | fn from_ast(e: Ast) -> DiagResult<Self> {
method from_ast (line 31) | fn from_ast(e: Ast) -> DiagResult<Self> {
method from_ast (line 47) | fn from_ast(e: Ast) -> DiagResult<Self> {
type FromAstElementContent (line 25) | pub trait FromAstElementContent: Sized {
constant ELEMENT_NAME (line 26) | const ELEMENT_NAME: &'static str;
method from_tail (line 27) | fn from_tail<I: Iterator<Item = Ast>>(span: Span, iter: AstIterator<I>...
FILE: crates/yuck/src/parser/lexer.rs
type Token (line 8) | pub enum Token {
method fmt (line 24) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type Lexer (line 70) | pub struct Lexer {
method new (line 78) | pub fn new(file_id: usize, source: String) -> Self {
method string_lit (line 82) | fn string_lit(&mut self) -> Option<Result<(usize, Token, usize), parse...
method simplexpr (line 95) | fn simplexpr(&mut self) -> Option<Result<(usize, Token, usize), parse_...
method advance_until_char_boundary (line 126) | fn advance_until_char_boundary(&mut self) {
type Item (line 134) | type Item = Result<(usize, Token, usize), parse_error::ParseError>;
method next (line 136) | fn next(&mut self) -> Option<Self::Item> {
FILE: crates/yuck/src/parser/mod.rs
function parse_string (line 21) | pub fn parse_string(file_id: usize, s: &str) -> DiagResult<Ast> {
function parse_toplevel (line 28) | pub fn parse_toplevel(file_id: usize, s: String) -> DiagResult<(Span, Ve...
function require_single_toplevel (line 35) | pub fn require_single_toplevel(span: Span, mut asts: Vec<Ast>) -> DiagRe...
function test (line 67) | fn test() {
FILE: crates/yuck/src/parser/parse_error.rs
type ParseError (line 4) | pub enum ParseError {
method span (line 12) | fn span(&self) -> Span {
FILE: crates/yuck/src/value/coords.rs
type Error (line 8) | pub enum Error {
type NumWithUnit (line 18) | pub enum NumWithUnit {
method pixels_relative_to (line 29) | pub fn pixels_relative_to(&self, max: i32) -> i32 {
method perc_relative_to (line 36) | pub fn perc_relative_to(&self, max: i32) -> f32 {
type Err (line 45) | type Err = Error;
method from_str (line 47) | fn from_str(s: &str) -> Result<Self, Self::Err> {
type Coords (line 62) | pub struct Coords {
method fmt (line 79) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
method from_pixels (line 85) | pub fn from_pixels((x, y): (i32, i32)) -> Self {
method from_strs (line 90) | pub fn from_strs(x: &str, y: &str) -> Result<Coords, Error> {
method relative_to (line 95) | pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) {
type Err (line 68) | type Err = Error;
method from_str (line 70) | fn from_str(s: &str) -> Result<Self, Self::Err> {
function test_parse_num_with_unit (line 106) | fn test_parse_num_with_unit() {
function test_parse_coords (line 115) | fn test_parse_coords() {
FILE: docs/theme/book.js
function playground_text (line 7) | function playground_text(playground) {
function fetch_with_timeout (line 19) | function fetch_with_timeout(url, options, timeout = 6000) {
function handle_crate_list_update (line 43) | function handle_crate_list_update(playground_block, playground_crates) {
function update_play_button (line 70) | function update_play_button(pre_block, playground_crates) {
function run_rust_code (line 100) | function run_rust_code(code_block) {
function showThemes (line 285) | function showThemes() {
function hideThemes (line 291) | function hideThemes() {
function get_theme (line 297) | function get_theme() {
function set_theme (line 307) | function set_theme(theme, store = true) {
function showSidebar (line 423) | function showSidebar() {
function toggleSection (line 437) | function toggleSection(ev) {
function hideSidebar (line 445) | function hideSidebar() {
function initResize (line 478) | function initResize(e) {
function resize (line 483) | function resize(e) {
function stopResize (line 496) | function stopResize(e) {
function hideTooltip (line 562) | function hideTooltip(elem) {
function showTooltip (line 567) | function showTooltip(elem, msg) {
FILE: docs/theme/highlight.js
function t (line 6) | function t(e){
class i (line 12) | class i{constructor(e){
method constructor (line 12) | constructor(e){
method ignoreMatch (line 14) | ignoreMatch(){this.isMatchIgnored=!0}
function r (line 14) | function r(e){
function s (line 16) | function s(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t]
class a (line 18) | class a{constructor(e,t){
method constructor (line 18) | constructor(e,t){
method addText (line 19) | addText(e){
method openNode (line 20) | openNode(e){if(!o(e))return;let t=e.kind
method closeNode (line 24) | closeNode(e){
method value (line 25) | value(){return this.buffer}
method span (line 25) | span(e){
class l (line 26) | class l{constructor(){this.rootNode={
method constructor (line 26) | constructor(){this.rootNode={
method top (line 27) | get top(){
method root (line 28) | get root(){return this.rootNode}
method add (line 28) | add(e){
method openNode (line 29) | openNode(e){const t={kind:e,children:[]}
method closeNode (line 30) | closeNode(){
method closeAllNodes (line 31) | closeAllNodes(){
method toJSON (line 32) | toJSON(){return JSON.stringify(this.rootNode,null,4)}
method walk (line 33) | walk(e){return this.constructor._walk(e,this.rootNode)}
method _walk (line 33) | static _walk(e,t){
method _collapse (line 35) | static _collapse(e){
class c (line 37) | class c extends l{constructor(e){super(),this.options=e}
method constructor (line 37) | constructor(e){super(),this.options=e}
method addKeyword (line 38) | addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNo...
method addText (line 39) | addText(e){""!==e&&this.add(e)}
method addSublanguage (line 39) | addSublanguage(e,t){const n=e.root
method toHTML (line 40) | toHTML(){
method finalize (line 41) | finalize(){return!0}
function g (line 41) | function g(e){
function d (line 42) | function d(...e){
function u (line 43) | function u(...e){return"("+((e=>{
function h (line 46) | function h(e){
function p (line 49) | function p(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n
function R (line 81) | function R(e,t){
function j (line 82) | function j(e,t){
function A (line 83) | function A(e,t){
function I (line 86) | function I(e,t){
function B (line 87) | function B(e,t){
function T (line 90) | function T(e,t){
function P (line 98) | function P(e,t,n="keyword"){const i=Object.create(null)
function C (line 102) | function C(e,t){
function W (line 106) | function W(e,t,{key:n}){let i=0;const r=e[n],s={},o={}
function X (line 108) | function X(e){(e=>{
function G (line 121) | function G(e){
function Z (line 167) | function Z(e){
function d (line 174) | function d(e){
function u (line 175) | function u(e,t,n,i){let r="",s=""
function h (line 181) | function h(e,n,r,s){
function f (line 244) | function f(e,n){n=n||g.languages||Object.keys(t);const i=(e=>{
function p (line 251) | function p(e){let t=null;const n=(e=>{
function m (line 266) | function m(){
function E (line 268) | function E(e){return e=(e||"").toLowerCase(),t[e]||t[r[e]]}
function x (line 269) | function x(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{
function y (line 270) | function y(e){const t=E(e)
function w (line 271) | function w(e,t){const n=e;s.forEach((e=>{
function e (line 331) | function e(...e){
function e (line 399) | function e(e){
function n (line 400) | function n(e){return a("(?=",e,")")}
function a (line 401) | function a(...n){return n.map((n=>e(n))).join("")}
method constructor (line 18) | constructor(e,t){
method addText (line 19) | addText(e){
method openNode (line 20) | openNode(e){if(!o(e))return;let t=e.kind
method closeNode (line 24) | closeNode(e){
method value (line 25) | value(){return this.buffer}
method span (line 25) | span(e){
function s (line 401) | function s(...n){
function n (line 429) | function n(...n){
function e (line 483) | function e(...e){
function e (line 495) | function e(e){
function n (line 496) | function n(...e){return e.map((e=>{
function e (line 565) | function e(...e){
function o (line 594) | function o(e){return l("(?=",e,")")}
function l (line 594) | function l(...e){return e.map((e=>{
method constructor (line 26) | constructor(){this.rootNode={
method top (line 27) | get top(){
method root (line 28) | get root(){return this.rootNode}
method add (line 28) | add(e){
method openNode (line 29) | openNode(e){const t={kind:e,children:[]}
method closeNode (line 30) | closeNode(){
method closeAllNodes (line 31) | closeAllNodes(){
method toJSON (line 32) | toJSON(){return JSON.stringify(this.rootNode,null,4)}
method walk (line 33) | walk(e){return this.constructor._walk(e,this.rootNode)}
method _walk (line 33) | static _walk(e,t){
method _collapse (line 35) | static _collapse(e){
FILE: gen-docs.ts
type WidgetData (line 1) | interface WidgetData {
type Widget (line 7) | interface Widget {
type MagicVar (line 15) | interface MagicVar {
function parseMagicVariables (line 21) | function parseMagicVariables(data: string) {
function parseVars (line 68) | function parseVars(code: string): Record<string, string> {
function replaceTypeNames (line 84) | function replaceTypeNames(type: string) {
function parseDocs (line 98) | function parseDocs(code: string) {
function printDocs (line 166) | function printDocs(vars: Record<string, string>, docs: Record<string, Wi...
function printWidget (line 182) | function printWidget(widget: Widget) {
Condensed preview — 282 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (837K chars).
[
{
"path": ".editorconfig",
"chars": 131,
"preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitesp"
},
{
"path": ".github/FUNDING.yml",
"chars": 704,
"preview": "# These are supported funding model platforms\n\ngithub: [elkowar]\npatreon: # Replace with a single Patreon username\nopen_"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 1962,
"preview": "name: Bug report\ndescription: Report a bug you have encountered\ntitle: \"[BUG] \"\nlabels: bug\nbody:\n - type: checkboxes\n "
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yml",
"chars": 864,
"preview": "name: Feature request\ndescription: Suggest an idea for this project\ntitle: \"[FEATURE] \"\nlabels: [enhancement]\nbody:\n - "
},
{
"path": ".github/ISSUE_TEMPLATE/widget-request.yml",
"chars": 1140,
"preview": "name: Widget request\ndescription: Suggest an new Widget\ntitle: \"[WIDGET] \"\nlabels: [widget-request]\nbody:\n - type: text"
},
{
"path": ".github/pull_request_template.md",
"chars": 1052,
"preview": "Please follow this template, if applicable.\n\n## Description\n\nProvide a short description of the changes your PR introduc"
},
{
"path": ".github/workflows/build.yml",
"chars": 1167,
"preview": "name: build\n\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n\nenv:\n CARGO_TERM_COLOR: "
},
{
"path": ".github/workflows/gh-pages.yml",
"chars": 1231,
"preview": "name: Build and deploy Github pages\non:\n push:\n branches:\n - master\n paths:\n - \"d"
},
{
"path": ".gitignore",
"chars": 37,
"preview": "/target\n/**/target\n/result\n/result-*\n"
},
{
"path": "CHANGELOG.md",
"chars": 7486,
"preview": "# Changelog\n\nAll notable changes to eww will be listed here, starting at changes since version 0.2.0.\n\n## Unreleased\n\n##"
},
{
"path": "Cargo.toml",
"chars": 1673,
"preview": "[workspace]\nmembers = [\"crates/*\"]\nresolver = \"2\"\n\n[workspace.dependencies]\n\nsimplexpr = { version = \"0.1.0\", path = \"cr"
},
{
"path": "LICENSE",
"chars": 1074,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2020 ElKowar\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "README.md",
"chars": 3472,
"preview": "[](https://deps.rs/repo/github/elkowar/eww)\n\n# E"
},
{
"path": "YUCK_MIGRATION.md",
"chars": 1413,
"preview": "# Migrating to yuck\n\nYuck is the new configuration syntax used by eww.\nWhile the syntax has changed dramatically, the ge"
},
{
"path": "crates/eww/Cargo.toml",
"chars": 1670,
"preview": "[package]\nname = \"eww\"\nversion = \"0.6.0\"\nauthors = [\"elkowar <5300871+elkowar@users.noreply.github.com>\"]\ndescription = "
},
{
"path": "crates/eww/build.rs",
"chars": 643,
"preview": "use std::process::Command;\nfn main() {\n let output = Command::new(\"git\").args([\"rev-parse\", \"HEAD\"]).output();\n if"
},
{
"path": "crates/eww/src/app.rs",
"chars": 31620,
"preview": "use crate::{\n daemon_response::DaemonResponseSender,\n display_backend::DisplayBackend,\n error_handling_ctx,\n "
},
{
"path": "crates/eww/src/application_lifecycle.rs",
"chars": 1364,
"preview": "//! Module concerned with handling the global application lifecycle of eww.\n//! Currently, this only means handling appl"
},
{
"path": "crates/eww/src/client.rs",
"chars": 1713,
"preview": "use std::process::Stdio;\n\nuse crate::{\n daemon_response::DaemonResponse,\n opts::{self, ActionClientOnly},\n path"
},
{
"path": "crates/eww/src/config/eww_config.rs",
"chars": 4816,
"preview": "use anyhow::{bail, Context, Result};\nuse eww_shared_util::VarName;\nuse std::collections::HashMap;\nuse yuck::{\n config"
},
{
"path": "crates/eww/src/config/inbuilt.rs",
"chars": 4150,
"preview": "use std::collections::HashMap;\n\nuse simplexpr::{dynval::DynVal, SimplExpr};\nuse yuck::config::{\n script_var_definitio"
},
{
"path": "crates/eww/src/config/mod.rs",
"chars": 166,
"preview": "pub mod eww_config;\npub mod inbuilt;\npub mod script_var;\npub mod scss;\npub mod system_stats;\npub mod window_definition;\n"
},
{
"path": "crates/eww/src/config/script_var.rs",
"chars": 1847,
"preview": "use std::process::Command;\n\nuse anyhow::{anyhow, bail, Context, Result};\nuse codespan_reporting::diagnostic::Severity;\nu"
},
{
"path": "crates/eww/src/config/scss.rs",
"chars": 1591,
"preview": "use std::path::Path;\n\nuse anyhow::{anyhow, Context};\n\nuse crate::{error_handling_ctx, util::replace_env_var_references};"
},
{
"path": "crates/eww/src/config/system_stats.rs",
"chars": 10174,
"preview": "use crate::util::IterAverage;\nuse anyhow::{Context, Result};\nuse once_cell::sync::Lazy;\nuse std::{fs::read_to_string, sy"
},
{
"path": "crates/eww/src/config/window_definition.rs",
"chars": 1,
"preview": "\n"
},
{
"path": "crates/eww/src/daemon_response.rs",
"chars": 2557,
"preview": "//! Types to manage messages that notify the eww client over the result of a command\n//!\n//! Communcation between the da"
},
{
"path": "crates/eww/src/display_backend.rs",
"chars": 13132,
"preview": "use crate::{widgets::window::Window, window_initiator::WindowInitiator};\n\nuse gtk::gdk;\n\n#[cfg(feature = \"wayland\")]\npub"
},
{
"path": "crates/eww/src/error_handling_ctx.rs",
"chars": 2391,
"preview": "//! Disgusting global state.\n//! I hate this, but [buffet](https://github.com/buffet) told me that this is what I should"
},
{
"path": "crates/eww/src/file_database.rs",
"chars": 4397,
"preview": "use std::collections::HashMap;\n\nuse codespan_reporting::files::Files;\nuse eww_shared_util::Span;\nuse yuck::{\n config:"
},
{
"path": "crates/eww/src/geometry.rs",
"chars": 212,
"preview": "use derive_more::{Debug, *};\n\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Display)]\n#[display(\".x*.y:.width*.height\")]\np"
},
{
"path": "crates/eww/src/ipc_server.rs",
"chars": 2916,
"preview": "use crate::{app, opts};\nuse anyhow::{Context, Result};\nuse std::time::Duration;\nuse tokio::{\n io::{AsyncReadExt, Asyn"
},
{
"path": "crates/eww/src/main.rs",
"chars": 9102,
"preview": "#![allow(rustdoc::private_intra_doc_links)]\n\nextern crate gtk;\n#[cfg(feature = \"wayland\")]\nextern crate gtk_layer_shell "
},
{
"path": "crates/eww/src/opts.rs",
"chars": 11934,
"preview": "use anyhow::{Context, Result};\nuse clap::{Parser, Subcommand};\nuse eww_shared_util::VarName;\nuse serde::{Deserialize, Se"
},
{
"path": "crates/eww/src/paths.rs",
"chars": 3281,
"preview": "use std::{\n collections::hash_map::DefaultHasher,\n hash::{Hash, Hasher},\n path::{Path, PathBuf},\n};\n\nuse anyhow"
},
{
"path": "crates/eww/src/script_var_handler.rs",
"chars": 13703,
"preview": "use std::collections::HashMap;\n\nuse crate::{\n app,\n config::{create_script_var_failed_warn, script_var},\n};\nuse an"
},
{
"path": "crates/eww/src/server.rs",
"chars": 12490,
"preview": "use crate::{\n app::{self, App, DaemonCommand},\n config, daemon_response,\n display_backend::DisplayBackend,\n "
},
{
"path": "crates/eww/src/state/mod.rs",
"chars": 87,
"preview": "mod one_to_n_elements_map;\npub mod scope;\npub mod scope_graph;\n\n#[cfg(test)]\nmod test;\n"
},
{
"path": "crates/eww/src/state/one_to_n_elements_map.rs",
"chars": 5207,
"preview": "use anyhow::{bail, Result};\nuse std::collections::{HashMap, HashSet};\n\n/// A map that represents a structure of a 1-n re"
},
{
"path": "crates/eww/src/state/scope.rs",
"chars": 1504,
"preview": "use anyhow::Result;\nuse std::{collections::HashMap, rc::Rc};\n\nuse eww_shared_util::VarName;\nuse simplexpr::dynval::DynVa"
},
{
"path": "crates/eww/src/state/scope_graph.rs",
"chars": 33264,
"preview": "use std::{\n collections::{HashMap, HashSet},\n rc::Rc,\n};\n\nuse anyhow::{anyhow, bail, Context, Result};\nuse eww_sha"
},
{
"path": "crates/eww/src/state/test.rs",
"chars": 4932,
"preview": "use super::scope::Listener;\nuse std::sync::{\n atomic::{AtomicBool, Ordering},\n Arc,\n};\n\nuse eww_shared_util::{Span"
},
{
"path": "crates/eww/src/util.rs",
"chars": 4242,
"preview": "use extend::ext;\nuse itertools::Itertools;\nuse std::fmt::Write;\n\n#[macro_export]\nmacro_rules! print_result_err {\n ($c"
},
{
"path": "crates/eww/src/widgets/build_widget.rs",
"chars": 14955,
"preview": "use anyhow::{Context, Result};\nuse codespan_reporting::diagnostic::Severity;\nuse eww_shared_util::{AttrName, Spanned};\nu"
},
{
"path": "crates/eww/src/widgets/circular_progressbar.rs",
"chars": 8698,
"preview": "use anyhow::{anyhow, Result};\nuse gtk::glib::{self, object_subclass, prelude::*, wrapper, Properties};\nuse gtk::{cairo, "
},
{
"path": "crates/eww/src/widgets/def_widget_macro.rs",
"chars": 5974,
"preview": "#[macro_export]\nmacro_rules! def_widget {\n ($args:ident, $scope_graph:ident, $gtk_widget:ident, {\n $(\n "
},
{
"path": "crates/eww/src/widgets/graph.rs",
"chars": 11825,
"preview": "use std::{cell::RefCell, collections::VecDeque};\n// https://www.figuiere.net/technotes/notes/tn002/\n// https://github.co"
},
{
"path": "crates/eww/src/widgets/mod.rs",
"chars": 2584,
"preview": "use std::process::Command;\n\npub mod build_widget;\npub mod circular_progressbar;\npub mod def_widget_macro;\npub mod graph;"
},
{
"path": "crates/eww/src/widgets/systray.rs",
"chars": 10265,
"preview": "use crate::widgets::window::Window;\nuse futures::StreamExt;\nuse gtk::{\n cairo::Surface,\n gdk::{self, ffi::gdk_cair"
},
{
"path": "crates/eww/src/widgets/transform.rs",
"chars": 7193,
"preview": "use anyhow::{anyhow, Result};\nuse gtk::glib::{self, object_subclass, wrapper, Properties};\nuse gtk::{prelude::*, subclas"
},
{
"path": "crates/eww/src/widgets/widget_definitions.rs",
"chars": 67354,
"preview": "#![allow(clippy::option_map_unit_fn)]\nuse super::{build_widget::BuilderArgs, circular_progressbar::*, run_command, trans"
},
{
"path": "crates/eww/src/widgets/window.rs",
"chars": 1698,
"preview": "use gtk::glib::{self, object_subclass, wrapper, Properties};\nuse gtk::{prelude::*, subclass::prelude::*};\nuse std::cell:"
},
{
"path": "crates/eww/src/window_arguments.rs",
"chars": 3936,
"preview": "use anyhow::{bail, Context, Result};\nuse eww_shared_util::VarName;\nuse simplexpr::dynval::DynVal;\nuse std::{\n collect"
},
{
"path": "crates/eww/src/window_initiator.rs",
"chars": 1867,
"preview": "use anyhow::Result;\nuse eww_shared_util::{AttrName, VarName};\nuse simplexpr::dynval::DynVal;\nuse std::collections::HashM"
},
{
"path": "crates/eww_shared_util/Cargo.toml",
"chars": 440,
"preview": "[package]\nname = \"eww_shared_util\"\nversion = \"0.1.0\"\nauthors = [\"elkowar <5300871+elkowar@users.noreply.github.com>\"]\ned"
},
{
"path": "crates/eww_shared_util/src/lib.rs",
"chars": 853,
"preview": "pub mod locale;\npub mod span;\npub mod wrappers;\n\npub use locale::*;\npub use span::*;\npub use wrappers::*;\n\n#[macro_expor"
},
{
"path": "crates/eww_shared_util/src/locale.rs",
"chars": 545,
"preview": "use chrono::Locale;\nuse std::env::var;\n\n/// Returns the `Locale` enum based on the `LC_ALL`, `LC_TIME`, and `LANG` envir"
},
{
"path": "crates/eww_shared_util/src/span.rs",
"chars": 2009,
"preview": "/// A span is made up of\n/// - the start location\n/// - the end location\n/// - the file id\n#[derive(Eq, PartialEq, Clone"
},
{
"path": "crates/eww_shared_util/src/wrappers.rs",
"chars": 1349,
"preview": "use derive_more::{Debug, *};\nuse ref_cast::RefCast;\nuse serde::{Deserialize, Serialize};\n\n/// The name of a variable\n#[r"
},
{
"path": "crates/notifier_host/Cargo.toml",
"chars": 604,
"preview": "[package]\nname = \"notifier_host\"\nversion = \"0.1.0\"\nauthors = [\"elkowar <5300871+elkowar@users.noreply.github.com>\"]\nedit"
},
{
"path": "crates/notifier_host/src/host.rs",
"chars": 5333,
"preview": "use crate::*;\n\nuse zbus::export::ordered_stream::{self, OrderedStreamExt};\n\n/// Trait for system tray implementations, t"
},
{
"path": "crates/notifier_host/src/icon.rs",
"chars": 7836,
"preview": "use crate::*;\n\nuse gtk::{self, prelude::*};\n\n#[derive(thiserror::Error, Debug)]\nenum IconError {\n #[error(\"while fetc"
},
{
"path": "crates/notifier_host/src/item.rs",
"chars": 6429,
"preview": "use crate::*;\n\nuse gtk::{self, prelude::*};\nuse serde::Deserialize;\nuse zbus::fdo::IntrospectableProxy;\n\n/// Recognised "
},
{
"path": "crates/notifier_host/src/lib.rs",
"chars": 2185,
"preview": "//! The system tray side of the [notifier host DBus\n//! protocols](https://freedesktop.org/wiki/Specifications/StatusNot"
},
{
"path": "crates/notifier_host/src/proxy/dbus_menu.xml",
"chars": 2616,
"preview": "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"http://www.freedesktop.org/standards/dbu"
},
{
"path": "crates/notifier_host/src/proxy/dbus_status_notifier_item.rs",
"chars": 3444,
"preview": "//! # DBus interface proxy for: `org.kde.StatusNotifierItem`\n//!\n//! This code was generated by `zbus-xmlgen` `3.1.0` fr"
},
{
"path": "crates/notifier_host/src/proxy/dbus_status_notifier_item.xml",
"chars": 2083,
"preview": "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"http://www.freedesktop.org/standards/dbu"
},
{
"path": "crates/notifier_host/src/proxy/dbus_status_notifier_watcher.rs",
"chars": 1904,
"preview": "//! # DBus interface proxy for: `org.kde.StatusNotifierWatcher`\n//!\n//! This code was generated by `zbus-xmlgen` `3.1.0`"
},
{
"path": "crates/notifier_host/src/proxy/dbus_status_notifier_watcher.xml",
"chars": 1868,
"preview": "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" \"http://www.freedesktop.org/standards/dbu"
},
{
"path": "crates/notifier_host/src/proxy/mod.rs",
"chars": 978,
"preview": "//! Proxies for DBus services, so we can call them.\n//!\n//! The interface XML files were taken from\n//! [Waybar](https:/"
},
{
"path": "crates/notifier_host/src/watcher.rs",
"chars": 12014,
"preview": "use crate::names;\nuse zbus::{dbus_interface, export::ordered_stream::OrderedStreamExt, Interface};\n\n/// An instance of ["
},
{
"path": "crates/simplexpr/Cargo.toml",
"chars": 1064,
"preview": "[package]\nname = \"simplexpr\"\nversion = \"0.1.0\"\nauthors = [\"elkowar <5300871+elkowar@users.noreply.github.com>\"]\nedition "
},
{
"path": "crates/simplexpr/README.md",
"chars": 348,
"preview": "# simplexpr\n\nsimplexpr is a parser and interpreter for a simple expression syntax that can be embedded into other applic"
},
{
"path": "crates/simplexpr/build.rs",
"chars": 116,
"preview": "extern crate lalrpop;\nfn main() {\n lalrpop::Configuration::new().log_verbose().process_current_dir().unwrap();\n}\n"
},
{
"path": "crates/simplexpr/src/ast.rs",
"chars": 6598,
"preview": "use crate::dynval::DynVal;\nuse eww_shared_util::{Span, Spanned};\nuse itertools::Itertools;\nuse serde::{Deserialize, Seri"
},
{
"path": "crates/simplexpr/src/dynval.rs",
"chars": 10038,
"preview": "use eww_shared_util::{Span, Spanned};\nuse itertools::Itertools;\nuse serde::{Deserialize, Serialize};\nuse std::{convert::"
},
{
"path": "crates/simplexpr/src/error.rs",
"chars": 1338,
"preview": "use crate::parser::lexer::{self, LexicalError};\nuse eww_shared_util::{Span, Spanned};\n\n#[derive(thiserror::Error, Debug)"
},
{
"path": "crates/simplexpr/src/eval.rs",
"chars": 28985,
"preview": "use bytesize::ByteSize;\nuse cached::proc_macro::cached;\nuse chrono::{Local, LocalResult, TimeZone};\nuse itertools::Itert"
},
{
"path": "crates/simplexpr/src/lib.rs",
"chars": 231,
"preview": "pub mod ast;\npub mod dynval;\npub mod error;\npub mod eval;\npub mod parser;\n\npub use ast::SimplExpr;\n\nuse lalrpop_util::la"
},
{
"path": "crates/simplexpr/src/parser/lalrpop_helpers.rs",
"chars": 1503,
"preview": "use eww_shared_util::Span;\n\nuse crate::SimplExpr;\n\nuse super::lexer::{LexicalError, Sp, StrLitSegment, Token};\n\npub fn b"
},
{
"path": "crates/simplexpr/src/parser/lexer.rs",
"chars": 10117,
"preview": "use eww_shared_util::{Span, Spanned};\nuse once_cell::sync::Lazy;\nuse regex::{Regex, RegexSet};\n\npub type Sp<T> = (usize,"
},
{
"path": "crates/simplexpr/src/parser/mod.rs",
"chars": 1456,
"preview": "pub mod lalrpop_helpers;\npub mod lexer;\n\nuse crate::{ast::SimplExpr, error::ParseError};\n\npub fn parse_string(byte_offse"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__basic.snap",
"chars": 156,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"bar \\\"foo\\\"\\\"#)\"\n\n---\n(0, Ident(\"bar\"), 3)\n(4, Stri"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__comments.snap",
"chars": 107,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(\\\"foo ; bar\\\")\"\n\n---\n(0, Ident(\"foo\"), 3)\n"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__digit.snap",
"chars": 103,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"12\\\"#)\"\n\n---\n(0, NumLit(\"12\"), 2)\n"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__empty_interpolation.snap",
"chars": 188,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nassertion_line: 306\nexpression: \"v!(r#\\\"\\\"${}\\\"\\\"#)\"\n---\n(0, StringLit("
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__escaping.snap",
"chars": 145,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\" \\\"a\\\\\\\"b\\\\{}\\\" \\\"#)\"\n\n---\n(1, StringLit([(1, Liter"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__interpolation_1.snap",
"chars": 255,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\" \\\"foo ${2 * 2} bar\\\" \\\"#)\"\n\n---\n(1, StringLit([(1,"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__interpolation_nested.snap",
"chars": 472,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\" \\\"foo ${(2 * 2) + \\\"${5 + 5}\\\"} bar\\\" \\\"#)\"\n\n---\n("
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__json_in_interpolation.snap",
"chars": 271,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\" \\\"${ {1: 2} }\\\" \\\"#)\"\n\n---\n(1, StringLit([(1, Lite"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__number_in_ident.snap",
"chars": 116,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"foo_1_bar\\\"#)\"\n\n---\n(0, Ident(\"foo_1_bar\"), 9)\n"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__quote_backslash_eof.snap",
"chars": 104,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nassertion_line: 313\nexpression: \"v!(r#\\\"\\\"\\\\\\\"#)\"\n---\n\n"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__safe_interpolation.snap",
"chars": 526,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"\\\"${ { \\\"key\\\": \\\"value\\\" }.key1?.key2 ?: \\\"Recover"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__simplexpr_lexer_basic.snap",
"chars": 581,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"Lexer::new(0, 0, r#\\\"bar \\\"foo\\\"\\\"#).collect_vec()\"\n\n---\n["
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate-2.snap",
"chars": 4876,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"Lexer::new(0, 0, r#\\\" \\\"foo {(2 * 2) + \\\"{5 + 5}\\\"} bar\\\" "
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate-3.snap",
"chars": 468,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"Lexer::new(0, 0, r#\\\" \\\"a\\\\\\\"b\\\\{}\\\" \\\"#).collect_vec()\"\n\n"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate.snap",
"chars": 1630,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"Lexer::new(0, 0, r#\\\" \\\"foo {2 * 2} bar\\\" \\\"#).collect_vec"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__symbol_spam.snap",
"chars": 388,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"(foo + - \\\"()\\\" \\\"a\\\\\\\"b\\\" true false [] 12.2)\\\"#)\""
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__weird_char_boundaries.snap",
"chars": 187,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"\\\" \\\" + music\\\"#)\"\n\n---\n(0, StringLit([(0, Liter"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__weird_nesting.snap",
"chars": 444,
"preview": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"\\n \\\"${ {\\\"hi\\\": \\\"ho\\\"}.hi }\\\".hi\\n "
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-10.snap",
"chars": 147,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"\\\\\\\"foo\\\\\\\" + 12.4\\\"))\"\n\n---\nO"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-11.snap",
"chars": 135,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"hi[\\\\\\\"ho\\\\\\\"]\\\"))\"\n\n---\nOk(\n "
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-12.snap",
"chars": 141,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"foo.bar.baz\\\"))\"\n\n---\nOk(\n "
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-13.snap",
"chars": 187,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"foo.bar[2 + 2] * asdf[foo.bar]"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-14.snap",
"chars": 194,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, r#\\\"[1, 2, 3 + 4, \\\"bla\\\", [blub"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-15.snap",
"chars": 210,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, r#\\\"{ \\\"key\\\": \\\"value\\\", 5: 1+2"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-16.snap",
"chars": 203,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, r#\\\"{ \\\"key\\\": \\\"value\\\" }?.key?"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-2.snap",
"chars": 129,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"2 + 5\\\"))\"\n\n---\nOk(\n (\"2\" +"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-3.snap",
"chars": 165,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"2 * 5 + 1 * 1 + 3\\\"))\"\n\n---\nOk"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-4.snap",
"chars": 143,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"(1 + 2) * 2\\\"))\"\n\n---\nOk(\n "
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-5.snap",
"chars": 157,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"1 + true ? 2 : 5\\\"))\"\n\n---\nOk("
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-6.snap",
"chars": 169,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"1 + true ? 2 : 5 + 2\\\"))\"\n\n---"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-7.snap",
"chars": 171,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"1 + (true ? 2 : 5) + 2\\\"))\"\n\n-"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-8.snap",
"chars": 135,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"foo(1, 2)\\\"))\"\n\n---\nOk(\n fo"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-9.snap",
"chars": 151,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"! false || ! true\\\"))\"\n\n---\nOk"
},
{
"path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test.snap",
"chars": 117,
"preview": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"1\\\"))\"\n\n---\nOk(\n \"1\",\n)\n"
},
{
"path": "crates/simplexpr/src/simplexpr_parser.lalrpop",
"chars": 4934,
"preview": "use crate::ast::{SimplExpr::{self, *}, BinOp::*, UnaryOp::*, AccessType};\nuse eww_shared_util::{Span, VarName};\nuse crat"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-2.snap",
"chars": 111,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"1s\\\").as_duration()\"\n\n---\nOk(\n 1s,\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-3.snap",
"chars": 116,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"0.1s\\\").as_duration()\"\n\n---\nOk(\n 100ms,\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-4.snap",
"chars": 113,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"5m\\\").as_duration()\"\n\n---\nOk(\n 300s,\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-5.snap",
"chars": 115,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"5min\\\").as_duration()\"\n\n---\nOk(\n 300s,\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-6.snap",
"chars": 114,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"0.5m\\\").as_duration()\"\n\n---\nOk(\n 30s,\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-7.snap",
"chars": 114,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"1h\\\").as_duration()\"\n\n---\nOk(\n 3600s,\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-8.snap",
"chars": 116,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"0.5h\\\").as_duration()\"\n\n---\nOk(\n 1800s,\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration.snap",
"chars": 117,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"100ms\\\").as_duration()\"\n\n---\nOk(\n 100ms,\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-2.snap",
"chars": 146,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"[hi]\\\".to_string()).as_vec()\"\n\n---\nOk(\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-3.snap",
"chars": 180,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"[hi,ho,hu]\\\".to_string()).as_vec()\"\n\n---\nO"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-4.snap",
"chars": 156,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"[hi\\\\\\\\,ho]\\\".to_string()).as_vec()\"\n\n---\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-5.snap",
"chars": 173,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"[hi\\\\\\\\,ho,hu]\\\".to_string()).as_vec()\"\n\n-"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-6.snap",
"chars": 123,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"\\\".to_string()).as_vec()\"\n\n---\nOk(\n [],"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-7.snap",
"chars": 222,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"[a,b\\\".to_string()).as_vec()\"\n\n---\nErr(\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-8.snap",
"chars": 218,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"a]\\\".to_string()).as_vec()\"\n\n---\nErr(\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec.snap",
"chars": 142,
"preview": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"[]\\\".to_string()).as_vec()\"\n\n---\nOk(\n ["
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-10.snap",
"chars": 114,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"\\\\\\\"foo\\\\\\\" + 12.4\\\"))\"\n\n---\nOk(\n (\"foo\" + \"12.4\"),\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-11.snap",
"chars": 102,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"hi[\\\\\\\"ho\\\\\\\"]\\\"))\"\n\n---\nOk(\n hi[\"ho\"],\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-12.snap",
"chars": 108,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"foo.bar.baz\\\"))\"\n\n---\nOk(\n foo[\"bar\"][\"baz\"],\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-13.snap",
"chars": 154,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"foo.bar[2 + 2] * asdf[foo.bar]\\\"))\"\n\n---\nOk(\n (foo[\"bar\"][(\""
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-14.snap",
"chars": 519,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"1 + (true ? 2 : 5) + 2\\\"))\"\n\n---\nOk(\n BinOp(\n BinOp(\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-15.snap",
"chars": 826,
"preview": "---\nsource: src/lib.rs\nexpression: \"Lexer::new(\\\"foo(1, 2)\\\").filter_map(|x|\\n x.o"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-16.snap",
"chars": 258,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"foo(1, 2)\\\"))\"\n\n---\nOk(\n FunctionCall(\n \"foo\",\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-17.snap",
"chars": 1247,
"preview": "---\nsource: src/lib.rs\nexpression: \"Lexer::new(\\\"! false || ! true\\\").filter_map(|x|\\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-18.snap",
"chars": 332,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"! false || ! true\\\"))\"\n\n---\nOk(\n BinOp(\n UnaryOp(\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-19.snap",
"chars": 1189,
"preview": "---\nsource: src/lib.rs\nexpression: \"Lexer::new(\\\"\\\\\\\"foo\\\\\\\" + 12.4\\\").filter_map(|x|\\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-2.snap",
"chars": 96,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"2 + 5\\\"))\"\n\n---\nOk(\n (\"2\" + \"5\"),\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-20.snap",
"chars": 219,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"\\\\\\\"foo\\\\\\\" + 12.4\\\"))\"\n\n---\nOk(\n BinOp(\n Literal(\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-21.snap",
"chars": 885,
"preview": "---\nsource: src/lib.rs\nexpression: \"Lexer::new(\\\"hi[\\\\\\\"ho\\\\\\\"]\\\").filter_map(|x|\\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-22.snap",
"chars": 202,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"hi[\\\\\\\"ho\\\\\\\"]\\\"))\"\n\n---\nOk(\n JsonAccess(\n VarRef(\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-23.snap",
"chars": 957,
"preview": "---\nsource: src/lib.rs\nexpression: \"Lexer::new(\\\"foo.bar.baz\\\").filter_map(|x|\\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-24.snap",
"chars": 303,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"foo.bar.baz\\\"))\"\n\n---\nOk(\n JsonAccess(\n JsonAccess(\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-25.snap",
"chars": 1871,
"preview": "---\nsource: src/lib.rs\nexpression: \"Lexer::new(\\\"foo.bar[2 + 2] * asdf[foo.bar]\\\").filter_map(|x|\\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-26.snap",
"chars": 812,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"foo.bar[2 + 2] * asdf[foo.bar]\\\"))\"\n\n---\nOk(\n BinOp(\n "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-3.snap",
"chars": 132,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"2 * 5 + 1 * 1 + 3\\\"))\"\n\n---\nOk(\n (((\"2\" * \"5\") + (\"1\" * \"1\")"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-4.snap",
"chars": 110,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"(1 + 2) * 2\\\"))\"\n\n---\nOk(\n ((\"1\" + \"2\") * \"2\"),\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-5.snap",
"chars": 133,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"1 + true ? 2 : 5\\\"))\"\n\n---\nOk(\n (if (\"1\" + \"true\") then \"2\" "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-6.snap",
"chars": 145,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"1 + true ? 2 : 5 + 2\\\"))\"\n\n---\nOk(\n (if (\"1\" + \"true\") then "
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-7.snap",
"chars": 147,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"1 + (true ? 2 : 5) + 2\\\"))\"\n\n---\nOk(\n ((\"1\" + (if \"true\" the"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-8.snap",
"chars": 102,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"foo(1, 2)\\\"))\"\n\n---\nOk(\n foo(\"1\", \"2\"),\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-9.snap",
"chars": 118,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"! false || ! true\\\"))\"\n\n---\nOk(\n (!\"false\" || !\"true\"),\n)\n"
},
{
"path": "crates/simplexpr/src/snapshots/simplexpr__tests__test.snap",
"chars": 84,
"preview": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"1\\\"))\"\n\n---\nOk(\n \"1\",\n)\n"
},
{
"path": "crates/yuck/Cargo.toml",
"chars": 973,
"preview": "[package]\nname = \"yuck\"\nversion = \"0.1.0\"\nauthors = [\"elkowar <5300871+elkowar@users.noreply.github.com>\"]\nedition = \"20"
},
{
"path": "crates/yuck/build.rs",
"chars": 74,
"preview": "extern crate lalrpop;\nfn main() {\n lalrpop::process_root().unwrap();\n}\n"
},
{
"path": "crates/yuck/src/ast_error.rs",
"chars": 2182,
"preview": "use eww_shared_util::{AttrName, Span};\n\nuse crate::{format_diagnostic::ToDiagnostic, gen_diagnostic, parser::ast::AstTyp"
},
{
"path": "crates/yuck/src/config/attributes.rs",
"chars": 4165,
"preview": "use std::collections::HashMap;\n\nuse simplexpr::{dynval::FromDynVal, eval::EvalError, SimplExpr};\n\nuse crate::{\n error"
},
{
"path": "crates/yuck/src/config/backend_window_options.rs",
"chars": 8166,
"preview": "use std::{collections::HashMap, str::FromStr};\n\nuse anyhow::Result;\nuse simplexpr::{\n dynval::{DynVal, FromDynVal},\n "
},
{
"path": "crates/yuck/src/config/file_provider.rs",
"chars": 533,
"preview": "use eww_shared_util::Span;\n\nuse crate::{error::DiagError, parser::ast::Ast};\n\n#[derive(thiserror::Error, Debug)]\npub enu"
},
{
"path": "crates/yuck/src/config/mod.rs",
"chars": 304,
"preview": "pub mod attributes;\npub mod backend_window_options;\npub mod file_provider;\npub mod monitor;\npub mod script_var_definitio"
},
{
"path": "crates/yuck/src/config/monitor.rs",
"chars": 2297,
"preview": "use std::{\n convert::Infallible,\n fmt,\n str::{self, FromStr},\n};\n\nuse serde::{Deserialize, Serialize};\nuse simp"
},
{
"path": "crates/yuck/src/config/script_var_definition.rs",
"chars": 4031,
"preview": "use simplexpr::{dynval::DynVal, SimplExpr};\n\nuse crate::{\n error::{DiagError, DiagResult, DiagResultExt},\n format_"
},
{
"path": "crates/yuck/src/config/snapshots/eww_config__config__test__config.snap",
"chars": 2640,
"preview": "---\nsource: src/config/test.rs\nexpression: config.unwrap()\n\n---\nConfig(\n widget_definitions: {\n \"bar\": WidgetDefinit"
},
{
"path": "crates/yuck/src/config/snapshots/yuck__config__test__config.snap",
"chars": 4283,
"preview": "---\nsource: crates/yuck/src/config/test.rs\nexpression: config.unwrap()\n\n---\nConfig(\n widget_definitions: {\n \"bar\": W"
},
{
"path": "crates/yuck/src/config/toplevel.rs",
"chars": 6255,
"preview": "use std::{\n collections::HashMap,\n path::{Path, PathBuf},\n};\n\nuse itertools::Itertools;\n\nuse super::{\n file_pro"
},
{
"path": "crates/yuck/src/config/validate.rs",
"chars": 4875,
"preview": "use std::collections::{HashMap, HashSet};\n\nuse simplexpr::SimplExpr;\n\nuse super::{widget_definition::WidgetDefinition, w"
},
{
"path": "crates/yuck/src/config/var_definition.rs",
"chars": 911,
"preview": "use simplexpr::dynval::DynVal;\n\nuse crate::{\n error::{DiagResult, DiagResultExt},\n parser::{ast::Ast, ast_iterator"
},
{
"path": "crates/yuck/src/config/widget_definition.rs",
"chars": 2577,
"preview": "use crate::{\n error::{DiagError, DiagResult, DiagResultExt},\n format_diagnostic::ToDiagnostic,\n gen_diagnostic,"
},
{
"path": "crates/yuck/src/config/widget_use.rs",
"chars": 4818,
"preview": "use simplexpr::SimplExpr;\n\nuse crate::{\n config::attributes::AttrEntry,\n error::{DiagError, DiagResult, DiagResult"
},
{
"path": "crates/yuck/src/config/window_definition.rs",
"chars": 5129,
"preview": "use std::{collections::HashMap, fmt::Display};\n\nuse crate::{\n config::monitor::MonitorIdentifier,\n error::{DiagErr"
},
{
"path": "crates/yuck/src/config/window_geometry.rs",
"chars": 6743,
"preview": "use std::collections::HashMap;\n\nuse crate::{\n enum_parse,\n error::DiagResult,\n format_diagnostic::ToDiagnostic,"
},
{
"path": "crates/yuck/src/error.rs",
"chars": 1873,
"preview": "use crate::{\n format_diagnostic::{lalrpop_error_to_diagnostic, DiagnosticExt, ToDiagnostic},\n parser::{lexer, pars"
},
{
"path": "crates/yuck/src/format_diagnostic.rs",
"chars": 9563,
"preview": "use codespan_reporting::diagnostic;\nuse itertools::Itertools;\nuse simplexpr::dynval;\n\nuse diagnostic::*;\n\nuse crate::con"
},
{
"path": "crates/yuck/src/lib.rs",
"chars": 145,
"preview": "#![allow(clippy::comparison_chain)]\n\npub mod ast_error;\npub mod config;\npub mod error;\npub mod format_diagnostic;\npub mo"
},
{
"path": "crates/yuck/src/parser/ast.rs",
"chars": 4240,
"preview": "use itertools::Itertools;\nuse simplexpr::ast::SimplExpr;\n\nuse eww_shared_util::{Span, Spanned, VarName};\nuse std::fmt::D"
},
{
"path": "crates/yuck/src/parser/ast_iterator.rs",
"chars": 5130,
"preview": "use simplexpr::{ast::SimplExpr, dynval::DynVal};\nuse std::collections::HashMap;\n\nuse super::ast::{Ast, AstType};\nuse cra"
},
{
"path": "crates/yuck/src/parser/from_ast.rs",
"chars": 2085,
"preview": "use super::{ast::Ast, ast_iterator::AstIterator};\nuse crate::{error::*, format_diagnostic::ToDiagnostic, gen_diagnostic}"
},
{
"path": "crates/yuck/src/parser/lexer.rs",
"chars": 7045,
"preview": "use once_cell::sync::Lazy;\nuse regex::{Regex, RegexSet};\n\nuse super::parse_error;\nuse eww_shared_util::{Span, Spanned};\n"
},
{
"path": "crates/yuck/src/parser/mod.rs",
"chars": 2530,
"preview": "use eww_shared_util::{Span, Spanned};\nuse lalrpop_util::lalrpop_mod;\n\nuse crate::gen_diagnostic;\n\nuse super::error::{Dia"
},
{
"path": "crates/yuck/src/parser/parse_error.rs",
"chars": 423,
"preview": "use eww_shared_util::{Span, Spanned};\n\n#[derive(Debug, thiserror::Error)]\npub enum ParseError {\n #[error(\"{0}\")]\n "
},
{
"path": "crates/yuck/src/parser/parser.lalrpop",
"chars": 2128,
"preview": "use crate::parser::{lexer::Token, ast::Ast, parse_error};\nuse eww_shared_util::Span;\nuse simplexpr::ast::SimplExpr;\nuse "
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__element__test__test.snap",
"chars": 284,
"preview": "---\nsource: src/parser/element.rs\nexpression: \"Element::<Ast, Ast>::from_ast(parser.parse(0, lexer).unwrap()).unwrap()\"\n"
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-10.snap",
"chars": 123,
"preview": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(lol😄 1)\\\".to_string()))\"\n\n---\nOk(\n (lol😄 \"1\"),"
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-11.snap",
"chars": 132,
"preview": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(test \\\"hi\\\")\\\"#.to_string()))\"\n\n---\nOk(\n (te"
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-12.snap",
"chars": 138,
"preview": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(test \\\"h\\\\\\\"i\\\")\\\"#.to_string()))\"\n\n---\nOk(\n "
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-13.snap",
"chars": 136,
"preview": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(test \\\" hi \\\")\\\"#.to_string()))\"\n\n---\nOk(\n ("
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-14.snap",
"chars": 147,
"preview": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(+ (1 2 (* 2 5)))\\\".to_string()))\"\n\n---\nOk(\n (+"
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-15.snap",
"chars": 121,
"preview": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"foo ; test\\\"#.to_string()))\"\n\n---\nOk(\n foo,\n)"
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-16.snap",
"chars": 148,
"preview": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(f arg ; test\\n arg2)\\\"#.to_string()))\"\n\n"
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-17.snap",
"chars": 129,
"preview": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"\\\\\\\"h\\\\\\\\\\\\\\\"i\\\\\\\"\\\".to_string()))\"\n\n---\nOk(\n \""
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-2.snap",
"chars": 115,
"preview": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(12)\\\".to_string()))\"\n\n---\nOk(\n (\"12\"),\n)\n"
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-3.snap",
"chars": 113,
"preview": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"1.2\\\".to_string()))\"\n\n---\nOk(\n \"1.2\",\n)\n"
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-4.snap",
"chars": 115,
"preview": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"-1.2\\\".to_string()))\"\n\n---\nOk(\n \"-1.2\",\n)\n"
},
{
"path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-5.snap",
"chars": 119,
"preview": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(1 2)\\\".to_string()))\"\n\n---\nOk(\n (\"1\" \"2\"),\n)\n"
}
]
// ... and 82 more files (download for full content)
About this extraction
This page contains the full source code of the elkowar/eww GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 282 files (764.2 KB), approximately 204.5k tokens, and a symbol index with 942 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.