feat(core): add restart option in context menu and improve exit handling
This commit is contained in:
		
							parent
							
								
									39758e2b9a
								
							
						
					
					
						commit
						4ab040da3c
					
				| 
						 | 
				
			
			@ -27,7 +27,14 @@ use espanso_ipc::IPCClient;
 | 
			
		|||
use espanso_path::Paths;
 | 
			
		||||
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};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -74,13 +81,11 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
 | 
			
		|||
 | 
			
		||||
  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");
 | 
			
		||||
 | 
			
		||||
  // TODO: start file watcher thread
 | 
			
		||||
 | 
			
		||||
  let mut exit_code: i32 = DAEMON_SUCCESS;
 | 
			
		||||
 | 
			
		||||
  loop {
 | 
			
		||||
    select! {
 | 
			
		||||
      recv(exit_signal) -> code => {
 | 
			
		||||
| 
						 | 
				
			
			@ -89,24 +94,28 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
 | 
			
		|||
            match code {
 | 
			
		||||
              WORKER_EXIT_ALL_PROCESSES => {
 | 
			
		||||
                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);
 | 
			
		||||
                exit_code = code
 | 
			
		||||
                return code;
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          Err(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>) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,7 @@ use espanso_ui::{UIRemote, event::UIEvent};
 | 
			
		|||
use log::info;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +48,7 @@ pub fn initialize_and_spawn(
 | 
			
		|||
  ui_remote: Box<dyn UIRemote>,
 | 
			
		||||
  exit_signal: Receiver<()>,
 | 
			
		||||
  ui_event_receiver: Receiver<UIEvent>,
 | 
			
		||||
) -> Result<JoinHandle<bool>> {
 | 
			
		||||
) -> Result<JoinHandle<ExitMode>> {
 | 
			
		||||
  let handle = std::thread::Builder::new()
 | 
			
		||||
    .name("engine thread".to_string())
 | 
			
		||||
    .spawn(move || {
 | 
			
		||||
| 
						 | 
				
			
			@ -154,12 +154,12 @@ pub fn initialize_and_spawn(
 | 
			
		|||
      );
 | 
			
		||||
 | 
			
		||||
      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...");
 | 
			
		||||
      ui_remote.exit();
 | 
			
		||||
 | 
			
		||||
      exit_all_processes
 | 
			
		||||
      exit_mode
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
  Ok(handle)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,7 @@
 | 
			
		|||
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +48,7 @@ impl<'a> funnel::Source<'a> for ExitSource<'a> {
 | 
			
		|||
      .expect("unable to select data from ExitSource receiver");
 | 
			
		||||
    Event {
 | 
			
		||||
      source_id: self.sequencer.next_id(),
 | 
			
		||||
      etype: EventType::ExitRequested(false),
 | 
			
		||||
      etype: EventType::ExitRequested(ExitMode::Exit),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +20,14 @@
 | 
			
		|||
use crossbeam::channel::unbounded;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -108,15 +115,20 @@ fn worker_main(args: CliModuleArgs) -> i32 {
 | 
			
		|||
 | 
			
		||||
  info!("waiting for engine exit mode...");
 | 
			
		||||
  match engine_handle.join() {
 | 
			
		||||
    Ok(exit_all_processes) => {
 | 
			
		||||
      if exit_all_processes {
 | 
			
		||||
        info!("exiting worker process and daemon...");
 | 
			
		||||
        return WORKER_EXIT_ALL_PROCESSES;
 | 
			
		||||
      } else {
 | 
			
		||||
    Ok(mode) => match mode {
 | 
			
		||||
      ExitMode::Exit => {
 | 
			
		||||
        info!("exiting worker process...");
 | 
			
		||||
        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) => {
 | 
			
		||||
      error!("unable to read engine exit mode: {:?}", err);
 | 
			
		||||
      return WORKER_GENERAL_ERROR;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -48,8 +48,8 @@ impl Event {
 | 
			
		|||
pub enum EventType {
 | 
			
		||||
  NOOP,
 | 
			
		||||
  ProcessingError(String),
 | 
			
		||||
  ExitRequested(bool),  // If true, exit also the daemon process and not just the worker
 | 
			
		||||
  Exit(bool),
 | 
			
		||||
  ExitRequested(ExitMode),
 | 
			
		||||
  Exit(ExitMode),
 | 
			
		||||
  
 | 
			
		||||
  // Inputs
 | 
			
		||||
  Keyboard(input::KeyboardEvent),
 | 
			
		||||
| 
						 | 
				
			
			@ -82,4 +82,11 @@ pub enum EventType {
 | 
			
		|||
 | 
			
		||||
  // UI
 | 
			
		||||
  ShowContextMenu(ui::ShowContextMenuEvent),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum ExitMode {
 | 
			
		||||
  Exit,
 | 
			
		||||
  ExitAllProcesses,
 | 
			
		||||
  RestartWorker,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +19,7 @@
 | 
			
		|||
 | 
			
		||||
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 event;
 | 
			
		||||
| 
						 | 
				
			
			@ -41,15 +41,15 @@ impl <'a> Engine<'a> {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub fn run(&mut self) -> bool {
 | 
			
		||||
  pub fn run(&mut self) -> ExitMode {
 | 
			
		||||
    loop {
 | 
			
		||||
      match self.funnel.receive() {
 | 
			
		||||
        FunnelResult::Event(event) => {
 | 
			
		||||
          let processed_events = self.processor.process(event);
 | 
			
		||||
          for event in processed_events {
 | 
			
		||||
            if let EventType::Exit(exit_all_processes) = &event.etype {
 | 
			
		||||
              debug!("exit event received, exiting engine");
 | 
			
		||||
              return *exit_all_processes;
 | 
			
		||||
            if let EventType::Exit(mode) = &event.etype {
 | 
			
		||||
              debug!("exit event received with mode {:?}, exiting engine", mode);
 | 
			
		||||
              return mode.clone();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            self.dispatcher.dispatch(event);
 | 
			
		||||
| 
						 | 
				
			
			@ -57,7 +57,7 @@ impl <'a> Engine<'a> {
 | 
			
		|||
        } 
 | 
			
		||||
        FunnelResult::EndOfStream => {
 | 
			
		||||
          debug!("end of stream received");
 | 
			
		||||
          return false;
 | 
			
		||||
          return ExitMode::Exit;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,10 +20,11 @@
 | 
			
		|||
use super::super::Middleware;
 | 
			
		||||
use crate::engine::event::{
 | 
			
		||||
  ui::{MenuItem, ShowContextMenuEvent, SimpleMenuItem},
 | 
			
		||||
  Event, EventType,
 | 
			
		||||
  Event, EventType, ExitMode,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const CONTEXT_ITEM_EXIT: u32 = 0;
 | 
			
		||||
const CONTEXT_ITEM_RELOAD: u32 = 1;
 | 
			
		||||
 | 
			
		||||
pub struct ContextMenuMiddleware {}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -51,18 +52,30 @@ impl Middleware for ContextMenuMiddleware {
 | 
			
		|||
          event.source_id,
 | 
			
		||||
          EventType::ShowContextMenu(ShowContextMenuEvent {
 | 
			
		||||
            // TODO: add actual entries
 | 
			
		||||
            items: vec![MenuItem::Simple(SimpleMenuItem {
 | 
			
		||||
              id: CONTEXT_ITEM_EXIT,
 | 
			
		||||
              label: "Exit espanso".to_string(),
 | 
			
		||||
            })],
 | 
			
		||||
            items: vec![
 | 
			
		||||
              MenuItem::Simple(SimpleMenuItem {
 | 
			
		||||
                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) => {
 | 
			
		||||
        match context_click_event.context_item_id {
 | 
			
		||||
          CONTEXT_ITEM_EXIT => {
 | 
			
		||||
            Event::caused_by(event.source_id, EventType::ExitRequested(true))
 | 
			
		||||
          }
 | 
			
		||||
          CONTEXT_ITEM_EXIT => Event::caused_by(
 | 
			
		||||
            event.source_id,
 | 
			
		||||
            EventType::ExitRequested(ExitMode::ExitAllProcesses),
 | 
			
		||||
          ),
 | 
			
		||||
          CONTEXT_ITEM_RELOAD => Event::caused_by(
 | 
			
		||||
            event.source_id,
 | 
			
		||||
            EventType::ExitRequested(ExitMode::RestartWorker),
 | 
			
		||||
          ),
 | 
			
		||||
          custom => {
 | 
			
		||||
            // TODO: handle dynamic items
 | 
			
		||||
            todo!()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,9 +36,9 @@ impl Middleware for ExitMiddleware {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
 | 
			
		||||
    if let EventType::ExitRequested(exit_all_processes) = &event.etype {
 | 
			
		||||
      debug!("received ExitRequested event with 'exit_all_processes: {}', dispatching exit", exit_all_processes);
 | 
			
		||||
      return Event::caused_by(event.source_id, EventType::Exit(*exit_all_processes));
 | 
			
		||||
    if let EventType::ExitRequested(mode) = &event.etype {
 | 
			
		||||
      debug!("received ExitRequested event with mode: {:?}, dispatching exit", mode);
 | 
			
		||||
      return Event::caused_by(event.source_id, EventType::Exit(mode.clone()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    event
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ pub const WORKER_SUCCESS: i32 = 0;
 | 
			
		|||
pub const WORKER_ALREADY_RUNNING: i32 = 1;
 | 
			
		||||
pub const WORKER_GENERAL_ERROR: i32 = 2;
 | 
			
		||||
pub const WORKER_EXIT_ALL_PROCESSES: i32 = 101;
 | 
			
		||||
pub const WORKER_RESTART: i32 = 102;
 | 
			
		||||
 | 
			
		||||
pub const DAEMON_SUCCESS: i32 = 0;
 | 
			
		||||
pub const DAEMON_ALREADY_RUNNING: i32 = 1;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user