0.6.0
This commit is contained in:
commit
48459d6cc8
50 changed files with 2198 additions and 1454 deletions
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -37,3 +37,9 @@ body:
|
||||||
description: "If applicable, provide additional context or screenshots here."
|
description: "If applicable, provide additional context or screenshots here."
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Platform and environment"
|
||||||
|
description: "Does this happen on wayland, X11, or on both? What WM/Compositor are you using? Which version of eww are you using? (when using a git version, optimally provide the exact commit ref)."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
40
CHANGELOG.md
40
CHANGELOG.md
|
@ -4,6 +4,45 @@ All notable changes to eww will be listed here, starting at changes since versio
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
- [#1176](https://github.com/elkowar/eww/pull/1176) changed safe access (`?.`) behavior:
|
||||||
|
Attempting to index in an empty JSON string (`'""'`) is now an error.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Fix crash on invalid `formattime` format string (By: luca3s)
|
||||||
|
- Fix crash on NaN or infinite graph value (By: luca3s)
|
||||||
|
- Re-enable some scss features (By: w-lfchen)
|
||||||
|
- Fix and refactor nix flake (By: w-lfchen)
|
||||||
|
- Fix remove items from systray (By: vnva)
|
||||||
|
- Fix the gtk `stack` widget (By: ovalkonia)
|
||||||
|
- Fix values in the `EWW_NET` variable (By: mario-kr)
|
||||||
|
- Fix the gtk `expander` widget (By: ovalkonia)
|
||||||
|
- Fix wayland monitor names support (By: dragonnn)
|
||||||
|
- Load systray items that are registered without a path (By: Kage-Yami)
|
||||||
|
- `get_locale` now follows POSIX standard for locale selection (By: mirhahn, w-lfchen)
|
||||||
|
- Improve multi-monitor handling under wayland (By: bkueng)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Add warning and docs for incompatible `:anchor` and `:exclusive` options
|
||||||
|
- Add `eww poll` subcommand to force-poll a variable (By: kiana-S)
|
||||||
|
- Add OnDemand support for focusable on wayland (By: GallowsDove)
|
||||||
|
- Add jq `raw-output` support (By: RomanHargrave)
|
||||||
|
- Update rust toolchain to 1.81.0 (By: w-lfchen)
|
||||||
|
- Add `:fill-svg` and `:preserve-aspect-ratio` properties to images (By: hypernova7, w-lfchen)
|
||||||
|
- Add `:truncate` property to labels, disabled by default (except in cases where truncation would be enabled in version `0.5.0` and before) (By: Rayzeq).
|
||||||
|
- Add support for `:hover` css selectors for tray items (By: zeapoz)
|
||||||
|
- Add scss support for the `:style` widget property (By: ovalkonia)
|
||||||
|
- Add `min` and `max` function calls to simplexpr (By: ovalkonia)
|
||||||
|
- Add `flip-x`, `flip-y`, `vertical` options to the graph widget to determine its direction
|
||||||
|
- Add `transform-origin-x`/`transform-origin-y` properties to transform widget (By: mario-kr)
|
||||||
|
- Add keyboard support for button presses (By: julianschuler)
|
||||||
|
- Support empty string for safe access operator (By: ModProg)
|
||||||
|
- Add `log` function calls to simplexpr (By: topongo)
|
||||||
|
- Add `:lines` and `:wrap-mode` properties to label widget (By: vaporii)
|
||||||
|
- Add `value-pos` to scale widget (By: ipsvn)
|
||||||
|
- Add `floor` and `ceil` function calls to simplexpr (By: wsbankenstein)
|
||||||
|
- Add `formatbytes` function calls to simplexpr (By: topongo)
|
||||||
|
|
||||||
## [0.6.0] (21.04.2024)
|
## [0.6.0] (21.04.2024)
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
@ -34,6 +73,7 @@ All notable changes to eww will be listed here, starting at changes since versio
|
||||||
- Made `and`, `or` and `?:` lazily evaluated in simplexpr (By: ModProg)
|
- Made `and`, `or` and `?:` lazily evaluated in simplexpr (By: ModProg)
|
||||||
- Add Vanilla CSS support (By: Ezequiel Ramis)
|
- Add Vanilla CSS support (By: Ezequiel Ramis)
|
||||||
- Add `jq` function, offering jq-style json processing
|
- Add `jq` function, offering jq-style json processing
|
||||||
|
- Add support for the `EWW_BATTERY` magic variable in FreeBSD, OpenBSD, and NetBSD (By: dangerdyke)
|
||||||
- Add `justify` property to the label widget, allowing text justification (By: n3oney)
|
- Add `justify` property to the label widget, allowing text justification (By: n3oney)
|
||||||
- Add `EWW_TIME` magic variable (By: Erenoit)
|
- Add `EWW_TIME` magic variable (By: Erenoit)
|
||||||
- Add trigonometric functions (`sin`, `cos`, `tan`, `cot`) and degree/radian conversions (`degtorad`, `radtodeg`) (By: end-4)
|
- Add trigonometric functions (`sin`, `cos`, `tan`, `cot`) and degree/radian conversions (`degtorad`, `radtodeg`) (By: end-4)
|
||||||
|
|
1979
Cargo.lock
generated
1979
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
51
Cargo.toml
51
Cargo.toml
|
@ -9,37 +9,45 @@ eww_shared_util = { version = "0.1.0", path = "crates/eww_shared_util" }
|
||||||
yuck = { version = "0.1.0", path = "crates/yuck", default-features = false }
|
yuck = { version = "0.1.0", path = "crates/yuck", default-features = false }
|
||||||
notifier_host = { version = "0.1.0", path = "crates/notifier_host" }
|
notifier_host = { version = "0.1.0", path = "crates/notifier_host" }
|
||||||
|
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.86"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
cached = "0.48.0"
|
bytesize = "2.0.1"
|
||||||
chrono = "0.4.26"
|
cached = "0.53.1"
|
||||||
chrono-tz = "0.8.2"
|
chrono = "0.4.38"
|
||||||
|
chrono-tz = "0.10.0"
|
||||||
clap = { version = "4.5.1", features = ["derive"] }
|
clap = { version = "4.5.1", features = ["derive"] }
|
||||||
clap_complete = "4.5.1"
|
clap_complete = "4.5.12"
|
||||||
codespan-reporting = "0.11"
|
codespan-reporting = "0.11"
|
||||||
derive_more = "0.99"
|
derive_more = { version = "1", features = [
|
||||||
|
"as_ref",
|
||||||
|
"debug",
|
||||||
|
"display",
|
||||||
|
"from",
|
||||||
|
"from_str",
|
||||||
|
] }
|
||||||
extend = "1.2"
|
extend = "1.2"
|
||||||
futures = "0.3.28"
|
futures = "0.3.30"
|
||||||
grass = {version = "0.13.1", default-features = false}
|
grass = "0.13.4"
|
||||||
|
gtk = "0.18.1"
|
||||||
insta = "1.7"
|
insta = "1.7"
|
||||||
itertools = "0.12.1"
|
itertools = "0.13.0"
|
||||||
jaq-core = "1.2.1"
|
jaq-core = "1.5.1"
|
||||||
jaq-parse = "1.0.2"
|
jaq-parse = "1.0.3"
|
||||||
jaq-std = {version = "1.2.1", features = ["bincode"]}
|
jaq-std = "1.6.0"
|
||||||
jaq-interpret = "1.2.1"
|
jaq-interpret = "1.5.0"
|
||||||
jaq-syn = "1.1.0"
|
jaq-syn = "1.6.0"
|
||||||
lalrpop = { version = "0.20.0", features = ["unicode"] }
|
lalrpop = { version = "0.21", features = ["unicode"] }
|
||||||
lalrpop-util = { version = "0.20.0", features = ["unicode"] }
|
lalrpop-util = { version = "0.21", features = ["unicode"] }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
maplit = "1"
|
maplit = "1"
|
||||||
nix = "0.27.1"
|
nix = "0.29.0"
|
||||||
notify = "6.1.1"
|
notify = "6.1.1"
|
||||||
once_cell = "1.19"
|
once_cell = "1.19"
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
pretty_env_logger = "0.5.0"
|
pretty_env_logger = "0.5.0"
|
||||||
ref-cast = "1.0.22"
|
ref-cast = "1.0.22"
|
||||||
regex = "1.10.3"
|
regex = "1.10.5"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
simple-signal = "1.1"
|
simple-signal = "1.1"
|
||||||
|
@ -47,12 +55,13 @@ smart-default = "0.7.1"
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
strsim = "0.11"
|
strsim = "0.11"
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
sysinfo = "0.30.5"
|
sysinfo = "0.31.2"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio-util = "0.7.8"
|
tokio-util = "0.7.11"
|
||||||
tokio = { version = "1.36.0", features = ["full"] }
|
tokio = { version = "1.39.2", features = ["full"] }
|
||||||
unescape = "0.1"
|
unescape = "0.1"
|
||||||
wait-timeout = "0.2"
|
wait-timeout = "0.2"
|
||||||
|
zbus = { version = "3.15.2", default-features = false, features = ["tokio"] }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
split-debuginfo = "unpacked"
|
split-debuginfo = "unpacked"
|
||||||
|
|
22
README.md
22
README.md
|
@ -11,10 +11,14 @@ Documentation **and instructions on how to install** can be found [here](https:/
|
||||||
|
|
||||||
Dharmx also wrote a nice, beginner friendly introductory guide for eww [here](https://dharmx.is-a.dev/eww-powermenu/).
|
Dharmx also wrote a nice, beginner friendly introductory guide for eww [here](https://dharmx.is-a.dev/eww-powermenu/).
|
||||||
|
|
||||||
## Eww needs your opinion!
|
## Check out another cool project by me
|
||||||
I've hit a bit of a design roadblock for one of the bigger features that are in the works right now.
|
|
||||||
|
|
||||||
**Please read through https://github.com/elkowar/eww/discussions/453 and share your thoughts, ideas and opinions!**
|
<img src="https://raw.githubusercontent.com/elkowar/yolk/refs/heads/main/.github/images/yolk_logo.svg" height="100" align="right"/>
|
||||||
|
|
||||||
|
I'm currently busy working [yolk](https://github.com/elkowar/yolk),
|
||||||
|
which is a dotfile management solution that supports a unique spin on templating: *templating without template files*.
|
||||||
|
|
||||||
|
To find out more, check out the [website and documentation](https://elkowar.github.io/yolk)!
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
@ -59,14 +63,26 @@ I've hit a bit of a design roadblock for one of the bigger features that are in
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
* [Activate Linux by Nycta](https://github.com/Nycta-b424b3c7/eww_activate-linux)
|
||||||
|
|
||||||
|
<div align="left">
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
## Contribewwting
|
## Contribewwting
|
||||||
|
|
||||||
If you want to contribute anything, like adding new widgets, features, or subcommands (including sample configs), you should definitely do so.
|
If you want to contribute anything, like adding new widgets, features, or subcommands (including sample configs), you should definitely do so.
|
||||||
|
|
||||||
### Steps
|
### Steps
|
||||||
|
|
||||||
1. Fork this repository
|
1. Fork this repository
|
||||||
2. Install dependencies
|
2. Install dependencies
|
||||||
3. Smash your head against the keyboard from frustration (coding is hard)
|
3. Smash your head against the keyboard from frustration (coding is hard)
|
||||||
4. Write down your changes in CHANGELOG.md
|
4. Write down your changes in CHANGELOG.md
|
||||||
5. Open a pull request once you're finished
|
5. Open a pull request once you're finished
|
||||||
|
|
||||||
|
## Widget
|
||||||
|
|
||||||
|
https://en.wikipedia.org/wiki/Wikipedia:Widget
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "eww"
|
name = "eww"
|
||||||
version = "0.5.0"
|
version = "0.6.0"
|
||||||
authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"]
|
authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"]
|
||||||
description = "Widgets for everyone!"
|
description = "Widgets for everyone!"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
@ -9,7 +9,6 @@ homepage = "https://github.com/elkowar/eww"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["x11", "wayland"]
|
default = ["x11", "wayland"]
|
||||||
x11 = ["gdkx11", "x11rb"]
|
x11 = ["gdkx11", "x11rb"]
|
||||||
|
@ -21,24 +20,15 @@ eww_shared_util.workspace = true
|
||||||
yuck.workspace = true
|
yuck.workspace = true
|
||||||
notifier_host.workspace = true
|
notifier_host.workspace = true
|
||||||
|
|
||||||
gtk = "0.17.1"
|
gtk-layer-shell = { version = "0.8.1", optional = true, features=["v0_6"] }
|
||||||
gdk = "0.17.1"
|
gdkx11 = { version = "0.18", optional = true }
|
||||||
pango = "0.17.1"
|
x11rb = { version = "0.13.1", features = ["randr"], optional = true }
|
||||||
glib = "0.17.8"
|
gdk-sys = "0.18.0"
|
||||||
glib-macros = "0.17.8"
|
|
||||||
|
|
||||||
cairo-rs = "0.17"
|
|
||||||
cairo-sys-rs = "0.17"
|
|
||||||
|
|
||||||
gdk-pixbuf = "0.17"
|
|
||||||
|
|
||||||
gtk-layer-shell = { version = "0.6.1", optional = true }
|
|
||||||
gdkx11 = { version = "0.17", optional = true }
|
|
||||||
x11rb = { version = "0.11.1", features = ["randr"], optional = true }
|
|
||||||
|
|
||||||
zbus = { version = "3.7.0", default-features = false, features = ["tokio"] }
|
|
||||||
ordered-stream = "0.2.0"
|
ordered-stream = "0.2.0"
|
||||||
|
|
||||||
|
|
||||||
|
grass.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
bincode.workspace = true
|
bincode.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
|
@ -48,7 +38,7 @@ codespan-reporting.workspace = true
|
||||||
derive_more.workspace = true
|
derive_more.workspace = true
|
||||||
extend.workspace = true
|
extend.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
grass = {workspace = true, default-features = false}
|
gtk.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
libc.workspace = true
|
libc.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
@ -61,11 +51,12 @@ regex.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
simple-signal.workspace = true
|
simple-signal.workspace = true
|
||||||
sysinfo = { workspace = true, features = ["linux-netdevs"] }
|
sysinfo = { workspace = true }
|
||||||
tokio-util.workspace = true
|
tokio-util.workspace = true
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
unescape.workspace = true
|
unescape.workspace = true
|
||||||
wait-timeout.workspace = true
|
wait-timeout.workspace = true
|
||||||
|
zbus = { workspace = true, default-features = false, features = ["tokio"] }
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
maintainer = "Penelope Gwen <support@pogmom.me>"
|
maintainer = "Penelope Gwen <support@pogmom.me>"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
config,
|
|
||||||
daemon_response::DaemonResponseSender,
|
daemon_response::DaemonResponseSender,
|
||||||
display_backend::DisplayBackend,
|
display_backend::DisplayBackend,
|
||||||
error_handling_ctx,
|
error_handling_ctx,
|
||||||
|
@ -17,12 +16,14 @@ use codespan_reporting::files::Files;
|
||||||
use eww_shared_util::{Span, VarName};
|
use eww_shared_util::{Span, VarName};
|
||||||
use gdk::Monitor;
|
use gdk::Monitor;
|
||||||
use glib::ObjectExt;
|
use glib::ObjectExt;
|
||||||
|
use gtk::{gdk, glib};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use simplexpr::{dynval::DynVal, SimplExpr};
|
use simplexpr::{dynval::DynVal, SimplExpr};
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
|
marker::PhantomData,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
@ -44,6 +45,7 @@ use yuck::{
|
||||||
pub enum DaemonCommand {
|
pub enum DaemonCommand {
|
||||||
NoOp,
|
NoOp,
|
||||||
UpdateVars(Vec<(VarName, DynVal)>),
|
UpdateVars(Vec<(VarName, DynVal)>),
|
||||||
|
PollVars(Vec<VarName>),
|
||||||
ReloadConfigAndCss(DaemonResponseSender),
|
ReloadConfigAndCss(DaemonResponseSender),
|
||||||
OpenInspector,
|
OpenInspector,
|
||||||
OpenMany {
|
OpenMany {
|
||||||
|
@ -66,6 +68,7 @@ pub enum DaemonCommand {
|
||||||
},
|
},
|
||||||
CloseWindows {
|
CloseWindows {
|
||||||
windows: Vec<String>,
|
windows: Vec<String>,
|
||||||
|
auto_reopen: bool,
|
||||||
sender: DaemonResponseSender,
|
sender: DaemonResponseSender,
|
||||||
},
|
},
|
||||||
KillServer,
|
KillServer,
|
||||||
|
@ -87,10 +90,6 @@ pub enum DaemonCommand {
|
||||||
/// An opened window.
|
/// An opened window.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EwwWindow {
|
pub struct EwwWindow {
|
||||||
/// Every window has an id, uniquely identifying it.
|
|
||||||
/// If no specific ID was specified whilst starting the window,
|
|
||||||
/// this will be the same as the window name.
|
|
||||||
pub instance_id: String,
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub scope_index: ScopeIndex,
|
pub scope_index: ScopeIndex,
|
||||||
pub gtk_window: Window,
|
pub gtk_window: Window,
|
||||||
|
@ -111,11 +110,13 @@ impl EwwWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App<B> {
|
pub struct App<B: DisplayBackend> {
|
||||||
pub display_backend: B,
|
|
||||||
pub scope_graph: Rc<RefCell<ScopeGraph>>,
|
pub scope_graph: Rc<RefCell<ScopeGraph>>,
|
||||||
pub eww_config: config::EwwConfig,
|
pub eww_config: config::EwwConfig,
|
||||||
/// Map of all currently open windows by their IDs
|
/// Map of all currently open windows to their unique IDs
|
||||||
|
/// If no specific ID was specified whilst starting the window,
|
||||||
|
/// it will be the same as the window name.
|
||||||
|
/// Therefore, only one window of a given name can exist when not using IDs.
|
||||||
pub open_windows: HashMap<String, EwwWindow>,
|
pub open_windows: HashMap<String, EwwWindow>,
|
||||||
pub instance_id_to_args: HashMap<String, WindowArguments>,
|
pub instance_id_to_args: HashMap<String, WindowArguments>,
|
||||||
/// Window names that are supposed to be open, but failed.
|
/// Window names that are supposed to be open, but failed.
|
||||||
|
@ -131,9 +132,10 @@ pub struct App<B> {
|
||||||
pub window_close_timer_abort_senders: HashMap<String, futures::channel::oneshot::Sender<()>>,
|
pub window_close_timer_abort_senders: HashMap<String, futures::channel::oneshot::Sender<()>>,
|
||||||
|
|
||||||
pub paths: EwwPaths,
|
pub paths: EwwPaths,
|
||||||
|
pub phantom: PhantomData<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> std::fmt::Debug for App<B> {
|
impl<B: DisplayBackend> std::fmt::Debug for App<B> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("App")
|
f.debug_struct("App")
|
||||||
.field("scope_graph", &*self.scope_graph.borrow())
|
.field("scope_graph", &*self.scope_graph.borrow())
|
||||||
|
@ -146,16 +148,34 @@ impl<B> std::fmt::Debug for App<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wait until the .model() is available for all monitors (or there is a timeout)
|
||||||
|
async fn wait_for_monitor_model() {
|
||||||
|
let display = gdk::Display::default().expect("could not get default display");
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
loop {
|
||||||
|
let all_monitors_set =
|
||||||
|
(0..display.n_monitors()).all(|i| display.monitor(i).and_then(|monitor| monitor.model()).is_some());
|
||||||
|
if all_monitors_set {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||||
|
if std::time::Instant::now() - start > Duration::from_millis(500) {
|
||||||
|
log::warn!("Timed out waiting for monitor model to be set");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<B: DisplayBackend> App<B> {
|
impl<B: DisplayBackend> App<B> {
|
||||||
/// Handle a [`DaemonCommand`] event, logging any errors that occur.
|
/// Handle a [`DaemonCommand`] event, logging any errors that occur.
|
||||||
pub fn handle_command(&mut self, event: DaemonCommand) {
|
pub async fn handle_command(&mut self, event: DaemonCommand) {
|
||||||
if let Err(err) = self.try_handle_command(event) {
|
if let Err(err) = self.try_handle_command(event).await {
|
||||||
error_handling_ctx::print_error(err);
|
error_handling_ctx::print_error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to handle a [`DaemonCommand`] event.
|
/// Try to handle a [`DaemonCommand`] event.
|
||||||
fn try_handle_command(&mut self, event: DaemonCommand) -> Result<()> {
|
async fn try_handle_command(&mut self, event: DaemonCommand) -> Result<()> {
|
||||||
log::debug!("Handling event: {:?}", &event);
|
log::debug!("Handling event: {:?}", &event);
|
||||||
match event {
|
match event {
|
||||||
DaemonCommand::NoOp => {}
|
DaemonCommand::NoOp => {}
|
||||||
|
@ -167,7 +187,16 @@ impl<B: DisplayBackend> App<B> {
|
||||||
self.update_global_variable(var_name, new_value);
|
self.update_global_variable(var_name, new_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DaemonCommand::PollVars(names) => {
|
||||||
|
for var_name in names {
|
||||||
|
self.force_poll_variable(var_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
DaemonCommand::ReloadConfigAndCss(sender) => {
|
DaemonCommand::ReloadConfigAndCss(sender) => {
|
||||||
|
// Wait for all monitor models to be set. When a new monitor gets added, this
|
||||||
|
// might not immediately be the case. And if we were to wait inside the
|
||||||
|
// connect_monitor_added callback, model() never gets set. So instead we wait here.
|
||||||
|
wait_for_monitor_model().await;
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
let config_result = config::read_from_eww_paths(&self.paths);
|
let config_result = config::read_from_eww_paths(&self.paths);
|
||||||
|
@ -194,7 +223,7 @@ impl<B: DisplayBackend> App<B> {
|
||||||
DaemonCommand::CloseAll => {
|
DaemonCommand::CloseAll => {
|
||||||
log::info!("Received close command, closing all windows");
|
log::info!("Received close command, closing all windows");
|
||||||
for window_name in self.open_windows.keys().cloned().collect::<Vec<String>>() {
|
for window_name in self.open_windows.keys().cloned().collect::<Vec<String>>() {
|
||||||
self.close_window(&window_name)?;
|
self.close_window(&window_name, false)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DaemonCommand::OpenMany { windows, args, should_toggle, sender } => {
|
DaemonCommand::OpenMany { windows, args, should_toggle, sender } => {
|
||||||
|
@ -203,7 +232,7 @@ impl<B: DisplayBackend> App<B> {
|
||||||
.map(|w| {
|
.map(|w| {
|
||||||
let (config_name, id) = w;
|
let (config_name, id) = w;
|
||||||
if should_toggle && self.open_windows.contains_key(id) {
|
if should_toggle && self.open_windows.contains_key(id) {
|
||||||
self.close_window(id)
|
self.close_window(id, false)
|
||||||
} else {
|
} else {
|
||||||
log::debug!("Config: {}, id: {}", config_name, id);
|
log::debug!("Config: {}, id: {}", config_name, id);
|
||||||
let window_args = args
|
let window_args = args
|
||||||
|
@ -234,7 +263,7 @@ impl<B: DisplayBackend> App<B> {
|
||||||
let is_open = self.open_windows.contains_key(&instance_id);
|
let is_open = self.open_windows.contains_key(&instance_id);
|
||||||
|
|
||||||
let result = if should_toggle && is_open {
|
let result = if should_toggle && is_open {
|
||||||
self.close_window(&instance_id)
|
self.close_window(&instance_id, false)
|
||||||
} else {
|
} else {
|
||||||
self.open_window(&WindowArguments {
|
self.open_window(&WindowArguments {
|
||||||
instance_id,
|
instance_id,
|
||||||
|
@ -250,9 +279,10 @@ impl<B: DisplayBackend> App<B> {
|
||||||
|
|
||||||
sender.respond_with_result(result)?;
|
sender.respond_with_result(result)?;
|
||||||
}
|
}
|
||||||
DaemonCommand::CloseWindows { windows, sender } => {
|
DaemonCommand::CloseWindows { windows, auto_reopen, sender } => {
|
||||||
let errors = windows.iter().map(|window| self.close_window(window)).filter_map(Result::err);
|
let errors = windows.iter().map(|window| self.close_window(window, auto_reopen)).filter_map(Result::err);
|
||||||
sender.respond_with_error_list(errors)?;
|
// Ignore sending errors, as the channel might already be closed
|
||||||
|
let _ = sender.respond_with_error_list(errors);
|
||||||
}
|
}
|
||||||
DaemonCommand::PrintState { all, sender } => {
|
DaemonCommand::PrintState { all, sender } => {
|
||||||
let scope_graph = self.scope_graph.borrow();
|
let scope_graph = self.scope_graph.borrow();
|
||||||
|
@ -336,8 +366,25 @@ impl<B: DisplayBackend> App<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn force_poll_variable(&mut self, name: VarName) {
|
||||||
|
match self.eww_config.get_script_var(&name) {
|
||||||
|
Err(err) => error_handling_ctx::print_error(err),
|
||||||
|
Ok(var) => {
|
||||||
|
if let ScriptVarDefinition::Poll(poll_var) = var {
|
||||||
|
log::debug!("force-polling var {}", &name);
|
||||||
|
match script_var_handler::run_poll_once(&poll_var) {
|
||||||
|
Err(err) => error_handling_ctx::print_error(err),
|
||||||
|
Ok(value) => self.update_global_variable(name, value),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error_handling_ctx::print_error(anyhow!("Script var '{}' is not polling", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Close a window and do all the required cleanups in the scope_graph and script_var_handler
|
/// Close a window and do all the required cleanups in the scope_graph and script_var_handler
|
||||||
fn close_window(&mut self, instance_id: &str) -> Result<()> {
|
fn close_window(&mut self, instance_id: &str, auto_reopen: bool) -> Result<()> {
|
||||||
if let Some(old_abort_send) = self.window_close_timer_abort_senders.remove(instance_id) {
|
if let Some(old_abort_send) = self.window_close_timer_abort_senders.remove(instance_id) {
|
||||||
_ = old_abort_send.send(());
|
_ = old_abort_send.send(());
|
||||||
}
|
}
|
||||||
|
@ -357,7 +404,17 @@ impl<B: DisplayBackend> App<B> {
|
||||||
self.script_var_handler.stop_for_variable(unused_var.clone());
|
self.script_var_handler.stop_for_variable(unused_var.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if auto_reopen {
|
||||||
|
self.failed_windows.insert(instance_id.to_string());
|
||||||
|
// There might be an alternative monitor available already, so try to re-open it immediately.
|
||||||
|
// This can happen for example when a monitor gets disconnected and another connected,
|
||||||
|
// and the connection event happens before the disconnect.
|
||||||
|
if let Some(window_arguments) = self.instance_id_to_args.get(instance_id) {
|
||||||
|
let _ = self.open_window(&window_arguments.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.instance_id_to_args.remove(instance_id);
|
self.instance_id_to_args.remove(instance_id);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -369,7 +426,7 @@ impl<B: DisplayBackend> App<B> {
|
||||||
|
|
||||||
// if an instance of this is already running, close it
|
// if an instance of this is already running, close it
|
||||||
if self.open_windows.contains_key(instance_id) {
|
if self.open_windows.contains_key(instance_id) {
|
||||||
self.close_window(instance_id)?;
|
self.close_window(instance_id, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.instance_id_to_args.insert(instance_id.to_string(), window_args.clone());
|
self.instance_id_to_args.insert(instance_id.to_string(), window_args.clone());
|
||||||
|
@ -422,9 +479,17 @@ impl<B: DisplayBackend> App<B> {
|
||||||
move |_| {
|
move |_| {
|
||||||
// we don't care about the actual error response from the daemon as this is mostly just a fallback.
|
// we don't care about the actual error response from the daemon as this is mostly just a fallback.
|
||||||
// Generally, this should get disconnected before the gtk window gets destroyed.
|
// Generally, this should get disconnected before the gtk window gets destroyed.
|
||||||
// It serves as a fallback for when the window is closed manually.
|
// This callback is triggered in 2 cases:
|
||||||
|
// - When the monitor of this window gets disconnected
|
||||||
|
// - When the window is closed manually.
|
||||||
|
// We don't distinguish here and assume the window should be reopened once a monitor
|
||||||
|
// becomes available again
|
||||||
let (response_sender, _) = daemon_response::create_pair();
|
let (response_sender, _) = daemon_response::create_pair();
|
||||||
let command = DaemonCommand::CloseWindows { windows: vec![instance_id.clone()], sender: response_sender };
|
let command = DaemonCommand::CloseWindows {
|
||||||
|
windows: vec![instance_id.clone()],
|
||||||
|
auto_reopen: true,
|
||||||
|
sender: response_sender,
|
||||||
|
};
|
||||||
if let Err(err) = app_evt_sender.send(command) {
|
if let Err(err) = app_evt_sender.send(command) {
|
||||||
log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err);
|
log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err);
|
||||||
}
|
}
|
||||||
|
@ -443,7 +508,7 @@ impl<B: DisplayBackend> App<B> {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = glib::timeout_future(duration) => {
|
_ = glib::timeout_future(duration) => {
|
||||||
let (response_sender, mut response_recv) = daemon_response::create_pair();
|
let (response_sender, mut response_recv) = daemon_response::create_pair();
|
||||||
let command = DaemonCommand::CloseWindows { windows: vec![instance_id.clone()], sender: response_sender };
|
let command = DaemonCommand::CloseWindows { windows: vec![instance_id.clone()], auto_reopen: false, sender: response_sender };
|
||||||
if let Err(err) = app_evt_sender.send(command) {
|
if let Err(err) = app_evt_sender.send(command) {
|
||||||
log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err);
|
log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err);
|
||||||
}
|
}
|
||||||
|
@ -572,7 +637,6 @@ fn initialize_window<B: DisplayBackend>(
|
||||||
window.show_all();
|
window.show_all();
|
||||||
|
|
||||||
Ok(EwwWindow {
|
Ok(EwwWindow {
|
||||||
instance_id: window_init.id.clone(),
|
|
||||||
name: window_init.name.clone(),
|
name: window_init.name.clone(),
|
||||||
gtk_window: window,
|
gtk_window: window,
|
||||||
scope_index: window_scope,
|
scope_index: window_scope,
|
||||||
|
@ -626,6 +690,18 @@ fn get_gdk_monitor(identifier: Option<MonitorIdentifier>) -> Result<Monitor> {
|
||||||
Ok(monitor)
|
Ok(monitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the name of monitor plug for given monitor number
|
||||||
|
/// workaround gdk not providing this information on wayland in regular calls
|
||||||
|
/// gdk_screen_get_monitor_plug_name is deprecated but works fine for that case
|
||||||
|
fn get_monitor_plug_name(display: &gdk::Display, monitor_num: i32) -> Option<&str> {
|
||||||
|
unsafe {
|
||||||
|
use glib::translate::ToGlibPtr;
|
||||||
|
let plug_name_pointer = gdk_sys::gdk_screen_get_monitor_plug_name(display.default_screen().to_glib_none().0, monitor_num);
|
||||||
|
use std::ffi::CStr;
|
||||||
|
CStr::from_ptr(plug_name_pointer).to_str().ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the [Monitor][gdk::Monitor] structure corresponding to the identifer.
|
/// Returns the [Monitor][gdk::Monitor] structure corresponding to the identifer.
|
||||||
/// Outside of x11, only [MonitorIdentifier::Numeric] is supported
|
/// Outside of x11, only [MonitorIdentifier::Numeric] is supported
|
||||||
pub fn get_monitor_from_display(display: &gdk::Display, identifier: &MonitorIdentifier) -> Option<gdk::Monitor> {
|
pub fn get_monitor_from_display(display: &gdk::Display, identifier: &MonitorIdentifier) -> Option<gdk::Monitor> {
|
||||||
|
@ -643,7 +719,7 @@ pub fn get_monitor_from_display(display: &gdk::Display, identifier: &MonitorIden
|
||||||
MonitorIdentifier::Name(name) => {
|
MonitorIdentifier::Name(name) => {
|
||||||
for m in 0..display.n_monitors() {
|
for m in 0..display.n_monitors() {
|
||||||
if let Some(model) = display.monitor(m).and_then(|x| x.model()) {
|
if let Some(model) = display.monitor(m).and_then(|x| x.model()) {
|
||||||
if model == *name {
|
if model == *name || Some(name.as_str()) == get_monitor_plug_name(display, m) {
|
||||||
return display.monitor(m);
|
return display.monitor(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,11 @@ macro_rules! define_builtin_vars {
|
||||||
}
|
}
|
||||||
|
|
||||||
define_builtin_vars! {
|
define_builtin_vars! {
|
||||||
// @desc EWW_TEMPS - Heat of the components in Celcius
|
// @desc EWW_TEMPS - Heat of the components in degree Celsius
|
||||||
// @prop { <name>: temperature }
|
// @prop { <name>: temperature }
|
||||||
"EWW_TEMPS" [2] => || Ok(DynVal::from(get_temperatures())),
|
"EWW_TEMPS" [2] => || Ok(DynVal::from(get_temperatures())),
|
||||||
|
|
||||||
// @desc EWW_RAM - Information on ram and swap usage in kB.
|
// @desc EWW_RAM - Information on ram and swap usage in bytes.
|
||||||
// @prop { total_mem, free_mem, total_swap, free_swap, available_mem, used_mem, used_mem_perc }
|
// @prop { total_mem, free_mem, total_swap, free_swap, available_mem, used_mem, used_mem_perc }
|
||||||
"EWW_RAM" [2] => || Ok(DynVal::from(get_ram())),
|
"EWW_RAM" [2] => || Ok(DynVal::from(get_ram())),
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ define_builtin_vars! {
|
||||||
// @prop { <mount_point>: { name, total, free, used, used_perc } }
|
// @prop { <mount_point>: { name, total, free, used, used_perc } }
|
||||||
"EWW_DISK" [2] => || Ok(DynVal::from(get_disks())),
|
"EWW_DISK" [2] => || Ok(DynVal::from(get_disks())),
|
||||||
|
|
||||||
// @desc EWW_BATTERY - Battery capacity in procent of the main battery
|
// @desc EWW_BATTERY - Battery capacity in percent of the main battery
|
||||||
// @prop { <name>: { capacity, status } }
|
// @prop { <name>: { capacity, status } }
|
||||||
"EWW_BATTERY" [2] => || Ok(DynVal::from(
|
"EWW_BATTERY" [2] => || Ok(DynVal::from(
|
||||||
match get_battery_capacity() {
|
match get_battery_capacity() {
|
||||||
|
|
|
@ -202,8 +202,54 @@ pub fn get_battery_capacity() -> Result<String> {
|
||||||
Ok(serde_json::to_string(&(Data { batteries, total_avg: (current / total) * 100_f64 })).unwrap())
|
Ok(serde_json::to_string(&(Data { batteries, total_avg: (current / total) * 100_f64 })).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "netbsd", target_os = "freebsd", target_os = "openbsd"))]
|
||||||
|
pub fn get_battery_capacity() -> Result<String> {
|
||||||
|
let batteries = String::from_utf8(
|
||||||
|
// I have only tested `apm` on FreeBSD, but it *should* work on all of the listed targets,
|
||||||
|
// based on what I can tell from their online man pages.
|
||||||
|
std::process::Command::new("apm")
|
||||||
|
.output()
|
||||||
|
.context("\nError while getting the battery values on bsd, with `apm`: ")?
|
||||||
|
.stdout,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// `apm` output should look something like this:
|
||||||
|
// $ apm
|
||||||
|
// ...
|
||||||
|
// Remaining battery life: 87%
|
||||||
|
// Remaining battery time: unknown
|
||||||
|
// Number of batteries: 1
|
||||||
|
// Battery 0
|
||||||
|
// Battery Status: charging
|
||||||
|
// Remaining battery life: 87%
|
||||||
|
// Remaining battery time: unknown
|
||||||
|
// ...
|
||||||
|
// last 4 lines are repeated for each battery.
|
||||||
|
// see also:
|
||||||
|
// https://www.freebsd.org/cgi/man.cgi?query=apm&manpath=FreeBSD+13.1-RELEASE+and+Ports
|
||||||
|
// https://man.openbsd.org/amd64/apm.8
|
||||||
|
// https://man.netbsd.org/apm.8
|
||||||
|
let mut json = String::from('{');
|
||||||
|
let re_total = regex!(r"(?m)^Remaining battery life: (\d+)%");
|
||||||
|
let re_single = regex!(r"(?sm)^Battery (\d+):.*?Status: (\w+).*?(\d+)%");
|
||||||
|
for bat in re_single.captures_iter(&batteries) {
|
||||||
|
json.push_str(&format!(
|
||||||
|
r#""BAT{}": {{ "status": "{}", "capacity": {} }}, "#,
|
||||||
|
bat.get(1).unwrap().as_str(),
|
||||||
|
bat.get(2).unwrap().as_str(),
|
||||||
|
bat.get(3).unwrap().as_str(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
json.push_str(&format!(r#""total_avg": {}}}"#, re_total.captures(&batteries).unwrap().get(1).unwrap().as_str()));
|
||||||
|
Ok(json)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
#[cfg(not(target_os = "netbsd"))]
|
||||||
|
#[cfg(not(target_os = "freebsd"))]
|
||||||
|
#[cfg(not(target_os = "openbsd"))]
|
||||||
pub fn get_battery_capacity() -> Result<String> {
|
pub fn get_battery_capacity() -> Result<String> {
|
||||||
Err(anyhow::anyhow!("Eww doesn't support your OS for getting the battery capacity"))
|
Err(anyhow::anyhow!("Eww doesn't support your OS for getting the battery capacity"))
|
||||||
}
|
}
|
||||||
|
@ -212,7 +258,6 @@ pub fn net() -> String {
|
||||||
let (ref mut last_refresh, ref mut networks) = &mut *NETWORKS.lock().unwrap();
|
let (ref mut last_refresh, ref mut networks) = &mut *NETWORKS.lock().unwrap();
|
||||||
|
|
||||||
networks.refresh_list();
|
networks.refresh_list();
|
||||||
networks.refresh();
|
|
||||||
let elapsed = last_refresh.next_refresh();
|
let elapsed = last_refresh.next_refresh();
|
||||||
|
|
||||||
networks
|
networks
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
|
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
|
||||||
|
|
||||||
|
use gtk::gdk;
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
pub use platform_wayland::WaylandBackend;
|
pub use platform_wayland::WaylandBackend;
|
||||||
|
|
||||||
|
@ -8,6 +10,7 @@ pub use platform_x11::{set_xprops, X11Backend};
|
||||||
|
|
||||||
pub trait DisplayBackend: Send + Sync + 'static {
|
pub trait DisplayBackend: Send + Sync + 'static {
|
||||||
const IS_X11: bool;
|
const IS_X11: bool;
|
||||||
|
const IS_WAYLAND: bool;
|
||||||
|
|
||||||
fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window>;
|
fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window>;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +19,7 @@ pub struct NoBackend;
|
||||||
|
|
||||||
impl DisplayBackend for NoBackend {
|
impl DisplayBackend for NoBackend {
|
||||||
const IS_X11: bool = false;
|
const IS_X11: bool = false;
|
||||||
|
const IS_WAYLAND: bool = false;
|
||||||
|
|
||||||
fn initialize_window(_window_init: &WindowInitiator, _monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {
|
fn initialize_window(_window_init: &WindowInitiator, _monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {
|
||||||
Some(Window::new(gtk::WindowType::Toplevel, x, y))
|
Some(Window::new(gtk::WindowType::Toplevel, x, y))
|
||||||
|
@ -24,26 +28,29 @@ impl DisplayBackend for NoBackend {
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
mod platform_wayland {
|
mod platform_wayland {
|
||||||
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
|
|
||||||
use gtk::prelude::*;
|
|
||||||
use yuck::config::{window_definition::WindowStacking, window_geometry::AnchorAlignment};
|
|
||||||
|
|
||||||
use super::DisplayBackend;
|
use super::DisplayBackend;
|
||||||
|
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
|
||||||
|
use gtk::gdk;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk_layer_shell::{KeyboardMode, LayerShell};
|
||||||
|
use yuck::config::backend_window_options::WlWindowFocusable;
|
||||||
|
use yuck::config::{window_definition::WindowStacking, window_geometry::AnchorAlignment};
|
||||||
|
|
||||||
pub struct WaylandBackend;
|
pub struct WaylandBackend;
|
||||||
|
|
||||||
impl DisplayBackend for WaylandBackend {
|
impl DisplayBackend for WaylandBackend {
|
||||||
const IS_X11: bool = false;
|
const IS_X11: bool = false;
|
||||||
|
const IS_WAYLAND: bool = true;
|
||||||
|
|
||||||
fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {
|
fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {
|
||||||
let window = Window::new(gtk::WindowType::Toplevel, x, y);
|
let window = Window::new(gtk::WindowType::Toplevel, x, y);
|
||||||
// Initialising a layer shell surface
|
// Initialising a layer shell surface
|
||||||
gtk_layer_shell::init_for_window(&window);
|
window.init_layer_shell();
|
||||||
// Sets the monitor where the surface is shown
|
// Sets the monitor where the surface is shown
|
||||||
if let Some(ident) = window_init.monitor.clone() {
|
if let Some(ident) = window_init.monitor.clone() {
|
||||||
let display = gdk::Display::default().expect("could not get default display");
|
let display = gdk::Display::default().expect("could not get default display");
|
||||||
if let Some(monitor) = crate::app::get_monitor_from_display(&display, &ident) {
|
if let Some(monitor) = crate::app::get_monitor_from_display(&display, &ident) {
|
||||||
gtk_layer_shell::set_monitor(&window, &monitor);
|
window.set_monitor(&monitor);
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -52,18 +59,22 @@ mod platform_wayland {
|
||||||
|
|
||||||
// Sets the layer where the layer shell surface will spawn
|
// Sets the layer where the layer shell surface will spawn
|
||||||
match window_init.stacking {
|
match window_init.stacking {
|
||||||
WindowStacking::Foreground => gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Top),
|
WindowStacking::Foreground => window.set_layer(gtk_layer_shell::Layer::Top),
|
||||||
WindowStacking::Background => gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Background),
|
WindowStacking::Background => window.set_layer(gtk_layer_shell::Layer::Background),
|
||||||
WindowStacking::Bottom => gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Bottom),
|
WindowStacking::Bottom => window.set_layer(gtk_layer_shell::Layer::Bottom),
|
||||||
WindowStacking::Overlay => gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Overlay),
|
WindowStacking::Overlay => window.set_layer(gtk_layer_shell::Layer::Overlay),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(namespace) = &window_init.backend_options.wayland.namespace {
|
if let Some(namespace) = &window_init.backend_options.wayland.namespace {
|
||||||
gtk_layer_shell::set_namespace(&window, namespace);
|
window.set_namespace(namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the keyboard interactivity
|
// Sets the keyboard interactivity
|
||||||
gtk_layer_shell::set_keyboard_interactivity(&window, window_init.backend_options.wayland.focusable);
|
match window_init.backend_options.wayland.focusable {
|
||||||
|
WlWindowFocusable::None => window.set_keyboard_mode(KeyboardMode::None),
|
||||||
|
WlWindowFocusable::Exclusive => window.set_keyboard_mode(KeyboardMode::Exclusive),
|
||||||
|
WlWindowFocusable::OnDemand => window.set_keyboard_mode(KeyboardMode::OnDemand),
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(geometry) = window_init.geometry {
|
if let Some(geometry) = window_init.geometry {
|
||||||
// Positioning surface
|
// Positioning surface
|
||||||
|
@ -83,27 +94,34 @@ mod platform_wayland {
|
||||||
AnchorAlignment::END => bottom = true,
|
AnchorAlignment::END => bottom = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk_layer_shell::set_anchor(&window, gtk_layer_shell::Edge::Left, left);
|
window.set_anchor(gtk_layer_shell::Edge::Left, left);
|
||||||
gtk_layer_shell::set_anchor(&window, gtk_layer_shell::Edge::Right, right);
|
window.set_anchor(gtk_layer_shell::Edge::Right, right);
|
||||||
gtk_layer_shell::set_anchor(&window, gtk_layer_shell::Edge::Top, top);
|
window.set_anchor(gtk_layer_shell::Edge::Top, top);
|
||||||
gtk_layer_shell::set_anchor(&window, gtk_layer_shell::Edge::Bottom, bottom);
|
window.set_anchor(gtk_layer_shell::Edge::Bottom, bottom);
|
||||||
|
|
||||||
let xoffset = geometry.offset.x.pixels_relative_to(monitor.width());
|
let xoffset = geometry.offset.x.pixels_relative_to(monitor.width());
|
||||||
let yoffset = geometry.offset.y.pixels_relative_to(monitor.height());
|
let yoffset = geometry.offset.y.pixels_relative_to(monitor.height());
|
||||||
|
|
||||||
if left {
|
if left {
|
||||||
gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Left, xoffset);
|
window.set_layer_shell_margin(gtk_layer_shell::Edge::Left, xoffset);
|
||||||
} else {
|
} else {
|
||||||
gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Right, xoffset);
|
window.set_layer_shell_margin(gtk_layer_shell::Edge::Right, xoffset);
|
||||||
}
|
}
|
||||||
if bottom {
|
if bottom {
|
||||||
gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Bottom, yoffset);
|
window.set_layer_shell_margin(gtk_layer_shell::Edge::Bottom, yoffset);
|
||||||
} else {
|
} else {
|
||||||
gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Top, yoffset);
|
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, yoffset);
|
||||||
|
}
|
||||||
|
// https://github.com/elkowar/eww/issues/296
|
||||||
|
if window_init.backend_options.wayland.exclusive
|
||||||
|
&& geometry.anchor_point.x != AnchorAlignment::CENTER
|
||||||
|
&& geometry.anchor_point.y != AnchorAlignment::CENTER
|
||||||
|
{
|
||||||
|
log::warn!("When ':exclusive true' the anchor has to include 'center', otherwise exlcusive won't work")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if window_init.backend_options.wayland.exclusive {
|
if window_init.backend_options.wayland.exclusive {
|
||||||
gtk_layer_shell::auto_exclusive_zone_enable(&window);
|
window.auto_exclusive_zone_enable();
|
||||||
}
|
}
|
||||||
Some(window)
|
Some(window)
|
||||||
}
|
}
|
||||||
|
@ -115,6 +133,7 @@ mod platform_x11 {
|
||||||
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
|
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use gdk::Monitor;
|
use gdk::Monitor;
|
||||||
|
use gtk::gdk;
|
||||||
use gtk::{self, prelude::*};
|
use gtk::{self, prelude::*};
|
||||||
use x11rb::protocol::xproto::ConnectionExt;
|
use x11rb::protocol::xproto::ConnectionExt;
|
||||||
|
|
||||||
|
@ -134,6 +153,7 @@ mod platform_x11 {
|
||||||
pub struct X11Backend;
|
pub struct X11Backend;
|
||||||
impl DisplayBackend for X11Backend {
|
impl DisplayBackend for X11Backend {
|
||||||
const IS_X11: bool = true;
|
const IS_X11: bool = true;
|
||||||
|
const IS_WAYLAND: bool = false;
|
||||||
|
|
||||||
fn initialize_window(window_init: &WindowInitiator, _monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {
|
fn initialize_window(window_init: &WindowInitiator, _monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {
|
||||||
let window_type =
|
let window_type =
|
||||||
|
|
|
@ -31,9 +31,6 @@ pub fn print_error(err: anyhow::Error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_error(err: &anyhow::Error) -> String {
|
pub fn format_error(err: &anyhow::Error) -> String {
|
||||||
for err in err.chain() {
|
|
||||||
format!("chain: {}", err);
|
|
||||||
}
|
|
||||||
anyhow_err_to_diagnostic(err).and_then(|diag| stringify_diagnostic(diag).ok()).unwrap_or_else(|| format!("{:?}", err))
|
anyhow_err_to_diagnostic(err).and_then(|diag| stringify_diagnostic(diag).ok()).unwrap_or_else(|| format!("{:?}", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use derive_more::*;
|
use derive_more::{Debug, *};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display)]
|
||||||
#[display(fmt = ".x*.y:.width*.height")]
|
#[display(".x*.y:.width*.height")]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
|
|
|
@ -53,28 +53,31 @@ fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let detected_wayland = detect_wayland();
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
let use_wayland = opts.force_wayland || detect_wayland();
|
let use_wayland = opts.force_wayland || detected_wayland;
|
||||||
#[cfg(all(feature = "wayland", feature = "x11"))]
|
#[cfg(all(feature = "wayland", feature = "x11"))]
|
||||||
let result = if use_wayland {
|
let result = if use_wayland {
|
||||||
run(opts, eww_binary_name, display_backend::WaylandBackend)
|
log::debug!("Running on wayland. force_wayland={}, detected_wayland={}", opts.force_wayland, detected_wayland);
|
||||||
|
run::<display_backend::WaylandBackend>(opts, eww_binary_name)
|
||||||
} else {
|
} else {
|
||||||
run(opts, eww_binary_name, display_backend::X11Backend)
|
log::debug!("Running on X11. force_wayland={}, detected_wayland={}", opts.force_wayland, detected_wayland);
|
||||||
|
run::<display_backend::X11Backend>(opts, eww_binary_name)
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
|
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
|
||||||
let result = {
|
let result = {
|
||||||
if use_wayland {
|
if use_wayland {
|
||||||
log::warn!("Eww compiled without wayland support. falling back to X11, eventhough wayland was requested.");
|
log::warn!("Eww compiled without wayland support. Falling back to X11, eventhough wayland was requested.");
|
||||||
}
|
}
|
||||||
run(opts, eww_binary_name, display_backend::X11Backend)
|
run::<display_backend::X11Backend>(opts, eww_binary_name)
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(all(feature = "wayland", not(feature = "x11")))]
|
#[cfg(all(feature = "wayland", not(feature = "x11")))]
|
||||||
let result = run(opts, eww_binary_name, display_backend::WaylandBackend);
|
let result = run::<display_backend::WaylandBackend>(opts, eww_binary_name);
|
||||||
|
|
||||||
#[cfg(not(any(feature = "wayland", feature = "x11")))]
|
#[cfg(not(any(feature = "wayland", feature = "x11")))]
|
||||||
let result = run(opts, eww_binary_name, display_backend::NoBackend);
|
let result = run::<display_backend::NoBackend>(opts, eww_binary_name);
|
||||||
|
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
error_handling_ctx::print_error(err);
|
error_handling_ctx::print_error(err);
|
||||||
|
@ -88,7 +91,7 @@ fn detect_wayland() -> bool {
|
||||||
session_type.contains("wayland") || (!wayland_display.is_empty() && !session_type.contains("x11"))
|
session_type.contains("wayland") || (!wayland_display.is_empty() && !session_type.contains("x11"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run<B: DisplayBackend>(opts: opts::Opt, eww_binary_name: String, display_backend: B) -> Result<()> {
|
fn run<B: DisplayBackend>(opts: opts::Opt, eww_binary_name: String) -> Result<()> {
|
||||||
let paths = opts
|
let paths = opts
|
||||||
.config_path
|
.config_path
|
||||||
.map(EwwPaths::from_config_dir)
|
.map(EwwPaths::from_config_dir)
|
||||||
|
@ -128,7 +131,7 @@ fn run<B: DisplayBackend>(opts: opts::Opt, eww_binary_name: String, display_back
|
||||||
if !opts.show_logs {
|
if !opts.show_logs {
|
||||||
println!("Run `{} logs` to see any errors while editing your configuration.", eww_binary_name);
|
println!("Run `{} logs` to see any errors while editing your configuration.", eww_binary_name);
|
||||||
}
|
}
|
||||||
let fork_result = server::initialize_server(paths.clone(), None, display_backend, !opts.no_daemonize)?;
|
let fork_result = server::initialize_server::<B>(paths.clone(), None, !opts.no_daemonize)?;
|
||||||
opts.no_daemonize || fork_result == ForkResult::Parent
|
opts.no_daemonize || fork_result == ForkResult::Parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +163,7 @@ fn run<B: DisplayBackend>(opts: opts::Opt, eww_binary_name: String, display_back
|
||||||
|
|
||||||
let (command, response_recv) = action.into_daemon_command();
|
let (command, response_recv) = action.into_daemon_command();
|
||||||
// start the daemon and give it the command
|
// start the daemon and give it the command
|
||||||
let fork_result = server::initialize_server(paths.clone(), Some(command), display_backend, true)?;
|
let fork_result = server::initialize_server::<B>(paths.clone(), Some(command), true)?;
|
||||||
let is_parent = fork_result == ForkResult::Parent;
|
let is_parent = fork_result == ForkResult::Parent;
|
||||||
if let (Some(recv), true) = (response_recv, is_parent) {
|
if let (Some(recv), true) = (response_recv, is_parent) {
|
||||||
listen_for_daemon_response(recv);
|
listen_for_daemon_response(recv);
|
||||||
|
|
|
@ -98,6 +98,16 @@ pub enum ActionWithServer {
|
||||||
mappings: Vec<(VarName, DynVal)>,
|
mappings: Vec<(VarName, DynVal)>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Update a polling variable using its script.
|
||||||
|
///
|
||||||
|
/// This will force the variable to be updated even if its
|
||||||
|
/// automatic polling is disabled.
|
||||||
|
#[command(name = "poll")]
|
||||||
|
Poll {
|
||||||
|
/// Variables to be polled
|
||||||
|
names: Vec<VarName>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Open the GTK debugger
|
/// Open the GTK debugger
|
||||||
#[command(name = "inspector", alias = "debugger")]
|
#[command(name = "inspector", alias = "debugger")]
|
||||||
OpenInspector,
|
OpenInspector,
|
||||||
|
@ -254,6 +264,7 @@ impl ActionWithServer {
|
||||||
pub fn into_daemon_command(self) -> (app::DaemonCommand, Option<daemon_response::DaemonResponseReceiver>) {
|
pub fn into_daemon_command(self) -> (app::DaemonCommand, Option<daemon_response::DaemonResponseReceiver>) {
|
||||||
let command = match self {
|
let command = match self {
|
||||||
ActionWithServer::Update { mappings } => app::DaemonCommand::UpdateVars(mappings),
|
ActionWithServer::Update { mappings } => app::DaemonCommand::UpdateVars(mappings),
|
||||||
|
ActionWithServer::Poll { names } => app::DaemonCommand::PollVars(names),
|
||||||
ActionWithServer::OpenInspector => app::DaemonCommand::OpenInspector,
|
ActionWithServer::OpenInspector => app::DaemonCommand::OpenInspector,
|
||||||
|
|
||||||
ActionWithServer::KillServer => app::DaemonCommand::KillServer,
|
ActionWithServer::KillServer => app::DaemonCommand::KillServer,
|
||||||
|
@ -281,7 +292,7 @@ impl ActionWithServer {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ActionWithServer::CloseWindows { windows } => {
|
ActionWithServer::CloseWindows { windows } => {
|
||||||
return with_response_channel(|sender| app::DaemonCommand::CloseWindows { windows, sender });
|
return with_response_channel(|sender| app::DaemonCommand::CloseWindows { windows, auto_reopen: false, sender });
|
||||||
}
|
}
|
||||||
ActionWithServer::Reload => return with_response_channel(app::DaemonCommand::ReloadConfigAndCss),
|
ActionWithServer::Reload => return with_response_channel(app::DaemonCommand::ReloadConfigAndCss),
|
||||||
ActionWithServer::ListWindows => return with_response_channel(app::DaemonCommand::ListWindows),
|
ActionWithServer::ListWindows => return with_response_channel(app::DaemonCommand::ListWindows),
|
||||||
|
|
|
@ -196,7 +196,7 @@ impl PollVarHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_poll_once(var: &PollScriptVar) -> Result<DynVal> {
|
pub fn run_poll_once(var: &PollScriptVar) -> Result<DynVal> {
|
||||||
match &var.command {
|
match &var.command {
|
||||||
VarSource::Shell(span, command) => {
|
VarSource::Shell(span, command) => {
|
||||||
script_var::run_command(command).map_err(|e| anyhow!(create_script_var_failed_warn(*span, &var.name, &e.to_string())))
|
script_var::run_command(command).map_err(|e| anyhow!(create_script_var_failed_warn(*span, &var.name, &e.to_string())))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{self, DaemonCommand},
|
app::{self, App, DaemonCommand},
|
||||||
config, daemon_response,
|
config, daemon_response,
|
||||||
display_backend::DisplayBackend,
|
display_backend::DisplayBackend,
|
||||||
error_handling_ctx, ipc_server, script_var_handler,
|
error_handling_ctx, ipc_server, script_var_handler,
|
||||||
|
@ -12,6 +12,7 @@ use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
io::Write,
|
io::Write,
|
||||||
|
marker::PhantomData,
|
||||||
os::unix::io::AsRawFd,
|
os::unix::io::AsRawFd,
|
||||||
path::Path,
|
path::Path,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
@ -22,7 +23,6 @@ use tokio::sync::mpsc::*;
|
||||||
pub fn initialize_server<B: DisplayBackend>(
|
pub fn initialize_server<B: DisplayBackend>(
|
||||||
paths: EwwPaths,
|
paths: EwwPaths,
|
||||||
action: Option<DaemonCommand>,
|
action: Option<DaemonCommand>,
|
||||||
display_backend: B,
|
|
||||||
should_daemonize: bool,
|
should_daemonize: bool,
|
||||||
) -> Result<ForkResult> {
|
) -> Result<ForkResult> {
|
||||||
let (ui_send, mut ui_recv) = tokio::sync::mpsc::unbounded_channel();
|
let (ui_send, mut ui_recv) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
@ -68,6 +68,9 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if B::IS_WAYLAND {
|
||||||
|
std::env::set_var("GDK_BACKEND", "wayland")
|
||||||
|
}
|
||||||
gtk::init()?;
|
gtk::init()?;
|
||||||
|
|
||||||
log::debug!("Initializing script var handler");
|
log::debug!("Initializing script var handler");
|
||||||
|
@ -75,8 +78,7 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||||
|
|
||||||
let (scope_graph_evt_send, mut scope_graph_evt_recv) = tokio::sync::mpsc::unbounded_channel();
|
let (scope_graph_evt_send, mut scope_graph_evt_recv) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
|
||||||
let mut app = app::App {
|
let mut app: App<B> = app::App {
|
||||||
display_backend,
|
|
||||||
scope_graph: Rc::new(RefCell::new(ScopeGraph::from_global_vars(
|
scope_graph: Rc::new(RefCell::new(ScopeGraph::from_global_vars(
|
||||||
eww_config.generate_initial_state()?,
|
eww_config.generate_initial_state()?,
|
||||||
scope_graph_evt_send,
|
scope_graph_evt_send,
|
||||||
|
@ -90,9 +92,10 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||||
app_evt_send: ui_send.clone(),
|
app_evt_send: ui_send.clone(),
|
||||||
window_close_timer_abort_senders: HashMap::new(),
|
window_close_timer_abort_senders: HashMap::new(),
|
||||||
paths,
|
paths,
|
||||||
|
phantom: PhantomData,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(screen) = gdk::Screen::default() {
|
if let Some(screen) = gtk::gdk::Screen::default() {
|
||||||
gtk::StyleContext::add_provider_for_screen(&screen, &app.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
|
gtk::StyleContext::add_provider_for_screen(&screen, &app.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,13 +105,15 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connect_monitor_added(ui_send.clone());
|
||||||
|
|
||||||
// initialize all the handlers and tasks running asyncronously
|
// initialize all the handlers and tasks running asyncronously
|
||||||
let tokio_handle = init_async_part(app.paths.clone(), ui_send);
|
let tokio_handle = init_async_part(app.paths.clone(), ui_send);
|
||||||
|
|
||||||
glib::MainContext::default().spawn_local(async move {
|
gtk::glib::MainContext::default().spawn_local(async move {
|
||||||
// if an action was given to the daemon initially, execute it first.
|
// if an action was given to the daemon initially, execute it first.
|
||||||
if let Some(action) = action {
|
if let Some(action) = action {
|
||||||
app.handle_command(action);
|
app.handle_command(action).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -117,7 +122,7 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||||
app.scope_graph.borrow_mut().handle_scope_graph_event(scope_graph_evt);
|
app.scope_graph.borrow_mut().handle_scope_graph_event(scope_graph_evt);
|
||||||
},
|
},
|
||||||
Some(ui_event) = ui_recv.recv() => {
|
Some(ui_event) = ui_recv.recv() => {
|
||||||
app.handle_command(ui_event);
|
app.handle_command(ui_event).await;
|
||||||
}
|
}
|
||||||
else => break,
|
else => break,
|
||||||
}
|
}
|
||||||
|
@ -133,6 +138,29 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||||
Ok(ForkResult::Child)
|
Ok(ForkResult::Child)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn connect_monitor_added(ui_send: UnboundedSender<DaemonCommand>) {
|
||||||
|
let display = gtk::gdk::Display::default().expect("could not get default display");
|
||||||
|
display.connect_monitor_added({
|
||||||
|
move |_display: >k::gdk::Display, _monitor: >k::gdk::Monitor| {
|
||||||
|
log::info!("New monitor connected, reloading configuration");
|
||||||
|
let _ = reload_config_and_css(&ui_send);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload_config_and_css(ui_send: &UnboundedSender<DaemonCommand>) -> Result<()> {
|
||||||
|
let (daemon_resp_sender, mut daemon_resp_response) = daemon_response::create_pair();
|
||||||
|
ui_send.send(DaemonCommand::ReloadConfigAndCss(daemon_resp_sender))?;
|
||||||
|
tokio::spawn(async move {
|
||||||
|
match daemon_resp_response.recv().await {
|
||||||
|
Some(daemon_response::DaemonResponse::Success(_)) => log::info!("Reloaded config successfully"),
|
||||||
|
Some(daemon_response::DaemonResponse::Failure(e)) => eprintln!("{}", e),
|
||||||
|
None => log::error!("No response to reload configuration-reload request"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::DaemonCommand>) -> tokio::runtime::Handle {
|
fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::DaemonCommand>) -> tokio::runtime::Handle {
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.thread_name("main-async-runtime")
|
.thread_name("main-async-runtime")
|
||||||
|
@ -213,20 +241,12 @@ async fn run_filewatch<P: AsRef<Path>>(config_dir: P, evt_send: UnboundedSender<
|
||||||
debounce_done.store(true, Ordering::SeqCst);
|
debounce_done.store(true, Ordering::SeqCst);
|
||||||
});
|
});
|
||||||
|
|
||||||
let (daemon_resp_sender, mut daemon_resp_response) = daemon_response::create_pair();
|
|
||||||
// without this sleep, reading the config file sometimes gives an empty file.
|
// without this sleep, reading the config file sometimes gives an empty file.
|
||||||
// This is probably a result of editors not locking the file correctly,
|
// This is probably a result of editors not locking the file correctly,
|
||||||
// and eww being too fast, thus reading the file while it's empty.
|
// and eww being too fast, thus reading the file while it's empty.
|
||||||
// There should be some cleaner solution for this, but this will do for now.
|
// There should be some cleaner solution for this, but this will do for now.
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||||
evt_send.send(app::DaemonCommand::ReloadConfigAndCss(daemon_resp_sender))?;
|
reload_config_and_css(&evt_send)?;
|
||||||
tokio::spawn(async move {
|
|
||||||
match daemon_resp_response.recv().await {
|
|
||||||
Some(daemon_response::DaemonResponse::Success(_)) => log::info!("Reloaded config successfully"),
|
|
||||||
Some(daemon_response::DaemonResponse::Failure(e)) => eprintln!("{}", e),
|
|
||||||
None => log::error!("No response to reload configuration-reload request"),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => break
|
else => break
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use codespan_reporting::diagnostic::Severity;
|
use codespan_reporting::diagnostic::Severity;
|
||||||
use eww_shared_util::{AttrName, Spanned};
|
use eww_shared_util::{AttrName, Spanned};
|
||||||
use gdk::prelude::Cast;
|
|
||||||
use gtk::{
|
use gtk::{
|
||||||
|
gdk::prelude::Cast,
|
||||||
prelude::{BoxExt, ContainerExt, WidgetExt},
|
prelude::{BoxExt, ContainerExt, WidgetExt},
|
||||||
Orientation,
|
Orientation,
|
||||||
};
|
};
|
||||||
|
@ -306,13 +306,14 @@ fn build_children_special_widget(
|
||||||
.children
|
.children
|
||||||
.get(nth_value as usize)
|
.get(nth_value as usize)
|
||||||
.with_context(|| format!("No child at index {}", nth_value))?;
|
.with_context(|| format!("No child at index {}", nth_value))?;
|
||||||
let new_child_widget = build_gtk_widget(
|
let scope = tree.register_new_scope(
|
||||||
tree,
|
format!("child {nth_value}"),
|
||||||
widget_defs.clone(),
|
Some(custom_widget_invocation.scope),
|
||||||
custom_widget_invocation.scope,
|
calling_scope,
|
||||||
nth_child_widget_use.clone(),
|
HashMap::new(),
|
||||||
None,
|
|
||||||
)?;
|
)?;
|
||||||
|
let new_child_widget =
|
||||||
|
build_gtk_widget(tree, widget_defs.clone(), scope, nth_child_widget_use.clone(), None)?;
|
||||||
child_container.children().iter().for_each(|f| child_container.remove(f));
|
child_container.children().iter().for_each(|f| child_container.remove(f));
|
||||||
child_container.set_child(Some(&new_child_widget));
|
child_container.set_child(Some(&new_child_widget));
|
||||||
new_child_widget.show();
|
new_child_widget.show();
|
||||||
|
@ -323,7 +324,13 @@ fn build_children_special_widget(
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
for child in &custom_widget_invocation.children {
|
for child in &custom_widget_invocation.children {
|
||||||
let child_widget = build_gtk_widget(tree, widget_defs.clone(), custom_widget_invocation.scope, child.clone(), None)?;
|
let scope = tree.register_new_scope(
|
||||||
|
String::from("child"),
|
||||||
|
Some(custom_widget_invocation.scope),
|
||||||
|
calling_scope,
|
||||||
|
HashMap::new(),
|
||||||
|
)?;
|
||||||
|
let child_widget = build_gtk_widget(tree, widget_defs.clone(), scope, child.clone(), None)?;
|
||||||
gtk_container.add(&child_widget);
|
gtk_container.add(&child_widget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use glib::{object_subclass, prelude::*, wrapper};
|
use gtk::glib::{self, object_subclass, prelude::*, wrapper, Properties};
|
||||||
use glib_macros::Properties;
|
use gtk::{cairo, gdk, prelude::*, subclass::prelude::*};
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use crate::error_handling_ctx;
|
use crate::error_handling_ctx;
|
||||||
|
@ -154,7 +153,7 @@ impl WidgetImpl for CircProgPriv {
|
||||||
self.preferred_height()
|
self.preferred_height()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, cr: &cairo::Context) -> Inhibit {
|
fn draw(&self, cr: &cairo::Context) -> glib::Propagation {
|
||||||
let res: Result<()> = (|| {
|
let res: Result<()> = (|| {
|
||||||
let value = *self.value.borrow();
|
let value = *self.value.borrow();
|
||||||
let start_at = *self.start_at.borrow();
|
let start_at = *self.start_at.borrow();
|
||||||
|
@ -226,7 +225,7 @@ impl WidgetImpl for CircProgPriv {
|
||||||
error_handling_ctx::print_error(error)
|
error_handling_ctx::print_error(error)
|
||||||
};
|
};
|
||||||
|
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,8 @@ use std::{cell::RefCell, collections::VecDeque};
|
||||||
// https://www.figuiere.net/technotes/notes/tn002/
|
// https://www.figuiere.net/technotes/notes/tn002/
|
||||||
// https://github.com/gtk-rs/examples/blob/master/src/bin/listbox_model.rs
|
// https://github.com/gtk-rs/examples/blob/master/src/bin/listbox_model.rs
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use glib::{object_subclass, wrapper};
|
use gtk::glib::{self, object_subclass, wrapper, Properties};
|
||||||
use glib_macros::Properties;
|
use gtk::{cairo, gdk, prelude::*, subclass::prelude::*};
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
|
||||||
|
|
||||||
use crate::error_handling_ctx;
|
use crate::error_handling_ctx;
|
||||||
|
|
||||||
|
@ -39,6 +38,13 @@ pub struct GraphPriv {
|
||||||
#[property(get, set, nick = "Time Range", blurb = "The Time Range", minimum = 0u64, maximum = u64::MAX, default = 10u64)]
|
#[property(get, set, nick = "Time Range", blurb = "The Time Range", minimum = 0u64, maximum = u64::MAX, default = 10u64)]
|
||||||
time_range: RefCell<u64>,
|
time_range: RefCell<u64>,
|
||||||
|
|
||||||
|
#[property(get, set, nick = "Flip X", blurb = "Flip the x axis", default = true)]
|
||||||
|
flip_x: RefCell<bool>,
|
||||||
|
#[property(get, set, nick = "Flip Y", blurb = "Flip the y axis", default = true)]
|
||||||
|
flip_y: RefCell<bool>,
|
||||||
|
#[property(get, set, nick = "Vertical", blurb = "Exchange the x and y axes", default = false)]
|
||||||
|
vertical: RefCell<bool>,
|
||||||
|
|
||||||
history: RefCell<VecDeque<(std::time::Instant, f64)>>,
|
history: RefCell<VecDeque<(std::time::Instant, f64)>>,
|
||||||
extra_point: RefCell<Option<(std::time::Instant, f64)>>,
|
extra_point: RefCell<Option<(std::time::Instant, f64)>>,
|
||||||
last_updated_at: RefCell<std::time::Instant>,
|
last_updated_at: RefCell<std::time::Instant>,
|
||||||
|
@ -54,6 +60,9 @@ impl Default for GraphPriv {
|
||||||
max: RefCell::new(100.0),
|
max: RefCell::new(100.0),
|
||||||
dynamic: RefCell::new(true),
|
dynamic: RefCell::new(true),
|
||||||
time_range: RefCell::new(10),
|
time_range: RefCell::new(10),
|
||||||
|
flip_x: RefCell::new(true),
|
||||||
|
flip_y: RefCell::new(true),
|
||||||
|
vertical: RefCell::new(false),
|
||||||
history: RefCell::new(VecDeque::new()),
|
history: RefCell::new(VecDeque::new()),
|
||||||
extra_point: RefCell::new(None),
|
extra_point: RefCell::new(None),
|
||||||
last_updated_at: RefCell::new(std::time::Instant::now()),
|
last_updated_at: RefCell::new(std::time::Instant::now()),
|
||||||
|
@ -78,6 +87,16 @@ impl GraphPriv {
|
||||||
}
|
}
|
||||||
history.push_back(v);
|
history.push_back(v);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Receives normalized (0-1) coordinates `x` and `y` and convert them to the
|
||||||
|
* point on the widget.
|
||||||
|
*/
|
||||||
|
fn value_to_point(&self, width: f64, height: f64, x: f64, y: f64) -> (f64, f64) {
|
||||||
|
let x = if *self.flip_x.borrow() { 1.0 - x } else { x };
|
||||||
|
let y = if *self.flip_y.borrow() { 1.0 - y } else { y };
|
||||||
|
let (x, y) = if *self.vertical.borrow() { (y, x) } else { (x, y) };
|
||||||
|
(width * x, height * y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectImpl for GraphPriv {
|
impl ObjectImpl for GraphPriv {
|
||||||
|
@ -111,6 +130,15 @@ impl ObjectImpl for GraphPriv {
|
||||||
"line-style" => {
|
"line-style" => {
|
||||||
self.line_style.replace(value.get().unwrap());
|
self.line_style.replace(value.get().unwrap());
|
||||||
}
|
}
|
||||||
|
"flip-x" => {
|
||||||
|
self.flip_x.replace(value.get().unwrap());
|
||||||
|
}
|
||||||
|
"flip-y" => {
|
||||||
|
self.flip_y.replace(value.get().unwrap());
|
||||||
|
}
|
||||||
|
"vertical" => {
|
||||||
|
self.vertical.replace(value.get().unwrap());
|
||||||
|
}
|
||||||
x => panic!("Tried to set inexistant property of Graph: {}", x,),
|
x => panic!("Tried to set inexistant property of Graph: {}", x,),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,7 +198,7 @@ impl WidgetImpl for GraphPriv {
|
||||||
(width, width)
|
(width, width)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, cr: &cairo::Context) -> Inhibit {
|
fn draw(&self, cr: &cairo::Context) -> glib::Propagation {
|
||||||
let res: Result<()> = (|| {
|
let res: Result<()> = (|| {
|
||||||
let history = &*self.history.borrow();
|
let history = &*self.history.borrow();
|
||||||
let extra_point = *self.extra_point.borrow();
|
let extra_point = *self.extra_point.borrow();
|
||||||
|
@ -215,18 +243,15 @@ impl WidgetImpl for GraphPriv {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(instant, value)| {
|
.map(|(instant, value)| {
|
||||||
let t = last_updated_at.duration_since(*instant).as_millis() as f64;
|
let t = last_updated_at.duration_since(*instant).as_millis() as f64;
|
||||||
let x = width * (1.0 - (t / time_range));
|
self.value_to_point(width, height, t / time_range, (value - min) / value_range)
|
||||||
let y = height * (1.0 - ((value - min) / value_range));
|
|
||||||
(x, y)
|
|
||||||
})
|
})
|
||||||
.collect::<VecDeque<(f64, f64)>>();
|
.collect::<VecDeque<(f64, f64)>>();
|
||||||
|
|
||||||
// Aad an extra point outside of the graph to extend the line to the left
|
// Aad an extra point outside of the graph to extend the line to the left
|
||||||
if let Some((instant, value)) = extra_point {
|
if let Some((instant, value)) = extra_point {
|
||||||
let t = last_updated_at.duration_since(instant).as_millis() as f64;
|
let t = last_updated_at.duration_since(instant).as_millis() as f64;
|
||||||
let x = -width * ((t - time_range) / time_range);
|
let (x, y) = self.value_to_point(width, height, (t - time_range) / time_range, (value - min) / value_range);
|
||||||
let y = height * (1.0 - ((value - min) / value_range));
|
points.push_front(if *self.vertical.borrow() { (x, -y) } else { (-x, y) });
|
||||||
points.push_front((x, y));
|
|
||||||
}
|
}
|
||||||
points
|
points
|
||||||
};
|
};
|
||||||
|
@ -276,7 +301,7 @@ impl WidgetImpl for GraphPriv {
|
||||||
error_handling_ctx::print_error(error)
|
error_handling_ctx::print_error(error)
|
||||||
};
|
};
|
||||||
|
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
use crate::widgets::window::Window;
|
use crate::widgets::window::Window;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gtk::{cairo::Surface, gdk::ffi::gdk_cairo_surface_create_from_pixbuf, prelude::*};
|
use gtk::{
|
||||||
use notifier_host;
|
cairo::Surface,
|
||||||
|
gdk::{self, ffi::gdk_cairo_surface_create_from_pixbuf, NotifyType},
|
||||||
|
glib,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
use std::{cell::RefCell, future::Future, rc::Rc};
|
use std::{cell::RefCell, future::Future, rc::Rc};
|
||||||
|
|
||||||
// DBus state shared between systray instances, to avoid creating too many connections etc.
|
// DBus state shared between systray instances, to avoid creating too many connections etc.
|
||||||
|
@ -105,6 +109,7 @@ impl notifier_host::Host for Tray {
|
||||||
fn remove_item(&mut self, id: &str) {
|
fn remove_item(&mut self, id: &str) {
|
||||||
if let Some(item) = self.items.get(id) {
|
if let Some(item) = self.items.get(id) {
|
||||||
self.container.remove(&item.widget);
|
self.container.remove(&item.widget);
|
||||||
|
self.items.remove(id);
|
||||||
} else {
|
} else {
|
||||||
log::warn!("Tried to remove nonexistent item {:?} from systray", id);
|
log::warn!("Tried to remove nonexistent item {:?} from systray", id);
|
||||||
}
|
}
|
||||||
|
@ -130,11 +135,27 @@ impl Drop for Item {
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
fn new(id: String, item: notifier_host::Item, icon_size: tokio::sync::watch::Receiver<i32>) -> Self {
|
fn new(id: String, item: notifier_host::Item, icon_size: tokio::sync::watch::Receiver<i32>) -> Self {
|
||||||
let widget = gtk::EventBox::new();
|
let gtk_widget = gtk::EventBox::new();
|
||||||
let out_widget = widget.clone(); // copy so we can return it
|
|
||||||
|
// Support :hover selector
|
||||||
|
gtk_widget.connect_enter_notify_event(|gtk_widget, evt| {
|
||||||
|
if evt.detail() != NotifyType::Inferior {
|
||||||
|
gtk_widget.clone().set_state_flags(gtk::StateFlags::PRELIGHT, false);
|
||||||
|
}
|
||||||
|
glib::Propagation::Proceed
|
||||||
|
});
|
||||||
|
|
||||||
|
gtk_widget.connect_leave_notify_event(|gtk_widget, evt| {
|
||||||
|
if evt.detail() != NotifyType::Inferior {
|
||||||
|
gtk_widget.clone().unset_state_flags(gtk::StateFlags::PRELIGHT);
|
||||||
|
}
|
||||||
|
glib::Propagation::Proceed
|
||||||
|
});
|
||||||
|
|
||||||
|
let out_widget = gtk_widget.clone(); // copy so we can return it
|
||||||
|
|
||||||
let task = glib::MainContext::default().spawn_local(async move {
|
let task = glib::MainContext::default().spawn_local(async move {
|
||||||
if let Err(e) = Item::maintain(widget.clone(), item, icon_size).await {
|
if let Err(e) = Item::maintain(gtk_widget.clone(), item, icon_size).await {
|
||||||
log::error!("error for systray item {}: {}", id, e);
|
log::error!("error for systray item {}: {}", id, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -213,7 +234,7 @@ impl Item {
|
||||||
if let Err(result) = result {
|
if let Err(result) = result {
|
||||||
log::error!("failed to handle mouse click {}: {}", evt.button(), result);
|
log::error!("failed to handle mouse click {}: {}", evt.button(), result);
|
||||||
}
|
}
|
||||||
gtk::Inhibit(true)
|
glib::Propagation::Stop
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// updates
|
// updates
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use glib::{object_subclass, wrapper};
|
use gtk::glib::{self, object_subclass, wrapper, Properties};
|
||||||
use glib_macros::Properties;
|
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
use std::{cell::RefCell, str::FromStr};
|
use std::{cell::RefCell, str::FromStr};
|
||||||
use yuck::value::NumWithUnit;
|
use yuck::value::NumWithUnit;
|
||||||
|
@ -18,6 +17,12 @@ pub struct TransformPriv {
|
||||||
#[property(get, set, nick = "Rotate", blurb = "The Rotation", minimum = f64::MIN, maximum = f64::MAX, default = 0f64)]
|
#[property(get, set, nick = "Rotate", blurb = "The Rotation", minimum = f64::MIN, maximum = f64::MAX, default = 0f64)]
|
||||||
rotate: RefCell<f64>,
|
rotate: RefCell<f64>,
|
||||||
|
|
||||||
|
#[property(get, set, nick = "Transform-Origin X", blurb = "X coordinate (%/px) for the Transform-Origin", default = None)]
|
||||||
|
transform_origin_x: RefCell<Option<String>>,
|
||||||
|
|
||||||
|
#[property(get, set, nick = "Transform-Origin Y", blurb = "Y coordinate (%/px) for the Transform-Origin", default = None)]
|
||||||
|
transform_origin_y: RefCell<Option<String>>,
|
||||||
|
|
||||||
#[property(get, set, nick = "Translate x", blurb = "The X Translation", default = None)]
|
#[property(get, set, nick = "Translate x", blurb = "The X Translation", default = None)]
|
||||||
translate_x: RefCell<Option<String>>,
|
translate_x: RefCell<Option<String>>,
|
||||||
|
|
||||||
|
@ -38,6 +43,8 @@ impl Default for TransformPriv {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
TransformPriv {
|
TransformPriv {
|
||||||
rotate: RefCell::new(0.0),
|
rotate: RefCell::new(0.0),
|
||||||
|
transform_origin_x: RefCell::new(None),
|
||||||
|
transform_origin_y: RefCell::new(None),
|
||||||
translate_x: RefCell::new(None),
|
translate_x: RefCell::new(None),
|
||||||
translate_y: RefCell::new(None),
|
translate_y: RefCell::new(None),
|
||||||
scale_x: RefCell::new(None),
|
scale_x: RefCell::new(None),
|
||||||
|
@ -58,6 +65,14 @@ impl ObjectImpl for TransformPriv {
|
||||||
self.rotate.replace(value.get().unwrap());
|
self.rotate.replace(value.get().unwrap());
|
||||||
self.obj().queue_draw(); // Queue a draw call with the updated value
|
self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||||
}
|
}
|
||||||
|
"transform-origin-x" => {
|
||||||
|
self.transform_origin_x.replace(value.get().unwrap());
|
||||||
|
self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||||
|
}
|
||||||
|
"transform-origin-y" => {
|
||||||
|
self.transform_origin_y.replace(value.get().unwrap());
|
||||||
|
self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||||
|
}
|
||||||
"translate-x" => {
|
"translate-x" => {
|
||||||
self.translate_x.replace(value.get().unwrap());
|
self.translate_x.replace(value.get().unwrap());
|
||||||
self.obj().queue_draw(); // Queue a draw call with the updated value
|
self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||||
|
@ -121,7 +136,7 @@ impl ContainerImpl for TransformPriv {
|
||||||
|
|
||||||
impl BinImpl for TransformPriv {}
|
impl BinImpl for TransformPriv {}
|
||||||
impl WidgetImpl for TransformPriv {
|
impl WidgetImpl for TransformPriv {
|
||||||
fn draw(&self, cr: &cairo::Context) -> Inhibit {
|
fn draw(&self, cr: >k::cairo::Context) -> glib::Propagation {
|
||||||
let res: Result<()> = (|| {
|
let res: Result<()> = (|| {
|
||||||
let rotate = *self.rotate.borrow();
|
let rotate = *self.rotate.borrow();
|
||||||
let total_width = self.obj().allocated_width() as f64;
|
let total_width = self.obj().allocated_width() as f64;
|
||||||
|
@ -129,6 +144,15 @@ impl WidgetImpl for TransformPriv {
|
||||||
|
|
||||||
cr.save()?;
|
cr.save()?;
|
||||||
|
|
||||||
|
let transform_origin_x = match &*self.transform_origin_x.borrow() {
|
||||||
|
Some(rcx) => NumWithUnit::from_str(rcx)?.pixels_relative_to(total_width as i32) as f64,
|
||||||
|
None => 0.0,
|
||||||
|
};
|
||||||
|
let transform_origin_y = match &*self.transform_origin_y.borrow() {
|
||||||
|
Some(rcy) => NumWithUnit::from_str(rcy)?.pixels_relative_to(total_height as i32) as f64,
|
||||||
|
None => 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
let translate_x = match &*self.translate_x.borrow() {
|
let translate_x = match &*self.translate_x.borrow() {
|
||||||
Some(tx) => NumWithUnit::from_str(tx)?.pixels_relative_to(total_width as i32) as f64,
|
Some(tx) => NumWithUnit::from_str(tx)?.pixels_relative_to(total_width as i32) as f64,
|
||||||
None => 0.0,
|
None => 0.0,
|
||||||
|
@ -149,9 +173,10 @@ impl WidgetImpl for TransformPriv {
|
||||||
None => 1.0,
|
None => 1.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
cr.scale(scale_x, scale_y);
|
cr.translate(transform_origin_x, transform_origin_y);
|
||||||
cr.rotate(perc_to_rad(rotate));
|
cr.rotate(perc_to_rad(rotate));
|
||||||
cr.translate(translate_x, translate_y);
|
cr.translate(translate_x - transform_origin_x, translate_y - transform_origin_y);
|
||||||
|
cr.scale(scale_x, scale_y);
|
||||||
|
|
||||||
// Children widget
|
// Children widget
|
||||||
if let Some(child) = &*self.content.borrow() {
|
if let Some(child) = &*self.content.borrow() {
|
||||||
|
@ -166,7 +191,7 @@ impl WidgetImpl for TransformPriv {
|
||||||
error_handling_ctx::print_error(error)
|
error_handling_ctx::print_error(error)
|
||||||
};
|
};
|
||||||
|
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,11 @@ use crate::{
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use codespan_reporting::diagnostic::Severity;
|
use codespan_reporting::diagnostic::Severity;
|
||||||
use eww_shared_util::Spanned;
|
use eww_shared_util::Spanned;
|
||||||
use gdk::{ModifierType, NotifyType};
|
|
||||||
|
|
||||||
|
use gdk::{ModifierType, NotifyType};
|
||||||
use glib::translate::FromGlib;
|
use glib::translate::FromGlib;
|
||||||
use gtk::{self, glib, prelude::*, DestDefaults, TargetEntry, TargetList};
|
use gtk::{self, glib, prelude::*, DestDefaults, TargetEntry, TargetList};
|
||||||
|
use gtk::{gdk, pango};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
@ -37,16 +38,16 @@ use yuck::{
|
||||||
/// thus not connecting a new handler unless the condition is met.
|
/// thus not connecting a new handler unless the condition is met.
|
||||||
macro_rules! connect_signal_handler {
|
macro_rules! connect_signal_handler {
|
||||||
($widget:ident, if $cond:expr, $connect_expr:expr) => {{
|
($widget:ident, if $cond:expr, $connect_expr:expr) => {{
|
||||||
|
const KEY:&str = std::concat!("signal-handler:", std::line!());
|
||||||
unsafe {
|
unsafe {
|
||||||
let key = ::std::concat!("signal-handler:", ::std::line!());
|
let old = $widget.data::<gtk::glib::SignalHandlerId>(KEY);
|
||||||
let old = $widget.data::<gtk::glib::SignalHandlerId>(key);
|
|
||||||
|
|
||||||
if let Some(old) = old {
|
if let Some(old) = old {
|
||||||
let a = old.as_ref().as_raw();
|
let a = old.as_ref().as_raw();
|
||||||
$widget.disconnect(gtk::glib::SignalHandlerId::from_glib(a));
|
$widget.disconnect(gtk::glib::SignalHandlerId::from_glib(a));
|
||||||
}
|
}
|
||||||
|
|
||||||
$widget.set_data::<gtk::glib::SignalHandlerId>(key, $connect_expr);
|
$widget.set_data::<gtk::glib::SignalHandlerId>(KEY, $connect_expr);
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
($widget:ident, $connect_expr:expr) => {{
|
($widget:ident, $connect_expr:expr) => {{
|
||||||
|
@ -208,10 +209,10 @@ pub(super) fn resolve_widget_attrs(bargs: &mut BuilderArgs, gtk_widget: >k::Wi
|
||||||
prop(visible: as_bool = true) {
|
prop(visible: as_bool = true) {
|
||||||
if visible { gtk_widget.show(); } else { gtk_widget.hide(); }
|
if visible { gtk_widget.show(); } else { gtk_widget.hide(); }
|
||||||
},
|
},
|
||||||
// @prop style - inline css style applied to the widget
|
// @prop style - inline scss style applied to the widget
|
||||||
prop(style: as_string) {
|
prop(style: as_string) {
|
||||||
gtk_widget.reset_style();
|
gtk_widget.reset_style();
|
||||||
css_provider.load_from_data(format!("* {{ {} }}", style).as_bytes())?;
|
css_provider.load_from_data(grass::from_string(format!("* {{ {} }}", style), &grass::Options::default())?.as_bytes())?;
|
||||||
gtk_widget.style_context().add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION)
|
gtk_widget.style_context().add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||||
},
|
},
|
||||||
// @prop css - scss code applied to the widget, i.e.: `button {color: red;}`
|
// @prop css - scss code applied to the widget, i.e.: `button {color: red;}`
|
||||||
|
@ -232,11 +233,11 @@ pub(super) fn resolve_range_attrs(bargs: &mut BuilderArgs, gtk_widget: >k::Ran
|
||||||
let is_being_dragged = Rc::new(RefCell::new(false));
|
let is_being_dragged = Rc::new(RefCell::new(false));
|
||||||
gtk_widget.connect_button_press_event(glib::clone!(@strong is_being_dragged => move |_, _| {
|
gtk_widget.connect_button_press_event(glib::clone!(@strong is_being_dragged => move |_, _| {
|
||||||
*is_being_dragged.borrow_mut() = true;
|
*is_being_dragged.borrow_mut() = true;
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
}));
|
}));
|
||||||
gtk_widget.connect_button_release_event(glib::clone!(@strong is_being_dragged => move |_, _| {
|
gtk_widget.connect_button_release_event(glib::clone!(@strong is_being_dragged => move |_, _| {
|
||||||
*is_being_dragged.borrow_mut() = false;
|
*is_being_dragged.borrow_mut() = false;
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// We keep track of the last value that has been set via gtk_widget.set_value (by a change in the value property).
|
// We keep track of the last value that has been set via gtk_widget.set_value (by a change in the value property).
|
||||||
|
@ -311,15 +312,50 @@ fn build_gtk_combo_box_text(bargs: &mut BuilderArgs) -> Result<gtk::ComboBoxText
|
||||||
|
|
||||||
const WIDGET_NAME_EXPANDER: &str = "expander";
|
const WIDGET_NAME_EXPANDER: &str = "expander";
|
||||||
/// @widget expander
|
/// @widget expander
|
||||||
/// @desc A widget that can expand and collapse, showing/hiding it's children.
|
/// @desc A widget that can expand and collapse, showing/hiding it's children. Should contain
|
||||||
|
/// exactly one child.
|
||||||
fn build_gtk_expander(bargs: &mut BuilderArgs) -> Result<gtk::Expander> {
|
fn build_gtk_expander(bargs: &mut BuilderArgs) -> Result<gtk::Expander> {
|
||||||
let gtk_widget = gtk::Expander::new(None);
|
let gtk_widget = gtk::Expander::new(None);
|
||||||
|
|
||||||
|
match bargs.widget_use.children.len().cmp(&1) {
|
||||||
|
Ordering::Less => {
|
||||||
|
return Err(DiagError(gen_diagnostic!("expander must contain exactly one element", bargs.widget_use.span)).into());
|
||||||
|
}
|
||||||
|
Ordering::Greater => {
|
||||||
|
let (_, additional_children) = bargs.widget_use.children.split_at(1);
|
||||||
|
// we know that there is more than one child, so unwrapping on first and last here is fine.
|
||||||
|
let first_span = additional_children.first().unwrap().span();
|
||||||
|
let last_span = additional_children.last().unwrap().span();
|
||||||
|
return Err(DiagError(gen_diagnostic!(
|
||||||
|
"expander must contain exactly one element, but got more",
|
||||||
|
first_span.to(last_span)
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
Ordering::Equal => {
|
||||||
|
let mut children = bargs.widget_use.children.iter().map(|child| {
|
||||||
|
build_gtk_widget(
|
||||||
|
bargs.scope_graph,
|
||||||
|
bargs.widget_defs.clone(),
|
||||||
|
bargs.calling_scope,
|
||||||
|
child.clone(),
|
||||||
|
bargs.custom_widget_invocation.clone(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
// we have exactly one child, we can unwrap
|
||||||
|
let child = children.next().unwrap()?;
|
||||||
|
gtk_widget.add(&child);
|
||||||
|
child.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def_widget!(bargs, _g, gtk_widget, {
|
def_widget!(bargs, _g, gtk_widget, {
|
||||||
// @prop name - name of the expander
|
// @prop name - name of the expander
|
||||||
prop(name: as_string) { gtk_widget.set_label(Some(&name)); },
|
prop(name: as_string) { gtk_widget.set_label(Some(&name)); },
|
||||||
// @prop expanded - sets if the tree is expanded
|
// @prop expanded - sets if the tree is expanded
|
||||||
prop(expanded: as_bool) { gtk_widget.set_expanded(expanded); }
|
prop(expanded: as_bool) { gtk_widget.set_expanded(expanded); }
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(gtk_widget)
|
Ok(gtk_widget)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,6 +459,9 @@ fn build_gtk_scale(bargs: &mut BuilderArgs) -> Result<gtk::Scale> {
|
||||||
// @prop draw-value - draw the value of the property
|
// @prop draw-value - draw the value of the property
|
||||||
prop(draw_value: as_bool = false) { gtk_widget.set_draw_value(draw_value) },
|
prop(draw_value: as_bool = false) { gtk_widget.set_draw_value(draw_value) },
|
||||||
|
|
||||||
|
// @prop value-pos - position of the drawn value. possible values: $position
|
||||||
|
prop(value_pos: as_string) { gtk_widget.set_value_pos(parse_position_type(&value_pos)?) },
|
||||||
|
|
||||||
// @prop round-digits - Sets the number of decimals to round the value to when it changes
|
// @prop round-digits - Sets the number of decimals to round the value to when it changes
|
||||||
prop(round_digits: as_i32 = 0) { gtk_widget.set_round_digits(round_digits) }
|
prop(round_digits: as_i32 = 0) { gtk_widget.set_round_digits(round_digits) }
|
||||||
|
|
||||||
|
@ -459,7 +498,6 @@ fn build_gtk_input(bargs: &mut BuilderArgs) -> Result<gtk::Entry> {
|
||||||
prop(value: as_string) {
|
prop(value: as_string) {
|
||||||
gtk_widget.set_text(&value);
|
gtk_widget.set_text(&value);
|
||||||
},
|
},
|
||||||
|
|
||||||
// @prop onchange - Command to run when the text changes. The placeholder `{}` will be replaced by the value
|
// @prop onchange - Command to run when the text changes. The placeholder `{}` will be replaced by the value
|
||||||
// @prop timeout - timeout of the command. Default: "200ms"
|
// @prop timeout - timeout of the command. Default: "200ms"
|
||||||
prop(timeout: as_duration = Duration::from_millis(200), onchange: as_string) {
|
prop(timeout: as_duration = Duration::from_millis(200), onchange: as_string) {
|
||||||
|
@ -484,7 +522,7 @@ fn build_gtk_input(bargs: &mut BuilderArgs) -> Result<gtk::Entry> {
|
||||||
|
|
||||||
const WIDGET_NAME_BUTTON: &str = "button";
|
const WIDGET_NAME_BUTTON: &str = "button";
|
||||||
/// @widget button
|
/// @widget button
|
||||||
/// @desc A button
|
/// @desc A button containing any widget as it's child. Events are triggered on release.
|
||||||
fn build_gtk_button(bargs: &mut BuilderArgs) -> Result<gtk::Button> {
|
fn build_gtk_button(bargs: &mut BuilderArgs) -> Result<gtk::Button> {
|
||||||
let gtk_widget = gtk::Button::new();
|
let gtk_widget = gtk::Button::new();
|
||||||
|
|
||||||
|
@ -492,25 +530,42 @@ fn build_gtk_button(bargs: &mut BuilderArgs) -> Result<gtk::Button> {
|
||||||
prop(
|
prop(
|
||||||
// @prop timeout - timeout of the command. Default: "200ms"
|
// @prop timeout - timeout of the command. Default: "200ms"
|
||||||
timeout: as_duration = Duration::from_millis(200),
|
timeout: as_duration = Duration::from_millis(200),
|
||||||
// @prop onclick - a command that get's run when the button is clicked
|
// @prop onclick - command to run when the button is activated either by leftclicking or keyboard
|
||||||
onclick: as_string = "",
|
onclick: as_string = "",
|
||||||
// @prop onmiddleclick - a command that get's run when the button is middleclicked
|
// @prop onmiddleclick - command to run when the button is middleclicked
|
||||||
onmiddleclick: as_string = "",
|
onmiddleclick: as_string = "",
|
||||||
// @prop onrightclick - a command that get's run when the button is rightclicked
|
// @prop onrightclick - command to run when the button is rightclicked
|
||||||
onrightclick: as_string = ""
|
onrightclick: as_string = ""
|
||||||
) {
|
) {
|
||||||
gtk_widget.add_events(gdk::EventMask::BUTTON_PRESS_MASK);
|
// animate button upon right-/middleclick (if gtk theme supports it)
|
||||||
connect_signal_handler!(gtk_widget, gtk_widget.connect_button_press_event(move |_, evt| {
|
// since we do this, we can't use `connect_clicked` as that would always run `onclick` as well
|
||||||
|
connect_signal_handler!(gtk_widget, gtk_widget.connect_button_press_event(move |button, _| {
|
||||||
|
button.emit_activate();
|
||||||
|
glib::Propagation::Proceed
|
||||||
|
}));
|
||||||
|
let onclick_ = onclick.clone();
|
||||||
|
// mouse click events
|
||||||
|
connect_signal_handler!(gtk_widget, gtk_widget.connect_button_release_event(move |_, evt| {
|
||||||
match evt.button() {
|
match evt.button() {
|
||||||
1 => run_command(timeout, &onclick, &[] as &[&str]),
|
1 => run_command(timeout, &onclick, &[] as &[&str]),
|
||||||
2 => run_command(timeout, &onmiddleclick, &[] as &[&str]),
|
2 => run_command(timeout, &onmiddleclick, &[] as &[&str]),
|
||||||
3 => run_command(timeout, &onrightclick, &[] as &[&str]),
|
3 => run_command(timeout, &onrightclick, &[] as &[&str]),
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
|
}));
|
||||||
|
// keyboard events
|
||||||
|
connect_signal_handler!(gtk_widget, gtk_widget.connect_key_release_event(move |_, evt| {
|
||||||
|
match evt.scancode() {
|
||||||
|
// return
|
||||||
|
36 => run_command(timeout, &onclick_, &[] as &[&str]),
|
||||||
|
// space
|
||||||
|
65 => run_command(timeout, &onclick_, &[] as &[&str]),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
glib::Propagation::Proceed
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
Ok(gtk_widget)
|
Ok(gtk_widget)
|
||||||
}
|
}
|
||||||
|
@ -536,12 +591,35 @@ fn build_gtk_image(bargs: &mut BuilderArgs) -> Result<gtk::Image> {
|
||||||
// @prop path - path to the image file
|
// @prop path - path to the image file
|
||||||
// @prop image-width - width of the image
|
// @prop image-width - width of the image
|
||||||
// @prop image-height - height of the image
|
// @prop image-height - height of the image
|
||||||
prop(path: as_string, image_width: as_i32 = -1, image_height: as_i32 = -1) {
|
// @prop preserve-aspect-ratio - whether to keep the aspect ratio when resizing an image. Default: true, false doesn't work for all image types
|
||||||
|
// @prop fill-svg - sets the color of svg images
|
||||||
|
prop(path: as_string, image_width: as_i32 = -1, image_height: as_i32 = -1, preserve_aspect_ratio: as_bool = true, fill_svg: as_string = "") {
|
||||||
|
if !path.ends_with(".svg") && !fill_svg.is_empty() {
|
||||||
|
log::warn!("Fill attribute ignored, file is not an svg image");
|
||||||
|
}
|
||||||
|
|
||||||
if path.ends_with(".gif") {
|
if path.ends_with(".gif") {
|
||||||
let pixbuf_animation = gtk::gdk_pixbuf::PixbufAnimation::from_file(std::path::PathBuf::from(path))?;
|
let pixbuf_animation = gtk::gdk_pixbuf::PixbufAnimation::from_file(std::path::PathBuf::from(path))?;
|
||||||
gtk_widget.set_from_animation(&pixbuf_animation);
|
gtk_widget.set_from_animation(&pixbuf_animation);
|
||||||
} else {
|
} else {
|
||||||
let pixbuf = gtk::gdk_pixbuf::Pixbuf::from_file_at_size(std::path::PathBuf::from(path), image_width, image_height)?;
|
let pixbuf;
|
||||||
|
// populate the pixel buffer
|
||||||
|
if path.ends_with(".svg") && !fill_svg.is_empty() {
|
||||||
|
let svg_data = std::fs::read_to_string(std::path::PathBuf::from(path.clone()))?;
|
||||||
|
// The fastest way to add/change fill color
|
||||||
|
let svg_data = if svg_data.contains("fill=") {
|
||||||
|
let reg = regex::Regex::new(r#"fill="[^"]*""#)?;
|
||||||
|
reg.replace(&svg_data, &format!("fill=\"{}\"", fill_svg))
|
||||||
|
} else {
|
||||||
|
let reg = regex::Regex::new(r"<svg")?;
|
||||||
|
reg.replace(&svg_data, &format!("<svg fill=\"{}\"", fill_svg))
|
||||||
|
};
|
||||||
|
let stream = gtk::gio::MemoryInputStream::from_bytes(>k::glib::Bytes::from(svg_data.as_bytes()));
|
||||||
|
pixbuf = gtk::gdk_pixbuf::Pixbuf::from_stream_at_scale(&stream, image_width, image_height, preserve_aspect_ratio, None::<>k::gio::Cancellable>)?;
|
||||||
|
stream.close(None::<>k::gio::Cancellable>)?;
|
||||||
|
} else {
|
||||||
|
pixbuf = gtk::gdk_pixbuf::Pixbuf::from_file_at_scale(std::path::PathBuf::from(path), image_width, image_height, preserve_aspect_ratio)?;
|
||||||
|
}
|
||||||
gtk_widget.set_from_pixbuf(Some(&pixbuf));
|
gtk_widget.set_from_pixbuf(Some(&pixbuf));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -727,27 +805,27 @@ fn build_gtk_event_box(bargs: &mut BuilderArgs) -> Result<gtk::EventBox> {
|
||||||
// Support :hover selector
|
// Support :hover selector
|
||||||
gtk_widget.connect_enter_notify_event(|gtk_widget, evt| {
|
gtk_widget.connect_enter_notify_event(|gtk_widget, evt| {
|
||||||
if evt.detail() != NotifyType::Inferior {
|
if evt.detail() != NotifyType::Inferior {
|
||||||
gtk_widget.clone().set_state_flags(gtk::StateFlags::PRELIGHT, false);
|
gtk_widget.set_state_flags(gtk::StateFlags::PRELIGHT, false);
|
||||||
}
|
}
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
});
|
});
|
||||||
|
|
||||||
gtk_widget.connect_leave_notify_event(|gtk_widget, evt| {
|
gtk_widget.connect_leave_notify_event(|gtk_widget, evt| {
|
||||||
if evt.detail() != NotifyType::Inferior {
|
if evt.detail() != NotifyType::Inferior {
|
||||||
gtk_widget.clone().unset_state_flags(gtk::StateFlags::PRELIGHT);
|
gtk_widget.unset_state_flags(gtk::StateFlags::PRELIGHT);
|
||||||
}
|
}
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
});
|
});
|
||||||
|
|
||||||
// Support :active selector
|
// Support :active selector
|
||||||
gtk_widget.connect_button_press_event(|gtk_widget, _| {
|
gtk_widget.connect_button_press_event(|gtk_widget, _| {
|
||||||
gtk_widget.clone().set_state_flags(gtk::StateFlags::ACTIVE, false);
|
gtk_widget.set_state_flags(gtk::StateFlags::ACTIVE, false);
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
});
|
});
|
||||||
|
|
||||||
gtk_widget.connect_button_release_event(|gtk_widget, _| {
|
gtk_widget.connect_button_release_event(|gtk_widget, _| {
|
||||||
gtk_widget.clone().unset_state_flags(gtk::StateFlags::ACTIVE);
|
gtk_widget.unset_state_flags(gtk::StateFlags::ACTIVE);
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
});
|
});
|
||||||
|
|
||||||
def_widget!(bargs, _g, gtk_widget, {
|
def_widget!(bargs, _g, gtk_widget, {
|
||||||
|
@ -761,7 +839,7 @@ fn build_gtk_event_box(bargs: &mut BuilderArgs) -> Result<gtk::EventBox> {
|
||||||
if delta != 0f64 { // Ignore the first event https://bugzilla.gnome.org/show_bug.cgi?id=675959
|
if delta != 0f64 { // Ignore the first event https://bugzilla.gnome.org/show_bug.cgi?id=675959
|
||||||
run_command(timeout, &onscroll, &[if delta < 0f64 { "up" } else { "down" }]);
|
run_command(timeout, &onscroll, &[if delta < 0f64 { "up" } else { "down" }]);
|
||||||
}
|
}
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
// @prop timeout - timeout of the command. Default: "200ms"
|
// @prop timeout - timeout of the command. Default: "200ms"
|
||||||
|
@ -772,7 +850,7 @@ fn build_gtk_event_box(bargs: &mut BuilderArgs) -> Result<gtk::EventBox> {
|
||||||
if evt.detail() != NotifyType::Inferior {
|
if evt.detail() != NotifyType::Inferior {
|
||||||
run_command(timeout, &onhover, &[evt.position().0, evt.position().1]);
|
run_command(timeout, &onhover, &[evt.position().0, evt.position().1]);
|
||||||
}
|
}
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
// @prop timeout - timeout of the command. Default: "200ms"
|
// @prop timeout - timeout of the command. Default: "200ms"
|
||||||
|
@ -783,7 +861,7 @@ fn build_gtk_event_box(bargs: &mut BuilderArgs) -> Result<gtk::EventBox> {
|
||||||
if evt.detail() != NotifyType::Inferior {
|
if evt.detail() != NotifyType::Inferior {
|
||||||
run_command(timeout, &onhoverlost, &[evt.position().0, evt.position().1]);
|
run_command(timeout, &onhoverlost, &[evt.position().0, evt.position().1]);
|
||||||
}
|
}
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
// @prop cursor - Cursor to show while hovering (see [gtk3-cursors](https://docs.gtk.org/gdk3/ctor.Cursor.new_from_name.html) for possible names)
|
// @prop cursor - Cursor to show while hovering (see [gtk3-cursors](https://docs.gtk.org/gdk3/ctor.Cursor.new_from_name.html) for possible names)
|
||||||
|
@ -799,7 +877,7 @@ fn build_gtk_event_box(bargs: &mut BuilderArgs) -> Result<gtk::EventBox> {
|
||||||
gdk_window.set_cursor(gdk::Cursor::from_name(&display, &cursor).as_ref());
|
gdk_window.set_cursor(gdk::Cursor::from_name(&display, &cursor).as_ref());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
}));
|
}));
|
||||||
connect_signal_handler!(gtk_widget, gtk_widget.connect_leave_notify_event(move |widget, _evt| {
|
connect_signal_handler!(gtk_widget, gtk_widget.connect_leave_notify_event(move |widget, _evt| {
|
||||||
if _evt.detail() != NotifyType::Inferior {
|
if _evt.detail() != NotifyType::Inferior {
|
||||||
|
@ -808,7 +886,7 @@ fn build_gtk_event_box(bargs: &mut BuilderArgs) -> Result<gtk::EventBox> {
|
||||||
gdk_window.set_cursor(None);
|
gdk_window.set_cursor(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
// @prop timeout - timeout of the command. Default: "200ms"
|
// @prop timeout - timeout of the command. Default: "200ms"
|
||||||
|
@ -857,32 +935,28 @@ fn build_gtk_event_box(bargs: &mut BuilderArgs) -> Result<gtk::EventBox> {
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO the fact that we have the same code here as for button is ugly, as we want to keep consistency
|
|
||||||
|
|
||||||
prop(
|
prop(
|
||||||
// @prop timeout - timeout of the command. Default: "200ms"
|
// @prop timeout - timeout of the command. Default: "200ms"
|
||||||
timeout: as_duration = Duration::from_millis(200),
|
timeout: as_duration = Duration::from_millis(200),
|
||||||
// @prop onclick - a command that get's run when the button is clicked
|
// @prop onclick - command to run when the widget is clicked
|
||||||
onclick: as_string = "",
|
onclick: as_string = "",
|
||||||
// @prop onmiddleclick - a command that get's run when the button is middleclicked
|
// @prop onmiddleclick - command to run when the widget is middleclicked
|
||||||
onmiddleclick: as_string = "",
|
onmiddleclick: as_string = "",
|
||||||
// @prop onrightclick - a command that get's run when the button is rightclicked
|
// @prop onrightclick - command to run when the widget is rightclicked
|
||||||
onrightclick: as_string = ""
|
onrightclick: as_string = ""
|
||||||
) {
|
) {
|
||||||
gtk_widget.add_events(gdk::EventMask::BUTTON_PRESS_MASK);
|
gtk_widget.add_events(gdk::EventMask::BUTTON_PRESS_MASK);
|
||||||
connect_signal_handler!(gtk_widget, gtk_widget.connect_button_press_event(move |_, evt| {
|
connect_signal_handler!(gtk_widget, gtk_widget.connect_button_release_event(move |_, evt| {
|
||||||
match evt.button() {
|
match evt.button() {
|
||||||
1 => run_command(timeout, &onclick, &[] as &[&str]),
|
1 => run_command(timeout, &onclick, &[] as &[&str]),
|
||||||
2 => run_command(timeout, &onmiddleclick, &[] as &[&str]),
|
2 => run_command(timeout, &onmiddleclick, &[] as &[&str]),
|
||||||
3 => run_command(timeout, &onrightclick, &[] as &[&str]),
|
3 => run_command(timeout, &onrightclick, &[] as &[&str]),
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
gtk::Inhibit(false)
|
glib::Propagation::Proceed
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(gtk_widget)
|
Ok(gtk_widget)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -894,11 +968,12 @@ fn build_gtk_label(bargs: &mut BuilderArgs) -> Result<gtk::Label> {
|
||||||
|
|
||||||
def_widget!(bargs, _g, gtk_widget, {
|
def_widget!(bargs, _g, gtk_widget, {
|
||||||
// @prop text - the text to display
|
// @prop text - the text to display
|
||||||
|
// @prop truncate - whether to truncate text (or pango markup). If `show-truncated` is `false`, or if `limit-width` has a value, this property has no effect and truncation is enabled.
|
||||||
// @prop limit-width - maximum count of characters to display
|
// @prop limit-width - maximum count of characters to display
|
||||||
// @prop truncate-left - whether to truncate on the left side
|
// @prop truncate-left - whether to truncate on the left side
|
||||||
// @prop show-truncated - show whether the text was truncated. Disabling it will also disable dynamic truncation (the labels won't be truncated more than `limit-width`, even if there is not enough space for them), and will completly disable truncation on pango markup.
|
// @prop show-truncated - show whether the text was truncated. Disabling it will also disable dynamic truncation (the labels won't be truncated more than `limit-width`, even if there is not enough space for them), and will completly disable truncation on pango markup.
|
||||||
// @prop unindent - whether to remove leading spaces
|
// @prop unindent - whether to remove leading spaces
|
||||||
prop(text: as_string, limit_width: as_i32 = i32::MAX, truncate_left: as_bool = false, show_truncated: as_bool = true, unindent: as_bool = true) {
|
prop(text: as_string, truncate: as_bool = false, limit_width: as_i32 = i32::MAX, truncate_left: as_bool = false, show_truncated: as_bool = true, unindent: as_bool = true) {
|
||||||
let text = if show_truncated {
|
let text = if show_truncated {
|
||||||
// gtk does weird thing if we set max_width_chars to i32::MAX
|
// gtk does weird thing if we set max_width_chars to i32::MAX
|
||||||
if limit_width == i32::MAX {
|
if limit_width == i32::MAX {
|
||||||
|
@ -906,11 +981,15 @@ fn build_gtk_label(bargs: &mut BuilderArgs) -> Result<gtk::Label> {
|
||||||
} else {
|
} else {
|
||||||
gtk_widget.set_max_width_chars(limit_width);
|
gtk_widget.set_max_width_chars(limit_width);
|
||||||
}
|
}
|
||||||
|
if truncate || limit_width != i32::MAX {
|
||||||
if truncate_left {
|
if truncate_left {
|
||||||
gtk_widget.set_ellipsize(pango::EllipsizeMode::Start);
|
gtk_widget.set_ellipsize(pango::EllipsizeMode::Start);
|
||||||
} else {
|
} else {
|
||||||
gtk_widget.set_ellipsize(pango::EllipsizeMode::End);
|
gtk_widget.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
gtk_widget.set_ellipsize(pango::EllipsizeMode::None);
|
||||||
|
}
|
||||||
|
|
||||||
text
|
text
|
||||||
} else {
|
} else {
|
||||||
|
@ -918,7 +997,7 @@ fn build_gtk_label(bargs: &mut BuilderArgs) -> Result<gtk::Label> {
|
||||||
|
|
||||||
let limit_width = limit_width as usize;
|
let limit_width = limit_width as usize;
|
||||||
let char_count = text.chars().count();
|
let char_count = text.chars().count();
|
||||||
if char_count > limit_width && !show_truncated {
|
if char_count > limit_width {
|
||||||
if truncate_left {
|
if truncate_left {
|
||||||
text.chars().skip(char_count - limit_width).collect()
|
text.chars().skip(char_count - limit_width).collect()
|
||||||
} else {
|
} else {
|
||||||
|
@ -934,11 +1013,12 @@ fn build_gtk_label(bargs: &mut BuilderArgs) -> Result<gtk::Label> {
|
||||||
gtk_widget.set_text(&text);
|
gtk_widget.set_text(&text);
|
||||||
},
|
},
|
||||||
// @prop markup - Pango markup to display
|
// @prop markup - Pango markup to display
|
||||||
|
// @prop truncate - whether to truncate text (or pango markup). If `show-truncated` is `false`, or if `limit-width` has a value, this property has no effect and truncation is enabled.
|
||||||
// @prop limit-width - maximum count of characters to display
|
// @prop limit-width - maximum count of characters to display
|
||||||
// @prop truncate-left - whether to truncate on the left side
|
// @prop truncate-left - whether to truncate on the left side
|
||||||
// @prop show-truncated - show whether the text was truncatedd. Disabling it will also disable dynamic truncation (the labels won't be truncated more than `limit-width`, even if there is not enough space for them), and will completly disable truncation on pango markup.
|
// @prop show-truncated - show whether the text was truncated. Disabling it will also disable dynamic truncation (the labels won't be truncated more than `limit-width`, even if there is not enough space for them), and will completly disable truncation on pango markup.
|
||||||
prop(markup: as_string, limit_width: as_i32 = i32::MAX, truncate_left: as_bool = false, show_truncated: as_bool = true) {
|
prop(markup: as_string, truncate: as_bool = false, limit_width: as_i32 = i32::MAX, truncate_left: as_bool = false, show_truncated: as_bool = true) {
|
||||||
if show_truncated {
|
if (truncate || limit_width != i32::MAX) && show_truncated {
|
||||||
// gtk does weird thing if we set max_width_chars to i32::MAX
|
// gtk does weird thing if we set max_width_chars to i32::MAX
|
||||||
if limit_width == i32::MAX {
|
if limit_width == i32::MAX {
|
||||||
gtk_widget.set_max_width_chars(-1);
|
gtk_widget.set_max_width_chars(-1);
|
||||||
|
@ -973,6 +1053,14 @@ fn build_gtk_label(bargs: &mut BuilderArgs) -> Result<gtk::Label> {
|
||||||
prop(justify: as_string = "left") {
|
prop(justify: as_string = "left") {
|
||||||
gtk_widget.set_justify(parse_justification(&justify)?);
|
gtk_widget.set_justify(parse_justification(&justify)?);
|
||||||
},
|
},
|
||||||
|
// @prop wrap-mode - how text is wrapped. possible options: $wrap_mode
|
||||||
|
prop(wrap_mode: as_string = "word") {
|
||||||
|
gtk_widget.set_wrap_mode(parse_wrap_mode(&wrap_mode)?);
|
||||||
|
},
|
||||||
|
// @prop lines - maximum number of lines to display (only works when `limit-width` has a value). A value of -1 (default) disables the limit.
|
||||||
|
prop(lines: as_i32 = -1) {
|
||||||
|
gtk_widget.set_lines(lines);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
Ok(gtk_widget)
|
Ok(gtk_widget)
|
||||||
}
|
}
|
||||||
|
@ -1082,20 +1170,11 @@ const WIDGET_NAME_STACK: &str = "stack";
|
||||||
/// @desc A widget that displays one of its children at a time
|
/// @desc A widget that displays one of its children at a time
|
||||||
fn build_gtk_stack(bargs: &mut BuilderArgs) -> Result<gtk::Stack> {
|
fn build_gtk_stack(bargs: &mut BuilderArgs) -> Result<gtk::Stack> {
|
||||||
let gtk_widget = gtk::Stack::new();
|
let gtk_widget = gtk::Stack::new();
|
||||||
def_widget!(bargs, _g, gtk_widget, {
|
|
||||||
// @prop selected - index of child which should be shown
|
|
||||||
prop(selected: as_i32) { gtk_widget.set_visible_child_name(&selected.to_string()); },
|
|
||||||
// @prop transition - the name of the transition. Possible values: $transition
|
|
||||||
prop(transition: as_string = "crossfade") { gtk_widget.set_transition_type(parse_stack_transition(&transition)?); },
|
|
||||||
// @prop same-size - sets whether all children should be the same size
|
|
||||||
prop(same_size: as_bool = false) { gtk_widget.set_homogeneous(same_size); }
|
|
||||||
});
|
|
||||||
|
|
||||||
match bargs.widget_use.children.len().cmp(&1) {
|
if bargs.widget_use.children.is_empty() {
|
||||||
Ordering::Less => {
|
return Err(DiagError(gen_diagnostic!("stack must contain at least one element", bargs.widget_use.span)).into());
|
||||||
Err(DiagError(gen_diagnostic!("stack must contain at least one element", bargs.widget_use.span)).into())
|
|
||||||
}
|
}
|
||||||
Ordering::Greater | Ordering::Equal => {
|
|
||||||
let children = bargs.widget_use.children.iter().map(|child| {
|
let children = bargs.widget_use.children.iter().map(|child| {
|
||||||
build_gtk_widget(
|
build_gtk_widget(
|
||||||
bargs.scope_graph,
|
bargs.scope_graph,
|
||||||
|
@ -1105,25 +1184,37 @@ fn build_gtk_stack(bargs: &mut BuilderArgs) -> Result<gtk::Stack> {
|
||||||
bargs.custom_widget_invocation.clone(),
|
bargs.custom_widget_invocation.clone(),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
for (i, child) in children.enumerate() {
|
for (i, child) in children.enumerate() {
|
||||||
let child = child?;
|
let child = child?;
|
||||||
gtk_widget.add_named(&child, &i.to_string());
|
gtk_widget.add_named(&child, &i.to_string());
|
||||||
child.show();
|
child.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def_widget!(bargs, _g, gtk_widget, {
|
||||||
|
// @prop selected - index of child which should be shown
|
||||||
|
prop(selected: as_i32) { gtk_widget.set_visible_child_name(&selected.to_string()); },
|
||||||
|
// @prop transition - the name of the transition. Possible values: $transition
|
||||||
|
prop(transition: as_string = "crossfade") { gtk_widget.set_transition_type(parse_stack_transition(&transition)?); },
|
||||||
|
// @prop same-size - sets whether all children should be the same size
|
||||||
|
prop(same_size: as_bool = false) { gtk_widget.set_homogeneous(same_size); }
|
||||||
|
});
|
||||||
|
|
||||||
Ok(gtk_widget)
|
Ok(gtk_widget)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const WIDGET_NAME_TRANSFORM: &str = "transform";
|
const WIDGET_NAME_TRANSFORM: &str = "transform";
|
||||||
/// @widget transform
|
/// @widget transform
|
||||||
/// @desc A widget that applies transformations to its content. They are applied in the following
|
/// @desc A widget that applies transformations to its content. They are applied in the following order: rotate -> translate -> scale
|
||||||
/// order: rotate->translate->scale)
|
|
||||||
fn build_transform(bargs: &mut BuilderArgs) -> Result<Transform> {
|
fn build_transform(bargs: &mut BuilderArgs) -> Result<Transform> {
|
||||||
let w = Transform::new();
|
let w = Transform::new();
|
||||||
def_widget!(bargs, _g, w, {
|
def_widget!(bargs, _g, w, {
|
||||||
// @prop rotate - the percentage to rotate
|
// @prop rotate - the percentage to rotate
|
||||||
prop(rotate: as_f64) { w.set_property("rotate", rotate); },
|
prop(rotate: as_f64) { w.set_property("rotate", rotate); },
|
||||||
|
// @prop transform-origin-x - x coordinate of origin of transformation (px or %)
|
||||||
|
prop(transform_origin_x: as_string) { w.set_property("transform-origin-x", transform_origin_x) },
|
||||||
|
// @prop transform-origin-y - y coordinate of origin of transformation (px or %)
|
||||||
|
prop(transform_origin_y: as_string) { w.set_property("transform-origin-y", transform_origin_y) },
|
||||||
// @prop translate-x - the amount to translate in the x direction (px or %)
|
// @prop translate-x - the amount to translate in the x direction (px or %)
|
||||||
prop(translate_x: as_string) { w.set_property("translate-x", translate_x); },
|
prop(translate_x: as_string) { w.set_property("translate-x", translate_x); },
|
||||||
// @prop translate-y - the amount to translate in the y direction (px or %)
|
// @prop translate-y - the amount to translate in the y direction (px or %)
|
||||||
|
@ -1161,7 +1252,14 @@ fn build_graph(bargs: &mut BuilderArgs) -> Result<super::graph::Graph> {
|
||||||
let w = super::graph::Graph::new();
|
let w = super::graph::Graph::new();
|
||||||
def_widget!(bargs, _g, w, {
|
def_widget!(bargs, _g, w, {
|
||||||
// @prop value - the value, between 0 - 100
|
// @prop value - the value, between 0 - 100
|
||||||
prop(value: as_f64) { w.set_property("value", value); },
|
prop(value: as_f64) {
|
||||||
|
if value.is_nan() || value.is_infinite() {
|
||||||
|
return Err(DiagError(gen_diagnostic!(
|
||||||
|
format!("Graph's value should never be NaN or infinite")
|
||||||
|
)).into());
|
||||||
|
}
|
||||||
|
w.set_property("value", value);
|
||||||
|
},
|
||||||
// @prop thickness - the thickness of the line
|
// @prop thickness - the thickness of the line
|
||||||
prop(thickness: as_f64) { w.set_property("thickness", thickness); },
|
prop(thickness: as_f64) { w.set_property("thickness", thickness); },
|
||||||
// @prop time-range - the range of time to show
|
// @prop time-range - the range of time to show
|
||||||
|
@ -1182,6 +1280,12 @@ fn build_graph(bargs: &mut BuilderArgs) -> Result<super::graph::Graph> {
|
||||||
// @prop line-style - changes the look of the edges in the graph. Values: "miter" (default), "round",
|
// @prop line-style - changes the look of the edges in the graph. Values: "miter" (default), "round",
|
||||||
// "bevel"
|
// "bevel"
|
||||||
prop(line_style: as_string) { w.set_property("line-style", line_style); },
|
prop(line_style: as_string) { w.set_property("line-style", line_style); },
|
||||||
|
// @prop flip-x - whether the x axis should go from high to low
|
||||||
|
prop(flip_x: as_bool) { w.set_property("flip-x", flip_x); },
|
||||||
|
// @prop flip-y - whether the y axis should go from high to low
|
||||||
|
prop(flip_y: as_bool) { w.set_property("flip-y", flip_y); },
|
||||||
|
// @prop vertical - if set to true, the x and y axes will be exchanged
|
||||||
|
prop(vertical: as_bool) { w.set_property("vertical", vertical); },
|
||||||
});
|
});
|
||||||
Ok(w)
|
Ok(w)
|
||||||
}
|
}
|
||||||
|
@ -1287,6 +1391,16 @@ fn parse_justification(j: &str) -> Result<gtk::Justification> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @var position - "left", "right", "top", "bottom"
|
||||||
|
fn parse_position_type(g: &str) -> Result<gtk::PositionType> {
|
||||||
|
enum_parse! { "position", g,
|
||||||
|
"left" => gtk::PositionType::Left,
|
||||||
|
"right" => gtk::PositionType::Right,
|
||||||
|
"top" => gtk::PositionType::Top,
|
||||||
|
"bottom" => gtk::PositionType::Bottom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// @var gravity - "south", "east", "west", "north", "auto"
|
/// @var gravity - "south", "east", "west", "north", "auto"
|
||||||
fn parse_gravity(g: &str) -> Result<gtk::pango::Gravity> {
|
fn parse_gravity(g: &str) -> Result<gtk::pango::Gravity> {
|
||||||
enum_parse! { "gravity", g,
|
enum_parse! { "gravity", g,
|
||||||
|
@ -1298,6 +1412,15 @@ fn parse_gravity(g: &str) -> Result<gtk::pango::Gravity> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @var wrap_mode - "word", "char", "wordchar"
|
||||||
|
fn parse_wrap_mode(w: &str) -> Result<gtk::pango::WrapMode> {
|
||||||
|
enum_parse! { "wrap-mode", w,
|
||||||
|
"word" => gtk::pango::WrapMode::Word,
|
||||||
|
"char" => gtk::pango::WrapMode::Char,
|
||||||
|
"wordchar" => gtk::pango::WrapMode::WordChar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Connect a function to the first map event of a widget. After that first map, the handler will get disconnected.
|
/// Connect a function to the first map event of a widget. After that first map, the handler will get disconnected.
|
||||||
fn connect_first_map<W: IsA<gtk::Widget>, F: Fn(&W) + 'static>(widget: &W, func: F) {
|
fn connect_first_map<W: IsA<gtk::Widget>, F: Fn(&W) + 'static>(widget: &W, func: F) {
|
||||||
let signal_handler_id = std::rc::Rc::new(std::cell::RefCell::new(None));
|
let signal_handler_id = std::rc::Rc::new(std::cell::RefCell::new(None));
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use glib::{object_subclass, wrapper};
|
use gtk::glib::{self, object_subclass, wrapper, Properties};
|
||||||
use glib_macros::Properties;
|
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
|
|
@ -59,10 +59,10 @@ impl WindowArguments {
|
||||||
|
|
||||||
// Ensure that the arguments passed to the window that are already interpreted by eww (id, screen)
|
// Ensure that the arguments passed to the window that are already interpreted by eww (id, screen)
|
||||||
// are set to the correct values
|
// are set to the correct values
|
||||||
if expected_args.contains(&"id".to_string()) {
|
if expected_args.contains(&String::from("id")) {
|
||||||
local_variables.insert(VarName::from("id"), DynVal::from(self.instance_id.clone()));
|
local_variables.insert(VarName::from("id"), DynVal::from(self.instance_id.clone()));
|
||||||
}
|
}
|
||||||
if self.monitor.is_some() && expected_args.contains(&"screen".to_string()) {
|
if self.monitor.is_some() && expected_args.contains(&String::from("screen")) {
|
||||||
let mon_dyn = DynVal::from(&self.monitor.clone().unwrap());
|
let mon_dyn = DynVal::from(&self.monitor.clone().unwrap());
|
||||||
local_variables.insert(VarName::from("screen"), mon_dyn);
|
local_variables.insert(VarName::from("screen"), mon_dyn);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ use crate::window_arguments::WindowArguments;
|
||||||
pub struct WindowInitiator {
|
pub struct WindowInitiator {
|
||||||
pub backend_options: BackendWindowOptions,
|
pub backend_options: BackendWindowOptions,
|
||||||
pub geometry: Option<WindowGeometry>,
|
pub geometry: Option<WindowGeometry>,
|
||||||
pub id: String,
|
|
||||||
pub local_variables: HashMap<VarName, DynVal>,
|
pub local_variables: HashMap<VarName, DynVal>,
|
||||||
pub monitor: Option<MonitorIdentifier>,
|
pub monitor: Option<MonitorIdentifier>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -37,7 +36,6 @@ impl WindowInitiator {
|
||||||
Ok(WindowInitiator {
|
Ok(WindowInitiator {
|
||||||
backend_options: window_def.backend_options.eval(&vars)?,
|
backend_options: window_def.backend_options.eval(&vars)?,
|
||||||
geometry,
|
geometry,
|
||||||
id: args.instance_id.clone(),
|
|
||||||
monitor,
|
monitor,
|
||||||
name: window_def.name.clone(),
|
name: window_def.name.clone(),
|
||||||
resizable: window_def.eval_resizable(&vars)?,
|
resizable: window_def.eval_resizable(&vars)?,
|
||||||
|
|
|
@ -12,3 +12,4 @@ homepage = "https://github.com/elkowar/eww"
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
derive_more.workspace = true
|
derive_more.workspace = true
|
||||||
ref-cast.workspace = true
|
ref-cast.workspace = true
|
||||||
|
chrono = { workspace = true, features = ["unstable-locales"] }
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
pub mod locale;
|
||||||
pub mod span;
|
pub mod span;
|
||||||
pub mod wrappers;
|
pub mod wrappers;
|
||||||
|
|
||||||
|
pub use locale::*;
|
||||||
pub use span::*;
|
pub use span::*;
|
||||||
pub use wrappers::*;
|
pub use wrappers::*;
|
||||||
|
|
||||||
|
|
12
crates/eww_shared_util/src/locale.rs
Normal file
12
crates/eww_shared_util/src/locale.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use chrono::Locale;
|
||||||
|
use std::env::var;
|
||||||
|
|
||||||
|
/// Returns the `Locale` enum based on the `LC_ALL`, `LC_TIME`, and `LANG` environment variables in
|
||||||
|
/// that order, which is the precedence order prescribed by Section 8.2 of POSIX.1-2017.
|
||||||
|
/// If the environment variable is not defined or is malformed use the POSIX locale.
|
||||||
|
pub fn get_locale() -> Locale {
|
||||||
|
var("LC_ALL")
|
||||||
|
.or_else(|_| var("LC_TIME"))
|
||||||
|
.or_else(|_| var("LANG"))
|
||||||
|
.map_or(Locale::POSIX, |v| v.split('.').next().and_then(|x| x.try_into().ok()).unwrap_or_default())
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
use derive_more::*;
|
use derive_more::{Debug, *};
|
||||||
use ref_cast::RefCast;
|
use ref_cast::RefCast;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// The name of a variable
|
/// The name of a variable
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom, RefCast)]
|
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, Debug, RefCast)]
|
||||||
#[debug(fmt = "VarName({})", .0)]
|
#[debug("VarName({})", _0)]
|
||||||
pub struct VarName(pub String);
|
pub struct VarName(pub String);
|
||||||
|
|
||||||
impl std::borrow::Borrow<str> for VarName {
|
impl std::borrow::Borrow<str> for VarName {
|
||||||
|
@ -34,8 +34,8 @@ impl From<AttrName> for VarName {
|
||||||
|
|
||||||
/// The name of an attribute
|
/// The name of an attribute
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom, RefCast)]
|
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, Debug, RefCast)]
|
||||||
#[debug(fmt="AttrName({})", .0)]
|
#[debug("AttrName({})", _0)]
|
||||||
pub struct AttrName(pub String);
|
pub struct AttrName(pub String);
|
||||||
|
|
||||||
impl AttrName {
|
impl AttrName {
|
||||||
|
|
|
@ -9,11 +9,12 @@ repository = "https://github.com/elkowar/eww"
|
||||||
homepage = "https://github.com/elkowar/eww"
|
homepage = "https://github.com/elkowar/eww"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gtk = "0.17.1"
|
|
||||||
gdk = "0.17.1"
|
|
||||||
zbus = { version = "3.7.0", default-features = false, features = ["tokio"] }
|
|
||||||
dbusmenu-gtk3 = "0.1.0"
|
dbusmenu-gtk3 = "0.1.0"
|
||||||
|
quick-xml = { version = "0.37.1", features = ["serialize"] }
|
||||||
|
serde = "1.0.215"
|
||||||
|
|
||||||
|
gtk.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
|
zbus = { workspace = true, default-features = false, features = ["tokio"] }
|
||||||
|
|
|
@ -105,7 +105,7 @@ fn icon_from_name(
|
||||||
) -> std::result::Result<gtk::gdk_pixbuf::Pixbuf, IconError> {
|
) -> std::result::Result<gtk::gdk_pixbuf::Pixbuf, IconError> {
|
||||||
let theme = if let Some(path) = theme_path {
|
let theme = if let Some(path) = theme_path {
|
||||||
let theme = gtk::IconTheme::new();
|
let theme = gtk::IconTheme::new();
|
||||||
theme.prepend_search_path(&path);
|
theme.prepend_search_path(path);
|
||||||
theme
|
theme
|
||||||
} else {
|
} else {
|
||||||
gtk::IconTheme::default().expect("Could not get default gtk theme")
|
gtk::IconTheme::default().expect("Could not get default gtk theme")
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use gtk::{self, prelude::*};
|
use gtk::{self, prelude::*};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use zbus::fdo::IntrospectableProxy;
|
||||||
|
|
||||||
/// Recognised values of [`org.freedesktop.StatusNotifierItem.Status`].
|
/// Recognised values of [`org.freedesktop.StatusNotifierItem.Status`].
|
||||||
///
|
///
|
||||||
|
@ -61,7 +63,12 @@ impl Item {
|
||||||
if let Some((addr, path)) = service.split_once('/') {
|
if let Some((addr, path)) = service.split_once('/') {
|
||||||
(addr.to_owned(), format!("/{}", path))
|
(addr.to_owned(), format!("/{}", path))
|
||||||
} else if service.starts_with(':') {
|
} else if service.starts_with(':') {
|
||||||
(service[0..6].to_owned(), names::ITEM_OBJECT.to_owned())
|
(
|
||||||
|
service.to_owned(),
|
||||||
|
resolve_pathless_address(con, service, "/".to_owned())
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| zbus::Error::Failure(format!("no StatusNotifierItem found for {service}")))?,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return Err(zbus::Error::Address(service.to_owned()));
|
return Err(zbus::Error::Address(service.to_owned()));
|
||||||
}
|
}
|
||||||
|
@ -88,9 +95,9 @@ impl Item {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn popup_menu(&self, event: &gdk::EventButton, x: i32, y: i32) -> zbus::Result<()> {
|
pub async fn popup_menu(&self, event: >k::gdk::EventButton, x: i32, y: i32) -> zbus::Result<()> {
|
||||||
if let Some(menu) = &self.gtk_menu {
|
if let Some(menu) = &self.gtk_menu {
|
||||||
menu.popup_at_pointer(event.downcast_ref::<gdk::Event>());
|
menu.popup_at_pointer(event.downcast_ref::<gtk::gdk::Event>());
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
self.sni.context_menu(x, y).await
|
self.sni.context_menu(x, y).await
|
||||||
|
@ -105,3 +112,59 @@ impl Item {
|
||||||
load_icon_from_sni(&self.sni, size, scale).await
|
load_icon_from_sni(&self.sni, size, scale).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct DBusNode {
|
||||||
|
#[serde(default)]
|
||||||
|
interface: Vec<DBusInterface>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
node: Vec<DBusNode>,
|
||||||
|
|
||||||
|
#[serde(rename = "@name")]
|
||||||
|
name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct DBusInterface {
|
||||||
|
#[serde(rename = "@name")]
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn resolve_pathless_address(con: &zbus::Connection, service: &str, path: String) -> zbus::Result<Option<String>> {
|
||||||
|
let introspection_xml =
|
||||||
|
IntrospectableProxy::builder(con).destination(service)?.path(path.as_str())?.build().await?.introspect().await?;
|
||||||
|
|
||||||
|
let dbus_node =
|
||||||
|
quick_xml::de::from_str::<DBusNode>(&introspection_xml).map_err(|err| zbus::Error::Failure(err.to_string()))?;
|
||||||
|
|
||||||
|
if dbus_node.interface.iter().any(|interface| interface.name == "org.kde.StatusNotifierItem") {
|
||||||
|
// This item implements the desired interface, so bubble it back up
|
||||||
|
Ok(Some(path))
|
||||||
|
} else {
|
||||||
|
for node in dbus_node.node {
|
||||||
|
if let Some(name) = node.name {
|
||||||
|
if name == "StatusNotifierItem" {
|
||||||
|
// If this exists, then there's a good chance DBus may not think anything
|
||||||
|
// implements the desired interface, so just bubble this up instead.
|
||||||
|
return Ok(Some(join_to_path(&path, name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = Box::pin(resolve_pathless_address(con, service, join_to_path(&path, name))).await?;
|
||||||
|
|
||||||
|
if path.is_some() {
|
||||||
|
// Return the first item found from a child
|
||||||
|
return Ok(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No children had the item we want...
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn join_to_path(path: &str, name: String) -> String {
|
||||||
|
// Make sure we don't double-up on the leading slash
|
||||||
|
format!("{path}/{name}", path = if path == "/" { "" } else { path })
|
||||||
|
}
|
||||||
|
|
|
@ -2,9 +2,15 @@
|
||||||
//!
|
//!
|
||||||
//! The interface XML files were taken from
|
//! The interface XML files were taken from
|
||||||
//! [Waybar](https://github.com/Alexays/Waybar/tree/master/protocol), and the proxies were
|
//! [Waybar](https://github.com/Alexays/Waybar/tree/master/protocol), and the proxies were
|
||||||
//! generated with [zbus-xmlgen](https://docs.rs/crate/zbus_xmlgen/latest) by running `zbus-xmlgen
|
//! generated with [zbus-xmlgen](https://docs.rs/crate/zbus_xmlgen/latest) by running
|
||||||
//! dbus_status_notifier_item.xml` and `zbus-xmlgen dbus_status_notifier_watcher.xml`. At the
|
//! `zbus-xmlgen file crates/notifier_host/src/proxy/dbus_status_notifier_item.xml` and
|
||||||
//! moment, `dbus_menu.xml` isn't used.
|
//! `zbus-xmlgen file crates/notifier_host/src/proxy/dbus_status_notifier_watcher.xml`.
|
||||||
|
//!
|
||||||
|
//! Note that the `dbus_status_notifier_watcher.rs` file has been slightly adjusted, the
|
||||||
|
//! default arguments to the [proxy](https://docs.rs/zbus/4.4.0/zbus/attr.proxy.html)
|
||||||
|
//! macro need some adjusting.
|
||||||
|
//!
|
||||||
|
//! At the moment, `dbus_menu.xml` isn't used.
|
||||||
//!
|
//!
|
||||||
//! For more information, see ["Writing a client proxy" in the zbus
|
//! For more information, see ["Writing a client proxy" in the zbus
|
||||||
//! tutorial](https://dbus2.github.io/zbus/).
|
//! tutorial](https://dbus2.github.io/zbus/).
|
||||||
|
|
|
@ -14,13 +14,14 @@ build = "build.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eww_shared_util.workspace = true
|
eww_shared_util.workspace = true
|
||||||
|
|
||||||
|
bytesize.workspace = true
|
||||||
cached.workspace = true
|
cached.workspace = true
|
||||||
chrono-tz.workspace = true
|
chrono-tz.workspace = true
|
||||||
chrono.workspace = true
|
chrono = { workspace = true, features = ["unstable-locales"] }
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
jaq-core.workspace = true
|
jaq-core.workspace = true
|
||||||
jaq-parse.workspace = true
|
jaq-parse.workspace = true
|
||||||
jaq-std = {workspace = true, features = ["bincode"]}
|
jaq-std.workspace = true
|
||||||
jaq-interpret.workspace = true
|
jaq-interpret.workspace = true
|
||||||
jaq-syn.workspace = true
|
jaq-syn.workspace = true
|
||||||
lalrpop-util.workspace = true
|
lalrpop-util.workspace = true
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use bytesize::ByteSize;
|
||||||
use cached::proc_macro::cached;
|
use cached::proc_macro::cached;
|
||||||
use chrono::{Local, LocalResult, TimeZone};
|
use chrono::{Local, LocalResult, TimeZone};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -7,7 +8,7 @@ use crate::{
|
||||||
ast::{AccessType, BinOp, SimplExpr, UnaryOp},
|
ast::{AccessType, BinOp, SimplExpr, UnaryOp},
|
||||||
dynval::{ConversionError, DynVal},
|
dynval::{ConversionError, DynVal},
|
||||||
};
|
};
|
||||||
use eww_shared_util::{Span, Spanned, VarName};
|
use eww_shared_util::{get_locale, Span, Spanned, VarName};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
convert::{Infallible, TryFrom, TryInto},
|
convert::{Infallible, TryFrom, TryInto},
|
||||||
|
@ -61,6 +62,9 @@ pub enum EvalError {
|
||||||
#[error("Error parsing date: {0}")]
|
#[error("Error parsing date: {0}")]
|
||||||
ChronoError(String),
|
ChronoError(String),
|
||||||
|
|
||||||
|
#[error("Error parsing byte format mode: {0}")]
|
||||||
|
ByteFormatModeError(String),
|
||||||
|
|
||||||
#[error("{1}")]
|
#[error("{1}")]
|
||||||
Spanned(Span, Box<EvalError>),
|
Spanned(Span, Box<EvalError>),
|
||||||
}
|
}
|
||||||
|
@ -268,6 +272,10 @@ impl SimplExpr {
|
||||||
|
|
||||||
let is_safe = *safe == AccessType::Safe;
|
let is_safe = *safe == AccessType::Safe;
|
||||||
|
|
||||||
|
// Needs to be done first as `as_json_value` fails on empty string
|
||||||
|
if is_safe && val.as_string()?.is_empty() {
|
||||||
|
return Ok(DynVal::from(&serde_json::Value::Null).at(*span));
|
||||||
|
}
|
||||||
match val.as_json_value()? {
|
match val.as_json_value()? {
|
||||||
serde_json::Value::Array(val) => {
|
serde_json::Value::Array(val) => {
|
||||||
let index = index.as_i32()?;
|
let index = index.as_i32()?;
|
||||||
|
@ -281,9 +289,6 @@ impl SimplExpr {
|
||||||
.unwrap_or(&serde_json::Value::Null);
|
.unwrap_or(&serde_json::Value::Null);
|
||||||
Ok(DynVal::from(indexed_value).at(*span))
|
Ok(DynVal::from(indexed_value).at(*span))
|
||||||
}
|
}
|
||||||
serde_json::Value::String(val) if val.is_empty() && is_safe => {
|
|
||||||
Ok(DynVal::from(&serde_json::Value::Null).at(*span))
|
|
||||||
}
|
|
||||||
serde_json::Value::Null if is_safe => Ok(DynVal::from(&serde_json::Value::Null).at(*span)),
|
serde_json::Value::Null if is_safe => Ok(DynVal::from(&serde_json::Value::Null).at(*span)),
|
||||||
_ => Err(EvalError::CannotIndex(format!("{}", val)).at(*span)),
|
_ => Err(EvalError::CannotIndex(format!("{}", val)).at(*span)),
|
||||||
}
|
}
|
||||||
|
@ -328,6 +333,52 @@ fn call_expr_function(name: &str, args: Vec<DynVal>) -> Result<DynVal, EvalError
|
||||||
}
|
}
|
||||||
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
||||||
},
|
},
|
||||||
|
"floor" => match args.as_slice() {
|
||||||
|
[num] => {
|
||||||
|
let num = num.as_f64()?;
|
||||||
|
Ok(DynVal::from(num.floor()))
|
||||||
|
}
|
||||||
|
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
||||||
|
},
|
||||||
|
"ceil" => match args.as_slice() {
|
||||||
|
[num] => {
|
||||||
|
let num = num.as_f64()?;
|
||||||
|
Ok(DynVal::from(num.ceil()))
|
||||||
|
}
|
||||||
|
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
||||||
|
},
|
||||||
|
"min" => match args.as_slice() {
|
||||||
|
[a, b] => {
|
||||||
|
let a = a.as_f64()?;
|
||||||
|
let b = b.as_f64()?;
|
||||||
|
Ok(DynVal::from(f64::min(a, b)))
|
||||||
|
}
|
||||||
|
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
||||||
|
},
|
||||||
|
"max" => match args.as_slice() {
|
||||||
|
[a, b] => {
|
||||||
|
let a = a.as_f64()?;
|
||||||
|
let b = b.as_f64()?;
|
||||||
|
Ok(DynVal::from(f64::max(a, b)))
|
||||||
|
}
|
||||||
|
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
||||||
|
},
|
||||||
|
"powi" => match args.as_slice() {
|
||||||
|
[num, n] => {
|
||||||
|
let num = num.as_f64()?;
|
||||||
|
let n = n.as_i32()?;
|
||||||
|
Ok(DynVal::from(f64::powi(num, n)))
|
||||||
|
}
|
||||||
|
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
||||||
|
},
|
||||||
|
"powf" => match args.as_slice() {
|
||||||
|
[num, n] => {
|
||||||
|
let num = num.as_f64()?;
|
||||||
|
let n = n.as_f64()?;
|
||||||
|
Ok(DynVal::from(f64::powf(num, n)))
|
||||||
|
}
|
||||||
|
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
||||||
|
},
|
||||||
"sin" => match args.as_slice() {
|
"sin" => match args.as_slice() {
|
||||||
[num] => {
|
[num] => {
|
||||||
let num = num.as_f64()?;
|
let num = num.as_f64()?;
|
||||||
|
@ -439,7 +490,9 @@ fn call_expr_function(name: &str, args: Vec<DynVal>) -> Result<DynVal, EvalError
|
||||||
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
||||||
},
|
},
|
||||||
"jq" => match args.as_slice() {
|
"jq" => match args.as_slice() {
|
||||||
[json, code] => run_jaq_function(json.as_json_value()?, code.as_string()?)
|
[json, code] => run_jaq_function(json.as_json_value()?, code.as_string()?, "")
|
||||||
|
.map_err(|e| EvalError::Spanned(code.span(), Box::new(e))),
|
||||||
|
[json, code, args] => run_jaq_function(json.as_json_value()?, code.as_string()?, &args.as_string()?)
|
||||||
.map_err(|e| EvalError::Spanned(code.span(), Box::new(e))),
|
.map_err(|e| EvalError::Spanned(code.span(), Box::new(e))),
|
||||||
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
||||||
},
|
},
|
||||||
|
@ -451,16 +504,68 @@ fn call_expr_function(name: &str, args: Vec<DynVal>) -> Result<DynVal, EvalError
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(DynVal::from(match timezone.timestamp_opt(timestamp.as_i64()?, 0) {
|
Ok(DynVal::from(match timezone.timestamp_opt(timestamp.as_i64()?, 0) {
|
||||||
LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => t.format(&format.as_string()?).to_string(),
|
LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => {
|
||||||
|
let format = format.as_string()?;
|
||||||
|
let delayed_format = t.format_localized(&format, get_locale());
|
||||||
|
let mut buffer = String::new();
|
||||||
|
if delayed_format.write_to(&mut buffer).is_err() {
|
||||||
|
return Err(EvalError::ChronoError("Invalid time formatting string: ".to_string() + &format));
|
||||||
|
}
|
||||||
|
buffer
|
||||||
|
}
|
||||||
LocalResult::None => return Err(EvalError::ChronoError("Invalid UNIX timestamp".to_string())),
|
LocalResult::None => return Err(EvalError::ChronoError("Invalid UNIX timestamp".to_string())),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
[timestamp, format] => Ok(DynVal::from(match Local.timestamp_opt(timestamp.as_i64()?, 0) {
|
[timestamp, format] => Ok(DynVal::from(match Local.timestamp_opt(timestamp.as_i64()?, 0) {
|
||||||
LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => t.format(&format.as_string()?).to_string(),
|
LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => {
|
||||||
|
let format = format.as_string()?;
|
||||||
|
let delayed_format = t.format_localized(&format, get_locale());
|
||||||
|
let mut buffer = String::new();
|
||||||
|
if delayed_format.write_to(&mut buffer).is_err() {
|
||||||
|
return Err(EvalError::ChronoError("Invalid time formatting string: ".to_string() + &format));
|
||||||
|
}
|
||||||
|
buffer
|
||||||
|
}
|
||||||
LocalResult::None => return Err(EvalError::ChronoError("Invalid UNIX timestamp".to_string())),
|
LocalResult::None => return Err(EvalError::ChronoError("Invalid UNIX timestamp".to_string())),
|
||||||
})),
|
})),
|
||||||
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
||||||
},
|
},
|
||||||
|
"log" => match args.as_slice() {
|
||||||
|
[num, n] => {
|
||||||
|
let num = num.as_f64()?;
|
||||||
|
let n = n.as_f64()?;
|
||||||
|
Ok(DynVal::from(f64::log(num, n)))
|
||||||
|
}
|
||||||
|
_ => Err(EvalError::WrongArgCount(name.to_string())),
|
||||||
|
},
|
||||||
|
"formatbytes" => {
|
||||||
|
let (bytes, short, mode) = match args.as_slice() {
|
||||||
|
[bytes] => (bytes.as_i64()?, false, "iec".to_owned()),
|
||||||
|
[bytes, short] => (bytes.as_i64()?, short.as_bool()?, "iec".to_owned()),
|
||||||
|
[bytes, short, mode] => (bytes.as_i64()?, short.as_bool()?, mode.as_string()?),
|
||||||
|
_ => return Err(EvalError::WrongArgCount(name.to_string())),
|
||||||
|
};
|
||||||
|
let neg = bytes < 0;
|
||||||
|
let disp = ByteSize(bytes.abs() as u64).display();
|
||||||
|
let disp = match mode.as_str() {
|
||||||
|
"iec" => {
|
||||||
|
if short {
|
||||||
|
disp.iec_short()
|
||||||
|
} else {
|
||||||
|
disp.iec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"si" => {
|
||||||
|
if short {
|
||||||
|
disp.si_short()
|
||||||
|
} else {
|
||||||
|
disp.si()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(EvalError::ByteFormatModeError(mode)),
|
||||||
|
};
|
||||||
|
Ok(DynVal::from(if neg { format!("-{disp}") } else { disp.to_string() }))
|
||||||
|
}
|
||||||
|
|
||||||
_ => Err(EvalError::UnknownFunction(name.to_string())),
|
_ => Err(EvalError::UnknownFunction(name.to_string())),
|
||||||
}
|
}
|
||||||
|
@ -485,16 +590,20 @@ fn prepare_jaq_filter(code: String) -> Result<Arc<jaq_interpret::Filter>, EvalEr
|
||||||
Ok(Arc::new(filter))
|
Ok(Arc::new(filter))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_jaq_function(json: serde_json::Value, code: String) -> Result<DynVal, EvalError> {
|
fn run_jaq_function(json: serde_json::Value, code: String, args: &str) -> Result<DynVal, EvalError> {
|
||||||
let filter: Arc<jaq_interpret::Filter> = prepare_jaq_filter(code)?;
|
use jaq_interpret::{Ctx, RcIter, Val};
|
||||||
let inputs = jaq_interpret::RcIter::new(std::iter::empty());
|
prepare_jaq_filter(code)?
|
||||||
let out = filter
|
.run((Ctx::new([], &RcIter::new(std::iter::empty())), Val::from(json)))
|
||||||
.run((jaq_interpret::Ctx::new([], &inputs), jaq_interpret::Val::from(json)))
|
.map(|r| r.map(Into::<serde_json::Value>::into))
|
||||||
.map(|x| x.map(Into::<serde_json::Value>::into))
|
.map(|x| {
|
||||||
.map(|x| x.map(|x| DynVal::from_string(serde_json::to_string(&x).unwrap())))
|
x.map(|val| match (args, val) {
|
||||||
|
("r", serde_json::Value::String(s)) => DynVal::from_string(s),
|
||||||
|
// invalid arguments are silently ignored
|
||||||
|
(_, v) => DynVal::from_string(serde_json::to_string(&v).unwrap()),
|
||||||
|
})
|
||||||
|
})
|
||||||
.collect::<Result<_, _>>()
|
.collect::<Result<_, _>>()
|
||||||
.map_err(|e| EvalError::JaqError(e.to_string()))?;
|
.map_err(|e| EvalError::JaqError(e.to_string()))
|
||||||
Ok(out)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -544,6 +653,8 @@ mod tests {
|
||||||
string_to_string(r#""Hello""#) => Ok(DynVal::from("Hello".to_string())),
|
string_to_string(r#""Hello""#) => Ok(DynVal::from("Hello".to_string())),
|
||||||
safe_access_to_existing(r#"{ "a": { "b": 2 } }.a?.b"#) => Ok(DynVal::from(2)),
|
safe_access_to_existing(r#"{ "a": { "b": 2 } }.a?.b"#) => Ok(DynVal::from(2)),
|
||||||
safe_access_to_missing(r#"{ "a": { "b": 2 } }.b?.b"#) => Ok(DynVal::from(&serde_json::Value::Null)),
|
safe_access_to_missing(r#"{ "a": { "b": 2 } }.b?.b"#) => Ok(DynVal::from(&serde_json::Value::Null)),
|
||||||
|
safe_access_to_empty(r#"""?.test"#) => Ok(DynVal::from(&serde_json::Value::Null)),
|
||||||
|
safe_access_to_empty_json_string(r#"'""'?.test"#) => Err(super::EvalError::CannotIndex("\"\"".to_string())),
|
||||||
safe_access_index_to_existing(r#"[1, 2]?.[1]"#) => Ok(DynVal::from(2)),
|
safe_access_index_to_existing(r#"[1, 2]?.[1]"#) => Ok(DynVal::from(2)),
|
||||||
safe_access_index_to_missing(r#""null"?.[1]"#) => Ok(DynVal::from(&serde_json::Value::Null)),
|
safe_access_index_to_missing(r#""null"?.[1]"#) => Ok(DynVal::from(&serde_json::Value::Null)),
|
||||||
safe_access_index_to_non_indexable(r#"32?.[1]"#) => Err(super::EvalError::CannotIndex("32".to_string())),
|
safe_access_index_to_non_indexable(r#"32?.[1]"#) => Err(super::EvalError::CannotIndex("32".to_string())),
|
||||||
|
@ -553,5 +664,9 @@ mod tests {
|
||||||
lazy_evaluation_or(r#"true || "null".test"#) => Ok(DynVal::from(true)),
|
lazy_evaluation_or(r#"true || "null".test"#) => Ok(DynVal::from(true)),
|
||||||
lazy_evaluation_elvis(r#""test"?: "null".test"#) => Ok(DynVal::from("test")),
|
lazy_evaluation_elvis(r#""test"?: "null".test"#) => Ok(DynVal::from("test")),
|
||||||
jq_basic_index(r#"jq("[7,8,9]", ".[0]")"#) => Ok(DynVal::from(7)),
|
jq_basic_index(r#"jq("[7,8,9]", ".[0]")"#) => Ok(DynVal::from(7)),
|
||||||
|
jq_raw_arg(r#"jq("[ \"foo\" ]", ".[0]", "r")"#) => Ok(DynVal::from("foo")),
|
||||||
|
jq_empty_arg(r#"jq("[ \"foo\" ]", ".[0]", "")"#) => Ok(DynVal::from(r#""foo""#)),
|
||||||
|
jq_invalid_arg(r#"jq("[ \"foo\" ]", ".[0]", "hello")"#) => Ok(DynVal::from(r#""foo""#)),
|
||||||
|
jq_no_arg(r#"jq("[ \"foo\" ]", ".[0]")"#) => Ok(DynVal::from(r#""foo""#)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use simplexpr::{
|
||||||
SimplExpr,
|
SimplExpr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::{attributes::Attributes, window_definition::EnumParseError};
|
||||||
use crate::{
|
use crate::{
|
||||||
enum_parse,
|
enum_parse,
|
||||||
error::DiagResult,
|
error::DiagResult,
|
||||||
|
@ -14,8 +15,7 @@ use crate::{
|
||||||
value::{coords, NumWithUnit},
|
value::{coords, NumWithUnit},
|
||||||
};
|
};
|
||||||
use eww_shared_util::{Span, VarName};
|
use eww_shared_util::{Span, VarName};
|
||||||
|
use simplexpr::dynval::ConversionError;
|
||||||
use super::{attributes::Attributes, window_definition::EnumParseError};
|
|
||||||
|
|
||||||
use crate::error::{DiagError, DiagResultExt};
|
use crate::error::{DiagError, DiagResultExt};
|
||||||
|
|
||||||
|
@ -27,6 +27,8 @@ pub enum Error {
|
||||||
CoordsError(#[from] coords::Error),
|
CoordsError(#[from] coords::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
EvalError(#[from] EvalError),
|
EvalError(#[from] EvalError),
|
||||||
|
#[error(transparent)]
|
||||||
|
ConversionError(#[from] ConversionError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Backend-specific options of a window
|
/// Backend-specific options of a window
|
||||||
|
@ -45,6 +47,7 @@ impl BackendWindowOptionsDef {
|
||||||
pub fn from_attrs(attrs: &mut Attributes) -> DiagResult<Self> {
|
pub fn from_attrs(attrs: &mut Attributes) -> DiagResult<Self> {
|
||||||
let struts = attrs.ast_optional("reserve")?;
|
let struts = attrs.ast_optional("reserve")?;
|
||||||
let window_type = attrs.ast_optional("windowtype")?;
|
let window_type = attrs.ast_optional("windowtype")?;
|
||||||
|
let focusable = attrs.ast_optional("focusable")?;
|
||||||
let x11 = X11BackendWindowOptionsDef {
|
let x11 = X11BackendWindowOptionsDef {
|
||||||
sticky: attrs.ast_optional("sticky")?,
|
sticky: attrs.ast_optional("sticky")?,
|
||||||
struts,
|
struts,
|
||||||
|
@ -53,7 +56,7 @@ impl BackendWindowOptionsDef {
|
||||||
};
|
};
|
||||||
let wayland = WlBackendWindowOptionsDef {
|
let wayland = WlBackendWindowOptionsDef {
|
||||||
exclusive: attrs.ast_optional("exclusive")?,
|
exclusive: attrs.ast_optional("exclusive")?,
|
||||||
focusable: attrs.ast_optional("focusable")?,
|
focusable,
|
||||||
namespace: attrs.ast_optional("namespace")?,
|
namespace: attrs.ast_optional("namespace")?,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -109,7 +112,7 @@ impl X11BackendWindowOptionsDef {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
|
||||||
pub struct WlBackendWindowOptions {
|
pub struct WlBackendWindowOptions {
|
||||||
pub exclusive: bool,
|
pub exclusive: bool,
|
||||||
pub focusable: bool,
|
pub focusable: WlWindowFocusable,
|
||||||
pub namespace: Option<String>,
|
pub namespace: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,10 +125,13 @@ pub struct WlBackendWindowOptionsDef {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WlBackendWindowOptionsDef {
|
impl WlBackendWindowOptionsDef {
|
||||||
fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<WlBackendWindowOptions, EvalError> {
|
fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<WlBackendWindowOptions, Error> {
|
||||||
Ok(WlBackendWindowOptions {
|
Ok(WlBackendWindowOptions {
|
||||||
exclusive: eval_opt_expr_as_bool(&self.exclusive, false, local_variables)?,
|
exclusive: eval_opt_expr_as_bool(&self.exclusive, false, local_variables)?,
|
||||||
focusable: eval_opt_expr_as_bool(&self.focusable, false, local_variables)?,
|
focusable: match &self.focusable {
|
||||||
|
Some(expr) => WlWindowFocusable::from_dynval(&expr.eval(local_variables)?)?,
|
||||||
|
None => WlWindowFocusable::default(),
|
||||||
|
},
|
||||||
namespace: match &self.namespace {
|
namespace: match &self.namespace {
|
||||||
Some(expr) => Some(expr.eval(local_variables)?.as_string()?),
|
Some(expr) => Some(expr.eval(local_variables)?.as_string()?),
|
||||||
None => None,
|
None => None,
|
||||||
|
@ -145,6 +151,28 @@ fn eval_opt_expr_as_bool(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)]
|
||||||
|
pub enum WlWindowFocusable {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Exclusive,
|
||||||
|
OnDemand,
|
||||||
|
}
|
||||||
|
impl FromStr for WlWindowFocusable {
|
||||||
|
type Err = EnumParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
enum_parse! { "focusable", s,
|
||||||
|
"none" => Self::None,
|
||||||
|
"exclusive" => Self::Exclusive,
|
||||||
|
"ondemand" => Self::OnDemand,
|
||||||
|
// legacy support
|
||||||
|
"true" => Self::Exclusive,
|
||||||
|
"false" => Self::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Window type of an x11 window
|
/// Window type of an x11 window
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)]
|
||||||
pub enum X11WindowType {
|
pub enum X11WindowType {
|
||||||
|
@ -182,7 +210,7 @@ pub enum Side {
|
||||||
Bottom,
|
Bottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for Side {
|
impl FromStr for Side {
|
||||||
type Err = EnumParseError;
|
type Err = EnumParseError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Side, Self::Err> {
|
fn from_str(s: &str) -> Result<Side, Self::Err> {
|
||||||
|
|
|
@ -90,7 +90,7 @@ impl<I: Iterator<Item = Ast>> AstIterator<I> {
|
||||||
parse_key_values(self, true)
|
parse_key_values(self, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put_back(&mut self, ast: Ast) {
|
pub fn put_back(&mut self, ast: Ast) -> Option<Ast> {
|
||||||
self.remaining_span.0 = ast.span().0;
|
self.remaining_span.0 = ast.span().0;
|
||||||
self.iter.put_back(ast)
|
self.iter.put_back(ast)
|
||||||
}
|
}
|
||||||
|
@ -100,9 +100,8 @@ impl<I: Iterator<Item = Ast>> Iterator for AstIterator<I> {
|
||||||
type Item = Ast;
|
type Item = Ast;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.iter.next().map(|x| {
|
self.iter.next().inspect(|x| {
|
||||||
self.remaining_span.0 = x.span().1;
|
self.remaining_span.0 = x.span().1;
|
||||||
x
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use derive_more::*;
|
use derive_more::{Debug, *};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smart_default::SmartDefault;
|
use smart_default::SmartDefault;
|
||||||
|
@ -14,13 +14,13 @@ pub enum Error {
|
||||||
MalformedCoords,
|
MalformedCoords,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Deserialize, Serialize, Display, DebugCustom, SmartDefault)]
|
#[derive(Clone, Copy, PartialEq, Deserialize, Serialize, Display, Debug, SmartDefault)]
|
||||||
pub enum NumWithUnit {
|
pub enum NumWithUnit {
|
||||||
#[display(fmt = "{}%", .0)]
|
#[display("{}%", _0)]
|
||||||
#[debug(fmt = "{}%", .0)]
|
#[debug("{}%", _0)]
|
||||||
Percent(f32),
|
Percent(f32),
|
||||||
#[display(fmt = "{}px", .0)]
|
#[display("{}px", _0)]
|
||||||
#[debug(fmt = "{}px", .0)]
|
#[debug("{}px", _0)]
|
||||||
#[default]
|
#[default]
|
||||||
Pixels(i32),
|
Pixels(i32),
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ impl FromStr for NumWithUnit {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Deserialize, Serialize, Display, Default)]
|
#[derive(Clone, Copy, PartialEq, Deserialize, Serialize, Display, Default)]
|
||||||
#[display(fmt = "{}*{}", x, y)]
|
#[display("{}*{}", x, y)]
|
||||||
pub struct Coords {
|
pub struct Coords {
|
||||||
pub x: NumWithUnit,
|
pub x: NumWithUnit,
|
||||||
pub y: NumWithUnit,
|
pub y: NumWithUnit,
|
||||||
|
|
13
default.nix
13
default.nix
|
@ -1,6 +1,11 @@
|
||||||
(import (let lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
(import (
|
||||||
in fetchTarball {
|
let
|
||||||
|
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||||
|
in
|
||||||
|
fetchTarball {
|
||||||
url =
|
url =
|
||||||
"https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
lock.nodes.flake-compat.locked.url
|
||||||
|
or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
||||||
sha256 = lock.nodes.flake-compat.locked.narHash;
|
sha256 = lock.nodes.flake-compat.locked.narHash;
|
||||||
}) { src = ./.; }).defaultNix
|
}
|
||||||
|
) { src = ./.; }).defaultNix
|
||||||
|
|
|
@ -61,7 +61,7 @@ This field can be:
|
||||||
- the string `<primary>`, in which case eww tries to identify the primary display (which may fail, especially on wayland)
|
- the string `<primary>`, in which case eww tries to identify the primary display (which may fail, especially on wayland)
|
||||||
- an integer, declaring the monitor index
|
- an integer, declaring the monitor index
|
||||||
- the name of the monitor
|
- the name of the monitor
|
||||||
- a string containing a JSON-array of monitor matchers, such as: `'["<primary>" "HDMI-A-1" "PHL 345B1C" 0]'`. Eww will try to find a match in order, allowing you to specify fallbacks.
|
- a string containing a JSON-array of monitor matchers, such as: `'["<primary>", "HDMI-A-1", "PHL 345B1C", 0]'`. Eww will try to find a match in order, allowing you to specify fallbacks.
|
||||||
|
|
||||||
|
|
||||||
**`geometry`-properties**
|
**`geometry`-properties**
|
||||||
|
@ -87,10 +87,10 @@ Depending on if you are using X11 or Wayland, some additional properties exist:
|
||||||
#### Wayland
|
#### Wayland
|
||||||
|
|
||||||
| Property | Description |
|
| Property | Description |
|
||||||
| ----------: | ------------------------------------------------------------ |
|
| ----------: |------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `stacking` | Where the window should appear in the stack. Possible values: `fg`, `bg`, `overlay`, `bottom`. |
|
| `stacking` | Where the window should appear in the stack. Possible values: `fg`, `bg`, `overlay`, `bottom`. |
|
||||||
| `exclusive` | Whether the compositor should reserve space for the window automatically. Either `true` or `false`. |
|
| `exclusive` | Whether the compositor should reserve space for the window automatically. Either `true` or `false`. If `true` `:anchor` has to include `center`. |
|
||||||
| `focusable` | Whether the window should be able to be focused. This is necessary for any widgets that use the keyboard to work. Either `true` or `false`. |
|
| `focusable` | Whether the window should be able to be focused. This is necessary for any widgets that use the keyboard to work. Possible values: `none`, `exclusive` and `ondemand`. |
|
||||||
| `namespace` | Set the wayland layersurface namespace eww uses. Accepts a `string` value. |
|
| `namespace` | Set the wayland layersurface namespace eww uses. Accepts a `string` value. |
|
||||||
|
|
||||||
|
|
||||||
|
@ -206,9 +206,12 @@ This may be the most commonly used type of variable.
|
||||||
They are useful to access any quickly retrieved value repeatedly,
|
They are useful to access any quickly retrieved value repeatedly,
|
||||||
and thus are the perfect choice for showing your time, date, as well as other bits of information such as pending package updates, weather, and battery level.
|
and thus are the perfect choice for showing your time, date, as well as other bits of information such as pending package updates, weather, and battery level.
|
||||||
|
|
||||||
You can also specify an initial-value. This should prevent eww from waiting for the result of a give command during startup, thus
|
You can also specify an initial-value. This should prevent eww from waiting for the result of a given command during startup, thus
|
||||||
making the startup time faster.
|
making the startup time faster.
|
||||||
|
|
||||||
|
To externally update a polling variable, `eww update` can be used like with basic variables to assign a value.
|
||||||
|
You can also call `eww poll` to poll the variable outside of its usual interval, or even while it isn't running at all.
|
||||||
|
|
||||||
**Listening variables (`deflisten`)**
|
**Listening variables (`deflisten`)**
|
||||||
|
|
||||||
```lisp
|
```lisp
|
||||||
|
@ -381,6 +384,8 @@ If you want to display a list of values, you can use the `for`-Element to fill a
|
||||||
This can be useful in many situations, for example when generating a workspace list from a JSON representation of your workspaces.
|
This can be useful in many situations, for example when generating a workspace list from a JSON representation of your workspaces.
|
||||||
In many cases, this can be used instead of `literal`, and should most likely be preferred in those cases.
|
In many cases, this can be used instead of `literal`, and should most likely be preferred in those cases.
|
||||||
|
|
||||||
|
To see how to declare and use more advanced data structures, check out the [data structures example](/examples/data-structures/eww.yuck).
|
||||||
|
|
||||||
## Splitting up your configuration
|
## Splitting up your configuration
|
||||||
|
|
||||||
As time passes, your configuration might grow larger and larger. Luckily, you can easily split up your configuration into multiple files!
|
As time passes, your configuration might grow larger and larger. Luckily, you can easily split up your configuration into multiple files!
|
||||||
|
|
|
@ -4,3 +4,7 @@ These configurations of eww are available in the `examples/` directory of the [r
|
||||||
|
|
||||||
An eww bar configuration:
|
An eww bar configuration:
|
||||||

|

|
||||||
|
|
||||||
|
A demo on how to declare and use data structures:
|
||||||
|
|
||||||
|

|
||||||
|
|
|
@ -24,14 +24,16 @@ Supported currently are the following features:
|
||||||
- comparisons (`==`, `!=`, `>`, `<`, `<=`, `>=`)
|
- comparisons (`==`, `!=`, `>`, `<`, `<=`, `>=`)
|
||||||
- boolean operations (`||`, `&&`, `!`)
|
- boolean operations (`||`, `&&`, `!`)
|
||||||
- regex match operator (`=~`)
|
- regex match operator (`=~`)
|
||||||
|
- Rust regex style, left hand is regex, right hand is string
|
||||||
|
- ex: workspace.name =~ '^special:.+$'
|
||||||
- elvis operator (`?:`)
|
- elvis operator (`?:`)
|
||||||
- if the left side is `""` or a JSON `null`, then returns the right side,
|
- if the left side is `""` or a JSON `null`, then returns the right side,
|
||||||
otherwise evaluates to the left side.
|
otherwise evaluates to the left side.
|
||||||
- Safe Access operator (`?.`) or (`?.[index]`)
|
- Safe Access operator (`?.`) or (`?.[index]`)
|
||||||
- if the left side is `""` or a JSON `null`, then return `null`. Otherwise,
|
- if the left side is an empty string or a JSON `null`, then return `null`. Otherwise,
|
||||||
attempt to index.
|
attempt to index. Note that indexing an empty JSON string (`'""'`) is an error.
|
||||||
- This can still cause an error to occur if the left hand side exists but is
|
- This can still cause an error to occur if the left hand side exists but is
|
||||||
not an object.
|
not an object or an array.
|
||||||
(`Number` or `String`).
|
(`Number` or `String`).
|
||||||
- conditionals (`condition ? 'value' : 'other value'`)
|
- conditionals (`condition ? 'value' : 'other value'`)
|
||||||
- numbers, strings, booleans and variable references (`12`, `'hi'`, `true`, `some_variable`)
|
- numbers, strings, booleans and variable references (`12`, `'hi'`, `true`, `some_variable`)
|
||||||
|
@ -39,7 +41,12 @@ Supported currently are the following features:
|
||||||
- for this, the object/array value needs to refer to a variable that contains a valid json string.
|
- for this, the object/array value needs to refer to a variable that contains a valid json string.
|
||||||
- some function calls:
|
- some function calls:
|
||||||
- `round(number, decimal_digits)`: Round a number to the given amount of decimals
|
- `round(number, decimal_digits)`: Round a number to the given amount of decimals
|
||||||
|
- `floor(number)`: Round a number down to the nearest integer
|
||||||
|
- `ceil(number)`: Round a number up to the nearest integer
|
||||||
- `sin(number)`, `cos(number)`, `tan(number)`, `cot(number)`: Calculate the trigonometric value of a given number in **radians**
|
- `sin(number)`, `cos(number)`, `tan(number)`, `cot(number)`: Calculate the trigonometric value of a given number in **radians**
|
||||||
|
- `min(a, b)`, `max(a, b)`: Get the smaller or bigger number out of two given numbers
|
||||||
|
- `powi(num, n)`, `powf(num, n)`: Raise number `num` to power `n`. `powi` expects `n` to be of type `i32`
|
||||||
|
- `log(num, n)`: Calculate the base `n` logarithm of `num`. `num`, `n` and return type are `f64`
|
||||||
- `degtorad(number)`: Converts a number from degrees to radians
|
- `degtorad(number)`: Converts a number from degrees to radians
|
||||||
- `radtodeg(number)`: Converts a number from radians to degrees
|
- `radtodeg(number)`: Converts a number from radians to degrees
|
||||||
- `replace(string, regex, replacement)`: Replace matches of a given regex in a string
|
- `replace(string, regex, replacement)`: Replace matches of a given regex in a string
|
||||||
|
@ -50,7 +57,10 @@ Supported currently are the following features:
|
||||||
- `substring(string, start, length)`: Return a substring of given length starting at the given index
|
- `substring(string, start, length)`: Return a substring of given length starting at the given index
|
||||||
- `arraylength(value)`: Gets the length of the array
|
- `arraylength(value)`: Gets the length of the array
|
||||||
- `objectlength(value)`: Gets the amount of entries in the object
|
- `objectlength(value)`: Gets the amount of entries in the object
|
||||||
- `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).
|
- `jq(value, jq_filter_string)`: run a [jq](https://jqlang.github.io/jq/manual/) style command on a json value. (Uses [jaq](https://crates.io/crates/jaq) internally).
|
||||||
|
- `jq(value, jq_filter_string, args)`: Emulate command line flags for jq, see [the docs](https://jqlang.github.io/jq/manual/#invoking-jq) on invoking jq for details. Invalid flags are silently ignored.
|
||||||
|
Currently supported flags:
|
||||||
|
- `"r"`: If the result is a string, it won't be formatted as a JSON string. The equivalent jq flag is `--raw-output`.
|
||||||
- `get_env(string)`: Gets the specified enviroment variable
|
- `get_env(string)`: Gets the specified enviroment variable
|
||||||
- `formattime(unix_timestamp, format_str, timezone)`: Gets the time in a given format from UNIX timestamp.
|
- `formattime(unix_timestamp, format_str, timezone)`: Gets the time in a given format from UNIX timestamp.
|
||||||
Check [chrono's documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) for more
|
Check [chrono's documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) for more
|
||||||
|
@ -60,3 +70,8 @@ Supported currently are the following features:
|
||||||
Same as other `formattime`, but does not accept timezone. Instead, it uses system's local timezone.
|
Same as other `formattime`, but does not accept timezone. Instead, it uses system's local timezone.
|
||||||
Check [chrono's documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) for more
|
Check [chrono's documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) for more
|
||||||
information about format string.
|
information about format string.
|
||||||
|
- `formatbytes(bytes, short, format_mode)`: Display bytes in a human-readable format.
|
||||||
|
Arguments:
|
||||||
|
- `bytes`: `i64` of bytes, supports negative sizes.
|
||||||
|
- `short`: set true for a compact version (default: false)
|
||||||
|
- `format_mode`: set to either to "iec" (eg. `1.0 GiB`) or "si" (eg. `1.2 GB`) (default: "iec")
|
||||||
|
|
BIN
examples/data-structures/data-structures-preview.png
Normal file
BIN
examples/data-structures/data-structures-preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.9 KiB |
29
examples/data-structures/eww.scss
Normal file
29
examples/data-structures/eww.scss
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
* {
|
||||||
|
all: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: bisque;
|
||||||
|
font-size: 16px;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animalLayout {
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animal {
|
||||||
|
font-size: 24px;
|
||||||
|
transition: 0.2s;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
border: 0 solid lightcoral;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animal.selected {
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
73
examples/data-structures/eww.yuck
Normal file
73
examples/data-structures/eww.yuck
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
(defvar stringArray `[
|
||||||
|
"🦝",
|
||||||
|
"🐱",
|
||||||
|
"🐵",
|
||||||
|
"🦁",
|
||||||
|
"🐹",
|
||||||
|
"🦊"
|
||||||
|
]`)
|
||||||
|
|
||||||
|
(defvar object `{
|
||||||
|
"🦝": "racoon",
|
||||||
|
"🐱": "cat",
|
||||||
|
"🐵": "ape",
|
||||||
|
"🦁": "lion",
|
||||||
|
"🐹": "hamster",
|
||||||
|
"🦊": "fox"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
; You could also create an array of objects:
|
||||||
|
; (defvar objectArray `[{ "emoji": "🦝", "name": "racoon" }, { "emoji": "🦊", "name": "fox" }]`)
|
||||||
|
|
||||||
|
(defvar selected `🦝`)
|
||||||
|
|
||||||
|
(defwidget animalButton [emoji]
|
||||||
|
(box
|
||||||
|
:class "animalLayout"
|
||||||
|
(eventbox
|
||||||
|
:class `animal ${selected == emoji ? "selected" : ""}`
|
||||||
|
:cursor "pointer"
|
||||||
|
:onhover "eww update selected=${emoji}"
|
||||||
|
emoji
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(defwidget animalRow []
|
||||||
|
(box
|
||||||
|
:class "animals"
|
||||||
|
:orientation "horizontal"
|
||||||
|
:halign "center"
|
||||||
|
(for animal in stringArray
|
||||||
|
(animalButton
|
||||||
|
:emoji animal
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(defwidget currentAnimal []
|
||||||
|
(box
|
||||||
|
`${object[selected]} ${selected}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(defwidget layout []
|
||||||
|
(box
|
||||||
|
:class "layout"
|
||||||
|
:orientation "vertical"
|
||||||
|
:halign "center"
|
||||||
|
(animalRow)
|
||||||
|
(currentAnimal)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(defwindow data-structures
|
||||||
|
:monitor 0
|
||||||
|
:exclusive false
|
||||||
|
:focusable none
|
||||||
|
:geometry (geometry
|
||||||
|
:anchor "center"
|
||||||
|
)
|
||||||
|
(layout)
|
||||||
|
)
|
|
@ -22,6 +22,7 @@
|
||||||
color: #000000;
|
color: #000000;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric scale trough {
|
.metric scale trough {
|
||||||
all: unset;
|
all: unset;
|
||||||
background-color: #4e4e4e;
|
background-color: #4e4e4e;
|
||||||
|
@ -31,24 +32,11 @@
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
.metric scale trough highlight {
|
|
||||||
all: unset;
|
|
||||||
background-color: #D35D6E;
|
|
||||||
color: #000000;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
.metric scale trough {
|
|
||||||
all: unset;
|
|
||||||
background-color: #4e4e4e;
|
|
||||||
border-radius: 50px;
|
|
||||||
min-height: 3px;
|
|
||||||
min-width: 50px;
|
|
||||||
margin-left: 10px;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
.label-ram {
|
.label-ram {
|
||||||
font-size: large;
|
font-size: large;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspaces button:hover {
|
.workspaces button:hover {
|
||||||
color: #D35D6E;
|
color: #D35D6E;
|
||||||
}
|
}
|
||||||
|
|
54
flake.lock
generated
54
flake.lock
generated
|
@ -1,46 +1,28 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1696426674,
|
"lastModified": 1709944340,
|
||||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
"narHash": "sha256-xr54XK0SjczlUxRo5YwodibUSlpivS9bqHt8BNyWVQA=",
|
||||||
"owner": "edolstra",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
"rev": "baa7aa7bd0a570b3b9edd0b8da859fee3ffaa4d4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "edolstra",
|
"owner": "edolstra",
|
||||||
|
"ref": "refs/pull/65/head",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1705309234,
|
|
||||||
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1708407374,
|
"lastModified": 1725534445,
|
||||||
"narHash": "sha256-EECzarm+uqnNDCwaGg/ppXCO11qibZ1iigORShkkDf0=",
|
"narHash": "sha256-Yd0FK9SkWy+ZPuNqUgmVPXokxDgMJoGuNpMEtkfcf84=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "f33dd27a47ebdf11dc8a5eb05e7c8fbdaf89e73f",
|
"rev": "9bb1e7571aadf31ddb4af77fc64b2d59580f9a39",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -59,17 +41,16 @@
|
||||||
},
|
},
|
||||||
"rust-overlay": {
|
"rust-overlay": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1708395022,
|
"lastModified": 1725675754,
|
||||||
"narHash": "sha256-pxHZbfDsLAAcyWz+snbudxhQPlAnK2nWGAqRx11veac=",
|
"narHash": "sha256-hXW3csqePOcF2e/PYnpXj72KEYyNj2HzTrVNmS/F7Ug=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "b4ae18c03af976549a0b6e396b2b5be56d275f8b",
|
"rev": "8cc45e678e914a16c8e224c3237fb07cf21e5e54",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -77,21 +58,6 @@
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|
132
flake.nix
132
flake.nix
|
@ -1,9 +1,6 @@
|
||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
flake-compat = {
|
flake-compat.url = "github:edolstra/flake-compat/refs/pull/65/head";
|
||||||
url = "github:edolstra/flake-compat";
|
|
||||||
flake = false;
|
|
||||||
};
|
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||||
rust-overlay = {
|
rust-overlay = {
|
||||||
url = "github:oxalica/rust-overlay";
|
url = "github:oxalica/rust-overlay";
|
||||||
|
@ -11,76 +8,89 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, rust-overlay, flake-compat }:
|
outputs =
|
||||||
|
{
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
rust-overlay,
|
||||||
|
flake-compat,
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
pkgsFor = system:
|
overlays = [
|
||||||
import nixpkgs {
|
(import rust-overlay)
|
||||||
inherit system;
|
self.overlays.default
|
||||||
overlays = [ self.overlays.default rust-overlay.overlays.default ];
|
];
|
||||||
};
|
pkgsFor = system: import nixpkgs { inherit system overlays; };
|
||||||
|
|
||||||
targetSystems = [ "aarch64-linux" "x86_64-linux" ];
|
targetSystems = [
|
||||||
mkRustToolchain = pkgs:
|
"aarch64-linux"
|
||||||
pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
"x86_64-linux"
|
||||||
in {
|
];
|
||||||
overlays.default = final: prev:
|
mkRustToolchain = pkgs: pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
||||||
let
|
in
|
||||||
rust = mkRustToolchain final;
|
{
|
||||||
|
overlays.default = final: prev: { inherit (self.packages.${prev.system}) eww eww-wayland; };
|
||||||
|
|
||||||
rustPlatform = prev.makeRustPlatform {
|
packages = nixpkgs.lib.genAttrs targetSystems (
|
||||||
cargo = rust;
|
system:
|
||||||
rustc = rust;
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
eww = (prev.eww.override { inherit rustPlatform; }).overrideAttrs
|
|
||||||
(old: {
|
|
||||||
version = self.rev or "dirty";
|
|
||||||
src = builtins.path {
|
|
||||||
name = "eww";
|
|
||||||
path = prev.lib.cleanSource ./.;
|
|
||||||
};
|
|
||||||
cargoDeps =
|
|
||||||
rustPlatform.importCargoLock { lockFile = ./Cargo.lock; };
|
|
||||||
patches = [ ];
|
|
||||||
# remove this when nixpkgs includes it
|
|
||||||
buildInputs = old.buildInputs ++ [ final.libdbusmenu-gtk3 ];
|
|
||||||
});
|
|
||||||
|
|
||||||
eww-wayland = final.eww;
|
|
||||||
};
|
|
||||||
|
|
||||||
packages = nixpkgs.lib.genAttrs targetSystems (system:
|
|
||||||
let pkgs = pkgsFor system;
|
|
||||||
in (self.overlays.default pkgs pkgs) // {
|
|
||||||
default = self.packages.${system}.eww;
|
|
||||||
});
|
|
||||||
|
|
||||||
devShells = nixpkgs.lib.genAttrs targetSystems (system:
|
|
||||||
let
|
let
|
||||||
pkgs = pkgsFor system;
|
pkgs = pkgsFor system;
|
||||||
rust = mkRustToolchain pkgs;
|
rust = mkRustToolchain pkgs;
|
||||||
in {
|
rustPlatform = pkgs.makeRustPlatform {
|
||||||
default = pkgs.mkShell {
|
cargo = rust;
|
||||||
packages = with pkgs; [
|
rustc = rust;
|
||||||
rust
|
};
|
||||||
rust-analyzer-unwrapped
|
version = (builtins.fromTOML (builtins.readFile ./crates/eww/Cargo.toml)).package.version;
|
||||||
gcc
|
in
|
||||||
glib
|
rec {
|
||||||
gdk-pixbuf
|
eww = rustPlatform.buildRustPackage {
|
||||||
librsvg
|
version = "${version}-dirty";
|
||||||
libdbusmenu-gtk3
|
pname = "eww";
|
||||||
gtk3
|
|
||||||
gtk-layer-shell
|
src = ./.;
|
||||||
|
cargoLock.lockFile = ./Cargo.lock;
|
||||||
|
cargoBuildFlags = [
|
||||||
|
"--bin"
|
||||||
|
"eww"
|
||||||
|
];
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
pkg-config
|
pkg-config
|
||||||
|
wrapGAppsHook
|
||||||
|
];
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
gtk3
|
||||||
|
librsvg
|
||||||
|
gtk-layer-shell
|
||||||
|
libdbusmenu-gtk3
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
eww-wayland = nixpkgs.lib.warn "`eww-wayland` is deprecated due to eww building with both X11 and wayland support by default. Use `eww` instead." eww;
|
||||||
|
default = eww;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
devShells = nixpkgs.lib.genAttrs targetSystems (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
pkgs = pkgsFor system;
|
||||||
|
rust = mkRustToolchain pkgs;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
inputsFrom = [ self.packages.${system}.eww ];
|
||||||
|
packages = with pkgs; [
|
||||||
deno
|
deno
|
||||||
mdbook
|
mdbook
|
||||||
|
zbus-xmlgen
|
||||||
];
|
];
|
||||||
|
|
||||||
RUST_SRC_PATH = "${rust}/lib/rustlib/src/rust/library";
|
RUST_SRC_PATH = "${rust}/lib/rustlib/src/rust/library";
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
formatter =
|
formatter = nixpkgs.lib.genAttrs targetSystems (system: (pkgsFor system).nixfmt-rfc-style);
|
||||||
nixpkgs.lib.genAttrs targetSystems (system: (pkgsFor system).nixfmt);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.76.0"
|
channel = "1.81.0"
|
||||||
components = [ "rust-src" ]
|
components = [ "rust-src" ]
|
||||||
profile = "default"
|
profile = "default"
|
||||||
|
|
13
shell.nix
13
shell.nix
|
@ -1,6 +1,11 @@
|
||||||
(import (let lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
(import (
|
||||||
in fetchTarball {
|
let
|
||||||
|
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||||
|
in
|
||||||
|
fetchTarball {
|
||||||
url =
|
url =
|
||||||
"https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
lock.nodes.flake-compat.locked.url
|
||||||
|
or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
||||||
sha256 = lock.nodes.flake-compat.locked.narHash;
|
sha256 = lock.nodes.flake-compat.locked.narHash;
|
||||||
}) { src = ./.; }).shellNix
|
}
|
||||||
|
) { src = ./.; }).shellNix
|
||||||
|
|
Loading…
Add table
Reference in a new issue