184
platform_windows.go
Normal file
184
platform_windows.go
Normal file
@@ -0,0 +1,184 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
const nativeDialogs = true
|
||||
|
||||
const createNoWindow = 0x08000000
|
||||
|
||||
// hideConsole stops child console programs (ffmpeg, powershell) from opening
|
||||
// a console window. HideWindow is deliberately not set: Windows would apply
|
||||
// SW_HIDE to the first window the child shows, i.e. the file dialog.
|
||||
func hideConsole(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: createNoWindow}
|
||||
}
|
||||
|
||||
func listRoots() []string {
|
||||
mask, _, _ := syscall.NewLazyDLL("kernel32.dll").NewProc("GetLogicalDrives").Call()
|
||||
var roots []string
|
||||
for i := 0; i < 26; i++ {
|
||||
if mask&(1<<uint(i)) != 0 {
|
||||
roots = append(roots, string(rune('A'+i))+`:\`)
|
||||
}
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
// openBrowser prefers a chromeless Edge (or Chrome) "app" window, which feels
|
||||
// like a normal desktop program, and falls back to the default browser.
|
||||
func openBrowser(url string, preferDefault bool) error {
|
||||
if !preferDefault {
|
||||
var candidates []string
|
||||
for _, env := range []string{"ProgramFiles(x86)", "ProgramFiles", "LocalAppData"} {
|
||||
base := os.Getenv(env)
|
||||
if base == "" {
|
||||
continue
|
||||
}
|
||||
candidates = append(candidates,
|
||||
filepath.Join(base, `Microsoft\Edge\Application\msedge.exe`),
|
||||
filepath.Join(base, `Google\Chrome\Application\chrome.exe`))
|
||||
}
|
||||
for _, p := range candidates {
|
||||
if _, err := os.Stat(p); err != nil {
|
||||
continue
|
||||
}
|
||||
cmd := exec.Command(p, "--app="+url, "--window-size=1440,920")
|
||||
if err := cmd.Start(); err == nil {
|
||||
go cmd.Wait()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||
}
|
||||
|
||||
func revealInFileManager(path string) error {
|
||||
cmd := exec.Command("explorer.exe")
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: `explorer.exe /select,"` + path + `"`}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
go cmd.Wait() // explorer exits with status 1 even on success
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitBeforeExit() {
|
||||
fmt.Fprint(os.Stderr, "\nPress Enter to close...")
|
||||
bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
}
|
||||
|
||||
// Native file dialogs are shown through PowerShell + Windows Forms so the
|
||||
// program needs no cgo. Values are passed via environment variables, never
|
||||
// spliced into the script.
|
||||
const psDialogPrelude = `
|
||||
$ErrorActionPreference = 'Stop'
|
||||
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding $false
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
$owner = New-Object System.Windows.Forms.Form
|
||||
$owner.TopMost = $true
|
||||
$owner.ShowInTaskbar = $false
|
||||
`
|
||||
|
||||
const psOpenDialog = psDialogPrelude + `
|
||||
$d = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$d.Title = $env:VIBEDIT_TITLE
|
||||
$d.Multiselect = $true
|
||||
$d.Filter = $env:VIBEDIT_FILTER
|
||||
if ($env:VIBEDIT_DIR -and (Test-Path -LiteralPath $env:VIBEDIT_DIR -PathType Container)) { $d.InitialDirectory = $env:VIBEDIT_DIR }
|
||||
if ($d.ShowDialog($owner) -eq [System.Windows.Forms.DialogResult]::OK) {
|
||||
foreach ($f in $d.FileNames) { [Console]::Out.WriteLine($f) }
|
||||
}
|
||||
$owner.Dispose()
|
||||
`
|
||||
|
||||
const psSaveDialog = psDialogPrelude + `
|
||||
$d = New-Object System.Windows.Forms.SaveFileDialog
|
||||
$d.Title = $env:VIBEDIT_TITLE
|
||||
$d.Filter = $env:VIBEDIT_FILTER
|
||||
$d.OverwritePrompt = $false
|
||||
$d.AddExtension = $true
|
||||
$d.DefaultExt = $env:VIBEDIT_EXT
|
||||
$d.FileName = $env:VIBEDIT_NAME
|
||||
if ($env:VIBEDIT_DIR -and (Test-Path -LiteralPath $env:VIBEDIT_DIR -PathType Container)) { $d.InitialDirectory = $env:VIBEDIT_DIR }
|
||||
if ($d.ShowDialog($owner) -eq [System.Windows.Forms.DialogResult]::OK) { [Console]::Out.WriteLine($d.FileName) }
|
||||
$owner.Dispose()
|
||||
`
|
||||
|
||||
func runPowerShell(script string, env map[string]string) ([]string, error) {
|
||||
u := utf16.Encode([]rune(script))
|
||||
b := make([]byte, len(u)*2)
|
||||
for i, c := range u {
|
||||
binary.LittleEndian.PutUint16(b[i*2:], c)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Hour)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, "powershell.exe", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-STA",
|
||||
"-EncodedCommand", base64.StdEncoding.EncodeToString(b))
|
||||
cmd.Env = os.Environ()
|
||||
for k, v := range env {
|
||||
cmd.Env = append(cmd.Env, k+"="+v)
|
||||
}
|
||||
hideConsole(cmd)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var lines []string
|
||||
for _, l := range strings.Split(strings.TrimPrefix(string(out), "\uFEFF"), "\n") {
|
||||
if l = strings.TrimSpace(l); l != "" {
|
||||
lines = append(lines, l)
|
||||
}
|
||||
}
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func videoFilter() string {
|
||||
var pats []string
|
||||
for _, e := range mediaExts {
|
||||
pats = append(pats, "*."+e)
|
||||
}
|
||||
return "Video files|" + strings.Join(pats, ";") + "|All files|*.*"
|
||||
}
|
||||
|
||||
func nativeOpenDialog(title, dir string) ([]string, error) {
|
||||
return runPowerShell(psOpenDialog, map[string]string{
|
||||
"VIBEDIT_TITLE": title,
|
||||
"VIBEDIT_FILTER": videoFilter(),
|
||||
"VIBEDIT_DIR": dir,
|
||||
})
|
||||
}
|
||||
|
||||
func nativeSaveDialog(title, path string) (string, error) {
|
||||
ext := strings.TrimPrefix(filepath.Ext(path), ".")
|
||||
filter := "All files|*.*"
|
||||
if ext != "" {
|
||||
filter = strings.ToUpper(ext) + " video|*." + ext + "|" + filter
|
||||
}
|
||||
lines, err := runPowerShell(psSaveDialog, map[string]string{
|
||||
"VIBEDIT_TITLE": title,
|
||||
"VIBEDIT_FILTER": filter,
|
||||
"VIBEDIT_EXT": ext,
|
||||
"VIBEDIT_NAME": filepath.Base(path),
|
||||
"VIBEDIT_DIR": filepath.Dir(path),
|
||||
})
|
||||
if err != nil || len(lines) == 0 {
|
||||
return "", err
|
||||
}
|
||||
return lines[0], nil
|
||||
}
|
||||
Reference in New Issue
Block a user