Skip to content

Commit

Permalink
feat(win,gui): log errors via notification system (#1049)
Browse files Browse the repository at this point in the history
Add an extra way for the Windows logger to notify on errors: use the OS
notification system to show whatever fits
Configurable, on by default.
Other log levels do not use OS notification to avoid spamming.
  • Loading branch information
eugenesvk authored May 25, 2024
1 parent 2a94c9c commit 96d6d08
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 31 deletions.
43 changes: 31 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ members = [
"example_tcp_client",
"tcp_protocol",
"windows_key_tester",
"win_dbg_logger",
"simulated_input",
"simulated_passthru",
]
Expand Down Expand Up @@ -101,12 +100,12 @@ windows-sys = { version = "0.52.0", features = [
"Wdk_System",
"Wdk_System_SystemServices",
], optional=true }
win_dbg_logger = { path = "win_dbg_logger", optional = true }
native-windows-gui = { version = "1.0.13", default_features = false}
native-windows-derive = { version = "1.0.5", default_features = false, optional = true }
regex = { version = "1.10.4", optional = true }
kanata-interception = { version = "0.2.0", optional = true }
muldiv = { version = "1.0.1", optional = true }
strip-ansi-escapes = { version = "0.2.0", optional = true }

[target.'cfg(target_os = "windows")'.build-dependencies]
embed-resource = { version = "2.4.2", optional = true }
Expand All @@ -126,7 +125,9 @@ simulated_output = ["indoc"]
simulated_input = ["indoc"]
passthru_ahk = ["simulated_input","simulated_output"]
wasm = [ "instant/wasm-bindgen" ]
gui = ["win_manifest","native-windows-derive","win_dbg_logger","win_dbg_logger/simple_shared","kanata-parser/gui","native-windows-gui/tray-notification","native-windows-gui/message-window","native-windows-gui/menu","native-windows-gui/cursor","native-windows-gui/high-dpi","native-windows-gui/embed-resource","native-windows-gui/image-decoder","native-windows-gui/notice","native-windows-gui/animation-timer","muldiv","dep:windows-sys","win_sendinput_send_scancodes","win_llhook_read_scancodes"]
gui = ["win_manifest","native-windows-derive","kanata-parser/gui","native-windows-gui/tray-notification","native-windows-gui/message-window","native-windows-gui/menu","native-windows-gui/cursor","native-windows-gui/high-dpi","native-windows-gui/embed-resource","native-windows-gui/image-decoder","native-windows-gui/notice","native-windows-gui/animation-timer","muldiv","strip-ansi-escapes","dep:windows-sys","win_sendinput_send_scancodes","win_llhook_read_scancodes",
"winapi/processthreadsapi",
]

[profile.release]
opt-level = "z"
Expand Down
18 changes: 11 additions & 7 deletions cfg_samples/tray-icon/tray-icon.kbd
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
(defcfg
process-unmapped-keys yes ;;|no| enable processing of keys that are not in defsrc, useful if mapping a few keys in defsrc instead of most of the keys on your keyboard. Without this, the tap-hold-release and tap-hold-press actions will not activate for keys that are not in defsrc. Disabled because some keys may not work correctly if they are intercepted. E.g. rctl/altgr on Windows; see the windows-altgr configuration item above for context.
log-layer-changes yes ;;|no| overhead
process-unmapped-keys yes ;;|no| enable processing of keys that are not in defsrc, useful if mapping a few keys in defsrc instead of most of the keys on your keyboard. Without this, the tap-hold-release and tap-hold-press actions will not activate for keys that are not in defsrc. Disabled because some keys may not work correctly if they are intercepted. E.g. rctl/altgr on Windows; see the windows-altgr configuration item above for context.
log-layer-changes yes ;;|no| overhead
tray-icon "./_custom-icons/s.png" ;; should activate for layers without icons like '5no-icn'
icon-match-layer-name yes ;;|yes| match layer name to icon files even without an explicit (icon name.ico) config
tooltip-layer-changes yes ;;|false|
tooltip-show-blank yes ;;|no|
tooltip-duration 500 ;;|500|
tooltip-size 24,24 ;;|24 24|
;;opt val |≝|
icon-match-layer-name yes ;;|yes| match layer name to icon files even without an explicit (icon name.ico) config
tooltip-layer-changes yes ;;|false|
tooltip-show-blank yes ;;|no|
tooltip-duration 500 ;;|500|
tooltip-size 24,24 ;;|24 24|
notify-cfg-reload yes ;;|yes|
notify-cfg-reload-silent no ;;|no|
notify-error yes ;;|yes|
)
(defalias l1 (layer-while-held 1emoji))
(defalias l2 (layer-while-held 2icon-quote))
Expand Down
6 changes: 6 additions & 0 deletions docs/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2566,6 +2566,12 @@ Show system notification message on config reload. Defaults to true. Requires <<

Disable sound for the system notification message on config reload. Defaults to false. Requires <<windows-only-win-tray>> gui-enabled build.

[[windows-only-notify-error]]
=== Windows only: notify-error
<<table-of-contents,Back to ToC>>

Show system notification message on kanata errors. Defaults to true. Requires <<windows-only-win-tray>> gui-enabled build.

[[using-multiple-defcfg-options]]
=== Using multiple defcfg options
<<table-of-contents,Back to ToC>>
Expand Down
13 changes: 13 additions & 0 deletions parser/src/cfg/defcfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ pub struct CfgOptionsGui {
/// Disable sound for the system notification message on config reload
#[cfg(all(target_os = "windows", feature = "gui"))]
pub notify_cfg_reload_silent: bool,
/// Show system notification message on errors
#[cfg(all(target_os = "windows", feature = "gui"))]
pub notify_error: bool,
/// Set tooltip size (width, height)
#[cfg(all(target_os = "windows", feature = "gui"))]
pub tooltip_size: (u16, u16),
Expand All @@ -48,6 +51,7 @@ impl Default for CfgOptionsGui {
tooltip_duration: 500,
notify_cfg_reload: true,
notify_cfg_reload_silent: false,
notify_error: true,
tooltip_size: (24, 24),
}
}
Expand Down Expand Up @@ -527,6 +531,15 @@ pub fn parse_defcfg(expr: &[SExpr]) -> Result<CfgOptions> {
parse_defcfg_val_bool(val, label)?
}
}
"notify-error" => {
#[cfg(all(
any(target_os = "windows", target_os = "unknown"),
feature = "gui"
))]
{
cfg.gui_opts.notify_error = parse_defcfg_val_bool(val, label)?
}
}
"tooltip-size" => {
#[cfg(all(
any(target_os = "windows", target_os = "unknown"),
Expand Down
1 change: 1 addition & 0 deletions parser/src/cfg/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1340,6 +1340,7 @@ fn parse_all_defcfg() {
tooltip-size 24,24
notify-cfg-reload yes
notify-cfg-reload-silent no
notify-error yes
windows-altgr add-lctl-release
windows-interception-mouse-hwid "70, 0, 60, 0"
windows-interception-mouse-hwids ("0, 0, 0" "1, 1, 1")
Expand Down
4 changes: 4 additions & 0 deletions src/gui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
pub mod win;
pub use win::*;
pub mod win_dbg_logger;
pub mod win_nwg_ext;
pub use win_dbg_logger as log_win;
pub use win_dbg_logger::WINDBG_LOGGER;
pub use win_nwg_ext::*;

use crate::*;
use parking_lot::Mutex;
use std::sync::mpsc::Sender as ASender;
use std::sync::{Arc, OnceLock};
pub static CFG: OnceLock<Arc<Mutex<Kanata>>> = OnceLock::new();
pub static GUI_TX: OnceLock<native_windows_gui::NoticeSender> = OnceLock::new();
pub static GUI_CFG_TX: OnceLock<native_windows_gui::NoticeSender> = OnceLock::new();
pub static GUI_ERR_TX: OnceLock<native_windows_gui::NoticeSender> = OnceLock::new();
pub static GUI_ERR_MSG_TX: OnceLock<ASender<(String, String)>> = OnceLock::new();
79 changes: 78 additions & 1 deletion src/gui/win.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ pub struct SystemTray {
win_tt_timer: nwg::AnimationTimer,
pub layer_notice: nwg::Notice,
pub cfg_notice: nwg::Notice,
pub err_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
/// (but not pass data) after sending data to this receiver)
pub err_recv: Option<Receiver<(String, String)>>,
pub tt2m_channel: Option<(ASender<bool>, Receiver<bool>)>,
// receiver will be created before a thread is spawned and moved there
pub m2tt_sender: RefCell<Option<ASender<bool>>>,
Expand Down Expand Up @@ -125,7 +130,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_TX};
use crate::gui::{CFG, GUI_CFG_TX, GUI_ERR_MSG_TX, GUI_ERR_TX, GUI_TX};

pub fn send_gui_notice() {
if let Some(gui_tx) = GUI_TX.get() {
Expand All @@ -141,6 +146,26 @@ pub fn send_gui_cfg_notice() {
error!("no GUI_CFG_TX to notify GUI thread of layer changes");
}
}
pub fn send_gui_err_notice() {
if let Some(gui_tx) = GUI_ERR_TX.get() {
gui_tx.notice();
} else {
error!("no GUI_ERR_TX to notify GUI thread of errors");
}
}
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() {
if gui_msg_tx.send((title, msg)).is_err() {
warn!("send_gui_err_msg_notice failed to use OS notifications")
} else {
// can't Error to avoid an ∞ error loop ↑
if let Some(gui_tx) = GUI_ERR_TX.get() {
gui_tx.notice();
}
}
}
}

/// Find an icon file that matches a given config icon name for a layer `lyr_icn` or a layer name
/// `lyr_nm` (if `match_name` is `true`) or a given config icon name for the whole config `cfg_p`
Expand Down Expand Up @@ -921,6 +946,48 @@ impl SystemTray {
fn reload_layer_icon(&self) {
let _ = self.reload_cfg_or_layer_icon(false);
}
/// Show OS notification message with an error coming from WinDbgLogger
fn notify_error(&self) {
let app_data = self.app_data.borrow();
if !app_data.gui_opts.notify_error {
return;
};
use nwg::TrayNotificationFlags as f_tray;
let mut msg_title = "".to_string();
let mut msg_content = "".to_string();
let mut flags = f_tray::empty();
if let Some(gui_msg_rx) = &self.err_recv {
match gui_msg_rx.try_recv() {
Ok((title, msg)) => {
msg_title += &title;
msg_content += &msg;
}
Err(TryRecvError::Empty) => {
msg_title += "internal";
msg_content += "channel to receive errors is Empty";
}
Err(TryRecvError::Disconnected) => {
msg_title += "internal";
msg_content += "channel to receive errors is Disconnected";
}
}
} else {
msg_title += "internal";
msg_content += "SystemTray is supposed to have a valid 'err_recv' field value"
}
flags |= f_tray::ERROR_ICON;
if app_data.gui_opts.notify_cfg_reload_silent {
flags |= f_tray::SILENT;
}
let msg_title = strip_ansi_escapes::strip_str(&msg_title);
let msg_content = strip_ansi_escapes::strip_str(&msg_content);
self.tray.show(
&msg_content,
Some(&msg_title),
Some(flags),
Some(&self.icon),
);
}
/// Update tray icon data on config reload
fn reload_cfg_icon(&self) {
let _ = self.reload_cfg_or_layer_icon(true);
Expand Down Expand Up @@ -1227,6 +1294,11 @@ pub mod system_tray_ui {

let (sndr, rcvr) = std::sync::mpsc::channel();
d.tt2m_channel = Some((sndr, rcvr));
let (sndr, rcvr) = std::sync::mpsc::channel();
d.err_recv = Some(rcvr);
if GUI_ERR_MSG_TX.set(sndr).is_err() {
warn!("Someone else set our ‘GUI_ERR_MSG_TX’");
};

// Controls
nwg::MessageWindow::builder().build(&mut d.window)?;
Expand All @@ -1243,6 +1315,9 @@ pub mod system_tray_ui {
nwg::Notice::builder()
.parent(&d.window)
.build(&mut d.tt_notice)?;
nwg::Notice::builder()
.parent(&d.window)
.build(&mut d.err_notice)?;
nwg::Menu::builder()
.parent(&d.tray_menu)
.text("&F Load config") //
Expand Down Expand Up @@ -1442,6 +1517,8 @@ pub mod system_tray_ui {
SystemTray::reload_layer_icon(&evt_ui);
} else if handle == evt_ui.cfg_notice {
SystemTray::reload_cfg_icon(&evt_ui);
} else if handle == evt_ui.err_notice {
SystemTray::notify_error(&evt_ui);
} else if handle == evt_ui.tt_notice {
SystemTray::update_tooltip_pos(&evt_ui);}
E::OnWindowClose =>
Expand Down
Loading

0 comments on commit 96d6d08

Please sign in to comment.