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

feat(ipc): commands for opening/closing popups

Also includes some refactoring around related GTK helper code
This commit is contained in:
Jake Stanger 2023-07-16 18:57:00 +01:00
parent c582bc3390
commit b7ee794bfc
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
30 changed files with 747 additions and 345 deletions

View file

@ -1,6 +1,7 @@
use std::path::PathBuf;
use clap::Subcommand;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Subcommand, Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
@ -52,4 +53,27 @@ pub enum Command {
/// Bar name to target.
bar_name: String,
},
/// Toggle a popup open/closed.
/// If opening this popup, and a different popup on the same bar is already open, the other is closed.
TogglePopup {
/// The name of the monitor the bar is located on.
bar_name: String,
/// The name of the widget.
name: String,
},
/// Open a popup, regardless of current state.
OpenPopup {
/// The name of the monitor the bar is located on.
bar_name: String,
/// The name of the widget.
name: String,
},
/// Close a popup, regardless of current state.
ClosePopup {
/// The name of the monitor the bar is located on.
bar_name: String,
},
}

View file

@ -3,21 +3,25 @@ pub mod commands;
pub mod responses;
mod server;
use std::path::PathBuf;
use std::cell::RefCell;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use tracing::warn;
use crate::GlobalState;
pub use commands::Command;
pub use responses::Response;
#[derive(Debug)]
pub struct Ipc {
path: PathBuf,
global_state: Rc<RefCell<GlobalState>>,
}
impl Ipc {
/// Creates a new IPC instance.
/// This can be used as both a server and client.
pub fn new() -> Self {
pub fn new(global_state: Rc<RefCell<GlobalState>>) -> Self {
let ipc_socket_file = std::env::var("XDG_RUNTIME_DIR")
.map_or_else(|_| PathBuf::from("/tmp"), PathBuf::from)
.join("ironbar-ipc.sock");
@ -28,6 +32,11 @@ impl Ipc {
Self {
path: ipc_socket_file,
global_state,
}
}
pub fn path(&self) -> &Path {
self.path.as_path()
}
}

View file

@ -1,20 +1,27 @@
use super::Ipc;
use crate::bridge_channel::BridgeChannel;
use crate::ipc::{Command, Response};
use crate::ironvar::get_variable_manager;
use crate::style::load_css;
use crate::{read_lock, send_async, try_send, write_lock};
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 std::fs;
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.
///
@ -29,7 +36,7 @@ impl Ipc {
if path.exists() {
warn!("Socket already exists. Did Ironbar exit abruptly?");
warn!("Attempting IPC shutdown to allow binding to address");
self.shutdown();
Self::shutdown(&path);
}
spawn(async move {
@ -63,8 +70,9 @@ impl Ipc {
});
let application = application.clone();
let global_state = self.global_state.clone();
bridge.recv(move |command| {
let res = Self::handle_command(command, &application);
let res = Self::handle_command(command, &application, &global_state);
try_send!(res_tx, res);
Continue(true)
});
@ -104,7 +112,11 @@ impl Ipc {
/// 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) -> Response {
fn handle_command(
command: Command,
application: &Application,
global_state: &Rc<RefCell<GlobalState>>,
) -> Response {
match command {
Command::Inspect => {
gtk::Window::set_interactive_debugging(true);
@ -117,7 +129,7 @@ impl Ipc {
window.close();
}
crate::load_interface(application);
crate::load_interface(application, global_state);
Response::Ok
}
@ -145,6 +157,71 @@ impl Ipc {
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();
@ -178,7 +255,9 @@ impl Ipc {
/// Shuts down the IPC server,
/// removing the socket file in the process.
pub fn shutdown(&self) {
fs::remove_file(&self.path).ok();
///
/// Note this is static as the `Ipc` struct is not `Send`.
pub fn shutdown<P: AsRef<Path>>(path: P) {
fs::remove_file(&path).ok();
}
}