Add eww windows
command and restructure daemon response.
Daemon response is now a dedicated type that contains information about failure / success, and used in more commands, such as `open` and `close`. Thus, this fixes #90 and also fixes #89. For #90, the decision has been made to not make this independent of the daemon, as that makes it possible to provide more information, and because in most cases, users will have the daemon running when checking windows anyways.
This commit is contained in:
parent
ff756da2c5
commit
8530f90a7c
7 changed files with 156 additions and 69 deletions
112
src/app.rs
112
src/app.rs
|
@ -14,26 +14,56 @@ use itertools::Itertools;
|
|||
use std::collections::HashMap;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
/// Response that the app may send as a response to a event.
|
||||
/// This is used in `DaemonCommand`s that contain a response sender.
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, derive_more::Display)]
|
||||
pub enum DaemonResponse {
|
||||
Success(String),
|
||||
Failure(String),
|
||||
}
|
||||
|
||||
impl DaemonResponse {
|
||||
pub fn is_success(&self) -> bool {
|
||||
match self {
|
||||
DaemonResponse::Success(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_failure(&self) -> bool {
|
||||
!self.is_success()
|
||||
}
|
||||
}
|
||||
|
||||
pub type DaemonResponseSender = tokio::sync::mpsc::UnboundedSender<DaemonResponse>;
|
||||
pub type DaemonResponseReceiver = tokio::sync::mpsc::UnboundedReceiver<DaemonResponse>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EwwCommand {
|
||||
pub enum DaemonCommand {
|
||||
NoOp,
|
||||
UpdateVars(Vec<(VarName, PrimitiveValue)>),
|
||||
ReloadConfig(config::EwwConfig),
|
||||
ReloadCss(String),
|
||||
OpenMany(Vec<WindowName>),
|
||||
OpenMany {
|
||||
windows: Vec<WindowName>,
|
||||
sender: DaemonResponseSender,
|
||||
},
|
||||
OpenWindow {
|
||||
window_name: WindowName,
|
||||
pos: Option<Coords>,
|
||||
size: Option<Coords>,
|
||||
anchor: Option<AnchorPoint>,
|
||||
sender: DaemonResponseSender,
|
||||
},
|
||||
CloseWindow {
|
||||
window_name: WindowName,
|
||||
sender: DaemonResponseSender,
|
||||
},
|
||||
KillServer,
|
||||
CloseAll,
|
||||
PrintState(tokio::sync::mpsc::UnboundedSender<String>),
|
||||
PrintDebug(tokio::sync::mpsc::UnboundedSender<String>),
|
||||
PrintState(DaemonResponseSender),
|
||||
PrintDebug(DaemonResponseSender),
|
||||
PrintWindows(DaemonResponseSender),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -55,68 +85,91 @@ pub struct App {
|
|||
pub eww_config: config::EwwConfig,
|
||||
pub windows: HashMap<WindowName, EwwWindow>,
|
||||
pub css_provider: gtk::CssProvider,
|
||||
pub app_evt_send: UnboundedSender<EwwCommand>,
|
||||
pub app_evt_send: UnboundedSender<DaemonCommand>,
|
||||
#[debug_stub = "ScriptVarHandler(...)"]
|
||||
pub script_var_handler: ScriptVarHandlerHandle,
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Handle an EwwCommand event.
|
||||
pub fn handle_command(&mut self, event: EwwCommand) {
|
||||
/// Handle a DaemonCommand event.
|
||||
pub fn handle_command(&mut self, event: DaemonCommand) {
|
||||
log::debug!("Handling event: {:?}", &event);
|
||||
let result: Result<_> = try {
|
||||
match event {
|
||||
EwwCommand::NoOp => {}
|
||||
EwwCommand::UpdateVars(mappings) => {
|
||||
DaemonCommand::NoOp => {}
|
||||
DaemonCommand::UpdateVars(mappings) => {
|
||||
for (var_name, new_value) in mappings {
|
||||
self.update_state(var_name, new_value)?;
|
||||
}
|
||||
}
|
||||
EwwCommand::ReloadConfig(config) => {
|
||||
DaemonCommand::ReloadConfig(config) => {
|
||||
self.reload_all_windows(config)?;
|
||||
}
|
||||
EwwCommand::ReloadCss(css) => {
|
||||
DaemonCommand::ReloadCss(css) => {
|
||||
self.load_css(&css)?;
|
||||
}
|
||||
EwwCommand::KillServer => {
|
||||
DaemonCommand::KillServer => {
|
||||
log::info!("Received kill command, stopping server!");
|
||||
self.stop_application();
|
||||
let _ = crate::application_lifecycle::send_exit();
|
||||
}
|
||||
EwwCommand::CloseAll => {
|
||||
DaemonCommand::CloseAll => {
|
||||
log::info!("Received close command, closing all windows");
|
||||
for (window_name, _window) in self.windows.clone() {
|
||||
self.close_window(&window_name)?;
|
||||
}
|
||||
}
|
||||
EwwCommand::OpenMany(windows) => {
|
||||
for window in windows {
|
||||
self.open_window(&window, None, None, None)?;
|
||||
DaemonCommand::OpenMany { windows, sender } => {
|
||||
let result = windows
|
||||
.iter()
|
||||
.map(|w| self.open_window(w, None, None, None))
|
||||
.collect::<Result<()>>();
|
||||
respond_with_error(sender, result)?;
|
||||
}
|
||||
}
|
||||
EwwCommand::OpenWindow {
|
||||
DaemonCommand::OpenWindow {
|
||||
window_name,
|
||||
pos,
|
||||
size,
|
||||
anchor,
|
||||
sender,
|
||||
} => {
|
||||
self.open_window(&window_name, pos, size, anchor)?;
|
||||
let result = self.open_window(&window_name, pos, size, anchor);
|
||||
respond_with_error(sender, result)?;
|
||||
}
|
||||
EwwCommand::CloseWindow { window_name } => {
|
||||
self.close_window(&window_name)?;
|
||||
DaemonCommand::CloseWindow { window_name, sender } => {
|
||||
let result = self.close_window(&window_name);
|
||||
respond_with_error(sender, result)?;
|
||||
}
|
||||
EwwCommand::PrintState(sender) => {
|
||||
DaemonCommand::PrintState(sender) => {
|
||||
let output = self
|
||||
.eww_state
|
||||
.get_variables()
|
||||
.iter()
|
||||
.map(|(key, value)| format!("{}: {}", key, value))
|
||||
.join("\n");
|
||||
sender.send(output).context("sending response from main thread")?
|
||||
sender
|
||||
.send(DaemonResponse::Success(output))
|
||||
.context("sending response from main thread")?
|
||||
}
|
||||
EwwCommand::PrintDebug(sender) => {
|
||||
DaemonCommand::PrintWindows(sender) => {
|
||||
let output = self
|
||||
.eww_config
|
||||
.get_windows()
|
||||
.keys()
|
||||
.map(|window_name| {
|
||||
let is_open = self.windows.contains_key(window_name);
|
||||
format!("{}{}", if is_open { "*" } else { "" }, window_name)
|
||||
})
|
||||
.join("\n");
|
||||
sender
|
||||
.send(DaemonResponse::Success(output))
|
||||
.context("sending response from main thread")?
|
||||
}
|
||||
DaemonCommand::PrintDebug(sender) => {
|
||||
let output = format!("state: {:#?}\n\nconfig: {:#?}", &self.eww_state, &self.eww_config);
|
||||
sender.send(output).context("sending response from main thread")?
|
||||
sender
|
||||
.send(DaemonResponse::Success(output))
|
||||
.context("sending response from main thread")?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -329,3 +382,12 @@ fn get_monitor_geometry(n: i32) -> gdk::Rectangle {
|
|||
.get_default_screen()
|
||||
.get_monitor_geometry(n)
|
||||
}
|
||||
|
||||
/// In case of an Err, send the error message to a sender.
|
||||
fn respond_with_error<T>(sender: DaemonResponseSender, result: Result<T>) -> Result<()> {
|
||||
match result {
|
||||
Ok(_) => sender.send(DaemonResponse::Success(String::new())),
|
||||
Err(e) => sender.send(DaemonResponse::Failure(format!("{:?}", e))),
|
||||
}
|
||||
.context("sending response from main thread")
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::process::Stdio;
|
||||
|
||||
use crate::opts::{self, ActionClientOnly};
|
||||
use crate::{
|
||||
app,
|
||||
opts::{self, ActionClientOnly},
|
||||
};
|
||||
use anyhow::*;
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
|
@ -20,7 +23,7 @@ pub fn handle_client_only_action(action: ActionClientOnly) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn do_server_call(mut stream: UnixStream, action: opts::ActionWithServer) -> Result<Option<String>> {
|
||||
pub fn do_server_call(mut stream: UnixStream, action: opts::ActionWithServer) -> Result<Option<app::DaemonResponse>> {
|
||||
log::info!("Forwarding options to server");
|
||||
stream
|
||||
.set_nonblocking(false)
|
||||
|
@ -36,13 +39,16 @@ pub fn do_server_call(mut stream: UnixStream, action: opts::ActionWithServer) ->
|
|||
.write_all(&message_bytes)
|
||||
.context("Failed to write command to IPC stream")?;
|
||||
|
||||
let mut buf = String::new();
|
||||
let mut buf = Vec::new();
|
||||
stream
|
||||
.set_read_timeout(Some(std::time::Duration::from_millis(100)))
|
||||
.context("Failed to set read timeout")?;
|
||||
stream
|
||||
.read_to_string(&mut buf)
|
||||
.context("Error reading response from server")?;
|
||||
stream.read_to_end(&mut buf).context("Error reading response from server")?;
|
||||
|
||||
Ok(if buf.is_empty() { None } else { Some(buf) })
|
||||
Ok(if buf.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let buf = bincode::deserialize(&buf)?;
|
||||
Some(buf)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use tokio::{
|
|||
sync::mpsc::*,
|
||||
};
|
||||
|
||||
pub async fn run_server(evt_send: UnboundedSender<app::EwwCommand>) -> Result<()> {
|
||||
pub async fn run_server(evt_send: UnboundedSender<app::DaemonCommand>) -> Result<()> {
|
||||
let listener = tokio::net::UnixListener::bind(&*crate::IPC_SOCKET_PATH)?;
|
||||
log::info!("IPC server initialized");
|
||||
crate::loop_select_exiting! {
|
||||
|
@ -25,21 +25,22 @@ pub async fn run_server(evt_send: UnboundedSender<app::EwwCommand>) -> Result<()
|
|||
}
|
||||
|
||||
/// Handle a single IPC connection from start to end.
|
||||
async fn handle_connection(mut stream: tokio::net::UnixStream, evt_send: UnboundedSender<app::EwwCommand>) -> Result<()> {
|
||||
async fn handle_connection(mut stream: tokio::net::UnixStream, evt_send: UnboundedSender<app::DaemonCommand>) -> Result<()> {
|
||||
let (mut stream_read, mut stream_write) = stream.split();
|
||||
|
||||
let action: opts::ActionWithServer = read_action_from_stream(&mut stream_read).await?;
|
||||
|
||||
log::info!("received command from IPC: {:?}", &action);
|
||||
|
||||
let (command, maybe_response_recv) = action.into_eww_command();
|
||||
let (command, maybe_response_recv) = action.into_daemon_command();
|
||||
|
||||
evt_send.send(command)?;
|
||||
|
||||
if let Some(mut response_recv) = maybe_response_recv {
|
||||
log::info!("Waiting for response for IPC client");
|
||||
if let Ok(Some(response)) = tokio::time::timeout(Duration::from_millis(100), response_recv.recv()).await {
|
||||
let result = &stream_write.write_all(response.as_bytes()).await;
|
||||
let response = bincode::serialize(&response)?;
|
||||
let result = &stream_write.write_all(&response).await;
|
||||
crate::print_result_err!("sending text response to ipc client", &result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,6 +69,9 @@ fn main() {
|
|||
client::do_server_call(stream, action).context("Error while forwarding command to server")?;
|
||||
if let Some(response) = response {
|
||||
println!("{}", response);
|
||||
if response.is_failure() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
|
|
47
src/opts.rs
47
src/opts.rs
|
@ -99,6 +99,10 @@ pub enum ActionWithServer {
|
|||
#[structopt(name = "state")]
|
||||
ShowState,
|
||||
|
||||
/// Print the names of all configured windows. Windows with a * in front of them are currently opened.
|
||||
#[structopt(name = "windows")]
|
||||
ShowWindows,
|
||||
|
||||
/// Print out the widget structure as seen by eww.
|
||||
///
|
||||
/// This may be useful if you are facing issues with how eww is interpreting your configuration,
|
||||
|
@ -129,38 +133,49 @@ fn parse_var_update_arg(s: &str) -> Result<(VarName, PrimitiveValue)> {
|
|||
}
|
||||
|
||||
impl ActionWithServer {
|
||||
pub fn into_eww_command(self) -> (app::EwwCommand, Option<tokio::sync::mpsc::UnboundedReceiver<String>>) {
|
||||
pub fn into_daemon_command(self) -> (app::DaemonCommand, Option<app::DaemonResponseReceiver>) {
|
||||
let command = match self {
|
||||
ActionWithServer::Update { mappings } => app::DaemonCommand::UpdateVars(mappings.into_iter().collect()),
|
||||
|
||||
ActionWithServer::KillServer => app::DaemonCommand::KillServer,
|
||||
ActionWithServer::CloseAll => app::DaemonCommand::CloseAll,
|
||||
ActionWithServer::Ping => {
|
||||
let (send, recv) = tokio::sync::mpsc::unbounded_channel();
|
||||
let _ = send.send("pong".to_owned());
|
||||
return (app::EwwCommand::NoOp, Some(recv));
|
||||
let _ = send.send(app::DaemonResponse::Success("pong".to_owned()));
|
||||
return (app::DaemonCommand::NoOp, Some(recv));
|
||||
}
|
||||
ActionWithServer::OpenMany { windows } => {
|
||||
return with_response_channel(|sender| app::DaemonCommand::OpenMany { windows, sender });
|
||||
}
|
||||
ActionWithServer::Update { mappings } => app::EwwCommand::UpdateVars(mappings.into_iter().collect()),
|
||||
ActionWithServer::OpenMany { windows } => app::EwwCommand::OpenMany(windows),
|
||||
ActionWithServer::OpenWindow {
|
||||
window_name,
|
||||
pos,
|
||||
size,
|
||||
anchor,
|
||||
} => app::EwwCommand::OpenWindow {
|
||||
} => {
|
||||
return with_response_channel(|sender| app::DaemonCommand::OpenWindow {
|
||||
window_name,
|
||||
pos,
|
||||
size,
|
||||
anchor,
|
||||
},
|
||||
ActionWithServer::CloseWindow { window_name } => app::EwwCommand::CloseWindow { window_name },
|
||||
ActionWithServer::KillServer => app::EwwCommand::KillServer,
|
||||
ActionWithServer::CloseAll => app::EwwCommand::CloseAll,
|
||||
ActionWithServer::ShowState => {
|
||||
let (send, recv) = tokio::sync::mpsc::unbounded_channel();
|
||||
return (app::EwwCommand::PrintState(send), Some(recv));
|
||||
sender,
|
||||
})
|
||||
}
|
||||
ActionWithServer::ShowDebug => {
|
||||
let (send, recv) = tokio::sync::mpsc::unbounded_channel();
|
||||
return (app::EwwCommand::PrintDebug(send), Some(recv));
|
||||
ActionWithServer::CloseWindow { window_name } => {
|
||||
return with_response_channel(|sender| app::DaemonCommand::CloseWindow { window_name, sender });
|
||||
}
|
||||
ActionWithServer::ShowWindows => return with_response_channel(app::DaemonCommand::PrintWindows),
|
||||
ActionWithServer::ShowState => return with_response_channel(app::DaemonCommand::PrintState),
|
||||
ActionWithServer::ShowDebug => return with_response_channel(app::DaemonCommand::PrintDebug),
|
||||
};
|
||||
(command, None)
|
||||
}
|
||||
}
|
||||
|
||||
fn with_response_channel<T, O, F>(f: F) -> (O, Option<tokio::sync::mpsc::UnboundedReceiver<T>>)
|
||||
where
|
||||
F: FnOnce(tokio::sync::mpsc::UnboundedSender<T>) -> O,
|
||||
{
|
||||
let (sender, recv) = tokio::sync::mpsc::unbounded_channel();
|
||||
(f(sender), Some(recv))
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
value::{PrimitiveValue, VarName},
|
||||
};
|
||||
use anyhow::*;
|
||||
use app::EwwCommand;
|
||||
use app::DaemonCommand;
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, BufReader},
|
||||
|
@ -15,7 +15,7 @@ use tokio_util::sync::CancellationToken;
|
|||
|
||||
/// Initialize the script var handler, and return a handle to that handler, which can be used to control
|
||||
/// the script var execution.
|
||||
pub fn init(evt_send: UnboundedSender<EwwCommand>) -> ScriptVarHandlerHandle {
|
||||
pub fn init(evt_send: UnboundedSender<DaemonCommand>) -> ScriptVarHandlerHandle {
|
||||
let (msg_send, mut msg_recv) = tokio::sync::mpsc::unbounded_channel();
|
||||
let handle = ScriptVarHandlerHandle { msg_send };
|
||||
std::thread::spawn(move || {
|
||||
|
@ -116,12 +116,12 @@ impl ScriptVarHandler {
|
|||
}
|
||||
|
||||
struct PollVarHandler {
|
||||
evt_send: UnboundedSender<EwwCommand>,
|
||||
evt_send: UnboundedSender<DaemonCommand>,
|
||||
poll_handles: HashMap<VarName, CancellationToken>,
|
||||
}
|
||||
|
||||
impl PollVarHandler {
|
||||
fn new(evt_send: UnboundedSender<EwwCommand>) -> Result<Self> {
|
||||
fn new(evt_send: UnboundedSender<DaemonCommand>) -> Result<Self> {
|
||||
let handler = PollVarHandler {
|
||||
evt_send,
|
||||
poll_handles: HashMap::new(),
|
||||
|
@ -139,7 +139,7 @@ impl PollVarHandler {
|
|||
_ = cancellation_token.cancelled() => break,
|
||||
_ = tokio::time::sleep(var.interval) => {
|
||||
let result: Result<_> = try {
|
||||
evt_send.send(app::EwwCommand::UpdateVars(vec![(var.name.clone(), var.run_once()?)]))?;
|
||||
evt_send.send(app::DaemonCommand::UpdateVars(vec![(var.name.clone(), var.run_once()?)]))?;
|
||||
};
|
||||
crate::print_result_err!("while running script-var command", &result);
|
||||
}
|
||||
|
@ -166,12 +166,12 @@ impl Drop for PollVarHandler {
|
|||
}
|
||||
|
||||
struct TailVarHandler {
|
||||
evt_send: UnboundedSender<EwwCommand>,
|
||||
evt_send: UnboundedSender<DaemonCommand>,
|
||||
tail_process_handles: HashMap<VarName, CancellationToken>,
|
||||
}
|
||||
|
||||
impl TailVarHandler {
|
||||
fn new(evt_send: UnboundedSender<EwwCommand>) -> Result<Self> {
|
||||
fn new(evt_send: UnboundedSender<DaemonCommand>) -> Result<Self> {
|
||||
let handler = TailVarHandler {
|
||||
evt_send,
|
||||
tail_process_handles: HashMap::new(),
|
||||
|
@ -199,7 +199,7 @@ impl TailVarHandler {
|
|||
_ = cancellation_token.cancelled() => break,
|
||||
Ok(Some(line)) = stdout_lines.next_line() => {
|
||||
let new_value = PrimitiveValue::from_string(line.to_owned());
|
||||
evt_send.send(EwwCommand::UpdateVars(vec![(var.name.to_owned(), new_value)]))?;
|
||||
evt_send.send(DaemonCommand::UpdateVars(vec![(var.name.to_owned(), new_value)]))?;
|
||||
}
|
||||
else => break,
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ pub fn initialize_server() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn init_async_part(config_file_path: PathBuf, scss_file_path: PathBuf, ui_send: UnboundedSender<app::EwwCommand>) {
|
||||
fn init_async_part(config_file_path: PathBuf, scss_file_path: PathBuf, ui_send: UnboundedSender<app::DaemonCommand>) {
|
||||
std::thread::spawn(move || {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
|
@ -91,7 +91,7 @@ fn init_async_part(config_file_path: PathBuf, scss_file_path: PathBuf, ui_send:
|
|||
let _ = crate::application_lifecycle::recv_exit().await;
|
||||
log::info!("Forward task received exit event");
|
||||
// Then forward that to the application
|
||||
let _ = ui_send.send(app::EwwCommand::KillServer);
|
||||
let _ = ui_send.send(app::DaemonCommand::KillServer);
|
||||
})
|
||||
};
|
||||
|
||||
|
@ -108,7 +108,7 @@ fn init_async_part(config_file_path: PathBuf, scss_file_path: PathBuf, ui_send:
|
|||
async fn run_filewatch<P: AsRef<Path>>(
|
||||
config_file_path: P,
|
||||
scss_file_path: P,
|
||||
evt_send: UnboundedSender<app::EwwCommand>,
|
||||
evt_send: UnboundedSender<app::DaemonCommand>,
|
||||
) -> Result<()> {
|
||||
let mut inotify = inotify::Inotify::init().context("Failed to initialize inotify")?;
|
||||
let config_file_descriptor = inotify
|
||||
|
@ -127,11 +127,11 @@ async fn run_filewatch<P: AsRef<Path>>(
|
|||
if event.wd == config_file_descriptor {
|
||||
log::info!("Reloading eww configuration");
|
||||
let new_eww_config = config::EwwConfig::read_from_file(config_file_path.as_ref())?;
|
||||
evt_send.send(app::EwwCommand::ReloadConfig(new_eww_config))?;
|
||||
evt_send.send(app::DaemonCommand::ReloadConfig(new_eww_config))?;
|
||||
} else if event.wd == scss_file_descriptor {
|
||||
log::info!("reloading eww css file");
|
||||
let eww_css = crate::util::parse_scss_from_file(scss_file_path.as_ref())?;
|
||||
evt_send.send(app::EwwCommand::ReloadCss(eww_css))?;
|
||||
evt_send.send(app::DaemonCommand::ReloadCss(eww_css))?;
|
||||
} else {
|
||||
eprintln!("Got inotify event for unknown thing: {:?}", event);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue