mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-07-04 04:01:03 +02:00
Major module refactor (#19)
* refactor: major module restructuring Modules now implement a "controller", which allows for separation of logic from UI code and enforces a tighter structure around how modules should be written. The introduction of this change required major refactoring or even rewriting of all modules. This also better integrates the popup into modules, making it easier for data to be passed around without fetching the same thing twice The refactor also improves some client code, switching from `ksway` to the much more stable `swayipc-async`. Partial multi-monitor for the tray module has been added. BREAKING CHANGE: The `mpd` module config has changed, moving the icons to their own object.
This commit is contained in:
parent
daafa0943e
commit
720ba7bfb0
26 changed files with 2381 additions and 1846 deletions
|
@ -1,62 +1,82 @@
|
|||
use crate::modules::{Module, ModuleInfo};
|
||||
use crate::sway::{get_client, Workspace};
|
||||
use crate::await_sync;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||
use crate::sway::{get_client, get_sub_client};
|
||||
use color_eyre::{Report, Result};
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Button, Orientation};
|
||||
use ksway::IpcCommand;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use swayipc_async::{Workspace, WorkspaceChange, WorkspaceEvent};
|
||||
use tokio::spawn;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::task::spawn_blocking;
|
||||
use tracing::{debug, trace};
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tracing::trace;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct WorkspacesModule {
|
||||
/// Map of actual workspace names to custom names.
|
||||
name_map: Option<HashMap<String, String>>,
|
||||
|
||||
/// Whether to display icons for all monitors.
|
||||
/// Whether to display buttons for all monitors.
|
||||
#[serde(default = "crate::config::default_false")]
|
||||
all_monitors: bool,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
fn as_button(&self, name_map: &HashMap<String, String>, tx: &mpsc::Sender<String>) -> Button {
|
||||
let button = Button::builder()
|
||||
.label(name_map.get(self.name.as_str()).unwrap_or(&self.name))
|
||||
.build();
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum WorkspaceUpdate {
|
||||
Init(Vec<Workspace>),
|
||||
Update(Box<WorkspaceEvent>),
|
||||
}
|
||||
|
||||
let style_context = button.style_context();
|
||||
style_context.add_class("item");
|
||||
/// Creates a button from a workspace
|
||||
fn create_button(
|
||||
workspace: &Workspace,
|
||||
name_map: &HashMap<String, String>,
|
||||
tx: &Sender<String>,
|
||||
) -> Button {
|
||||
let button = Button::builder()
|
||||
.label(
|
||||
name_map
|
||||
.get(workspace.name.as_str())
|
||||
.unwrap_or(&workspace.name),
|
||||
)
|
||||
.build();
|
||||
|
||||
if self.focused {
|
||||
style_context.add_class("focused");
|
||||
}
|
||||
let style_context = button.style_context();
|
||||
style_context.add_class("item");
|
||||
|
||||
{
|
||||
let tx = tx.clone();
|
||||
let name = self.name.clone();
|
||||
button.connect_clicked(move |_item| {
|
||||
tx.try_send(name.clone())
|
||||
.expect("Failed to send workspace click event");
|
||||
});
|
||||
}
|
||||
|
||||
button
|
||||
if workspace.focused {
|
||||
style_context.add_class("focused");
|
||||
}
|
||||
|
||||
{
|
||||
let tx = tx.clone();
|
||||
let name = workspace.name.clone();
|
||||
button.connect_clicked(move |_item| {
|
||||
tx.try_send(name.clone())
|
||||
.expect("Failed to send workspace click event");
|
||||
});
|
||||
}
|
||||
|
||||
button
|
||||
}
|
||||
|
||||
impl Module<gtk::Box> for WorkspacesModule {
|
||||
fn into_widget(self, info: &ModuleInfo) -> Result<gtk::Box> {
|
||||
let container = gtk::Box::new(Orientation::Horizontal, 0);
|
||||
type SendMessage = WorkspaceUpdate;
|
||||
type ReceiveMessage = String;
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
info: &ModuleInfo,
|
||||
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
mut rx: Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let workspaces = {
|
||||
trace!("Getting current workspaces");
|
||||
let sway = get_client();
|
||||
let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
|
||||
let raw = sway.ipc(IpcCommand::GetWorkspaces)?;
|
||||
let workspaces = serde_json::from_slice::<Vec<Workspace>>(&raw)?;
|
||||
let workspaces = await_sync(async {
|
||||
let sway = get_client().await;
|
||||
let mut sway = sway.lock().await;
|
||||
sway.get_workspaces().await
|
||||
})?;
|
||||
|
||||
if self.all_monitors {
|
||||
workspaces
|
||||
|
@ -69,43 +89,67 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||
}
|
||||
};
|
||||
|
||||
tx.try_send(ModuleUpdateEvent::Update(WorkspaceUpdate::Init(workspaces)))
|
||||
.expect("Failed to send initial workspace list");
|
||||
|
||||
// Subscribe & send events
|
||||
spawn(async move {
|
||||
let mut srx = {
|
||||
let sway = get_sub_client();
|
||||
sway.subscribe_workspace()
|
||||
};
|
||||
|
||||
trace!("Set up Sway workspace subscription");
|
||||
|
||||
while let Ok(payload) = srx.recv().await {
|
||||
tx.send(ModuleUpdateEvent::Update(WorkspaceUpdate::Update(payload)))
|
||||
.await
|
||||
.expect("Failed to send workspace update");
|
||||
}
|
||||
});
|
||||
|
||||
// Change workspace focus
|
||||
spawn(async move {
|
||||
trace!("Setting up UI event handler");
|
||||
let sway = get_client().await;
|
||||
while let Some(name) = rx.recv().await {
|
||||
let mut sway = sway.lock().await;
|
||||
sway.run_command(format!("workspace {}", name)).await?;
|
||||
}
|
||||
|
||||
Ok::<(), Report>(())
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn into_widget(
|
||||
self,
|
||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
info: &ModuleInfo,
|
||||
) -> Result<ModuleWidget<gtk::Box>> {
|
||||
let container = gtk::Box::new(Orientation::Horizontal, 0);
|
||||
|
||||
let name_map = self.name_map.unwrap_or_default();
|
||||
|
||||
let mut button_map: HashMap<String, Button> = HashMap::new();
|
||||
|
||||
let (ui_tx, mut ui_rx) = mpsc::channel(32);
|
||||
|
||||
trace!("Creating workspace buttons");
|
||||
for workspace in workspaces {
|
||||
let item = workspace.as_button(&name_map, &ui_tx);
|
||||
container.add(&item);
|
||||
button_map.insert(workspace.name, item);
|
||||
}
|
||||
|
||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||
|
||||
spawn_blocking(move || {
|
||||
trace!("Starting workspace event listener task");
|
||||
let srx = {
|
||||
let sway = get_client();
|
||||
let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
|
||||
|
||||
sway.subscribe_workspace()
|
||||
};
|
||||
|
||||
while let Ok(payload) = srx.recv() {
|
||||
tx.send(payload).expect("Failed to send workspace event");
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
trace!("Setting up sway event handler");
|
||||
let menubar = container.clone();
|
||||
let container = container.clone();
|
||||
let output_name = info.output_name.to_string();
|
||||
rx.attach(None, move |event| {
|
||||
debug!("Received workspace event {:?}", event);
|
||||
match event.change.as_str() {
|
||||
"focus" => {
|
||||
|
||||
context.widget_rx.attach(None, move |event| {
|
||||
match event {
|
||||
WorkspaceUpdate::Init(workspaces) => {
|
||||
trace!("Creating workspace buttons");
|
||||
for workspace in workspaces {
|
||||
let item = create_button(&workspace, &name_map, &context.controller_tx);
|
||||
container.add(&item);
|
||||
button_map.insert(workspace.name, item);
|
||||
}
|
||||
container.show_all();
|
||||
}
|
||||
WorkspaceUpdate::Update(event) if event.change == WorkspaceChange::Focus => {
|
||||
let old = event.old.and_then(|old| button_map.get(&old.name));
|
||||
if let Some(old) = old {
|
||||
old.style_context().remove_class("focused");
|
||||
|
@ -115,62 +159,55 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||
if let Some(new) = new {
|
||||
new.style_context().add_class("focused");
|
||||
}
|
||||
|
||||
trace!("{:?} {:?}", old, new);
|
||||
}
|
||||
"init" => {
|
||||
WorkspaceUpdate::Update(event) if event.change == WorkspaceChange::Init => {
|
||||
if let Some(workspace) = event.current {
|
||||
if self.all_monitors || workspace.output == output_name {
|
||||
let item = workspace.as_button(&name_map, &ui_tx);
|
||||
let item =
|
||||
create_button(&workspace, &name_map, &context.controller_tx);
|
||||
|
||||
item.show();
|
||||
menubar.add(&item);
|
||||
container.add(&item);
|
||||
button_map.insert(workspace.name, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
"move" => {
|
||||
WorkspaceUpdate::Update(event) if event.change == WorkspaceChange::Move => {
|
||||
if let Some(workspace) = event.current {
|
||||
if !self.all_monitors {
|
||||
if workspace.output == output_name {
|
||||
let item = workspace.as_button(&name_map, &ui_tx);
|
||||
let item = create_button(
|
||||
&workspace,
|
||||
&name_map,
|
||||
&context.controller_tx,
|
||||
);
|
||||
|
||||
item.show();
|
||||
menubar.add(&item);
|
||||
container.add(&item);
|
||||
button_map.insert(workspace.name, item);
|
||||
} else if let Some(item) = button_map.get(&workspace.name) {
|
||||
menubar.remove(item);
|
||||
container.remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"empty" => {
|
||||
WorkspaceUpdate::Update(event) if event.change == WorkspaceChange::Empty => {
|
||||
if let Some(workspace) = event.current {
|
||||
if let Some(item) = button_map.get(&workspace.name) {
|
||||
menubar.remove(item);
|
||||
container.remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
WorkspaceUpdate::Update(_) => {}
|
||||
};
|
||||
|
||||
Continue(true)
|
||||
});
|
||||
}
|
||||
|
||||
spawn(async move {
|
||||
trace!("Setting up UI event handler");
|
||||
let sway = get_client();
|
||||
while let Some(name) = ui_rx.recv().await {
|
||||
let mut sway = sway
|
||||
.lock()
|
||||
.expect("Failed to get write lock on Sway IPC client");
|
||||
sway.run(format!("workspace {}", name))?;
|
||||
}
|
||||
|
||||
Ok::<(), Report>(())
|
||||
});
|
||||
|
||||
Ok(container)
|
||||
Ok(ModuleWidget {
|
||||
widget: container,
|
||||
popup: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue