├── docs ├── .gitignore ├── src │ ├── widgets.md │ ├── magic-vars.md │ ├── examples.md │ ├── SUMMARY.md │ ├── working_with_gtk.md │ ├── troubleshooting.md │ ├── eww.md │ └── expression_language.md ├── theme │ ├── favicon.png │ ├── css │ │ ├── print.css │ │ └── general.css │ ├── highlight.css │ └── favicon.svg └── book.toml ├── crates ├── yuck │ ├── rust-toolchain │ ├── src │ │ ├── value │ │ │ ├── mod.rs │ │ │ └── coords.rs │ │ ├── parser │ │ │ ├── snapshots │ │ │ │ ├── eww_config__parser__test.snap │ │ │ │ ├── eww_config__parser__test-2.snap │ │ │ │ ├── eww_config__parser__test-3.snap │ │ │ │ ├── eww_config__parser__test-4.snap │ │ │ │ ├── yuck__parser__test.snap │ │ │ │ ├── eww_config__parser__test-15.snap │ │ │ │ ├── eww_config__parser__test-5.snap │ │ │ │ ├── eww_config__parser__test-9.snap │ │ │ │ ├── yuck__parser__test-2.snap │ │ │ │ ├── yuck__parser__test-3.snap │ │ │ │ ├── yuck__parser__test-4.snap │ │ │ │ ├── eww_config__parser__test-10.snap │ │ │ │ ├── eww_config__parser__test-7.snap │ │ │ │ ├── yuck__parser__test-5.snap │ │ │ │ ├── yuck__parser__test-9.snap │ │ │ │ ├── eww_config__parser__test-17.snap │ │ │ │ ├── eww_config__parser__test-6.snap │ │ │ │ ├── eww_config__parser__test-8.snap │ │ │ │ ├── yuck__parser__test-10.snap │ │ │ │ ├── yuck__parser__test-15.snap │ │ │ │ ├── yuck__parser__test-7.snap │ │ │ │ ├── eww_config__parser__test-11.snap │ │ │ │ ├── eww_config__parser__test-13.snap │ │ │ │ ├── yuck__parser__test-17.snap │ │ │ │ ├── yuck__parser__test-6.snap │ │ │ │ ├── yuck__parser__test-8.snap │ │ │ │ ├── yuck__parser__test__test.snap │ │ │ │ ├── eww_config__parser__test-12.snap │ │ │ │ ├── yuck__parser__test-11.snap │ │ │ │ ├── yuck__parser__test-12.snap │ │ │ │ ├── yuck__parser__test-13.snap │ │ │ │ ├── yuck__parser__test__test-2.snap │ │ │ │ ├── yuck__parser__test__test-3.snap │ │ │ │ ├── yuck__parser__test__test-4.snap │ │ │ │ ├── eww_config__parser__test-14.snap │ │ │ │ ├── eww_config__parser__test-16.snap │ │ │ │ ├── yuck__parser__test__test-15.snap │ │ │ │ ├── yuck__parser__test__test-5.snap │ │ │ │ ├── yuck__parser__test__test-9.snap │ │ │ │ ├── yuck__parser__test-14.snap │ │ │ │ ├── yuck__parser__test-16.snap │ │ │ │ ├── yuck__parser__test__test-10.snap │ │ │ │ ├── yuck__parser__test__test-7.snap │ │ │ │ ├── yuck__parser__lexer__test__escaped_quote.snap │ │ │ │ ├── yuck__parser__test__test-17.snap │ │ │ │ ├── yuck__parser__test__test-6.snap │ │ │ │ ├── yuck__parser__test__test-8.snap │ │ │ │ ├── yuck__parser__lexer__test__basic_simplexpr.snap │ │ │ │ ├── yuck__parser__test__test-11.snap │ │ │ │ ├── yuck__parser__test__test-13.snap │ │ │ │ ├── yuck__parser__lexer__test__quotes_in_quotes.snap │ │ │ │ ├── yuck__parser__test__test-12.snap │ │ │ │ ├── yuck__parser__test__test-14.snap │ │ │ │ ├── yuck__parser__test__test-16.snap │ │ │ │ ├── yuck__parser__lexer__test__char_boundary.snap │ │ │ │ ├── yuck__parser__lexer__test__escaped_strings.snap │ │ │ │ ├── yuck__parser__lexer__test__basic.snap │ │ │ │ ├── eww_config__parser__element__test__test.snap │ │ │ │ ├── yuck__parser__lexer__test__end_with_string_interpolation.snap │ │ │ │ ├── yuck__parser__lexer__yuck_lexer-3.snap │ │ │ │ ├── yuck__parser__lexer__yuck_lexer-4.snap │ │ │ │ ├── yuck__parser__lexer__yuck_lexer.snap │ │ │ │ └── yuck__parser__lexer__yuck_lexer-2.snap │ │ │ ├── parse_error.rs │ │ │ ├── from_ast.rs │ │ │ ├── parser.lalrpop │ │ │ ├── mod.rs │ │ │ └── ast.rs │ │ ├── lib.rs │ │ ├── config │ │ │ ├── mod.rs │ │ │ ├── file_provider.rs │ │ │ ├── var_definition.rs │ │ │ ├── monitor.rs │ │ │ ├── snapshots │ │ │ │ ├── yuck__config__test__config.snap │ │ │ │ └── eww_config__config__test__config.snap │ │ │ ├── widget_definition.rs │ │ │ ├── window_definition.rs │ │ │ ├── attributes.rs │ │ │ ├── backend_window_options.rs │ │ │ └── script_var_definition.rs │ │ ├── error.rs │ │ └── ast_error.rs │ ├── build.rs │ ├── rustfmt.toml │ └── Cargo.toml ├── simplexpr │ ├── rust-toolchain │ ├── build.rs │ ├── src │ │ ├── snapshots │ │ │ ├── simplexpr__tests__test.snap │ │ │ ├── simplexpr__tests__test-2.snap │ │ │ ├── simplexpr__tests__test-11.snap │ │ │ ├── simplexpr__tests__test-8.snap │ │ │ ├── simplexpr__tests__test-12.snap │ │ │ ├── simplexpr__tests__test-4.snap │ │ │ ├── simplexpr__tests__test-10.snap │ │ │ ├── simplexpr__tests__test-9.snap │ │ │ ├── simplexpr__dynval__test__parse_duration-2.snap │ │ │ ├── simplexpr__dynval__test__parse_duration-4.snap │ │ │ ├── simplexpr__dynval__test__parse_duration-6.snap │ │ │ ├── simplexpr__dynval__test__parse_duration-7.snap │ │ │ ├── simplexpr__dynval__test__parse_duration-3.snap │ │ │ ├── simplexpr__dynval__test__parse_duration-5.snap │ │ │ ├── simplexpr__dynval__test__parse_duration-8.snap │ │ │ ├── simplexpr__dynval__test__parse_duration.snap │ │ │ ├── simplexpr__dynval__test__parse_vec-6.snap │ │ │ ├── simplexpr__tests__test-3.snap │ │ │ ├── simplexpr__tests__test-5.snap │ │ │ ├── simplexpr__tests__test-6.snap │ │ │ ├── simplexpr__tests__test-7.snap │ │ │ ├── simplexpr__tests__test-13.snap │ │ │ ├── simplexpr__dynval__test__parse_vec.snap │ │ │ ├── simplexpr__dynval__test__parse_vec-2.snap │ │ │ ├── simplexpr__dynval__test__parse_vec-4.snap │ │ │ ├── simplexpr__dynval__test__parse_vec-5.snap │ │ │ ├── simplexpr__dynval__test__parse_vec-3.snap │ │ │ ├── simplexpr__tests__test-22.snap │ │ │ ├── simplexpr__dynval__test__parse_vec-8.snap │ │ │ ├── simplexpr__dynval__test__parse_vec-7.snap │ │ │ ├── simplexpr__tests__test-20.snap │ │ │ ├── simplexpr__tests__test-16.snap │ │ │ ├── simplexpr__tests__test-24.snap │ │ │ ├── simplexpr__tests__test-18.snap │ │ │ ├── simplexpr__tests__test-14.snap │ │ │ ├── simplexpr__tests__test-15.snap │ │ │ ├── simplexpr__tests__test-21.snap │ │ │ ├── simplexpr__tests__test-23.snap │ │ │ ├── simplexpr__tests__test-26.snap │ │ │ ├── simplexpr__tests__test-19.snap │ │ │ ├── simplexpr__tests__test-17.snap │ │ │ └── simplexpr__tests__test-25.snap │ │ ├── parser │ │ │ ├── snapshots │ │ │ │ ├── simplexpr__parser__lexer__test__digit.snap │ │ │ │ ├── simplexpr__parser__lexer__test__comments.snap │ │ │ │ ├── simplexpr__parser__lexer__test__quote_backslash_eof.snap │ │ │ │ ├── simplexpr__parser__tests__test.snap │ │ │ │ ├── simplexpr__parser__lexer__test__number_in_ident.snap │ │ │ │ ├── simplexpr__parser__tests__test-2.snap │ │ │ │ ├── simplexpr__parser__tests__test-11.snap │ │ │ │ ├── simplexpr__parser__tests__test-8.snap │ │ │ │ ├── simplexpr__parser__lexer__test__escaping.snap │ │ │ │ ├── simplexpr__parser__tests__test-12.snap │ │ │ │ ├── simplexpr__parser__tests__test-4.snap │ │ │ │ ├── simplexpr__parser__tests__test-10.snap │ │ │ │ ├── simplexpr__parser__tests__test-9.snap │ │ │ │ ├── simplexpr__parser__lexer__test__basic.snap │ │ │ │ ├── simplexpr__parser__tests__test-5.snap │ │ │ │ ├── simplexpr__parser__tests__test-3.snap │ │ │ │ ├── simplexpr__parser__tests__test-6.snap │ │ │ │ ├── simplexpr__parser__tests__test-7.snap │ │ │ │ ├── simplexpr__parser__tests__test-13.snap │ │ │ │ ├── simplexpr__parser__tests__test-14.snap │ │ │ │ ├── simplexpr__parser__lexer__test__empty_interpolation.snap │ │ │ │ ├── simplexpr__parser__tests__test-16.snap │ │ │ │ ├── simplexpr__parser__lexer__test__weird_char_boundaries.snap │ │ │ │ ├── simplexpr__parser__tests__test-15.snap │ │ │ │ ├── simplexpr__parser__lexer__test__interpolation_1.snap │ │ │ │ ├── simplexpr__parser__lexer__test__json_in_interpolation.snap │ │ │ │ ├── simplexpr__parser__lexer__test__symbol_spam.snap │ │ │ │ ├── simplexpr__parser__lexer__test__weird_nesting.snap │ │ │ │ ├── simplexpr__parser__lexer__test__interpolation_nested.snap │ │ │ │ ├── simplexpr__parser__lexer__test__safe_interpolation.snap │ │ │ │ ├── simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate-3.snap │ │ │ │ ├── simplexpr__parser__lexer__test__simplexpr_lexer_basic.snap │ │ │ │ └── simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate.snap │ │ │ ├── mod.rs │ │ │ └── lalrpop_helpers.rs │ │ ├── lib.rs │ │ └── error.rs │ ├── README.md │ ├── rustfmt.toml │ └── Cargo.toml ├── eww │ ├── src │ │ ├── config │ │ │ ├── window_definition.rs │ │ │ ├── mod.rs │ │ │ ├── scss.rs │ │ │ ├── script_var.rs │ │ │ └── inbuilt.rs │ │ ├── state │ │ │ ├── mod.rs │ │ │ └── scope.rs │ │ ├── geometry.rs │ │ ├── application_lifecycle.rs │ │ ├── client.rs │ │ ├── error_handling_ctx.rs │ │ ├── daemon_response.rs │ │ ├── widgets │ │ │ └── mod.rs │ │ ├── ipc_server.rs │ │ └── paths.rs │ ├── rustfmt.toml │ ├── build.rs │ └── Cargo.toml └── eww_shared_util │ ├── Cargo.toml │ └── src │ ├── lib.rs │ ├── wrappers.rs │ └── span.rs ├── .gitignore ├── .github ├── EwwGithubPreview.png ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.yml │ ├── widget-request.yml │ └── bug_report.yml ├── pull_request_template.md └── workflows │ ├── build.yml │ └── gh-pages.yml ├── examples └── eww-bar │ ├── eww-bar.png │ ├── scripts │ └── getvol │ ├── eww.scss │ └── eww.yuck ├── rust-toolchain.toml ├── Cargo.toml ├── .editorconfig ├── shell.nix ├── default.nix ├── rustfmt.toml ├── LICENSE ├── YUCK_MIGRATION.md ├── flake.lock ├── flake.nix ├── README.md └── CHANGELOG.md /docs/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /crates/yuck/rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly 2 | -------------------------------------------------------------------------------- /docs/src/widgets.md: -------------------------------------------------------------------------------- 1 | # Widgets 2 | 3 | -------------------------------------------------------------------------------- /crates/simplexpr/rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly 2 | -------------------------------------------------------------------------------- /crates/eww/src/config/window_definition.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /**/target 3 | /result 4 | /result-* 5 | -------------------------------------------------------------------------------- /crates/yuck/src/value/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod coords; 2 | pub use coords::*; 3 | -------------------------------------------------------------------------------- /docs/theme/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/eww/master/docs/theme/favicon.png -------------------------------------------------------------------------------- /.github/EwwGithubPreview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/eww/master/.github/EwwGithubPreview.png -------------------------------------------------------------------------------- /examples/eww-bar/eww-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/eww/master/examples/eww-bar/eww-bar.png -------------------------------------------------------------------------------- /crates/yuck/build.rs: -------------------------------------------------------------------------------- 1 | extern crate lalrpop; 2 | fn main() { 3 | lalrpop::process_root().unwrap(); 4 | } 5 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2023-07-14" 3 | components = [ "rust-src" ] 4 | profile = "default" 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/*"] 3 | resolver = "2" 4 | 5 | [profile.dev] 6 | split-debuginfo = "unpacked" 7 | 8 | -------------------------------------------------------------------------------- /crates/eww/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod one_to_n_elements_map; 2 | pub mod scope; 3 | pub mod scope_graph; 4 | 5 | #[cfg(test)] 6 | mod test; 7 | -------------------------------------------------------------------------------- /crates/simplexpr/build.rs: -------------------------------------------------------------------------------- 1 | extern crate lalrpop; 2 | fn main() { 3 | lalrpop::Configuration::new().log_verbose().process_current_dir().unwrap(); 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"1\"))" 4 | 5 | --- 6 | Ok( 7 | "1", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"2 + 5\"))" 4 | 5 | --- 6 | Ok( 7 | ("2" + "5"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-11.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"hi[\\\"ho\\\"]\"))" 4 | 5 | --- 6 | Ok( 7 | hi["ho"], 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-8.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"foo(1, 2)\"))" 4 | 5 | --- 6 | Ok( 7 | foo("1", "2"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-12.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"foo.bar.baz\"))" 4 | 5 | --- 6 | Ok( 7 | foo["bar"]["baz"], 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-4.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"(1 + 2) * 2\"))" 4 | 5 | --- 6 | Ok( 7 | (("1" + "2") * "2"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"1\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | "1", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__digit.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "v!(r#\"12\"#)" 4 | 5 | --- 6 | (0, NumLit("12"), 2) 7 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-10.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"\\\"foo\\\" + 12.4\"))" 4 | 5 | --- 6 | Ok( 7 | ("foo" + "12.4"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-9.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"! false || ! true\"))" 4 | 5 | --- 6 | Ok( 7 | (!"false" || !"true"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(12)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | ("12"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"1.2\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | "1.2", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-4.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"-1.2\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | "-1.2", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"1\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | "1", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__comments.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "v!(\"foo ; bar\")" 4 | 5 | --- 6 | (0, Ident("foo"), 3) 7 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-15.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, r#\"foo ; test\"#.to_string()))" 4 | 5 | --- 6 | Ok( 7 | foo, 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-5.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(1 2)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | ("1" "2"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-9.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(foo 1)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | (foo "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(12)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | ("12"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"1.2\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | "1.2", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-4.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"-1.2\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | "-1.2", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__quote_backslash_eof.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | assertion_line: 313 4 | expression: "v!(r#\"\"\\\"#)" 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"1\"))" 4 | 5 | --- 6 | Ok( 7 | "1", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from(\"1s\").as_duration()" 4 | 5 | --- 6 | Ok( 7 | 1s, 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-4.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from(\"5m\").as_duration()" 4 | 5 | --- 6 | Ok( 7 | 300s, 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-6.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from(\"0.5m\").as_duration()" 4 | 5 | --- 6 | Ok( 7 | 30s, 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-7.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from(\"1h\").as_duration()" 4 | 5 | --- 6 | Ok( 7 | 3600s, 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-10.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(lol😄 1)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | (lol😄 "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-7.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(:foo 1)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | (:foo "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-5.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(1 2)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | ("1" "2"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-9.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(foo 1)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | (foo "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from(\"0.1s\").as_duration()" 4 | 5 | --- 6 | Ok( 7 | 100ms, 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-5.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from(\"5min\").as_duration()" 4 | 5 | --- 6 | Ok( 7 | 300s, 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-8.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from(\"0.5h\").as_duration()" 4 | 5 | --- 6 | Ok( 7 | 1800s, 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from(\"100ms\").as_duration()" 4 | 5 | --- 6 | Ok( 7 | 100ms, 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-6.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from_string(\"\".to_string()).as_vec()" 4 | 5 | --- 6 | Ok( 7 | [], 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"2 * 5 + 1 * 1 + 3\"))" 4 | 5 | --- 6 | Ok( 7 | ((("2" * "5") + ("1" * "1")) + "3"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-5.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"1 + true ? 2 : 5\"))" 4 | 5 | --- 6 | Ok( 7 | (if ("1" + "true") then "2" else "5"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-17.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"\\\"h\\\\\\\"i\\\"\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | "h\"i", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-6.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(1 :foo 1)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | ("1" :foo "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-8.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(:foo->: 1)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | (:foo->: "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-10.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(lol😄 1)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | (lol😄 "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-15.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, r#\"foo ; test\"#.to_string()))" 4 | 5 | --- 6 | Ok( 7 | foo, 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-7.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(:foo 1)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | (:foo "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__number_in_ident.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "v!(r#\"foo_1_bar\"#)" 4 | 5 | --- 6 | (0, Ident("foo_1_bar"), 9) 7 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-11.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, r#\"(test \"hi\")\"#.to_string()))" 4 | 5 | --- 6 | Ok( 7 | (test "hi"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-13.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, r#\"(test \" hi \")\"#.to_string()))" 4 | 5 | --- 6 | Ok( 7 | (test " hi "), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-17.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"\\\"h\\\\\\\"i\\\"\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | "h"i", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-6.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(1 :foo 1)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | ("1" :foo "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-8.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(:foo->: 1)\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | (:foo->: "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, \"1\".to_string()))" 5 | --- 6 | Ok( 7 | "1", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"2 + 5\"))" 4 | 5 | --- 6 | Ok( 7 | ("2" + "5"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-6.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"1 + true ? 2 : 5 + 2\"))" 4 | 5 | --- 6 | Ok( 7 | (if ("1" + "true") then "2" else ("5" + "2")), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-12.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, r#\"(test \"h\\\"i\")\"#.to_string()))" 4 | 5 | --- 6 | Ok( 7 | (test "h\"i"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-11.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, r#\"(test \"hi\")\"#.to_string()))" 4 | 5 | --- 6 | Ok( 7 | (test "hi"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-11.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"hi[\\\"ho\\\"]\"))" 4 | 5 | --- 6 | Ok( 7 | hi["ho"], 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-8.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"foo(1, 2)\"))" 4 | 5 | --- 6 | Ok( 7 | foo("1", "2"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-7.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"1 + (true ? 2 : 5) + 2\"))" 4 | 5 | --- 6 | Ok( 7 | (("1" + (if "true" then "2" else "5")) + "2"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-12.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, r#\"(test \"h\\\"i\")\"#.to_string()))" 4 | 5 | --- 6 | Ok( 7 | (test "h"i"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-13.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, r#\"(test \" hi \")\"#.to_string()))" 4 | 5 | --- 6 | Ok( 7 | (test " hi "), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, \"(12)\".to_string()))" 5 | --- 6 | Ok( 7 | ("12"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, \"1.2\".to_string()))" 5 | --- 6 | Ok( 7 | "1.2", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-4.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, \"-1.2\".to_string()))" 5 | --- 6 | Ok( 7 | "-1.2", 8 | ) 9 | -------------------------------------------------------------------------------- /docs/src/magic-vars.md: -------------------------------------------------------------------------------- 1 | # Magic variables 2 | 3 | These are variables that are always there, without you having to import them. 4 | 5 | The delay between all the updating variables except `EWW_TIME` is 2s, for `EWW_TIME` it is 1s. 6 | 7 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-13.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"foo.bar[2 + 2] * asdf[foo.bar]\"))" 4 | 5 | --- 6 | Ok( 7 | (foo["bar"][("2" + "2")] * asdf[foo["bar"]]), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-14.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(+ (1 2 (* 2 5)))\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | (+ ("1" "2" (* "2" "5"))), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__test-16.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, r#\"(f arg ; test\n arg2)\"#.to_string()))" 4 | 5 | --- 6 | Ok( 7 | (f arg arg2), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-15.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, r#\"foo ; test\"#.to_string()))" 5 | --- 6 | Ok( 7 | foo, 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-5.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, \"(1 2)\".to_string()))" 5 | --- 6 | Ok( 7 | ("1" "2"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-9.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, \"(foo 1)\".to_string()))" 5 | --- 6 | Ok( 7 | (foo "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__escaping.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "v!(r#\" \"a\\\"b\\{}\" \"#)" 4 | 5 | --- 6 | (1, StringLit([(1, Literal("a\"b{}"), 10)]), 10) 7 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-12.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"foo.bar.baz\"))" 4 | 5 | --- 6 | Ok( 7 | foo["bar"]["baz"], 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-4.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"(1 + 2) * 2\"))" 4 | 5 | --- 6 | Ok( 7 | (("1" + "2") * "2"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::comparison_chain)] 2 | #![feature(try_blocks, box_patterns)] 3 | 4 | pub mod ast_error; 5 | pub mod config; 6 | pub mod error; 7 | pub mod format_diagnostic; 8 | pub mod parser; 9 | pub mod value; 10 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-14.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, \"(+ (1 2 (* 2 5)))\".to_string()))" 4 | 5 | --- 6 | Ok( 7 | (+ ("1" "2" (* "2" "5"))), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test-16.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, r#\"(f arg ; test\n arg2)\"#.to_string()))" 4 | 5 | --- 6 | Ok( 7 | (f arg arg2), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-10.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, \"(lol😄 1)\".to_string()))" 5 | --- 6 | Ok( 7 | (lol😄 "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-7.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, \"(:foo 1)\".to_string()))" 5 | --- 6 | Ok( 7 | (:foo "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-10.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"\\\"foo\\\" + 12.4\"))" 4 | 5 | --- 6 | Ok( 7 | ("foo" + "12.4"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-9.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"! false || ! true\"))" 4 | 5 | --- 6 | Ok( 7 | (!"false" || !"true"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__escaped_quote.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/lexer.rs 3 | expression: "v!(r#\"\"< \\\" >\"\"#)" 4 | 5 | --- 6 | (0, SimplExpr([(0, StringLit([(0, Literal("< \" >"), 8)]), 8)]), 8) 7 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-17.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, \"\\\"h\\\\\\\"i\\\"\".to_string()))" 5 | --- 6 | Ok( 7 | "h"i", 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-6.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, \"(1 :foo 1)\".to_string()))" 5 | --- 6 | Ok( 7 | ("1" :foo "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-8.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, \"(:foo->: 1)\".to_string()))" 5 | --- 6 | Ok( 7 | (:foo->: "1"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from_string(\"[]\".to_string()).as_vec()" 4 | 5 | --- 6 | Ok( 7 | [ 8 | "", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__basic_simplexpr.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/lexer.rs 3 | expression: "v!(r#\"({2})\"#)" 4 | 5 | --- 6 | (0, LPren, 1) 7 | (2, SimplExpr([(2, NumLit("2"), 3)]), 4) 8 | (4, RPren, 5) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-11.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, r#\"(test \"hi\")\"#.to_string()))" 5 | --- 6 | Ok( 7 | (test "hi"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-13.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, r#\"(test \" hi \")\"#.to_string()))" 5 | --- 6 | Ok( 7 | (test " hi "), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__basic.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "v!(r#\"bar \"foo\"\"#)" 4 | 5 | --- 6 | (0, Ident("bar"), 3) 7 | (4, StringLit([(4, Literal("foo"), 9)]), 9) 8 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-5.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"1 + true ? 2 : 5\"))" 4 | 5 | --- 6 | Ok( 7 | (("1" + "true") ? "2" : "5"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__quotes_in_quotes.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/lexer.rs 3 | expression: "v!(r#\"{ \" } ' }\" }\"#)" 4 | 5 | --- 6 | (2, SimplExpr([(2, StringLit([(2, Literal(" } ' }"), 10)]), 10)]), 12) 7 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-12.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, r#\"(test \"h\\\"i\")\"#.to_string()))" 5 | --- 6 | Ok( 7 | (test "h"i"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/eww/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod eww_config; 2 | pub mod inbuilt; 3 | pub mod script_var; 4 | pub mod scss; 5 | pub mod system_stats; 6 | pub mod window_definition; 7 | pub use eww_config::*; 8 | pub use script_var::*; 9 | pub use window_definition::*; 10 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"2 * 5 + 1 * 1 + 3\"))" 4 | 5 | --- 6 | Ok( 7 | ((("2" * "5") + ("1" * "1")) + "3"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from_string(\"[hi]\".to_string()).as_vec()" 4 | 5 | --- 6 | Ok( 7 | [ 8 | "hi", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-6.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"1 + true ? 2 : 5 + 2\"))" 4 | 5 | --- 6 | Ok( 7 | (("1" + "true") ? "2" : ("5" + "2")), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-7.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"1 + (true ? 2 : 5) + 2\"))" 4 | 5 | --- 6 | Ok( 7 | (("1" + ("true" ? "2" : "5")) + "2"), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-14.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, \"(+ (1 2 (* 2 5)))\".to_string()))" 5 | --- 6 | Ok( 7 | (+ ("1" "2" (* "2" "5"))), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__test__test-16.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/mod.rs 3 | assertion_line: 68 4 | expression: "p.parse(0, Lexer::new(0, r#\"(f arg ; test\n arg2)\"#.to_string()))" 5 | --- 6 | Ok( 7 | (f arg arg2), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-4.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from_string(\"[hi\\\\,ho]\".to_string()).as_vec()" 4 | 5 | --- 6 | Ok( 7 | [ 8 | "hi,ho", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-13.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, \"foo.bar[2 + 2] * asdf[foo.bar]\"))" 4 | 5 | --- 6 | Ok( 7 | (foo["bar"][("2" + "2")] * asdf[foo["bar"]]), 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-14.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, r#\"[1, 2, 3 + 4, \"bla\", [blub, blo]]\"#))" 4 | 5 | --- 6 | Ok( 7 | ["1", "2", ("3" + "4"), "bla", [blub, blo]], 8 | ) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-5.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from_string(\"[hi\\\\,ho,hu]\".to_string()).as_vec()" 4 | 5 | --- 6 | Ok( 7 | [ 8 | "hi,ho", 9 | "hu", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /docs/src/examples.md: -------------------------------------------------------------------------------- 1 | ## Example Configurations 2 | 3 | These configurations of eww are available in the `examples/` directory of the [repo](https://github.com/elkowar/eww). 4 | 5 | An eww bar configuration: 6 | ![Example bar](https://github.com/elkowar/eww/raw/master/examples/eww-bar/eww-bar.png) 7 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__empty_interpolation.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | assertion_line: 306 4 | expression: "v!(r#\"\"${}\"\"#)" 5 | --- 6 | (0, StringLit([(0, Literal(""), 3), (3, Interp([]), 3), (3, Literal(""), 5)]), 5) 7 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-16.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, r#\"{ \"key\": \"value\" }?.key?.does_not_exist\"#))" 4 | --- 5 | Ok( 6 | {"key": "value"}?.["key"]?.["does_not_exist"], 7 | ) 8 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from_string(\"[hi,ho,hu]\".to_string()).as_vec()" 4 | 5 | --- 6 | Ok( 7 | [ 8 | "hi", 9 | "ho", 10 | "hu", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__weird_char_boundaries.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "v!(r#\"\" \" + music\"#)" 4 | 5 | --- 6 | (0, StringLit([(0, Literal("\u{f001} "), 8)]), 8) 7 | (9, Plus, 10) 8 | (11, Ident("music"), 16) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-15.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/mod.rs 3 | expression: "p.parse(0, Lexer::new(0, 0, r#\"{ \"key\": \"value\", 5: 1+2, true: false }\"#))" 4 | 5 | --- 6 | Ok( 7 | {"key": "value", "5": ("1" + "2"), "true": "false"}, 8 | ) 9 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__char_boundary.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/lexer.rs 3 | expression: "v!(r#\"{ \" \" + music}\"#)" 4 | 5 | --- 6 | (2, SimplExpr([(2, StringLit([(2, Literal("\u{f001} "), 10)]), 10), (11, Plus, 12), (13, Ident("music"), 18)]), 19) 7 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["elkowar"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "eww documentation" 7 | 8 | 9 | [output.html] 10 | default-theme = "ayu" 11 | no-section-label = true 12 | git-repository-url = "https://github.com/elkowar/eww/tree/master/docs" 13 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-22.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"hi[\\\"ho\\\"]\"))" 4 | 5 | --- 6 | Ok( 7 | JsonAccess( 8 | VarRef( 9 | "hi", 10 | ), 11 | Literal( 12 | "ho", 13 | ), 14 | ), 15 | ) 16 | -------------------------------------------------------------------------------- /examples/eww-bar/scripts/getvol: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if command -v pamixer &>/dev/null; then 4 | if [ true == $(pamixer --get-mute) ]; then 5 | echo 0 6 | exit 7 | else 8 | pamixer --get-volume 9 | fi 10 | else 11 | amixer -D pulse sget Master | awk -F '[^0-9]+' '/Left:/{print $3}' 12 | fi 13 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-8.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from_string(\"a]\".to_string()).as_vec()" 4 | 5 | --- 6 | Err( 7 | ConversionError { 8 | value: "a]", 9 | target_type: "vec", 10 | source: None, 11 | }, 12 | ) 13 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__escaped_strings.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/lexer.rs 3 | expression: "v!(r#\"{ bla \"} \\\" }\" \" \\\" \"}\"#)" 4 | 5 | --- 6 | (2, SimplExpr([(2, Ident("bla"), 5), (6, StringLit([(6, Literal("} \" }"), 14)]), 14), (15, StringLit([(15, Literal(" \" "), 21)]), 21)]), 22) 7 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-7.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/dynval.rs 3 | expression: "DynVal::from_string(\"[a,b\".to_string()).as_vec()" 4 | 5 | --- 6 | Err( 7 | ConversionError { 8 | value: "[a,b", 9 | target_type: "vec", 10 | source: None, 11 | }, 12 | ) 13 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-20.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"\\\"foo\\\" + 12.4\"))" 4 | 5 | --- 6 | Ok( 7 | BinOp( 8 | Literal( 9 | "foo", 10 | ), 11 | Plus, 12 | Literal( 13 | "12.4", 14 | ), 15 | ), 16 | ) 17 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__interpolation_1.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "v!(r#\" \"foo ${2 * 2} bar\" \"#)" 4 | 5 | --- 6 | (1, StringLit([(1, Literal("foo "), 8), (8, Interp([(8, NumLit("2"), 9), (10, Times, 11), (12, NumLit("2"), 13)]), 13), (13, Literal(" bar"), 19)]), 19) 7 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__basic.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/lexer.rs 3 | expression: "v!(r#\"(foo + - \"text\" )\"#)" 4 | 5 | --- 6 | (0, LPren, 1) 7 | (1, Symbol("foo"), 4) 8 | (5, Symbol("+"), 6) 9 | (7, Symbol("-"), 8) 10 | (9, SimplExpr([(9, StringLit([(9, Literal("text"), 15)]), 15)]), 15) 11 | (16, RPren, 17) 12 | -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Eww - Widgets for everyone!](./eww.md) 4 | - [Configuration](./configuration.md) 5 | - [Eww expressions](./expression_language.md) 6 | - [Theming with GTK](./working_with_gtk.md) 7 | - [Magic Variables](./magic-vars.md) 8 | - [Widgets](./widgets.md) 9 | - [Troubleshooting](./troubleshooting.md) 10 | - [Examples](./examples.md) 11 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__json_in_interpolation.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "v!(r#\" \"${ {1: 2} }\" \"#)" 4 | 5 | --- 6 | (1, StringLit([(1, Literal(""), 4), (4, Interp([(5, LCurl, 6), (6, NumLit("1"), 7), (7, Colon, 8), (9, NumLit("2"), 10), (10, RCurl, 11)]), 12), (12, Literal(""), 14)]), 14) 7 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import 2 | ( 3 | let 4 | lock = builtins.fromJSON (builtins.readFile ./flake.lock); 5 | in 6 | fetchTarball { 7 | url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 8 | sha256 = lock.nodes.flake-compat.locked.narHash; 9 | } 10 | ) 11 | { 12 | src = ./.; 13 | }).shellNix 14 | -------------------------------------------------------------------------------- /crates/simplexpr/README.md: -------------------------------------------------------------------------------- 1 | # simplexpr 2 | 3 | simplexpr is a parser and interpreter for a simple expression syntax that can be embedded into other applications or crates. 4 | It is being developed to be used in [eww](https://github.com/elkowar/eww), but may also other uses. 5 | 6 | For now, this is highly experimental, unstable, and ugly. You most definitely do not want to use this crate. 7 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | (import 2 | ( 3 | let 4 | lock = builtins.fromJSON (builtins.readFile ./flake.lock); 5 | in 6 | fetchTarball { 7 | url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 8 | sha256 = lock.nodes.flake-compat.locked.narHash; 9 | } 10 | ) 11 | { 12 | src = ./.; 13 | }).defaultNix 14 | -------------------------------------------------------------------------------- /crates/yuck/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod attributes; 2 | pub mod backend_window_options; 3 | pub mod file_provider; 4 | pub mod monitor; 5 | pub mod script_var_definition; 6 | pub mod toplevel; 7 | pub mod validate; 8 | pub mod var_definition; 9 | pub mod widget_definition; 10 | pub mod widget_use; 11 | pub mod window_definition; 12 | pub mod window_geometry; 13 | 14 | pub use toplevel::*; 15 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-16.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"foo(1, 2)\"))" 4 | 5 | --- 6 | Ok( 7 | FunctionCall( 8 | "foo", 9 | [ 10 | Literal( 11 | "1", 12 | ), 13 | Literal( 14 | "2", 15 | ), 16 | ], 17 | ), 18 | ) 19 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/eww_config__parser__element__test__test.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/parser/element.rs 3 | expression: "Element::::from_ast(parser.parse(0, lexer).unwrap()).unwrap()" 4 | 5 | --- 6 | Element { 7 | name: "box", 8 | attrs: { 9 | "baz": "hi", 10 | "bar": "12", 11 | }, 12 | children: [ 13 | foo, 14 | (bar), 15 | ], 16 | span: 0..33, 17 | } 18 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__end_with_string_interpolation.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/lexer.rs 3 | expression: "v!(r#\"(box \"foo ${1 + 2}\")\"#)" 4 | 5 | --- 6 | (0, LPren, 1) 7 | (1, Symbol("box"), 4) 8 | (5, SimplExpr([(5, StringLit([(5, Literal("foo "), 12), (12, Interp([(12, NumLit("1"), 13), (14, Plus, 15), (16, NumLit("2"), 17)]), 17), (17, Literal(""), 19)]), 19)]), 19) 9 | (19, RPren, 20) 10 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | fn_single_line = false 3 | max_width = 130 4 | reorder_impl_items = true 5 | imports_granularity = "Crate" 6 | normalize_comments = true 7 | use_field_init_shorthand = true 8 | #wrap_comments = true 9 | combine_control_expr = false 10 | condense_wildcard_suffixes = true 11 | format_code_in_doc_comments = true 12 | format_macro_matchers = true 13 | format_strings = true 14 | use_small_heuristics = "Max" 15 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-24.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"foo.bar.baz\"))" 4 | 5 | --- 6 | Ok( 7 | JsonAccess( 8 | JsonAccess( 9 | VarRef( 10 | "foo", 11 | ), 12 | Literal( 13 | "bar", 14 | ), 15 | ), 16 | Literal( 17 | "baz", 18 | ), 19 | ), 20 | ) 21 | -------------------------------------------------------------------------------- /crates/eww/rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | fn_single_line = false 3 | max_width = 130 4 | reorder_impl_items = true 5 | imports_granularity = "Crate" 6 | normalize_comments = true 7 | use_field_init_shorthand = true 8 | #wrap_comments = true 9 | combine_control_expr = false 10 | condense_wildcard_suffixes = true 11 | format_code_in_doc_comments = true 12 | format_macro_matchers = true 13 | format_strings = true 14 | use_small_heuristics = "Max" 15 | -------------------------------------------------------------------------------- /crates/yuck/rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | fn_single_line = false 3 | max_width = 130 4 | reorder_impl_items = true 5 | imports_granularity = "Crate" 6 | normalize_comments = true 7 | use_field_init_shorthand = true 8 | #wrap_comments = true 9 | combine_control_expr = false 10 | condense_wildcard_suffixes = true 11 | format_code_in_doc_comments = true 12 | format_macro_matchers = true 13 | format_strings = true 14 | use_small_heuristics = "Max" 15 | -------------------------------------------------------------------------------- /crates/simplexpr/rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | fn_single_line = false 3 | max_width = 130 4 | reorder_impl_items = true 5 | imports_granularity = "Crate" 6 | normalize_comments = true 7 | use_field_init_shorthand = true 8 | #wrap_comments = true 9 | combine_control_expr = false 10 | condense_wildcard_suffixes = true 11 | format_code_in_doc_comments = true 12 | format_macro_matchers = true 13 | format_strings = true 14 | use_small_heuristics = "Max" 15 | -------------------------------------------------------------------------------- /crates/eww_shared_util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eww_shared_util" 3 | version = "0.1.0" 4 | authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"] 5 | edition = "2021" 6 | license = "MIT" 7 | description = "Utility crate used in eww" 8 | repository = "https://github.com/elkowar/eww" 9 | homepage = "https://github.com/elkowar/eww" 10 | 11 | [dependencies] 12 | serde = {version = "1.0", features = ["derive"]} 13 | derive_more = "0.99" 14 | ref-cast = "1.0.6" 15 | -------------------------------------------------------------------------------- /crates/simplexpr/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(box_patterns)] 2 | #![feature(pattern)] 3 | #![feature(try_blocks)] 4 | #![feature(unwrap_infallible)] 5 | #![feature(never_type)] 6 | 7 | pub mod ast; 8 | pub mod dynval; 9 | pub mod error; 10 | pub mod eval; 11 | pub mod parser; 12 | 13 | pub use ast::SimplExpr; 14 | 15 | use lalrpop_util::lalrpop_mod; 16 | 17 | lalrpop_mod!( 18 | #[allow(clippy::all)] 19 | pub simplexpr_parser 20 | ); 21 | 22 | pub use parser::parse_string; 23 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-18.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"! false || ! true\"))" 4 | 5 | --- 6 | Ok( 7 | BinOp( 8 | UnaryOp( 9 | Not, 10 | Literal( 11 | "false", 12 | ), 13 | ), 14 | Or, 15 | UnaryOp( 16 | Not, 17 | Literal( 18 | "true", 19 | ), 20 | ), 21 | ), 22 | ) 23 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/parse_error.rs: -------------------------------------------------------------------------------- 1 | use eww_shared_util::{Span, Spanned}; 2 | 3 | #[derive(Debug, thiserror::Error)] 4 | pub enum ParseError { 5 | #[error("{0}")] 6 | SimplExpr(simplexpr::error::ParseError), 7 | #[error("Unknown token")] 8 | LexicalError(Span), 9 | } 10 | 11 | impl Spanned for ParseError { 12 | fn span(&self) -> Span { 13 | match self { 14 | ParseError::SimplExpr(err) => err.span(), 15 | ParseError::LexicalError(span) => *span, 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__symbol_spam.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "v!(r#\"(foo + - \"()\" \"a\\\"b\" true false [] 12.2)\"#)" 4 | 5 | --- 6 | (0, LPren, 1) 7 | (1, Ident("foo"), 4) 8 | (5, Plus, 6) 9 | (7, Minus, 8) 10 | (9, StringLit([(9, Literal("()"), 13)]), 13) 11 | (14, StringLit([(14, Literal("a\"b"), 20)]), 20) 12 | (21, True, 25) 13 | (26, False, 31) 14 | (32, LBrack, 33) 15 | (33, RBrack, 34) 16 | (35, NumLit("12.2"), 39) 17 | (39, RPren, 40) 18 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__weird_nesting.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "v!(r#\"\n \"${ {\"hi\": \"ho\"}.hi }\".hi\n \"#)" 4 | 5 | --- 6 | (13, StringLit([(13, Literal(""), 16), (16, Interp([(17, LCurl, 18), (18, StringLit([(18, Literal("hi"), 22)]), 22), (22, Colon, 23), (24, StringLit([(24, Literal("ho"), 28)]), 28), (28, RCurl, 29), (29, Dot, 30), (30, Ident("hi"), 32)]), 33), (33, Literal(""), 35)]), 35) 7 | (35, Dot, 36) 8 | (36, Ident("hi"), 38) 9 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__interpolation_nested.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "v!(r#\" \"foo ${(2 * 2) + \"${5 + 5}\"} bar\" \"#)" 4 | 5 | --- 6 | (1, StringLit([(1, Literal("foo "), 8), (8, Interp([(8, LPren, 9), (9, NumLit("2"), 10), (11, Times, 12), (13, NumLit("2"), 14), (14, RPren, 15), (16, Plus, 17), (18, StringLit([(18, Literal(""), 21), (21, Interp([(21, NumLit("5"), 22), (23, Plus, 24), (25, NumLit("5"), 26)]), 26), (26, Literal(""), 28)]), 28)]), 28), (28, Literal(" bar"), 34)]), 34) 7 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__safe_interpolation.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "v!(r#\"\"${ { \"key\": \"value\" }.key1?.key2 ?: \"Recovery\" }\"\"#)" 4 | --- 5 | (0, StringLit([(0, Literal(""), 3), (3, Interp([(4, LCurl, 5), (6, StringLit([(6, Literal("key"), 11)]), 11), (11, Colon, 12), (13, StringLit([(13, Literal("value"), 20)]), 20), (21, RCurl, 22), (22, Dot, 23), (23, Ident("key1"), 27), (27, SafeAccess, 29), (29, Ident("key2"), 33), (34, Elvis, 36), (37, StringLit([(37, Literal("Recovery"), 47)]), 47)]), 48), (48, Literal(""), 50)]), 50) 6 | -------------------------------------------------------------------------------- /crates/yuck/src/config/file_provider.rs: -------------------------------------------------------------------------------- 1 | use eww_shared_util::Span; 2 | 3 | use crate::{error::DiagError, parser::ast::Ast}; 4 | 5 | #[derive(thiserror::Error, Debug)] 6 | pub enum FilesError { 7 | #[error(transparent)] 8 | IoError(#[from] std::io::Error), 9 | 10 | #[error(transparent)] 11 | DiagError(#[from] DiagError), 12 | } 13 | 14 | pub trait YuckFileProvider { 15 | fn load_yuck_file(&mut self, path: std::path::PathBuf) -> Result<(Span, Vec), FilesError>; 16 | fn load_yuck_str(&mut self, name: String, content: String) -> Result<(Span, Vec), DiagError>; 17 | fn unload(&mut self, id: usize); 18 | } 19 | -------------------------------------------------------------------------------- /crates/eww/src/geometry.rs: -------------------------------------------------------------------------------- 1 | use derive_more::*; 2 | pub trait Rectangular { 3 | fn get_rect(&self) -> Rect; 4 | } 5 | 6 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Display)] 7 | #[display(fmt = ".x*.y:.width*.height")] 8 | pub struct Rect { 9 | pub x: i32, 10 | pub y: i32, 11 | pub width: i32, 12 | pub height: i32, 13 | } 14 | 15 | impl Rectangular for Rect { 16 | fn get_rect(&self) -> Rect { 17 | *self 18 | } 19 | } 20 | 21 | impl Rectangular for gdk::Rectangle { 22 | fn get_rect(&self) -> Rect { 23 | Rect { x: self.x(), y: self.y(), width: self.width(), height: self.height() } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "Lexer::new(0, 0, r#\" \"a\\\"b\\{}\" \"#).collect_vec()" 4 | 5 | --- 6 | [ 7 | Ok( 8 | ( 9 | 1, 10 | StringLit( 11 | [ 12 | ( 13 | 1, 14 | Literal( 15 | "a\"b{}", 16 | ), 17 | 10, 18 | ), 19 | ], 20 | ), 21 | 10, 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-14.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"1 + (true ? 2 : 5) + 2\"))" 4 | 5 | --- 6 | Ok( 7 | BinOp( 8 | BinOp( 9 | Literal( 10 | "1", 11 | ), 12 | Plus, 13 | IfElse( 14 | Literal( 15 | "true", 16 | ), 17 | Literal( 18 | "2", 19 | ), 20 | Literal( 21 | "5", 22 | ), 23 | ), 24 | ), 25 | Plus, 26 | Literal( 27 | "2", 28 | ), 29 | ), 30 | ) 31 | -------------------------------------------------------------------------------- /crates/eww/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | fn main() { 3 | let output = Command::new("git").args(&["rev-parse", "HEAD"]).output(); 4 | if let Ok(output) = output { 5 | if let Ok(hash) = String::from_utf8(output.stdout) { 6 | println!("cargo:rustc-env=GIT_HASH={}", hash); 7 | println!("cargo:rustc-env=CARGO_PKG_VERSION={} {}", env!("CARGO_PKG_VERSION"), hash); 8 | } 9 | } 10 | let output = Command::new("git").args(&["show", "-s", "--format=%ci"]).output(); 11 | if let Ok(output) = output { 12 | if let Ok(date) = String::from_utf8(output.stdout) { 13 | println!("cargo:rustc-env=GIT_COMMIT_DATE={}", date); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [elkowar] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: elkowar 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__simplexpr_lexer_basic.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "Lexer::new(0, 0, r#\"bar \"foo\"\"#).collect_vec()" 4 | 5 | --- 6 | [ 7 | Ok( 8 | ( 9 | 0, 10 | Ident( 11 | "bar", 12 | ), 13 | 3, 14 | ), 15 | ), 16 | Ok( 17 | ( 18 | 4, 19 | StringLit( 20 | [ 21 | ( 22 | 4, 23 | Literal( 24 | "foo", 25 | ), 26 | 9, 27 | ), 28 | ], 29 | ), 30 | 9, 31 | ), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-15.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "Lexer::new(\"foo(1, 2)\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x) |\n Token::NumLit(x) |\n Token::StrLit(x) =>\n format!(\"{}\", x),\n x =>\n format!(\"{}\", x),\n }).collect::>()" 4 | 5 | --- 6 | [ 7 | "foo", 8 | "LPren", 9 | "1", 10 | "Comma", 11 | "2", 12 | "RPren", 13 | ] 14 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-21.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "Lexer::new(\"hi[\\\"ho\\\"]\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x) |\n Token::NumLit(x) |\n Token::StrLit(x)\n =>\n format!(\"{}\", x),\n x =>\n format!(\"{}\", x),\n }).collect::>()" 4 | 5 | --- 6 | [ 7 | "hi", 8 | "LBrack", 9 | "\"ho\"", 10 | "RBrack", 11 | ] 12 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__lexer__yuck_lexer-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/lexer.rs 3 | expression: "Lexer::new(0, r#\"\"< \\\" >\"\"#.to_string()).collect_vec()" 4 | 5 | --- 6 | [ 7 | Ok( 8 | ( 9 | 0, 10 | SimplExpr( 11 | [ 12 | ( 13 | 0, 14 | StringLit( 15 | [ 16 | ( 17 | 0, 18 | Literal( 19 | "< \" >", 20 | ), 21 | 8, 22 | ), 23 | ], 24 | ), 25 | 8, 26 | ), 27 | ], 28 | ), 29 | 8, 30 | ), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | title: "[FEATURE] " 4 | labels: [enhancement] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: "Description of the requested feature" 9 | description: "Give a clear and concise description of the feature you are proposing, including examples for when it would be useful." 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: "Proposed configuration syntax" 15 | 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." 16 | validations: 17 | required: false 18 | - type: textarea 19 | attributes: 20 | label: "Additional context" 21 | description: "If applicable, provide additional context or screenshots here." 22 | validations: 23 | required: false 24 | -------------------------------------------------------------------------------- /crates/eww_shared_util/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod span; 2 | pub mod wrappers; 3 | 4 | pub use span::*; 5 | pub use wrappers::*; 6 | 7 | #[macro_export] 8 | macro_rules! snapshot_debug { 9 | ( $($name:ident => $test:expr),* $(,)?) => { 10 | $( 11 | #[test] 12 | fn $name() { ::insta::assert_debug_snapshot!($test); } 13 | )* 14 | }; 15 | } 16 | #[macro_export] 17 | macro_rules! snapshot_string { 18 | ( $($name:ident => $test:expr),* $(,)?) => { 19 | $( 20 | #[test] 21 | fn $name() { ::insta::assert_snapshot!($test); } 22 | )* 23 | }; 24 | } 25 | 26 | #[macro_export] 27 | macro_rules! snapshot_ron { 28 | ( $($name:ident => $test:expr),* $(,)?) => { 29 | $( 30 | #[test] 31 | fn $name() { 32 | ::insta::with_settings!({sort_maps => true}, { 33 | ::insta::assert_ron_snapshot!($test); 34 | }); 35 | } 36 | )* 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /docs/theme/css/print.css: -------------------------------------------------------------------------------- 1 | 2 | #sidebar, 3 | #menu-bar, 4 | .nav-chapters, 5 | .mobile-nav-chapters { 6 | display: none; 7 | } 8 | 9 | #page-wrapper.page-wrapper { 10 | transform: none; 11 | margin-left: 0px; 12 | overflow-y: initial; 13 | } 14 | 15 | #content { 16 | max-width: none; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | .page { 22 | overflow-y: initial; 23 | } 24 | 25 | code { 26 | background-color: #666666; 27 | border-radius: 5px; 28 | 29 | /* Force background to be printed in Chrome */ 30 | -webkit-print-color-adjust: exact; 31 | } 32 | 33 | pre > .buttons { 34 | z-index: 2; 35 | } 36 | 37 | a, a:visited, a:active, a:hover { 38 | color: #4183c4; 39 | text-decoration: none; 40 | } 41 | 42 | h1, h2, h3, h4, h5, h6 { 43 | page-break-inside: avoid; 44 | page-break-after: avoid; 45 | } 46 | 47 | pre, code { 48 | page-break-inside: avoid; 49 | white-space: pre-wrap; 50 | } 51 | 52 | .fa { 53 | display: none !important; 54 | } 55 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-23.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "Lexer::new(\"foo.bar.baz\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x) |\n Token::NumLit(x)\n |\n Token::StrLit(x)\n =>\n format!(\"{}\", x),\n x =>\n format!(\"{}\", x),\n }).collect::>()" 4 | 5 | --- 6 | [ 7 | "foo", 8 | "Dot", 9 | "bar", 10 | "Dot", 11 | "baz", 12 | ] 13 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-26.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "p.parse(Lexer::new(\"foo.bar[2 + 2] * asdf[foo.bar]\"))" 4 | 5 | --- 6 | Ok( 7 | BinOp( 8 | JsonAccess( 9 | JsonAccess( 10 | VarRef( 11 | "foo", 12 | ), 13 | Literal( 14 | "bar", 15 | ), 16 | ), 17 | BinOp( 18 | Literal( 19 | "2", 20 | ), 21 | Plus, 22 | Literal( 23 | "2", 24 | ), 25 | ), 26 | ), 27 | Times, 28 | JsonAccess( 29 | VarRef( 30 | "asdf", 31 | ), 32 | JsonAccess( 33 | VarRef( 34 | "foo", 35 | ), 36 | Literal( 37 | "bar", 38 | ), 39 | ), 40 | ), 41 | ), 42 | ) 43 | -------------------------------------------------------------------------------- /crates/yuck/src/config/var_definition.rs: -------------------------------------------------------------------------------- 1 | use simplexpr::dynval::DynVal; 2 | 3 | use crate::{ 4 | error::{DiagResult, DiagResultExt}, 5 | parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent}, 6 | }; 7 | use eww_shared_util::{Span, VarName}; 8 | 9 | #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] 10 | pub struct VarDefinition { 11 | pub name: VarName, 12 | pub initial_value: DynVal, 13 | pub span: Span, 14 | } 15 | 16 | impl FromAstElementContent for VarDefinition { 17 | const ELEMENT_NAME: &'static str = "defvar"; 18 | 19 | fn from_tail>(span: Span, mut iter: AstIterator) -> DiagResult { 20 | let result: DiagResult<_> = try { 21 | let (_, name) = iter.expect_symbol()?; 22 | let (_, initial_value) = iter.expect_literal()?; 23 | iter.expect_done()?; 24 | Self { name: VarName(name), initial_value, span } 25 | }; 26 | result.note(r#"Expected format: `(defvar name "initial-value")`"#) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/simplexpr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simplexpr" 3 | version = "0.1.0" 4 | authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"] 5 | edition = "2021" 6 | license = "MIT" 7 | description = "A simple expression language, used as a part of eww" 8 | repository = "https://github.com/elkowar/eww" 9 | homepage = "https://github.com/elkowar/eww" 10 | 11 | 12 | build = "build.rs" 13 | 14 | [dependencies] 15 | lalrpop-util = "0.19.5" 16 | regex = "1.5.5" 17 | itertools = "0.10" 18 | thiserror = "1.0" 19 | 20 | once_cell = "1.8.0" 21 | serde = {version = "1.0", features = ["derive"]} 22 | serde_json = "1.0" 23 | strsim = "0.10" 24 | jaq-core = "0.9.0" 25 | jaq-std = {version = "0.9.0", features = ["bincode"]} 26 | static_assertions = "1.1.0" 27 | cached = "0.42.0" 28 | chrono = "0.4.26" 29 | chrono-tz = "0.8.2" 30 | 31 | strum = { version = "0.24", features = ["derive"] } 32 | 33 | eww_shared_util = { version = "0.1.0", path = "../eww_shared_util" } 34 | 35 | 36 | [build-dependencies] 37 | lalrpop = "0.19.5" 38 | 39 | [dev-dependencies] 40 | insta = "1.7" 41 | -------------------------------------------------------------------------------- /crates/yuck/src/config/monitor.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::Infallible, fmt, str}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// The type of the identifier used to select a monitor 6 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 7 | pub enum MonitorIdentifier { 8 | Numeric(i32), 9 | Name(String), 10 | } 11 | 12 | impl MonitorIdentifier { 13 | pub fn is_numeric(&self) -> bool { 14 | matches!(self, Self::Numeric(_)) 15 | } 16 | } 17 | 18 | impl fmt::Display for MonitorIdentifier { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | match self { 21 | Self::Numeric(n) => write!(f, "{}", n), 22 | Self::Name(n) => write!(f, "{}", n), 23 | } 24 | } 25 | } 26 | 27 | impl str::FromStr for MonitorIdentifier { 28 | type Err = Infallible; 29 | 30 | fn from_str(s: &str) -> Result { 31 | match s.parse::() { 32 | Ok(n) => Ok(Self::Numeric(n)), 33 | Err(_) => Ok(Self::Name(s.to_owned())), 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crates/yuck/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yuck" 3 | version = "0.1.0" 4 | authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"] 5 | edition = "2021" 6 | license = "MIT" 7 | description = "Implementation of the yuck language, the declarative UI description language used by eww" 8 | repository = "https://github.com/elkowar/eww" 9 | homepage = "https://github.com/elkowar/eww" 10 | 11 | build = "build.rs" 12 | 13 | [dependencies] 14 | lalrpop-util = "0.19.5" 15 | regex = "1.5.5" 16 | itertools = "0.10" 17 | thiserror = "1.0" 18 | maplit = "1.0" 19 | codespan-reporting = "0.11" 20 | 21 | derive_more = "0.99" 22 | smart-default = "0.6" 23 | serde = {version = "1.0", features = ["derive"]} 24 | once_cell = "1.8" 25 | 26 | strum = { version = "0.24", features = ["derive"] } 27 | anyhow = "1" 28 | static_assertions = "1.1" 29 | 30 | simplexpr = { version = "0.1.0", path = "../simplexpr" } 31 | eww_shared_util = { version = "0.1.0", path = "../eww_shared_util" } 32 | 33 | 34 | [build-dependencies] 35 | lalrpop = "0.19.5" 36 | 37 | [dev-dependencies] 38 | insta = "1.7" 39 | pretty_assertions = "1.2" 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 ElKowar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Please follow this template, if applicable. 2 | 3 | ## Description 4 | 5 | Provide a short description of the changes your PR introduces. 6 | This includes the actual feature you are adding, 7 | as well as any other relevant additions that were necessary to implement your feature. 8 | 9 | ## Usage 10 | 11 | When adding a widget or anything else that affects the configuration, 12 | please provide a minimal example configuration snippet showcasing how to use it and 13 | 14 | ### Showcase 15 | 16 | When adding widgets, please provide screenshots showcasing how your widget looks. 17 | This is not strictly required, but strongly appreciated. 18 | 19 | ## Additional Notes 20 | 21 | Anything else you want to add, such as remaining questions or explanations. 22 | 23 | ## Checklist 24 | 25 | Please make sure you can check all the boxes that apply to this PR. 26 | 27 | - [ ] All widgets I've added are correctly documented. 28 | - [ ] I added my changes to CHANGELOG.md, if appropriate. 29 | - [ ] The documentation in the `docs/content/main` directory has been adjusted to reflect my changes. 30 | - [ ] I used `cargo fmt` to automatically format all code before committing 31 | -------------------------------------------------------------------------------- /examples/eww-bar/eww.scss: -------------------------------------------------------------------------------- 1 | * { 2 | all: unset; //Unsets everything so you can style everything from scratch 3 | } 4 | 5 | //Global Styles 6 | .bar { 7 | background-color: #3a3a3a; 8 | color: #b0b4bc; 9 | padding: 10px; 10 | } 11 | 12 | // Styles on classes (see eww.yuck for more information) 13 | 14 | .sidestuff slider { 15 | all: unset; 16 | color: #ffd5cd; 17 | } 18 | 19 | .metric scale trough highlight { 20 | all: unset; 21 | background-color: #D35D6E; 22 | color: #000000; 23 | border-radius: 10px; 24 | } 25 | .metric scale trough { 26 | all: unset; 27 | background-color: #4e4e4e; 28 | border-radius: 50px; 29 | min-height: 3px; 30 | min-width: 50px; 31 | margin-left: 10px; 32 | margin-right: 20px; 33 | } 34 | .metric scale trough highlight { 35 | all: unset; 36 | background-color: #D35D6E; 37 | color: #000000; 38 | border-radius: 10px; 39 | } 40 | .metric scale trough { 41 | all: unset; 42 | background-color: #4e4e4e; 43 | border-radius: 50px; 44 | min-height: 3px; 45 | min-width: 50px; 46 | margin-left: 10px; 47 | margin-right: 20px; 48 | } 49 | .label-ram { 50 | font-size: large; 51 | } 52 | .workspaces button:hover { 53 | color: #D35D6E; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /docs/src/working_with_gtk.md: -------------------------------------------------------------------------------- 1 | # GTK 2 | 3 | ## Gtk-theming 4 | 5 | Eww is styled in GTK CSS. 6 | You can use `Vanilla CSS` or `SCSS` to make theming even easier. The latter is compiled into CSS for you. 7 | If you don't know any way to style something check out the [GTK CSS Overview wiki](https://docs.gtk.org/gtk3/css-overview.html), 8 | the [GTK CSS Properties Overview wiki ](https://docs.gtk.org/gtk3/css-properties.html), 9 | or use the [GTK-Debugger](#gtk-debugger). 10 | 11 | If you have **NO** clue about how to do CSS, check out some online guides or tutorials. 12 | 13 | SCSS is _very_ close to CSS, so if you know CSS you'll have no problem learning SCSS. 14 | 15 | ## GTK-Debugger 16 | 17 | The debugger can be used for **a lot** of things, especially if something doesn't work or isn't styled right. 18 | 19 | To open the GTK debugger, simply run 20 | 21 | ```bash 22 | eww inspector 23 | ``` 24 | 25 | If a style or something similar doesn't work, you can click on the icon in the top left to select the thing that isn't being styled correctly. 26 | 27 | Then you can click on the drop down menu in the top right corner and select CSS Nodes. Here you will see everything about styling it, CSS Properties, and how it's structured. 28 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-19.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "Lexer::new(\"\\\"foo\\\" + 12.4\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x)\n |\n Token::NumLit(x)\n |\n Token::StrLit(x)\n =>\n format!(\"{}\",\n x),\n x =>\n format!(\"{}\",\n x),\n }).collect::>()" 4 | 5 | --- 6 | [ 7 | "\"foo\"", 8 | "+", 9 | "12.4", 10 | ] 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/widget-request.yml: -------------------------------------------------------------------------------- 1 | name: Widget request 2 | description: Suggest an new Widget 3 | title: "[WIDGET] " 4 | labels: [widget-request] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: "Description of the widget" 9 | description: "Provide an explanation of the widget." 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: "Implementation proposal" 15 | 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." 16 | validations: 17 | required: false 18 | - type: textarea 19 | attributes: 20 | label: "Example usage" 21 | 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." 22 | validations: 23 | required: false 24 | - type: textarea 25 | attributes: 26 | label: "Additional context" 27 | description: "Provide any additional context if applicable." 28 | validations: 29 | required: false 30 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | #push: 5 | #branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Install dependencies 17 | run: sudo apt-get update && sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev 18 | - name: Set up 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: nightly 22 | override: true 23 | components: rustfmt 24 | - uses: actions/checkout@v2 25 | - uses: Swatinem/rust-cache@v1 26 | - uses: r7kamura/rust-problem-matchers@v1 27 | - name: Check formatting 28 | run: cargo fmt -- --check 29 | - name: Check with default features 30 | run: cargo check 31 | - name: Run tests 32 | run: cargo test 33 | - name: Build x11 only 34 | run: cargo check --no-default-features --features=x11 35 | - name: Build wayland only 36 | run: cargo check --no-default-features --features=wayland 37 | - name: Build no-backend 38 | run: cargo check --no-default-features 39 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-17.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "Lexer::new(\"! false || ! true\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x)\n |\n Token::NumLit(x)\n |\n Token::StrLit(x)\n =>\n format!(\"{}\",\n x),\n x =>\n format!(\"{}\",\n x),\n }).collect::>()" 4 | 5 | --- 6 | [ 7 | "!", 8 | "False", 9 | "||", 10 | "!", 11 | "True", 12 | ] 13 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__lexer__yuck_lexer-4.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/lexer.rs 3 | expression: "Lexer::new(0, r#\"{ \" \" + music}\"#.to_string()).collect_vec()" 4 | 5 | --- 6 | [ 7 | Ok( 8 | ( 9 | 2, 10 | SimplExpr( 11 | [ 12 | ( 13 | 2, 14 | StringLit( 15 | [ 16 | ( 17 | 2, 18 | Literal( 19 | "\u{f001} ", 20 | ), 21 | 10, 22 | ), 23 | ], 24 | ), 25 | 10, 26 | ), 27 | ( 28 | 11, 29 | Plus, 30 | 12, 31 | ), 32 | ( 33 | 13, 34 | Ident( 35 | "music", 36 | ), 37 | 18, 38 | ), 39 | ], 40 | ), 41 | 18, 42 | ), 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy Github pages 2 | on: 3 | push: 4 | branches: 5 | - master 6 | paths: 7 | - "docs/**" 8 | - "gen-docs.ts" 9 | - "crates/eww/src/widgets/widget_definitions.rs" 10 | - "crates/eww/src/config/inbuilt.rs" 11 | - ".github/workflows/**" 12 | jobs: 13 | build: 14 | name: Build mdBook 15 | runs-on: ubuntu-latest 16 | steps: 17 | # Checkout 18 | - uses: actions/checkout@master 19 | 20 | # Build widget documentation 21 | - name: Use deno to build widget documentation 22 | uses: denoland/setup-deno@main 23 | with: 24 | deno-version: "v1.x" 25 | - run: deno run --allow-read --allow-write gen-docs.ts ./crates/eww/src/widgets/widget_definitions.rs ./crates/eww/src/config/inbuilt.rs 26 | 27 | # Build & deploy 28 | - name: build mdBook page 29 | uses: peaceiris/actions-mdbook@v1 30 | with: 31 | mdbook-version: '0.4.8' 32 | - run: mdbook build docs 33 | 34 | - name: Deploy 35 | uses: peaceiris/actions-gh-pages@v3 36 | with: 37 | github_token: ${{ secrets.GITHUB_TOKEN }} 38 | publish_dir: ./docs/book/ 39 | -------------------------------------------------------------------------------- /crates/simplexpr/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::lexer::{self, LexicalError}; 2 | use eww_shared_util::{Span, Spanned}; 3 | 4 | #[derive(thiserror::Error, Debug)] 5 | #[error("Error parsing expression: {source}")] 6 | pub struct ParseError { 7 | pub file_id: usize, 8 | pub source: lalrpop_util::ParseError, 9 | } 10 | 11 | impl ParseError { 12 | pub fn from_parse_error(file_id: usize, err: lalrpop_util::ParseError) -> Self { 13 | Self { file_id, source: err } 14 | } 15 | } 16 | 17 | impl Spanned for ParseError { 18 | fn span(&self) -> Span { 19 | match &self.source { 20 | lalrpop_util::ParseError::InvalidToken { location } => Span(*location, *location, self.file_id), 21 | lalrpop_util::ParseError::UnrecognizedEOF { location, expected: _ } => Span(*location, *location, self.file_id), 22 | lalrpop_util::ParseError::UnrecognizedToken { token, expected: _ } => Span(token.0, token.2, self.file_id), 23 | lalrpop_util::ParseError::ExtraToken { token } => Span(token.0, token.2, self.file_id), 24 | lalrpop_util::ParseError::User { error: LexicalError(span) } => *span, 25 | } 26 | } 27 | } 28 | 29 | #[macro_export] 30 | macro_rules! spanned { 31 | ($err:ty, $span:expr, $block:expr) => {{ 32 | let span = $span; 33 | let result: Result<_, $err> = try { $block }; 34 | result.at(span) 35 | }}; 36 | } 37 | -------------------------------------------------------------------------------- /YUCK_MIGRATION.md: -------------------------------------------------------------------------------- 1 | # Migrating to yuck 2 | 3 | Yuck is the new configuration syntax used by eww. 4 | While the syntax has changed dramatically, the general structure of the configuration 5 | has stayed mostly the same. 6 | 7 | Most notably, the top-level blocks are now gone. 8 | This means that `defvar`, `defwidget`, etc blocks no longer need to be in separate 9 | sections of the file, but instead can be put wherever you need them. 10 | 11 | Explaining the exact syntax of yuck would be significantly less effective than just 12 | looking at an example, as the general syntax is very simple. 13 | 14 | Thus, to get a feel for yuck, read through the [example configuration](./examples/eww-bar/eww.yuck). 15 | 16 | 17 | Additionally, a couple smaller things have been changed. 18 | The fields and structure of the `defwindow` block as been adjusted to better reflect 19 | the options provided by the displayserver that is being used. 20 | The major changes are: 21 | - The `screen` field is now called `monitor` 22 | - `reserve` and `geometry` are now structured slightly differently (see [here](./docs/src/configuration.md#creating-your-first-window)) 23 | To see how exactly the configuration now looks, check the [respective documentation](./docs/src/configuration.md#creating-your-first-window) 24 | 25 | 26 | ## Automatically converting your configuration 27 | 28 | A couple _amazing_ people have started to work on an [automatic converter](https://github.com/undefinedDarkness/ewwxml) that can turn your 29 | old eww.xml into the new yuck format! 30 | -------------------------------------------------------------------------------- /crates/eww/src/application_lifecycle.rs: -------------------------------------------------------------------------------- 1 | //! Module concerned with handling the global application lifecycle of eww. 2 | //! Currently, this only means handling application exit by providing a global 3 | //! `recv_exit()` function which can be awaited to receive an event in case of application termination. 4 | 5 | use anyhow::{Context, Result}; 6 | use once_cell::sync::Lazy; 7 | use tokio::sync::broadcast; 8 | 9 | pub static APPLICATION_EXIT_SENDER: Lazy> = Lazy::new(|| broadcast::channel(2).0); 10 | 11 | /// Notify all listening tasks of the termination of the eww application process. 12 | pub fn send_exit() -> Result<()> { 13 | (APPLICATION_EXIT_SENDER).send(()).context("Failed to send exit lifecycle event")?; 14 | Ok(()) 15 | } 16 | 17 | /// Yields Ok(()) on application termination. Await on this in all long-running tasks 18 | /// and perform any cleanup if necessary. 19 | pub async fn recv_exit() -> Result<()> { 20 | (APPLICATION_EXIT_SENDER).subscribe().recv().await.context("Failed to receive lifecycle event") 21 | } 22 | 23 | /// Select in a loop, breaking once a application termination event (see `crate::application_lifecycle`) is received. 24 | #[macro_export] 25 | macro_rules! loop_select_exiting { 26 | ($($content:tt)*) => { 27 | loop { 28 | tokio::select! { 29 | Ok(()) = $crate::application_lifecycle::recv_exit() => { 30 | break; 31 | } 32 | $($content)* 33 | } 34 | } 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /docs/theme/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | * An increased contrast highlighting scheme loosely based on the 3 | * "Base16 Atelier Dune Light" theme by Bram de Haan 4 | * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) 5 | * Original Base16 color scheme by Chris Kempson 6 | * (https://github.com/chriskempson/base16) 7 | */ 8 | 9 | /* Comment */ 10 | .hljs-comment, 11 | .hljs-quote { 12 | color: #575757; 13 | } 14 | 15 | /* Red */ 16 | .hljs-variable, 17 | .hljs-template-variable, 18 | .hljs-attribute, 19 | .hljs-tag, 20 | .hljs-name, 21 | .hljs-regexp, 22 | .hljs-link, 23 | .hljs-name, 24 | .hljs-selector-id, 25 | .hljs-selector-class { 26 | color: #d70025; 27 | } 28 | 29 | /* Orange */ 30 | .hljs-number, 31 | .hljs-meta, 32 | .hljs-built_in, 33 | .hljs-builtin-name, 34 | .hljs-literal, 35 | .hljs-type, 36 | .hljs-params { 37 | color: #b21e00; 38 | } 39 | 40 | /* Green */ 41 | .hljs-string, 42 | .hljs-symbol, 43 | .hljs-bullet { 44 | color: #008200; 45 | } 46 | 47 | /* Blue */ 48 | .hljs-title, 49 | .hljs-section { 50 | color: #0030f2; 51 | } 52 | 53 | /* Purple */ 54 | .hljs-keyword, 55 | .hljs-selector-tag { 56 | color: #9d00ec; 57 | } 58 | 59 | .hljs { 60 | display: block; 61 | overflow-x: auto; 62 | background: #f6f7f6; 63 | color: #000; 64 | padding: 0.5em; 65 | } 66 | 67 | .hljs-emphasis { 68 | font-style: italic; 69 | } 70 | 71 | .hljs-strong { 72 | font-weight: bold; 73 | } 74 | 75 | .hljs-addition { 76 | color: #22863a; 77 | background-color: #f0fff4; 78 | } 79 | 80 | .hljs-deletion { 81 | color: #b31d28; 82 | background-color: #ffeef0; 83 | } 84 | -------------------------------------------------------------------------------- /crates/eww_shared_util/src/wrappers.rs: -------------------------------------------------------------------------------- 1 | use derive_more::*; 2 | use ref_cast::RefCast; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// The name of a variable 6 | #[repr(transparent)] 7 | #[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom, RefCast)] 8 | #[debug(fmt = "VarName({})", .0)] 9 | pub struct VarName(pub String); 10 | 11 | impl std::borrow::Borrow for VarName { 12 | fn borrow(&self) -> &str { 13 | &self.0 14 | } 15 | } 16 | 17 | impl AttrName { 18 | pub fn to_attr_name_ref(&self) -> &AttrName { 19 | AttrName::ref_cast(&self.0) 20 | } 21 | } 22 | 23 | impl From<&str> for VarName { 24 | fn from(s: &str) -> Self { 25 | VarName(s.to_owned()) 26 | } 27 | } 28 | 29 | impl From for VarName { 30 | fn from(x: AttrName) -> Self { 31 | VarName(x.0) 32 | } 33 | } 34 | 35 | /// The name of an attribute 36 | #[repr(transparent)] 37 | #[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom, RefCast)] 38 | #[debug(fmt="AttrName({})", .0)] 39 | pub struct AttrName(pub String); 40 | 41 | impl AttrName { 42 | pub fn to_var_name_ref(&self) -> &VarName { 43 | VarName::ref_cast(&self.0) 44 | } 45 | } 46 | 47 | impl std::borrow::Borrow for AttrName { 48 | fn borrow(&self) -> &str { 49 | &self.0 50 | } 51 | } 52 | 53 | impl From<&str> for AttrName { 54 | fn from(s: &str) -> Self { 55 | AttrName(s.to_owned()) 56 | } 57 | } 58 | 59 | impl From for AttrName { 60 | fn from(x: VarName) -> Self { 61 | AttrName(x.0) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod lalrpop_helpers; 2 | pub mod lexer; 3 | 4 | use crate::{ast::SimplExpr, error::ParseError}; 5 | 6 | pub fn parse_string(byte_offset: usize, file_id: usize, s: &str) -> Result { 7 | let lexer = lexer::Lexer::new(file_id, byte_offset, s); 8 | let parser = crate::simplexpr_parser::ExprParser::new(); 9 | parser.parse(file_id, lexer).map_err(|e| ParseError::from_parse_error(file_id, e)) 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | macro_rules! test_parser { 15 | ($($text:literal),* $(,)?) => {{ 16 | let p = crate::simplexpr_parser::ExprParser::new(); 17 | use crate::parser::lexer::Lexer; 18 | ::insta::with_settings!({sort_maps => true}, { 19 | $( 20 | ::insta::assert_debug_snapshot!(p.parse(0, Lexer::new(0, 0, $text))); 21 | )* 22 | }); 23 | }} 24 | } 25 | 26 | #[test] 27 | fn test() { 28 | test_parser!( 29 | "1", 30 | "2 + 5", 31 | "2 * 5 + 1 * 1 + 3", 32 | "(1 + 2) * 2", 33 | "1 + true ? 2 : 5", 34 | "1 + true ? 2 : 5 + 2", 35 | "1 + (true ? 2 : 5) + 2", 36 | "foo(1, 2)", 37 | "! false || ! true", 38 | "\"foo\" + 12.4", 39 | "hi[\"ho\"]", 40 | "foo.bar.baz", 41 | "foo.bar[2 + 2] * asdf[foo.bar]", 42 | r#"[1, 2, 3 + 4, "bla", [blub, blo]]"#, 43 | r#"{ "key": "value", 5: 1+2, true: false }"#, 44 | r#"{ "key": "value" }?.key?.does_not_exist"#, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/eww/src/state/scope.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::{collections::HashMap, rc::Rc}; 3 | 4 | use eww_shared_util::VarName; 5 | use simplexpr::dynval::DynVal; 6 | 7 | use super::scope_graph::{ScopeGraph, ScopeIndex}; 8 | 9 | #[derive(Debug)] 10 | pub struct Scope { 11 | pub name: String, 12 | pub ancestor: Option, 13 | pub data: HashMap, 14 | /// The listeners that react to value changes in this scope. 15 | /// **Note** that there might be VarNames referenced here that are not defined in this scope. 16 | /// In those cases it is necessary to look into the scopes this scope is inheriting from. 17 | pub listeners: HashMap>>, 18 | pub node_index: ScopeIndex, 19 | } 20 | 21 | impl Scope { 22 | /// Initializes a scope **incompletely**. The [`Self::node_index`] is not set correctly, and needs to be 23 | /// set to the index of the node in the scope graph that connects to this scope. 24 | pub(super) fn new(name: String, created_by: Option, data: HashMap) -> Self { 25 | Self { name, ancestor: created_by, data, listeners: HashMap::new(), node_index: ScopeIndex(0) } 26 | } 27 | } 28 | 29 | pub type ListenerFn = Box) -> Result<()>>; 30 | 31 | pub struct Listener { 32 | pub needed_variables: Vec, 33 | pub f: ListenerFn, 34 | } 35 | impl std::fmt::Debug for Listener { 36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 | f.debug_struct("Listener").field("needed_variables", &self.needed_variables).field("f", &"function").finish() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__lexer__yuck_lexer.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/lexer.rs 3 | expression: "Lexer::new(0, r#\"(foo + - \"text\" )\"#.to_string()).collect_vec()" 4 | 5 | --- 6 | [ 7 | Ok( 8 | ( 9 | 0, 10 | LPren, 11 | 1, 12 | ), 13 | ), 14 | Ok( 15 | ( 16 | 1, 17 | Symbol( 18 | "foo", 19 | ), 20 | 4, 21 | ), 22 | ), 23 | Ok( 24 | ( 25 | 5, 26 | Symbol( 27 | "+", 28 | ), 29 | 6, 30 | ), 31 | ), 32 | Ok( 33 | ( 34 | 7, 35 | Symbol( 36 | "-", 37 | ), 38 | 8, 39 | ), 40 | ), 41 | Ok( 42 | ( 43 | 9, 44 | SimplExpr( 45 | [ 46 | ( 47 | 9, 48 | StringLit( 49 | [ 50 | ( 51 | 9, 52 | Literal( 53 | "text", 54 | ), 55 | 15, 56 | ), 57 | ], 58 | ), 59 | 15, 60 | ), 61 | ], 62 | ), 63 | 15, 64 | ), 65 | ), 66 | Ok( 67 | ( 68 | 16, 69 | RPren, 70 | 17, 71 | ), 72 | ), 73 | ] 74 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/lalrpop_helpers.rs: -------------------------------------------------------------------------------- 1 | use eww_shared_util::Span; 2 | 3 | use crate::SimplExpr; 4 | 5 | use super::lexer::{LexicalError, Sp, StrLitSegment, Token}; 6 | 7 | pub fn b(x: T) -> Box { 8 | Box::new(x) 9 | } 10 | 11 | pub fn parse_stringlit( 12 | span: Span, 13 | mut segs: Vec>, 14 | ) -> Result> { 15 | let file_id = span.2; 16 | let parser = crate::simplexpr_parser::ExprParser::new(); 17 | 18 | if segs.len() == 1 { 19 | let (lo, seg, hi) = segs.remove(0); 20 | let span = Span(lo, hi, file_id); 21 | match seg { 22 | StrLitSegment::Literal(lit) => Ok(SimplExpr::literal(span, lit)), 23 | StrLitSegment::Interp(toks) => { 24 | let token_stream = toks.into_iter().map(Ok); 25 | parser.parse(file_id, token_stream) 26 | } 27 | } 28 | } else { 29 | let elems = segs 30 | .into_iter() 31 | .filter_map(|(lo, segment, hi)| { 32 | let span = Span(lo, hi, file_id); 33 | match segment { 34 | StrLitSegment::Literal(lit) if lit.is_empty() => None, 35 | StrLitSegment::Literal(lit) => Some(Ok(SimplExpr::literal(span, lit))), 36 | StrLitSegment::Interp(toks) => { 37 | let token_stream = toks.into_iter().map(Ok); 38 | Some(parser.parse(file_id, token_stream)) 39 | } 40 | } 41 | }) 42 | .collect::, _>>()?; 43 | Ok(SimplExpr::Concat(span, elems)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/eww/src/config/scss.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::{anyhow, Context}; 4 | 5 | use crate::{error_handling_ctx, util::replace_env_var_references}; 6 | 7 | /// read an (s)css file, replace all environment variable references within it and 8 | /// then parse it into css. 9 | /// Also adds the CSS to the [`crate::file_database::FileDatabase`] 10 | pub fn parse_scss_from_config(path: &Path) -> anyhow::Result<(usize, String)> { 11 | let css_file = path.join("eww.css"); 12 | let scss_file = path.join("eww.scss"); 13 | if css_file.exists() && scss_file.exists() { 14 | return Err(anyhow!("Encountered both an SCSS and CSS file. Only one of these may exist at a time")); 15 | } 16 | 17 | let (s_css_path, css) = if css_file.exists() { 18 | let css_file_content = std::fs::read_to_string(&css_file) 19 | .with_context(|| format!("Given CSS file doesn't exist: {}", css_file.display()))?; 20 | let css = replace_env_var_references(css_file_content); 21 | (css_file, css) 22 | } else { 23 | let scss_file_content = 24 | std::fs::read_to_string(&scss_file).with_context(|| format!("Given SCSS file doesn't exist! {}", path.display()))?; 25 | let file_content = replace_env_var_references(scss_file_content); 26 | let grass_config = grass::Options::default().load_path(path); 27 | let css = grass::from_string(file_content, &grass_config).map_err(|err| anyhow!("SCSS parsing error: {}", err))?; 28 | (scss_file, css) 29 | }; 30 | 31 | let mut file_db = error_handling_ctx::FILE_DATABASE.write().unwrap(); 32 | let file_id = file_db.insert_string(s_css_path.display().to_string(), css.clone())?; 33 | Ok((file_id, css)) 34 | } 35 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/snapshots/yuck__parser__lexer__yuck_lexer-2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/parser/lexer.rs 3 | expression: "Lexer::new(0, r#\"{ bla \"} \\\" }\" \" \\\" \"}\"#.to_string()).collect_vec()" 4 | 5 | --- 6 | [ 7 | Ok( 8 | ( 9 | 2, 10 | SimplExpr( 11 | [ 12 | ( 13 | 2, 14 | Ident( 15 | "bla", 16 | ), 17 | 5, 18 | ), 19 | ( 20 | 6, 21 | StringLit( 22 | [ 23 | ( 24 | 6, 25 | Literal( 26 | "} \" }", 27 | ), 28 | 14, 29 | ), 30 | ], 31 | ), 32 | 14, 33 | ), 34 | ( 35 | 15, 36 | StringLit( 37 | [ 38 | ( 39 | 15, 40 | Literal( 41 | " \" ", 42 | ), 43 | 21, 44 | ), 45 | ], 46 | ), 47 | 21, 48 | ), 49 | ], 50 | ), 51 | 21, 52 | ), 53 | ), 54 | ] 55 | -------------------------------------------------------------------------------- /crates/eww/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eww" 3 | version = "0.4.0" 4 | authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"] 5 | description = "Widgets for everyone!" 6 | license = "MIT" 7 | repository = "https://github.com/elkowar/eww" 8 | homepage = "https://github.com/elkowar/eww" 9 | edition = "2021" 10 | 11 | 12 | 13 | [features] 14 | default = ["x11", "wayland"] 15 | x11 = ["gdkx11", "x11rb"] 16 | wayland = ["gtk-layer-shell"] 17 | 18 | [dependencies] 19 | gtk = "0.17.1" 20 | gdk = "0.17.1" 21 | glib = "0.17.8" 22 | glib-macros = "0.17.8" 23 | 24 | cairo-rs = "0.17" 25 | cairo-sys-rs = "0.17" 26 | 27 | gdk-pixbuf = "0.17" 28 | 29 | gtk-layer-shell = { version = "0.6.1", optional = true } 30 | gdkx11 = { version = "0.17", optional = true } 31 | x11rb = { version = "0.11.1", features = ["randr"], optional = true } 32 | 33 | regex = "1.9.3" 34 | bincode = "1.3.3" 35 | anyhow = "1.0.70" 36 | derive_more = "0.99" 37 | maplit = "1" 38 | clap = {version = "4.3.21", features = ["derive"] } 39 | serde = {version = "1.0", features = ["derive"]} 40 | serde_json = "1.0" 41 | extend = "1.2" 42 | grass = {version = "0.13.1", default-features = false} 43 | itertools = "0.11" 44 | log = "0.4" 45 | pretty_env_logger = "0.5" 46 | libc = "0.2" 47 | once_cell = "1.18" 48 | nix = "0.26.2" 49 | simple-signal = "1.1" 50 | unescape = "0.1" 51 | 52 | tokio = { version = "1.31.0", features = ["full"] } 53 | futures = "0.3.28" 54 | tokio-util = "0.7.8" 55 | 56 | sysinfo = "0.29.8" 57 | chrono = "0.4.26" 58 | 59 | wait-timeout = "0.2" 60 | 61 | notify = "6.0.1" 62 | 63 | codespan-reporting = "0.11" 64 | 65 | simplexpr = { version = "0.1.0", path = "../simplexpr" } 66 | eww_shared_util = { version = "0.1.0", path = "../eww_shared_util" } 67 | yuck = { version = "0.1.0", path = "../yuck", default-features = false} 68 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a bug you have encountered 3 | title: "[BUG] " 4 | labels: bug 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Checklist before submitting an issue 9 | options: 10 | - 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 11 | required: true 12 | - 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) 13 | required: true 14 | - 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) 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: "Description of the bug" 19 | description: "A clear an concise description of what the bug is." 20 | validations: 21 | required: true 22 | - type: textarea 23 | attributes: 24 | label: "Reproducing the issue" 25 | 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." 26 | validations: 27 | required: false 28 | - type: textarea 29 | attributes: 30 | label: "Expected behaviour" 31 | description: "A clear and concise description of what you expected to happen." 32 | validations: 33 | required: false 34 | - type: textarea 35 | attributes: 36 | label: "Additional context" 37 | description: "If applicable, provide additional context or screenshots here." 38 | validations: 39 | required: false 40 | -------------------------------------------------------------------------------- /docs/theme/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /crates/eww/src/client.rs: -------------------------------------------------------------------------------- 1 | use std::process::Stdio; 2 | 3 | use crate::{ 4 | daemon_response::DaemonResponse, 5 | opts::{self, ActionClientOnly}, 6 | paths::EwwPaths, 7 | }; 8 | use anyhow::{Context, Result}; 9 | use std::{ 10 | io::{Read, Write}, 11 | os::unix::net::UnixStream, 12 | }; 13 | 14 | pub fn handle_client_only_action(paths: &EwwPaths, action: ActionClientOnly) -> Result<()> { 15 | match action { 16 | ActionClientOnly::Logs => { 17 | std::process::Command::new("tail") 18 | .args(["-f", paths.get_log_file().to_string_lossy().as_ref()].iter()) 19 | .stdin(Stdio::null()) 20 | .spawn()? 21 | .wait()?; 22 | } 23 | } 24 | Ok(()) 25 | } 26 | 27 | /// Connect to the daemon and send the given request. 28 | /// 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. 29 | pub fn do_server_call(stream: &mut UnixStream, action: &opts::ActionWithServer) -> Result> { 30 | log::debug!("Forwarding options to server"); 31 | stream.set_nonblocking(false).context("Failed to set stream to non-blocking")?; 32 | 33 | let message_bytes = bincode::serialize(&action)?; 34 | 35 | stream.write(&(message_bytes.len() as u32).to_be_bytes()).context("Failed to send command size header to IPC stream")?; 36 | 37 | stream.write_all(&message_bytes).context("Failed to write command to IPC stream")?; 38 | 39 | let mut buf = Vec::new(); 40 | stream.set_read_timeout(Some(std::time::Duration::from_millis(100))).context("Failed to set read timeout")?; 41 | stream.read_to_end(&mut buf).context("Error reading response from server")?; 42 | 43 | Ok(if buf.is_empty() { 44 | None 45 | } else { 46 | let buf = bincode::deserialize(&buf)?; 47 | Some(buf) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /docs/src/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | Here you will find help if something doesn't work. If the issue isn't listed here, please [open an issue on the GitHub repo.](https://github.com/elkowar/eww/issues) 4 | 5 | ## Eww does not compile 6 | 7 | 1. Make sure that you are compiling eww using the nightly Rust toolchain. 8 | 2. Make sure you have all the necessary dependencies. If there are compile-errors, the compiler will tell you what you're missing. 9 | 10 | ## Eww does not work on Wayland 11 | 12 | 1. Make sure you compiled eww with the `--no-default-features --features=wayland` flags. 13 | 2. Make sure that you're not trying to use X11-specific features (these are (hopefully) explicitly specified as such in the documentation). 14 | 15 | ## My configuration is not loaded correctly 16 | 17 | 1. Make sure the `eww.yuck` and `eww.(s)css` files are in the correct places. 18 | 2. Sometimes, eww might fail to load your configuration as a result of a configuration error. Make sure your configuration is valid. 19 | 20 | ## Something isn't styled correctly! 21 | 22 | Check the [GTK-Debugger](working_with_gtk.md#gtk-debugger) to get more insight into what styles GTK is applying to which elements. 23 | 24 | ## General issues 25 | 26 | You should try the following things before opening an issue or doing more specialized troubleshooting: 27 | 28 | - Kill the eww daemon by running `eww kill` and re-open your window with the `--debug`-flag to get additional log output. 29 | - Now you can take a look at the logs by running `eww logs`. 30 | - Use `eww state` to see the state of all variables. 31 | - Use `eww debug` to see the structure of your widget and other information. 32 | - Update to the latest eww version. 33 | - Sometimes hot reloading doesn't work. In that case, you can make use of `eww reload` manually. 34 | 35 | Remember, if your issue isn't listed here, [open an issue on the GitHub repo](https://github.com/elkowar/eww/issues). 36 | -------------------------------------------------------------------------------- /crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/simplexpr/src/parser/lexer.rs 3 | expression: "Lexer::new(0, 0, r#\" \"foo {2 * 2} bar\" \"#).collect_vec()" 4 | 5 | --- 6 | [ 7 | Ok( 8 | ( 9 | 1, 10 | StringLit( 11 | [ 12 | ( 13 | 1, 14 | Literal( 15 | "foo ", 16 | ), 17 | 7, 18 | ), 19 | ( 20 | 7, 21 | Interp( 22 | [ 23 | ( 24 | 7, 25 | NumLit( 26 | "2", 27 | ), 28 | 8, 29 | ), 30 | ( 31 | 9, 32 | Times, 33 | 10, 34 | ), 35 | ( 36 | 11, 37 | NumLit( 38 | "2", 39 | ), 40 | 12, 41 | ), 42 | ], 43 | ), 44 | 12, 45 | ), 46 | ( 47 | 12, 48 | Literal( 49 | " bar", 50 | ), 51 | 18, 52 | ), 53 | ], 54 | ), 55 | 18, 56 | ), 57 | ), 58 | ] 59 | -------------------------------------------------------------------------------- /crates/simplexpr/src/snapshots/simplexpr__tests__test-25.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/lib.rs 3 | expression: "Lexer::new(\"foo.bar[2 + 2] * asdf[foo.bar]\").filter_map(|x|\n x.ok()).map(|(_,\n x,\n _)|\n match x\n {\n Token::Ident(x)\n |\n Token::NumLit(x)\n |\n Token::StrLit(x)\n =>\n format!(\"{}\",\n x),\n x\n =>\n format!(\"{}\",\n x),\n }).collect::>()" 4 | 5 | --- 6 | [ 7 | "foo", 8 | "Dot", 9 | "bar", 10 | "LBrack", 11 | "2", 12 | "+", 13 | "2", 14 | "RBrack", 15 | "*", 16 | "asdf", 17 | "LBrack", 18 | "foo", 19 | "Dot", 20 | "bar", 21 | "RBrack", 22 | ] 23 | -------------------------------------------------------------------------------- /crates/eww/src/config/script_var.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | use anyhow::{anyhow, bail, Context, Result}; 4 | use codespan_reporting::diagnostic::Severity; 5 | use eww_shared_util::{Span, VarName}; 6 | use simplexpr::dynval::DynVal; 7 | use yuck::{ 8 | config::script_var_definition::{ScriptVarDefinition, VarSource}, 9 | error::DiagError, 10 | gen_diagnostic, 11 | }; 12 | 13 | pub fn create_script_var_failed_warn(span: Span, var_name: &VarName, error_output: &str) -> DiagError { 14 | DiagError(gen_diagnostic! { 15 | kind = Severity::Warning, 16 | msg = format!("The script for the `{}`-variable exited unsuccessfully", var_name), 17 | label = span => "Defined here", 18 | note = error_output, 19 | }) 20 | } 21 | 22 | pub fn initial_value(var: &ScriptVarDefinition) -> Result { 23 | match var { 24 | ScriptVarDefinition::Poll(x) => match &x.initial_value { 25 | Some(value) => Ok(value.clone()), 26 | None => match &x.command { 27 | VarSource::Function(f) => f() 28 | .map_err(|err| anyhow!(err)) 29 | .with_context(|| format!("Failed to compute initial value for {}", &var.name())), 30 | VarSource::Shell(span, command) => { 31 | run_command(command).map_err(|e| anyhow!(create_script_var_failed_warn(*span, var.name(), &e.to_string()))) 32 | } 33 | }, 34 | }, 35 | 36 | ScriptVarDefinition::Listen(var) => Ok(var.initial_value.clone()), 37 | } 38 | } 39 | 40 | /// Run a command and get the output 41 | pub fn run_command(cmd: &str) -> Result { 42 | log::debug!("Running command: {}", cmd); 43 | let command = Command::new("/bin/sh").arg("-c").arg(cmd).output()?; 44 | if !command.status.success() { 45 | bail!("Failed with output:\n{}", String::from_utf8(command.stderr)?); 46 | } 47 | let output = String::from_utf8(command.stdout)?; 48 | let output = output.trim_matches('\n'); 49 | Ok(DynVal::from(output)) 50 | } 51 | -------------------------------------------------------------------------------- /crates/yuck/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | format_diagnostic::{lalrpop_error_to_diagnostic, DiagnosticExt, ToDiagnostic}, 3 | parser::{lexer, parse_error}, 4 | }; 5 | use codespan_reporting::diagnostic; 6 | use eww_shared_util::{Span, Spanned}; 7 | use simplexpr::dynval; 8 | use thiserror::Error; 9 | 10 | pub type DiagResult = Result; 11 | 12 | #[derive(Debug, Error)] 13 | #[error("{}", .0.to_message())] 14 | pub struct DiagError(pub diagnostic::Diagnostic); 15 | 16 | static_assertions::assert_impl_all!(DiagError: Send, Sync); 17 | static_assertions::assert_impl_all!(dynval::ConversionError: Send, Sync); 18 | static_assertions::assert_impl_all!(lalrpop_util::ParseError < usize, lexer::Token, parse_error::ParseError>: Send, Sync); 19 | 20 | impl From for DiagError { 21 | fn from(x: T) -> Self { 22 | Self(x.to_diagnostic()) 23 | } 24 | } 25 | 26 | impl DiagError { 27 | pub fn note(self, note: &str) -> Self { 28 | DiagError(self.0.with_note(note.to_string())) 29 | } 30 | 31 | pub fn from_parse_error( 32 | file_id: usize, 33 | err: lalrpop_util::ParseError, 34 | ) -> DiagError { 35 | DiagError(lalrpop_error_to_diagnostic(&err, file_id)) 36 | } 37 | } 38 | 39 | pub fn get_parse_error_span(file_id: usize, err: &lalrpop_util::ParseError) -> Span { 40 | use lalrpop_util::ParseError::*; 41 | match err { 42 | InvalidToken { location } => Span(*location, *location, file_id), 43 | UnrecognizedEOF { location, .. } => Span(*location, *location, file_id), 44 | UnrecognizedToken { token, .. } => Span(token.0, token.2, file_id), 45 | ExtraToken { token } => Span(token.0, token.2, file_id), 46 | User { error } => error.span(), 47 | } 48 | } 49 | 50 | pub trait DiagResultExt { 51 | fn note(self, note: &str) -> DiagResult; 52 | } 53 | 54 | impl DiagResultExt for DiagResult { 55 | fn note(self, note: &str) -> DiagResult { 56 | self.map_err(|e| e.note(note)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /docs/src/eww.md: -------------------------------------------------------------------------------- 1 | # Eww - Widgets for everyone! 2 | 3 | Eww (ElKowar's Wacky Widgets, pronounced with sufficient amounts of disgust) 4 | is a widget system made in [Rust](https://www.rust-lang.org/), 5 | which lets you create your own widgets similarly to how you can in AwesomeWM. 6 | The key difference: It is independent of your window manager! 7 | 8 | Configured in yuck and themed using CSS, it is easy to customize and provides all the flexibility you need! 9 | 10 | 11 | ## How to install Eww 12 | 13 | ### Prerequisites 14 | 15 | * rustc 16 | * cargo (nightly toolchain) 17 | 18 | Rather than with your system package manager, 19 | I recommend installing it using [rustup](https://rustup.rs/), 20 | as this makes it easy to use the nightly toolchain necessary to build eww. 21 | 22 | Additionally, eww requires some dynamic libraries to be available on your system. 23 | The exact names of the packages that provide these may differ depending on your distribution. 24 | The following list of package names should work for arch linux: 25 | 26 |
27 | Packages 28 | 29 | - gtk3 (libgdk-3, libgtk-3) 30 | - gtk-layer-shell (only on Wayland) 31 | - pango (libpango) 32 | - gdk-pixbuf2 (libgdk_pixbuf-2) 33 | - cairo (libcairo, libcairo-gobject) 34 | - glib2 (libgio, libglib-2, libgobject-2) 35 | - gcc-libs (libgcc) 36 | - glibc 37 | 38 |
39 | 40 | (Note that you will most likely need the -devel variants of your distro's packages to be able to compile eww.) 41 | 42 | ### Building 43 | 44 | Once you have the prerequisites ready, you're ready to install and build eww. 45 | 46 | First clone the repo: 47 | ```bash 48 | git clone https://github.com/elkowar/eww 49 | ``` 50 | 51 | ```bash 52 | cd eww 53 | ``` 54 | Then build: 55 | ```bash 56 | cargo build --release --no-default-features --features x11 57 | ``` 58 | **NOTE:** 59 | When you're on Wayland, build with: 60 | ```bash 61 | cargo build --release --no-default-features --features=wayland 62 | ``` 63 | 64 | ### Running eww 65 | Once you've built it you can now run it by entering: 66 | ```bash 67 | cd target/release 68 | ``` 69 | Then make the Eww binary executable: 70 | ```bash 71 | chmod +x ./eww 72 | ``` 73 | Then to run it, enter: 74 | ``` 75 | ./eww daemon 76 | ./eww open 77 | ``` 78 | -------------------------------------------------------------------------------- /examples/eww-bar/eww.yuck: -------------------------------------------------------------------------------- 1 | (defwidget bar [] 2 | (centerbox :orientation "h" 3 | (workspaces) 4 | (music) 5 | (sidestuff))) 6 | 7 | (defwidget sidestuff [] 8 | (box :class "sidestuff" :orientation "h" :space-evenly false :halign "end" 9 | (metric :label "🔊" 10 | :value volume 11 | :onchange "amixer -D pulse sset Master {}%") 12 | (metric :label "" 13 | :value {EWW_RAM.used_mem_perc} 14 | :onchange "") 15 | (metric :label "💾" 16 | :value {round((1 - (EWW_DISK["/"].free / EWW_DISK["/"].total)) * 100, 0)} 17 | :onchange "") 18 | time)) 19 | 20 | (defwidget workspaces [] 21 | (box :class "workspaces" 22 | :orientation "h" 23 | :space-evenly true 24 | :halign "start" 25 | :spacing 10 26 | (button :onclick "wmctrl -s 0" 1) 27 | (button :onclick "wmctrl -s 1" 2) 28 | (button :onclick "wmctrl -s 2" 3) 29 | (button :onclick "wmctrl -s 3" 4) 30 | (button :onclick "wmctrl -s 4" 5) 31 | (button :onclick "wmctrl -s 5" 6) 32 | (button :onclick "wmctrl -s 6" 7) 33 | (button :onclick "wmctrl -s 7" 8) 34 | (button :onclick "wmctrl -s 8" 9))) 35 | 36 | (defwidget music [] 37 | (box :class "music" 38 | :orientation "h" 39 | :space-evenly false 40 | :halign "center" 41 | {music != "" ? "🎵${music}" : ""})) 42 | 43 | 44 | (defwidget metric [label value onchange] 45 | (box :orientation "h" 46 | :class "metric" 47 | :space-evenly false 48 | (box :class "label" label) 49 | (scale :min 0 50 | :max 101 51 | :active {onchange != ""} 52 | :value value 53 | :onchange onchange))) 54 | 55 | 56 | 57 | (deflisten music :initial "" 58 | "playerctl --follow metadata --format '{{ artist }} - {{ title }}' || true") 59 | 60 | (defpoll volume :interval "1s" 61 | "scripts/getvol") 62 | 63 | (defpoll time :interval "10s" 64 | "date '+%H:%M %b %d, %Y'") 65 | 66 | (defwindow bar 67 | :monitor 0 68 | :windowtype "dock" 69 | :geometry (geometry :x "0%" 70 | :y "0%" 71 | :width "90%" 72 | :height "10px" 73 | :anchor "top center") 74 | :reserve (struts :side "top" :distance "4%") 75 | (bar)) 76 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/from_ast.rs: -------------------------------------------------------------------------------- 1 | use super::{ast::Ast, ast_iterator::AstIterator}; 2 | use crate::{error::*, format_diagnostic::ToDiagnostic, gen_diagnostic}; 3 | use eww_shared_util::{Span, Spanned}; 4 | 5 | use simplexpr::ast::SimplExpr; 6 | 7 | pub trait FromAst: Sized { 8 | fn from_ast(e: Ast) -> DiagResult; 9 | } 10 | 11 | impl FromAst for Ast { 12 | fn from_ast(e: Ast) -> DiagResult { 13 | Ok(e) 14 | } 15 | } 16 | 17 | impl FromAst for String { 18 | fn from_ast(e: Ast) -> DiagResult { 19 | Ok(e.as_simplexpr()?.eval_no_vars().map_err(|e| DiagError(e.to_diagnostic()))?.to_string()) 20 | } 21 | } 22 | 23 | /// A trait that allows creating a type from the tail of a list-node. 24 | /// I.e. to parse (foo [a b] (c d)), [`FromAstElementContent::from_tail`] would just get [a b] (c d). 25 | pub trait FromAstElementContent: Sized { 26 | const ELEMENT_NAME: &'static str; 27 | fn from_tail>(span: Span, iter: AstIterator) -> DiagResult; 28 | } 29 | 30 | impl FromAst for T { 31 | fn from_ast(e: Ast) -> DiagResult { 32 | let span = e.span(); 33 | let mut iter = e.try_ast_iter()?; 34 | let (element_name_span, element_name) = iter.expect_symbol()?; 35 | if Self::ELEMENT_NAME != element_name { 36 | return Err(DiagError(gen_diagnostic! { 37 | msg = format!("Expected element `{}`, but found `{element_name}`", Self::ELEMENT_NAME), 38 | label = element_name_span => format!("Expected `{}` here", Self::ELEMENT_NAME), 39 | note = format!("Expected: {}\n Got: {element_name}", Self::ELEMENT_NAME), 40 | })); 41 | } 42 | Self::from_tail(span, iter) 43 | } 44 | } 45 | 46 | impl FromAst for SimplExpr { 47 | fn from_ast(e: Ast) -> DiagResult { 48 | match e { 49 | Ast::Symbol(span, x) => Ok(SimplExpr::var_ref(span, x)), 50 | Ast::SimplExpr(_span, x) => Ok(x), 51 | _ => Err(DiagError(gen_diagnostic! { 52 | msg = format!("Expected value, but got `{}`", e.expr_type()), 53 | label = e.span() => "Expected some value here", 54 | note = format!("Got: {}", e.expr_type()), 55 | })), 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/eww_shared_util/src/span.rs: -------------------------------------------------------------------------------- 1 | /// A span is made up of 2 | /// - the start location 3 | /// - the end location 4 | /// - the file id 5 | #[derive(Eq, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] 6 | pub struct Span(pub usize, pub usize, pub usize); 7 | 8 | impl Span { 9 | pub const DUMMY: Span = Span(usize::MAX, usize::MAX, usize::MAX); 10 | 11 | pub fn point(loc: usize, file_id: usize) -> Self { 12 | Span(loc, loc, file_id) 13 | } 14 | 15 | /// Get the span that includes this and the other span completely. 16 | /// Will panic if the spans are from different file_ids. 17 | pub fn to(mut self, other: Span) -> Self { 18 | assert!(other.2 == self.2); 19 | self.1 = other.1; 20 | self 21 | } 22 | 23 | pub fn ending_at(mut self, end: usize) -> Self { 24 | self.1 = end; 25 | self 26 | } 27 | 28 | /// Turn this span into a span only highlighting the point it starts at, setting the length to 0. 29 | pub fn point_span(mut self) -> Self { 30 | self.1 = self.0; 31 | self 32 | } 33 | 34 | /// Turn this span into a span only highlighting the point it ends at, setting the length to 0. 35 | pub fn point_span_at_end(mut self) -> Self { 36 | self.0 = self.1; 37 | self 38 | } 39 | 40 | pub fn shifted(mut self, n: isize) -> Self { 41 | self.0 = isize::max(0, self.0 as isize + n) as usize; 42 | self.1 = isize::max(0, self.0 as isize + n) as usize; 43 | self 44 | } 45 | 46 | pub fn new_relative(mut self, other_start: usize, other_end: usize) -> Self { 47 | self.0 += other_start; 48 | self.1 += other_end; 49 | self 50 | } 51 | 52 | pub fn is_dummy(&self) -> bool { 53 | *self == Self::DUMMY 54 | } 55 | } 56 | 57 | impl std::fmt::Display for Span { 58 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 59 | if self.is_dummy() { 60 | write!(f, "DUMMY") 61 | } else { 62 | write!(f, "{}..{}", self.0, self.1) 63 | } 64 | } 65 | } 66 | 67 | impl std::fmt::Debug for Span { 68 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 69 | write!(f, "{}", self) 70 | } 71 | } 72 | 73 | pub trait Spanned { 74 | fn span(&self) -> Span; 75 | } 76 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1650374568, 7 | "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", 8 | "owner": "edolstra", 9 | "repo": "flake-compat", 10 | "rev": "b4a34015c698c7793d592d66adbab377907a2be8", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "edolstra", 15 | "repo": "flake-compat", 16 | "type": "github" 17 | } 18 | }, 19 | "flake-utils": { 20 | "locked": { 21 | "lastModified": 1656928814, 22 | "narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=", 23 | "owner": "numtide", 24 | "repo": "flake-utils", 25 | "rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "numtide", 30 | "repo": "flake-utils", 31 | "type": "github" 32 | } 33 | }, 34 | "nixpkgs": { 35 | "locked": { 36 | "lastModified": 1661353537, 37 | "narHash": "sha256-1E2IGPajOsrkR49mM5h55OtYnU0dGyre6gl60NXKITE=", 38 | "owner": "nixos", 39 | "repo": "nixpkgs", 40 | "rev": "0e304ff0d9db453a4b230e9386418fd974d5804a", 41 | "type": "github" 42 | }, 43 | "original": { 44 | "owner": "nixos", 45 | "ref": "nixpkgs-unstable", 46 | "repo": "nixpkgs", 47 | "type": "github" 48 | } 49 | }, 50 | "root": { 51 | "inputs": { 52 | "flake-compat": "flake-compat", 53 | "nixpkgs": "nixpkgs", 54 | "rust-overlay": "rust-overlay" 55 | } 56 | }, 57 | "rust-overlay": { 58 | "inputs": { 59 | "flake-utils": "flake-utils", 60 | "nixpkgs": [ 61 | "nixpkgs" 62 | ] 63 | }, 64 | "locked": { 65 | "lastModified": 1661655464, 66 | "narHash": "sha256-by9Hb0mNVdiCR7TBvUHIgDb0QIv3znp8VMGh7Bl35VQ=", 67 | "owner": "oxalica", 68 | "repo": "rust-overlay", 69 | "rev": "0c4c1432353e12b325d1472bea99e364871d2cb3", 70 | "type": "github" 71 | }, 72 | "original": { 73 | "owner": "oxalica", 74 | "repo": "rust-overlay", 75 | "type": "github" 76 | } 77 | } 78 | }, 79 | "root": "root", 80 | "version": 7 81 | } 82 | -------------------------------------------------------------------------------- /crates/yuck/src/ast_error.rs: -------------------------------------------------------------------------------- 1 | use eww_shared_util::{AttrName, Span}; 2 | 3 | use crate::{format_diagnostic::ToDiagnostic, gen_diagnostic, parser::ast::AstType}; 4 | 5 | /// Error type representing errors that occur when trying to access parts of the AST specifically 6 | #[derive(Debug, thiserror::Error)] 7 | pub enum AstError { 8 | #[error("Did not expect any further elements here. Make sure your format is correct")] 9 | NoMoreElementsExpected(Span), 10 | 11 | #[error("Expected more elements")] 12 | TooFewElements(Span), 13 | 14 | #[error("Wrong type of expression: Expected {1} but got {2}")] 15 | WrongExprType(Span, AstType, AstType), 16 | 17 | #[error("'{0}' is missing a value")] 18 | DanglingKeyword(Span, AttrName), 19 | 20 | /// May occur when we need to evaluate an expression when expecting a literal value 21 | #[error(transparent)] 22 | EvalError(#[from] simplexpr::eval::EvalError), 23 | } 24 | 25 | impl ToDiagnostic for AstError { 26 | fn to_diagnostic(&self) -> codespan_reporting::diagnostic::Diagnostic { 27 | match self { 28 | AstError::NoMoreElementsExpected(span) => gen_diagnostic!(self, span), 29 | AstError::TooFewElements(span) => gen_diagnostic! { 30 | msg = self, 31 | label = span => "Expected another element here" 32 | }, 33 | AstError::WrongExprType(span, expected, actual) => gen_diagnostic! { 34 | msg = "Wrong type of expression", 35 | label = span => format!("Expected a `{expected}` here"), 36 | note = format!("Expected: {expected}\n Got: {actual}"), 37 | }, 38 | AstError::DanglingKeyword(span, kw) => gen_diagnostic! { 39 | msg = format!("{kw} is missing a value"), 40 | label = span => "No value provided for this", 41 | }, 42 | AstError::EvalError(e) => e.to_diagnostic(), 43 | } 44 | } 45 | } 46 | 47 | impl eww_shared_util::Spanned for AstError { 48 | fn span(&self) -> Span { 49 | match self { 50 | AstError::NoMoreElementsExpected(span) => *span, 51 | AstError::TooFewElements(span) => *span, 52 | AstError::WrongExprType(span, ..) => *span, 53 | AstError::DanglingKeyword(span, _) => *span, 54 | AstError::EvalError(e) => e.span(), 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | flake-compat = { url = "github:edolstra/flake-compat"; flake = false; }; 4 | rust-overlay.url = "github:oxalica/rust-overlay"; 5 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 6 | rust-overlay.inputs.nixpkgs.follows = "nixpkgs"; 7 | }; 8 | 9 | outputs = { self, nixpkgs, rust-overlay, flake-compat, ... }: 10 | let 11 | pkgsFor = system: import nixpkgs { 12 | inherit system; 13 | 14 | overlays = [ 15 | self.overlays.default 16 | rust-overlay.overlays.default 17 | ]; 18 | }; 19 | 20 | targetSystems = [ "aarch64-linux" "x86_64-linux" ]; 21 | mkRustToolchain = pkgs: pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; 22 | in 23 | { 24 | overlays.default = final: prev: 25 | let 26 | rust = mkRustToolchain final; 27 | 28 | rustPlatform = prev.makeRustPlatform { 29 | cargo = rust; 30 | rustc = rust; 31 | }; 32 | in 33 | { 34 | eww = (prev.eww.override { inherit rustPlatform; }).overrideAttrs (old: { 35 | version = self.rev or "dirty"; 36 | src = builtins.path { name = "eww"; path = prev.lib.cleanSource ./.; }; 37 | cargoDeps = rustPlatform.importCargoLock { lockFile = ./Cargo.lock; }; 38 | patches = [ ]; 39 | }); 40 | 41 | eww-wayland = final.eww.override { withWayland = true; }; 42 | }; 43 | 44 | packages = nixpkgs.lib.genAttrs targetSystems (system: 45 | let 46 | pkgs = pkgsFor system; 47 | in 48 | (self.overlays.default pkgs pkgs) // { 49 | default = self.packages.${system}.eww; 50 | } 51 | ); 52 | 53 | devShells = nixpkgs.lib.genAttrs targetSystems (system: 54 | let 55 | pkgs = pkgsFor system; 56 | rust = mkRustToolchain pkgs; 57 | in 58 | { 59 | default = pkgs.mkShell { 60 | packages = with pkgs; [ 61 | rust 62 | rust-analyzer-unwrapped 63 | gcc 64 | gtk3 65 | gtk-layer-shell 66 | pkg-config 67 | deno 68 | mdbook 69 | ]; 70 | 71 | RUST_SRC_PATH = "${rust}/lib/rustlib/src/rust/library"; 72 | }; 73 | } 74 | ); 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/parser.lalrpop: -------------------------------------------------------------------------------- 1 | use crate::parser::{lexer::Token, ast::Ast, parse_error}; 2 | use eww_shared_util::Span; 3 | use simplexpr::ast::SimplExpr; 4 | use simplexpr; 5 | use lalrpop_util::ParseError; 6 | 7 | grammar(file_id: usize); 8 | 9 | extern { 10 | type Location = usize; 11 | type Error = parse_error::ParseError; 12 | 13 | enum Token { 14 | "(" => Token::LPren, 15 | ")" => Token::RPren, 16 | "[" => Token::LBrack, 17 | "]" => Token::RBrack, 18 | "true" => Token::True, 19 | "false" => Token::False, 20 | "number" => Token::NumLit(), 21 | "symbol" => Token::Symbol(), 22 | "keyword" => Token::Keyword(), 23 | "simplexpr" => Token::SimplExpr(>), 24 | "comment" => Token::Comment, 25 | } 26 | } 27 | 28 | pub Toplevel: (Span, Vec) = { 29 | )*> => (Span(l, r, file_id), elems) 30 | } 31 | 32 | pub Ast: Ast = { 33 | "(" )*> ")" => Ast::List(Span(l, r, file_id), elems), 34 | "[" )*> "]" => Ast::Array(Span(l, r, file_id), elems), 35 | => Ast::SimplExpr(Span(l, r, file_id), expr), 36 | => x, 37 | => x, 38 | => Ast::SimplExpr(Span(l, r, file_id), SimplExpr::literal(Span(l, r, file_id), x.into())), 39 | "comment" => Ast::Comment(Span(l, r, file_id)), 40 | }; 41 | 42 | Keyword: Ast = => Ast::Keyword(Span(l, r, file_id), x[1..].to_string()); 43 | Symbol: Ast = => Ast::Symbol(Span(l, r, file_id), x.to_string()); 44 | 45 | Literal: String = { 46 | => <>, 47 | => <>, 48 | }; 49 | 50 | SimplExpr: SimplExpr = { 51 | =>? { 52 | let parser = simplexpr::simplexpr_parser::ExprParser::new(); 53 | parser.parse(file_id, x.into_iter().map(Ok)) 54 | .map_err(|e| ParseError::User { 55 | error: parse_error::ParseError::SimplExpr(simplexpr::error::ParseError::from_parse_error(file_id, e)) 56 | }) 57 | } 58 | } 59 | 60 | 61 | Num: String = <"number"> => <>.to_string(); 62 | Bool: String = { 63 | "true" => "true".to_string(), 64 | "false" => "false".to_string(), 65 | } 66 | 67 | 68 | // vim:shiftwidth=4 69 | -------------------------------------------------------------------------------- /crates/eww/src/error_handling_ctx.rs: -------------------------------------------------------------------------------- 1 | //! Disgusting global state. 2 | //! I hate this, but [buffet](https://github.com/buffet) told me that this is what I should do for peak maintainability! 3 | 4 | use std::sync::{Arc, RwLock}; 5 | 6 | use codespan_reporting::{ 7 | diagnostic::Diagnostic, 8 | term::{self, Chars}, 9 | }; 10 | use eww_shared_util::Span; 11 | use once_cell::sync::Lazy; 12 | use simplexpr::{dynval::ConversionError, eval::EvalError}; 13 | use yuck::{config::validate::ValidationError, error::DiagError, format_diagnostic::ToDiagnostic}; 14 | 15 | use crate::file_database::FileDatabase; 16 | 17 | pub static FILE_DATABASE: Lazy>> = Lazy::new(|| Arc::new(RwLock::new(FileDatabase::new()))); 18 | 19 | pub fn clear_files() { 20 | *FILE_DATABASE.write().unwrap() = FileDatabase::new(); 21 | } 22 | 23 | pub fn print_error(err: anyhow::Error) { 24 | match anyhow_err_to_diagnostic(&err) { 25 | Some(diag) => match stringify_diagnostic(diag) { 26 | Ok(diag) => eprintln!("{}", diag), 27 | Err(_) => log::error!("{:?}", err), 28 | }, 29 | None => log::error!("{:?}", err), 30 | } 31 | } 32 | 33 | pub fn format_error(err: &anyhow::Error) -> String { 34 | for err in err.chain() { 35 | format!("chain: {}", err); 36 | } 37 | anyhow_err_to_diagnostic(err).and_then(|diag| stringify_diagnostic(diag).ok()).unwrap_or_else(|| format!("{:?}", err)) 38 | } 39 | 40 | pub fn anyhow_err_to_diagnostic(err: &anyhow::Error) -> Option> { 41 | #[allow(clippy::manual_map)] 42 | if let Some(err) = err.downcast_ref::() { 43 | Some(err.0.clone()) 44 | } else if let Some(err) = err.downcast_ref::() { 45 | Some(err.to_diagnostic()) 46 | } else if let Some(err) = err.downcast_ref::() { 47 | Some(err.to_diagnostic()) 48 | } else if let Some(err) = err.downcast_ref::() { 49 | Some(err.to_diagnostic()) 50 | } else { 51 | None 52 | } 53 | } 54 | 55 | pub fn stringify_diagnostic(mut diagnostic: codespan_reporting::diagnostic::Diagnostic) -> anyhow::Result { 56 | diagnostic.labels.retain(|label| !Span(label.range.start, label.range.end, label.file_id).is_dummy()); 57 | 58 | let mut config = term::Config::default(); 59 | let mut chars = Chars::box_drawing(); 60 | chars.single_primary_caret = '─'; 61 | config.chars = chars; 62 | config.chars.note_bullet = '→'; 63 | let mut buf = Vec::new(); 64 | let mut writer = term::termcolor::Ansi::new(&mut buf); 65 | let files = FILE_DATABASE.read().unwrap(); 66 | term::emit(&mut writer, &config, &*files, &diagnostic)?; 67 | Ok(String::from_utf8(buf)?) 68 | } 69 | -------------------------------------------------------------------------------- /crates/eww/src/daemon_response.rs: -------------------------------------------------------------------------------- 1 | //! Types to manage messages that notify the eww client over the result of a command 2 | //! 3 | //! Communcation between the daemon and eww client happens via IPC. 4 | //! If the daemon needs to send messages back to the client as a response to a command (mostly for CLI output), 5 | //! this happens via the DaemonResponse types 6 | 7 | use anyhow::{Context, Result}; 8 | use itertools::Itertools; 9 | use tokio::sync::mpsc; 10 | 11 | use crate::error_handling_ctx; 12 | 13 | /// Response that the app may send as a response to a event. 14 | /// This is used in `DaemonCommand`s that contain a response sender. 15 | #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, derive_more::Display)] 16 | pub enum DaemonResponse { 17 | Success(String), 18 | Failure(String), 19 | } 20 | 21 | #[derive(Debug)] 22 | pub struct DaemonResponseSender(mpsc::UnboundedSender); 23 | 24 | pub fn create_pair() -> (DaemonResponseSender, mpsc::UnboundedReceiver) { 25 | let (sender, recv) = mpsc::unbounded_channel(); 26 | (DaemonResponseSender(sender), recv) 27 | } 28 | 29 | impl DaemonResponseSender { 30 | pub fn send_success(&self, s: String) -> Result<()> { 31 | self.0.send(DaemonResponse::Success(s)).context("Failed to send success response from application thread") 32 | } 33 | 34 | pub fn send_failure(&self, s: String) -> Result<()> { 35 | self.0.send(DaemonResponse::Failure(s)).context("Failed to send failure response from application thread") 36 | } 37 | 38 | /// Given a list of errors, respond with an error value if there are any errors, and respond with success otherwise. 39 | pub fn respond_with_error_list(&self, errors: impl IntoIterator) -> Result<()> { 40 | let errors = errors.into_iter().map(|e| error_handling_ctx::format_error(&e)).join("\n"); 41 | if errors.is_empty() { 42 | self.send_success(String::new()) 43 | } else { 44 | self.respond_with_error_msg(errors) 45 | } 46 | } 47 | 48 | /// In case of an Err, send the error message to a sender. 49 | pub fn respond_with_result(&self, result: Result) -> Result<()> { 50 | match result { 51 | Ok(_) => self.send_success(String::new()), 52 | Err(e) => { 53 | let formatted = error_handling_ctx::format_error(&e); 54 | self.respond_with_error_msg(formatted) 55 | } 56 | } 57 | .context("sending response from main thread") 58 | } 59 | 60 | fn respond_with_error_msg(&self, msg: String) -> Result<()> { 61 | println!("Action failed with error: {}", msg); 62 | self.send_failure(msg) 63 | } 64 | } 65 | 66 | pub type DaemonResponseReceiver = mpsc::UnboundedReceiver; 67 | -------------------------------------------------------------------------------- /crates/eww/src/widgets/mod.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | pub mod build_widget; 4 | pub mod circular_progressbar; 5 | pub mod def_widget_macro; 6 | pub mod graph; 7 | pub mod transform; 8 | pub mod widget_definitions; 9 | 10 | /// Run a command that was provided as an attribute. 11 | /// This command may use placeholders which will be replaced by the values of the arguments given. 12 | /// This can either be the placeholder `{}`, which will be replaced by the first argument, 13 | /// Or a placeholder like `{0}`, `{1}`, etc, which will refer to the respective argument. 14 | pub(self) fn run_command(timeout: std::time::Duration, cmd: &str, args: &[T]) 15 | where 16 | T: 'static + std::fmt::Display + Send + Sync + Clone, 17 | { 18 | use wait_timeout::ChildExt; 19 | let cmd = replace_placeholders(cmd, args); 20 | std::thread::Builder::new() 21 | .name("command-execution-thread".to_string()) 22 | .spawn(move || { 23 | log::debug!("Running command from widget [timeout: {}ms]: {}", timeout.as_millis(), cmd); 24 | let child = Command::new("/bin/sh").arg("-c").arg(&cmd).spawn(); 25 | match child { 26 | Ok(mut child) => match child.wait_timeout(timeout) { 27 | // child timed out 28 | Ok(None) => { 29 | log::error!("WARNING: command {} timed out", &cmd); 30 | let _ = child.kill(); 31 | let _ = child.wait(); 32 | } 33 | Err(err) => log::error!("Failed to execute command {}: {}", cmd, err), 34 | Ok(Some(_)) => {} 35 | }, 36 | Err(err) => log::error!("Failed to launch child process: {}", err), 37 | } 38 | }) 39 | .expect("Failed to start command-execution-thread"); 40 | } 41 | 42 | fn replace_placeholders(cmd: &str, args: &[T]) -> String 43 | where 44 | T: 'static + std::fmt::Display + Send + Sync + Clone, 45 | { 46 | if !args.is_empty() { 47 | let cmd = cmd.replace("{}", &format!("{}", args[0])); 48 | args.iter().enumerate().fold(cmd, |acc, (i, arg)| acc.replace(&format!("{{{}}}", i), &format!("{}", arg))) 49 | } else { 50 | cmd.to_string() 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod test { 56 | use super::*; 57 | #[test] 58 | fn test_replace_placeholders() { 59 | assert_eq!("foo", replace_placeholders("foo", &[""]),); 60 | assert_eq!("foo hi", replace_placeholders("foo {}", &["hi"]),); 61 | assert_eq!("foo hi", replace_placeholders("foo {}", &["hi", "ho"]),); 62 | assert_eq!("bar foo baz", replace_placeholders("{0} foo {1}", &["bar", "baz"]),); 63 | assert_eq!("baz foo bar", replace_placeholders("{1} foo {0}", &["bar", "baz"]),); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | use eww_shared_util::{Span, Spanned}; 2 | use lalrpop_util::lalrpop_mod; 3 | 4 | use crate::gen_diagnostic; 5 | 6 | use super::error::{DiagError, DiagResult}; 7 | use ast::Ast; 8 | 9 | pub mod ast; 10 | pub mod ast_iterator; 11 | pub mod from_ast; 12 | pub(crate) mod lexer; 13 | pub(crate) mod parse_error; 14 | 15 | lalrpop_mod!( 16 | #[allow(clippy::all)] 17 | pub parser, 18 | "/parser/parser.rs" 19 | ); 20 | 21 | pub fn parse_string(file_id: usize, s: &str) -> DiagResult { 22 | let lexer = lexer::Lexer::new(file_id, s.to_string()); 23 | let parser = parser::AstParser::new(); 24 | parser.parse(file_id, lexer).map_err(|e| DiagError::from_parse_error(file_id, e)) 25 | } 26 | 27 | /// Parse multiple toplevel nodes into a list of [Ast] 28 | pub fn parse_toplevel(file_id: usize, s: String) -> DiagResult<(Span, Vec)> { 29 | let lexer = lexer::Lexer::new(file_id, s); 30 | let parser = parser::ToplevelParser::new(); 31 | parser.parse(file_id, lexer).map_err(|e| DiagError::from_parse_error(file_id, e)) 32 | } 33 | 34 | /// get a single ast node from a list of asts, returning an Err if the length is not exactly 1. 35 | pub fn require_single_toplevel(span: Span, mut asts: Vec) -> DiagResult { 36 | match asts.len() { 37 | 1 => Ok(asts.remove(0)), 38 | 0 => Err(DiagError(gen_diagnostic! { 39 | msg = "Expected exactly one element, but got none", 40 | label = span 41 | })), 42 | _n => Err(DiagError(gen_diagnostic! { 43 | msg = "Expected exactly one element, but but got {n}", 44 | label = asts.get(1).unwrap().span().to(asts.last().unwrap().span()) => "these elements must not be here", 45 | note = "Consider wrapping the elements in some container element", 46 | })), 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod test { 52 | use super::*; 53 | macro_rules! test_parser { 54 | ($($text:literal),*) => {{ 55 | let p = parser::AstParser::new(); 56 | use lexer::Lexer; 57 | 58 | ::insta::with_settings!({sort_maps => true}, { 59 | $( 60 | ::insta::assert_debug_snapshot!(p.parse(0, Lexer::new(0, $text.to_string()))); 61 | )* 62 | }); 63 | }} 64 | } 65 | 66 | #[test] 67 | fn test() { 68 | test_parser!( 69 | "1", 70 | "(12)", 71 | "1.2", 72 | "-1.2", 73 | "(1 2)", 74 | "(1 :foo 1)", 75 | "(:foo 1)", 76 | "(:foo->: 1)", 77 | "(foo 1)", 78 | "(lol😄 1)", 79 | r#"(test "hi")"#, 80 | r#"(test "h\"i")"#, 81 | r#"(test " hi ")"#, 82 | "(+ (1 2 (* 2 5)))", 83 | r#"foo ; test"#, 84 | r#"(f arg ; test 85 | arg2)"#, 86 | "\"h\\\"i\"" 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /crates/eww/src/ipc_server.rs: -------------------------------------------------------------------------------- 1 | use crate::{app, opts}; 2 | use anyhow::{Context, Result}; 3 | use std::time::Duration; 4 | use tokio::{ 5 | io::{AsyncReadExt, AsyncWriteExt}, 6 | sync::mpsc::*, 7 | }; 8 | 9 | pub async fn run_server>(evt_send: UnboundedSender, socket_path: P) -> Result<()> { 10 | let socket_path = socket_path.as_ref(); 11 | let listener = { tokio::net::UnixListener::bind(socket_path)? }; 12 | log::info!("IPC server initialized"); 13 | crate::loop_select_exiting! { 14 | connection = listener.accept() => match connection { 15 | Ok((stream, _addr)) => { 16 | let evt_send = evt_send.clone(); 17 | tokio::spawn(async move { 18 | let result = handle_connection(stream, evt_send.clone()).await; 19 | crate::print_result_err!("while handling IPC connection with client", result); 20 | }); 21 | }, 22 | Err(e) => eprintln!("Failed to connect to client: {:?}", e), 23 | } 24 | } 25 | Ok(()) 26 | } 27 | 28 | /// Handle a single IPC connection from start to end. 29 | async fn handle_connection(mut stream: tokio::net::UnixStream, evt_send: UnboundedSender) -> Result<()> { 30 | let (mut stream_read, mut stream_write) = stream.split(); 31 | 32 | let action: opts::ActionWithServer = read_action_from_stream(&mut stream_read).await?; 33 | 34 | log::debug!("received command from IPC: {:?}", &action); 35 | 36 | let (command, maybe_response_recv) = action.into_daemon_command(); 37 | 38 | evt_send.send(command)?; 39 | 40 | if let Some(mut response_recv) = maybe_response_recv { 41 | log::debug!("Waiting for response for IPC client"); 42 | if let Ok(Some(response)) = tokio::time::timeout(Duration::from_millis(100), response_recv.recv()).await { 43 | let response = bincode::serialize(&response)?; 44 | let result = &stream_write.write_all(&response).await; 45 | crate::print_result_err!("sending text response to ipc client", &result); 46 | } 47 | } 48 | stream_write.shutdown().await?; 49 | Ok(()) 50 | } 51 | 52 | /// Read a single message from a unix stream, and parses it into a `ActionWithServer` 53 | /// 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. 54 | async fn read_action_from_stream(stream_read: &'_ mut tokio::net::unix::ReadHalf<'_>) -> Result { 55 | let mut message_byte_length = [0u8; 4]; 56 | stream_read.read_exact(&mut message_byte_length).await.context("Failed to read message size header in IPC message")?; 57 | let message_byte_length = u32::from_be_bytes(message_byte_length); 58 | let mut raw_message = Vec::::with_capacity(message_byte_length as usize); 59 | while raw_message.len() < message_byte_length as usize { 60 | stream_read.read_buf(&mut raw_message).await.context("Failed to read actual IPC message")?; 61 | } 62 | 63 | bincode::deserialize(&raw_message).context("Failed to parse client message") 64 | } 65 | -------------------------------------------------------------------------------- /crates/yuck/src/config/snapshots/yuck__config__test__config.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/yuck/src/config/test.rs 3 | expression: config.unwrap() 4 | 5 | --- 6 | Config( 7 | widget_definitions: { 8 | "bar": WidgetDefinition( 9 | name: "bar", 10 | expected_args: [ 11 | AttrSpec( 12 | name: AttrName("arg"), 13 | optional: false, 14 | span: Span(25, 28, 0), 15 | ), 16 | AttrSpec( 17 | name: AttrName("arg2"), 18 | optional: false, 19 | span: Span(29, 33, 0), 20 | ), 21 | ], 22 | widget: Basic(BasicWidgetUse( 23 | name: "foo", 24 | attrs: Attributes( 25 | span: Span(51, 61, 0), 26 | attrs: { 27 | AttrName("arg"): AttrEntry( 28 | key_span: Span(52, 56, 0), 29 | value: SimplExpr(Span(57, 61, 0), Literal(DynVal("hi", Span(57, 61, 0)))), 30 | ), 31 | }, 32 | ), 33 | children: [], 34 | span: Span(47, 62, 0), 35 | name_span: Span(48, 51, 0), 36 | )), 37 | span: Span(9, 63, 0), 38 | args_span: Span(24, 34, 0), 39 | ), 40 | }, 41 | window_definitions: { 42 | "some-window": WindowDefinition( 43 | name: "some-window", 44 | geometry: Some(WindowGeometry( 45 | anchor_point: AnchorPoint( 46 | x: START, 47 | y: START, 48 | ), 49 | offset: Coords( 50 | x: Pixels(0), 51 | y: Pixels(0), 52 | ), 53 | size: Coords( 54 | x: Percent(12), 55 | y: Pixels(20), 56 | ), 57 | )), 58 | stacking: Foreground, 59 | monitor_number: Some(12), 60 | widget: Basic(BasicWidgetUse( 61 | name: "bar", 62 | attrs: Attributes( 63 | span: Span(467, 478, 0), 64 | attrs: { 65 | AttrName("arg"): AttrEntry( 66 | key_span: Span(468, 472, 0), 67 | value: SimplExpr(Span(473, 478, 0), Literal(DynVal("bla", Span(473, 478, 0)))), 68 | ), 69 | }, 70 | ), 71 | children: [], 72 | span: Span(463, 479, 0), 73 | name_span: Span(464, 467, 0), 74 | )), 75 | resizable: true, 76 | backend_options: BackendWindowOptions( 77 | wm_ignore: false, 78 | sticky: true, 79 | window_type: Dock, 80 | struts: StrutDefinition( 81 | side: Left, 82 | dist: Pixels(30), 83 | ), 84 | ), 85 | ), 86 | }, 87 | var_definitions: { 88 | VarName("some_var"): VarDefinition( 89 | name: VarName("some_var"), 90 | initial_value: DynVal("bla", Span(89, 94, 0)), 91 | span: Span(72, 95, 0), 92 | ), 93 | }, 94 | script_vars: { 95 | VarName("stuff"): Listen(ListenScriptVar( 96 | name: VarName("stuff"), 97 | command: "tail -f stuff", 98 | initial_value: DynVal("", Span(18446744073709551615, 18446744073709551615, 18446744073709551615)), 99 | command_span: Span(168, 183, 0), 100 | name_span: Span(162, 167, 0), 101 | )), 102 | }, 103 | ) 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![dependency status](https://deps.rs/repo/github/elkowar/eww/status.svg)](https://deps.rs/repo/github/elkowar/eww) 2 | 3 | # Eww 4 | 5 | 6 | 7 | Elkowars Wacky Widgets is a standalone widget system made in Rust that allows you to implement 8 | your own, custom widgets in any window manager. 9 | 10 | Documentation **and instructions on how to install** can be found [here](https://elkowar.github.io/eww). 11 | 12 | Dharmx also wrote a nice, beginner friendly introductory guide for eww [here](https://dharmx.is-a.dev/eww-powermenu/). 13 | 14 | ## Eww needs your opinion! 15 | I've hit a bit of a design roadblock for one of the bigger features that are in the works right now. 16 | 17 | **Please read through https://github.com/elkowar/eww/discussions/453 and share your thoughts, ideas and opinions!** 18 | 19 | ## Examples 20 | 21 | (Note that some of these still make use of the old configuration syntax.) 22 | 23 | * A basic bar, see [examples](./examples/eww-bar) 24 | ![Example 1](./examples/eww-bar/eww-bar.png) 25 | 26 | * [Some setups by Druskus20](https://github.com/druskus20/eugh) 27 | ![Druskus20-bar](https://raw.githubusercontent.com/druskus20/eugh/master/polybar-replacement/.github/preview.png) 28 | 29 | * [My own vertical bar](https://github.com/elkowar/dots-of-war/tree/master/eww-bar/.config/eww-bar) 30 | 31 | 32 | 33 | * [Vertical Bar by Rxyhn](https://github.com/rxyhn/bspdots) 34 | 35 |
36 | 37 | ![Rxyhn-rice](https://user-images.githubusercontent.com/93292023/152228869-d618335a-7a1e-40f7-95f9-b1cf401be89e.gif) 38 | 39 |
40 | 41 | * [Setup by Axarva](https://github.com/Axarva/dotfiles-2.0) 42 | ![Axarva-rice](https://raw.githubusercontent.com/Axarva/dotfiles-2.0/main/screenshots/center.png) 43 | 44 | * [Setup by adi1090x](https://github.com/adi1090x/widgets) 45 | ![Nordic](https://raw.githubusercontent.com/adi1090x/widgets/main/previews/dashboard.png) 46 | 47 | * [i3 Bar replacement by owenrumney](https://github.com/owenrumney/eww-bar) 48 | ![Top bar](https://raw.githubusercontent.com/owenrumney/eww-bar/master/.github/topbar.gif) 49 | ![Bottom bar](https://raw.githubusercontent.com/owenrumney/eww-bar/master/.github/bottombar.gif) 50 | 51 | * [Setups by iSparsh](https://github.com/iSparsh/gross) 52 | ![iSparsh-gross](https://user-images.githubusercontent.com/57213270/140309158-e65cbc1d-f3a8-4aec-848c-eef800de3364.png) 53 | 54 | * [topbar by saimoomedits](https://github.com/Saimoomedits/eww-widgets) 55 | 56 |
57 | 58 | ![eww-top-bar](https://user-images.githubusercontent.com/72156551/153045183-227b62b2-223a-4a5b-a499-3f31044b5b65.gif) 59 | 60 |
61 | 62 | 63 | ## Contribewwting 64 | 65 | If you want to contribute anything, like adding new widgets, features, or subcommands (including sample configs), you should definitely do so. 66 | 67 | ### Steps 68 | 1. Fork this repository 69 | 2. Install dependencies 70 | 3. Smash your head against the keyboard from frustration (coding is hard) 71 | 4. Write down your changes in CHANGELOG.md 72 | 5. Open a pull request once you're finished 73 | -------------------------------------------------------------------------------- /crates/yuck/src/config/snapshots/eww_config__config__test__config.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/config/test.rs 3 | expression: config.unwrap() 4 | 5 | --- 6 | Config( 7 | widget_definitions: { 8 | "bar": WidgetDefinition( 9 | name: "bar", 10 | expected_args: [ 11 | AttrName("arg"), 12 | AttrName("arg2"), 13 | ], 14 | widget: WidgetUse( 15 | name: "text", 16 | attrs: Attributes( 17 | span: Span(99, 104, 0), 18 | attrs: { 19 | AttrName("text"): AttrEntry( 20 | key_span: Span(99, 104, 0), 21 | value: Literal(Span(99, 104, 0), DynVal("bla", None)), 22 | ), 23 | }, 24 | ), 25 | children: [], 26 | span: Span(99, 104, 0), 27 | ), 28 | span: Span(61, 105, 0), 29 | args_span: Span(76, 86, 0), 30 | ), 31 | "foo": WidgetDefinition( 32 | name: "foo", 33 | expected_args: [ 34 | AttrName("arg"), 35 | ], 36 | widget: WidgetUse( 37 | name: "text", 38 | attrs: Attributes( 39 | span: Span(44, 51, 0), 40 | attrs: { 41 | AttrName("text"): AttrEntry( 42 | key_span: Span(44, 51, 0), 43 | value: Literal(Span(44, 51, 0), DynVal("heyho", None)), 44 | ), 45 | }, 46 | ), 47 | children: [], 48 | span: Span(44, 51, 0), 49 | ), 50 | span: Span(11, 52, 0), 51 | args_span: Span(26, 31, 0), 52 | ), 53 | }, 54 | window_definitions: { 55 | "some-window": WindowDefinition( 56 | name: "some-window", 57 | geometry: Some(WindowGeometry( 58 | anchor_point: AnchorPoint( 59 | x: START, 60 | y: START, 61 | ), 62 | offset: Coords( 63 | x: Pixels(0), 64 | y: Pixels(0), 65 | ), 66 | size: Coords( 67 | x: Percent(12), 68 | y: Pixels(20), 69 | ), 70 | )), 71 | stacking: Foreground, 72 | monitor_number: Some(12), 73 | widget: WidgetUse( 74 | name: "foo", 75 | attrs: Attributes( 76 | span: Span(509, 509, 513), 77 | attrs: { 78 | AttrName("arg"): AttrEntry( 79 | key_span: Span(514, 518, 0), 80 | value: Literal(Span(519, 524, 0), DynVal("bla", None)), 81 | ), 82 | }, 83 | ), 84 | children: [], 85 | span: Span(509, 525, 0), 86 | ), 87 | resizable: true, 88 | backend_options: X11WindowOptions( 89 | wm_ignore: false, 90 | sticky: true, 91 | window_type: Dock, 92 | struts: StrutDefinition( 93 | side: Left, 94 | dist: Pixels(30), 95 | ), 96 | ), 97 | ), 98 | }, 99 | var_definitions: { 100 | VarName("some_var"): VarDefinition( 101 | name: VarName("some_var"), 102 | initial_value: DynVal("bla", None), 103 | span: Span(114, 137, 0), 104 | ), 105 | }, 106 | script_vars: { 107 | VarName("stuff"): Tail(TailScriptVar( 108 | name: VarName("stuff"), 109 | command: "tail -f stuff", 110 | )), 111 | }, 112 | ) 113 | -------------------------------------------------------------------------------- /crates/yuck/src/config/widget_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::{DiagError, DiagResult, DiagResultExt}, 3 | format_diagnostic::ToDiagnostic, 4 | gen_diagnostic, 5 | parser::{ 6 | ast::Ast, 7 | ast_iterator::AstIterator, 8 | from_ast::{FromAst, FromAstElementContent}, 9 | }, 10 | }; 11 | use eww_shared_util::{AttrName, Span, Spanned}; 12 | 13 | use super::widget_use::WidgetUse; 14 | 15 | #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] 16 | pub struct AttrSpec { 17 | pub name: AttrName, 18 | pub optional: bool, 19 | pub span: Span, 20 | } 21 | 22 | impl FromAst for AttrSpec { 23 | fn from_ast(e: Ast) -> DiagResult { 24 | let span = e.span(); 25 | let symbol = e.as_symbol()?; 26 | let (name, optional) = if let Some(name) = symbol.strip_prefix('?') { (name.to_string(), true) } else { (symbol, false) }; 27 | Ok(Self { name: AttrName(name), optional, span }) 28 | } 29 | } 30 | 31 | #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] 32 | pub struct WidgetDefinition { 33 | pub name: String, 34 | pub expected_args: Vec, 35 | pub widget: WidgetUse, 36 | pub span: Span, 37 | pub args_span: Span, 38 | } 39 | 40 | impl FromAstElementContent for WidgetDefinition { 41 | const ELEMENT_NAME: &'static str = "defwidget"; 42 | 43 | fn from_tail>(span: Span, mut iter: AstIterator) -> DiagResult { 44 | let (name_span, name) = iter.expect_symbol().map_err(DiagError::from).note(EXPECTED_WIDGET_DEF_FORMAT)?; 45 | let (args_span, expected_args) = iter 46 | .expect_array() 47 | .map_err(|e| { 48 | DiagError(match e { 49 | crate::ast_error::AstError::WrongExprType(..) => gen_diagnostic! { 50 | msg = "Widget definition missing argument list", 51 | label = name_span.point_span_at_end() => "Insert the argument list (e.g.: `[]`) here", 52 | note = "This list needs to declare all the non-global variables / attributes used in this widget." 53 | }, 54 | other => other.to_diagnostic(), 55 | }) 56 | }) 57 | .note(EXPECTED_WIDGET_DEF_FORMAT)?; 58 | let expected_args = expected_args.into_iter().map(AttrSpec::from_ast).collect::>()?; 59 | let widget = iter.expect_any().map_err(DiagError::from).note(EXPECTED_WIDGET_DEF_FORMAT).and_then(WidgetUse::from_ast)?; 60 | iter.expect_done().map_err(|e| { 61 | DiagError(gen_diagnostic! { 62 | msg = "Widget definition has more than one child widget", 63 | label = e.span() => "Found more than one child element here.", 64 | note = "A widget-definition may only contain one child element.\n\ 65 | To include multiple elements, wrap these elements in a single container widget such as `box`.\n\ 66 | This is necessary as eww can't know how you want these elements to be layed out otherwise." 67 | }) 68 | })?; 69 | 70 | Ok(Self { name, expected_args, widget, span, args_span }) 71 | } 72 | } 73 | 74 | static EXPECTED_WIDGET_DEF_FORMAT: &str = r#"Expected format: `(defwidget name [] (contained-widgets))`"#; 75 | -------------------------------------------------------------------------------- /crates/eww/src/paths.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::hash_map::DefaultHasher, 3 | hash::{Hash, Hasher}, 4 | path::{Path, PathBuf}, 5 | }; 6 | 7 | use anyhow::{bail, Result}; 8 | 9 | /// Stores references to all the paths relevant to eww, and abstracts access to these files and directories 10 | #[derive(Debug, Clone)] 11 | pub struct EwwPaths { 12 | pub log_file: PathBuf, 13 | pub ipc_socket_file: PathBuf, 14 | pub config_dir: PathBuf, 15 | } 16 | 17 | impl EwwPaths { 18 | pub fn from_config_dir>(config_dir: P) -> Result { 19 | let config_dir = config_dir.as_ref(); 20 | if config_dir.is_file() { 21 | bail!("Please provide the path to the config directory, not a file within it") 22 | } 23 | 24 | if !config_dir.exists() { 25 | bail!("Configuration directory {} does not exist", config_dir.display()); 26 | } 27 | 28 | let config_dir = config_dir.canonicalize()?; 29 | 30 | let mut hasher = DefaultHasher::new(); 31 | format!("{}", config_dir.display()).hash(&mut hasher); 32 | // daemon_id is a hash of the config dir path to ensure that, given a normal XDG_RUNTIME_DIR, 33 | // the absolute path to the socket stays under the 108 bytes limit. (see #387, man 7 unix) 34 | let daemon_id = format!("{:x}", hasher.finish()); 35 | 36 | let ipc_socket_file = std::env::var("XDG_RUNTIME_DIR") 37 | .map(std::path::PathBuf::from) 38 | .unwrap_or_else(|_| std::path::PathBuf::from("/tmp")) 39 | .join(format!("eww-server_{}", daemon_id)); 40 | 41 | // 100 as the limit isn't quite 108 everywhere (i.e 104 on BSD or mac) 42 | if format!("{}", ipc_socket_file.display()).len() > 100 { 43 | log::warn!("The IPC socket file's absolute path exceeds 100 bytes, the socket may fail to create."); 44 | } 45 | 46 | Ok(EwwPaths { 47 | config_dir, 48 | log_file: std::env::var("XDG_CACHE_HOME") 49 | .map(PathBuf::from) 50 | .unwrap_or_else(|_| PathBuf::from(std::env::var("HOME").unwrap()).join(".cache")) 51 | .join(format!("eww_{}.log", daemon_id)), 52 | ipc_socket_file, 53 | }) 54 | } 55 | 56 | pub fn default() -> Result { 57 | let config_dir = std::env::var("XDG_CONFIG_HOME") 58 | .map(PathBuf::from) 59 | .unwrap_or_else(|_| PathBuf::from(std::env::var("HOME").unwrap()).join(".config")) 60 | .join("eww"); 61 | 62 | Self::from_config_dir(config_dir) 63 | } 64 | 65 | pub fn get_log_file(&self) -> &Path { 66 | self.log_file.as_path() 67 | } 68 | 69 | pub fn get_ipc_socket_file(&self) -> &Path { 70 | self.ipc_socket_file.as_path() 71 | } 72 | 73 | pub fn get_config_dir(&self) -> &Path { 74 | self.config_dir.as_path() 75 | } 76 | 77 | pub fn get_yuck_path(&self) -> PathBuf { 78 | self.config_dir.join("eww.yuck") 79 | } 80 | } 81 | 82 | impl std::fmt::Display for EwwPaths { 83 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 84 | write!( 85 | f, 86 | "config-dir: {}, ipc-socket: {}, log-file: {}", 87 | self.config_dir.display(), 88 | self.ipc_socket_file.display(), 89 | self.log_file.display() 90 | ) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/yuck/src/config/window_definition.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use crate::{ 4 | config::monitor::MonitorIdentifier, 5 | error::{DiagError, DiagResult}, 6 | parser::{ 7 | ast::Ast, 8 | ast_iterator::AstIterator, 9 | from_ast::{FromAst, FromAstElementContent}, 10 | }, 11 | }; 12 | use eww_shared_util::Span; 13 | 14 | use super::{backend_window_options::BackendWindowOptions, widget_use::WidgetUse, window_geometry::WindowGeometry}; 15 | 16 | #[derive(Debug, Clone, serde::Serialize, PartialEq)] 17 | pub struct WindowDefinition { 18 | pub name: String, 19 | pub geometry: Option, 20 | pub stacking: WindowStacking, 21 | pub monitor: Option, 22 | pub widget: WidgetUse, 23 | pub resizable: bool, 24 | pub backend_options: BackendWindowOptions, 25 | } 26 | 27 | impl FromAstElementContent for WindowDefinition { 28 | const ELEMENT_NAME: &'static str = "defwindow"; 29 | 30 | fn from_tail>(_span: Span, mut iter: AstIterator) -> DiagResult { 31 | let (_, name) = iter.expect_symbol()?; 32 | let mut attrs = iter.expect_key_values()?; 33 | let monitor = attrs.primitive_optional("monitor")?; 34 | let resizable = attrs.primitive_optional("resizable")?.unwrap_or(true); 35 | let stacking = attrs.primitive_optional("stacking")?.unwrap_or(WindowStacking::Foreground); 36 | let geometry = attrs.ast_optional("geometry")?; 37 | let backend_options = BackendWindowOptions::from_attrs(&mut attrs)?; 38 | let widget = iter.expect_any().map_err(DiagError::from).and_then(WidgetUse::from_ast)?; 39 | iter.expect_done()?; 40 | Ok(Self { name, monitor, resizable, widget, stacking, geometry, backend_options }) 41 | } 42 | } 43 | 44 | #[derive(Debug, thiserror::Error)] 45 | pub struct EnumParseError { 46 | pub input: String, 47 | pub expected: Vec<&'static str>, 48 | } 49 | impl Display for EnumParseError { 50 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 51 | write!(f, "Failed to parse `{}`, must be one of {}", self.input, self.expected.join(", ")) 52 | } 53 | } 54 | 55 | /// Parse a string with a concrete set of options into some data-structure, 56 | /// and return an [EnumParseError] 57 | /// ```rs 58 | /// let input = "up"; 59 | /// enum_parse { "direction", input, 60 | /// "up" => Direction::Up, 61 | /// "down" => Direction::Down, 62 | /// } 63 | /// ``` 64 | #[macro_export] 65 | macro_rules! enum_parse { 66 | ($name:literal, $input:expr, $($($s:literal)|* => $val:expr),* $(,)?) => { 67 | let input = $input.to_lowercase(); 68 | match input.as_str() { 69 | $( $( $s )|* => Ok($val) ),*, 70 | _ => Err(EnumParseError { 71 | input, 72 | expected: vec![$($($s),*),*], 73 | }) 74 | } 75 | }; 76 | } 77 | 78 | #[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display, smart_default::SmartDefault, serde::Serialize)] 79 | pub enum WindowStacking { 80 | #[default] 81 | Foreground, 82 | Background, 83 | Bottom, 84 | Overlay, 85 | } 86 | 87 | impl std::str::FromStr for WindowStacking { 88 | type Err = EnumParseError; 89 | 90 | fn from_str(s: &str) -> Result { 91 | enum_parse! { "WindowStacking", s, 92 | "foreground" | "fg" => WindowStacking::Foreground, 93 | "background" | "bg" => WindowStacking::Background, 94 | "bottom" | "bt" => WindowStacking::Bottom, 95 | "overlay" | "ov" => WindowStacking::Overlay, 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /docs/src/expression_language.md: -------------------------------------------------------------------------------- 1 | # Simple expression language 2 | 3 | Yuck includes a small expression language that can be used to run several operations on your data. 4 | This can be used to show different values depending on certain conditions, 5 | do mathematic operations, and even access values within JSON-structures. 6 | 7 | These expressions can be placed anywhere within your configuration inside `{ ... }`, 8 | as well as within strings, inside string-interpolation blocks (`"foo ${ ... } bar"`). 9 | 10 | ## Example 11 | 12 | ```lisp 13 | (box 14 | "Some math: ${12 + foo * 10}" 15 | (button :class {button_active ? "active" : "inactive"} 16 | :onclick "toggle_thing" 17 | {button_active ? "disable" : "enable"})) 18 | ``` 19 | 20 | ## Features 21 | 22 | Supported currently are the following features: 23 | - simple mathematical operations (`+`, `-`, `*`, `/`, `%`) 24 | - comparisons (`==`, `!=`, `>`, `<`, `<=`, `>=`) 25 | - boolean operations (`||`, `&&`, `!`) 26 | - regex match operator (`=~`) 27 | - elvis operator (`?:`) 28 | - if the left side is `""` or a JSON `null`, then returns the right side, 29 | otherwise evaluates to the left side. 30 | - Safe Access operator (`?.`) or (`?.[index]`) 31 | - if the left side is `""` or a JSON `null`, then return `null`. Otherwise, 32 | attempt to index. 33 | - This can still cause an error to occur if the left hand side exists but is 34 | not an object. 35 | (`Number` or `String`). 36 | - conditionals (`condition ? 'value' : 'other value'`) 37 | - numbers, strings, booleans and variable references (`12`, `'hi'`, `true`, `some_variable`) 38 | - json access (`object.field`, `array[12]`, `object["field"]`) 39 | - for this, the object/array value needs to refer to a variable that contains a valid json string. 40 | - some function calls: 41 | - `round(number, decimal_digits)`: Round a number to the given amount of decimals 42 | - `sin(number)`, `cos(number)`, `tan(number)`, `cot(number)`: Calculate the trigonometric value of a given number in **radians** 43 | - `degtorad(number)`: Converts a number from degrees to radians 44 | - `radtodeg(number)`: Converts a number from radians to degrees 45 | - `replace(string, regex, replacement)`: Replace matches of a given regex in a string 46 | - `search(string, regex)`: Search for a given regex in a string (returns array) 47 | - `matches(string, regex)`: check if a given string matches a given regex (returns bool) 48 | - `captures(string, regex)`: Get the captures of a given regex in a string (returns array) 49 | - `strlength(value)`: Gets the length of the string 50 | - `substring(string, start, length)`: Return a substring of given length starting at the given index 51 | - `arraylength(value)`: Gets the length of the array 52 | - `objectlength(value)`: Gets the amount of entries in the object 53 | - `jq(value, jq_filter_string)`: run a [jq](https://stedolan.github.io/jq/manual/) style command on a json value. (Uses [jaq](https://crates.io/crates/jaq) internally). 54 | - `formattime(unix_timestamp, format_str, timezone)`: Gets the time in a given format from UNIX timestamp. 55 | Check [chrono's documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) for more 56 | information about format string and [chrono-tz's documentation](https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html) 57 | for available time zones. 58 | - `formattime(unix_timestamp, format_str)`: Gets the time in a given format from UNIX timestamp. 59 | Same as other `formattime`, but does not accept timezone. Instead, it uses system's local timezone. 60 | Check [chrono's documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) for more 61 | information about format string. 62 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to eww will be listed here, starting at changes since version 0.2.0. 4 | 5 | 6 | ## [Unreleased] 7 | 8 | ### Features 9 | - Add `:namespace` window option 10 | - Default to building with x11 and wayland support simultaneously 11 | - Add `truncate-left` property on `label` widgets (By: kawaki-san) 12 | - Add support for safe access (`?.`) in simplexpr (By: oldwomanjosiah) 13 | - Allow floating-point numbers in percentages for window-geometry 14 | - Add support for safe access with index (`?.[n]`) (By: ModProg) 15 | - Made `and`, `or` and `?:` lazily evaluated in simplexpr (By: ModProg) 16 | - Add Vanilla CSS support (By: Ezequiel Ramis) 17 | - Add `jq` function, offering jq-style json processing 18 | - Add `justify` property to the label widget, allowing text justification (By: n3oney) 19 | - Add `EWW_TIME` magic variable (By: Erenoit) 20 | - Add trigonometric functions (`sin`, `cos`, `tan`, `cot`) and degree/radian conversions (`degtorad`, `radtodeg`) (By: end-4) 21 | - Add `substring` function to simplexpr 22 | - Add `--duration` flag to `eww open` 23 | 24 | ## [0.4.0] (04.09.2022) 25 | 26 | ### BREAKING CHANGES 27 | - Change `calendar`-widget to index months starting at 1 rather than indexed from 0 28 | 29 | ### Features 30 | - Add support for output names in X11 to select `:monitor`. 31 | - Add support for `:active`-pseudoselector on eventbox (By: viandoxdev) 32 | - Add support for `:password` on input (By: viandoxdev) 33 | 34 | ### Notable fixes and other changes 35 | - Scale now only runs the onchange command on changes caused by user-interaction 36 | - Improve CSS error reporting 37 | - Fix deflisten scripts not always getting cleaned up properly 38 | - Add `:round-digits` to scale widget (By: gavynriebau) 39 | - Fix cirular-progress not properly displaying 100% values when clockwise is false 40 | - Fix temperatures inside `EWW_TEMPS` not being accessible if at least one value is `NaN` 41 | 42 | 43 | ## 0.3.0 (26.05.2022) 44 | 45 | ### BREAKING CHANGES 46 | - Change the onclick command API to support multiple placeholders. 47 | This changes. the behaviour of the calendar widget's onclick as well as the onhover and onhoverlost 48 | events. Instead of providing the entire date (or, respecively, the x and y mouse coordinates) in 49 | a single value (`day.month.year`, `x y`), the values are now provided as separate placeholders. 50 | The day can now be accessed with `{0}`, the month with `{1}`, and the year with `{2}`, and 51 | similarly x and y are accessed with `{0}` and `{1}`. 52 | 53 | ### Features 54 | - Add `eww inspector` command 55 | - Add `--no-daemonize` flag 56 | - Add support for displaying marks on `scale`-widget (By: druskus20) 57 | - Add `children`-widget that allows custom widgets to make use of children 58 | - Add support for `:hover` css selectors for eventbox (By: druskus20) 59 | - Add `eww get` subcommand (By: druskus20) 60 | - Add circular progress widget (By: druskus20) 61 | - Add `:xalign` and `:yalign` to labels (By: alecsferra) 62 | - Add `graph` widget (By: druskus20) 63 | - Add `>=` and `<=` operators to simplexpr (By: viandoxdev) 64 | - Add `desktop` window type (By: Alvaro Lopez) 65 | - Add `scroll` widget (By: viandoxdev) 66 | - Add `notification` window type 67 | - Add drag and drop functionality to eventbox 68 | - Add `search`, `captures`, `stringlength`, `arraylength` and `objectlength` functions for expressions (By: MartinJM, ElKowar) 69 | - Add `matches` function 70 | - Add `transform` widget (By: druskus20) 71 | - Add `:onaccept` to input field, add `:onclick` to eventbox 72 | - Add `EWW_CMD`, `EWW_CONFIG_DIR`, `EWW_EXECUTABLE` magic variables 73 | - Add `overlay` widget (By: viandoxdev) 74 | 75 | ### Notable Internal changes 76 | - Rework state management completely, now making local state and dynamic widget hierarchy changes possible. 77 | 78 | ### Notable fixes and other changes 79 | - Fix `onscroll` gtk-bug where the first event is emitted incorrectly (By: druskus20) 80 | - Allow windows to get moved when windowtype is `normal` 81 | - Added more examples 82 | - List system-level dependencies in documentation 83 | - Document structure of magic variables (By: legendofmiracles) 84 | - Updated dependencies 85 | -------------------------------------------------------------------------------- /crates/yuck/src/config/attributes.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use simplexpr::{dynval::FromDynVal, eval::EvalError, SimplExpr}; 4 | 5 | use crate::{ 6 | error::DiagError, 7 | parser::{ast::Ast, from_ast::FromAst}, 8 | }; 9 | use eww_shared_util::{AttrName, Span, Spanned}; 10 | 11 | #[derive(Debug, thiserror::Error)] 12 | pub enum AttrError { 13 | #[error("Missing required attribute {1}")] 14 | MissingRequiredAttr(Span, AttrName), 15 | 16 | #[error("{1}")] 17 | EvaluationError(Span, EvalError), 18 | 19 | #[error("{1}")] 20 | Other(Span, Box), 21 | } 22 | 23 | impl Spanned for AttrError { 24 | fn span(&self) -> Span { 25 | match self { 26 | AttrError::MissingRequiredAttr(span, _) => *span, 27 | AttrError::EvaluationError(span, _) => *span, 28 | AttrError::Other(span, _) => *span, 29 | } 30 | } 31 | } 32 | 33 | #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] 34 | pub struct AttrEntry { 35 | pub key_span: Span, 36 | pub value: Ast, 37 | } 38 | 39 | impl AttrEntry { 40 | pub fn new(key_span: Span, value: Ast) -> AttrEntry { 41 | AttrEntry { key_span, value } 42 | } 43 | } 44 | 45 | // TODO maybe make this generic over the contained content 46 | #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)] 47 | pub struct Attributes { 48 | pub span: Span, 49 | pub attrs: HashMap, 50 | } 51 | 52 | impl Attributes { 53 | pub fn new(span: Span, attrs: HashMap) -> Self { 54 | Attributes { span, attrs } 55 | } 56 | 57 | pub fn ast_required(&mut self, key: &str) -> Result { 58 | let key = AttrName(key.to_string()); 59 | match self.attrs.remove(&key) { 60 | Some(AttrEntry { value, .. }) => T::from_ast(value), 61 | None => Err(AttrError::MissingRequiredAttr(self.span, key.clone()).into()), 62 | } 63 | } 64 | 65 | pub fn ast_optional(&mut self, key: &str) -> Result, DiagError> { 66 | match self.attrs.remove(&AttrName(key.to_string())) { 67 | Some(AttrEntry { value, .. }) => T::from_ast(value).map(Some), 68 | None => Ok(None), 69 | } 70 | } 71 | 72 | /// Retrieve a required attribute from the set which _must not_ reference any variables, 73 | /// and is thus known to be static. 74 | pub fn primitive_required(&mut self, key: &str) -> Result 75 | where 76 | E: std::error::Error + 'static + Sync + Send, 77 | T: FromDynVal, 78 | { 79 | let ast: SimplExpr = self.ast_required(key)?; 80 | Ok(ast 81 | .eval_no_vars() 82 | .map_err(|err| AttrError::EvaluationError(ast.span(), err))? 83 | .read_as() 84 | .map_err(|e| AttrError::Other(ast.span(), Box::new(e)))?) 85 | } 86 | 87 | /// Retrieve an optional attribute from the set which _must not_ reference any variables, 88 | /// and is thus known to be static. 89 | pub fn primitive_optional(&mut self, key: &str) -> Result, DiagError> 90 | where 91 | E: std::error::Error + 'static + Sync + Send, 92 | T: FromDynVal, 93 | { 94 | let ast: SimplExpr = match self.ast_optional(key)? { 95 | Some(ast) => ast, 96 | None => return Ok(None), 97 | }; 98 | Ok(Some( 99 | ast.eval_no_vars() 100 | .map_err(|err| AttrError::EvaluationError(ast.span(), err))? 101 | .read_as() 102 | .map_err(|e| AttrError::Other(ast.span(), Box::new(e)))?, 103 | )) 104 | } 105 | 106 | /// Consumes the attributes to return a list of unused attributes which may be used to emit a warning. 107 | /// TODO actually use this and emit warnings 108 | pub fn get_unused(self) -> impl Iterator { 109 | self.attrs.into_iter().map(|(k, v)| (v.key_span.to(v.value.span()), k)) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /crates/yuck/src/config/backend_window_options.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use anyhow::Result; 4 | 5 | use crate::{ 6 | enum_parse, 7 | error::DiagResult, 8 | parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent}, 9 | value::NumWithUnit, 10 | }; 11 | use eww_shared_util::Span; 12 | 13 | use super::{attributes::Attributes, window_definition::EnumParseError}; 14 | 15 | use crate::error::{DiagError, DiagResultExt}; 16 | 17 | /// Backend-specific options of a window that are backend 18 | #[derive(Debug, Clone, serde::Serialize, PartialEq)] 19 | pub struct BackendWindowOptions { 20 | pub x11: X11BackendWindowOptions, 21 | pub wayland: WlBackendWindowOptions, 22 | } 23 | 24 | impl BackendWindowOptions { 25 | pub fn from_attrs(attrs: &mut Attributes) -> DiagResult { 26 | let struts = attrs.ast_optional("reserve")?; 27 | let window_type = attrs.primitive_optional("windowtype")?; 28 | let x11 = X11BackendWindowOptions { 29 | wm_ignore: attrs.primitive_optional("wm-ignore")?.unwrap_or(window_type.is_none() && struts.is_none()), 30 | window_type: window_type.unwrap_or_default(), 31 | sticky: attrs.primitive_optional("sticky")?.unwrap_or(true), 32 | struts: struts.unwrap_or_default(), 33 | }; 34 | let wayland = WlBackendWindowOptions { 35 | exclusive: attrs.primitive_optional("exclusive")?.unwrap_or(false), 36 | focusable: attrs.primitive_optional("focusable")?.unwrap_or(false), 37 | namespace: attrs.primitive_optional("namespace")?, 38 | }; 39 | Ok(Self { x11, wayland }) 40 | } 41 | } 42 | 43 | #[derive(Debug, Clone, PartialEq, serde::Serialize)] 44 | pub struct X11BackendWindowOptions { 45 | pub wm_ignore: bool, 46 | pub sticky: bool, 47 | pub window_type: X11WindowType, 48 | pub struts: X11StrutDefinition, 49 | } 50 | 51 | #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] 52 | pub struct WlBackendWindowOptions { 53 | pub exclusive: bool, 54 | pub focusable: bool, 55 | pub namespace: Option, 56 | } 57 | 58 | /// Window type of an x11 window 59 | #[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)] 60 | pub enum X11WindowType { 61 | #[default] 62 | Dock, 63 | Dialog, 64 | Toolbar, 65 | Normal, 66 | Utility, 67 | Desktop, 68 | Notification, 69 | } 70 | impl FromStr for X11WindowType { 71 | type Err = EnumParseError; 72 | 73 | fn from_str(s: &str) -> Result { 74 | enum_parse! { "window type", s, 75 | "dock" => Self::Dock, 76 | "toolbar" => Self::Toolbar, 77 | "dialog" => Self::Dialog, 78 | "normal" => Self::Normal, 79 | "utility" => Self::Utility, 80 | "desktop" => Self::Desktop, 81 | "notification" => Self::Notification, 82 | } 83 | } 84 | } 85 | 86 | #[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault, serde::Serialize)] 87 | pub enum Side { 88 | #[default] 89 | Top, 90 | Left, 91 | Right, 92 | Bottom, 93 | } 94 | 95 | impl std::str::FromStr for Side { 96 | type Err = EnumParseError; 97 | 98 | fn from_str(s: &str) -> Result { 99 | enum_parse! { "side", s, 100 | "l" | "left" => Side::Left, 101 | "r" | "right" => Side::Right, 102 | "t" | "top" => Side::Top, 103 | "b" | "bottom" => Side::Bottom, 104 | } 105 | } 106 | } 107 | 108 | #[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize)] 109 | pub struct X11StrutDefinition { 110 | pub side: Side, 111 | pub dist: NumWithUnit, 112 | } 113 | 114 | impl FromAstElementContent for X11StrutDefinition { 115 | const ELEMENT_NAME: &'static str = "struts"; 116 | 117 | fn from_tail>(_span: Span, mut iter: AstIterator) -> DiagResult { 118 | let mut attrs = iter.expect_key_values()?; 119 | iter.expect_done().map_err(DiagError::from).note("Check if you are missing a colon in front of a key")?; 120 | Ok(X11StrutDefinition { side: attrs.primitive_required("side")?, dist: attrs.primitive_required("distance")? }) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /crates/eww/src/config/inbuilt.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use simplexpr::{dynval::DynVal, SimplExpr}; 4 | use yuck::config::{ 5 | script_var_definition::{PollScriptVar, ScriptVarDefinition, VarSource}, 6 | var_definition::VarDefinition, 7 | }; 8 | 9 | use crate::{config::system_stats::*, paths::EwwPaths}; 10 | use eww_shared_util::VarName; 11 | 12 | macro_rules! define_builtin_vars { 13 | ($($name:literal [$interval:literal] => $fun:expr),*$(,)?) => { 14 | pub static INBUILT_VAR_NAMES: &[&'static str] = &[$($name),*]; 15 | pub fn get_inbuilt_vars() -> HashMap { 16 | maplit::hashmap! { 17 | $( 18 | VarName::from($name) => ScriptVarDefinition::Poll(PollScriptVar { 19 | name: VarName::from($name), 20 | run_while_expr: SimplExpr::Literal(DynVal::from(true)), 21 | command: VarSource::Function($fun), 22 | initial_value: None, 23 | interval: std::time::Duration::from_secs($interval), 24 | name_span: eww_shared_util::span::Span::DUMMY, 25 | }) 26 | ),* 27 | } 28 | } 29 | } 30 | } 31 | 32 | define_builtin_vars! { 33 | // @desc EWW_TEMPS - Heat of the components in Celcius 34 | // @prop { : temperature } 35 | "EWW_TEMPS" [2] => || Ok(DynVal::from(get_temperatures())), 36 | 37 | // @desc EWW_RAM - Information on ram and swap usage in kB. 38 | // @prop { total_mem, free_mem, total_swap, free_swap, available_mem, used_mem, used_mem_perc } 39 | "EWW_RAM" [2] => || Ok(DynVal::from(get_ram())), 40 | 41 | // @desc EWW_DISK - Information on on all mounted partitions (Might report inaccurately on some filesystems, like btrfs and zfs) Example: `{EWW_DISK["/"]}` 42 | // @prop { : { name, total, free, used, used_perc } } 43 | "EWW_DISK" [2] => || Ok(DynVal::from(get_disks())), 44 | 45 | // @desc EWW_BATTERY - Battery capacity in procent of the main battery 46 | // @prop { : { capacity, status } } 47 | "EWW_BATTERY" [2] => || Ok(DynVal::from( 48 | match get_battery_capacity() { 49 | Err(e) => { 50 | log::error!("Couldn't get the battery capacity: {:?}", e); 51 | "Error: Check `eww log` for more details".to_string() 52 | } 53 | Ok(o) => o, 54 | } 55 | )), 56 | 57 | // @desc EWW_CPU - Information on the CPU cores: frequency and usage (No MacOS support) 58 | // @prop { cores: [{ core, freq, usage }], avg } 59 | "EWW_CPU" [2] => || Ok(DynVal::from(get_cpus())) , 60 | 61 | // @desc EWW_NET - Bytes up/down on all interfaces 62 | // @prop { : { up, down } } 63 | "EWW_NET" [2] => || Ok(DynVal::from(net())) , 64 | 65 | // @desc EWW_TIME - the current UNIX timestamp 66 | "EWW_TIME" [1] => || Ok(DynVal::from(get_time())) , 67 | } 68 | 69 | macro_rules! define_magic_constants { 70 | ($eww_paths:ident, $($name:literal => $value:expr),*$(,)?) => { 71 | pub static MAGIC_CONSTANT_NAMES: &[&'static str] = &[$($name),*]; 72 | pub fn get_magic_constants($eww_paths: &EwwPaths) -> HashMap { 73 | maplit::hashmap! { 74 | $(VarName::from($name) => VarDefinition { 75 | name: VarName::from($name), 76 | initial_value: $value, 77 | span: eww_shared_util::span::Span::DUMMY 78 | }),* 79 | } 80 | } 81 | } 82 | } 83 | define_magic_constants! { eww_paths, 84 | // @desc EWW_CONFIG_DIR - Path to the eww configuration of the current process 85 | "EWW_CONFIG_DIR" => DynVal::from_string(eww_paths.get_config_dir().to_string_lossy().into_owned()), 86 | 87 | // @desc EWW_CMD - eww command running in the current configuration, useful in event handlers. I.e.: `:onclick "${EWW_CMD} update foo=bar"` 88 | "EWW_CMD" => DynVal::from_string( 89 | format!("\"{}\" --config \"{}\"", 90 | std::env::current_exe().map(|x| x.to_string_lossy().into_owned()).unwrap_or_else(|_| "eww".to_string()), 91 | eww_paths.get_config_dir().to_string_lossy().into_owned() 92 | ) 93 | ), 94 | // @desc EWW_EXECUTABLE - Full path of the eww executable 95 | "EWW_EXECUTABLE" => DynVal::from_string( 96 | std::env::current_exe().map(|x| x.to_string_lossy().into_owned()).unwrap_or_else(|_| "eww".to_string()), 97 | ), 98 | } 99 | -------------------------------------------------------------------------------- /crates/yuck/src/value/coords.rs: -------------------------------------------------------------------------------- 1 | use derive_more::*; 2 | use once_cell::sync::Lazy; 3 | use serde::{Deserialize, Serialize}; 4 | use smart_default::SmartDefault; 5 | use std::{fmt, str::FromStr}; 6 | 7 | #[derive(Debug, thiserror::Error)] 8 | pub enum Error { 9 | #[error("Failed to parse \"{0}\" as a length value")] 10 | NumParseFailed(String), 11 | #[error("Invalid unit \"{0}\", must be either % or px")] 12 | InvalidUnit(String), 13 | #[error("Invalid format. Coordinates must be formated like 200x100")] 14 | MalformedCoords, 15 | } 16 | 17 | #[derive(Clone, Copy, PartialEq, Deserialize, Serialize, Display, DebugCustom, SmartDefault)] 18 | pub enum NumWithUnit { 19 | #[display(fmt = "{}%", .0)] 20 | #[debug(fmt = "{}%", .0)] 21 | Percent(f32), 22 | #[display(fmt = "{}px", .0)] 23 | #[debug(fmt = "{}px", .0)] 24 | #[default] 25 | Pixels(i32), 26 | } 27 | 28 | impl NumWithUnit { 29 | pub fn pixels_relative_to(&self, max: i32) -> i32 { 30 | match *self { 31 | NumWithUnit::Percent(n) => ((max as f64 / 100.0) * n as f64) as i32, 32 | NumWithUnit::Pixels(n) => n, 33 | } 34 | } 35 | 36 | pub fn perc_relative_to(&self, max: i32) -> f32 { 37 | match *self { 38 | NumWithUnit::Percent(n) => n, 39 | NumWithUnit::Pixels(n) => ((n as f64 / max as f64) * 100.0) as f32, 40 | } 41 | } 42 | } 43 | 44 | impl FromStr for NumWithUnit { 45 | type Err = Error; 46 | 47 | fn from_str(s: &str) -> Result { 48 | static PATTERN: Lazy = Lazy::new(|| regex::Regex::new("^(-?\\d+(?:.\\d+)?)(.*)$").unwrap()); 49 | 50 | let captures = PATTERN.captures(s).ok_or_else(|| Error::NumParseFailed(s.to_string()))?; 51 | let value = captures.get(1).unwrap().as_str().parse::().map_err(|_| Error::NumParseFailed(s.to_string()))?; 52 | match captures.get(2).unwrap().as_str() { 53 | "px" | "" => Ok(NumWithUnit::Pixels(value.floor() as i32)), 54 | "%" => Ok(NumWithUnit::Percent(value)), 55 | unit => Err(Error::InvalidUnit(unit.to_string())), 56 | } 57 | } 58 | } 59 | 60 | #[derive(Clone, Copy, PartialEq, Deserialize, Serialize, Display, Default)] 61 | #[display(fmt = "{}*{}", x, y)] 62 | pub struct Coords { 63 | pub x: NumWithUnit, 64 | pub y: NumWithUnit, 65 | } 66 | 67 | impl FromStr for Coords { 68 | type Err = Error; 69 | 70 | fn from_str(s: &str) -> Result { 71 | let (x, y) = s 72 | .split_once(|x: char| x.to_ascii_lowercase() == 'x' || x.to_ascii_lowercase() == '*') 73 | .ok_or(Error::MalformedCoords)?; 74 | Coords::from_strs(x, y) 75 | } 76 | } 77 | 78 | impl fmt::Debug for Coords { 79 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 80 | write!(f, "CoordsWithUnits({}, {})", self.x, self.y) 81 | } 82 | } 83 | 84 | impl Coords { 85 | pub fn from_pixels((x, y): (i32, i32)) -> Self { 86 | Coords { x: NumWithUnit::Pixels(x), y: NumWithUnit::Pixels(y) } 87 | } 88 | 89 | /// parse a string for x and a string for y into a [`Coords`] object. 90 | pub fn from_strs(x: &str, y: &str) -> Result { 91 | Ok(Coords { x: x.parse()?, y: y.parse()? }) 92 | } 93 | 94 | /// resolve the possibly relative coordinates relative to a given containers size 95 | pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) { 96 | (self.x.pixels_relative_to(width), self.y.pixels_relative_to(height)) 97 | } 98 | } 99 | 100 | #[cfg(test)] 101 | mod test { 102 | use super::*; 103 | use pretty_assertions::assert_eq; 104 | 105 | #[test] 106 | fn test_parse_num_with_unit() { 107 | assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str("55").unwrap()); 108 | assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str("55px").unwrap()); 109 | assert_eq!(NumWithUnit::Percent(55.0), NumWithUnit::from_str("55%").unwrap()); 110 | assert_eq!(NumWithUnit::Percent(55.5), NumWithUnit::from_str("55.5%").unwrap()); 111 | assert!(NumWithUnit::from_str("55pp").is_err()); 112 | } 113 | 114 | #[test] 115 | fn test_parse_coords() { 116 | assert_eq!(Coords { x: NumWithUnit::Pixels(50), y: NumWithUnit::Pixels(60) }, Coords::from_str("50x60").unwrap()); 117 | assert!(Coords::from_str("5060").is_err()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /crates/yuck/src/config/script_var_definition.rs: -------------------------------------------------------------------------------- 1 | use simplexpr::{dynval::DynVal, SimplExpr}; 2 | 3 | use crate::{ 4 | error::{DiagError, DiagResult, DiagResultExt}, 5 | format_diagnostic::ToDiagnostic, 6 | parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent}, 7 | }; 8 | use eww_shared_util::{Span, VarName}; 9 | 10 | #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] 11 | pub enum ScriptVarDefinition { 12 | Poll(PollScriptVar), 13 | Listen(ListenScriptVar), 14 | } 15 | 16 | impl ScriptVarDefinition { 17 | pub fn name_span(&self) -> Span { 18 | match self { 19 | ScriptVarDefinition::Poll(x) => x.name_span, 20 | ScriptVarDefinition::Listen(x) => x.name_span, 21 | } 22 | } 23 | 24 | pub fn name(&self) -> &VarName { 25 | match self { 26 | ScriptVarDefinition::Poll(x) => &x.name, 27 | ScriptVarDefinition::Listen(x) => &x.name, 28 | } 29 | } 30 | 31 | pub fn command_span(&self) -> Option { 32 | match self { 33 | ScriptVarDefinition::Poll(x) => match x.command { 34 | VarSource::Shell(span, ..) => Some(span), 35 | VarSource::Function(_) => None, 36 | }, 37 | ScriptVarDefinition::Listen(x) => Some(x.command_span), 38 | } 39 | } 40 | } 41 | 42 | #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] 43 | pub enum VarSource { 44 | // TODO allow for other executors? (python, etc) 45 | Shell(Span, String), 46 | #[serde(skip)] 47 | Function(fn() -> Result>), 48 | } 49 | 50 | #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] 51 | pub struct PollScriptVar { 52 | pub name: VarName, 53 | pub run_while_expr: SimplExpr, 54 | pub command: VarSource, 55 | pub initial_value: Option, 56 | pub interval: std::time::Duration, 57 | pub name_span: Span, 58 | } 59 | 60 | impl FromAstElementContent for PollScriptVar { 61 | const ELEMENT_NAME: &'static str = "defpoll"; 62 | 63 | fn from_tail>(_span: Span, mut iter: AstIterator) -> DiagResult { 64 | let result: DiagResult<_> = try { 65 | let (name_span, name) = iter.expect_symbol()?; 66 | let mut attrs = iter.expect_key_values()?; 67 | let initial_value = Some(attrs.primitive_optional("initial")?.unwrap_or_else(|| DynVal::from_string(String::new()))); 68 | let interval = 69 | attrs.primitive_required::("interval")?.as_duration().map_err(|e| DiagError(e.to_diagnostic()))?; 70 | let (script_span, script) = iter.expect_literal()?; 71 | 72 | let run_while_expr = 73 | attrs.ast_optional::("run-while")?.unwrap_or_else(|| SimplExpr::Literal(DynVal::from(true))); 74 | 75 | iter.expect_done()?; 76 | Self { 77 | name_span, 78 | name: VarName(name), 79 | run_while_expr, 80 | command: VarSource::Shell(script_span, script.to_string()), 81 | initial_value, 82 | interval, 83 | } 84 | }; 85 | result.note(r#"Expected format: `(defpoll name :interval "10s" "echo 'a shell script'")`"#) 86 | } 87 | } 88 | 89 | #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] 90 | pub struct ListenScriptVar { 91 | pub name: VarName, 92 | pub command: String, 93 | pub initial_value: DynVal, 94 | pub command_span: Span, 95 | pub name_span: Span, 96 | } 97 | impl FromAstElementContent for ListenScriptVar { 98 | const ELEMENT_NAME: &'static str = "deflisten"; 99 | 100 | fn from_tail>(_span: Span, mut iter: AstIterator) -> DiagResult { 101 | let result: DiagResult<_> = try { 102 | let (name_span, name) = iter.expect_symbol()?; 103 | let mut attrs = iter.expect_key_values()?; 104 | let initial_value = attrs.primitive_optional("initial")?.unwrap_or_else(|| DynVal::from_string(String::new())); 105 | let (command_span, script) = iter.expect_literal()?; 106 | iter.expect_done()?; 107 | Self { name_span, name: VarName(name), command: script.to_string(), initial_value, command_span } 108 | }; 109 | result.note(r#"Expected format: `(deflisten name :initial "0" "tail -f /tmp/example")`"#) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /crates/yuck/src/parser/ast.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use simplexpr::ast::SimplExpr; 3 | 4 | use eww_shared_util::{Span, Spanned, VarName}; 5 | use std::fmt::Display; 6 | 7 | use super::ast_iterator::AstIterator; 8 | use crate::ast_error::AstError; 9 | 10 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 11 | pub enum AstType { 12 | List, 13 | Array, 14 | Keyword, 15 | Symbol, 16 | // TODO this does no longer correspond to an actual literal ast type as that's replaced with SimplExpr 17 | Literal, 18 | SimplExpr, 19 | Comment, 20 | /// A value that could be used as a [SimplExpr] 21 | IntoPrimitive, 22 | } 23 | 24 | impl Display for AstType { 25 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 26 | match self { 27 | AstType::IntoPrimitive => write!(f, "primitive"), 28 | _ => write!(f, "{:?}", self), 29 | } 30 | } 31 | } 32 | 33 | #[derive(PartialEq, Eq, Clone, serde::Serialize)] 34 | pub enum Ast { 35 | /// I.e.: `(foo bar baz)` 36 | List(Span, Vec), 37 | /// I.e.: `[foo bar baz]` 38 | Array(Span, Vec), 39 | /// I.e.: `:foo` 40 | Keyword(Span, String), 41 | /// I.e.: `foo` 42 | Symbol(Span, String), 43 | /// I.e.: `{1 + 2}` 44 | SimplExpr(Span, SimplExpr), 45 | /// I.e.: `// foo` 46 | Comment(Span), 47 | } 48 | 49 | macro_rules! as_func { 50 | ($exprtype:expr, $name:ident $nameref:ident < $t:ty > = $p:pat => $value:expr) => { 51 | pub fn $name(self) -> Result<$t, AstError> { 52 | match self { 53 | $p => Ok($value), 54 | x => Err(AstError::WrongExprType(x.span(), $exprtype, x.expr_type())), 55 | } 56 | } 57 | 58 | pub fn $nameref(&self) -> Result<&$t, AstError> { 59 | match self { 60 | $p => Ok($value), 61 | x => Err(AstError::WrongExprType(x.span(), $exprtype, x.expr_type())), 62 | } 63 | } 64 | }; 65 | } 66 | 67 | impl Ast { 68 | as_func!(AstType::Symbol, as_symbol as_symbol_ref = Ast::Symbol(_, x) => x); 69 | 70 | as_func!(AstType::Keyword, as_keyword as_keyword_ref = Ast::Keyword(_, x) => x); 71 | 72 | as_func!(AstType::List, as_list as_list_ref> = Ast::List(_, x) => x); 73 | 74 | pub fn expr_type(&self) -> AstType { 75 | match self { 76 | Ast::List(..) => AstType::List, 77 | Ast::Array(..) => AstType::Array, 78 | Ast::Keyword(..) => AstType::Keyword, 79 | Ast::Symbol(..) => AstType::Symbol, 80 | Ast::SimplExpr(..) => AstType::SimplExpr, 81 | Ast::Comment(_) => AstType::Comment, 82 | } 83 | } 84 | 85 | pub fn as_simplexpr(&self) -> Result { 86 | match self { 87 | // TODO do I do this? 88 | // Ast::Array(span, elements) => todo!() 89 | Ast::Symbol(span, x) => Ok(SimplExpr::VarRef(*span, VarName(x.clone()))), 90 | Ast::SimplExpr(_span, x) => Ok(x.clone()), 91 | _ => Err(AstError::WrongExprType(self.span(), AstType::IntoPrimitive, self.expr_type())), 92 | } 93 | } 94 | 95 | pub fn try_ast_iter(self) -> Result>, AstError> { 96 | let span = self.span(); 97 | let list = self.as_list()?; 98 | Ok(AstIterator::new(span, list.into_iter())) 99 | } 100 | } 101 | 102 | impl std::fmt::Display for Ast { 103 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 104 | use Ast::*; 105 | match self { 106 | List(_, x) => write!(f, "({})", x.iter().map(|e| format!("{}", e)).join(" ")), 107 | Array(_, x) => write!(f, "({})", x.iter().map(|e| format!("{}", e)).join(" ")), 108 | Keyword(_, x) => write!(f, ":{}", x), 109 | Symbol(_, x) => write!(f, "{}", x), 110 | SimplExpr(_, simplexpr::SimplExpr::Literal(value)) => write!(f, "\"{}\"", value), 111 | SimplExpr(_, x) => write!(f, "{{{}}}", x), 112 | Comment(_) => write!(f, ""), 113 | } 114 | } 115 | } 116 | impl std::fmt::Debug for Ast { 117 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 118 | write!(f, "{}", self) 119 | } 120 | } 121 | 122 | impl Spanned for Ast { 123 | fn span(&self) -> Span { 124 | match self { 125 | Ast::List(span, _) => *span, 126 | Ast::Array(span, _) => *span, 127 | Ast::Keyword(span, _) => *span, 128 | Ast::Symbol(span, _) => *span, 129 | Ast::SimplExpr(span, _) => *span, 130 | Ast::Comment(span) => *span, 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /docs/theme/css/general.css: -------------------------------------------------------------------------------- 1 | /* Base styles and content styles */ 2 | 3 | @import 'variables.css'; 4 | 5 | :root { 6 | /* Browser default font-size is 16px, this way 1 rem = 10px */ 7 | font-size: 62.5%; 8 | } 9 | 10 | html { 11 | font-family: "Open Sans", sans-serif; 12 | color: var(--fg); 13 | background-color: var(--bg); 14 | text-size-adjust: none; 15 | } 16 | 17 | body { 18 | margin: 0; 19 | font-size: 1.6rem; 20 | overflow-x: hidden; 21 | } 22 | 23 | code { 24 | font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important; 25 | font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ 26 | } 27 | 28 | /* Don't change font size in headers. */ 29 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 30 | font-size: unset; 31 | } 32 | 33 | .left { float: left; } 34 | .right { float: right; } 35 | .boring { opacity: 0.6; } 36 | .hide-boring .boring { display: none; } 37 | .hidden { display: none !important; } 38 | 39 | h2, h3 { margin-top: 2.5em; } 40 | h4, h5 { margin-top: 2em; } 41 | 42 | .header + .header h3, 43 | .header + .header h4, 44 | .header + .header h5 { 45 | margin-top: 1em; 46 | } 47 | 48 | h1:target::before, 49 | h2:target::before, 50 | h3:target::before, 51 | h4:target::before, 52 | h5:target::before, 53 | h6:target::before { 54 | display: inline-block; 55 | content: "»"; 56 | margin-left: -30px; 57 | width: 30px; 58 | } 59 | 60 | /* This is broken on Safari as of version 14, but is fixed 61 | in Safari Technology Preview 117 which I think will be Safari 14.2. 62 | https://bugs.webkit.org/show_bug.cgi?id=218076 63 | */ 64 | :target { 65 | scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); 66 | } 67 | 68 | .page { 69 | outline: 0; 70 | padding: 0 var(--page-padding); 71 | margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ 72 | } 73 | .page-wrapper { 74 | box-sizing: border-box; 75 | } 76 | .js:not(.sidebar-resizing) .page-wrapper { 77 | transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ 78 | } 79 | 80 | .content { 81 | overflow-y: auto; 82 | padding: 0 15px; 83 | padding-bottom: 50px; 84 | } 85 | .content main { 86 | margin-left: auto; 87 | margin-right: auto; 88 | max-width: var(--content-max-width); 89 | } 90 | .content p { line-height: 1.45em; } 91 | .content ol { line-height: 1.45em; } 92 | .content ul { line-height: 1.45em; } 93 | .content a { text-decoration: none; } 94 | .content a:hover { text-decoration: underline; } 95 | .content img, .content video { max-width: 100%; } 96 | .content .header:link, 97 | .content .header:visited { 98 | color: var(--fg); 99 | } 100 | .content .header:link, 101 | .content .header:visited:hover { 102 | text-decoration: none; 103 | } 104 | 105 | table { 106 | margin: 0 auto; 107 | border-collapse: collapse; 108 | } 109 | table td { 110 | padding: 3px 20px; 111 | border: 1px var(--table-border-color) solid; 112 | } 113 | table thead { 114 | background: var(--table-header-bg); 115 | } 116 | table thead td { 117 | font-weight: 700; 118 | border: none; 119 | } 120 | table thead th { 121 | padding: 3px 20px; 122 | } 123 | table thead tr { 124 | border: 1px var(--table-header-bg) solid; 125 | } 126 | /* Alternate background colors for rows */ 127 | table tbody tr:nth-child(2n) { 128 | background: var(--table-alternate-bg); 129 | } 130 | 131 | 132 | blockquote { 133 | margin: 20px 0; 134 | padding: 0 20px; 135 | color: var(--fg); 136 | background-color: var(--quote-bg); 137 | border-top: .1em solid var(--quote-border); 138 | border-bottom: .1em solid var(--quote-border); 139 | } 140 | 141 | 142 | :not(.footnote-definition) + .footnote-definition, 143 | .footnote-definition + :not(.footnote-definition) { 144 | margin-top: 2em; 145 | } 146 | .footnote-definition { 147 | font-size: 0.9em; 148 | margin: 0.5em 0; 149 | } 150 | .footnote-definition p { 151 | display: inline; 152 | } 153 | 154 | .tooltiptext { 155 | position: absolute; 156 | visibility: hidden; 157 | color: #fff; 158 | background-color: #333; 159 | transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ 160 | left: -8px; /* Half of the width of the icon */ 161 | top: -35px; 162 | font-size: 0.8em; 163 | text-align: center; 164 | border-radius: 6px; 165 | padding: 5px 8px; 166 | margin: 5px; 167 | z-index: 1000; 168 | } 169 | .tooltipped .tooltiptext { 170 | visibility: visible; 171 | } 172 | 173 | .chapter li.part-title { 174 | color: var(--sidebar-fg); 175 | margin: 5px 0px; 176 | font-weight: bold; 177 | } 178 | --------------------------------------------------------------------------------