1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-08-19 07:41:04 +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:
Jake Stanger 2022-09-25 22:49:00 +01:00 committed by GitHub
parent daafa0943e
commit 720ba7bfb0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 2381 additions and 1846 deletions

View file

@ -1,224 +1,94 @@
use color_eyre::{Report, Result};
use crossbeam_channel::Receiver;
use ksway::{Error, IpcCommand, IpcEvent};
use async_once::AsyncOnce;
use color_eyre::Report;
use futures_util::StreamExt;
use lazy_static::lazy_static;
use serde::Deserialize;
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use swayipc_async::{Connection, Event, EventType, WindowEvent, WorkspaceEvent};
use tokio::spawn;
use tracing::{debug, info, trace};
use tokio::sync::broadcast::{channel, Receiver, Sender};
use tokio::sync::Mutex;
use tracing::{info, trace};
pub mod node;
#[derive(Deserialize, Debug, Clone)]
pub struct WorkspaceEvent {
pub change: String,
pub old: Option<Workspace>,
pub current: Option<Workspace>,
pub struct SwayEventClient {
workspace_tx: Sender<Box<WorkspaceEvent>>,
_workspace_rx: Receiver<Box<WorkspaceEvent>>,
window_tx: Sender<Box<WindowEvent>>,
_window_rx: Receiver<Box<WindowEvent>>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct Workspace {
pub name: String,
pub focused: bool,
// pub num: i32,
pub output: String,
}
impl SwayEventClient {
fn new() -> Self {
let (workspace_tx, workspace_rx) = channel(16);
let (window_tx, window_rx) = channel(16);
#[derive(Debug, Deserialize, Clone)]
pub struct WindowEvent {
pub change: String,
pub container: SwayNode,
}
let workspace_tx2 = workspace_tx.clone();
let window_tx2 = window_tx.clone();
#[derive(Debug, Deserialize, Clone)]
pub struct SwayNode {
#[serde(rename = "type")]
pub node_type: String,
pub id: i32,
pub name: Option<String>,
pub app_id: Option<String>,
pub focused: bool,
pub urgent: bool,
pub nodes: Vec<SwayNode>,
pub floating_nodes: Vec<SwayNode>,
pub shell: Option<String>,
pub window_properties: Option<WindowProperties>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct WindowProperties {
pub class: Option<String>,
}
#[derive(Deserialize)]
pub struct SwayOutput {
pub name: String,
}
type Broadcaster<T> = Arc<Mutex<UnboundedBroadcast<T>>>;
pub struct SwayClient {
client: ksway::Client,
workspace_bc: Broadcaster<WorkspaceEvent>,
window_bc: Broadcaster<WindowEvent>,
}
impl SwayClient {
fn connect() -> Result<Self> {
let client = match ksway::Client::connect() {
Ok(client) => Ok(client),
Err(err) => Err(get_client_error(err)),
}?;
info!("Sway IPC client connected");
let workspace_bc = Arc::new(Mutex::new(UnboundedBroadcast::new()));
let window_bc = Arc::new(Mutex::new(UnboundedBroadcast::new()));
let workspace_bc2 = workspace_bc.clone();
let window_bc2 = window_bc.clone();
spawn(async move {
let mut sub_client = match ksway::Client::connect() {
Ok(client) => Ok(client),
Err(err) => Err(get_client_error(err)),
}
.expect("Failed to connect to Sway IPC server");
let workspace_tx = workspace_tx2;
let window_tx = window_tx2;
let client = Connection::new().await?;
info!("Sway IPC subscription client connected");
let event_types = vec![IpcEvent::Window, IpcEvent::Workspace];
let rx = match sub_client.subscribe(event_types) {
Ok(res) => Ok(res),
Err(err) => Err(get_client_error(err)),
}
.expect("Failed to subscribe to Sway IPC server");
let event_types = [EventType::Window, EventType::Workspace];
loop {
while let Ok((ev_type, payload)) = rx.try_recv() {
debug!("Received sway event {:?}", ev_type);
match ev_type {
IpcEvent::Workspace => {
let json = serde_json::from_slice::<WorkspaceEvent>(&payload).expect(
"Received invalid workspace event payload from Sway IPC server",
);
workspace_bc
.lock()
.expect("Failed to get lock on workspace event bus")
.send(json)
.expect("Failed to broadcast workspace event");
}
IpcEvent::Window => {
let json = serde_json::from_slice::<WindowEvent>(&payload).expect(
"Received invalid window event payload from Sway IPC server",
);
window_bc
.lock()
.expect("Failed to get lock on window event bus")
.send(json)
.expect("Failed to broadcast window event");
}
_ => {}
let mut events = client.subscribe(event_types).await?;
while let Some(event) = events.next().await {
trace!("event: {:?}", event);
match event? {
Event::Workspace(ev) => {
workspace_tx.send(ev)?;
}
}
match sub_client.poll() {
Ok(()) => Ok(()),
Err(err) => Err(get_client_error(err)),
}
.expect("Failed to poll Sway IPC client");
Event::Window(ev) => {
window_tx.send(ev)?;
}
_ => {}
};
}
Ok::<(), Report>(())
});
Ok(Self {
client,
workspace_bc: workspace_bc2,
window_bc: window_bc2,
})
}
pub fn ipc(&mut self, command: IpcCommand) -> Result<Vec<u8>> {
debug!("Sending command: {:?}", command);
match self.client.ipc(command) {
Ok(res) => Ok(res),
Err(err) => Err(get_client_error(err)),
Self {
workspace_tx,
_workspace_rx: workspace_rx,
window_tx,
_window_rx: window_rx,
}
}
pub(crate) fn run(&mut self, cmd: String) -> Result<Vec<u8>> {
debug!("Sending command: {}", cmd);
match self.client.run(cmd) {
Ok(res) => Ok(res),
Err(err) => Err(get_client_error(err)),
}
/// Gets an event receiver for workspace events
pub fn subscribe_workspace(&self) -> Receiver<Box<WorkspaceEvent>> {
self.workspace_tx.subscribe()
}
pub fn subscribe_workspace(&mut self) -> Receiver<WorkspaceEvent> {
trace!("Adding new workspace subscriber");
self.workspace_bc
.lock()
.expect("Failed to get lock on workspace event bus")
.subscribe()
}
pub fn subscribe_window(&mut self) -> Receiver<WindowEvent> {
trace!("Adding new window subscriber");
self.window_bc
.lock()
.expect("Failed to get lock on window event bus")
.subscribe()
}
}
/// Gets an error report from a `ksway` error enum variant
pub fn get_client_error(error: Error) -> Report {
match error {
Error::SockPathNotFound => Report::msg("Sway socket path not found"),
Error::SubscriptionError => Report::msg("Sway IPC subscription error"),
Error::AlreadySubscribed => Report::msg("Already subscribed to Sway IPC server"),
Error::Io(err) => Report::new(err),
/// Gets an event receiver for window events
pub fn subscribe_window(&self) -> Receiver<Box<WindowEvent>> {
self.window_tx.subscribe()
}
}
lazy_static! {
static ref CLIENT: Arc<Mutex<SwayClient>> = {
let client = SwayClient::connect();
match client {
Ok(client) => Arc::new(Mutex::new(client)),
Err(err) => panic!("{:?}", err),
}
};
static ref CLIENT: AsyncOnce<Arc<Mutex<Connection>>> = AsyncOnce::new(async {
let client = Connection::new()
.await
.expect("Failed to connect to Sway socket");
Arc::new(Mutex::new(client))
});
static ref SUB_CLIENT: SwayEventClient = SwayEventClient::new();
}
pub fn get_client() -> Arc<Mutex<SwayClient>> {
Arc::clone(&CLIENT)
/// Gets the sway IPC client
pub async fn get_client() -> Arc<Mutex<Connection>> {
let client = CLIENT.get().await;
Arc::clone(client)
}
/// Crossbeam channel wrapper
/// which sends messages to all receivers.
pub struct UnboundedBroadcast<T> {
channels: Vec<crossbeam_channel::Sender<T>>,
}
impl<T: 'static + Clone + Send + Sync> UnboundedBroadcast<T> {
/// Creates a new broadcaster.
pub const fn new() -> Self {
Self { channels: vec![] }
}
/// Creates a new sender/receiver pair.
/// The sender is stored locally and the receiver is returned.
pub fn subscribe(&mut self) -> Receiver<T> {
let (tx, rx) = crossbeam_channel::unbounded();
self.channels.push(tx);
rx
}
/// Attempts to send a messsge to all receivers.
pub fn send(&self, message: T) -> Result<(), crossbeam_channel::SendError<T>> {
for c in &self.channels {
c.send(message.clone())?;
}
Ok(())
}
/// Gets the sway IPC event subscription client
pub fn get_sub_client() -> &'static SwayEventClient {
&SUB_CLIENT
}