diff --git a/crates/eww/src/config/script_var.rs b/crates/eww/src/config/script_var.rs index a62a864..46db670 100644 --- a/crates/eww/src/config/script_var.rs +++ b/crates/eww/src/config/script_var.rs @@ -26,8 +26,8 @@ pub fn initial_value(var: &ScriptVarDefinition) -> Result { VarSource::Function(f) => { f().map_err(|err| anyhow!(err)).with_context(|| format!("Failed to compute initial value for {}", &var.name())) } - VarSource::Shell(span, f) => { - run_command(f).map_err(|e| anyhow!(create_script_var_failed_warn(*span, var.name(), &e.to_string()))) + VarSource::Shell(span, command) => { + run_command(command).map_err(|e| anyhow!(create_script_var_failed_warn(*span, var.name(), &e.to_string()))) } }, ScriptVarDefinition::Listen(_) => Ok(DynVal::from_string(String::new())), diff --git a/crates/eww/src/script_var_handler.rs b/crates/eww/src/script_var_handler.rs index f28a534..8fc2202 100644 --- a/crates/eww/src/script_var_handler.rs +++ b/crates/eww/src/script_var_handler.rs @@ -171,8 +171,8 @@ impl PollVarHandler { fn run_poll_once(var: &PollScriptVar) -> Result { match &var.command { - VarSource::Shell(span, x) => { - script_var::run_command(x).map_err(|e| anyhow!(create_script_var_failed_warn(*span, &var.name, &e.to_string()))) + VarSource::Shell(span, command) => { + script_var::run_command(command).map_err(|e| anyhow!(create_script_var_failed_warn(*span, &var.name, &e.to_string()))) } VarSource::Function(x) => x().map_err(|e| anyhow!(e)), } diff --git a/crates/eww/src/widgets/mod.rs b/crates/eww/src/widgets/mod.rs index a918390..6d83bb2 100644 --- a/crates/eww/src/widgets/mod.rs +++ b/crates/eww/src/widgets/mod.rs @@ -16,7 +16,7 @@ const CMD_STRING_PLACEHODLER: &str = "{}"; /// Run a command that was provided as an attribute. This command may use a /// placeholder ('{}') which will be replaced by the value provided as [`arg`] -pub(self) fn run_command(cmd: &str, arg: T) { +pub(self) fn run_command(timeout: std::time::Duration, cmd: &str, arg: T) { use wait_timeout::ChildExt; let cmd = cmd.to_string(); std::thread::spawn(move || { @@ -24,7 +24,7 @@ pub(self) fn run_command(cmd: &str log::debug!("Running command from widget: {}", cmd); let child = Command::new("/bin/sh").arg("-c").arg(&cmd).spawn(); match child { - Ok(mut child) => match child.wait_timeout(std::time::Duration::from_millis(200)) { + Ok(mut child) => match child.wait_timeout(timeout) { // child timed out Ok(None) => { log::error!("WARNING: command {} timed out", &cmd); diff --git a/crates/eww/src/widgets/widget_definitions.rs b/crates/eww/src/widgets/widget_definitions.rs index 755f3f2..b67bd04 100644 --- a/crates/eww/src/widgets/widget_definitions.rs +++ b/crates/eww/src/widgets/widget_definitions.rs @@ -104,24 +104,26 @@ pub(super) fn resolve_widget_attrs(bargs: &mut BuilderArgs, gtk_widget: >k::Wi css_provider.load_from_data(format!("* {{ {} }}", style).as_bytes())?; gtk_widget.get_style_context().add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION) }, + // @prop timeout - timeout of the command // @prop onscroll - event to execute when the user scrolls with the mouse over the widget. The placeholder `{}` used in the command will be replaced with either `up` or `down`. - prop(onscroll: as_string) { + prop(timeout: as_duration = Duration::from_millis(200), onscroll: as_string) { gtk_widget.add_events(gdk::EventMask::SCROLL_MASK); gtk_widget.add_events(gdk::EventMask::SMOOTH_SCROLL_MASK); let old_id = on_scroll_handler_id.replace(Some( gtk_widget.connect_scroll_event(move |_, evt| { - run_command(&onscroll, if evt.get_delta().1 < 0f64 { "up" } else { "down" }); + run_command(timeout, &onscroll, if evt.get_delta().1 < 0f64 { "up" } else { "down" }); gtk::Inhibit(false) }) )); old_id.map(|id| gtk_widget.disconnect(id)); }, + // @prop timeout - timeout of the command // @prop onhover - event to execute when the user hovers over the widget - prop(onhover: as_string) { + prop(timeout: as_duration = Duration::from_millis(200),onhover: as_string) { gtk_widget.add_events(gdk::EventMask::ENTER_NOTIFY_MASK); let old_id = on_hover_handler_id.replace(Some( gtk_widget.connect_enter_notify_event(move |_, evt| { - run_command(&onhover, format!("{} {}", evt.get_position().0, evt.get_position().1)); + run_command(timeout, &onhover, format!("{} {}", evt.get_position().0, evt.get_position().1)); gtk::Inhibit(false) }) )); @@ -192,13 +194,14 @@ pub(super) fn resolve_range_attrs(bargs: &mut BuilderArgs, gtk_widget: >k::Ran prop(min: as_f64) { gtk_widget.get_adjustment().set_lower(min)}, // @prop max - the maximum value prop(max: as_f64) { gtk_widget.get_adjustment().set_upper(max)}, + // @prop timeout - timeout of the command // @prop onchange - command executed once the value is changes. The placeholder `{}`, used in the command will be replaced by the new value. - prop(onchange: as_string) { + prop(timeout: as_duration = Duration::from_millis(200), onchange: as_string) { gtk_widget.set_sensitive(true); gtk_widget.add_events(gdk::EventMask::ENTER_NOTIFY_MASK); let old_id = on_change_handler_id.replace(Some( gtk_widget.connect_value_changed(move |gtk_widget| { - run_command(&onchange, gtk_widget.get_value()); + run_command(timeout, &onchange, gtk_widget.get_value()); }) )); old_id.map(|id| gtk_widget.disconnect(id)); @@ -253,11 +256,12 @@ fn build_gtk_combo_box_text(bargs: &mut BuilderArgs) -> Result Result { let gtk_widget = gtk::CheckButton::new(); let on_change_handler_id: Rc>> = Rc::new(RefCell::new(None)); resolve_block!(bargs, gtk_widget, { - // @prop onchecked - action (command) to be executed when checked by the user - // @prop onunchecked - similar to onchecked but when the widget is unchecked - prop(onchecked: as_string = "", onunchecked: as_string = "") { + // @prop timeout - timeout of the command + // @prop onchecked - action (command) to be executed when checked by the user + // @prop onunchecked - similar to onchecked but when the widget is unchecked + prop(timeout: as_duration = Duration::from_millis(200), onchecked: as_string = "", onunchecked: as_string = "") { let old_id = on_change_handler_id.replace(Some( gtk_widget.connect_toggled(move |gtk_widget| { - run_command(if gtk_widget.get_active() { &onchecked } else { &onunchecked }, ""); + run_command(timeout, if gtk_widget.get_active() { &onchecked } else { &onunchecked }, ""); }) )); old_id.map(|id| gtk_widget.disconnect(id)); @@ -324,10 +329,11 @@ fn build_gtk_color_button(bargs: &mut BuilderArgs) -> Result { prop(use_alpha: as_bool) {gtk_widget.set_use_alpha(use_alpha);}, // @prop onchange - runs the code when the color was selected - prop(onchange: as_string) { + // @prop timeout - timeout of the command + prop(timeout: as_duration = Duration::from_millis(200), onchange: as_string) { let old_id = on_change_handler_id.replace(Some( gtk_widget.connect_color_set(move |gtk_widget| { - run_command(&onchange, gtk_widget.get_rgba()); + run_command(timeout, &onchange, gtk_widget.get_rgba()); }) )); old_id.map(|id| gtk_widget.disconnect(id)); @@ -347,10 +353,11 @@ fn build_gtk_color_chooser(bargs: &mut BuilderArgs) -> Result Result { }, // @prop onchange - Command to run when the text changes. The placeholder `{}` will be replaced by the value - prop(onchange: as_string) { + // @prop timeout - timeout of the command + prop(timeout: as_duration = Duration::from_millis(200), onchange: as_string) { let old_id = on_change_handler_id.replace(Some( gtk_widget.connect_changed(move |gtk_widget| { - run_command(&onchange, gtk_widget.get_text().to_string()); + run_command(timeout, &onchange, gtk_widget.get_text().to_string()); }) )); old_id.map(|id| gtk_widget.disconnect(id)); @@ -425,14 +433,20 @@ fn build_gtk_button(bargs: &mut BuilderArgs) -> Result { // @prop onclick - a command that get's run when the button is clicked // @prop onmiddleclick - a command that get's run when the button is middleclicked // @prop onrightclick - a command that get's run when the button is rightclicked - prop(onclick: as_string = "", onmiddleclick: as_string = "", onrightclick: as_string = "") { + // @prop timeout - timeout of the command + prop( + timeout: as_duration = Duration::from_millis(200), + onclick: as_string = "", + onmiddleclick: as_string = "", + onrightclick: as_string = "" + ) { gtk_widget.add_events(gdk::EventMask::ENTER_NOTIFY_MASK); let old_id = on_click_handler_id.replace(Some( gtk_widget.connect_button_press_event(move |_, evt| { match evt.get_button() { - 1 => run_command(&onclick, ""), - 2 => run_command(&onmiddleclick, ""), - 3 => run_command(&onrightclick, ""), + 1 => run_command(timeout, &onclick, ""), + 2 => run_command(timeout, &onmiddleclick, ""), + 3 => run_command(timeout, &onrightclick, ""), _ => {}, } gtk::Inhibit(false) @@ -573,10 +587,12 @@ fn build_gtk_calendar(bargs: &mut BuilderArgs) -> Result { // @prop show-week-numbers - show week numbers prop(show_week_numbers: as_bool) { gtk_widget.set_property_show_week_numbers(show_week_numbers) }, // @prop onclick - command to run when the user selects a date. The `{}` placeholder will be replaced by the selected date. - prop(onclick: as_string) { + // @prop timeout - timeout of the command + prop(timeout: as_duration = Duration::from_millis(200), onclick: as_string) { let old_id = on_click_handler_id.replace(Some( gtk_widget.connect_day_selected(move |w| { run_command( + timeout, &onclick, format!("{}.{}.{}", w.get_property_day(), w.get_property_month(), w.get_property_year()) ) diff --git a/crates/yuck/src/config/script_var_definition.rs b/crates/yuck/src/config/script_var_definition.rs index 82d0928..d616dd3 100644 --- a/crates/yuck/src/config/script_var_definition.rs +++ b/crates/yuck/src/config/script_var_definition.rs @@ -70,6 +70,11 @@ impl FromAstElementContent for PollScriptVar { let (name_span, name) = iter.expect_symbol()?; let mut attrs = iter.expect_key_values()?; let interval = attrs.primitive_required::("interval")?.as_duration()?; + let timeout = attrs + .primitive_optional::("timeout")? + .map(|x| x.as_duration()) + .transpose()? + .unwrap_or_else(|| std::time::Duration::from_millis(200)); let (script_span, script) = iter.expect_literal()?; iter.expect_done()?; Self { name_span, name: VarName(name), command: VarSource::Shell(script_span, script.to_string()), interval } @@ -92,10 +97,10 @@ impl FromAstElementContent for ListenScriptVar { fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { let result: AstResult<_> = try { - let (name_span, name) = iter.expect_symbol()?; - let (command_span, script) = iter.expect_literal()?; - iter.expect_done()?; - Self { name_span, name: VarName(name), command: script.to_string(), command_span } + let (name_span, name) = iter.expect_symbol()?; + let (command_span, script) = iter.expect_literal()?; + iter.expect_done()?; + Self { name_span, name: VarName(name), command: script.to_string(), command_span } }; result.note(r#"Expected format: `(deflisten name "tail -f /tmp/example")`"#) }