diff --git a/Cargo.lock b/Cargo.lock
index 9ab99c9..ee2e45a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -307,6 +307,7 @@ dependencies = [
"espanso-detect",
"espanso-info",
"espanso-inject",
+ "espanso-ipc",
"espanso-match",
"espanso-path",
"espanso-render",
diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml
index 3850c00..2439ed6 100644
--- a/espanso/Cargo.toml
+++ b/espanso/Cargo.toml
@@ -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"
diff --git a/espanso/src/cli/daemon/mod.rs b/espanso/src/cli/daemon/mod.rs
index cc83226..25fcc2d 100644
--- a/espanso/src/cli/daemon/mod.rs
+++ b/espanso/src/cli/daemon/mod.rs
@@ -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")]
diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs
index 306c339..d2f39cf 100644
--- a/espanso/src/cli/worker/engine/mod.rs
+++ b/espanso/src/cli/worker/engine/mod.rs
@@ -17,9 +17,14 @@
* along with espanso. If not, see .
*/
+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,
match_store: Box,
icon_paths: IconPaths,
-) -> Result<()> {
- std::thread::Builder::new()
+ ui_remote: Box,
+ exit_signal: Receiver<()>,
+) -> Result> {
+ 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)
}
diff --git a/espanso/src/cli/worker/engine/source/exit.rs b/espanso/src/cli/worker/engine/source/exit.rs
new file mode 100644
index 0000000..3b1c351
--- /dev/null
+++ b/espanso/src/cli/worker/engine/source/exit.rs
@@ -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 .
+ */
+
+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,
+ }
+ }
+}
\ No newline at end of file
diff --git a/espanso/src/cli/worker/engine/source/mod.rs b/espanso/src/cli/worker/engine/source/mod.rs
index a35b546..57b6be1 100644
--- a/espanso/src/cli/worker/engine/source/mod.rs
+++ b/espanso/src/cli/worker/engine/source/mod.rs
@@ -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;
diff --git a/espanso/src/cli/worker/ipc.rs b/espanso/src/cli/worker/ipc.rs
new file mode 100644
index 0000000..f2a6dc9
--- /dev/null
+++ b/espanso/src/cli/worker/ipc.rs
@@ -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 .
+ */
+
+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(())
+}
diff --git a/espanso/src/cli/worker/mod.rs b/espanso/src/cli/worker/mod.rs
index ce39055..840005b 100644
--- a/espanso/src/cli/worker/mod.rs
+++ b/espanso/src/cli/worker/mod.rs
@@ -17,7 +17,8 @@
* along with espanso. If not, see .
*/
-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...");
}
diff --git a/espanso/src/engine/event/mod.rs b/espanso/src/engine/event/mod.rs
index e199867..b3e3c95 100644
--- a/espanso/src/engine/event/mod.rs
+++ b/espanso/src/engine/event/mod.rs
@@ -47,6 +47,8 @@ impl Event {
pub enum EventType {
NOOP,
ProcessingError(String),
+ ExitRequested,
+ Exit,
// Inputs
Keyboard(input::KeyboardEvent),
diff --git a/espanso/src/engine/mod.rs b/espanso/src/engine/mod.rs
index aae386c..26deadf 100644
--- a/espanso/src/engine/mod.rs
+++ b/espanso/src/engine/mod.rs
@@ -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);
}
}
diff --git a/espanso/src/engine/process/default.rs b/espanso/src/engine/process/default.rs
index f8432eb..93e769e 100644
--- a/espanso/src/engine/process/default.rs
+++ b/espanso/src/engine/process/default.rs
@@ -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)),
diff --git a/espanso/src/engine/process/middleware/exit.rs b/espanso/src/engine/process/middleware/exit.rs
new file mode 100644
index 0000000..51e026b
--- /dev/null
+++ b/espanso/src/engine/process/middleware/exit.rs
@@ -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 .
+ */
+
+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
diff --git a/espanso/src/engine/process/middleware/mod.rs b/espanso/src/engine/process/middleware/mod.rs
index cd930b2..49076e8 100644
--- a/espanso/src/engine/process/middleware/mod.rs
+++ b/espanso/src/engine/process/middleware/mod.rs
@@ -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;
diff --git a/espanso/src/ipc.rs b/espanso/src/ipc.rs
new file mode 100644
index 0000000..0de770a
--- /dev/null
+++ b/espanso/src/ipc.rs
@@ -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 .
+ */
+
+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> {
+ spawn_ipc_server(runtime_dir, "daemon")
+}
+
+pub fn spawn_worker_ipc_server(runtime_dir: &Path) -> Result> {
+ spawn_ipc_server(runtime_dir, "worker")
+}
+
+pub fn create_ipc_client_to_worker(runtime_dir: &Path) -> Result> {
+ create_ipc_client(runtime_dir, "worker")
+}
+
+fn spawn_ipc_server(
+ runtime_dir: &Path,
+ name: &str,
+) -> Result> {
+ 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> {
+ let client = espanso_ipc::client(&format!("espanso{}", target_process), runtime_dir)?;
+ Ok(client)
+}
\ No newline at end of file
diff --git a/espanso/src/main.rs b/espanso/src/main.rs
index 2e9a25d..5eca592 100644
--- a/espanso/src/main.rs
+++ b/espanso/src/main.rs
@@ -35,6 +35,7 @@ use crate::cli::LogMode;
mod cli;
mod engine;
mod gui;
+mod ipc;
mod lock;
mod logging;
mod util;