Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(win): IPC communication via Windows messages #1082

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Cargo.lock

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

34 changes: 18 additions & 16 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ rustc-hash = "1.1.0"
simplelog = "0.12.0"
serde_json = { version = "1", features = ["std"], default_features = false, optional = true }
time = "0.3.36"
colored = "2.1.0"
# kanata-keyberon = "0.161.0"
# kanata-parser = "0.161.0"
# kanata-tcp-protocol = "0.161.0"
Expand Down Expand Up @@ -84,23 +85,11 @@ winapi = { version = "0.3.9", features = [
"mmsystem",
] }
windows-sys = { version = "0.52.0", features = [
"Win32_Devices_DeviceAndDriverInstallation",
"Win32_Devices_Usb",
"Win32_Foundation",
"Win32_Graphics_Gdi",
"Win32_Security",
"Win32_System_Diagnostics_Debug",
"Win32_System_Registry",
"Win32_System_Threading",
"Win32_UI_Controls",
"Win32_UI_Shell",
"Win32_UI_HiDpi",
"Win32_UI_WindowsAndMessaging",
"Win32_System_SystemInformation",
"Wdk",
"Wdk_System",
"Wdk_System_SystemServices",
], optional=true }
]}

widestring = "1.1.0"
native-windows-gui = { version = "1.0.13", default_features = false}
regex = { version = "1.10.4", optional = true }
kanata-interception = { version = "0.2.0", optional = true }
Expand Down Expand Up @@ -130,7 +119,20 @@ wasm = [ "instant/wasm-bindgen" ]
gui = ["win_manifest","kanata-parser/gui",
"win_sendinput_send_scancodes","win_llhook_read_scancodes",
"muldiv","strip-ansi-escapes","open",
"dep:windows-sys",
"windows-sys/Win32_Devices_DeviceAndDriverInstallation",
"windows-sys/Win32_Devices_Usb",
"windows-sys/Win32_Graphics_Gdi",
"windows-sys/Win32_Security",
"windows-sys/Win32_System_Diagnostics_Debug",
"windows-sys/Win32_System_Registry",
"windows-sys/Win32_System_Threading",
"windows-sys/Win32_UI_Controls",
"windows-sys/Win32_UI_Shell",
"windows-sys/Win32_UI_HiDpi",
"windows-sys/Win32_System_SystemInformation",
"windows-sys/Wdk",
"windows-sys/Wdk_System",
"windows-sys/Wdk_System_SystemServices",
"winapi/processthreadsapi",
"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",
]
Expand Down
43 changes: 43 additions & 0 deletions cfg_samples/win-msg/insert-date.ahk
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#Requires AutoHotkey v2.0
Persistent true
listen_to_Kanata1()
listen_to_Kanata1() {
static msgIDtxt := "kanata_4117d2917ccb4678a7a8c71a5ff898ed" ; must be set to the same value in Kanata
static msgID := DllCall("RegisterWindowMessage", "Str",msgIDtxt), MSGFLT_ALLOW := 1
if winID_self:=WinExist(A_ScriptHwnd) { ; need to allow some messages through due to AHK running with UIA access https://stackoverflow.com/questions/40122964/cross-process-postmessage-uipi-restrictions-and-uiaccess-true
isRes := DllCall("ChangeWindowMessageFilterEx" ; learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-changewindowmessagefilterex?redirectedfrom=MSDN
, "Ptr",winID_self ;i HWND hwnd handle to the window whose UIPI message filter is to be modified
,"UInt",msgID ;i UINT message message that the message filter allows through or blocks
,"UInt",MSGFLT_ALLOW ;i DWORD action
, "Ptr",0) ;io opt PCHANGEFILTERSTRUCT pChangeFilterStruct
}
OnMessage(msgID, setnv_mode, MaxThreads:=1)
setnv_mode(wParam, lParam, msgID, hwnd) {
if wParam == 1 {
curtime := FormatTime(,"dddd MMMM d, yyyy H:mm:ss")
} else if wParam == 2 {
curtime := FormatTime(,"yy")
} else {
curtime := "✗ wParam=" wParam " lParam=" lParam
}
SetKeyDelay(-1, 0)
SendEvent(curtime)
}
}

listen_to_Kanata2()
listen_to_Kanata2() {
static msgIDtxt := "kanata_your_custom_message_string_unique_id" ; must be set to the same value in Kanata
static msgID := DllCall("RegisterWindowMessage", "Str",msgIDtxt), MSGFLT_ALLOW := 1
if winID_self:=WinExist(A_ScriptHwnd) { ; need to allow some messages through due to AHK running with UIA access https://stackoverflow.com/questions/40122964/cross-process-postmessage-uipi-restrictions-and-uiaccess-true
isRes := DllCall("ChangeWindowMessageFilterEx" ; learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-changewindowmessagefilterex?redirectedfrom=MSDN
, "Ptr",winID_self ;i HWND hwnd handle to the window whose UIPI message filter is to be modified
,"UInt",msgID ;i UINT message message that the message filter allows through or blocks
,"UInt",MSGFLT_ALLOW ;i DWORD action
, "Ptr",0) ;io opt PCHANGEFILTERSTRUCT pChangeFilterStruct
}
OnMessage(msgID, setnv_mode, MaxThreads:=1)
setnv_mode(wParam, lParam, msgID, hwnd) {
SendInput("@kanata_your_custom_message_string_unique_id Unknown wParam=" wParam "lParam=" lParam)
}
}
17 changes: 17 additions & 0 deletions cfg_samples/win-msg/win-msg.kbd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#|
|#
(defcfg
process-unmapped-keys yes
log-layer-changes yes
danger-enable-cmd yes
)
(defsrc 1 2 3 4 5 6 7 8 9 0)
(deflayermap (win-msg) 1 1 2 2
3 (msg❖async 1 ) ;; print date in the ‘dddd MMMM d, yyyy H:mm:ss’ format
4 (win-post-msg 2 ) ;; print date in the ‘yy’ format
5 (win-post-msg 3 ) ;; print error ‘✗ wParam=3 lParam=0’
6 (msg❖async 3 0 "" "kanata_your_custom_message_string_unique_id") ;; print long message
8 lrld
9 lrld-prev
0 lrld-next
)
30 changes: 30 additions & 0 deletions docs/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2509,6 +2509,36 @@ they are an arbitrary length and can be very long.
)
----

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

`msg❖async` or `win-post-msg` command allows sharing 2 numbers with any app you control
via Windows system messages mechanism. For example, you can set your AutoHotkey script to print

- a short date if Kanata sends it `1`, and
- a long date if it sends `2`

The command accepts at most 5 optional parameters (order sensitive!):
[cols="1,1"]
|===
|Name | Default
|numeric arg #1 |`0`
|numeric arg #2 |`0`
|target window title |`\AutoHotkey.ahk`
|shared message string id |`kanata_4117d2917ccb4678a7a8c71a5ff898ed`
|target window class |`AutoHotkey`
|===

Window title and class are currently **ignored** and the messages are posted to all the windows.
Parameters are order sensitive, so if you want to skip the 3rd title string, set it to an empty `""`.

See https://github.com/jtroo/kanata/blob/main/cfg_samples/win-msg/win-msg.kbd[example config] for more details.
Requires https://www.autohotkey.com/download/[AutoHotkey v2] and a running
https://github.com/jtroo/kanata/blob/main/cfg_samples/win-msg/insert-date.ahk[example script].
See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postmessagew[PostMessageW] for more
API details.

[[windows-only-tray-icon]]
=== Windows only: tray-icon
<<table-of-contents,Back to ToC>>
Expand Down
2 changes: 2 additions & 0 deletions parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ thiserror = "1.0.38"
# binary.
kanata-keyberon = { path = "../keyberon" }
bytemuck = "1.15.0"
colored = "2.1.0"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use termcolor instead; it is already a dependency and colored is not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but how do you compose it with the rest of the existing macros and log messages and do stuff like let cmd_name = cmd_name.blue().bold(); which can then be used anywhere. That crate seems to work on whole streams by adding apis to add colors to those

num-format = "0.4.4"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not seem worth adding as a dependency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you'd be fine with errors like

  • expected 0–18446744073709551615. isntead of
  • expected 0–18,446,744,073,709,551,615


[features]
cmd = []
Expand Down
2 changes: 2 additions & 0 deletions parser/src/cfg/linux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub struct WinMsg {}
8 changes: 8 additions & 0 deletions parser/src/cfg/list_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ pub const DYNAMIC_MACRO_PLAY: &str = "dynamic-macro-play";
pub const ARBITRARY_CODE: &str = "arbitrary-code";
pub const CMD: &str = "cmd";
pub const PUSH_MESSAGE: &str = "push-msg";
pub const SEND_WMSG_SYNC: &str = "win-send-msg";
pub const SEND_WMSG_SYNC_A: &str = "msg❖sync";
pub const SEND_WMSG_ASYNC: &str = "win-post-msg";
pub const SEND_WMSG_ASYNC_A: &str = "msg❖async";
pub const CMD_OUTPUT_KEYS: &str = "cmd-output-keys";
pub const FORK: &str = "fork";
pub const CAPS_WORD: &str = "caps-word";
Expand Down Expand Up @@ -197,6 +201,10 @@ pub fn is_list_action(ac: &str) -> bool {
CMD,
CMD_OUTPUT_KEYS,
PUSH_MESSAGE,
SEND_WMSG_SYNC,
SEND_WMSG_ASYNC,
SEND_WMSG_SYNC_A,
SEND_WMSG_ASYNC_A,
FORK,
CAPS_WORD,
CAPS_WORD_A,
Expand Down
2 changes: 2 additions & 0 deletions parser/src/cfg/macos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub struct WinMsg {}
20 changes: 20 additions & 0 deletions parser/src/cfg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1693,6 +1693,14 @@ fn parse_action_list(ac: &[SExpr], s: &ParserState) -> Result<&'static KanataAct
CMD => parse_cmd(&ac[1..], s, CmdType::Standard),
CMD_OUTPUT_KEYS => parse_cmd(&ac[1..], s, CmdType::OutputKeys),
PUSH_MESSAGE => parse_push_message(&ac[1..], s),
#[cfg(any(target_os = "windows", target_os = "unknown"))]
SEND_WMSG_SYNC => win_send_message(&ac[1..], s, SEND_WMSG_SYNC),
#[cfg(any(target_os = "windows", target_os = "unknown"))]
SEND_WMSG_SYNC_A => win_send_message(&ac[1..], s, SEND_WMSG_SYNC_A),
#[cfg(any(target_os = "windows", target_os = "unknown"))]
SEND_WMSG_ASYNC => win_post_message(&ac[1..], s, SEND_WMSG_ASYNC),
#[cfg(any(target_os = "windows", target_os = "unknown"))]
SEND_WMSG_ASYNC_A => win_post_message(&ac[1..], s, SEND_WMSG_ASYNC_A),
FORK => parse_fork(&ac[1..], s),
CAPS_WORD | CAPS_WORD_A => {
parse_caps_word(&ac[1..], CapsWordRepressBehaviour::Overwrite, s)
Expand Down Expand Up @@ -2299,6 +2307,18 @@ fn parse_push_message(ac_params: &[SExpr], s: &ParserState) -> Result<&'static K
let message = to_simple_expr(ac_params, s);
custom(CustomAction::PushMessage(message), &s.a)
}
#[cfg(any(target_os = "windows", target_os = "unknown"))]
pub mod windows;
#[cfg(any(target_os = "windows", target_os = "unknown"))]
pub use windows::*;
#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(target_os = "linux")]
pub use linux::*;
#[cfg(target_os = "macos")]
pub mod macos;
#[cfg(target_os = "macos")]
pub use macos::*;

fn to_simple_expr(params: &[SExpr], s: &ParserState) -> Vec<SimpleSExpr> {
let mut result: Vec<SimpleSExpr> = Vec::new();
Expand Down
51 changes: 51 additions & 0 deletions parser/src/cfg/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1954,3 +1954,54 @@ fn disallow_whitespace_in_tooltip_size() {
";
parse_cfg(source).map(|_| ()).expect_err("fails");
}

#[cfg(any(target_os = "windows", target_os = "unknown"))]
#[test]
fn win_message_ok() {
let source = r#"
(defcfg)
(defsrc 3 4 5 6 7)
(deflayermap (win-msg)
3 (msg❖async 1 )
4 (win-post-msg 2 )
5 (win-post-msg 3 )
6 (msg❖async 3 0 "" "kanata_your_custom_message_string_unique_id")
)
"#;
parse_cfg(source)
.map_err(|e| eprintln!("{:?}", miette::Error::from(e)))
.expect("parse succeeds");
}

#[cfg(any(target_os = "windows", target_os = "unknown"))]
#[test]
fn win_message_wrong_number_value() {
let source = "
(defcfg)
(defsrc 1)
(deflayer base (msg❖async -1))
";
parse_cfg(source).map(|_| ()).expect_err("fails");
}

#[cfg(any(target_os = "windows", target_os = "unknown"))]
#[test]
fn win_message_wrong_number_type() {
let source = r#"
(defcfg)
(defsrc 1)
(deflayer base (msg❖async "a" ))
"#;
parse_cfg(source).map(|_| ()).expect_err("fails");
}

#[cfg(any(target_os = "windows", target_os = "unknown"))]
#[test]
fn win_message_too_many_args() {
let source = r#"
(defcfg)
(defsrc 1)
(deflayer base (msg❖async 0 1 "a" "b" "c" 5 ))
"#;
parse_cfg(source).map(|_| ()).expect_err("fails");
}
Loading
Loading