feat(core): improve exit handling and connect first IPC worker POC
This commit is contained in:
parent
91046b3c18
commit
db5b7c1c38
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -307,6 +307,7 @@ dependencies = [
|
|||
"espanso-detect",
|
||||
"espanso-info",
|
||||
"espanso-inject",
|
||||
"espanso-ipc",
|
||||
"espanso-match",
|
||||
"espanso-path",
|
||||
"espanso-render",
|
||||
|
|
|
@ -23,6 +23,7 @@ espanso-clipboard = { path = "../espanso-clipboard" }
|
|||
espanso-info = { path = "../espanso-info" }
|
||||
espanso-render = { path = "../espanso-render" }
|
||||
espanso-path = { path = "../espanso-path" }
|
||||
espanso-ipc = { path = "../espanso-ipc" }
|
||||
maplit = "1.0.2"
|
||||
simplelog = "0.9.0"
|
||||
log = "0.4.14"
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
|
||||
use std::process::Command;
|
||||
|
||||
use espanso_ipc::IPCClient;
|
||||
use log::{error, info};
|
||||
|
||||
use crate::lock::acquire_daemon_lock;
|
||||
use crate::{ipc::{IPCEvent, create_ipc_client_to_worker}, lock::acquire_daemon_lock};
|
||||
|
||||
use super::{CliModule, CliModuleArgs};
|
||||
|
||||
|
@ -53,6 +54,8 @@ fn daemon_main(args: CliModuleArgs) {
|
|||
info!("espanso version: {}", VERSION);
|
||||
// TODO: print os system and version? (with os_info crate)
|
||||
|
||||
let ipc_client = create_ipc_client_to_worker(&paths.runtime)
|
||||
.expect("unable to create IPC client to worker process");
|
||||
|
||||
// TODO: check worker lock file, if taken stop the worker process through IPC
|
||||
|
||||
|
@ -60,14 +63,23 @@ fn daemon_main(args: CliModuleArgs) {
|
|||
|
||||
let espanso_exe_path =
|
||||
std::env::current_exe().expect("unable to obtain espanso executable location");
|
||||
|
||||
|
||||
info!("spawning the worker process...");
|
||||
|
||||
let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string());
|
||||
command.args(&["worker"]);
|
||||
command.env("ESPANSO_CONFIG_DIR", paths.config.to_string_lossy().to_string());
|
||||
command.env("ESPANSO_PACKAGE_DIR", paths.packages.to_string_lossy().to_string());
|
||||
command.env("ESPANSO_RUNTIME_DIR", paths.runtime.to_string_lossy().to_string());
|
||||
command.env(
|
||||
"ESPANSO_CONFIG_DIR",
|
||||
paths.config.to_string_lossy().to_string(),
|
||||
);
|
||||
command.env(
|
||||
"ESPANSO_PACKAGE_DIR",
|
||||
paths.packages.to_string_lossy().to_string(),
|
||||
);
|
||||
command.env(
|
||||
"ESPANSO_RUNTIME_DIR",
|
||||
paths.runtime.to_string_lossy().to_string(),
|
||||
);
|
||||
|
||||
// On windows, we need to spawn the process as "Detached"
|
||||
#[cfg(target_os = "windows")]
|
||||
|
|
|
@ -17,9 +17,14 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use anyhow::Result;
|
||||
use crossbeam::channel::Receiver;
|
||||
use espanso_config::{config::ConfigStore, matches::store::MatchStore};
|
||||
use espanso_path::Paths;
|
||||
use espanso_ui::UIRemote;
|
||||
use log::info;
|
||||
use ui::selector::MatchSelectorAdapter;
|
||||
|
||||
use crate::cli::worker::engine::{matcher::regex::RegexMatcherAdapterOptions, path::PathProviderAdapter};
|
||||
|
@ -40,8 +45,10 @@ pub fn initialize_and_spawn(
|
|||
config_store: Box<dyn ConfigStore>,
|
||||
match_store: Box<dyn MatchStore>,
|
||||
icon_paths: IconPaths,
|
||||
) -> Result<()> {
|
||||
std::thread::Builder::new()
|
||||
ui_remote: Box<dyn UIRemote>,
|
||||
exit_signal: Receiver<()>,
|
||||
) -> Result<JoinHandle<()>> {
|
||||
let handle = std::thread::Builder::new()
|
||||
.name("engine thread".to_string())
|
||||
.spawn(move || {
|
||||
// TODO: properly order the initializations if necessary
|
||||
|
@ -60,7 +67,8 @@ pub fn initialize_and_spawn(
|
|||
|
||||
let (detect_source, modifier_state_store, sequencer) =
|
||||
super::engine::source::init_and_spawn().expect("failed to initialize detector module");
|
||||
let sources: Vec<&dyn crate::engine::funnel::Source> = vec![&detect_source];
|
||||
let exit_source = super::engine::source::exit::ExitSource::new(exit_signal, &sequencer);
|
||||
let sources: Vec<&dyn crate::engine::funnel::Source> = vec![&detect_source, &exit_source];
|
||||
let funnel = crate::engine::funnel::default(&sources);
|
||||
|
||||
let rolling_matcher = super::engine::matcher::rolling::RollingMatcherAdapter::new(
|
||||
|
@ -143,7 +151,10 @@ pub fn initialize_and_spawn(
|
|||
|
||||
let mut engine = crate::engine::Engine::new(&funnel, &mut processor, &dispatcher);
|
||||
engine.run();
|
||||
|
||||
info!("engine eventloop has terminated, propagating exit event...");
|
||||
ui_remote.exit();
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
Ok(handle)
|
||||
}
|
||||
|
|
54
espanso/src/cli/worker/engine/source/exit.rs
Normal file
54
espanso/src/cli/worker/engine/source/exit.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Federico Terzi
|
||||
*
|
||||
* espanso is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* espanso is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use crossbeam::channel::{Receiver, Select, SelectedOperation};
|
||||
|
||||
use crate::engine::{event::{Event, EventType}, funnel};
|
||||
|
||||
use super::sequencer::Sequencer;
|
||||
|
||||
pub struct ExitSource<'a> {
|
||||
pub exit_signal: Receiver<()>,
|
||||
pub sequencer: &'a Sequencer,
|
||||
}
|
||||
|
||||
impl <'a> ExitSource<'a> {
|
||||
pub fn new(exit_signal: Receiver<()>, sequencer: &'a Sequencer) -> Self {
|
||||
ExitSource {
|
||||
exit_signal,
|
||||
sequencer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> funnel::Source<'a> for ExitSource<'a> {
|
||||
fn register(&'a self, select: &mut Select<'a>) -> usize {
|
||||
select.recv(&self.exit_signal)
|
||||
}
|
||||
|
||||
fn receive(&self, op: SelectedOperation) -> Event {
|
||||
op
|
||||
.recv(&self.exit_signal)
|
||||
.expect("unable to select data from ExitSource receiver");
|
||||
Event {
|
||||
source_id: self.sequencer.next_id(),
|
||||
etype: EventType::ExitRequested,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ use detect::DetectSource;
|
|||
use self::{modifier::{Modifier, ModifierStateStore}, sequencer::Sequencer};
|
||||
|
||||
pub mod detect;
|
||||
pub mod exit;
|
||||
pub mod modifier;
|
||||
pub mod sequencer;
|
||||
|
||||
|
|
54
espanso/src/cli/worker/ipc.rs
Normal file
54
espanso/src/cli/worker/ipc.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Federico Terzi
|
||||
*
|
||||
* espanso is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* espanso is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use crossbeam::channel::{Sender};
|
||||
use log::{error, warn};
|
||||
|
||||
use crate::ipc::IPCEvent;
|
||||
|
||||
pub fn initialize_and_spawn(runtime_dir: &Path, exit_notify: Sender<()>) -> Result<()> {
|
||||
let receiver = crate::ipc::spawn_worker_ipc_server(runtime_dir)?;
|
||||
|
||||
std::thread::Builder::new()
|
||||
.name("worker-ipc-handler".to_string())
|
||||
.spawn(move || loop {
|
||||
match receiver.recv() {
|
||||
Ok(event) => {
|
||||
match event {
|
||||
IPCEvent::Exit => {
|
||||
if let Err(err) = exit_notify.send(()) {
|
||||
error!("experienced error while sending exit signal from worker ipc handler: {}", err);
|
||||
}
|
||||
},
|
||||
unexpected_event => {
|
||||
warn!("received unexpected event in worker ipc handler: {:?}", unexpected_event);
|
||||
},
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("experienced error while receiving ipc event from worker handler: {}", err);
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -17,7 +17,8 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use log::error;
|
||||
use crossbeam::channel::unbounded;
|
||||
use log::{error, info};
|
||||
|
||||
use crate::lock::acquire_worker_lock;
|
||||
|
||||
|
@ -27,6 +28,7 @@ use super::{CliModule, CliModuleArgs};
|
|||
|
||||
mod config;
|
||||
mod engine;
|
||||
mod ipc;
|
||||
mod ui;
|
||||
|
||||
pub fn new() -> CliModule {
|
||||
|
@ -77,12 +79,26 @@ fn worker_main(args: CliModuleArgs) {
|
|||
.initialize()
|
||||
.expect("unable to initialize UI module");
|
||||
|
||||
// TODO: pass the remote
|
||||
let (engine_exit_notify, engine_exit_receiver) = unbounded();
|
||||
|
||||
// Initialize the engine on another thread and start it
|
||||
engine::initialize_and_spawn(paths.clone(), config_store, match_store, icon_paths)
|
||||
.expect("unable to initialize engine");
|
||||
engine::initialize_and_spawn(
|
||||
paths.clone(),
|
||||
config_store,
|
||||
match_store,
|
||||
icon_paths,
|
||||
remote,
|
||||
engine_exit_receiver,
|
||||
)
|
||||
.expect("unable to initialize engine");
|
||||
|
||||
// Setup the IPC server
|
||||
ipc::initialize_and_spawn(&paths.runtime, engine_exit_notify)
|
||||
.expect("unable to initialize IPC server");
|
||||
|
||||
eventloop.run(Box::new(move |event| {
|
||||
// TODO: handle event
|
||||
}));
|
||||
|
||||
info!("exiting worker process...");
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ impl Event {
|
|||
pub enum EventType {
|
||||
NOOP,
|
||||
ProcessingError(String),
|
||||
ExitRequested,
|
||||
Exit,
|
||||
|
||||
// Inputs
|
||||
Keyboard(input::KeyboardEvent),
|
||||
|
|
|
@ -19,12 +19,7 @@
|
|||
|
||||
use log::{debug};
|
||||
|
||||
use self::{
|
||||
dispatch::Dispatcher,
|
||||
event::{Event},
|
||||
process::Processor,
|
||||
funnel::{Funnel, FunnelResult},
|
||||
};
|
||||
use self::{dispatch::Dispatcher, event::{Event, EventType}, funnel::{Funnel, FunnelResult}, process::Processor};
|
||||
|
||||
pub mod dispatch;
|
||||
pub mod event;
|
||||
|
@ -47,11 +42,16 @@ impl <'a> Engine<'a> {
|
|||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
loop {
|
||||
'main: loop {
|
||||
match self.funnel.receive() {
|
||||
FunnelResult::Event(event) => {
|
||||
let processed_events = self.processor.process(event);
|
||||
for event in processed_events {
|
||||
if let EventType::Exit = &event.etype {
|
||||
debug!("exit event received, exiting engine");
|
||||
break 'main;
|
||||
}
|
||||
|
||||
self.dispatcher.dispatch(event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ use super::{MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware,
|
|||
delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider}, markdown::MarkdownMiddleware,
|
||||
past_discard::PastEventsDiscardMiddleware,
|
||||
}};
|
||||
use crate::engine::{event::{Event, EventType}, process::middleware::image_resolve::ImageResolverMiddleware};
|
||||
use crate::engine::{event::{Event, EventType}, process::middleware::{exit::ExitMiddleware, image_resolve::ImageResolverMiddleware}};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub struct DefaultProcessor<'a> {
|
||||
|
@ -56,6 +56,7 @@ impl<'a> DefaultProcessor<'a> {
|
|||
Box::new(RenderMiddleware::new(renderer)),
|
||||
Box::new(ImageResolverMiddleware::new(path_provider)),
|
||||
Box::new(CursorHintMiddleware::new()),
|
||||
Box::new(ExitMiddleware::new()),
|
||||
Box::new(ActionMiddleware::new(match_info_provider, event_sequence_provider)),
|
||||
Box::new(MarkdownMiddleware::new()),
|
||||
Box::new(DelayForModifierReleaseMiddleware::new(modifier_status_provider)),
|
||||
|
|
48
espanso/src/engine/process/middleware/exit.rs
Normal file
48
espanso/src/engine/process/middleware/exit.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Federico Terzi
|
||||
*
|
||||
* espanso is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* espanso is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use log::debug;
|
||||
|
||||
use super::super::Middleware;
|
||||
use crate::engine::{event::{Event, EventType}};
|
||||
|
||||
pub struct ExitMiddleware {}
|
||||
|
||||
impl ExitMiddleware {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Middleware for ExitMiddleware {
|
||||
fn name(&self) -> &'static str {
|
||||
"exit"
|
||||
}
|
||||
|
||||
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
|
||||
if let EventType::ExitRequested = &event.etype {
|
||||
debug!("received ExitRequested event, dispatching exit");
|
||||
return Event::caused_by(event.source_id, EventType::Exit);
|
||||
}
|
||||
|
||||
event
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test
|
|
@ -21,6 +21,7 @@ pub mod action;
|
|||
pub mod cause;
|
||||
pub mod cursor_hint;
|
||||
pub mod delay_modifiers;
|
||||
pub mod exit;
|
||||
pub mod image_resolve;
|
||||
pub mod match_select;
|
||||
pub mod matcher;
|
||||
|
|
62
espanso/src/ipc.rs
Normal file
62
espanso/src/ipc.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Federico Terzi
|
||||
*
|
||||
* espanso is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* espanso is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use anyhow::Result;
|
||||
use crossbeam::channel::Receiver;
|
||||
use espanso_ipc::{IPCServer, IPCClient};
|
||||
use std::path::Path;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum IPCEvent {
|
||||
Exit,
|
||||
}
|
||||
|
||||
pub fn spawn_daemon_ipc_server(runtime_dir: &Path) -> Result<Receiver<IPCEvent>> {
|
||||
spawn_ipc_server(runtime_dir, "daemon")
|
||||
}
|
||||
|
||||
pub fn spawn_worker_ipc_server(runtime_dir: &Path) -> Result<Receiver<IPCEvent>> {
|
||||
spawn_ipc_server(runtime_dir, "worker")
|
||||
}
|
||||
|
||||
pub fn create_ipc_client_to_worker(runtime_dir: &Path) -> Result<impl IPCClient<IPCEvent>> {
|
||||
create_ipc_client(runtime_dir, "worker")
|
||||
}
|
||||
|
||||
fn spawn_ipc_server(
|
||||
runtime_dir: &Path,
|
||||
name: &str,
|
||||
) -> Result<Receiver<IPCEvent>> {
|
||||
let (server, receiver) = espanso_ipc::server(&format!("espanso{}", name), runtime_dir)?;
|
||||
|
||||
std::thread::Builder::new().name(format!("espanso-ipc-server-{}", name)).spawn(move || {
|
||||
server.run().expect("unable to run ipc server");
|
||||
})?;
|
||||
|
||||
// TODO: refactor the ipc server to handle a graceful exit?
|
||||
|
||||
Ok(receiver)
|
||||
}
|
||||
|
||||
|
||||
fn create_ipc_client(runtime_dir: &Path, target_process: &str) -> Result<impl IPCClient<IPCEvent>> {
|
||||
let client = espanso_ipc::client(&format!("espanso{}", target_process), runtime_dir)?;
|
||||
Ok(client)
|
||||
}
|
|
@ -35,6 +35,7 @@ use crate::cli::LogMode;
|
|||
mod cli;
|
||||
mod engine;
|
||||
mod gui;
|
||||
mod ipc;
|
||||
mod lock;
|
||||
mod logging;
|
||||
mod util;
|
||||
|
|
Loading…
Reference in New Issue
Block a user