-
Notifications
You must be signed in to change notification settings - Fork 126
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
base: main
Are you sure you want to change the base?
Conversation
a27b731
to
29ed32c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My first impression is that this overlaps too much in purpose with existing functionality:
Line 2252 in d45b8cc
custom(CustomAction::PushMessage(message), &s.a) |
Admittedly push-msg
never got documented in config.adoc
.
Seems like it is possible to use TCP sockets with AHK, even though it doesn't seem as convenient as using WinAPI. Still, I don't currently feel that this feature pulls its weight.
@@ -27,6 +27,8 @@ thiserror = "1.0.38" | |||
# binary. | |||
kanata-keyberon = { path = "../keyberon" } | |||
bytemuck = "1.15.0" | |||
colored = "2.1.0" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
@@ -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" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ofexpected 0–18,446,744,073,709,551,615
571e6f6
to
f715861
Compare
Never heard of the socket lib and the author mentions some of the examples aren't even working TheArkive/Socket_ahk2#3 (comment) But also AHK doesn't have JSON natively, so would need yet another dependency to parse JSON Also, windows messages are more universal, so not limited to AHK (it's just that AHK is the example I have available since that's what I'm using to coordinate mode switches in another app and it seemed like the simplest method to achieve simple things like inserting a date), so it's more likely that it'll be easier (or maybe even the only way possible) to integrate with other apps. Besides this could be exended to send other messages like |
Edit: actually considering that PushMessage (as jtroo mentioned) is already an existing feature that solves the wanted way of communication, the problem with this PR is not bevause of not bringing an additional IPC method to linux/mac, but about not adding an additional (redundant) one to windows-only.
That's not a problem of used IPC method. We currently use json in tcp communication, but we might as well allow switching to some other/custom one on client's demand. Edit: the most reasonable approach from kanata standpoint would be to stay with json-only communication, and require clients to use json. Json is a standard
What are "regular" messages? |
That's still usable if the target app supports it. This type of comm is strictly additive (and seems to be simpler / powerful) |
Rethinking this, json is standard enough, and it shouldn't be on kanata side to switch to an alternative communication format just because some client don't support it OOTB. |
What are the things that kanata can't do but AHK can? Can these things be added to kanata natively? If so, maybe the integration with AHK wouldn't be worth adding? |
Any OS integration: launch an app, paste some text, resize or minimize a window, show a tooltip, change keyboard layout, press some GUI button, hide a mouse, ... + a million of other things
Theoretically yes, and it'd be awesome if kanata could become the x-platform AHK!!! (its key rebinding base is more powerful)
Not if you want to use that stuff now and not in the rather distant future |
Platform-native OS IPC can be more convenient than sockets. Linux/BSD have dbus and macos appears to have distributed notifications too: https://stackoverflow.com/a/2724092 Some additional research/design should be done to ensure a single action that triggers platform-native IPC can (eventually, particularly for macos) be used on the supported platforms. |
great idea before you discarded it. Simple things should be simple, so if someone wants to send a |
think the latest & greatest IPC on a Mac is this low-level creature https://developer.apple.com/documentation/xpc though it seems that would require a Mac gui? (or maybe there is a way to stick a cli app in a bundle)
|
You should try using my AHK script for TCP-IPC with Kanata that I've written with a lot of hair pulling and head holding, because AHK, the programming/scripting language is not a well designed programming language, in my opinion, it seems ad-hocly designed, I kind of think the people who use AHK, are Stockholm-syndrome victims with AHK being the captor/tormentor, including me, and I wish the world, would move on to a better programming language, but AHK integrates so well with Windows, I wish a Python library that wraps the 3 cpp files in the AutoHotkey repository existed. Preview: (note how you can send arbitrary messages with tcpjsonipc3.mp4Here's the script: #NoEnv
#SingleInstance
SetBatchLines, -1
#Include %A_ScriptDir%\Jxon.ahk ; https://github.com/cocobelgica/AutoHotkey-JSON/blob/master/Jxon.ahk
#Include %A_ScriptDir%\Socket.ahk ; https://github.com/G33kDude/Socket.ahk/blob/master/Socket.ahk
Server := "127.0.0.1"
Port := 8089
; Create the Gui
Gui, Margin, 5, 5
Gui, Font, s12, Lucida Console
Gui, Add, Edit, w720 h320 ReadOnly hWndhLog
Gui, Add, Edit, w720 h320 ReadOnly hWndhChat
Gui, Add, Edit, w580 h20 hwndmsg vMessage
EM_SETCUEBANNER := 0x1501
SendMessage, EM_SETCUEBANNER, true, "message.. e.g. {""ChangeLayer"": {""new"": ""mouseLyr""}}",, ahk_id %msg%
Gui, Font ; default
Gui, Add, Button, x+m yp-1 w55 h22 gSendBtnClick Default, Send
GuiControl, Focus, Message
Gui, Show
; Create the TCP client class instance
Client := new KanataTCP()
Client.hLog := hLog
Client.hChat := hChat
Client.Connect([Server, Port])
GuiClose()
{
ExitApp
}
SendBtnClick()
{
global Client, Chan, hChat
; Get the message box contents and empty the message box
GuiControlGet, Message
GuiControl,, Message
Client.SendText(Message)
EditAppend(hChat, "sent: " Message)
}
; Use the windows API to append text to an edit control quickly and efficiently
EditAppend(hEdit, Text)
{
Text .= "`r`n"
GuiControl, -Redraw, %hEdit%
SendMessage, 0x00E, 0, 0,, ahk_id %hEdit% ; WM_GETTEXTLENGTH
SendMessage, 0x0B1, ErrorLevel, ErrorLevel,, ahk_id %hEdit% ; EM_SETSEL
SendMessage, 0x0C2, False, &Text,, ahk_id %hEdit% ; EM_REPLACESEL
SendMessage, 0x115, 7, 0,, ahk_id %hEdit% ; WM_VSCROLL SB_BOTTOM
GuiControl, +Redraw, %hEdit%
}
EditAppend(hChat, "started listening..")
class KanataTCP extends SocketTCP
{
static Blocking := False
static Buffer, hLog, hChat
Connect(Address)
{
Socket.Connect.Call(this, Address)
}
SendText(Text, Encoding:="UTF-8")
{
EditAppend(this.hLog, "client: " Text)
SocketTCP.SendText.Call(this, Text)
}
OnRecv()
{
this.Buffer .= this.RecvText(,, "UTF-8")
; Split the buffer into one or more lines, putting
; any remaining text back into the buffer.
Lines := StrSplit(this.Buffer, "`n", "`r")
this.Buffer := Lines.Pop()
for Index, Line in Lines
{
EditAppend(this.hLog, "server: " Line)
try {
parsed := Jxon_Load(Line)
;; example of how to check for specific message types:
if parsed.LayerChange["new"] == "mouseLayer" {
EditAppend(this.hLog, "CHANGED TO MOUSE LAYER")
;; you can do anything you want here
}
if pmessage:= parsed.MessagePush.message {
if pmessage[1] == "make_fullscreen" {
WinMaximize, A ;; AHK function that makes currently active window fullscreen/maximized
EditAppend(this.hLog, "message: " pmessage[1] " window maximized")
}
}
} catch e {
EditAppend(this.hChat, "parse failed: " e.Message)
}
}
}
OnDisconnect()
{
global Client
EditAppend(this.hChat, Chr(0x26A0) Chr(0xFE0E) " disconnected..")
SetTimer, Reconnect, 2000
}
}
Reconnect()
{
global Server, Port, Client
EditAppend(Client.hChat, "attempting to reconnect..")
try {
Client.Disconnect()
Client.Connect([Server, Port])
SetTimer, Reconnect, Off
EditAppend(Client.hChat, "reconnected")
} catch e {
EditAppend(Client.hChat, "reconnect failed: " e.Message)
}
} Note how in the code above you can check for message types sent with I think the Kanata project should continue to strive for feature parity and consistency on all platforms.
I think you guys are underestimating the task of creating a cross-platform IPC construct that works on Windows, macOS and Linux, instead of implementing a cross-platform IPC abstraction in Kanata, I think it is better to leverage the Rust ecosystem, which has a few crates/libraries that provide an implementation for a abstracted cross-platform IPC construct (based on named pipes on Windows and unix domain sockets on Unix OSes tokio-rs/tokio#3760, these are what Chromium uses for IPC too, with its Process isolation architecture. also as of 2017, Windows 10, Windows has Unix domain sockets). but I can't think of any advantages abstracted sockets have over TCP. I also think similar to the existing IPC mechanism, if a new mechanism is added, it should be asynchronous, not synchronous/blocking. it appears I think JSON was the right choice, and is still the ideal choice for a serialization format, because it is ubiquitously implemented in almost all programming languages, schemaless, simple, and easy for machines (to deserialize and serialize/generate) and for humans (to read and write). |
It's nice that you managed to make IPC with kanata via TCP working with AHK. Maybe if it's possible it could be made into a library to make the integration from AHK side as simple as importing and just activating?
Unix sockets have build-in access control by setting owner and UGO permission on linux/mac (and in Windows too (see here) by using file permissions), while TCP is not secured by default from being read/written by any user on the system. But do we need this additional security in the first place? |
It's ahk v1, which I don't plan to go back to since it's much worse than v2
And this PR is literally about enabling simple IPC that doesn't require pulling your hair out or holding your head on the receiving end to get a simple integer
There is also this very cool IPC project https://iceoryx.io "cutting-edge service-oriented zero-copy lock-free inter-process communication middleware", might just as well use the all the new shiny modern things :)
Where is this expense? Messages are OS-level mechanism that can work with any app that has a window, implemented in any language of your choice
I think these do exist, like https://github.com/spyoungtech/ahk or https://github.com/mkzeender/autohotpy. Though haven't used any of them, think projects like these had serious limitations |
e08dbb1
to
cdfc271
Compare
To state my earlier position more strongly, actions that are explicitly platform-specific will not be merged (in contrast to, for example
Iceoryx looks interesting but if the goal is user-convenience+use-any-language then it seems to not be a winner over existing platform-native IPC. Reading more about Checking what's possible in PowerShell, I adapted this example and it seems to work; I didn't test any listener, but the post returned if (-not ("win32.nativemethods" -as [type])) {
# import functions from win32
add-type -Namespace Win32 -Name NativeMethods -MemberDefinition @"
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool PostMessageW(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern uint RegisterWindowMessageW(string lpString);
"@
}
$msgid = [win32.nativemethods]::RegisterWindowMessageW("hello")
[win32.nativemethods]::PostMessageW([intptr]0xffff, $msgid, [UIntPtr]::new(123), [intptr]321) On another point, if all one really wants to do is use AHK from kanata, another option is to call AHK from I guess |
That's unfortunate as you're just blocking a simple powerful interface with no real alternatives
AutoHotkey is already running, cmd would launch a new instance, which upon parsing the args would need to do everything else to communicate with the existing instance, so a rather slow and clunky mechanism with unavoidable downsides
Which in the case of AHK would mean depending on 2 libraries, including an untested TCP one... just to send a single number
By the way, PowerShell has the unfortunate property of being slow to start, so also not a good alternative for many per-key actions |
Please, let's discuss first before treating any statements as facts. There are alternatives, and, one of which we discussed above, which is using TCP+JSON libs in AHK, and making no changes in kanata. But you claim that it's not a "real" solution, how do you define "real" anyway? Assuming it means low latency and simple setup on ahk side, then TCP+JSON already provides low latency, and if the script that Wis has written could be ported to v2 and made refactored into library with nice API, it would also make setup comparably easy to using WinMessages. If one still insisted on using Win Messages in AHK, another alternative could be writing a simple powershell script just to act as a middleman between TCP <-> Win Messages. It would require a bit more involve involved setup on user side though, as this script would need to be first ran in the background. |
At least as convenient and at least as powerful
But not the simple setup. Also, we don't really know what this provides since it hasn't been tested, the author of one lib has some explicit reservations, not sure how extensively wis' lib has been used (though both seem to work).
IF, what should the poor user wanting to paste a simple date do in the meantime?
Which is another big hurdle - do you now go back to the barbaric days of using task scheduler to try to autorun it on Windows startup? |
What are the concerns? It either works or not. For the TCP lib that jtroo linked above, there's currently only 1 issue opened and it's just about error message being wrong. It doesn't seem to be a major blocker, this is not some production environent where every edge case has has to be handled correctly. We really only care about happy path working well.
Specifically for outputting the current date, you even posted a workaround yourself: #275 (comment). Maybe another idea for the future after adding rust plugin/script system to kanata (tracking issue), it would be possible to implement the get-current-date function in a plugin/script.
I'm providing you with a real solution to this problem. It gets the job done, it's better than cmd (no cmd feature needed), no flashing windows in GUI build. It's one time setup, and we got whole thread about how to auto-run a program #193. I recognize that you put a lot of effort into this PR, and probably feel upset about it not getting an approval. If you care about your patches getting merged and don't want something similar to happen in the future, generally it's a good idea to first open an issue and participate in the discussion before committing to making any significant changes. |
Including features can have negatives (maintenance burden, documentation complexity, precedent for future features, etc.). Excluding features has the benefit of avoiding these negatives. The main reason in this case is precedent - my current stance is "no platform-specific actions". That stance may one day change still, but this particular new action is not the one that will change it. |
"Some of my old examples, that I thought were simple, aren't actually working anymore". So i guess the concern that it might not work?
and I myself noted this is a bad workround and went on to add a proper mechanism which resulted in this PR
Good idea, I'd love some wasm plugin for custom fuctions #797 (comment). But the complexity of this is huge compared to passind data to a script
That thread has no solution for running the UIAccessed version of kanata, and by extension, your "real powershell solution" script would also run without UIA, and as such afaik won't be able to send a message to the main AHK script that runs with UIA. So no job will get done. Sure, there might be another obscure Win API workaround (task scheduler uses the "wrong" API to launch processes), but it doesn't exist now I don't get why you're so eagerly pushing such poor untested alternatives onto users when "Usability issues" are explicitly considered bugs in this project
But then it's even easier to shoot down an idea. At least now we know there is a simple working solution without extra dependencies |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, I am open to the idea of a "platform-specific IPC action" that calls PostMessageW
on Windows, integrates with dbus
on Linux, and does whatever is suitable on macOS.
But such functionality must first be attempted as a single unified action, and not be separately implemented by a different win-*
action, linux-*
action, and macos-*
action, until the proper effort and investigation has been done to conclude that it does not make sense to do so, at the very least considering Windows and Linux.
That's a worthy goal, though can we not in the interim have this as experimental subject-to-future-unification command marked as such in the docs? Then whenever that bright unified future arrives users would be able to simply rename the commands and have the benefit of them now working everywhere while at the same time being able to benefit today on at least some platforms! |
I see no need to rush this. This is not core functionality; anyone who urgently desires a WinAPI integration can see this code and should be capable of compiling from source. Code is already here for Windows, and unlike macOS, Linux is free to develop and test on, so let's do so. Here's some suggestions for the winAPI side:
Here's some relations to Linux's dbus that I've identified thus far. Seems like dbus signals rather method calls (i.e.
So for the "IPC identifier" of sorts, Windows would use the shared message ID as required and target window/title as optional, and Linux has the items above. Perhaps the "IPC identifier" could be a list as the first parameter so that the item count can be allowed to differ while still remaining as the "IPC identifier". Thinking on the "body" of the message rather than the identifier, dbus has a richer type system than can be supported by winAPI's two string parameters. To unify them, perhaps the body should be limited to a single string, and perhaps we should instead treat winAPI's two strings limit - which each has a 255 byte limit - as a single 510 byte string by mandating that the receiver concatenate them together. The body could just be some arbitrary string, or we could make it a serialized
|
ba21f86
to
2a84fc3
Compare
Describe your changes. Use imperative present tense.
Allows IPC communication via Windows Post messages (currently system-wide, but only caught by apps with the same registered message ID) so that you can, e.g., use AutoHotkey to do what kanata can't do at the moment
Closes #1078
Checklist