mirror of
				https://github.com/Zedfrigg/ironbar.git
				synced 2025-10-31 13:41:54 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			263 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			263 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use std::cell::RefCell;
 | |
| use std::fs;
 | |
| use std::path::Path;
 | |
| use std::rc::Rc;
 | |
| 
 | |
| use color_eyre::{Report, Result};
 | |
| use glib::Continue;
 | |
| use gtk::prelude::*;
 | |
| use gtk::Application;
 | |
| use tokio::io::{AsyncReadExt, AsyncWriteExt};
 | |
| use tokio::net::{UnixListener, UnixStream};
 | |
| use tokio::spawn;
 | |
| use tokio::sync::mpsc::{self, Receiver, Sender};
 | |
| use tracing::{debug, error, info, warn};
 | |
| 
 | |
| use crate::bridge_channel::BridgeChannel;
 | |
| use crate::ipc::{Command, Response};
 | |
| use crate::ironvar::get_variable_manager;
 | |
| use crate::modules::PopupButton;
 | |
| use crate::style::load_css;
 | |
| use crate::{read_lock, send_async, try_send, write_lock, GlobalState};
 | |
| 
 | |
| use super::Ipc;
 | |
| 
 | |
| impl Ipc {
 | |
|     /// Starts the IPC server on its socket.
 | |
|     ///
 | |
|     /// Once started, the server will begin accepting connections.
 | |
|     pub fn start(&self, application: &Application) {
 | |
|         let bridge = BridgeChannel::<Command>::new();
 | |
|         let cmd_tx = bridge.create_sender();
 | |
|         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)) => {
 | |
|                         if let Err(err) =
 | |
|                             Self::handle_connection(stream, &cmd_tx, &mut res_rx).await
 | |
|                         {
 | |
|                             error!("{err:?}");
 | |
|                         }
 | |
|                     }
 | |
|                     Err(err) => {
 | |
|                         error!("{err:?}");
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         let application = application.clone();
 | |
|         let global_state = self.global_state.clone();
 | |
|         bridge.recv(move |command| {
 | |
|             let res = Self::handle_command(command, &application, &global_state);
 | |
|             try_send!(res_tx, res);
 | |
|             Continue(true)
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /// 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<()> {
 | |
|         let (mut stream_read, mut stream_write) = stream.split();
 | |
| 
 | |
|         let mut read_buffer = vec![0; 1024];
 | |
|         let bytes = stream_read.read(&mut read_buffer).await?;
 | |
| 
 | |
|         let command = serde_json::from_slice::<Command>(&read_buffer[..bytes])?;
 | |
| 
 | |
|         debug!("Received command: {command:?}");
 | |
| 
 | |
|         send_async!(cmd_tx, command);
 | |
|         let res = res_rx
 | |
|             .recv()
 | |
|             .await
 | |
|             .unwrap_or(Response::Err { message: None });
 | |
|         let res = serde_json::to_vec(&res)?;
 | |
| 
 | |
|         stream_write.write_all(&res).await?;
 | |
|         stream_write.shutdown().await?;
 | |
| 
 | |
|         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,
 | |
|         global_state: &Rc<RefCell<GlobalState>>,
 | |
|     ) -> Response {
 | |
|         match command {
 | |
|             Command::Inspect => {
 | |
|                 gtk::Window::set_interactive_debugging(true);
 | |
|                 Response::Ok
 | |
|             }
 | |
|             Command::Reload => {
 | |
|                 info!("Closing existing bars");
 | |
|                 let windows = application.windows();
 | |
|                 for window in windows {
 | |
|                     window.close();
 | |
|                 }
 | |
| 
 | |
|                 crate::load_interface(application, global_state);
 | |
| 
 | |
|                 Response::Ok
 | |
|             }
 | |
|             Command::Set { key, value } => {
 | |
|                 let variable_manager = get_variable_manager();
 | |
|                 let mut variable_manager = write_lock!(variable_manager);
 | |
|                 match variable_manager.set(key, value) {
 | |
|                     Ok(()) => Response::Ok,
 | |
|                     Err(err) => Response::error(&format!("{err}")),
 | |
|                 }
 | |
|             }
 | |
|             Command::Get { key } => {
 | |
|                 let variable_manager = get_variable_manager();
 | |
|                 let value = read_lock!(variable_manager).get(&key);
 | |
|                 match value {
 | |
|                     Some(value) => Response::OkValue { value },
 | |
|                     None => Response::error("Variable not found"),
 | |
|                 }
 | |
|             }
 | |
|             Command::LoadCss { path } => {
 | |
|                 if path.exists() {
 | |
|                     load_css(path);
 | |
|                     Response::Ok
 | |
|                 } else {
 | |
|                     Response::error("File not found")
 | |
|                 }
 | |
|             }
 | |
|             Command::TogglePopup { bar_name, name } => {
 | |
|                 let global_state = global_state.borrow();
 | |
|                 let response = global_state.with_popup_mut(&bar_name, |mut popup| {
 | |
|                     let current_widget = popup.current_widget();
 | |
|                     popup.hide();
 | |
| 
 | |
|                     let data = popup
 | |
|                         .cache
 | |
|                         .iter()
 | |
|                         .find(|(_, (module_name, _))| module_name == &name)
 | |
|                         .map(|module| (module, module.1 .1.buttons.first()));
 | |
| 
 | |
|                     match data {
 | |
|                         Some(((&id, _), Some(button))) if current_widget != Some(id) => {
 | |
|                             let button_id = button.popup_id();
 | |
|                             popup.show(id, button_id);
 | |
| 
 | |
|                             Response::Ok
 | |
|                         }
 | |
|                         Some((_, None)) => Response::error("Module has no popup functionality"),
 | |
|                         Some(_) => Response::Ok,
 | |
|                         None => Response::error("Invalid module name"),
 | |
|                     }
 | |
|                 });
 | |
| 
 | |
|                 response.unwrap_or_else(|| Response::error("Invalid monitor name"))
 | |
|             }
 | |
|             Command::OpenPopup { bar_name, name } => {
 | |
|                 let global_state = global_state.borrow();
 | |
|                 let response = global_state.with_popup_mut(&bar_name, |mut popup| {
 | |
|                     // only one popup per bar, so hide if open for another widget
 | |
|                     popup.hide();
 | |
| 
 | |
|                     let data = popup
 | |
|                         .cache
 | |
|                         .iter()
 | |
|                         .find(|(_, (module_name, _))| module_name == &name)
 | |
|                         .map(|module| (module, module.1 .1.buttons.first()));
 | |
| 
 | |
|                     match data {
 | |
|                         Some(((&id, _), Some(button))) => {
 | |
|                             let button_id = button.popup_id();
 | |
|                             popup.show(id, button_id);
 | |
| 
 | |
|                             Response::Ok
 | |
|                         }
 | |
|                         Some((_, None)) => Response::error("Module has no popup functionality"),
 | |
|                         None => Response::error("Invalid module name"),
 | |
|                     }
 | |
|                 });
 | |
| 
 | |
|                 response.unwrap_or_else(|| Response::error("Invalid monitor name"))
 | |
|             }
 | |
|             Command::ClosePopup { bar_name } => {
 | |
|                 let global_state = global_state.borrow();
 | |
|                 let popup_found = global_state
 | |
|                     .with_popup_mut(&bar_name, |mut popup| popup.hide())
 | |
|                     .is_some();
 | |
| 
 | |
|                 if popup_found {
 | |
|                     Response::Ok
 | |
|                 } else {
 | |
|                     Response::error("Invalid monitor name")
 | |
|                 }
 | |
|             }
 | |
|             Command::Ping => Response::Ok,
 | |
|             Command::SetVisible { bar_name, visible } => {
 | |
|                 let windows = application.windows();
 | |
|                 let found = windows
 | |
|                     .iter()
 | |
|                     .find(|window| window.widget_name() == bar_name);
 | |
| 
 | |
|                 if let Some(window) = found {
 | |
|                     window.set_visible(visible);
 | |
|                     Response::Ok
 | |
|                 } else {
 | |
|                     Response::error("Bar not found")
 | |
|                 }
 | |
|             }
 | |
|             Command::GetVisible { bar_name } => {
 | |
|                 let windows = application.windows();
 | |
|                 let found = windows
 | |
|                     .iter()
 | |
|                     .find(|window| window.widget_name() == bar_name);
 | |
| 
 | |
|                 if let Some(window) = found {
 | |
|                     Response::OkValue {
 | |
|                         value: window.is_visible().to_string(),
 | |
|                     }
 | |
|                 } else {
 | |
|                     Response::error("Bar not found")
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// 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();
 | |
|     }
 | |
| }
 |