2024-05-18 17:17:26 +01:00
|
|
|
mod bar;
|
|
|
|
mod ironvar;
|
|
|
|
|
|
|
|
use std::fs;
|
|
|
|
use std::path::Path;
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
|
|
|
use color_eyre::{Report, Result};
|
|
|
|
use gtk::Application;
|
2025-02-21 16:35:54 +00:00
|
|
|
use gtk::prelude::*;
|
2025-06-24 23:04:24 +01:00
|
|
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
2024-05-18 17:17:26 +01:00
|
|
|
use tokio::net::{UnixListener, UnixStream};
|
|
|
|
use tokio::sync::mpsc::{self, Receiver, Sender};
|
2025-06-24 23:04:24 +01:00
|
|
|
use tracing::{debug, error, info, trace, warn};
|
2024-05-18 17:17:26 +01:00
|
|
|
|
2025-05-18 15:17:09 +01:00
|
|
|
use super::Ipc;
|
|
|
|
use crate::channels::{AsyncSenderExt, MpscReceiverExt};
|
2024-05-18 17:17:26 +01:00
|
|
|
use crate::ipc::{Command, Response};
|
|
|
|
use crate::style::load_css;
|
2025-05-18 15:17:09 +01:00
|
|
|
use crate::{Ironbar, spawn};
|
2024-05-18 17:17:26 +01:00
|
|
|
|
|
|
|
impl Ipc {
|
|
|
|
/// Starts the IPC server on its socket.
|
|
|
|
///
|
|
|
|
/// Once started, the server will begin accepting connections.
|
|
|
|
pub fn start(&self, application: &Application, ironbar: Rc<Ironbar>) {
|
|
|
|
let (cmd_tx, cmd_rx) = mpsc::channel(32);
|
|
|
|
let (res_tx, mut res_rx) = mpsc::channel(32);
|
|
|
|
|
|
|
|
let path = self.path.clone();
|
|
|
|
|
|
|
|
if path.exists() {
|
|
|
|
warn!("Socket already exists. Did Ironbar exit abruptly?");
|
|
|
|
warn!("Attempting IPC shutdown to allow binding to address");
|
|
|
|
Self::shutdown(&path);
|
|
|
|
}
|
|
|
|
|
|
|
|
spawn(async move {
|
|
|
|
info!("Starting IPC on {}", path.display());
|
|
|
|
|
|
|
|
let listener = match UnixListener::bind(&path) {
|
|
|
|
Ok(listener) => listener,
|
|
|
|
Err(err) => {
|
|
|
|
error!(
|
|
|
|
"{:?}",
|
|
|
|
Report::new(err).wrap_err("Unable to start IPC server")
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
loop {
|
|
|
|
match listener.accept().await {
|
|
|
|
Ok((stream, _addr)) => {
|
2025-06-24 23:04:24 +01:00
|
|
|
debug!("handling incoming connection");
|
2024-05-18 17:17:26 +01:00
|
|
|
if let Err(err) =
|
|
|
|
Self::handle_connection(stream, &cmd_tx, &mut res_rx).await
|
|
|
|
{
|
|
|
|
error!("{err:?}");
|
|
|
|
}
|
2025-06-24 23:04:24 +01:00
|
|
|
debug!("done");
|
2024-05-18 17:17:26 +01:00
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
error!("{err:?}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-05-26 22:17:26 +01:00
|
|
|
cmd_rx.recv_glib(application, move |application, command| {
|
2025-05-27 13:33:05 +01:00
|
|
|
let res = Self::handle_command(command, application, &ironbar);
|
2025-05-18 15:17:09 +01:00
|
|
|
res_tx.send_spawn(res);
|
2024-05-18 17:17:26 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Takes an incoming connections,
|
|
|
|
/// reads the command message, and sends the response.
|
|
|
|
///
|
|
|
|
/// The connection is closed once the response has been written.
|
|
|
|
async fn handle_connection(
|
|
|
|
mut stream: UnixStream,
|
|
|
|
cmd_tx: &Sender<Command>,
|
|
|
|
res_rx: &mut Receiver<Response>,
|
|
|
|
) -> Result<()> {
|
2025-06-24 23:04:24 +01:00
|
|
|
trace!("awaiting readable state");
|
|
|
|
stream.readable().await?;
|
2024-05-18 17:17:26 +01:00
|
|
|
|
2025-06-24 23:04:24 +01:00
|
|
|
let mut read_buffer = Vec::with_capacity(1024);
|
|
|
|
|
|
|
|
let mut reader = BufReader::new(&mut stream);
|
|
|
|
|
|
|
|
trace!("reading bytes");
|
|
|
|
let bytes = reader.read_until(b'\n', &mut read_buffer).await?;
|
|
|
|
debug!("read {} bytes", bytes);
|
2024-05-18 17:17:26 +01:00
|
|
|
|
|
|
|
// FIXME: Error on invalid command
|
|
|
|
let command = serde_json::from_slice::<Command>(&read_buffer[..bytes])?;
|
|
|
|
|
|
|
|
debug!("Received command: {command:?}");
|
|
|
|
|
2025-05-18 15:17:09 +01:00
|
|
|
cmd_tx.send_expect(command).await;
|
2024-05-18 17:17:26 +01:00
|
|
|
let res = res_rx
|
|
|
|
.recv()
|
|
|
|
.await
|
|
|
|
.unwrap_or(Response::Err { message: None });
|
|
|
|
|
2025-06-24 23:04:24 +01:00
|
|
|
let mut res = serde_json::to_vec(&res)?;
|
|
|
|
res.push(b'\n');
|
|
|
|
|
|
|
|
trace!("awaiting writable state");
|
|
|
|
stream.writable().await?;
|
|
|
|
|
|
|
|
debug!("writing {} bytes", res.len());
|
|
|
|
stream.write_all(&res).await?;
|
|
|
|
|
|
|
|
trace!("bytes written, shutting down stream");
|
|
|
|
stream.shutdown().await?;
|
2024-05-18 17:17:26 +01:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Takes an input command, runs it and returns with the appropriate response.
|
|
|
|
///
|
|
|
|
/// This runs on the main thread, allowing commands to interact with GTK.
|
|
|
|
fn handle_command(
|
|
|
|
command: Command,
|
|
|
|
application: &Application,
|
|
|
|
ironbar: &Rc<Ironbar>,
|
|
|
|
) -> Response {
|
|
|
|
match command {
|
|
|
|
Command::Ping => Response::Ok,
|
|
|
|
Command::Inspect => {
|
|
|
|
gtk::Window::set_interactive_debugging(true);
|
|
|
|
Response::Ok
|
|
|
|
}
|
|
|
|
Command::Reload => {
|
|
|
|
info!("Closing existing bars");
|
|
|
|
ironbar.bars.borrow_mut().clear();
|
|
|
|
|
|
|
|
let windows = application.windows();
|
|
|
|
for window in windows {
|
|
|
|
window.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
let wl = ironbar.clients.borrow_mut().wayland();
|
|
|
|
let outputs = wl.output_info_all();
|
|
|
|
|
|
|
|
ironbar.reload_config();
|
|
|
|
|
|
|
|
for output in outputs {
|
|
|
|
match crate::load_output_bars(ironbar, application, &output) {
|
|
|
|
Ok(mut bars) => ironbar.bars.borrow_mut().append(&mut bars),
|
|
|
|
Err(err) => error!("{err:?}"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Response::Ok
|
|
|
|
}
|
|
|
|
Command::LoadCss { path } => {
|
|
|
|
if path.exists() {
|
2024-11-05 13:06:23 +00:00
|
|
|
load_css(path, application.clone());
|
2024-05-18 17:17:26 +01:00
|
|
|
Response::Ok
|
|
|
|
} else {
|
|
|
|
Response::error("File not found")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Command::Var(cmd) => ironvar::handle_command(cmd),
|
2025-02-21 16:35:54 +00:00
|
|
|
Command::Bar(cmd) => bar::handle_command(&cmd, ironbar),
|
2024-05-18 17:17:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Shuts down the IPC server,
|
|
|
|
/// removing the socket file in the process.
|
|
|
|
///
|
|
|
|
/// Note this is static as the `Ipc` struct is not `Send`.
|
|
|
|
pub fn shutdown<P: AsRef<Path>>(path: P) {
|
|
|
|
fs::remove_file(&path).ok();
|
|
|
|
}
|
|
|
|
}
|