diff --git a/justfile b/justfile index c2189903b..f90f37897 100644 --- a/justfile +++ b/justfile @@ -21,6 +21,8 @@ build_release_windows output_dir: cargo build --release --features passthru_ahk --package=simulated_passthru; cp target/release/kanata_passthru.dll "{{output_dir}}\kanata_passthru.dll" cargo build --release --features win_manifest,gui ; cp target/release/kanata.exe "{{output_dir}}\kanata_gui.exe" cargo build --release --features win_manifest,gui,cmd; cp target/release/kanata.exe "{{output_dir}}\kanata_gui_cmd_allowed.exe" + cargo build --release --features win_manifest,gui ,interception_driver; cp target/release/kanata.exe "{{output_dir}}\kanata_gui_wintercept.exe" + cargo build --release --features win_manifest,gui,cmd,interception_driver; cp target/release/kanata.exe "{{output_dir}}\kanata_gui_wintercept_cmd_allowed.exe" cp cfg_samples/kanata.kbd "{{output_dir}}" # Generate the sha256sums for all files in the output directory diff --git a/src/gui/mod.rs b/src/gui/mod.rs index cbfe02483..2817a3aa5 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -15,3 +15,4 @@ pub static GUI_TX: OnceLock = OnceLock::new(); pub static GUI_CFG_TX: OnceLock = OnceLock::new(); pub static GUI_ERR_TX: OnceLock = OnceLock::new(); pub static GUI_ERR_MSG_TX: OnceLock> = OnceLock::new(); +pub static GUI_EXIT_TX: OnceLock = OnceLock::new(); diff --git a/src/gui/win.rs b/src/gui/win.rs index d090c8c74..fb1385116 100644 --- a/src/gui/win.rs +++ b/src/gui/win.rs @@ -97,6 +97,7 @@ pub struct SystemTray { pub layer_notice: nwg::Notice, pub cfg_notice: nwg::Notice, pub err_notice: nwg::Notice, + pub exit_notice: nwg::Notice, pub tt_notice: nwg::Notice, /// Receiver of error message content sent from other threads /// (e.g., from key event thread via WinDbgLogger that will also notify our GUI @@ -130,7 +131,7 @@ const ASSET_FD: [&str; 4] = ["", "icon", "img", "icons"]; const IMG_EXT: [&str; 7] = ["ico", "jpg", "jpeg", "png", "bmp", "dds", "tiff"]; const PRE_LAYER: &str = "\n🗍: "; // : invalid path marker, so should be safe to use as a separator const TTTIMER_L: u16 = 9; // lifetime delta to duration for a tooltip timer -use crate::gui::{CFG, GUI_CFG_TX, GUI_ERR_MSG_TX, GUI_ERR_TX, GUI_TX}; +use crate::gui::{CFG, GUI_CFG_TX, GUI_ERR_MSG_TX, GUI_ERR_TX, GUI_EXIT_TX, GUI_TX}; pub fn send_gui_notice() { if let Some(gui_tx) = GUI_TX.get() { @@ -153,6 +154,13 @@ pub fn send_gui_err_notice() { error!("no GUI_ERR_TX to notify GUI thread of errors"); } } +pub fn send_gui_exit_notice() { + if let Some(gui_tx) = GUI_EXIT_TX.get() { + gui_tx.notice(); + } else { + error!("no GUI_EXIT_TX to ask GUI thread to exit"); + } +} pub fn show_err_msg_nofail(title: String, msg: String) { // log gets insalized before gui, so some errors might have no target to log to, ignore them if let Some(gui_msg_tx) = GUI_ERR_MSG_TX.get() { @@ -1318,6 +1326,9 @@ pub mod system_tray_ui { nwg::Notice::builder() .parent(&d.window) .build(&mut d.err_notice)?; + nwg::Notice::builder() + .parent(&d.window) + .build(&mut d.exit_notice)?; nwg::Menu::builder() .parent(&d.tray_menu) .text("&F Load config") // @@ -1519,6 +1530,8 @@ pub mod system_tray_ui { SystemTray::reload_cfg_icon(&evt_ui); } else if handle == evt_ui.err_notice { SystemTray::notify_error(&evt_ui); + } else if handle == evt_ui.exit_notice { + SystemTray::exit(&evt_ui); } else if handle == evt_ui.tt_notice { SystemTray::update_tooltip_pos(&evt_ui);} E::OnWindowClose => diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index 967eb5a70..7b30a6d8d 100755 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -2029,7 +2029,13 @@ fn check_for_exit(_event: &KeyEvent) { log::info!("{EXIT_MSG}"); #[cfg(all(target_os = "windows", feature = "gui"))] { + #[cfg(not(feature = "interception_driver"))] native_windows_gui::stop_thread_dispatch(); + #[cfg(feature = "interception_driver")] + send_gui_exit_notice(); // interception driver is running in another thread to allow + // GUI take the main one, so it's calling check_for_exit + // from a thread that has no access to the main one, so + // can't stop main thread's dispatch } #[cfg(all( not(target_os = "linux"), diff --git a/src/kanata/windows/interception.rs b/src/kanata/windows/interception.rs index ca837a285..e22638414 100644 --- a/src/kanata/windows/interception.rs +++ b/src/kanata/windows/interception.rs @@ -10,7 +10,7 @@ use crate::oskbd::KeyValue; use kanata_parser::keys::OsCode; impl Kanata { - pub fn event_loop(kanata: Arc>, tx: Sender) -> Result<()> { + pub fn event_loop_inner(kanata: Arc>, tx: Sender) -> Result<()> { let intrcptn = ic::Interception::new().ok_or_else(|| anyhow!("interception driver should init: have you completed the interception driver installation?"))?; intrcptn.set_filter(ic::is_keyboard, ic::Filter::KeyFilter(ic::KeyFilter::all())); let mut strokes = [ic::Stroke::Keyboard { @@ -29,7 +29,6 @@ impl Kanata { ); } let mut is_dev_interceptable: HashMap = HashMap::default(); - loop { let dev = intrcptn.wait(); if dev > 0 { @@ -110,6 +109,23 @@ impl Kanata { } } } + pub fn event_loop( + kanata: Arc>, + tx: Sender, + #[cfg(feature = "gui")] ui: crate::gui::system_tray_ui::SystemTrayUi, + ) -> Result<()> { + #[cfg(not(feature = "gui"))] + { + Self::event_loop_inner(kanata, tx) + } + #[cfg(feature = "gui")] + { + std::thread::spawn(move || -> Result<()> { Self::event_loop_inner(kanata, tx) }); + let _ui = ui; // prevents thread from panicking on exiting via a GUI + native_windows_gui::dispatch_thread_events(); + Ok(()) + } + } } fn is_device_interceptable( @@ -134,7 +150,6 @@ fn is_device_interceptable( }, } } - fn mouse_state_to_event( input_dev: ic::Device, allowed_hwids: &Option>, diff --git a/src/main_lib/win_gui.rs b/src/main_lib/win_gui.rs index 85281632e..4652415d1 100644 --- a/src/main_lib/win_gui.rs +++ b/src/main_lib/win_gui.rs @@ -152,6 +152,7 @@ fn main_impl() -> Result<()> { let gui_tx = ui.layer_notice.sender(); let gui_cfg_tx = ui.cfg_notice.sender(); // allows notifying GUI on config reloads let gui_err_tx = ui.err_notice.sender(); // allows notifying GUI on erorrs (from logger) + let gui_exit_tx = ui.exit_notice.sender(); // allows notifying GUI on app quit if GUI_TX.set(gui_tx).is_err() { warn!("Someone else set our ‘GUI_TX’"); }; @@ -161,6 +162,9 @@ fn main_impl() -> Result<()> { if GUI_ERR_TX.set(gui_err_tx).is_err() { warn!("Someone else set our ‘GUI_ERR_TX’"); }; + if GUI_EXIT_TX.set(gui_exit_tx).is_err() { + warn!("Someone else set our ‘GUI_EXIT_TX’"); + }; Kanata::start_processing_loop(kanata_arc.clone(), rx, ntx, args.nodelay); if let (Some(server), Some(nrx)) = (server, nrx) {