//go:build windows // Package dialog wraps the handful of Win32 message boxes ipswap needs. // // This is raw user32 rather than a GUI toolkit on purpose: the fast path must // stay native and dependency-free, and a confirmation prompt is the only // blocking UI the tray side has. package dialog import ( "syscall" "unsafe" ) var ( user32 = syscall.NewLazyDLL("user32.dll") messageBoxW = user32.NewProc("MessageBoxW") ) // MessageBoxW uType flags. const ( mbOK = 0x00000000 mbOKCancel = 0x00000001 mbYesNo = 0x00000004 mbIconError = 0x00000010 mbIconQuestion = 0x00000020 mbIconWarning = 0x00000030 mbIconInformation = 0x00000040 // The tray has no window to own the dialog, so without TOPMOST the box can // open behind whatever is focused and look like a hang. mbSetForeground = 0x00010000 mbTopMost = 0x00040000 ) // MessageBoxW return values. const ( idOK = 1 idYes = 6 ) func show(title, text string, flags uint32) int { t, err := syscall.UTF16PtrFromString(text) if err != nil { return 0 } c, err := syscall.UTF16PtrFromString(title) if err != nil { return 0 } ret, _, _ := messageBoxW.Call( 0, uintptr(unsafe.Pointer(t)), uintptr(unsafe.Pointer(c)), uintptr(flags|mbSetForeground|mbTopMost), ) return int(ret) } // Confirm asks a yes/no question and reports whether the user said yes. func Confirm(title, text string) bool { return show(title, text, mbYesNo|mbIconQuestion) == idYes } // Info shows a plain acknowledgement box. func Info(title, text string) { show(title, text, mbOK|mbIconInformation) } // Error shows a failure. The caller is expected to include the raw netsh // output — that text is the only diagnostic the user has on site. func Error(title, text string) { show(title, text, mbOK|mbIconError) } // ErrorWithRetryAsAdmin reports a failure and offers to relaunch elevated. // It reports whether the user accepted. func ErrorWithRetryAsAdmin(title, text string) bool { return show(title, text+"\n\nRelaunch ipswap as administrator and try again?", mbOKCancel|mbIconWarning) == idOK }