diff --git a/Cargo.lock b/Cargo.lock index a138550..51adf2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,17 +191,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -335,45 +324,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "clap" -version = "3.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" -dependencies = [ - "atty", - "bitflags", - "clap_derive", - "clap_lex", - "indexmap", - "once_cell", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_derive" -version = "3.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -411,17 +361,6 @@ dependencies = [ "tracing-error", ] -[[package]] -name = "colored" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" -dependencies = [ - "atty", - "lazy_static", - "winapi", -] - [[package]] name = "concurrent-queue" version = "1.2.4" @@ -437,23 +376,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" -[[package]] -name = "cornfig" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6981753b68f7642c3737b302cd37dee779189fcdad975a69d6a7bb165f134e" -dependencies = [ - "cfg-if", - "clap", - "colored", - "pest", - "pest_derive", - "serde", - "serde_json", - "serde_yaml", - "toml", -] - [[package]] name = "cpufeatures" version = "0.2.5" @@ -518,9 +440,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4" +checksum = "3f83d0ebf42c6eafb8d7c52f7e5f2d3003b89c7aa4fd2b79229209459a849af8" dependencies = [ "cc", "cxxbridge-flags", @@ -530,9 +452,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199" +checksum = "07d050484b55975889284352b0ffc2ecbda25c0c55978017c132b29ba0818a86" dependencies = [ "cc", "codespan-reporting", @@ -545,15 +467,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c" +checksum = "99d2199b00553eda8012dfec8d3b1c75fce747cf27c169a270b3b99e3448ab78" [[package]] name = "cxxbridge-macro" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea" +checksum = "dcb67a6de1f602736dd7eaead0080cf3435df806c61b24b13328db128c58868f" dependencies = [ "proc-macro2", "quote", @@ -762,15 +684,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "futures-channel" version = "0.3.24" @@ -1154,9 +1067,9 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" dependencies = [ "cxx", "cxx-build", @@ -1220,7 +1133,6 @@ dependencies = [ "async_once", "chrono", "color-eyre", - "cornfig", "derive_builder", "dirs", "futures-util", @@ -1228,6 +1140,7 @@ dependencies = [ "gtk", "gtk-layer-shell", "lazy_static", + "libcorn", "mpd_client", "notify", "regex", @@ -1267,9 +1180,9 @@ dependencies = [ [[package]] name = "kqueue" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6112e8f37b59803ac47a42d14f1f3a59bbf72fc6857ffc5be455e28a691f8e" +checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" dependencies = [ "kqueue-sys", "libc", @@ -1297,6 +1210,19 @@ version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" +[[package]] +name = "libcorn" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0cc0f2cad23e81a2f48e4b0142a9f278bc7369a98b04cd7cc190ede637085da" +dependencies = [ + "cfg-if", + "pest", + "pest_derive", + "serde", + "toml", +] + [[package]] name = "libloading" version = "0.7.3" @@ -1462,9 +1388,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a" dependencies = [ "bitflags", - "crossbeam-channel", "filetime", - "fsevent-sys", "inotify", "kqueue", "libc", @@ -1555,12 +1479,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "os_str_bytes" -version = "6.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" - [[package]] name = "overload" version = "0.1.1" @@ -1901,12 +1819,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - [[package]] name = "scopeguard" version = "1.1.0" @@ -2122,7 +2034,8 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "swayipc-async" version = "2.0.1" -source = "git+https://github.com/JakeStanger/swayipc-rs.git?branch=feat/derive-clone#d8322a5412b021d0c002506b92b83b91ffd238d7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812eaecd6f9e658587570dffe2768d7b58410cbcaad6579dd44414a890594f74" dependencies = [ "async-io", "async-pidfd", @@ -2134,8 +2047,9 @@ dependencies = [ [[package]] name = "swayipc-types" -version = "1.1.2" -source = "git+https://github.com/JakeStanger/swayipc-rs.git?branch=feat/derive-clone#d8322a5412b021d0c002506b92b83b91ffd238d7" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a558cee16f6eeb791722bbf060607cc53f7f9a47fe80055eca81bceebd34928e" dependencies = [ "serde", "serde_json", @@ -2204,12 +2118,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" - [[package]] name = "thiserror" version = "1.0.37" @@ -2577,7 +2485,6 @@ dependencies = [ "downcast-rs", "libc", "nix 0.24.2", - "scoped-tls", "wayland-commons", "wayland-scanner", "wayland-sys", @@ -2635,8 +2542,6 @@ version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" dependencies = [ - "dlib", - "lazy_static", "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index dc63f79..4809ff6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ description = "Customisable wlroots/sway bar" [dependencies] derive_builder = "0.11.2" gtk = "0.15.5" -gtk-layer-shell = "0.4.1" +gtk-layer-shell = "0.4.4" glib = "0.15.12" tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread", "time"] } tracing = "0.1.37" @@ -25,20 +25,17 @@ serde = { version = "1.0.141", features = ["derive"] } serde_json = "1.0.82" serde_yaml = "0.9.4" toml = "0.5.9" -cornfig = "0.3.0" +libcorn = "0.4.0" lazy_static = "1.4.0" async_once = "0.2.6" -regex = "1.6.0" +regex = { version = "1.6.0", default-features = false, features = ["std"] } stray = { version = "0.1.2" } dirs = "4.0.0" walkdir = "2.3.2" -notify = "5.0.0" +notify = { version = "5.0.0", default-features = false } mpd_client = "1.0.0" swayipc-async = { version = "2.0.1" } sysinfo = "0.26.4" wayland-client = "0.29.5" -wayland-protocols = { version = "0.29.5", features=["unstable_protocols", "client"] } -smithay-client-toolkit = "0.16.0" - -[patch.crates-io] -swayipc-async = { git = "https://github.com/JakeStanger/swayipc-rs.git", branch = "feat/derive-clone" } \ No newline at end of file +wayland-protocols = { version = "0.29.5", features = ["unstable_protocols", "client"] } +smithay-client-toolkit = { version = "0.16.0", default-features = false, features=["calloop"] } \ No newline at end of file diff --git a/examples/custom.corn b/examples/custom.corn new file mode 100644 index 0000000..41ad89b --- /dev/null +++ b/examples/custom.corn @@ -0,0 +1,25 @@ +let { + $power_menu = { + type = "custom" + class = "power-menu" + + bar = [ { type = "button" name="power-btn" label = "" exec = "popup:toggle" } ] + + popup = [ { + type = "box" + orientation = "vertical" + widgets = [ + { type = "label" name = "header" label = "Power menu" } + { + type = "box" + widgets = [ + { type = "button" class="power-btn" label = "" exec = "!shutdown now" } + { type = "button" class="power-btn" label = "" exec = "!reboot" } + ] + } + ] + } ] + } +} in { + end = [ { type = "clock" } $power_menu ] +} \ No newline at end of file diff --git a/src/bar.rs b/src/bar.rs index 7ef4078..0011f61 100644 --- a/src/bar.rs +++ b/src/bar.rs @@ -1,5 +1,6 @@ use crate::bridge_channel::BridgeChannel; use crate::config::{BarPosition, ModuleConfig}; +use crate::modules::custom::ExecEvent; use crate::modules::launcher::{ItemEvent, LauncherUpdate}; use crate::modules::mpd::{PlayerCommand, SongUpdate}; use crate::modules::workspaces::WorkspaceUpdate; @@ -236,6 +237,9 @@ fn add_modules( ModuleConfig::Launcher(module) => { add_module!(module, id, "launcher", LauncherUpdate, ItemEvent); } + ModuleConfig::Custom(module) => { + add_module!(module, id, "custom", (), ExecEvent); + } } } diff --git a/src/config.rs b/src/config.rs index d7b2460..7256289 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,5 @@ use crate::modules::clock::ClockModule; +use crate::modules::custom::CustomModule; use crate::modules::focused::FocusedModule; use crate::modules::launcher::LauncherModule; use crate::modules::mpd::MpdModule; @@ -28,6 +29,7 @@ pub enum ModuleConfig { Launcher(LauncherModule), Script(ScriptModule), Focused(FocusedModule), + Custom(CustomModule), } #[derive(Debug, Deserialize, Clone)] @@ -167,7 +169,7 @@ impl Config { // so serialize the interpreted result then deserialize that let file = String::from_utf8(file).wrap_err("Config file contains invalid UTF-8")?; - let config = cornfig::parse(&file).wrap_err("Invalid corn config")?.value; + let config = libcorn::parse(&file).wrap_err("Invalid corn config")?.value; Ok(serde_json::from_str(&serde_json::to_string(&config)?)?) } _ => unreachable!(), diff --git a/src/main.rs b/src/main.rs index 5e7e3b4..4d656ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod icon; mod logging; mod modules; mod popup; +mod script; mod style; mod sway; mod wayland; diff --git a/src/modules/custom.rs b/src/modules/custom.rs new file mode 100644 index 0000000..f284cca --- /dev/null +++ b/src/modules/custom.rs @@ -0,0 +1,241 @@ +use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; +use crate::popup::{ButtonGeometry, Popup}; +use crate::script::exec_command; +use color_eyre::{Report, Result}; +use gtk::prelude::*; +use gtk::{Button, Label, Orientation}; +use serde::Deserialize; +use tokio::spawn; +use tokio::sync::mpsc::{Receiver, Sender}; +use tracing::{debug, error}; + +#[derive(Debug, Deserialize, Clone)] +pub struct CustomModule { + /// Container class name + class: Option, + /// Widgets to add to the bar container + bar: Vec, + /// Widgets to add to the popup container + popup: Option>, +} + +/// Attempts to parse an `Orientation` from `String` +fn try_get_orientation(orientation: String) -> Result { + match orientation.to_lowercase().as_str() { + "horizontal" | "h" => Ok(Orientation::Horizontal), + "vertical" | "v" => Ok(Orientation::Vertical), + _ => Err(Report::msg("Invalid orientation string in config")), + } +} + +/// Widget attributes +#[derive(Debug, Deserialize, Clone)] +pub struct Widget { + /// Type of GTK widget to add + #[serde(rename = "type")] + widget_type: WidgetType, + widgets: Option>, + label: Option, + name: Option, + class: Option, + exec: Option, + orientation: Option, +} + +/// Supported GTK widget types +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "kebab-case")] +pub enum WidgetType { + Box, + Label, + Button, +} + +impl Widget { + /// Creates this widget and adds it to the parent container + fn add_to(self, parent: >k::Box, tx: Sender, 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::Button => parent.add(&self.into_button(tx, bar_orientation)), + } + } + + /// Creates a `gtk::Box` from this widget + fn into_box(self, tx: Sender, bar_orientation: Orientation) -> gtk::Box { + let mut builder = gtk::Box::builder(); + + if let Some(name) = self.name { + builder = builder.name(&name); + } + + if let Some(orientation) = self.orientation { + builder = builder + .orientation(try_get_orientation(orientation).unwrap_or(Orientation::Horizontal)); + } + + let container = builder.build(); + + if let Some(class) = self.class { + container.style_context().add_class(&class); + } + + if let Some(widgets) = self.widgets { + widgets + .into_iter() + .for_each(|widget| widget.add_to(&container, tx.clone(), bar_orientation)) + } + + container + } + + /// Creates a `gtk::Label` from this widget + fn into_label(self) -> Label { + let mut builder = Label::builder().use_markup(true); + + if let Some(name) = self.name { + builder = builder.name(&name); + } + + 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 + } + + /// Creates a `gtk::Button` from this widget + fn into_button(self, tx: Sender, bar_orientation: Orientation) -> Button { + let mut builder = Button::builder(); + + if let Some(name) = self.name { + builder = builder.name(&name); + } + + let button = builder.build(); + + if let Some(text) = self.label { + let label = Label::new(None); + label.set_use_markup(true); + label.set_markup(&text); + button.add(&label); + } + + if let Some(class) = self.class { + button.style_context().add_class(&class); + } + + if let Some(exec) = self.exec { + button.connect_clicked(move |button| { + tx.try_send(ExecEvent { + cmd: exec.clone(), + geometry: Popup::button_pos(button, bar_orientation), + }) + .expect("Failed to send exec message"); + }); + } + + button + } +} + +#[derive(Debug)] +pub struct ExecEvent { + cmd: String, + geometry: ButtonGeometry, +} + +impl Module for CustomModule { + type SendMessage = (); + type ReceiveMessage = ExecEvent; + + fn spawn_controller( + &self, + _info: &ModuleInfo, + tx: Sender>, + mut rx: Receiver, + ) -> Result<()> { + spawn(async move { + while let Some(event) = rx.recv().await { + println!("{:?}", event); + if event.cmd.starts_with('!') { + debug!("executing command: '{}'", &event.cmd[1..]); + if let Err(err) = exec_command(&event.cmd[1..]) { + error!("{err:?}"); + } + } else if event.cmd == "popup:toggle" { + tx.send(ModuleUpdateEvent::TogglePopup(event.geometry)) + .await + .expect("Failed to send open popup event"); + } else if event.cmd == "popup:open" { + tx.send(ModuleUpdateEvent::OpenPopup(event.geometry)) + .await + .expect("Failed to send open popup event"); + } else if event.cmd == "popup:close" { + tx.send(ModuleUpdateEvent::ClosePopup) + .await + .expect("Failed to send open popup event"); + } else { + error!("Received invalid command: '{}'", event.cmd); + } + } + }); + + Ok(()) + } + + fn into_widget( + self, + context: WidgetContext, + info: &ModuleInfo, + ) -> Result> { + let orientation = info.bar_position.get_orientation(); + let container = gtk::Box::builder().orientation(orientation).build(); + + if let Some(ref class) = self.class { + container.style_context().add_class(class) + } + + self.bar.clone().into_iter().for_each(|widget| { + widget.add_to(&container, context.controller_tx.clone(), orientation) + }); + + let popup = self.into_popup(context.controller_tx, context.popup_rx); + + Ok(ModuleWidget { + widget: container, + popup, + }) + } + + fn into_popup( + self, + tx: Sender, + _rx: glib::Receiver, + ) -> Option + where + Self: Sized, + { + let container = gtk::Box::builder().name("popup-custom").build(); + + if let Some(class) = self.class { + container + .style_context() + .add_class(format!("popup-{class}").as_str()) + } + + if let Some(popup) = self.popup { + popup + .into_iter() + .for_each(|widget| widget.add_to(&container, tx.clone(), Orientation::Horizontal)); + } + + Some(container) + } +} diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 037e0d2..69f16e1 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -5,6 +5,7 @@ /// Clicking the widget opens a popup containing the current time /// with second-level precision and a calendar. pub mod clock; +pub mod custom; pub mod focused; pub mod launcher; pub mod mpd; diff --git a/src/modules/script.rs b/src/modules/script.rs index 280c66b..49a85e3 100644 --- a/src/modules/script.rs +++ b/src/modules/script.rs @@ -1,13 +1,13 @@ use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; -use color_eyre::{eyre::Report, eyre::Result, eyre::WrapErr, Section}; +use crate::script::exec_command; +use color_eyre::Result; use gtk::prelude::*; use gtk::Label; use serde::Deserialize; -use std::process::Command; use tokio::spawn; use tokio::sync::mpsc::{Receiver, Sender}; use tokio::time::sleep; -use tracing::{error, instrument}; +use tracing::error; #[derive(Debug, Deserialize, Clone)] pub struct ScriptModule { @@ -37,7 +37,7 @@ impl Module