~eliasnaur/gio

4 4

[PROPOSAL] Event based IPC (inter-process communication) using WM_COPYDATA

Jack Mordaunt <jackmordaunt@gmail.com>
Details
Message ID
<CAG7_9-AzZ-tnNpMitXrpA4C+Jvt3WhEGnosStwDQ3VEh0A609w@mail.gmail.com>
DKIM signature
missing
Download raw message
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 {
```
Details
Message ID
<CANtNKfphGutd6zL=RYhPkGmOC=mv0zNwAzLDDf-tROE9M+7qig@mail.gmail.com>
In-Reply-To
<CAG7_9-AzZ-tnNpMitXrpA4C+Jvt3WhEGnosStwDQ3VEh0A609w@mail.gmail.com> (view parent)
DKIM signature
missing
Download raw message
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 {
> ```
Details
Message ID
<26900d53-cef6-44a1-8417-4f8f8873083c@app.fastmail.com>
In-Reply-To
<CANtNKfphGutd6zL=RYhPkGmOC=mv0zNwAzLDDf-tROE9M+7qig@mail.gmail.com> (view parent)
DKIM signature
missing
Download raw message
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 {
>> ```
Details
Message ID
<CAMAFT9W7r6tS+YiPXs+d11soyDbCZtfkk6qJ+bTF90WgmJKFqg@mail.gmail.com>
In-Reply-To
<CANtNKfphGutd6zL=RYhPkGmOC=mv0zNwAzLDDf-tROE9M+7qig@mail.gmail.com> (view parent)
DKIM signature
missing
Download raw message
Hi Jack,

See also https://github.com/gioui/gio/pull/117 which is implementing
singleton semantics for
file opening on Windows, based on WM_COPYDATA.

Just like Egon, I'd like to know what makes WM_COPYDATA IPC superior
to other methods,
in particular methods that are independent of the GUI implementation.

Elias

On Thu, 30 Nov 2023 at 02:59, Egon Elbre <egonelbre@gmail.com> 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 {
> > ```
Jack Mordaunt <jackmordaunt@gmail.com>
Details
Message ID
<CAG7_9-C5WuiLoPJ75U-97kUXNkUyFzbPHo1XDeL-cEo+H==YTA@mail.gmail.com>
In-Reply-To
<CAMAFT9W7r6tS+YiPXs+d11soyDbCZtfkk6qJ+bTF90WgmJKFqg@mail.gmail.com> (view parent)
DKIM signature
missing
Download raw message
Thanks everyone for the tips.

On second thought, I'll explore the use of pipes for IPC as suggested.

I can't think of a convincing argument to pursue the WM_COPYDATA
approach instead of named pipes.

Thanks again!

On Thu, Nov 30, 2023 at 10:24 PM Elias Naur <mail@eliasnaur.com> wrote:
>
> Hi Jack,
>
> See also https://github.com/gioui/gio/pull/117 which is implementing
> singleton semantics for
> file opening on Windows, based on WM_COPYDATA.
>
> Just like Egon, I'd like to know what makes WM_COPYDATA IPC superior
> to other methods,
> in particular methods that are independent of the GUI implementation.
>
> Elias
>
> On Thu, 30 Nov 2023 at 02:59, Egon Elbre <egonelbre@gmail.com> 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 {
> > > ```
Reply to thread Export thread (mbox)