diff --git a/Cargo.lock b/Cargo.lock index b6c990905..84e40cd79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -229,6 +235,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -575,6 +591,7 @@ version = "1.7.0-prerelease" dependencies = [ "anyhow", "clap", + "colored", "core-graphics", "dirs", "embed-resource", @@ -606,6 +623,7 @@ dependencies = [ "simplelog", "strip-ansi-escapes", "time", + "widestring", "winapi", "windows-sys 0.52.0", ] @@ -648,10 +666,12 @@ version = "0.161.0" dependencies = [ "anyhow", "bytemuck", + "colored", "itertools", "kanata-keyberon", "log", "miette", + "num-format", "once_cell", "parking_lot", "patricia_tree", @@ -885,6 +905,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + [[package]] name = "num_enum" version = "0.6.1" diff --git a/Cargo.toml b/Cargo.toml index 92b899b1d..8e312c19c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" @@ -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 } @@ -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", ] diff --git a/cfg_samples/win-msg/insert-date.ahk b/cfg_samples/win-msg/insert-date.ahk new file mode 100644 index 000000000..2953fb756 --- /dev/null +++ b/cfg_samples/win-msg/insert-date.ahk @@ -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) + } +} diff --git a/cfg_samples/win-msg/win-msg.kbd b/cfg_samples/win-msg/win-msg.kbd new file mode 100644 index 000000000..83c4b7f4d --- /dev/null +++ b/cfg_samples/win-msg/win-msg.kbd @@ -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 +) diff --git a/docs/config.adoc b/docs/config.adoc index 213db400b..78499a67a 100644 --- a/docs/config.adoc +++ b/docs/config.adoc @@ -2509,6 +2509,36 @@ they are an arbitrary length and can be very long. ) ---- +[[windows-only-send-message]] +=== Windows only: send-message +<> + +`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 <> diff --git a/parser/Cargo.toml b/parser/Cargo.toml index f2116c914..b5ac56eef 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -27,6 +27,8 @@ thiserror = "1.0.38" # binary. kanata-keyberon = { path = "../keyberon" } bytemuck = "1.15.0" +colored = "2.1.0" +num-format = "0.4.4" [features] cmd = [] diff --git a/parser/src/cfg/linux.rs b/parser/src/cfg/linux.rs new file mode 100644 index 000000000..5e0740386 --- /dev/null +++ b/parser/src/cfg/linux.rs @@ -0,0 +1,2 @@ +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] +pub struct WinMsg {} diff --git a/parser/src/cfg/list_actions.rs b/parser/src/cfg/list_actions.rs index 243f6991f..de8c525da 100644 --- a/parser/src/cfg/list_actions.rs +++ b/parser/src/cfg/list_actions.rs @@ -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"; @@ -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, diff --git a/parser/src/cfg/macos.rs b/parser/src/cfg/macos.rs new file mode 100644 index 000000000..5e0740386 --- /dev/null +++ b/parser/src/cfg/macos.rs @@ -0,0 +1,2 @@ +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] +pub struct WinMsg {} diff --git a/parser/src/cfg/mod.rs b/parser/src/cfg/mod.rs index b77ec7ad9..c949ccdf3 100755 --- a/parser/src/cfg/mod.rs +++ b/parser/src/cfg/mod.rs @@ -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) @@ -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 { let mut result: Vec = Vec::new(); diff --git a/parser/src/cfg/tests.rs b/parser/src/cfg/tests.rs index 9381d63d9..5e3f8a1f1 100644 --- a/parser/src/cfg/tests.rs +++ b/parser/src/cfg/tests.rs @@ -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"); +} diff --git a/parser/src/cfg/windows.rs b/parser/src/cfg/windows.rs new file mode 100644 index 000000000..dbdcb02dd --- /dev/null +++ b/parser/src/cfg/windows.rs @@ -0,0 +1,194 @@ +use crate::cfg::error::*; + +use crate::cfg::custom; +use crate::cfg::str_ext::TrimAtomQuotes; +use crate::cfg::KanataAction; +use crate::cfg::ParseError; +use crate::cfg::ParserState; +use crate::cfg::SExpr; +use crate::custom_action::CustomAction; +use std::ops::Deref; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct WinMsgSid(String); +impl Default for WinMsgSid { + fn default() -> Self { + Self("kanata_4117d2917ccb4678a7a8c71a5ff898ed".to_string()) + } //TODO: replace with str +} +impl From for String { + fn from(val: WinMsgSid) -> Self { + val.0 + } +} +impl Deref for WinMsgSid { + type Target = String; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl AsRef for WinMsgSid +where + T: ?Sized, + ::Target: AsRef, +{ + fn as_ref(&self) -> &T { + self.deref().as_ref() + } +} +impl From for WinMsgSid { + fn from(s: String) -> Self { + WinMsgSid(s) + } +} +impl From<&str> for WinMsgSid { + fn from(s: &str) -> Self { + WinMsgSid(s.to_string()) + } +} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct WinMsgTarget { + win_class: String, + win_name: String, +} +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] +pub struct WinMsg { + pub win_tgt: WinMsgTarget, + pub msg_sid: WinMsgSid, + pub argu: usize, + pub argi: isize, +} +impl Default for WinMsgTarget { + fn default() -> Self { + Self { + win_class: "AutoHotkey".to_string(), //TODO: replace with str + win_name: "\\AutoHotkey.ahk".to_string(), + } + } +} + +use colored::*; +use num_format::{Locale, ToFormattedString}; +pub fn to_win_msg(ac_params: &[SExpr], s: &ParserState, cmd_name: &str) -> Result { + const ERR_MSG: &str = "expects at most 5 parameters: 2 message numeric arguments, target window title, shared message string id, target window class"; + let cmd_name = cmd_name.blue().bold(); + if ac_params.len() > 5 { + bail!("{} {}", cmd_name, ERR_MSG); + } + let mut win_tgt: WinMsgTarget = Default::default(); + let mut msg_sid = Default::default(); + let mut argu = Default::default(); + let mut argi = Default::default(); + if !ac_params.is_empty() { + let arg = &ac_params[0]; + if let Some(a) = arg.atom(s.vars()) { + argu = match str::parse::(a.trim_atom_quotes()) { + Ok(argu) => argu, + Err(_) => bail_expr!( + &arg, + "invalid numeric argument, expected {}–{}. {} {}", + usize::MIN.to_formatted_string(&Locale::en).blue(), + usize::MAX.to_formatted_string(&Locale::en).blue(), + cmd_name, + ERR_MSG + ), + } + } else { + bail!("{ERR_MSG}"); + } + } + if ac_params.len() > 1 { + let arg = &ac_params[1]; + if let Some(a) = arg.atom(s.vars()) { + argi = match str::parse::(a.trim_atom_quotes()) { + Ok(argi) => argi, + Err(_) => bail_expr!( + &arg, + "invalid numeric argument, expected {}–{}. {} {}", + isize::MIN.to_formatted_string(&Locale::en).blue(), + isize::MAX.to_formatted_string(&Locale::en).blue(), + cmd_name, + ERR_MSG + ), + } + } else { + bail!("{ERR_MSG}"); + } + } + if ac_params.len() > 2 { + let arg = &ac_params[2]; + if let Some(a) = arg.atom(s.vars()) { + let a = a.trim_atom_quotes(); + if !a.is_empty() { + win_tgt.win_name = a.to_string(); + } + } else { + bail_expr!( + &arg, + "invalid target window {}. {} {}", + "file name".blue(), + cmd_name, + ERR_MSG + ) + } + } + if ac_params.len() > 3 { + let arg = &ac_params[3]; + if let Some(a) = arg.atom(s.vars()) { + let a = a.trim_atom_quotes(); + if !a.is_empty() { + msg_sid = a.into(); + } + } else { + bail_expr!( + &arg, + "invalid message shared {}. {} {}", + "string id".blue(), + cmd_name, + ERR_MSG + ) + } + } + if ac_params.len() > 4 { + let arg = &ac_params[4]; + if let Some(a) = arg.atom(s.vars()) { + let a = a.trim_atom_quotes(); + if !a.is_empty() { + win_tgt.win_class = a.to_string(); + } + } else { + bail_expr!( + &arg, + "invalid target window {}. {} {}", + "class".blue(), + cmd_name, + ERR_MSG + ) + } + } + Ok(WinMsg { + win_tgt, + msg_sid, + argu, + argi, + }) +} + +pub fn win_send_message( + ac_params: &[SExpr], + s: &ParserState, + cmd_name: &str, +) -> Result<&'static KanataAction> { + let win_msg = to_win_msg(ac_params, s, cmd_name)?; + log::trace!("win_msg = {:?}", win_msg); + custom(CustomAction::WinSendMessage(win_msg), &s.a) +} +pub fn win_post_message( + ac_params: &[SExpr], + s: &ParserState, + cmd_name: &str, +) -> Result<&'static KanataAction> { + let win_msg = to_win_msg(ac_params, s, cmd_name)?; + log::trace!("win_msg = {:?}", win_msg); + custom(CustomAction::WinPostMessage(win_msg), &s.a) +} diff --git a/parser/src/custom_action.rs b/parser/src/custom_action.rs index 491e53a95..c8eb2be57 100644 --- a/parser/src/custom_action.rs +++ b/parser/src/custom_action.rs @@ -3,6 +3,7 @@ //! When adding a new custom action, the macro section of the config.adoc documentation may need to //! be updated, to include the new action to the documented list of supported actions in macro. +use crate::cfg::WinMsg; use anyhow::{anyhow, Result}; use core::fmt; use kanata_keyberon::key_code::KeyCode; @@ -14,6 +15,8 @@ pub enum CustomAction { Cmd(Vec), CmdOutputKeys(Vec), PushMessage(Vec), + WinSendMessage(WinMsg), + WinPostMessage(WinMsg), Unicode(char), Mouse(Btn), MouseTap(Btn), diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index 365d93303..9a480ea22 100755 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -1323,6 +1323,62 @@ impl Kanata { PUSH_MESSAGE ); } + CustomAction::WinSendMessage(_win_msg) => { + #[cfg(target_os = "windows")] + { + // log::trace!("Sent a message {_win_msg:?}"); + log::warn!("Sending a message isn't implemented yet"); + } + #[cfg(not(target_os = "windows"))] + log::warn!( + "{} or {} was used, but this only works on Windows.", + SEND_WMSG_SYNC, + SEND_WMSG_SYNC_A + ); + } + CustomAction::WinPostMessage(_win_msg) => { + #[cfg(target_os = "windows")] + { + log::trace!("Posted a message {_win_msg:?}"); + use colored::*; + use widestring::U16CString; + use windows_sys::core::PCWSTR; + use windows_sys::Win32::UI::WindowsAndMessaging::HWND_BROADCAST; + use windows_sys::Win32::UI::WindowsAndMessaging::{ + PostMessageW, RegisterWindowMessageW, + }; + log::debug!("Action PostMessage (async)"); + if let Ok(val_w16cs) = + U16CString::from_str(_win_msg.msg_sid.clone()) + { + let msg_txt: PCWSTR = val_w16cs.into_raw(); + let msg_id = unsafe { RegisterWindowMessageW(msg_txt) }; + let ret = unsafe { + PostMessageW( + HWND_BROADCAST, + msg_id, + _win_msg.argu, + _win_msg.argi, + ) + }; + if ret == 0 { + log::error!("Failed to post a message: {_win_msg:?}, OS error# {ret}"); + } + // TODO: call GetLastError to get error content + } else { + log::error!( + "Failed to parse {} into a Windows string", + _win_msg.msg_sid.to_string().blue() + ); + } + } + #[cfg(not(target_os = "windows"))] + log::warn!( + "{} or {} was used, but this only works on Windows.", + SEND_WMSG_ASYNC, + SEND_WMSG_ASYNC_A + ); + } CustomAction::FakeKey { coord, action } => { let (x, y) = (coord.x, coord.y); log::debug!(