feat(core): add restart option in context menu and improve exit handling

This commit is contained in:
Federico Terzi 2021-05-18 21:23:14 +02:00
parent 39758e2b9a
commit 4ab040da3c
9 changed files with 82 additions and 40 deletions

View File

@ -27,7 +27,14 @@ use espanso_ipc::IPCClient;
use espanso_path::Paths; use espanso_path::Paths;
use log::{error, info, warn}; use log::{error, info, warn};
use crate::{exit_code::{DAEMON_ALREADY_RUNNING, DAEMON_GENERAL_ERROR, DAEMON_SUCCESS, WORKER_EXIT_ALL_PROCESSES, WORKER_SUCCESS}, ipc::{create_ipc_client_to_worker, IPCEvent}, lock::{acquire_daemon_lock, acquire_worker_lock}}; use crate::{
exit_code::{
DAEMON_ALREADY_RUNNING, DAEMON_GENERAL_ERROR, DAEMON_SUCCESS, WORKER_EXIT_ALL_PROCESSES,
WORKER_RESTART, WORKER_SUCCESS,
},
ipc::{create_ipc_client_to_worker, IPCEvent},
lock::{acquire_daemon_lock, acquire_worker_lock},
};
use super::{CliModule, CliModuleArgs}; use super::{CliModule, CliModuleArgs};
@ -74,13 +81,11 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
spawn_worker(&paths, exit_notify.clone()); spawn_worker(&paths, exit_notify.clone());
ipc::initialize_and_spawn(&paths.runtime, exit_notify) ipc::initialize_and_spawn(&paths.runtime, exit_notify.clone())
.expect("unable to initialize ipc server for daemon"); .expect("unable to initialize ipc server for daemon");
// TODO: start file watcher thread // TODO: start file watcher thread
let mut exit_code: i32 = DAEMON_SUCCESS;
loop { loop {
select! { select! {
recv(exit_signal) -> code => { recv(exit_signal) -> code => {
@ -89,24 +94,28 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
match code { match code {
WORKER_EXIT_ALL_PROCESSES => { WORKER_EXIT_ALL_PROCESSES => {
info!("worker requested a general exit, quitting the daemon"); info!("worker requested a general exit, quitting the daemon");
break;
}
WORKER_RESTART => {
info!("worker requested a restart, spawning a new one...");
spawn_worker(&paths, exit_notify.clone());
} }
_ => { _ => {
error!("received unexpected exit code from worker {}, exiting", code); error!("received unexpected exit code from worker {}, exiting", code);
exit_code = code return code;
} }
} }
}, },
Err(err) => { Err(err) => {
error!("received error when unwrapping exit_code: {}", err); error!("received error when unwrapping exit_code: {}", err);
exit_code = DAEMON_GENERAL_ERROR; return DAEMON_GENERAL_ERROR;
}, },
} }
break;
}, },
} }
} }
exit_code DAEMON_SUCCESS
} }
fn terminate_worker_if_already_running(runtime_dir: &Path, worker_ipc: impl IPCClient<IPCEvent>) { fn terminate_worker_if_already_running(runtime_dir: &Path, worker_ipc: impl IPCClient<IPCEvent>) {

View File

@ -27,7 +27,7 @@ use espanso_ui::{UIRemote, event::UIEvent};
use log::info; use log::info;
use ui::selector::MatchSelectorAdapter; use ui::selector::MatchSelectorAdapter;
use crate::cli::worker::engine::{matcher::regex::RegexMatcherAdapterOptions, path::PathProviderAdapter}; use crate::{cli::worker::engine::{matcher::regex::RegexMatcherAdapterOptions, path::PathProviderAdapter}, engine::event::ExitMode};
use super::ui::icon::IconPaths; use super::ui::icon::IconPaths;
@ -48,7 +48,7 @@ pub fn initialize_and_spawn(
ui_remote: Box<dyn UIRemote>, ui_remote: Box<dyn UIRemote>,
exit_signal: Receiver<()>, exit_signal: Receiver<()>,
ui_event_receiver: Receiver<UIEvent>, ui_event_receiver: Receiver<UIEvent>,
) -> Result<JoinHandle<bool>> { ) -> Result<JoinHandle<ExitMode>> {
let handle = std::thread::Builder::new() let handle = std::thread::Builder::new()
.name("engine thread".to_string()) .name("engine thread".to_string())
.spawn(move || { .spawn(move || {
@ -154,12 +154,12 @@ pub fn initialize_and_spawn(
); );
let mut engine = crate::engine::Engine::new(&funnel, &mut processor, &dispatcher); let mut engine = crate::engine::Engine::new(&funnel, &mut processor, &dispatcher);
let exit_all_processes = engine.run(); let exit_mode = engine.run();
info!("engine eventloop has terminated, propagating exit event..."); info!("engine eventloop has terminated, propagating exit event...");
ui_remote.exit(); ui_remote.exit();
exit_all_processes exit_mode
})?; })?;
Ok(handle) Ok(handle)

View File

@ -19,7 +19,7 @@
use crossbeam::channel::{Receiver, Select, SelectedOperation}; use crossbeam::channel::{Receiver, Select, SelectedOperation};
use crate::engine::{event::{Event, EventType}, funnel}; use crate::engine::{event::{Event, EventType, ExitMode}, funnel};
use super::sequencer::Sequencer; use super::sequencer::Sequencer;
@ -48,7 +48,7 @@ impl<'a> funnel::Source<'a> for ExitSource<'a> {
.expect("unable to select data from ExitSource receiver"); .expect("unable to select data from ExitSource receiver");
Event { Event {
source_id: self.sequencer.next_id(), source_id: self.sequencer.next_id(),
etype: EventType::ExitRequested(false), etype: EventType::ExitRequested(ExitMode::Exit),
} }
} }
} }

View File

@ -20,7 +20,14 @@
use crossbeam::channel::unbounded; use crossbeam::channel::unbounded;
use log::{error, info}; use log::{error, info};
use crate::{exit_code::{WORKER_ALREADY_RUNNING, WORKER_EXIT_ALL_PROCESSES, WORKER_GENERAL_ERROR, WORKER_SUCCESS}, lock::acquire_worker_lock}; use crate::{
engine::event::ExitMode,
exit_code::{
WORKER_ALREADY_RUNNING, WORKER_EXIT_ALL_PROCESSES, WORKER_GENERAL_ERROR, WORKER_RESTART,
WORKER_SUCCESS,
},
lock::acquire_worker_lock,
};
use self::ui::util::convert_icon_paths_to_tray_vec; use self::ui::util::convert_icon_paths_to_tray_vec;
@ -108,15 +115,20 @@ fn worker_main(args: CliModuleArgs) -> i32 {
info!("waiting for engine exit mode..."); info!("waiting for engine exit mode...");
match engine_handle.join() { match engine_handle.join() {
Ok(exit_all_processes) => { Ok(mode) => match mode {
if exit_all_processes { ExitMode::Exit => {
info!("exiting worker process and daemon...");
return WORKER_EXIT_ALL_PROCESSES;
} else {
info!("exiting worker process..."); info!("exiting worker process...");
return WORKER_SUCCESS; return WORKER_SUCCESS;
} }
} ExitMode::ExitAllProcesses => {
info!("exiting worker process and daemon...");
return WORKER_EXIT_ALL_PROCESSES;
}
ExitMode::RestartWorker => {
info!("exiting worker (to be restarted)");
return WORKER_RESTART;
}
},
Err(err) => { Err(err) => {
error!("unable to read engine exit mode: {:?}", err); error!("unable to read engine exit mode: {:?}", err);
return WORKER_GENERAL_ERROR; return WORKER_GENERAL_ERROR;

View File

@ -48,8 +48,8 @@ impl Event {
pub enum EventType { pub enum EventType {
NOOP, NOOP,
ProcessingError(String), ProcessingError(String),
ExitRequested(bool), // If true, exit also the daemon process and not just the worker ExitRequested(ExitMode),
Exit(bool), Exit(ExitMode),
// Inputs // Inputs
Keyboard(input::KeyboardEvent), Keyboard(input::KeyboardEvent),
@ -82,4 +82,11 @@ pub enum EventType {
// UI // UI
ShowContextMenu(ui::ShowContextMenuEvent), ShowContextMenu(ui::ShowContextMenuEvent),
}
#[derive(Debug, Clone)]
pub enum ExitMode {
Exit,
ExitAllProcesses,
RestartWorker,
} }

View File

@ -19,7 +19,7 @@
use log::{debug}; use log::{debug};
use self::{dispatch::Dispatcher, event::{Event, EventType}, funnel::{Funnel, FunnelResult}, process::Processor}; use self::{dispatch::Dispatcher, event::{Event, EventType, ExitMode}, funnel::{Funnel, FunnelResult}, process::Processor};
pub mod dispatch; pub mod dispatch;
pub mod event; pub mod event;
@ -41,15 +41,15 @@ impl <'a> Engine<'a> {
} }
} }
pub fn run(&mut self) -> bool { pub fn run(&mut self) -> ExitMode {
loop { loop {
match self.funnel.receive() { match self.funnel.receive() {
FunnelResult::Event(event) => { FunnelResult::Event(event) => {
let processed_events = self.processor.process(event); let processed_events = self.processor.process(event);
for event in processed_events { for event in processed_events {
if let EventType::Exit(exit_all_processes) = &event.etype { if let EventType::Exit(mode) = &event.etype {
debug!("exit event received, exiting engine"); debug!("exit event received with mode {:?}, exiting engine", mode);
return *exit_all_processes; return mode.clone();
} }
self.dispatcher.dispatch(event); self.dispatcher.dispatch(event);
@ -57,7 +57,7 @@ impl <'a> Engine<'a> {
} }
FunnelResult::EndOfStream => { FunnelResult::EndOfStream => {
debug!("end of stream received"); debug!("end of stream received");
return false; return ExitMode::Exit;
} }
} }
} }

View File

@ -20,10 +20,11 @@
use super::super::Middleware; use super::super::Middleware;
use crate::engine::event::{ use crate::engine::event::{
ui::{MenuItem, ShowContextMenuEvent, SimpleMenuItem}, ui::{MenuItem, ShowContextMenuEvent, SimpleMenuItem},
Event, EventType, Event, EventType, ExitMode,
}; };
const CONTEXT_ITEM_EXIT: u32 = 0; const CONTEXT_ITEM_EXIT: u32 = 0;
const CONTEXT_ITEM_RELOAD: u32 = 1;
pub struct ContextMenuMiddleware {} pub struct ContextMenuMiddleware {}
@ -51,18 +52,30 @@ impl Middleware for ContextMenuMiddleware {
event.source_id, event.source_id,
EventType::ShowContextMenu(ShowContextMenuEvent { EventType::ShowContextMenu(ShowContextMenuEvent {
// TODO: add actual entries // TODO: add actual entries
items: vec![MenuItem::Simple(SimpleMenuItem { items: vec![
id: CONTEXT_ITEM_EXIT, MenuItem::Simple(SimpleMenuItem {
label: "Exit espanso".to_string(), id: CONTEXT_ITEM_RELOAD,
})], label: "Reload config".to_string(),
}),
MenuItem::Separator,
MenuItem::Simple(SimpleMenuItem {
id: CONTEXT_ITEM_EXIT,
label: "Exit espanso".to_string(),
}),
],
}), }),
); );
} }
EventType::ContextMenuClicked(context_click_event) => { EventType::ContextMenuClicked(context_click_event) => {
match context_click_event.context_item_id { match context_click_event.context_item_id {
CONTEXT_ITEM_EXIT => { CONTEXT_ITEM_EXIT => Event::caused_by(
Event::caused_by(event.source_id, EventType::ExitRequested(true)) event.source_id,
} EventType::ExitRequested(ExitMode::ExitAllProcesses),
),
CONTEXT_ITEM_RELOAD => Event::caused_by(
event.source_id,
EventType::ExitRequested(ExitMode::RestartWorker),
),
custom => { custom => {
// TODO: handle dynamic items // TODO: handle dynamic items
todo!() todo!()

View File

@ -36,9 +36,9 @@ impl Middleware for ExitMiddleware {
} }
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
if let EventType::ExitRequested(exit_all_processes) = &event.etype { if let EventType::ExitRequested(mode) = &event.etype {
debug!("received ExitRequested event with 'exit_all_processes: {}', dispatching exit", exit_all_processes); debug!("received ExitRequested event with mode: {:?}, dispatching exit", mode);
return Event::caused_by(event.source_id, EventType::Exit(*exit_all_processes)); return Event::caused_by(event.source_id, EventType::Exit(mode.clone()));
} }
event event

View File

@ -21,6 +21,7 @@ pub const WORKER_SUCCESS: i32 = 0;
pub const WORKER_ALREADY_RUNNING: i32 = 1; pub const WORKER_ALREADY_RUNNING: i32 = 1;
pub const WORKER_GENERAL_ERROR: i32 = 2; pub const WORKER_GENERAL_ERROR: i32 = 2;
pub const WORKER_EXIT_ALL_PROCESSES: i32 = 101; pub const WORKER_EXIT_ALL_PROCESSES: i32 = 101;
pub const WORKER_RESTART: i32 = 102;
pub const DAEMON_SUCCESS: i32 = 0; pub const DAEMON_SUCCESS: i32 = 0;
pub const DAEMON_ALREADY_RUNNING: i32 = 1; pub const DAEMON_ALREADY_RUNNING: i32 = 1;