mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-07-01 10:41:03 +02:00
refactor: move most of the horrible add_module
macro content into proper functions
This commit is contained in:
parent
490f3f3f65
commit
2c1b2924d4
15 changed files with 317 additions and 344 deletions
85
Cargo.lock
generated
85
Cargo.lock
generated
|
@ -493,41 +493,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "darling"
|
|
||||||
version = "0.14.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
|
|
||||||
dependencies = [
|
|
||||||
"darling_core",
|
|
||||||
"darling_macro",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "darling_core"
|
|
||||||
version = "0.14.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
|
|
||||||
dependencies = [
|
|
||||||
"fnv",
|
|
||||||
"ident_case",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"strsim",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "darling_macro"
|
|
||||||
version = "0.14.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
|
|
||||||
dependencies = [
|
|
||||||
"darling_core",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derivative"
|
name = "derivative"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
@ -539,37 +504,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "derive_builder"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3"
|
|
||||||
dependencies = [
|
|
||||||
"derive_builder_macro",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "derive_builder_core"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4"
|
|
||||||
dependencies = [
|
|
||||||
"darling",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "derive_builder_macro"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68"
|
|
||||||
dependencies = [
|
|
||||||
"derive_builder_core",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
|
@ -689,12 +623,6 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fnv"
|
|
||||||
version = "1.0.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.25"
|
version = "0.3.25"
|
||||||
|
@ -1091,12 +1019,6 @@ dependencies = [
|
||||||
"cxx-build",
|
"cxx-build",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ident_case"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indenter"
|
name = "indenter"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -1149,7 +1071,6 @@ dependencies = [
|
||||||
"async_once",
|
"async_once",
|
||||||
"chrono",
|
"chrono",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"derive_builder",
|
|
||||||
"dirs",
|
"dirs",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"glib",
|
"glib",
|
||||||
|
@ -2064,12 +1985,6 @@ dependencies = [
|
||||||
"vte",
|
"vte",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swayipc-async"
|
name = "swayipc-async"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
|
|
|
@ -6,7 +6,6 @@ license = "MIT"
|
||||||
description = "Customisable GTK Layer Shell wlroots/sway bar"
|
description = "Customisable GTK Layer Shell wlroots/sway bar"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
derive_builder = "0.11.2"
|
|
||||||
gtk = "0.16.0"
|
gtk = "0.16.0"
|
||||||
gtk-layer-shell = "0.5.0"
|
gtk-layer-shell = "0.5.0"
|
||||||
glib = "0.16.2"
|
glib = "0.16.2"
|
||||||
|
|
505
src/bar.rs
505
src/bar.rs
|
@ -1,23 +1,15 @@
|
||||||
use crate::bridge_channel::BridgeChannel;
|
use crate::bridge_channel::BridgeChannel;
|
||||||
use crate::config::{BarPosition, ModuleConfig};
|
use crate::config::{BarPosition, CommonConfig, ModuleConfig};
|
||||||
use crate::dynamic_string::DynamicString;
|
use crate::dynamic_string::DynamicString;
|
||||||
use crate::modules::custom::ExecEvent;
|
use crate::modules::{Module, ModuleInfo, ModuleLocation, ModuleUpdateEvent, WidgetContext};
|
||||||
use crate::modules::launcher::{ItemEvent, LauncherUpdate};
|
|
||||||
use crate::modules::mpd::{PlayerCommand, SongUpdate};
|
|
||||||
use crate::modules::workspaces::WorkspaceUpdate;
|
|
||||||
use crate::modules::{Module, ModuleInfoBuilder, ModuleLocation, ModuleUpdateEvent, WidgetContext};
|
|
||||||
use crate::popup::Popup;
|
use crate::popup::Popup;
|
||||||
use crate::script::{OutputStream, Script};
|
use crate::script::{OutputStream, Script};
|
||||||
use crate::{await_sync, Config};
|
use crate::{await_sync, Config};
|
||||||
use chrono::{DateTime, Local};
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use gtk::gdk::Monitor;
|
use gtk::gdk::Monitor;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Application, ApplicationWindow, Orientation};
|
use gtk::{Application, ApplicationWindow, EventBox, Orientation, Widget};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use stray::message::NotifierItemCommand;
|
|
||||||
use stray::NotifierItemMessage;
|
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::{debug, error, info, trace};
|
use tracing::{debug, error, info, trace};
|
||||||
|
@ -49,26 +41,11 @@ pub fn create_bar(
|
||||||
}
|
}
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let start = gtk::Box::builder()
|
|
||||||
.orientation(orientation)
|
|
||||||
.spacing(0)
|
|
||||||
.name("start")
|
|
||||||
.build();
|
|
||||||
let center = gtk::Box::builder()
|
|
||||||
.orientation(orientation)
|
|
||||||
.spacing(0)
|
|
||||||
.name("center")
|
|
||||||
.build();
|
|
||||||
let end = gtk::Box::builder()
|
|
||||||
.orientation(orientation)
|
|
||||||
.spacing(0)
|
|
||||||
.name("end")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
content.style_context().add_class("container");
|
content.style_context().add_class("container");
|
||||||
start.style_context().add_class("container");
|
|
||||||
center.style_context().add_class("container");
|
let start = create_container("start", orientation);
|
||||||
end.style_context().add_class("container");
|
let center = create_container("center", orientation);
|
||||||
|
let end = create_container("end", orientation);
|
||||||
|
|
||||||
content.add(&start);
|
content.add(&start);
|
||||||
content.set_center_widget(Some(¢er));
|
content.set_center_widget(Some(¢er));
|
||||||
|
@ -84,6 +61,9 @@ pub fn create_bar(
|
||||||
});
|
});
|
||||||
|
|
||||||
debug!("Showing bar");
|
debug!("Showing bar");
|
||||||
|
|
||||||
|
// show each box but do not use `show_all`.
|
||||||
|
// this ensures `show_if` option works as intended.
|
||||||
start.show();
|
start.show();
|
||||||
center.show();
|
center.show();
|
||||||
end.show();
|
end.show();
|
||||||
|
@ -93,218 +73,6 @@ pub fn create_bar(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the configured modules onto a bar.
|
|
||||||
fn load_modules(
|
|
||||||
left: >k::Box,
|
|
||||||
center: >k::Box,
|
|
||||||
right: >k::Box,
|
|
||||||
app: &Application,
|
|
||||||
config: Config,
|
|
||||||
monitor: &Monitor,
|
|
||||||
output_name: &str,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut info_builder = ModuleInfoBuilder::default();
|
|
||||||
let info_builder = info_builder
|
|
||||||
.app(app)
|
|
||||||
.bar_position(config.position)
|
|
||||||
.monitor(monitor)
|
|
||||||
.output_name(output_name);
|
|
||||||
|
|
||||||
if let Some(modules) = config.start {
|
|
||||||
let info_builder = info_builder.location(ModuleLocation::Left);
|
|
||||||
|
|
||||||
add_modules(left, modules, info_builder)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(modules) = config.center {
|
|
||||||
let info_builder = info_builder.location(ModuleLocation::Center);
|
|
||||||
|
|
||||||
add_modules(center, modules, info_builder)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(modules) = config.end {
|
|
||||||
let info_builder = info_builder.location(ModuleLocation::Right);
|
|
||||||
|
|
||||||
add_modules(right, modules, info_builder)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds modules into a provided GTK box,
|
|
||||||
/// which should be one of its left, center or right containers.
|
|
||||||
fn add_modules(
|
|
||||||
content: >k::Box,
|
|
||||||
modules: Vec<ModuleConfig>,
|
|
||||||
info_builder: &mut ModuleInfoBuilder,
|
|
||||||
) -> Result<()> {
|
|
||||||
let base_popup_info = info_builder.module_name("").build()?;
|
|
||||||
let popup = Popup::new(&base_popup_info);
|
|
||||||
let popup = Arc::new(RwLock::new(popup));
|
|
||||||
|
|
||||||
macro_rules! add_module {
|
|
||||||
($module:expr, $id:expr, $name:literal, $send_message:ty, $receive_message:ty) => {
|
|
||||||
let info = info_builder.module_name($name).build()?;
|
|
||||||
|
|
||||||
let (w_tx, w_rx) = glib::MainContext::channel::<$send_message>(glib::PRIORITY_DEFAULT);
|
|
||||||
let (p_tx, p_rx) = glib::MainContext::channel::<$send_message>(glib::PRIORITY_DEFAULT);
|
|
||||||
|
|
||||||
let channel = BridgeChannel::<ModuleUpdateEvent<$send_message>>::new();
|
|
||||||
let (ui_tx, ui_rx) = mpsc::channel::<$receive_message>(16);
|
|
||||||
|
|
||||||
$module.spawn_controller(&info, channel.create_sender(), ui_rx)?;
|
|
||||||
|
|
||||||
let context = WidgetContext {
|
|
||||||
id: $id,
|
|
||||||
widget_rx: w_rx,
|
|
||||||
popup_rx: p_rx,
|
|
||||||
tx: channel.create_sender(),
|
|
||||||
controller_tx: ui_tx,
|
|
||||||
};
|
|
||||||
|
|
||||||
let common = $module.common.clone();
|
|
||||||
|
|
||||||
let widget = $module.into_widget(context, &info)?;
|
|
||||||
|
|
||||||
let container = gtk::EventBox::new();
|
|
||||||
container.add(&widget.widget);
|
|
||||||
|
|
||||||
content.add(&container);
|
|
||||||
widget.widget.set_widget_name(info.module_name);
|
|
||||||
|
|
||||||
if let Some(show_if) = common.show_if {
|
|
||||||
let script = Script::new_polling(show_if);
|
|
||||||
let container = container.clone();
|
|
||||||
|
|
||||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
|
||||||
|
|
||||||
spawn(async move {
|
|
||||||
script
|
|
||||||
.run(|(_, success)| {
|
|
||||||
tx.send(success)
|
|
||||||
.expect("Failed to send widget visibility toggle message");
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
});
|
|
||||||
|
|
||||||
rx.attach(None, move |success| {
|
|
||||||
if success {
|
|
||||||
container.show_all()
|
|
||||||
} else {
|
|
||||||
container.hide()
|
|
||||||
};
|
|
||||||
Continue(true)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
container.show_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(on_click) = common.on_click {
|
|
||||||
let script = Script::new_polling(on_click);
|
|
||||||
container.connect_button_press_event(move |_, _| {
|
|
||||||
trace!("Running on-click script");
|
|
||||||
match await_sync(async { script.get_output().await }) {
|
|
||||||
Ok((OutputStream::Stderr(out), _)) => error!("{out}"),
|
|
||||||
Err(err) => error!("{err:?}"),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Inhibit(false)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(tooltip) = common.tooltip {
|
|
||||||
DynamicString::new(&tooltip, move |string| {
|
|
||||||
container.set_tooltip_text(Some(&string));
|
|
||||||
Continue(true)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let has_popup = widget.popup.is_some();
|
|
||||||
if let Some(popup_content) = widget.popup {
|
|
||||||
popup
|
|
||||||
.write()
|
|
||||||
.expect("Failed to get write lock on popup")
|
|
||||||
.register_content($id, popup_content);
|
|
||||||
}
|
|
||||||
|
|
||||||
let popup2 = Arc::clone(&popup);
|
|
||||||
channel.recv(move |ev| {
|
|
||||||
let popup = popup2.clone();
|
|
||||||
match ev {
|
|
||||||
ModuleUpdateEvent::Update(update) => {
|
|
||||||
if has_popup {
|
|
||||||
p_tx.send(update.clone())
|
|
||||||
.expect("Failed to send update to popup");
|
|
||||||
}
|
|
||||||
|
|
||||||
w_tx.send(update).expect("Failed to send update to module");
|
|
||||||
}
|
|
||||||
ModuleUpdateEvent::TogglePopup(geometry) => {
|
|
||||||
debug!("Toggling popup for {} [#{}]", $name, $id);
|
|
||||||
let popup = popup.read().expect("Failed to get read lock on popup");
|
|
||||||
if popup.is_visible() {
|
|
||||||
popup.hide()
|
|
||||||
} else {
|
|
||||||
popup.show_content($id);
|
|
||||||
popup.show(geometry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ModuleUpdateEvent::OpenPopup(geometry) => {
|
|
||||||
debug!("Opening popup for {} [#{}]", $name, $id);
|
|
||||||
|
|
||||||
let popup = popup.read().expect("Failed to get read lock on popup");
|
|
||||||
popup.hide();
|
|
||||||
popup.show_content($id);
|
|
||||||
popup.show(geometry);
|
|
||||||
}
|
|
||||||
ModuleUpdateEvent::ClosePopup => {
|
|
||||||
debug!("Closing popup for {} [#{}]", $name, $id);
|
|
||||||
|
|
||||||
let popup = popup.read().expect("Failed to get read lock on popup");
|
|
||||||
popup.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Continue(true)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (id, config) in modules.into_iter().enumerate() {
|
|
||||||
match config {
|
|
||||||
ModuleConfig::Clock(module) => {
|
|
||||||
add_module!(module, id, "clock", DateTime<Local>, ());
|
|
||||||
}
|
|
||||||
ModuleConfig::Script(module) => {
|
|
||||||
add_module!(module, id, "script", String, ());
|
|
||||||
}
|
|
||||||
ModuleConfig::SysInfo(module) => {
|
|
||||||
add_module!(module, id, "sysinfo", HashMap<String, String>, ());
|
|
||||||
}
|
|
||||||
ModuleConfig::Focused(module) => {
|
|
||||||
add_module!(module, id, "focused", (String, String), ());
|
|
||||||
}
|
|
||||||
ModuleConfig::Workspaces(module) => {
|
|
||||||
add_module!(module, id, "workspaces", WorkspaceUpdate, String);
|
|
||||||
}
|
|
||||||
ModuleConfig::Tray(module) => {
|
|
||||||
add_module!(module, id, "tray", NotifierItemMessage, NotifierItemCommand);
|
|
||||||
}
|
|
||||||
ModuleConfig::Mpd(module) => {
|
|
||||||
add_module!(module, id, "mpd", Option<SongUpdate>, PlayerCommand);
|
|
||||||
}
|
|
||||||
ModuleConfig::Launcher(module) => {
|
|
||||||
add_module!(module, id, "launcher", LauncherUpdate, ItemEvent);
|
|
||||||
}
|
|
||||||
ModuleConfig::Custom(module) => {
|
|
||||||
add_module!(module, id, "custom", (), ExecEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets up GTK layer shell for a provided application window.
|
/// Sets up GTK layer shell for a provided application window.
|
||||||
fn setup_layer_shell(
|
fn setup_layer_shell(
|
||||||
win: &ApplicationWindow,
|
win: &ApplicationWindow,
|
||||||
|
@ -349,3 +117,256 @@ fn setup_layer_shell(
|
||||||
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
|
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a `gtk::Box` container to place widgets inside.
|
||||||
|
fn create_container(name: &str, orientation: Orientation) -> gtk::Box {
|
||||||
|
let container = gtk::Box::builder()
|
||||||
|
.orientation(orientation)
|
||||||
|
.spacing(0)
|
||||||
|
.name(name)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
container.style_context().add_class("container");
|
||||||
|
container
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads the configured modules onto a bar.
|
||||||
|
fn load_modules(
|
||||||
|
left: >k::Box,
|
||||||
|
center: >k::Box,
|
||||||
|
right: >k::Box,
|
||||||
|
app: &Application,
|
||||||
|
config: Config,
|
||||||
|
monitor: &Monitor,
|
||||||
|
output_name: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
macro_rules! info {
|
||||||
|
($location:expr) => {
|
||||||
|
ModuleInfo {
|
||||||
|
app,
|
||||||
|
bar_position: config.position,
|
||||||
|
monitor,
|
||||||
|
output_name,
|
||||||
|
location: $location,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(modules) = config.start {
|
||||||
|
let info = info!(ModuleLocation::Left);
|
||||||
|
add_modules(left, modules, &info)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(modules) = config.center {
|
||||||
|
let info = info!(ModuleLocation::Center);
|
||||||
|
add_modules(center, modules, &info)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(modules) = config.end {
|
||||||
|
let info = info!(ModuleLocation::Right);
|
||||||
|
add_modules(right, modules, &info)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds modules into a provided GTK box,
|
||||||
|
/// which should be one of its left, center or right containers.
|
||||||
|
fn add_modules(content: >k::Box, modules: Vec<ModuleConfig>, info: &ModuleInfo) -> Result<()> {
|
||||||
|
let popup = Popup::new(info);
|
||||||
|
let popup = Arc::new(RwLock::new(popup));
|
||||||
|
|
||||||
|
macro_rules! add_module {
|
||||||
|
($module:expr, $id:expr) => {{
|
||||||
|
let common = $module.common.take().expect("Common config did not exist");
|
||||||
|
let widget = create_module($module, $id, &info, &Arc::clone(&popup))?;
|
||||||
|
|
||||||
|
let container = wrap_widget(&widget);
|
||||||
|
content.add(&container);
|
||||||
|
setup_module_common_options(container, common);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, config) in modules.into_iter().enumerate() {
|
||||||
|
match config {
|
||||||
|
ModuleConfig::Clock(mut module) => add_module!(module, id),
|
||||||
|
ModuleConfig::Script(mut module) => add_module!(module, id),
|
||||||
|
ModuleConfig::SysInfo(mut module) => add_module!(module, id),
|
||||||
|
ModuleConfig::Focused(mut module) => add_module!(module, id),
|
||||||
|
ModuleConfig::Workspaces(mut module) => add_module!(module, id),
|
||||||
|
ModuleConfig::Tray(mut module) => add_module!(module, id),
|
||||||
|
ModuleConfig::Mpd(mut module) => add_module!(module, id),
|
||||||
|
ModuleConfig::Launcher(mut module) => add_module!(module, id),
|
||||||
|
ModuleConfig::Custom(mut module) => add_module!(module, id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a module and sets it up.
|
||||||
|
/// This setup includes widget/popup content and event channels.
|
||||||
|
fn create_module<TModule, TWidget, TSend, TRec>(
|
||||||
|
module: TModule,
|
||||||
|
id: usize,
|
||||||
|
info: &ModuleInfo,
|
||||||
|
popup: &Arc<RwLock<Popup>>,
|
||||||
|
) -> Result<TWidget>
|
||||||
|
where
|
||||||
|
TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRec>,
|
||||||
|
TWidget: IsA<Widget>,
|
||||||
|
TSend: Clone + Send + 'static,
|
||||||
|
{
|
||||||
|
let (w_tx, w_rx) = glib::MainContext::channel::<TSend>(glib::PRIORITY_DEFAULT);
|
||||||
|
let (p_tx, p_rx) = glib::MainContext::channel::<TSend>(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
|
let channel = BridgeChannel::<ModuleUpdateEvent<TSend>>::new();
|
||||||
|
let (ui_tx, ui_rx) = mpsc::channel::<TRec>(16);
|
||||||
|
|
||||||
|
module.spawn_controller(info, channel.create_sender(), ui_rx)?;
|
||||||
|
|
||||||
|
let context = WidgetContext {
|
||||||
|
id,
|
||||||
|
widget_rx: w_rx,
|
||||||
|
popup_rx: p_rx,
|
||||||
|
tx: channel.create_sender(),
|
||||||
|
controller_tx: ui_tx,
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = TModule::name();
|
||||||
|
|
||||||
|
let module_parts = module.into_widget(context, info)?;
|
||||||
|
module_parts.widget.set_widget_name(name);
|
||||||
|
|
||||||
|
let mut has_popup = false;
|
||||||
|
if let Some(popup_content) = module_parts.popup {
|
||||||
|
register_popup_content(popup, id, popup_content);
|
||||||
|
has_popup = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_receiver(channel, w_tx, p_tx, popup.clone(), name, id, has_popup);
|
||||||
|
|
||||||
|
Ok(module_parts.widget)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers the popup content with the popup.
|
||||||
|
fn register_popup_content(popup: &Arc<RwLock<Popup>>, id: usize, popup_content: gtk::Box) {
|
||||||
|
popup
|
||||||
|
.write()
|
||||||
|
.expect("Failed to get write lock on popup")
|
||||||
|
.register_content(id, popup_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets up the bridge channel receiver
|
||||||
|
/// to pick up events from the controller, widget or popup.
|
||||||
|
///
|
||||||
|
/// Handles opening/closing popups
|
||||||
|
/// and communicating update messages between controllers and widgets/popups.
|
||||||
|
fn setup_receiver<TSend>(
|
||||||
|
channel: BridgeChannel<ModuleUpdateEvent<TSend>>,
|
||||||
|
w_tx: glib::Sender<TSend>,
|
||||||
|
p_tx: glib::Sender<TSend>,
|
||||||
|
popup: Arc<RwLock<Popup>>,
|
||||||
|
name: &'static str,
|
||||||
|
id: usize,
|
||||||
|
has_popup: bool,
|
||||||
|
) where
|
||||||
|
TSend: Clone + Send + 'static,
|
||||||
|
{
|
||||||
|
channel.recv(move |ev| {
|
||||||
|
match ev {
|
||||||
|
ModuleUpdateEvent::Update(update) => {
|
||||||
|
if has_popup {
|
||||||
|
p_tx.send(update.clone())
|
||||||
|
.expect("Failed to send update to popup");
|
||||||
|
}
|
||||||
|
|
||||||
|
w_tx.send(update).expect("Failed to send update to widget");
|
||||||
|
}
|
||||||
|
ModuleUpdateEvent::TogglePopup(geometry) => {
|
||||||
|
debug!("Toggling popup for {} [#{}]", name, id);
|
||||||
|
let popup = popup.read().expect("Failed to get read lock on popup");
|
||||||
|
if popup.is_visible() {
|
||||||
|
popup.hide();
|
||||||
|
} else {
|
||||||
|
popup.show_content(id);
|
||||||
|
popup.show(geometry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModuleUpdateEvent::OpenPopup(geometry) => {
|
||||||
|
debug!("Opening popup for {} [#{}]", name, id);
|
||||||
|
|
||||||
|
let popup = popup.read().expect("Failed to get read lock on popup");
|
||||||
|
popup.hide();
|
||||||
|
popup.show_content(id);
|
||||||
|
popup.show(geometry);
|
||||||
|
}
|
||||||
|
ModuleUpdateEvent::ClosePopup => {
|
||||||
|
debug!("Closing popup for {} [#{}]", name, id);
|
||||||
|
|
||||||
|
let popup = popup.read().expect("Failed to get read lock on popup");
|
||||||
|
popup.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Continue(true)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a widget and adds it into a new `gtk::EventBox`.
|
||||||
|
/// The event box container is returned.
|
||||||
|
fn wrap_widget<W: IsA<Widget>>(widget: &W) -> EventBox {
|
||||||
|
let container = EventBox::new();
|
||||||
|
container.add(widget);
|
||||||
|
container
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configures the module's container according to the common config options.
|
||||||
|
fn setup_module_common_options(container: EventBox, common: CommonConfig) {
|
||||||
|
common.show_if.map_or_else(
|
||||||
|
|| {
|
||||||
|
container.show_all();
|
||||||
|
},
|
||||||
|
|show_if| {
|
||||||
|
let script = Script::new_polling(show_if);
|
||||||
|
let container = container.clone();
|
||||||
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
spawn(async move {
|
||||||
|
script
|
||||||
|
.run(|(_, success)| {
|
||||||
|
tx.send(success)
|
||||||
|
.expect("Failed to send widget visibility toggle message");
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
});
|
||||||
|
rx.attach(None, move |success| {
|
||||||
|
if success {
|
||||||
|
container.show_all();
|
||||||
|
} else {
|
||||||
|
container.hide();
|
||||||
|
};
|
||||||
|
Continue(true)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(on_click) = common.on_click {
|
||||||
|
let script = Script::new_polling(on_click);
|
||||||
|
container.connect_button_press_event(move |_, _| {
|
||||||
|
trace!("Running on-click script");
|
||||||
|
match await_sync(async { script.get_output().await }) {
|
||||||
|
Ok((OutputStream::Stderr(out), _)) => error!("{out}"),
|
||||||
|
Err(err) => error!("{err:?}"),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Inhibit(false)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tooltip) = common.tooltip {
|
||||||
|
DynamicString::new(&tooltip, move |string| {
|
||||||
|
container.set_tooltip_text(Some(&string));
|
||||||
|
Continue(true)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ const STATE_ACTIVE: u32 = 2;
|
||||||
const STATE_FULLSCREEN: u32 = 3;
|
const STATE_FULLSCREEN: u32 = 3;
|
||||||
|
|
||||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||||
|
|
||||||
fn get_id() -> usize {
|
fn get_id() -> usize {
|
||||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
@ -108,9 +109,9 @@ where
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::OutputEnter { output: _ } => None,
|
Event::OutputEnter { output: _ }
|
||||||
Event::OutputLeave { output: _ } => None,
|
| Event::OutputLeave { output: _ }
|
||||||
Event::Parent { parent: _ } => None,
|
| Event::Parent { parent: _ } => None,
|
||||||
Event::Done => {
|
Event::Done => {
|
||||||
if info.ready || info.app_id.is_empty() {
|
if info.ready || info.app_id.is_empty() {
|
||||||
None
|
None
|
||||||
|
@ -119,6 +120,7 @@ where
|
||||||
Some(ToplevelChange::New)
|
Some(ToplevelChange::New)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -178,6 +178,8 @@ fn create_bars(
|
||||||
/// Do note it must be called from within a Tokio runtime still.
|
/// Do note it must be called from within a Tokio runtime still.
|
||||||
///
|
///
|
||||||
/// Use sparingly! Prefer async functions wherever possible.
|
/// Use sparingly! Prefer async functions wherever possible.
|
||||||
|
///
|
||||||
|
/// TODO: remove all instances of this once async trait funcs are stable
|
||||||
pub fn await_sync<F: Future>(f: F) -> F::Output {
|
pub fn await_sync<F: Future>(f: F) -> F::Output {
|
||||||
block_in_place(|| Handle::current().block_on(f))
|
block_in_place(|| Handle::current().block_on(f))
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub struct ClockModule {
|
||||||
format: String,
|
format: String,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub common: CommonConfig,
|
pub common: Option<CommonConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_format() -> String {
|
fn default_format() -> String {
|
||||||
|
@ -33,6 +33,10 @@ impl Module<Button> for ClockModule {
|
||||||
type SendMessage = DateTime<Local>;
|
type SendMessage = DateTime<Local>;
|
||||||
type ReceiveMessage = ();
|
type ReceiveMessage = ();
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"clock"
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub struct CustomModule {
|
||||||
popup: Option<Vec<Widget>>,
|
popup: Option<Vec<Widget>>,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub common: CommonConfig,
|
pub common: Option<CommonConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to parse an `Orientation` from `String`
|
/// Attempts to parse an `Orientation` from `String`
|
||||||
|
@ -168,6 +168,10 @@ impl Module<gtk::Box> for CustomModule {
|
||||||
type SendMessage = ();
|
type SendMessage = ();
|
||||||
type ReceiveMessage = ExecEvent;
|
type ReceiveMessage = ExecEvent;
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"custom"
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub struct FocusedModule {
|
||||||
icon_theme: Option<String>,
|
icon_theme: Option<String>,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub common: CommonConfig,
|
pub common: Option<CommonConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn default_icon_size() -> i32 {
|
const fn default_icon_size() -> i32 {
|
||||||
|
@ -37,6 +37,10 @@ impl Module<gtk::Box> for FocusedModule {
|
||||||
type SendMessage = (String, String);
|
type SendMessage = (String, String);
|
||||||
type ReceiveMessage = ();
|
type ReceiveMessage = ();
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"focused"
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
|
|
|
@ -36,7 +36,7 @@ pub struct LauncherModule {
|
||||||
icon_theme: Option<String>,
|
icon_theme: Option<String>,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub common: CommonConfig,
|
pub common: Option<CommonConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -78,6 +78,10 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
type SendMessage = LauncherUpdate;
|
type SendMessage = LauncherUpdate;
|
||||||
type ReceiveMessage = ItemEvent;
|
type ReceiveMessage = ItemEvent;
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"launcher"
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
|
|
|
@ -17,7 +17,6 @@ pub mod workspaces;
|
||||||
use crate::config::BarPosition;
|
use crate::config::BarPosition;
|
||||||
use crate::popup::ButtonGeometry;
|
use crate::popup::ButtonGeometry;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use derive_builder::Builder;
|
|
||||||
use glib::IsA;
|
use glib::IsA;
|
||||||
use gtk::gdk::Monitor;
|
use gtk::gdk::Monitor;
|
||||||
use gtk::{Application, Widget};
|
use gtk::{Application, Widget};
|
||||||
|
@ -29,15 +28,12 @@ pub enum ModuleLocation {
|
||||||
Center,
|
Center,
|
||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Builder)]
|
|
||||||
pub struct ModuleInfo<'a> {
|
pub struct ModuleInfo<'a> {
|
||||||
pub app: &'a Application,
|
pub app: &'a Application,
|
||||||
pub location: ModuleLocation,
|
pub location: ModuleLocation,
|
||||||
pub bar_position: BarPosition,
|
pub bar_position: BarPosition,
|
||||||
pub monitor: &'a Monitor,
|
pub monitor: &'a Monitor,
|
||||||
pub output_name: &'a str,
|
pub output_name: &'a str,
|
||||||
pub module_name: &'a str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -73,6 +69,8 @@ where
|
||||||
type SendMessage;
|
type SendMessage;
|
||||||
type ReceiveMessage;
|
type ReceiveMessage;
|
||||||
|
|
||||||
|
fn name() -> &'static str;
|
||||||
|
|
||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
info: &ModuleInfo,
|
info: &ModuleInfo,
|
||||||
|
|
|
@ -68,7 +68,7 @@ pub struct MpdModule {
|
||||||
music_dir: PathBuf,
|
music_dir: PathBuf,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub common: CommonConfig,
|
pub common: Option<CommonConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_socket() -> String {
|
fn default_socket() -> String {
|
||||||
|
@ -128,6 +128,10 @@ impl Module<Button> for MpdModule {
|
||||||
type SendMessage = Option<SongUpdate>;
|
type SendMessage = Option<SongUpdate>;
|
||||||
type ReceiveMessage = PlayerCommand;
|
type ReceiveMessage = PlayerCommand;
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"mpd"
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub struct ScriptModule {
|
||||||
interval: u64,
|
interval: u64,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub common: CommonConfig,
|
pub common: Option<CommonConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Mode::Poll`
|
/// `Mode::Poll`
|
||||||
|
@ -48,6 +48,10 @@ impl Module<Label> for ScriptModule {
|
||||||
type SendMessage = String;
|
type SendMessage = String;
|
||||||
type ReceiveMessage = ();
|
type ReceiveMessage = ();
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"script"
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub struct SysInfoModule {
|
||||||
interval: Interval,
|
interval: Interval,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub common: CommonConfig,
|
pub common: Option<CommonConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Copy, Clone)]
|
#[derive(Debug, Deserialize, Copy, Clone)]
|
||||||
|
@ -116,6 +116,10 @@ impl Module<gtk::Box> for SysInfoModule {
|
||||||
type SendMessage = HashMap<String, String>;
|
type SendMessage = HashMap<String, String>;
|
||||||
type ReceiveMessage = ();
|
type ReceiveMessage = ();
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"sysinfo"
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
|
|
|
@ -17,7 +17,7 @@ use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct TrayModule {
|
pub struct TrayModule {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub common: CommonConfig,
|
pub common: Option<CommonConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a GTK `Image` component
|
/// Gets a GTK `Image` component
|
||||||
|
@ -92,6 +92,10 @@ impl Module<MenuBar> for TrayModule {
|
||||||
type SendMessage = NotifierItemMessage;
|
type SendMessage = NotifierItemMessage;
|
||||||
type ReceiveMessage = NotifierItemCommand;
|
type ReceiveMessage = NotifierItemCommand;
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"tray"
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub struct WorkspacesModule {
|
||||||
all_monitors: bool,
|
all_monitors: bool,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub common: CommonConfig,
|
pub common: Option<CommonConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -65,6 +65,10 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||||
type SendMessage = WorkspaceUpdate;
|
type SendMessage = WorkspaceUpdate;
|
||||||
type ReceiveMessage = String;
|
type ReceiveMessage = String;
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"workspaces"
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_controller(
|
fn spawn_controller(
|
||||||
&self,
|
&self,
|
||||||
info: &ModuleInfo,
|
info: &ModuleInfo,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue