I would like to propose that we export a means for a Gio application
to handle IPC data events.
I have a prototype that works for Windows, and I'm not sure whether
this generalizes to macOS or Linux - but maybe it does.
Basically, on Windows we can listen for WM_COPYDATA messages and
generate a corresponding app event that the Gio app can consume.
This allows the application to receive and process custom data
delivered to it by another.
The prototype is here https://git.sr.ht/~jackmordaunt/activation-test
To achieve this I patched Gio like so:
1. define `UserData` type to contain the payload (no-ops for all
systems but Windows, possibly generalizes to other OS)
2. handle the `WM_COPYDATA` message in the window proc, generating a
`UserData` event containing the payload
```
diff --git a/app/os_android.go b/app/os_android.go
index 23e1f6ba..777a9881 100644
--- a/app/os_android.go
+++ b/app/os_android.go
@@ -209,6 +209,8 @@ type ViewEvent struct {
View uintptr
}
+type UserEvent []byte
+
type jvalue uint64 // The largest JNI type fits in 64 bits.
var dataDirChan = make(chan string, 1)
@@ -1455,3 +1457,4 @@ func Java_org_gioui_Gio_scheduleMainFuncs(env
*C.JNIEnv, cls C.jclass) {
}
func (_ ViewEvent) ImplementsEvent() {}
+func (_ UserEvent) ImplementsEvent() {}
diff --git a/app/os_ios.go b/app/os_ios.go
index 5cbabfe8..0cffa025 100644
--- a/app/os_ios.go
+++ b/app/os_ios.go
@@ -91,6 +91,8 @@ type ViewEvent struct {
ViewController uintptr
}
+type UserEvent []byte
+
type window struct {
view C.CFTypeRef
w *callbacks
@@ -357,3 +359,4 @@ func gio_runMain() {
}
func (_ ViewEvent) ImplementsEvent() {}
+func (_ UserEvent) ImplementsEvent() {}
diff --git a/app/os_js.go b/app/os_js.go
index f7fece4c..95d4f56e 100644
--- a/app/os_js.go
+++ b/app/os_js.go
@@ -26,6 +26,8 @@ type ViewEvent struct {
Element js.Value
}
+type UserEvent []byte
+
type contextStatus int
const (
@@ -822,3 +824,4 @@ func translateKey(k string) (string, bool) {
}
func (_ ViewEvent) ImplementsEvent() {}
+func (_ UserEvent) ImplementsEvent() {}
diff --git a/app/os_macos.go b/app/os_macos.go
index 1b4ac557..17fb919c 100644
--- a/app/os_macos.go
+++ b/app/os_macos.go
@@ -240,6 +240,8 @@ type ViewEvent struct {
Layer uintptr
}
+type UserEvent []byte
+
type window struct {
view C.CFTypeRef
w *callbacks
@@ -992,3 +994,4 @@ func convertMods(mods C.NSUInteger) key.Modifiers {
}
func (_ ViewEvent) ImplementsEvent() {}
+func (_ UserEvent) ImplementsEvent() {}
diff --git a/app/os_windows.go b/app/os_windows.go
index 4aa806f1..773da76d 100644
--- a/app/os_windows.go
+++ b/app/os_windows.go
@@ -32,6 +32,12 @@ type ViewEvent struct {
HWND uintptr
}
+// UserEvent contains a custom payload sent over a WM_COPYDATA.
+// This type implements event.Event so that app logic can process it.
+// Maybe there can be an anolog for macOS and linux, and this
+// could be a cross platform way to do IPC? I'm not sure.
+type UserEvent []byte
+
type window struct {
hwnd syscall.Handle
hdc syscall.Handle
@@ -54,7 +60,10 @@ type window struct {
config Config
}
-const _WM_WAKEUP = windows.WM_USER + iota
+const (
+ _WM_WAKEUP = windows.WM_USER + iota
+ _WM_COPYDATA = 0x004A
+)
type gpuAPI struct {
priority int
@@ -432,11 +441,25 @@ func windowProc(hwnd syscall.Handle, msg uint32,
wParam, lParam uintptr) uintptr
case windows.WM_IME_ENDCOMPOSITION:
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
return windows.TRUE
+ case _WM_COPYDATA:
+ data := (*COPYDATA)(unsafe.Pointer(lParam))
+ by := unsafe.Slice((*byte)(unsafe.Pointer(data.ptr)), data.size)
+ local := make([]byte, len(by))
+ copy(local, by)
+ w.w.Event(UserEvent(local))
+ return windows.TRUE
}
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
}
+// COPYDATA describes the data structure referenced by WM_COPYDATA.
+type COPYDATA struct {
+ kind uintptr
+ size uint32
+ ptr uintptr
+}
+
func getModifiers() key.Modifiers {
var kmods key.Modifiers
if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 ||
windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {
@@ -968,3 +991,4 @@ func configForDPI(dpi int) unit.Metric {
}
func (_ ViewEvent) ImplementsEvent() {}
+func (_ UserEvent) ImplementsEvent() {}
diff --git a/app/window.go b/app/window.go
index 342d2f62..3a2324e0 100644
--- a/app/window.go
+++ b/app/window.go
@@ -899,6 +899,8 @@ func (w *Window) processEvent(d driver, e
event.Event) bool {
w.decorations.Config = e2.Config
e2.Config = w.effectiveConfig()
w.out <- e2
+ case UserEvent:
+ w.out <- e2
case event.Event:
handled := w.queue.q.Queue(e2)
if e, ok := e.(key.Event); ok && !handled {
```
Btw. It's possible to pipes for IPC, which is also more cross-platform
and doesn't require integrating with the windowing framework.
Also, there are other approaches
https://learn.microsoft.com/en-us/windows/win32/ipc/interprocess-communications
On Thu, Nov 30, 2023 at 10:41 AM Jack Mordaunt <jackmordaunt@gmail.com> wrote:
>> I would like to propose that we export a means for a Gio application> to handle IPC data events.>> I have a prototype that works for Windows, and I'm not sure whether> this generalizes to macOS or Linux - but maybe it does.>> Basically, on Windows we can listen for WM_COPYDATA messages and> generate a corresponding app event that the Gio app can consume.> This allows the application to receive and process custom data> delivered to it by another.>> The prototype is here https://git.sr.ht/~jackmordaunt/activation-test>> To achieve this I patched Gio like so:> 1. define `UserData` type to contain the payload (no-ops for all> systems but Windows, possibly generalizes to other OS)> 2. handle the `WM_COPYDATA` message in the window proc, generating a> `UserData` event containing the payload>> ```> diff --git a/app/os_android.go b/app/os_android.go> index 23e1f6ba..777a9881 100644> --- a/app/os_android.go> +++ b/app/os_android.go> @@ -209,6 +209,8 @@ type ViewEvent struct {> View uintptr> }>> +type UserEvent []byte> +> type jvalue uint64 // The largest JNI type fits in 64 bits.>> var dataDirChan = make(chan string, 1)> @@ -1455,3 +1457,4 @@ func Java_org_gioui_Gio_scheduleMainFuncs(env> *C.JNIEnv, cls C.jclass) {> }>> func (_ ViewEvent) ImplementsEvent() {}> +func (_ UserEvent) ImplementsEvent() {}> diff --git a/app/os_ios.go b/app/os_ios.go> index 5cbabfe8..0cffa025 100644> --- a/app/os_ios.go> +++ b/app/os_ios.go> @@ -91,6 +91,8 @@ type ViewEvent struct {> ViewController uintptr> }>> +type UserEvent []byte> +> type window struct {> view C.CFTypeRef> w *callbacks> @@ -357,3 +359,4 @@ func gio_runMain() {> }>> func (_ ViewEvent) ImplementsEvent() {}> +func (_ UserEvent) ImplementsEvent() {}> diff --git a/app/os_js.go b/app/os_js.go> index f7fece4c..95d4f56e 100644> --- a/app/os_js.go> +++ b/app/os_js.go> @@ -26,6 +26,8 @@ type ViewEvent struct {> Element js.Value> }>> +type UserEvent []byte> +> type contextStatus int>> const (> @@ -822,3 +824,4 @@ func translateKey(k string) (string, bool) {> }>> func (_ ViewEvent) ImplementsEvent() {}> +func (_ UserEvent) ImplementsEvent() {}> diff --git a/app/os_macos.go b/app/os_macos.go> index 1b4ac557..17fb919c 100644> --- a/app/os_macos.go> +++ b/app/os_macos.go> @@ -240,6 +240,8 @@ type ViewEvent struct {> Layer uintptr> }>> +type UserEvent []byte> +> type window struct {> view C.CFTypeRef> w *callbacks> @@ -992,3 +994,4 @@ func convertMods(mods C.NSUInteger) key.Modifiers {> }>> func (_ ViewEvent) ImplementsEvent() {}> +func (_ UserEvent) ImplementsEvent() {}> diff --git a/app/os_windows.go b/app/os_windows.go> index 4aa806f1..773da76d 100644> --- a/app/os_windows.go> +++ b/app/os_windows.go> @@ -32,6 +32,12 @@ type ViewEvent struct {> HWND uintptr> }>> +// UserEvent contains a custom payload sent over a WM_COPYDATA.> +// This type implements event.Event so that app logic can process it.> +// Maybe there can be an anolog for macOS and linux, and this> +// could be a cross platform way to do IPC? I'm not sure.> +type UserEvent []byte> +> type window struct {> hwnd syscall.Handle> hdc syscall.Handle> @@ -54,7 +60,10 @@ type window struct {> config Config> }>> -const _WM_WAKEUP = windows.WM_USER + iota> +const (> + _WM_WAKEUP = windows.WM_USER + iota> + _WM_COPYDATA = 0x004A> +)>> type gpuAPI struct {> priority int> @@ -432,11 +441,25 @@ func windowProc(hwnd syscall.Handle, msg uint32,> wParam, lParam uintptr) uintptr> case windows.WM_IME_ENDCOMPOSITION:> w.w.SetComposingRegion(key.Range{Start: -1, End: -1})> return windows.TRUE> + case _WM_COPYDATA:> + data := (*COPYDATA)(unsafe.Pointer(lParam))> + by := unsafe.Slice((*byte)(unsafe.Pointer(data.ptr)), data.size)> + local := make([]byte, len(by))> + copy(local, by)> + w.w.Event(UserEvent(local))> + return windows.TRUE> }>> return windows.DefWindowProc(hwnd, msg, wParam, lParam)> }>> +// COPYDATA describes the data structure referenced by WM_COPYDATA.> +type COPYDATA struct {> + kind uintptr> + size uint32> + ptr uintptr> +}> +> func getModifiers() key.Modifiers {> var kmods key.Modifiers> if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 ||> windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {> @@ -968,3 +991,4 @@ func configForDPI(dpi int) unit.Metric {> }>> func (_ ViewEvent) ImplementsEvent() {}> +func (_ UserEvent) ImplementsEvent() {}> diff --git a/app/window.go b/app/window.go> index 342d2f62..3a2324e0 100644> --- a/app/window.go> +++ b/app/window.go> @@ -899,6 +899,8 @@ func (w *Window) processEvent(d driver, e> event.Event) bool {> w.decorations.Config = e2.Config> e2.Config = w.effectiveConfig()> w.out <- e2> + case UserEvent:> + w.out <- e2> case event.Event:> handled := w.queue.q.Queue(e2)> if e, ok := e.(key.Event); ok && !handled {> ```
Not directly related, but one PR(https://github.com/gioui/gio/pull/117/files) uses
WM_COPYDATA to handle "deeplinking" on Windows. (Previously, the original
PR used "named-mmap").
--
Lucas Rodrigues
inkeliz@inkeliz.com
On Thu, Nov 30, 2023, at 8:58 AM, Egon Elbre wrote:
> Btw. It's possible to pipes for IPC, which is also more cross-platform> and doesn't require integrating with the windowing framework.>> Also, there are other approaches> https://learn.microsoft.com/en-us/windows/win32/ipc/interprocess-communications>> On Thu, Nov 30, 2023 at 10:41 AM Jack Mordaunt <jackmordaunt@gmail.com> wrote:>>>> I would like to propose that we export a means for a Gio application>> to handle IPC data events.>>>> I have a prototype that works for Windows, and I'm not sure whether>> this generalizes to macOS or Linux - but maybe it does.>>>> Basically, on Windows we can listen for WM_COPYDATA messages and>> generate a corresponding app event that the Gio app can consume.>> This allows the application to receive and process custom data>> delivered to it by another.>>>> The prototype is here https://git.sr.ht/~jackmordaunt/activation-test>>>> To achieve this I patched Gio like so:>> 1. define `UserData` type to contain the payload (no-ops for all>> systems but Windows, possibly generalizes to other OS)>> 2. handle the `WM_COPYDATA` message in the window proc, generating a>> `UserData` event containing the payload>>>> ```>> diff --git a/app/os_android.go b/app/os_android.go>> index 23e1f6ba..777a9881 100644>> --- a/app/os_android.go>> +++ b/app/os_android.go>> @@ -209,6 +209,8 @@ type ViewEvent struct {>> View uintptr>> }>>>> +type UserEvent []byte>> +>> type jvalue uint64 // The largest JNI type fits in 64 bits.>>>> var dataDirChan = make(chan string, 1)>> @@ -1455,3 +1457,4 @@ func Java_org_gioui_Gio_scheduleMainFuncs(env>> *C.JNIEnv, cls C.jclass) {>> }>>>> func (_ ViewEvent) ImplementsEvent() {}>> +func (_ UserEvent) ImplementsEvent() {}>> diff --git a/app/os_ios.go b/app/os_ios.go>> index 5cbabfe8..0cffa025 100644>> --- a/app/os_ios.go>> +++ b/app/os_ios.go>> @@ -91,6 +91,8 @@ type ViewEvent struct {>> ViewController uintptr>> }>>>> +type UserEvent []byte>> +>> type window struct {>> view C.CFTypeRef>> w *callbacks>> @@ -357,3 +359,4 @@ func gio_runMain() {>> }>>>> func (_ ViewEvent) ImplementsEvent() {}>> +func (_ UserEvent) ImplementsEvent() {}>> diff --git a/app/os_js.go b/app/os_js.go>> index f7fece4c..95d4f56e 100644>> --- a/app/os_js.go>> +++ b/app/os_js.go>> @@ -26,6 +26,8 @@ type ViewEvent struct {>> Element js.Value>> }>>>> +type UserEvent []byte>> +>> type contextStatus int>>>> const (>> @@ -822,3 +824,4 @@ func translateKey(k string) (string, bool) {>> }>>>> func (_ ViewEvent) ImplementsEvent() {}>> +func (_ UserEvent) ImplementsEvent() {}>> diff --git a/app/os_macos.go b/app/os_macos.go>> index 1b4ac557..17fb919c 100644>> --- a/app/os_macos.go>> +++ b/app/os_macos.go>> @@ -240,6 +240,8 @@ type ViewEvent struct {>> Layer uintptr>> }>>>> +type UserEvent []byte>> +>> type window struct {>> view C.CFTypeRef>> w *callbacks>> @@ -992,3 +994,4 @@ func convertMods(mods C.NSUInteger) key.Modifiers {>> }>>>> func (_ ViewEvent) ImplementsEvent() {}>> +func (_ UserEvent) ImplementsEvent() {}>> diff --git a/app/os_windows.go b/app/os_windows.go>> index 4aa806f1..773da76d 100644>> --- a/app/os_windows.go>> +++ b/app/os_windows.go>> @@ -32,6 +32,12 @@ type ViewEvent struct {>> HWND uintptr>> }>>>> +// UserEvent contains a custom payload sent over a WM_COPYDATA.>> +// This type implements event.Event so that app logic can process it.>> +// Maybe there can be an anolog for macOS and linux, and this>> +// could be a cross platform way to do IPC? I'm not sure.>> +type UserEvent []byte>> +>> type window struct {>> hwnd syscall.Handle>> hdc syscall.Handle>> @@ -54,7 +60,10 @@ type window struct {>> config Config>> }>>>> -const _WM_WAKEUP = windows.WM_USER + iota>> +const (>> + _WM_WAKEUP = windows.WM_USER + iota>> + _WM_COPYDATA = 0x004A>> +)>>>> type gpuAPI struct {>> priority int>> @@ -432,11 +441,25 @@ func windowProc(hwnd syscall.Handle, msg uint32,>> wParam, lParam uintptr) uintptr>> case windows.WM_IME_ENDCOMPOSITION:>> w.w.SetComposingRegion(key.Range{Start: -1, End: -1})>> return windows.TRUE>> + case _WM_COPYDATA:>> + data := (*COPYDATA)(unsafe.Pointer(lParam))>> + by := unsafe.Slice((*byte)(unsafe.Pointer(data.ptr)), data.size)>> + local := make([]byte, len(by))>> + copy(local, by)>> + w.w.Event(UserEvent(local))>> + return windows.TRUE>> }>>>> return windows.DefWindowProc(hwnd, msg, wParam, lParam)>> }>>>> +// COPYDATA describes the data structure referenced by WM_COPYDATA.>> +type COPYDATA struct {>> + kind uintptr>> + size uint32>> + ptr uintptr>> +}>> +>> func getModifiers() key.Modifiers {>> var kmods key.Modifiers>> if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 ||>> windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {>> @@ -968,3 +991,4 @@ func configForDPI(dpi int) unit.Metric {>> }>>>> func (_ ViewEvent) ImplementsEvent() {}>> +func (_ UserEvent) ImplementsEvent() {}>> diff --git a/app/window.go b/app/window.go>> index 342d2f62..3a2324e0 100644>> --- a/app/window.go>> +++ b/app/window.go>> @@ -899,6 +899,8 @@ func (w *Window) processEvent(d driver, e>> event.Event) bool {>> w.decorations.Config = e2.Config>> e2.Config = w.effectiveConfig()>> w.out <- e2>> + case UserEvent:>> + w.out <- e2>> case event.Event:>> handled := w.queue.q.Queue(e2)>> if e, ok := e.(key.Event); ok && !handled {>> ```