1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-07-01 10:41:03 +02:00

feat(custom): ability to embed scripts in labels for dynamic content

Fully resolves #34.
This commit is contained in:
Jake Stanger 2022-11-28 22:27:31 +00:00
parent e274ba39cd
commit 5d153a02fc
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
6 changed files with 196 additions and 33 deletions

View file

@ -27,7 +27,7 @@ pub struct CommonConfig {
}
#[derive(Debug, Deserialize, Clone)]
#[serde(tag = "type", rename_all = "kebab-case")]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ModuleConfig {
Clock(ClockModule),
Mpd(MpdModule),
@ -48,7 +48,7 @@ pub enum MonitorConfig {
}
#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
#[serde(rename_all = "snake_case")]
pub enum BarPosition {
Top,
Bottom,

View file

@ -8,6 +8,7 @@ mod modules;
mod popup;
mod script;
mod style;
mod widgets;
use crate::bar::create_bar;
use crate::config::{Config, MonitorConfig};

View file

@ -4,6 +4,7 @@ use crate::config::CommonConfig;
use color_eyre::{Report, Result};
use crate::script::Script;
use gtk::prelude::*;
use crate::widgets::DynamicLabel;
use gtk::{Button, Label, Orientation};
use serde::Deserialize;
use tokio::spawn;
@ -48,7 +49,7 @@ pub struct Widget {
/// Supported GTK widget types
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
#[serde(rename_all = "snake_case")]
pub enum WidgetType {
Box,
Label,
@ -60,7 +61,7 @@ impl Widget {
fn add_to(self, parent: &gtk::Box, tx: Sender<ExecEvent>, bar_orientation: Orientation) {
match self.widget_type {
WidgetType::Box => parent.add(&self.into_box(&tx, bar_orientation)),
WidgetType::Label => parent.add(&self.into_label()),
WidgetType::Label => parent.add(&self.into_label().label),
WidgetType::Button => parent.add(&self.into_button(tx, bar_orientation)),
}
}
@ -94,7 +95,7 @@ impl Widget {
}
/// Creates a `gtk::Label` from this widget
fn into_label(self) -> Label {
fn into_label(self) -> DynamicLabel {
let mut builder = Label::builder().use_markup(true);
if let Some(name) = self.name {
@ -103,15 +104,13 @@ impl Widget {
let label = builder.build();
if let Some(text) = self.label {
label.set_markup(&text);
}
if let Some(class) = self.class {
label.style_context().add_class(&class);
}
label
let text = self.label.map_or_else(String::new, |text| text);
DynamicLabel::new(label, &text)
}
/// Creates a `gtk::Button` from this widget

View file

@ -0,0 +1,125 @@
use crate::script::{OutputStream, Script};
use gtk::prelude::*;
use indexmap::IndexMap;
use std::sync::{Arc, Mutex};
use tokio::spawn;
#[derive(Debug)]
enum DynamicLabelSegment {
Static(String),
Dynamic(Script),
}
pub struct DynamicLabel {
pub label: gtk::Label,
}
impl DynamicLabel {
pub fn new(label: gtk::Label, input: &str) -> Self {
let mut segments = vec![];
let mut chars = input.chars().collect::<Vec<_>>();
while !chars.is_empty() {
let char = &chars[..=1];
let (token, skip) = if let ['{', '{'] = char {
const SKIP_BRACKETS: usize = 4;
let str = chars
.iter()
.skip(2)
.enumerate()
.take_while(|(i, &c)| c != '}' && chars[i + 1] != '}')
.map(|(_, c)| c)
.collect::<String>();
let len = str.len();
(
DynamicLabelSegment::Dynamic(Script::from(str.as_str())),
len + SKIP_BRACKETS,
)
} else {
let str = chars
.iter()
.enumerate()
.take_while(|(i, &c)| !(c == '{' && chars[i + 1] == '{'))
.map(|(_, c)| c)
.collect::<String>();
let len = str.len();
(DynamicLabelSegment::Static(str), len)
};
assert_ne!(skip, 0);
segments.push(token);
chars.drain(..skip);
}
let label_parts = Arc::new(Mutex::new(IndexMap::new()));
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
for (i, segment) in segments.into_iter().enumerate() {
match segment {
DynamicLabelSegment::Static(str) => {
label_parts
.lock()
.expect("Failed to get lock on label parts")
.insert(i, str);
}
DynamicLabelSegment::Dynamic(script) => {
let tx = tx.clone();
let label_parts = label_parts.clone();
spawn(async move {
script
.run(|(out, _)| {
if let OutputStream::Stdout(out) = out {
label_parts
.lock()
.expect("Failed to get lock on label parts")
.insert(i, out);
tx.send(()).expect("Failed to send update");
}
})
.await;
});
}
}
}
tx.send(()).expect("Failed to send update");
{
let label = label.clone();
rx.attach(None, move |_| {
let new_label = label_parts
.lock()
.expect("Failed to get lock on label parts")
.iter()
.map(|(_, part)| part.as_str())
.collect::<String>();
label.set_label(new_label.as_str());
Continue(true)
});
}
Self { label }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test() {
gtk::init().unwrap();
let label = gtk::Label::new(None);
DynamicLabel::new(label, "Uptime: {{1000:uptime -p | cut -d ' ' -f2-}}");
}
}

3
src/widgets/mod.rs Normal file
View file

@ -0,0 +1,3 @@
mod dynamic_label;
pub use dynamic_label::DynamicLabel;