134 lines
3.4 KiB
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 调用失败")
|
|
}
|