//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 调用失败") }