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