Implement trigger for defpoll variable (#273)

* Implement trigger for defpoll variable

* Rename functions and configuration properties, edit docs to show sample usage, improve nearby fields in docs.
This commit is contained in:
Animesh Sahu 2021-09-11 21:31:41 +05:30 committed by GitHub
parent 3a486f181e
commit 80b89dd391
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 105 additions and 9 deletions

View file

@ -12,7 +12,10 @@ use simplexpr::dynval::DynVal;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use yuck::{ use yuck::{
config::window_geometry::{AnchorPoint, WindowGeometry}, config::{
script_var_definition::ScriptVarDefinition,
window_geometry::{AnchorPoint, WindowGeometry},
},
value::Coords, value::Coords,
}; };
@ -200,7 +203,20 @@ impl App {
} }
fn update_state(&mut self, fieldname: VarName, value: DynVal) { fn update_state(&mut self, fieldname: VarName, value: DynVal) {
self.eww_state.update_variable(fieldname, value) self.eww_state.update_variable(fieldname.clone(), value);
if let Ok(linked_poll_vars) = self.eww_config.get_poll_var_link(&fieldname) {
linked_poll_vars.iter().filter_map(|name| self.eww_config.get_script_var(name).ok()).for_each(|var| {
if let ScriptVarDefinition::Poll(poll_var) = var {
match poll_var.run_while_expr.eval(self.eww_state.get_variables()).map(|v| v.as_bool()) {
Ok(Ok(true)) => self.script_var_handler.add(var.clone()),
Ok(Ok(false)) => self.script_var_handler.stop_for_variable(poll_var.name.clone()),
Ok(Err(err)) => error_handling_ctx::print_error(anyhow!(err)),
Err(err) => error_handling_ctx::print_error(anyhow!(err)),
};
}
});
}
} }
fn close_window(&mut self, window_name: &String) -> Result<()> { fn close_window(&mut self, window_name: &String) -> Result<()> {

View file

@ -24,11 +24,20 @@ pub struct EwwConfig {
windows: HashMap<String, EwwWindowDefinition>, windows: HashMap<String, EwwWindowDefinition>,
initial_variables: HashMap<VarName, DynVal>, initial_variables: HashMap<VarName, DynVal>,
script_vars: HashMap<VarName, ScriptVarDefinition>, script_vars: HashMap<VarName, ScriptVarDefinition>,
// Links variable which affect state (active/inactive) of poll var to those poll variables
poll_var_links: HashMap<VarName, Vec<VarName>>,
} }
impl Default for EwwConfig { impl Default for EwwConfig {
fn default() -> Self { fn default() -> Self {
Self { widgets: HashMap::new(), windows: HashMap::new(), initial_variables: HashMap::new(), script_vars: HashMap::new() } Self {
widgets: HashMap::new(),
windows: HashMap::new(),
initial_variables: HashMap::new(),
script_vars: HashMap::new(),
poll_var_links: HashMap::new(),
}
} }
} }
@ -44,6 +53,17 @@ impl EwwConfig {
let Config { widget_definitions, window_definitions, var_definitions, mut script_vars } = config; let Config { widget_definitions, window_definitions, var_definitions, mut script_vars } = config;
script_vars.extend(crate::config::inbuilt::get_inbuilt_vars()); script_vars.extend(crate::config::inbuilt::get_inbuilt_vars());
let mut poll_var_links = HashMap::<VarName, Vec<VarName>>::new();
script_vars
.iter()
.filter_map(|(_, var)| if let ScriptVarDefinition::Poll(poll_var) = var { Some(poll_var) } else { None })
.for_each(|var| {
var.run_while_var_refs
.iter()
.for_each(|name| poll_var_links.entry(name.clone()).or_default().push(var.name.clone()))
});
Ok(EwwConfig { Ok(EwwConfig {
windows: window_definitions windows: window_definitions
.into_iter() .into_iter()
@ -52,6 +72,7 @@ impl EwwConfig {
widgets: widget_definitions, widgets: widget_definitions,
initial_variables: var_definitions.into_iter().map(|(k, v)| (k, v.initial_value)).collect(), initial_variables: var_definitions.into_iter().map(|(k, v)| (k, v.initial_value)).collect(),
script_vars, script_vars,
poll_var_links,
}) })
} }
@ -87,4 +108,8 @@ impl EwwConfig {
pub fn get_widget_definitions(&self) -> &HashMap<String, WidgetDefinition> { pub fn get_widget_definitions(&self) -> &HashMap<String, WidgetDefinition> {
&self.widgets &self.widgets
} }
pub fn get_poll_var_link(&self, name: &VarName) -> Result<&Vec<VarName>> {
self.poll_var_links.get(name).with_context(|| format!("{} does not links to any poll variable", name.0))
}
} }

View file

@ -1,6 +1,6 @@
use std::{collections::HashMap, time::Duration}; use std::{collections::HashMap, time::Duration};
use simplexpr::dynval::DynVal; use simplexpr::{dynval::DynVal, SimplExpr};
use yuck::config::script_var_definition::{PollScriptVar, ScriptVarDefinition, VarSource}; use yuck::config::script_var_definition::{PollScriptVar, ScriptVarDefinition, VarSource};
use crate::config::system_stats::*; use crate::config::system_stats::*;
@ -12,6 +12,8 @@ macro_rules! builtin_vars {
$( $(
VarName::from($name) => ScriptVarDefinition::Poll(PollScriptVar { VarName::from($name) => ScriptVarDefinition::Poll(PollScriptVar {
name: VarName::from($name), name: VarName::from($name),
run_while_expr: SimplExpr::Literal(DynVal::from(true)),
run_while_var_refs: Vec::new(),
command: VarSource::Function($fun), command: VarSource::Function($fun),
initial_value: None, initial_value: None,
interval: $interval, interval: $interval,

View file

@ -134,6 +134,10 @@ impl PollVarHandler {
} }
async fn start(&mut self, var: PollScriptVar) { async fn start(&mut self, var: PollScriptVar) {
if self.poll_handles.contains_key(&var.name) {
return;
}
log::debug!("starting poll var {}", &var.name); log::debug!("starting poll var {}", &var.name);
let cancellation_token = CancellationToken::new(); let cancellation_token = CancellationToken::new();
self.poll_handles.insert(var.name.clone(), cancellation_token.clone()); self.poll_handles.insert(var.name.clone(), cancellation_token.clone());

View file

@ -116,7 +116,6 @@ pub fn parse_scss_from_file(path: &Path) -> Result<String> {
grass::from_string(file_content, &grass_config).map_err(|err| anyhow!("Encountered SCSS parsing error: {:?}", err)) grass::from_string(file_content, &grass_config).map_err(|err| anyhow!("Encountered SCSS parsing error: {:?}", err))
} }
#[ext(pub, name = StringExt)] #[ext(pub, name = StringExt)]
impl<T: AsRef<str>> T { impl<T: AsRef<str>> T {
/// check if the string is empty after removing all linebreaks and trimming /// check if the string is empty after removing all linebreaks and trimming

View file

@ -86,6 +86,39 @@ impl SimplExpr {
pub fn synth_literal<T: Into<DynVal>>(s: T) -> Self { pub fn synth_literal<T: Into<DynVal>>(s: T) -> Self {
Self::Literal(s.into()) Self::Literal(s.into())
} }
pub fn collect_var_refs_into(&self, dest: &mut Vec<VarName>) {
match self {
SimplExpr::VarRef(_, x) => dest.push(x.clone()),
SimplExpr::UnaryOp(_, _, x) => x.as_ref().collect_var_refs_into(dest),
SimplExpr::BinOp(_, l, _, r) => {
l.as_ref().collect_var_refs_into(dest);
r.as_ref().collect_var_refs_into(dest);
}
SimplExpr::IfElse(_, a, b, c) => {
a.as_ref().collect_var_refs_into(dest);
b.as_ref().collect_var_refs_into(dest);
c.as_ref().collect_var_refs_into(dest);
}
SimplExpr::JsonAccess(_, value, index) => {
value.as_ref().collect_var_refs_into(dest);
index.as_ref().collect_var_refs_into(dest);
}
SimplExpr::FunctionCall(_, _, args) => args.iter().for_each(|x: &SimplExpr| x.collect_var_refs_into(dest)),
SimplExpr::JsonArray(_, values) => values.iter().for_each(|v| v.collect_var_refs_into(dest)),
SimplExpr::JsonObject(_, entries) => entries.iter().for_each(|(k, v)| {
k.collect_var_refs_into(dest);
v.collect_var_refs_into(dest);
}),
_ => (),
};
}
pub fn collect_var_refs(&self) -> Vec<VarName> {
let mut dest = Vec::new();
self.collect_var_refs_into(&mut dest);
dest
}
} }
impl Spanned for SimplExpr { impl Spanned for SimplExpr {

View file

@ -55,6 +55,8 @@ pub enum VarSource {
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
pub struct PollScriptVar { pub struct PollScriptVar {
pub name: VarName, pub name: VarName,
pub run_while_expr: SimplExpr,
pub run_while_var_refs: Vec<VarName>,
pub command: VarSource, pub command: VarSource,
pub initial_value: Option<DynVal>, pub initial_value: Option<DynVal>,
pub interval: std::time::Duration, pub interval: std::time::Duration,
@ -71,10 +73,17 @@ impl FromAstElementContent for PollScriptVar {
let initial_value = Some(attrs.primitive_optional("initial")?.unwrap_or_else(|| DynVal::from_string(String::new()))); let initial_value = Some(attrs.primitive_optional("initial")?.unwrap_or_else(|| DynVal::from_string(String::new())));
let interval = attrs.primitive_required::<DynVal, _>("interval")?.as_duration()?; let interval = attrs.primitive_required::<DynVal, _>("interval")?.as_duration()?;
let (script_span, script) = iter.expect_literal()?; let (script_span, script) = iter.expect_literal()?;
let run_while_expr =
attrs.ast_optional::<SimplExpr>("run-while")?.unwrap_or_else(|| SimplExpr::Literal(DynVal::from(true)));
let run_while_var_refs = run_while_expr.collect_var_refs();
iter.expect_done()?; iter.expect_done()?;
Self { Self {
name_span, name_span,
name: VarName(name), name: VarName(name),
run_while_expr,
run_while_var_refs,
command: VarSource::Shell(script_span, script.to_string()), command: VarSource::Shell(script_span, script.to_string()),
initial_value, initial_value,
interval, interval,

View file

@ -154,8 +154,13 @@ They may also be useful to have buttons within eww change what is shown within y
**Polling variables (`defpoll`)** **Polling variables (`defpoll`)**
```lisp ```lisp
(defvar time-visible false) ; for :run-while property of below variable
; when this turns true, the polling starts and
; var gets updated with given interval
(defpoll time :interval "1s" (defpoll time :interval "1s"
:initial "initial-value" ; setting initial is optional :initial "initial-value" ; optional, defaults to poll at startup
:run-while time-visible ; optional, defaults to 'true'
`date +%H:%M:%S`) `date +%H:%M:%S`)
``` ```
@ -163,7 +168,7 @@ A polling variable is a variable which runs a provided shell-script repeatedly,
This may be the most commonly used type of variable. 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 your volume. 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 give command during startup, thus,
making the startup time faster. making the startup time faster.
@ -180,9 +185,12 @@ A listening variable runs a script once, and reads its output continously.
Whenever the script outputs a new line, the value will be updated to that new line. Whenever the script outputs a new line, the value will be updated to that new line.
In the example given above, the value of `foo` will start out as `"whatever"`, and will change whenever a new line is appended to `/tmp/some_file`. In the example given above, the value of `foo` will start out as `"whatever"`, and will change whenever a new line is appended to `/tmp/some_file`.
These are particularly useful if you have a script that can monitor some value on its own. These are particularly useful when you want to apply changes instantaneously when an operation happens if you have a script
that can monitor some value on its own. Volume, brighness, workspaces that get added/removed at runtime,
monitoring currently focused desktop/tag, etc. are the most common usecases of this type of variable.
These are particularly efficient and should be preffered if possible.
For example, the command `xprop -spy -root _NET_CURRENT_DESKTOP` writes the currently focused desktop whenever it changes. For example, the command `xprop -spy -root _NET_CURRENT_DESKTOP` writes the currently focused desktop whenever it changes.
This can be used to implement a workspace widget for a bar, for example.
Another example usecase is monitoring the currently playing song with playerctl: `playerctl --follow metadata --format {{title}}`. Another example usecase is monitoring the currently playing song with playerctl: `playerctl --follow metadata --format {{title}}`.
**Built-in "magic" variables** **Built-in "magic" variables**