[client/uiwails] Windows cross-build support and notification actions

- Add build/windows/Taskfile.yml to cross-compile from Linux with
  mingw-w64 (CC=x86_64-w64-mingw32-gcc, CGO_ENABLED=1, -H=windowsgui).
- Rename xembed_tray.{c,h} to xembed_tray_linux.{c,h} so the Go
  toolchain only compiles these X11/GTK sources on Linux.
- Add sendShowWindowSignal on Windows: opens the named Global event
  and calls SetEvent so the already-running instance shows its window.
- Register a notification category with Open/Dismiss action buttons
  and wire a response handler. Do this inside the ApplicationStarted
  hook so it runs after the notifications service's Startup has
  initialized appName/appGUID on Windows; otherwise the category is
  saved under an empty registry path and SendNotificationWithActions
  silently falls back to a plain toast with no buttons.
- Bump github.com/wailsapp/wails/v3 to v3.0.0-alpha.78.
This commit is contained in:
Zoltán Papp
2026-04-22 16:53:40 +02:00
parent 1451cedf86
commit 9ae1ca1c8e
9 changed files with 129 additions and 58 deletions

View File

@@ -4,6 +4,7 @@ includes:
common: ./build/Taskfile.yml
linux: ./build/linux/Taskfile.yml
darwin: ./build/darwin/Taskfile.yml
windows: ./build/windows/Taskfile.yml
vars:
APP_NAME: "netbird-ui"

View File

@@ -0,0 +1,41 @@
version: '3'
includes:
common: ../Taskfile.yml
tasks:
build:
summary: Cross-compiles the application for Windows from Linux using mingw-w64
cmds:
- task: build:cross
vars:
DEV: '{{.DEV}}'
OUTPUT: '{{.OUTPUT}}'
build:cross:
summary: Cross-compiles for Windows with mingw-w64
internal: true
deps:
- task: common:build:frontend
vars:
DEV:
ref: .DEV
preconditions:
- sh: command -v {{.CC}}
msg: "{{.CC}} not found. Install with: sudo apt-get install gcc-mingw-w64-x86-64"
cmds:
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
vars:
BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l" -ldflags="-H=windowsgui"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H=windowsgui"{{end}}'
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}.exe'
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
CC: '{{.CC | default "x86_64-w64-mingw32-gcc"}}'
env:
GOOS: windows
GOARCH: amd64
CGO_ENABLED: 1
CC: '{{.CC}}'
run:
cmds:
- '{{.BIN_DIR}}/{{.APP_NAME}}.exe'

View File

@@ -124,13 +124,48 @@ func main() {
evtManager := event.NewManager(*daemonAddr, notify)
go evtManager.Start(ctx)
// TEST: fire a desktop notification shortly after startup so we can
// verify that the notification pipeline works end-to-end.
go func() {
time.Sleep(3 * time.Second)
log.Infof("--- trigger notification ---")
notify("NetBird Test", "If you see this, notifications are working!")
}()
// Response handler can be wired early — it's just a callback registration.
const testCategoryID = "netbird-test-actions"
notifSvc.OnNotificationResponse(func(result notifications.NotificationResult) {
if result.Error != nil {
log.Warnf("notification response error: %v", result.Error)
return
}
log.Infof("notification action: id=%q category=%q", result.Response.ActionIdentifier, result.Response.CategoryID)
if result.Response.ActionIdentifier == "open" {
window.Show()
}
})
// Category registration and the test notification must happen AFTER the
// notifications service has run its Startup (which initializes appName,
// appGUID, and the COM activator on Windows). ApplicationStarted fires
// after all services are started.
app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) {
if err := notifSvc.RegisterNotificationCategory(notifications.NotificationCategory{
ID: testCategoryID,
Actions: []notifications.NotificationAction{
{ID: "open", Title: "Open NetBird"},
{ID: "dismiss", Title: "Dismiss"},
},
}); err != nil {
log.Warnf("register notification category: %v", err)
return
}
go func() {
time.Sleep(3 * time.Second)
log.Infof("--- trigger notification ---")
if err := notifSvc.SendNotificationWithActions(notifications.NotificationOptions{
ID: "netbird-test",
Title: "NetBird Test (with buttons)",
Body: "ACTIONS TEST — you should see Open/Dismiss buttons below this text.",
CategoryID: testCategoryID,
}); err != nil {
log.Warnf("send notification with actions: %v", err)
}
}()
})
if err := app.Run(); err != nil {
log.Fatalf("app run: %v", err)

View File

@@ -49,6 +49,23 @@ func setupSignalHandler(ctx context.Context, window *application.WebviewWindow)
go waitForWindowsEvent(ctx, eventHandle, window)
}
// sendShowWindowSignal signals the already-running instance (identified by pid,
// unused on Windows since the event is named globally) to show its window.
func sendShowWindowSignal(_ int32) error {
eventNamePtr, err := windows.UTF16PtrFromString(fancyUITriggerEventName)
if err != nil {
return err
}
handle, err := windows.OpenEvent(desiredAccesses, false, eventNamePtr)
if err != nil {
return err
}
defer windows.CloseHandle(handle)
return windows.SetEvent(handle)
}
func waitForWindowsEvent(ctx context.Context, eventHandle windows.Handle, window *application.WebviewWindow) {
defer func() {
if err := windows.CloseHandle(eventHandle); err != nil {

View File

@@ -5,7 +5,7 @@ package main
/*
#cgo pkg-config: x11 gtk+-3.0
#cgo LDFLAGS: -lX11
#include "xembed_tray.h"
#include "xembed_tray_linux.h"
#include <X11/Xlib.h>
#include <stdlib.h>
*/

View File

@@ -1,4 +1,4 @@
#include "xembed_tray.h"
#include "xembed_tray_linux.h"
#include <X11/Xatom.h>
#include <X11/Xutil.h>