1
0
Files
nex/backend/cmd/desktop/dialog_windows.go

134 lines
3.4 KiB
Go

//go:build windows
package main
import (
"encoding/base64"
"errors"
"fmt"
"syscall"
"unicode/utf16"
"unsafe"
)
const (
mbOK = 0x00000000
mbIconError = 0x10
mbIconInformation = 0x40
mbTaskModal = 0x00002000
mbSetForeground = 0x00010000
mbTopMost = 0x00040000
)
var (
user32 = syscall.NewLazyDLL("user32.dll")
procMessageBoxW = user32.NewProc("MessageBoxW")
callMessageBoxW = func(hwnd, text, caption, flags uintptr) (uintptr, error) {
ret, _, err := procMessageBoxW.Call(hwnd, text, caption, flags)
return ret, err
}
)
func platformStartupChannels(runner commandRunner) []promptChannel {
return []promptChannel{
{
name: "windows-toast",
available: func() error {
_, err := findPowerShell(runner)
return err
},
run: func(req promptRequest) error {
name, err := findPowerShell(runner)
if err != nil {
return err
}
return runner.Run(promptCommandTimeout, []string{
"NEX_TOAST_TITLE=" + req.title,
"NEX_TOAST_BODY=" + req.message,
}, name, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encodePowerShellCommand(windowsToastScript()))
},
},
{
name: "windows-messagebox",
available: func() error {
return messageBoxAvailable()
},
run: func(req promptRequest) error {
return messageBox(req.title, req.message, messageBoxStartupFlags())
},
},
}
}
func findPowerShell(runner commandRunner) (string, error) {
for _, name := range []string{"powershell.exe", "powershell"} {
if _, err := runner.LookPath(name); err == nil {
return name, nil
}
}
return "", fmt.Errorf("PowerShell 不可用")
}
func windowsToastScript() string {
return `$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Runtime.WindowsRuntime
$template = [Windows.UI.Notifications.ToastTemplateType]::ToastText02
$xml = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent($template)
$texts = $xml.GetElementsByTagName('text')
$texts.Item(0).AppendChild($xml.CreateTextNode($env:NEX_TOAST_TITLE)) | Out-Null
$texts.Item(1).AppendChild($xml.CreateTextNode($env:NEX_TOAST_BODY)) | Out-Null
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Nex').Show($toast)`
}
func encodePowerShellCommand(script string) string {
encoded := utf16.Encode([]rune(script))
buf := make([]byte, 0, len(encoded)*2)
for _, value := range encoded {
buf = append(buf, byte(value), byte(value>>8))
}
return base64.StdEncoding.EncodeToString(buf)
}
func messageBoxAvailable() error {
if _, err := syscall.UTF16PtrFromString("Nex"); err != nil {
return err
}
if _, err := syscall.UTF16PtrFromString("test"); err != nil {
return err
}
return procMessageBoxW.Find()
}
func messageBoxStartupFlags() uint {
return mbOK | mbIconError | mbTaskModal | mbSetForeground | mbTopMost
}
func messageBox(title, message string, flags uint) error {
titlePtr, err := syscall.UTF16PtrFromString(title)
if err != nil {
return err
}
messagePtr, err := syscall.UTF16PtrFromString(message)
if err != nil {
return err
}
ret, callErr := callMessageBoxW(
0,
uintptr(unsafe.Pointer(messagePtr)),
uintptr(unsafe.Pointer(titlePtr)),
uintptr(flags),
)
if ret != 0 {
return nil
}
if callErr != nil && !errors.Is(callErr, syscall.Errno(0)) {
return callErr
}
return fmt.Errorf("MessageBoxW 调用失败")
}