1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-07-01 10:41:03 +02:00

fix: avoid creating loads of sway/mpd clients

This commit is contained in:
Jake Stanger 2022-08-25 21:53:42 +01:00
parent 649b0efb19
commit 6dcae66570
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
9 changed files with 254 additions and 136 deletions

1
Cargo.lock generated
View file

@ -1139,6 +1139,7 @@ dependencies = [
"gtk", "gtk",
"gtk-layer-shell", "gtk-layer-shell",
"ksway", "ksway",
"lazy_static",
"mpd_client", "mpd_client",
"notify", "notify",
"regex", "regex",

View file

@ -25,6 +25,7 @@ serde_json = "1.0.82"
serde_yaml = "0.9.4" serde_yaml = "0.9.4"
toml = "0.5.9" toml = "0.5.9"
cornfig = "0.2.0" cornfig = "0.2.0"
lazy_static = "1.4.0"
regex = "1.6.0" regex = "1.6.0"
stray = "0.1.1" stray = "0.1.1"
dirs = "4.0.0" dirs = "4.0.0"

View file

@ -11,14 +11,13 @@ mod sway;
use crate::bar::create_bar; use crate::bar::create_bar;
use crate::config::{Config, MonitorConfig}; use crate::config::{Config, MonitorConfig};
use crate::style::load_css; use crate::style::load_css;
use crate::sway::{get_client_error, SwayOutput}; use crate::sway::{get_client, SwayOutput};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use color_eyre::Report; use color_eyre::Report;
use dirs::config_dir; use dirs::config_dir;
use gtk::gdk::Display; use gtk::gdk::Display;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::Application; use gtk::Application;
use ksway::client::Client;
use ksway::IpcCommand; use ksway::IpcCommand;
use std::env; use std::env;
use std::process::exit; use std::process::exit;
@ -97,14 +96,16 @@ async fn main() -> Result<()> {
} }
fn create_bars(app: &Application, display: &Display, config: &Config) -> Result<()> { fn create_bars(app: &Application, display: &Display, config: &Config) -> Result<()> {
let mut sway_client = match Client::connect() { let outputs = {
Ok(client) => Ok(client), let sway = get_client();
Err(err) => Err(get_client_error(err)), let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
}?;
let outputs = match sway_client.ipc(IpcCommand::GetOutputs) { let outputs = sway.ipc(IpcCommand::GetOutputs);
Ok(outputs) => Ok(outputs),
Err(err) => Err(get_client_error(err)), match outputs {
Ok(outputs) => Ok(outputs),
Err(err) => Err(err),
}
}?; }?;
let outputs = serde_json::from_slice::<Vec<SwayOutput>>(&outputs)?; let outputs = serde_json::from_slice::<Vec<SwayOutput>>(&outputs)?;

View file

@ -1,14 +1,12 @@
use crate::icon; use crate::icon;
use crate::modules::{Module, ModuleInfo}; use crate::modules::{Module, ModuleInfo};
use crate::sway::{SwayClient, WindowEvent}; use crate::sway::get_client;
use color_eyre::Result; use color_eyre::Result;
use glib::Continue; use glib::Continue;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{IconTheme, Image, Label, Orientation}; use gtk::{IconTheme, Image, Label, Orientation};
use ksway::IpcEvent;
use serde::Deserialize; use serde::Deserialize;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
use tracing::error;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct FocusedModule { pub struct FocusedModule {
@ -42,42 +40,39 @@ impl Module<gtk::Box> for FocusedModule {
container.add(&icon); container.add(&icon);
container.add(&label); container.add(&label);
let mut sway = SwayClient::connect()?;
let srx = sway.subscribe(vec![IpcEvent::Window])?;
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
let focused = sway let focused = {
.get_open_windows()? let sway = get_client();
.into_iter() let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
.find(|node| node.focused); sway.get_open_windows()?
.into_iter()
.find(|node| node.focused)
};
if let Some(focused) = focused { if let Some(focused) = focused {
tx.send(focused)?; tx.send(focused)?;
} }
spawn_blocking(move || loop { spawn_blocking(move || {
while let Ok((_, payload)) = srx.try_recv() { let srx = {
match serde_json::from_slice::<WindowEvent>(&payload) { let sway = get_client();
Ok(payload) => { let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
let update = match payload.change.as_str() { sway.subscribe_window()
"focus" => true, };
"title" => payload.container.focused,
_ => false,
};
if update { while let Ok(payload) = srx.recv() {
tx.send(payload.container) let update = match payload.change.as_str() {
.expect("Failed to sendf focus update"); "focus" => true,
} "title" => payload.container.focused,
} _ => false,
Err(err) => error!("{:?}", err), };
if update {
tx.send(payload.container)
.expect("Failed to sendf focus update");
} }
} }
if let Err(err) = sway.poll() {
error!("{:?}", err);
}
}); });
{ {

View file

@ -5,17 +5,15 @@ use crate::collection::Collection;
use crate::modules::launcher::item::{ButtonConfig, LauncherItem, LauncherWindow, OpenState}; use crate::modules::launcher::item::{ButtonConfig, LauncherItem, LauncherWindow, OpenState};
use crate::modules::launcher::popup::Popup; use crate::modules::launcher::popup::Popup;
use crate::modules::{Module, ModuleInfo}; use crate::modules::{Module, ModuleInfo};
use crate::sway::{SwayClient, SwayNode, WindowEvent}; use crate::sway::{get_client, SwayNode};
use color_eyre::{Report, Result}; use color_eyre::{Report, Result};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{IconTheme, Orientation}; use gtk::{IconTheme, Orientation};
use ksway::IpcEvent;
use serde::Deserialize; use serde::Deserialize;
use std::rc::Rc; use std::rc::Rc;
use tokio::spawn; use tokio::spawn;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
use tracing::error;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct LauncherModule { pub struct LauncherModule {
@ -210,8 +208,6 @@ impl Module<gtk::Box> for LauncherModule {
icon_theme.set_custom_theme(Some(&theme)); icon_theme.set_custom_theme(Some(&theme));
} }
let mut sway = SwayClient::connect()?;
let popup = Popup::new( let popup = Popup::new(
"popup-launcher", "popup-launcher",
info.app, info.app,
@ -237,28 +233,28 @@ impl Module<gtk::Box> for LauncherModule {
button_config, button_config,
); );
let open_windows = sway.get_open_windows()?; let open_windows = {
let sway = get_client();
let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
sway.get_open_windows()
}?;
for window in open_windows { for window in open_windows {
launcher.add_window(window); launcher.add_window(window);
} }
let srx = sway.subscribe(vec![IpcEvent::Window])?;
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
spawn_blocking(move || loop { spawn_blocking(move || {
while let Ok((_, payload)) = srx.try_recv() { let srx = {
match serde_json::from_slice::<WindowEvent>(&payload) { let sway = get_client();
Ok(payload) => { let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
tx.send(payload) sway.subscribe_window()
.expect("Failed to send window event payload"); };
}
Err(err) => error!("{:?}", err),
}
}
if let Err(err) = sway.poll() { while let Ok(payload) = srx.recv() {
error!("{:?}", err); tx.send(payload)
.expect("Failed to send window event payload");
} }
}); });
@ -278,14 +274,15 @@ impl Module<gtk::Box> for LauncherModule {
} }
spawn(async move { spawn(async move {
let mut sway = SwayClient::connect()?; let sway = get_client();
while let Some(event) = ui_rx.recv().await { while let Some(event) = ui_rx.recv().await {
let selector = match event { let selector = match event {
FocusEvent::AppId(app_id) => format!("[app_id={}]", app_id), FocusEvent::AppId(app_id) => format!("[app_id={}]", app_id),
FocusEvent::Class(class) => format!("[class={}]", class), FocusEvent::Class(class) => format!("[class={}]", class),
FocusEvent::ConId(id) => format!("[con_id={}]", id), FocusEvent::ConId(id) => format!("[con_id={}]", id),
}; };
let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
sway.run(format!("{} focus", selector))?; sway.run(format!("{} focus", selector))?;
} }

View file

@ -1,55 +1,67 @@
use lazy_static::lazy_static;
use mpd_client::commands::responses::Status; use mpd_client::commands::responses::Status;
use mpd_client::raw::MpdProtocolError; use mpd_client::raw::MpdProtocolError;
use mpd_client::{Client, Connection}; use mpd_client::{Client, Connection};
use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::net::{TcpStream, UnixStream}; use tokio::net::{TcpStream, UnixStream};
use tokio::spawn; use tokio::sync::Mutex;
use tokio::time::sleep; use tokio::time::sleep;
pub async fn wait_for_connection( lazy_static! {
hosts: Vec<String>, static ref CLIENTS: Arc<Mutex<HashMap<String, Arc<Client>>>> =
Arc::new(Mutex::new(HashMap::new()));
}
pub async fn get_connection(host: &str) -> Option<Arc<Client>> {
let mut clients = CLIENTS.lock().await;
match clients.get(host) {
Some(client) => Some(Arc::clone(client)),
None => {
let client = wait_for_connection(host, Duration::from_secs(5), None).await?;
let client = Arc::new(client);
clients.insert(host.to_string(), Arc::clone(&client));
Some(client)
}
}
}
async fn wait_for_connection(
host: &str,
interval: Duration, interval: Duration,
max_retries: Option<usize>, max_retries: Option<usize>,
) -> Option<Client> { ) -> Option<Client> {
let mut retries = 0; let mut retries = 0;
let max_retries = max_retries.unwrap_or(usize::MAX);
spawn(async move { loop {
let max_retries = max_retries.unwrap_or(usize::MAX); if retries == max_retries {
loop { break None;
if retries == max_retries {
break None;
}
if let Some(conn) = try_get_mpd_conn(&hosts).await {
break Some(conn.0);
}
retries += 1;
sleep(interval).await;
} }
})
.await if let Some(conn) = try_get_mpd_conn(host).await {
.expect("Error occurred while handling tasks") break Some(conn.0);
}
retries += 1;
sleep(interval).await;
}
} }
/// Cycles through each MPD host and /// Cycles through each MPD host and
/// returns the first one which connects, /// returns the first one which connects,
/// or none if there are none /// or none if there are none
async fn try_get_mpd_conn(hosts: &[String]) -> Option<Connection> { async fn try_get_mpd_conn(host: &str) -> Option<Connection> {
for host in hosts { let connection = if is_unix_socket(host) {
let connection = if is_unix_socket(host) { connect_unix(host).await
connect_unix(host).await } else {
} else { connect_tcp(host).await
connect_tcp(host).await };
};
if let Ok(connection) = connection { connection.ok()
return Some(connection);
}
}
None
} }
fn is_unix_socket(host: &str) -> bool { fn is_unix_socket(host: &str) -> bool {

View file

@ -2,7 +2,7 @@ mod client;
mod popup; mod popup;
use self::popup::Popup; use self::popup::Popup;
use crate::modules::mpd::client::{get_duration, get_elapsed, wait_for_connection}; use crate::modules::mpd::client::{get_connection, get_duration, get_elapsed};
use crate::modules::mpd::popup::{MpdPopup, PopupEvent}; use crate::modules::mpd::popup::{MpdPopup, PopupEvent};
use crate::modules::{Module, ModuleInfo}; use crate::modules::{Module, ModuleInfo};
use color_eyre::Result; use color_eyre::Result;
@ -120,7 +120,7 @@ impl Module<Button> for MpdModule {
let host = self.host.clone(); let host = self.host.clone();
let host2 = self.host.clone(); let host2 = self.host.clone();
spawn(async move { spawn(async move {
let client = wait_for_connection(vec![host], Duration::from_secs(1), None) let client = get_connection(&host)
.await .await
.expect("Unexpected error when trying to connect to MPD server"); .expect("Unexpected error when trying to connect to MPD server");
@ -145,7 +145,7 @@ impl Module<Button> for MpdModule {
}); });
spawn(async move { spawn(async move {
let client = wait_for_connection(vec![host2], Duration::from_secs(1), None) let client = get_connection(&host2)
.await .await
.expect("Unexpected error when trying to connect to MPD server"); .expect("Unexpected error when trying to connect to MPD server");

View file

@ -1,15 +1,15 @@
use crate::modules::{Module, ModuleInfo}; use crate::modules::{Module, ModuleInfo};
use crate::sway::{SwayClient, Workspace, WorkspaceEvent}; use crate::sway::{get_client, Workspace};
use color_eyre::{Report, Result}; use color_eyre::{Report, Result};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, Orientation}; use gtk::{Button, Orientation};
use ksway::{IpcCommand, IpcEvent}; use ksway::IpcCommand;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use tokio::spawn; use tokio::spawn;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
use tracing::error; use tracing::{debug, trace};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct WorkspacesModule { pub struct WorkspacesModule {
@ -47,11 +47,12 @@ impl Workspace {
impl Module<gtk::Box> for WorkspacesModule { impl Module<gtk::Box> for WorkspacesModule {
fn into_widget(self, info: &ModuleInfo) -> Result<gtk::Box> { fn into_widget(self, info: &ModuleInfo) -> Result<gtk::Box> {
let mut sway = SwayClient::connect()?;
let container = gtk::Box::new(Orientation::Horizontal, 0); let container = gtk::Box::new(Orientation::Horizontal, 0);
let workspaces = { 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 raw = sway.ipc(IpcCommand::GetWorkspaces)?;
let workspaces = serde_json::from_slice::<Vec<Workspace>>(&raw)?; let workspaces = serde_json::from_slice::<Vec<Workspace>>(&raw)?;
@ -77,19 +78,19 @@ impl Module<gtk::Box> for WorkspacesModule {
button_map.insert(workspace.name, item); button_map.insert(workspace.name, item);
} }
let srx = sway.subscribe(vec![IpcEvent::Workspace])?;
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
spawn_blocking(move || loop { spawn_blocking(move || {
while let Ok((_, payload)) = srx.try_recv() { trace!("Starting workspace event listener task");
match serde_json::from_slice::<WorkspaceEvent>(&payload) { let srx = {
Ok(payload) => tx.send(payload).expect("Failed to send workspace event"), let sway = get_client();
Err(err) => error!("{:?}", err), let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
}
}
if let Err(err) = sway.poll() { sway.subscribe_workspace()
error!("{:?}", err); };
while let Ok(payload) = srx.recv() {
tx.send(payload).expect("Failed to send workspace event");
} }
}); });
@ -150,8 +151,12 @@ impl Module<gtk::Box> for WorkspacesModule {
} }
spawn(async move { spawn(async move {
let mut sway = SwayClient::connect()?; trace!("Setting up UI event handler");
let sway = get_client();
while let Some(name) = ui_rx.recv().await { 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))?; sway.run(format!("workspace {}", name))?;
} }

View file

@ -1,17 +1,22 @@
use color_eyre::{Report, Result}; use color_eyre::{Report, Result};
use crossbeam_channel::Receiver;
use ksway::{Error, IpcCommand, IpcEvent}; use ksway::{Error, IpcCommand, IpcEvent};
use lazy_static::lazy_static;
use serde::Deserialize; use serde::Deserialize;
use std::sync::{Arc, Mutex};
use tokio::spawn;
use tracing::{debug, info, trace};
pub mod node; pub mod node;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug, Clone)]
pub struct WorkspaceEvent { pub struct WorkspaceEvent {
pub change: String, pub change: String,
pub old: Option<Workspace>, pub old: Option<Workspace>,
pub current: Option<Workspace>, pub current: Option<Workspace>,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug, Clone)]
pub struct Workspace { pub struct Workspace {
pub name: String, pub name: String,
pub focused: bool, pub focused: bool,
@ -19,13 +24,13 @@ pub struct Workspace {
pub output: String, pub output: String,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, Clone)]
pub struct WindowEvent { pub struct WindowEvent {
pub change: String, pub change: String,
pub container: SwayNode, pub container: SwayNode,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, Clone)]
pub struct SwayNode { pub struct SwayNode {
#[serde(rename = "type")] #[serde(rename = "type")]
pub node_type: String, pub node_type: String,
@ -40,7 +45,7 @@ pub struct SwayNode {
pub window_properties: Option<WindowProperties>, pub window_properties: Option<WindowProperties>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, Clone)]
pub struct WindowProperties { pub struct WindowProperties {
pub class: Option<String>, pub class: Option<String>,
} }
@ -52,25 +57,79 @@ pub struct SwayOutput {
pub struct SwayClient { pub struct SwayClient {
client: ksway::Client, client: ksway::Client,
workspace_bc: Arc<Mutex<UnboundedBroadcast<WorkspaceEvent>>>,
window_bc: Arc<Mutex<UnboundedBroadcast<WindowEvent>>>,
} }
impl SwayClient { impl SwayClient {
pub(crate) fn run(&mut self, cmd: String) -> Result<Vec<u8>> { fn connect() -> Result<Self> {
match self.client.run(cmd) {
Ok(res) => Ok(res),
Err(err) => Err(get_client_error(err)),
}
}
}
impl SwayClient {
pub fn connect() -> Result<Self> {
let client = match ksway::Client::connect() { let client = match ksway::Client::connect() {
Ok(client) => Ok(client), Ok(client) => Ok(client),
Err(err) => Err(get_client_error(err)), Err(err) => Err(get_client_error(err)),
}?; }?;
info!("Sway IPC client connected");
Ok(Self { client }) 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");
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");
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");
}
_ => {}
}
}
match sub_client.poll() {
Ok(()) => Ok(()),
Err(err) => Err(get_client_error(err)),
}
.expect("Failed to poll Sway IPC client");
}
});
Ok(Self {
client,
workspace_bc: workspace_bc2,
window_bc: window_bc2,
})
} }
pub fn ipc(&mut self, command: IpcCommand) -> Result<Vec<u8>> { pub fn ipc(&mut self, command: IpcCommand) -> Result<Vec<u8>> {
@ -80,21 +139,28 @@ impl SwayClient {
} }
} }
pub fn subscribe( pub(crate) fn run(&mut self, cmd: String) -> Result<Vec<u8>> {
&mut self, debug!("Sending command: {}", cmd);
event_types: Vec<IpcEvent>, match self.client.run(cmd) {
) -> Result<crossbeam_channel::Receiver<(IpcEvent, Vec<u8>)>> {
match self.client.subscribe(event_types) {
Ok(res) => Ok(res), Ok(res) => Ok(res),
Err(err) => Err(get_client_error(err)), Err(err) => Err(get_client_error(err)),
} }
} }
pub fn poll(&mut self) -> Result<()> { pub fn subscribe_workspace(&mut self) -> Receiver<WorkspaceEvent> {
match self.client.poll() { trace!("Adding new workspace subscriber");
Ok(()) => Ok(()), self.workspace_bc
Err(err) => Err(get_client_error(err)), .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()
} }
} }
@ -107,3 +173,43 @@ pub fn get_client_error(error: Error) -> Report {
Error::Io(err) => Report::new(err), Error::Io(err) => Report::new(err),
} }
} }
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),
}
};
}
pub fn get_client() -> Arc<Mutex<SwayClient>> {
Arc::clone(&CLIENT)
}
pub struct UnboundedBroadcast<T> {
channels: Vec<crossbeam_channel::Sender<T>>,
}
impl<T: 'static + Clone + Send + Sync> UnboundedBroadcast<T> {
pub const fn new() -> Self {
Self { channels: vec![] }
}
pub fn subscribe(&mut self) -> Receiver<T> {
let (tx, rx) = crossbeam_channel::unbounded();
self.channels.push(tx);
rx
}
pub fn send(&self, message: T) -> Result<(), crossbeam_channel::SendError<T>> {
for c in &self.channels {
c.send(message.clone())?;
}
Ok(())
}
}