mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-07-01 10:41:03 +02:00
feat: support for 'ironvar' dynamic variables
This commit is contained in:
parent
f5bdc5a027
commit
ded50cca6f
17 changed files with 613 additions and 195 deletions
|
@ -1,11 +1,9 @@
|
||||||
use crate::dynamic_string::DynamicString;
|
use crate::dynamic_value::{dynamic_string, DynamicBool};
|
||||||
use crate::script::{Script, ScriptInput};
|
use crate::script::{Script, ScriptInput};
|
||||||
use crate::send;
|
|
||||||
use gtk::gdk::ScrollDirection;
|
use gtk::gdk::ScrollDirection;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{EventBox, Orientation, Revealer, RevealerTransitionType};
|
use gtk::{EventBox, Orientation, Revealer, RevealerTransitionType};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::spawn;
|
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
/// Common configuration options
|
/// Common configuration options
|
||||||
|
@ -15,7 +13,7 @@ pub struct CommonConfig {
|
||||||
pub class: Option<String>,
|
pub class: Option<String>,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
|
||||||
pub show_if: Option<ScriptInput>,
|
pub show_if: Option<DynamicBool>,
|
||||||
pub transition_type: Option<TransitionType>,
|
pub transition_type: Option<TransitionType>,
|
||||||
pub transition_duration: Option<u32>,
|
pub transition_duration: Option<u32>,
|
||||||
|
|
||||||
|
@ -114,7 +112,7 @@ impl CommonConfig {
|
||||||
|
|
||||||
if let Some(tooltip) = self.tooltip {
|
if let Some(tooltip) = self.tooltip {
|
||||||
let container = container.clone();
|
let container = container.clone();
|
||||||
DynamicString::new(&tooltip, move |string| {
|
dynamic_string(&tooltip, move |string| {
|
||||||
container.set_tooltip_text(Some(&string));
|
container.set_tooltip_text(Some(&string));
|
||||||
Continue(true)
|
Continue(true)
|
||||||
});
|
});
|
||||||
|
@ -127,23 +125,13 @@ impl CommonConfig {
|
||||||
container.show_all();
|
container.show_all();
|
||||||
},
|
},
|
||||||
|show_if| {
|
|show_if| {
|
||||||
let script = Script::new_polling(show_if);
|
|
||||||
let container = container.clone();
|
let container = container.clone();
|
||||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
|
||||||
|
|
||||||
spawn(async move {
|
|
||||||
script
|
|
||||||
.run(None, |_, success| {
|
|
||||||
send!(tx, success);
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let revealer = revealer.clone();
|
let revealer = revealer.clone();
|
||||||
let container = container.clone();
|
let container = container.clone();
|
||||||
|
|
||||||
rx.attach(None, move |success| {
|
show_if.subscribe(move |success| {
|
||||||
if success {
|
if success {
|
||||||
container.show_all();
|
container.show_all();
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,8 @@ pub struct Config {
|
||||||
/// GTK icon theme to use.
|
/// GTK icon theme to use.
|
||||||
pub icon_theme: Option<String>,
|
pub icon_theme: Option<String>,
|
||||||
|
|
||||||
|
pub ironvar_defaults: Option<HashMap<Box<str>, String>>,
|
||||||
|
|
||||||
pub start: Option<Vec<ModuleConfig>>,
|
pub start: Option<Vec<ModuleConfig>>,
|
||||||
pub center: Option<Vec<ModuleConfig>>,
|
pub center: Option<Vec<ModuleConfig>>,
|
||||||
pub end: Option<Vec<ModuleConfig>>,
|
pub end: Option<Vec<ModuleConfig>>,
|
||||||
|
|
|
@ -1,160 +0,0 @@
|
||||||
use crate::script::{OutputStream, Script};
|
|
||||||
use crate::{lock, send};
|
|
||||||
use gtk::prelude::*;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use tokio::spawn;
|
|
||||||
|
|
||||||
/// A segment of a dynamic string,
|
|
||||||
/// containing either a static string
|
|
||||||
/// or a script.
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum DynamicStringSegment {
|
|
||||||
Static(String),
|
|
||||||
Dynamic(Script),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A string with embedded scripts for dynamic content.
|
|
||||||
pub struct DynamicString;
|
|
||||||
|
|
||||||
impl DynamicString {
|
|
||||||
/// Creates a new dynamic string, based off the input template.
|
|
||||||
/// Runs `f` with the compiled string each time one of the scripts updates.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rs
|
|
||||||
/// DynamicString::new(&text, move |string| {
|
|
||||||
/// label.set_markup(&string);
|
|
||||||
/// Continue(true)
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
pub fn new<F>(input: &str, f: F) -> Self
|
|
||||||
where
|
|
||||||
F: FnMut(String) -> Continue + 'static,
|
|
||||||
{
|
|
||||||
let segments = Self::parse_input(input);
|
|
||||||
|
|
||||||
let label_parts = Arc::new(Mutex::new(Vec::new()));
|
|
||||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
|
||||||
|
|
||||||
for (i, segment) in segments.into_iter().enumerate() {
|
|
||||||
match segment {
|
|
||||||
DynamicStringSegment::Static(str) => {
|
|
||||||
lock!(label_parts).push(str);
|
|
||||||
}
|
|
||||||
DynamicStringSegment::Dynamic(script) => {
|
|
||||||
let tx = tx.clone();
|
|
||||||
let label_parts = label_parts.clone();
|
|
||||||
|
|
||||||
// insert blank value to preserve segment order
|
|
||||||
lock!(label_parts).push(String::new());
|
|
||||||
|
|
||||||
spawn(async move {
|
|
||||||
script
|
|
||||||
.run(None, |out, _| {
|
|
||||||
if let OutputStream::Stdout(out) = out {
|
|
||||||
let mut label_parts = lock!(label_parts);
|
|
||||||
|
|
||||||
let _: String = std::mem::replace(&mut label_parts[i], out);
|
|
||||||
|
|
||||||
let string = label_parts.join("");
|
|
||||||
send!(tx, string);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize
|
|
||||||
{
|
|
||||||
let label_parts = lock!(label_parts).join("");
|
|
||||||
send!(tx, label_parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
rx.attach(None, f);
|
|
||||||
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses the input string into static and dynamic segments
|
|
||||||
fn parse_input(input: &str) -> Vec<DynamicStringSegment> {
|
|
||||||
if !input.contains("{{") {
|
|
||||||
return vec![DynamicStringSegment::Static(input.to_string())];
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut segments = vec![];
|
|
||||||
|
|
||||||
let mut chars = input.chars().collect::<Vec<_>>();
|
|
||||||
while !chars.is_empty() {
|
|
||||||
let char_pair = if chars.len() > 1 {
|
|
||||||
Some(&chars[..=1])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let (token, skip) = if let Some(['{', '{']) = char_pair {
|
|
||||||
const SKIP_BRACKETS: usize = 4; // two braces either side
|
|
||||||
|
|
||||||
let str = chars
|
|
||||||
.windows(2)
|
|
||||||
.skip(2)
|
|
||||||
.take_while(|win| win != &['}', '}'])
|
|
||||||
.map(|w| w[0])
|
|
||||||
.collect::<String>();
|
|
||||||
|
|
||||||
let len = str.len();
|
|
||||||
|
|
||||||
(
|
|
||||||
DynamicStringSegment::Dynamic(Script::from(str.as_str())),
|
|
||||||
len + SKIP_BRACKETS,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let mut str = chars
|
|
||||||
.windows(2)
|
|
||||||
.take_while(|win| win != &['{', '{'])
|
|
||||||
.map(|w| w[0])
|
|
||||||
.collect::<String>();
|
|
||||||
|
|
||||||
// if segment is at end of string, last char gets missed above due to uneven window.
|
|
||||||
if chars.len() == str.len() + 1 {
|
|
||||||
let remaining_char = *chars.get(str.len()).expect("Failed to find last char");
|
|
||||||
str.push(remaining_char);
|
|
||||||
}
|
|
||||||
|
|
||||||
let len = str.len();
|
|
||||||
|
|
||||||
(DynamicStringSegment::Static(str), len)
|
|
||||||
};
|
|
||||||
|
|
||||||
// quick runtime check to make sure the parser is working as expected
|
|
||||||
assert_ne!(skip, 0);
|
|
||||||
|
|
||||||
segments.push(token);
|
|
||||||
chars.drain(..skip);
|
|
||||||
}
|
|
||||||
|
|
||||||
segments
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test() {
|
|
||||||
// TODO: see if we can run gtk tests in ci
|
|
||||||
if gtk::init().is_ok() {
|
|
||||||
let label = gtk::Label::new(None);
|
|
||||||
DynamicString::new(
|
|
||||||
"Uptime: {{1000:uptime -p | cut -d ' ' -f2-}}",
|
|
||||||
move |string| {
|
|
||||||
label.set_label(&string);
|
|
||||||
Continue(true)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
78
src/dynamic_value/dynamic_bool.rs
Normal file
78
src/dynamic_value/dynamic_bool.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
use crate::ironvar::get_variable_manager;
|
||||||
|
use crate::script::Script;
|
||||||
|
use crate::send;
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
use glib::Continue;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tokio::spawn;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum DynamicBool {
|
||||||
|
/// Either a script or variable, to be determined.
|
||||||
|
Unknown(String),
|
||||||
|
Script(Script),
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
Variable(Box<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynamicBool {
|
||||||
|
pub fn subscribe<F>(self, f: F)
|
||||||
|
where
|
||||||
|
F: FnMut(bool) -> Continue + 'static,
|
||||||
|
{
|
||||||
|
let value = match self {
|
||||||
|
Self::Unknown(input) => {
|
||||||
|
if input.starts_with('#') {
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(feature = "ipc")] {
|
||||||
|
Self::Variable(input.into())
|
||||||
|
} else {
|
||||||
|
Self::Unknown(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let script = Script::from(input.as_str());
|
||||||
|
Self::Script(script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => self,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
|
rx.attach(None, f);
|
||||||
|
|
||||||
|
spawn(async move {
|
||||||
|
match value {
|
||||||
|
DynamicBool::Script(script) => {
|
||||||
|
script
|
||||||
|
.run(None, |_, success| {
|
||||||
|
send!(tx, success);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
DynamicBool::Variable(variable) => {
|
||||||
|
let variable_manager = get_variable_manager();
|
||||||
|
|
||||||
|
let variable_name = variable[1..].into(); // remove hash
|
||||||
|
let mut rx = crate::write_lock!(variable_manager).subscribe(variable_name);
|
||||||
|
|
||||||
|
while let Ok(value) = rx.recv().await {
|
||||||
|
let has_value = value.map(|s| is_truthy(&s)).unwrap_or_default();
|
||||||
|
send!(tx, has_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DynamicBool::Unknown(_) => unreachable!(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a string ironvar is 'truthy'
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
fn is_truthy(string: &str) -> bool {
|
||||||
|
!(string.is_empty() || string == "0" || string == "false")
|
||||||
|
}
|
321
src/dynamic_value/dynamic_string.rs
Normal file
321
src/dynamic_value/dynamic_string.rs
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
use crate::ironvar::get_variable_manager;
|
||||||
|
use crate::script::{OutputStream, Script};
|
||||||
|
use crate::{lock, send};
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use tokio::spawn;
|
||||||
|
|
||||||
|
/// A segment of a dynamic string,
|
||||||
|
/// containing either a static string
|
||||||
|
/// or a script.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum DynamicStringSegment {
|
||||||
|
Static(String),
|
||||||
|
Script(Script),
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
Variable(Box<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new dynamic string, based off the input template.
|
||||||
|
/// Runs `f` with the compiled string each time one of the scripts or variables updates.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rs
|
||||||
|
/// dynamic_string(&text, move |string| {
|
||||||
|
/// label.set_markup(&string);
|
||||||
|
/// Continue(true)
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn dynamic_string<F>(input: &str, f: F)
|
||||||
|
where
|
||||||
|
F: FnMut(String) -> Continue + 'static,
|
||||||
|
{
|
||||||
|
let tokens = parse_input(input);
|
||||||
|
|
||||||
|
let label_parts = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
|
for (i, segment) in tokens.into_iter().enumerate() {
|
||||||
|
match segment {
|
||||||
|
DynamicStringSegment::Static(str) => {
|
||||||
|
lock!(label_parts).push(str);
|
||||||
|
}
|
||||||
|
DynamicStringSegment::Script(script) => {
|
||||||
|
let tx = tx.clone();
|
||||||
|
let label_parts = label_parts.clone();
|
||||||
|
|
||||||
|
// insert blank value to preserve segment order
|
||||||
|
lock!(label_parts).push(String::new());
|
||||||
|
|
||||||
|
spawn(async move {
|
||||||
|
script
|
||||||
|
.run(None, |out, _| {
|
||||||
|
if let OutputStream::Stdout(out) = out {
|
||||||
|
let mut label_parts = lock!(label_parts);
|
||||||
|
|
||||||
|
let _: String = std::mem::replace(&mut label_parts[i], out);
|
||||||
|
|
||||||
|
let string = label_parts.join("");
|
||||||
|
send!(tx, string);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
DynamicStringSegment::Variable(name) => {
|
||||||
|
let tx = tx.clone();
|
||||||
|
let label_parts = label_parts.clone();
|
||||||
|
|
||||||
|
// insert blank value to preserve segment order
|
||||||
|
lock!(label_parts).push(String::new());
|
||||||
|
|
||||||
|
spawn(async move {
|
||||||
|
let variable_manager = get_variable_manager();
|
||||||
|
let mut rx = crate::write_lock!(variable_manager).subscribe(name);
|
||||||
|
|
||||||
|
while let Ok(value) = rx.recv().await {
|
||||||
|
if let Some(value) = value {
|
||||||
|
let mut label_parts = lock!(label_parts);
|
||||||
|
|
||||||
|
let _: String = std::mem::replace(&mut label_parts[i], value);
|
||||||
|
|
||||||
|
let string = label_parts.join("");
|
||||||
|
send!(tx, string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rx.attach(None, f);
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
{
|
||||||
|
let label_parts = lock!(label_parts).join("");
|
||||||
|
send!(tx, label_parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the input string into static and dynamic segments
|
||||||
|
fn parse_input(input: &str) -> Vec<DynamicStringSegment> {
|
||||||
|
// short-circuit parser if it's all static
|
||||||
|
if !input.contains("{{") && !input.contains('#') {
|
||||||
|
return vec![DynamicStringSegment::Static(input.to_string())];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tokens = vec![];
|
||||||
|
|
||||||
|
let mut chars = input.chars().collect::<Vec<_>>();
|
||||||
|
while !chars.is_empty() {
|
||||||
|
let char_pair = if chars.len() > 1 {
|
||||||
|
Some(&chars[..=1])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let (token, skip) = match char_pair {
|
||||||
|
Some(['{', '{']) => parse_script(&chars),
|
||||||
|
Some(['#', '#']) => (DynamicStringSegment::Static("#".to_string()), 2),
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
Some(['#', _]) => parse_variable(&chars),
|
||||||
|
_ => parse_static(&chars),
|
||||||
|
};
|
||||||
|
|
||||||
|
// quick runtime check to make sure the parser is working as expected
|
||||||
|
assert_ne!(skip, 0);
|
||||||
|
|
||||||
|
tokens.push(token);
|
||||||
|
chars.drain(..skip);
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_script(chars: &[char]) -> (DynamicStringSegment, usize) {
|
||||||
|
const SKIP_BRACKETS: usize = 4; // two braces either side
|
||||||
|
|
||||||
|
let str = chars
|
||||||
|
.windows(2)
|
||||||
|
.skip(2)
|
||||||
|
.take_while(|win| win != &['}', '}'])
|
||||||
|
.map(|w| w[0])
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
let len = str.len() + SKIP_BRACKETS;
|
||||||
|
let script = Script::from(str.as_str());
|
||||||
|
|
||||||
|
(DynamicStringSegment::Script(script), len)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
fn parse_variable(chars: &[char]) -> (DynamicStringSegment, usize) {
|
||||||
|
const SKIP_HASH: usize = 1;
|
||||||
|
|
||||||
|
let str = chars
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.take_while(|&c| !c.is_whitespace())
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
let len = str.len() + SKIP_HASH;
|
||||||
|
let value = str.into();
|
||||||
|
|
||||||
|
(DynamicStringSegment::Variable(value), len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_static(chars: &[char]) -> (DynamicStringSegment, usize) {
|
||||||
|
let mut str = chars
|
||||||
|
.windows(2)
|
||||||
|
.take_while(|&win| win != ['{', '{'] && win[0] != '#')
|
||||||
|
.map(|w| w[0])
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
// if segment is at end of string, last char gets missed above due to uneven window.
|
||||||
|
if chars.len() == str.len() + 1 {
|
||||||
|
let remaining_char = *chars.get(str.len()).expect("Failed to find last char");
|
||||||
|
str.push(remaining_char);
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = str.len();
|
||||||
|
|
||||||
|
(DynamicStringSegment::Static(str), len)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_static() {
|
||||||
|
const INPUT: &str = "hello world";
|
||||||
|
let tokens = parse_input(INPUT);
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 1);
|
||||||
|
assert!(matches!(&tokens[0], DynamicStringSegment::Static(value) if value == INPUT))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_static_odd_char_count() {
|
||||||
|
const INPUT: &str = "hello";
|
||||||
|
let tokens = parse_input(INPUT);
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 1);
|
||||||
|
assert!(matches!(&tokens[0], DynamicStringSegment::Static(value) if value == INPUT))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_script() {
|
||||||
|
const INPUT: &str = "{{echo hello}}";
|
||||||
|
let tokens = parse_input(INPUT);
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 1);
|
||||||
|
assert!(
|
||||||
|
matches!(&tokens[0], DynamicStringSegment::Script(script) if script.cmd == "echo hello")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_variable() {
|
||||||
|
const INPUT: &str = "#variable";
|
||||||
|
let tokens = parse_input(INPUT);
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 1);
|
||||||
|
assert!(
|
||||||
|
matches!(&tokens[0], DynamicStringSegment::Variable(name) if name.to_string() == "variable")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_static_script() {
|
||||||
|
const INPUT: &str = "hello {{echo world}}";
|
||||||
|
let tokens = parse_input(INPUT);
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 2);
|
||||||
|
assert!(matches!(&tokens[0], DynamicStringSegment::Static(str) if str == "hello "));
|
||||||
|
assert!(
|
||||||
|
matches!(&tokens[1], DynamicStringSegment::Script(script) if script.cmd == "echo world")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_static_variable() {
|
||||||
|
const INPUT: &str = "hello #subject";
|
||||||
|
let tokens = parse_input(INPUT);
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 2);
|
||||||
|
assert!(matches!(&tokens[0], DynamicStringSegment::Static(str) if str == "hello "));
|
||||||
|
assert!(
|
||||||
|
matches!(&tokens[1], DynamicStringSegment::Variable(name) if name.to_string() == "subject")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_static_script_static() {
|
||||||
|
const INPUT: &str = "hello {{echo world}} foo";
|
||||||
|
let tokens = parse_input(INPUT);
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 3);
|
||||||
|
assert!(matches!(&tokens[0], DynamicStringSegment::Static(str) if str == "hello "));
|
||||||
|
assert!(
|
||||||
|
matches!(&tokens[1], DynamicStringSegment::Script(script) if script.cmd == "echo world")
|
||||||
|
);
|
||||||
|
assert!(matches!(&tokens[2], DynamicStringSegment::Static(str) if str == " foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_static_variable_static() {
|
||||||
|
const INPUT: &str = "hello #subject foo";
|
||||||
|
let tokens = parse_input(INPUT);
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 3);
|
||||||
|
assert!(matches!(&tokens[0], DynamicStringSegment::Static(str) if str == "hello "));
|
||||||
|
assert!(
|
||||||
|
matches!(&tokens[1], DynamicStringSegment::Variable(name) if name.to_string() == "subject")
|
||||||
|
);
|
||||||
|
assert!(matches!(&tokens[2], DynamicStringSegment::Static(str) if str == " foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_static_script_variable() {
|
||||||
|
const INPUT: &str = "hello {{echo world}} #foo";
|
||||||
|
let tokens = parse_input(INPUT);
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 4);
|
||||||
|
assert!(matches!(&tokens[0], DynamicStringSegment::Static(str) if str == "hello "));
|
||||||
|
assert!(
|
||||||
|
matches!(&tokens[1], DynamicStringSegment::Script(script) if script.cmd == "echo world")
|
||||||
|
);
|
||||||
|
assert!(matches!(&tokens[2], DynamicStringSegment::Static(str) if str == " "));
|
||||||
|
assert!(
|
||||||
|
matches!(&tokens[3], DynamicStringSegment::Variable(name) if name.to_string() == "foo")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_escape_hash() {
|
||||||
|
const INPUT: &str = "number ###num";
|
||||||
|
let tokens = parse_input(INPUT);
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 3);
|
||||||
|
assert!(matches!(&tokens[0], DynamicStringSegment::Static(str) if str == "number "));
|
||||||
|
assert!(matches!(&tokens[1], DynamicStringSegment::Static(str) if str == "#"));
|
||||||
|
assert!(
|
||||||
|
matches!(&tokens[2], DynamicStringSegment::Variable(name) if name.to_string() == "num")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_script_with_hash() {
|
||||||
|
const INPUT: &str = "{{echo #hello}}";
|
||||||
|
let tokens = parse_input(INPUT);
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 1);
|
||||||
|
assert!(
|
||||||
|
matches!(&tokens[0], DynamicStringSegment::Script(script) if script.cmd == "echo #hello")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
7
src/dynamic_value/mod.rs
Normal file
7
src/dynamic_value/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#![doc = include_str!("../../docs/Dynamic values.md")]
|
||||||
|
|
||||||
|
mod dynamic_bool;
|
||||||
|
mod dynamic_string;
|
||||||
|
|
||||||
|
pub use dynamic_bool::DynamicBool;
|
||||||
|
pub use dynamic_string::dynamic_string;
|
|
@ -10,5 +10,21 @@ pub enum Command {
|
||||||
|
|
||||||
/// Open the GTK inspector
|
/// Open the GTK inspector
|
||||||
Inspect,
|
Inspect,
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Set an `ironvar` value.
|
||||||
|
/// This creates it if it does not already exist, and updates it if it does.
|
||||||
|
/// Any references to this variable are automatically and immediately updated.
|
||||||
|
/// Keys and values can be any valid UTF-8 string.
|
||||||
|
Set {
|
||||||
|
/// Variable key. Can be any valid UTF-8 string.
|
||||||
|
key: Box<str>,
|
||||||
|
/// Variable value. Can be any valid UTF-8 string.
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Get the current value of an `ironvar`.
|
||||||
|
Get {
|
||||||
|
/// Variable key.
|
||||||
|
key: Box<str>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum Response {
|
pub enum Response {
|
||||||
Ok,
|
Ok,
|
||||||
|
OkValue { value: String },
|
||||||
Err { message: Option<String> },
|
Err { message: Option<String> },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,22 @@ impl Ipc {
|
||||||
Command::Inspect => {
|
Command::Inspect => {
|
||||||
gtk::Window::set_interactive_debugging(true);
|
gtk::Window::set_interactive_debugging(true);
|
||||||
Response::Ok
|
Response::Ok
|
||||||
|
},
|
||||||
|
Command::Set { key, value } => {
|
||||||
|
let variable_manager = get_variable_manager();
|
||||||
|
let mut variable_manager = write_lock!(variable_manager);
|
||||||
|
match variable_manager.set(key, value) {
|
||||||
|
Ok(_) => Response::Ok,
|
||||||
|
Err(err) => Response::error(&format!("{err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::Get { key } => {
|
||||||
|
let variable_manager = get_variable_manager();
|
||||||
|
let value = read_lock!(variable_manager).get(&key);
|
||||||
|
match value {
|
||||||
|
Some(value) => Response::OkValue { value },
|
||||||
|
None => Response::error("Variable not found"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Command::Ping => Response::Ok,
|
Command::Ping => Response::Ok,
|
||||||
}
|
}
|
||||||
|
|
107
src/ironvar.rs
Normal file
107
src/ironvar.rs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
#![doc = include_str!("../docs/Ironvars.md")]
|
||||||
|
|
||||||
|
use crate::{arc_rw, send};
|
||||||
|
use color_eyre::{Report, Result};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref VARIABLE_MANAGER: Arc<RwLock<VariableManager>> = arc_rw!(VariableManager::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_variable_manager() -> Arc<RwLock<VariableManager>> {
|
||||||
|
VARIABLE_MANAGER.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Global singleton manager for `IronVar` variables.
|
||||||
|
pub struct VariableManager {
|
||||||
|
variables: HashMap<Box<str>, IronVar>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VariableManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
variables: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the value for a variable,
|
||||||
|
/// creating it if it does not exist.
|
||||||
|
pub fn set(&mut self, key: Box<str>, value: String) -> Result<()> {
|
||||||
|
if Self::key_is_valid(&key) {
|
||||||
|
if let Some(var) = self.variables.get_mut(&key) {
|
||||||
|
var.set(Some(value));
|
||||||
|
} else {
|
||||||
|
let var = IronVar::new(Some(value));
|
||||||
|
self.variables.insert(key, var);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Report::msg("Invalid key"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the current value of an `ironvar`.
|
||||||
|
/// Prefer to use `subscribe` where possible.
|
||||||
|
pub fn get(&self, key: &str) -> Option<String> {
|
||||||
|
self.variables.get(key).and_then(IronVar::get)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subscribes to an `ironvar`, creating it if it does not exist.
|
||||||
|
/// Any time the var is set, its value is sent on the channel.
|
||||||
|
pub fn subscribe(&mut self, key: Box<str>) -> broadcast::Receiver<Option<String>> {
|
||||||
|
self.variables
|
||||||
|
.entry(key)
|
||||||
|
.or_insert_with(|| IronVar::new(None))
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_is_valid(key: &str) -> bool {
|
||||||
|
!key.is_empty()
|
||||||
|
&& key
|
||||||
|
.chars()
|
||||||
|
.all(|char| char.is_alphanumeric() || char == '_' || char == '-')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ironbar dynamic variable representation.
|
||||||
|
/// Interact with them through the `VARIABLE_MANAGER` `VariableManager` singleton.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct IronVar {
|
||||||
|
value: Option<String>,
|
||||||
|
tx: broadcast::Sender<Option<String>>,
|
||||||
|
_rx: broadcast::Receiver<Option<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IronVar {
|
||||||
|
/// Creates a new variable.
|
||||||
|
fn new(value: Option<String>) -> Self {
|
||||||
|
let (tx, rx) = broadcast::channel(32);
|
||||||
|
|
||||||
|
Self { value, tx, _rx: rx }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the current variable value.
|
||||||
|
/// Prefer to subscribe to changes where possible.
|
||||||
|
fn get(&self) -> Option<String> {
|
||||||
|
self.value.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the current variable value.
|
||||||
|
/// The change is broadcast to all receivers.
|
||||||
|
fn set(&mut self, value: Option<String>) {
|
||||||
|
self.value = value.clone();
|
||||||
|
send!(self.tx, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subscribes to the variable.
|
||||||
|
/// The latest value is immediately sent to all receivers.
|
||||||
|
fn subscribe(&self) -> broadcast::Receiver<Option<String>> {
|
||||||
|
let rx = self.tx.subscribe();
|
||||||
|
send!(self.tx, self.value.clone());
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
/// Sends a message on an asynchronous `Sender` using `send()`
|
/// Sends a message on an asynchronous `Sender` using `send()`
|
||||||
/// Panics if the message cannot be sent.
|
/// Panics if the message cannot be sent.
|
||||||
///
|
///
|
||||||
/// Usage:
|
/// # Usage:
|
||||||
///
|
///
|
||||||
/// ```rs
|
/// ```rs
|
||||||
/// send_async!(tx, "my message");
|
/// send_async!(tx, "my message");
|
||||||
|
@ -16,7 +16,7 @@ macro_rules! send_async {
|
||||||
/// Sends a message on an synchronous `Sender` using `send()`
|
/// Sends a message on an synchronous `Sender` using `send()`
|
||||||
/// Panics if the message cannot be sent.
|
/// Panics if the message cannot be sent.
|
||||||
///
|
///
|
||||||
/// Usage:
|
/// # Usage:
|
||||||
///
|
///
|
||||||
/// ```rs
|
/// ```rs
|
||||||
/// send!(tx, "my message");
|
/// send!(tx, "my message");
|
||||||
|
@ -31,7 +31,7 @@ macro_rules! send {
|
||||||
/// Sends a message on an synchronous `Sender` using `try_send()`
|
/// Sends a message on an synchronous `Sender` using `try_send()`
|
||||||
/// Panics if the message cannot be sent.
|
/// Panics if the message cannot be sent.
|
||||||
///
|
///
|
||||||
/// Usage:
|
/// # Usage:
|
||||||
///
|
///
|
||||||
/// ```rs
|
/// ```rs
|
||||||
/// try_send!(tx, "my message");
|
/// try_send!(tx, "my message");
|
||||||
|
@ -46,7 +46,7 @@ macro_rules! try_send {
|
||||||
/// Locks a `Mutex`.
|
/// Locks a `Mutex`.
|
||||||
/// Panics if the `Mutex` cannot be locked.
|
/// Panics if the `Mutex` cannot be locked.
|
||||||
///
|
///
|
||||||
/// Usage:
|
/// # Usage:
|
||||||
///
|
///
|
||||||
/// ```rs
|
/// ```rs
|
||||||
/// let mut val = lock!(my_mutex);
|
/// let mut val = lock!(my_mutex);
|
||||||
|
@ -62,7 +62,7 @@ macro_rules! lock {
|
||||||
/// Gets a read lock on a `RwLock`.
|
/// Gets a read lock on a `RwLock`.
|
||||||
/// Panics if the `RwLock` cannot be locked.
|
/// Panics if the `RwLock` cannot be locked.
|
||||||
///
|
///
|
||||||
/// Usage:
|
/// # Usage:
|
||||||
///
|
///
|
||||||
/// ```rs
|
/// ```rs
|
||||||
/// let val = read_lock!(my_rwlock);
|
/// let val = read_lock!(my_rwlock);
|
||||||
|
@ -77,7 +77,7 @@ macro_rules! read_lock {
|
||||||
/// Gets a write lock on a `RwLock`.
|
/// Gets a write lock on a `RwLock`.
|
||||||
/// Panics if the `RwLock` cannot be locked.
|
/// Panics if the `RwLock` cannot be locked.
|
||||||
///
|
///
|
||||||
/// Usage:
|
/// # Usage:
|
||||||
///
|
///
|
||||||
/// ```rs
|
/// ```rs
|
||||||
/// let mut val = write_lock!(my_rwlock);
|
/// let mut val = write_lock!(my_rwlock);
|
||||||
|
@ -88,3 +88,33 @@ macro_rules! write_lock {
|
||||||
$rwlock.write().expect($crate::error::ERR_WRITE_LOCK)
|
$rwlock.write().expect($crate::error::ERR_WRITE_LOCK)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wraps `val` in a new `Arc<Mutex<T>>`.
|
||||||
|
///
|
||||||
|
/// # Usage:
|
||||||
|
///
|
||||||
|
/// ```rs
|
||||||
|
/// let val = arc_mut!(MyService::new());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! arc_mut {
|
||||||
|
($val:expr) => {
|
||||||
|
std::sync::Arc::new(std::Sync::Mutex::new($val))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps `val` in a new `Arc<RwLock<T>>`.
|
||||||
|
///
|
||||||
|
/// # Usage:
|
||||||
|
///
|
||||||
|
/// ```rs
|
||||||
|
/// let val = arc_rw!(MyService::new());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! arc_rw {
|
||||||
|
($val:expr) => {
|
||||||
|
std::sync::Arc::new(std::sync::RwLock::new($val))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
16
src/main.rs
16
src/main.rs
|
@ -7,12 +7,14 @@ mod cli;
|
||||||
mod clients;
|
mod clients;
|
||||||
mod config;
|
mod config;
|
||||||
mod desktop_file;
|
mod desktop_file;
|
||||||
mod dynamic_string;
|
mod dynamic_value;
|
||||||
mod error;
|
mod error;
|
||||||
mod gtk_helpers;
|
mod gtk_helpers;
|
||||||
mod image;
|
mod image;
|
||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
mod ipc;
|
mod ipc;
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
mod ironvar;
|
||||||
mod logging;
|
mod logging;
|
||||||
mod macros;
|
mod macros;
|
||||||
mod modules;
|
mod modules;
|
||||||
|
@ -119,7 +121,7 @@ async fn start_ironbar() {
|
||||||
ConfigLoader::load,
|
ConfigLoader::load,
|
||||||
);
|
);
|
||||||
|
|
||||||
let config = match config_res {
|
let mut config: Config = match config_res {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("{:?}", err);
|
error!("{:?}", err);
|
||||||
|
@ -129,6 +131,16 @@ async fn start_ironbar() {
|
||||||
|
|
||||||
debug!("Loaded config file");
|
debug!("Loaded config file");
|
||||||
|
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
if let Some(ironvars) = config.ironvar_defaults.take() {
|
||||||
|
let variable_manager = ironvar::get_variable_manager();
|
||||||
|
for (k, v) in ironvars {
|
||||||
|
if write_lock!(variable_manager).set(k.clone(), v).is_err() {
|
||||||
|
tracing::warn!("Ignoring invalid ironvar: '{k}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(err) = create_bars(app, &display, wayland_client, &config) {
|
if let Err(err) = create_bars(app, &display, wayland_client, &config) {
|
||||||
error!("{:?}", err);
|
error!("{:?}", err);
|
||||||
exit(ExitCode::CreateBars as i32);
|
exit(ExitCode::CreateBars as i32);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::{CustomWidget, CustomWidgetContext, ExecEvent};
|
use super::{CustomWidget, CustomWidgetContext, ExecEvent};
|
||||||
use crate::dynamic_string::DynamicString;
|
use crate::dynamic_value::dynamic_string;
|
||||||
use crate::popup::Popup;
|
use crate::popup::Popup;
|
||||||
use crate::{build, try_send};
|
use crate::{build, try_send};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
@ -25,7 +25,7 @@ impl CustomWidget for ButtonWidget {
|
||||||
label.set_use_markup(true);
|
label.set_use_markup(true);
|
||||||
button.add(&label);
|
button.add(&label);
|
||||||
|
|
||||||
DynamicString::new(&text, move |string| {
|
dynamic_string(&text, move |string| {
|
||||||
label.set_markup(&string);
|
label.set_markup(&string);
|
||||||
Continue(true)
|
Continue(true)
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{CustomWidget, CustomWidgetContext};
|
use super::{CustomWidget, CustomWidgetContext};
|
||||||
use crate::build;
|
use crate::build;
|
||||||
use crate::dynamic_string::DynamicString;
|
use crate::dynamic_value::dynamic_string;
|
||||||
use crate::image::ImageProvider;
|
use crate::image::ImageProvider;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::Image;
|
use gtk::Image;
|
||||||
|
@ -29,7 +29,7 @@ impl CustomWidget for ImageWidget {
|
||||||
let gtk_image = gtk_image.clone();
|
let gtk_image = gtk_image.clone();
|
||||||
let icon_theme = context.icon_theme.clone();
|
let icon_theme = context.icon_theme.clone();
|
||||||
|
|
||||||
DynamicString::new(&self.src, move |src| {
|
dynamic_string(&self.src, move |src| {
|
||||||
ImageProvider::parse(&src, &icon_theme, self.size)
|
ImageProvider::parse(&src, &icon_theme, self.size)
|
||||||
.map(|image| image.load_into_image(gtk_image.clone()));
|
.map(|image| image.load_into_image(gtk_image.clone()));
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{CustomWidget, CustomWidgetContext};
|
use super::{CustomWidget, CustomWidgetContext};
|
||||||
use crate::build;
|
use crate::build;
|
||||||
use crate::dynamic_string::DynamicString;
|
use crate::dynamic_value::dynamic_string;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::Label;
|
use gtk::Label;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -22,7 +22,7 @@ impl CustomWidget for LabelWidget {
|
||||||
|
|
||||||
{
|
{
|
||||||
let label = label.clone();
|
let label = label.clone();
|
||||||
DynamicString::new(&self.label, move |string| {
|
dynamic_string(&self.label, move |string| {
|
||||||
label.set_markup(&string);
|
label.set_markup(&string);
|
||||||
Continue(true)
|
Continue(true)
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::{try_get_orientation, CustomWidget, CustomWidgetContext};
|
use super::{try_get_orientation, CustomWidget, CustomWidgetContext};
|
||||||
use crate::dynamic_string::DynamicString;
|
use crate::dynamic_value::dynamic_string;
|
||||||
use crate::modules::custom::set_length;
|
use crate::modules::custom::set_length;
|
||||||
use crate::script::{OutputStream, Script, ScriptInput};
|
use crate::script::{OutputStream, Script, ScriptInput};
|
||||||
use crate::{build, send};
|
use crate::{build, send};
|
||||||
|
@ -69,7 +69,7 @@ impl CustomWidget for ProgressWidget {
|
||||||
let progress = progress.clone();
|
let progress = progress.clone();
|
||||||
progress.set_show_text(true);
|
progress.set_show_text(true);
|
||||||
|
|
||||||
DynamicString::new(&text, move |string| {
|
dynamic_string(&text, move |string| {
|
||||||
progress.set_text(Some(&string));
|
progress.set_text(Some(&string));
|
||||||
Continue(true)
|
Continue(true)
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::dynamic_string::DynamicString;
|
use crate::dynamic_value::dynamic_string;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||||
use crate::try_send;
|
use crate::try_send;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
@ -31,7 +31,7 @@ impl Module<Label> for LabelModule {
|
||||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||||
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
DynamicString::new(&self.label, move |string| {
|
dynamic_string(&self.label, move |string| {
|
||||||
try_send!(tx, ModuleUpdateEvent::Update(string));
|
try_send!(tx, ModuleUpdateEvent::Update(string));
|
||||||
Continue(true)
|
Continue(true)
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue