mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 07:16:38 +00:00
* Open quick settings window if netbird-ui is already running * [client-ui] fix connection status comparison * [client-ui] modularize quick actions code * [client-ui] add netbird-disconnected logo * [client-ui] change quickactions UI It now displays the NetBird logo and a single button with a round icon * [client-ui] add hint message to quick actions screen This also updates fyne to v2.7.0 * [client-ui] remove unnecessary default clause * [client-ui] remove commented code * [client-ui] remove unused dependency * [client-ui] close quick actions on connection change * [client-ui] add function to get image from embed resources * [client] Return error when calling sendShowWindowSignal from Windows * [client-ui] Add commentary on empty OnTapped function for toggleConnectionButton * [client-ui] Fix tests * [client-ui] Add context to menuUpClick call * [client-ui] Pass serviceClient app as parameter To use its clipboard rather than the window's when showing the upload success dialog * [client-ui] Replace for select with for range chan * [client-ui] Replace settings change listener channel Settings now accept a function callback * [client-ui] Add missing iconAboutDisconnected to icons_windows.go * [client] Add quick actions signal handler for Windows with named events * [client] Run go mod tidy * [client] Remove line break * [client] Log unexpected status in separate function * [client-ui] Refactor quick actions window To address racing conditions, it also replaces usage of pause and resume channels with an atomic bool. * [client-ui] use derived context from ServiceClient * [client] Update signal_windows log message Also, format error when trying to set event on sendShowWindowSignal * go mod tidy * [client-ui] Add struct to pass fewer parameters to applyQuickActionsUiState function * [client] Add missing import --------- Co-authored-by: Viktor Liu <viktor@netbird.io>
172 lines
4.5 KiB
Go
172 lines
4.5 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
const (
|
|
quickActionsTriggerEventName = `Global\NetBirdQuickActionsTriggerEvent`
|
|
waitTimeout = 5 * time.Second
|
|
// SYNCHRONIZE is needed for WaitForSingleObject, EVENT_MODIFY_STATE for ResetEvent.
|
|
desiredAccesses = windows.SYNCHRONIZE | windows.EVENT_MODIFY_STATE
|
|
)
|
|
|
|
func getEventNameUint16Pointer() (*uint16, error) {
|
|
eventNamePtr, err := windows.UTF16PtrFromString(quickActionsTriggerEventName)
|
|
if err != nil {
|
|
log.Errorf("Failed to convert event name '%s' to UTF16: %v", quickActionsTriggerEventName, err)
|
|
return nil, err
|
|
}
|
|
|
|
return eventNamePtr, nil
|
|
}
|
|
|
|
// setupSignalHandler sets up signal handling for Windows.
|
|
// Windows doesn't support SIGUSR1, so this uses a similar approach using windows.Events.
|
|
func (s *serviceClient) setupSignalHandler(ctx context.Context) {
|
|
eventNamePtr, err := getEventNameUint16Pointer()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
eventHandle, err := windows.CreateEvent(nil, 1, 0, eventNamePtr)
|
|
|
|
if err != nil {
|
|
if errors.Is(err, windows.ERROR_ALREADY_EXISTS) {
|
|
log.Warnf("Quick actions trigger event '%s' already exists. Attempting to open.", quickActionsTriggerEventName)
|
|
eventHandle, err = windows.OpenEvent(desiredAccesses, false, eventNamePtr)
|
|
if err != nil {
|
|
log.Errorf("Failed to open existing quick actions trigger event '%s': %v", quickActionsTriggerEventName, err)
|
|
return
|
|
}
|
|
log.Infof("Successfully opened existing quick actions trigger event '%s'.", quickActionsTriggerEventName)
|
|
} else {
|
|
log.Errorf("Failed to create quick actions trigger event '%s': %v", quickActionsTriggerEventName, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
if eventHandle == windows.InvalidHandle {
|
|
log.Errorf("Obtained an invalid handle for quick actions trigger event '%s'", quickActionsTriggerEventName)
|
|
return
|
|
}
|
|
|
|
log.Infof("Quick actions handler waiting for signal on event: %s", quickActionsTriggerEventName)
|
|
|
|
go s.waitForEvent(ctx, eventHandle)
|
|
}
|
|
|
|
func (s *serviceClient) waitForEvent(ctx context.Context, eventHandle windows.Handle) {
|
|
defer func() {
|
|
if err := windows.CloseHandle(eventHandle); err != nil {
|
|
log.Errorf("Failed to close quick actions event handle '%s': %v", quickActionsTriggerEventName, err)
|
|
}
|
|
}()
|
|
|
|
for {
|
|
if ctx.Err() != nil {
|
|
return
|
|
}
|
|
|
|
status, err := windows.WaitForSingleObject(eventHandle, uint32(waitTimeout.Milliseconds()))
|
|
|
|
switch status {
|
|
case windows.WAIT_OBJECT_0:
|
|
log.Info("Received signal on quick actions event. Opening quick actions window.")
|
|
|
|
// reset the event so it can be triggered again later (manual reset == 1)
|
|
if err := windows.ResetEvent(eventHandle); err != nil {
|
|
log.Errorf("Failed to reset quick actions event '%s': %v", quickActionsTriggerEventName, err)
|
|
}
|
|
|
|
s.openQuickActions()
|
|
case uint32(windows.WAIT_TIMEOUT):
|
|
|
|
default:
|
|
if isDone := logUnexpectedStatus(ctx, status, err); isDone {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func logUnexpectedStatus(ctx context.Context, status uint32, err error) bool {
|
|
log.Errorf("Unexpected status %d from WaitForSingleObject for quick actions event '%s': %v",
|
|
status, quickActionsTriggerEventName, err)
|
|
select {
|
|
case <-time.After(5 * time.Second):
|
|
return false
|
|
case <-ctx.Done():
|
|
return true
|
|
}
|
|
}
|
|
|
|
// openQuickActions opens the quick actions window by spawning a new process.
|
|
func (s *serviceClient) openQuickActions() {
|
|
proc, err := os.Executable()
|
|
if err != nil {
|
|
log.Errorf("get executable path: %v", err)
|
|
return
|
|
}
|
|
|
|
cmd := exec.CommandContext(s.ctx, proc,
|
|
"--quick-actions=true",
|
|
"--daemon-addr="+s.addr,
|
|
)
|
|
|
|
if out := s.attachOutput(cmd); out != nil {
|
|
defer func() {
|
|
if err := out.Close(); err != nil {
|
|
log.Errorf("close log file %s: %v", s.logFile, err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
log.Infof("running command: %s --quick-actions=true --daemon-addr=%s", proc, s.addr)
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
log.Errorf("error starting quick actions window: %v", err)
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
if err := cmd.Wait(); err != nil {
|
|
log.Debugf("quick actions window exited: %v", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func sendShowWindowSignal(pid int32) error {
|
|
_, err := os.FindProcess(int(pid))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
eventNamePtr, err := getEventNameUint16Pointer()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
eventHandle, err := windows.OpenEvent(desiredAccesses, false, eventNamePtr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = windows.SetEvent(eventHandle)
|
|
if err != nil {
|
|
return fmt.Errorf("Error setting event: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|