Timeouts, cancellation (macos, linux).
This commit is contained in:
parent
e211121451
commit
1fdf2f7d73
19 changed files with 132 additions and 57 deletions
12
cmd/zenity/build.sh
Executable file
12
cmd/zenity/build.sh
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
|
||||
GOOS=windows GOARCH=386 go build -ldflags="-s -w" &&
|
||||
zip -9 zenity_win32.zip zenity.exe
|
||||
|
||||
GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" &&
|
||||
zip -9 zenity_win64.zip zenity.exe
|
||||
|
||||
GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" &&
|
||||
zip -9 zenity_macos.zip zenity
|
||||
|
||||
go build
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"image/color"
|
||||
"os"
|
||||
|
@ -8,6 +9,7 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/zenity"
|
||||
"github.com/ncruces/zenity/internal/zenutil"
|
||||
|
@ -46,7 +48,6 @@ var (
|
|||
confirmCreate bool
|
||||
showHidden bool
|
||||
filename string
|
||||
separator string
|
||||
fileFilters FileFilters
|
||||
|
||||
// Color selection options
|
||||
|
@ -58,12 +59,25 @@ var (
|
|||
wslpath bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
prevUsage := flag.Usage
|
||||
flag.Usage = func() {
|
||||
prevUsage()
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
setupFlags()
|
||||
flag.Parse()
|
||||
validateFlags()
|
||||
opts := loadFlags()
|
||||
zenutil.Command = true
|
||||
if zenutil.Timeout > 0 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(zenutil.Timeout)*time.Second)
|
||||
opts = append(opts, zenity.Context(ctx))
|
||||
_ = cancel
|
||||
}
|
||||
|
||||
switch {
|
||||
case notification:
|
||||
|
@ -93,12 +107,10 @@ func main() {
|
|||
}
|
||||
|
||||
flag.Usage()
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
func setupFlags() {
|
||||
// Application Options
|
||||
|
||||
flag.BoolVar(¬ification, "notification", false, "Display notification")
|
||||
flag.BoolVar(&errorDlg, "error", false, "Display error dialog")
|
||||
flag.BoolVar(&infoDlg, "info", false, "Display info dialog")
|
||||
|
@ -108,12 +120,10 @@ func setupFlags() {
|
|||
flag.BoolVar(&colorSelectionDlg, "color-selection", false, "Display color selection dialog")
|
||||
|
||||
// General options
|
||||
|
||||
flag.StringVar(&title, "title", "", "Set the dialog title")
|
||||
flag.StringVar(&icon, "window-icon", "", "Set the window icon (error, info, question, warning)")
|
||||
|
||||
// Message options
|
||||
|
||||
flag.StringVar(&text, "text", "", "Set the dialog text")
|
||||
flag.StringVar(&icon, "icon-name", "", "Set the dialog icon (error, info, question, warning)")
|
||||
flag.StringVar(&okLabel, "ok-label", "", "Set the label of the OK button")
|
||||
|
@ -124,7 +134,6 @@ func setupFlags() {
|
|||
flag.BoolVar(&defaultCancel, "default-cancel", false, "Give Cancel button focus by default")
|
||||
|
||||
// File selection options
|
||||
|
||||
flag.BoolVar(&save, "save", false, "Activate save mode")
|
||||
flag.BoolVar(&multiple, "multiple", false, "Allow multiple files to be selected")
|
||||
flag.BoolVar(&directory, "directory", false, "Activate directory-only selection")
|
||||
|
@ -132,7 +141,6 @@ func setupFlags() {
|
|||
flag.BoolVar(&confirmCreate, "confirm-create", false, "Confirm file selection if filename does not yet exist (Windows only)")
|
||||
flag.BoolVar(&showHidden, "show-hidden", false, "Show hidden files (Windows and macOS only)")
|
||||
flag.StringVar(&filename, "filename", "", "Set the filename")
|
||||
flag.StringVar(&separator, "separator", "|", "Set output separator character")
|
||||
flag.Var(&fileFilters, "file-filter", "Set a filename filter (NAME | PATTERN1 PATTERN2 ...)")
|
||||
|
||||
// Color selection options
|
||||
|
@ -144,6 +152,10 @@ func setupFlags() {
|
|||
flag.BoolVar(&cygpath, "cygpath", false, "Use cygpath for path translation (Windows only)")
|
||||
flag.BoolVar(&wslpath, "wslpath", false, "Use wslpath for path translation (Windows only)")
|
||||
}
|
||||
|
||||
// Internal options
|
||||
flag.IntVar(&zenutil.Timeout, "timeout", 0, "Set dialog timeout in seconds")
|
||||
flag.StringVar(&zenutil.Separator, "separator", "|", "Set output separator character")
|
||||
}
|
||||
|
||||
func validateFlags() {
|
||||
|
@ -171,16 +183,15 @@ func validateFlags() {
|
|||
}
|
||||
if n != 1 {
|
||||
flag.Usage()
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
func loadFlags() []zenity.Option {
|
||||
var options []zenity.Option
|
||||
var opts []zenity.Option
|
||||
|
||||
// General options
|
||||
|
||||
options = append(options, zenity.Title(title))
|
||||
opts = append(opts, zenity.Title(title))
|
||||
|
||||
// Message options
|
||||
|
||||
|
@ -196,51 +207,49 @@ func loadFlags() []zenity.Option {
|
|||
ico = zenity.WarningIcon
|
||||
}
|
||||
|
||||
options = append(options, zenity.Icon(ico))
|
||||
options = append(options, zenity.OKLabel(okLabel))
|
||||
options = append(options, zenity.CancelLabel(cancelLabel))
|
||||
options = append(options, zenity.ExtraButton(extraButton))
|
||||
opts = append(opts, zenity.Icon(ico))
|
||||
opts = append(opts, zenity.OKLabel(okLabel))
|
||||
opts = append(opts, zenity.CancelLabel(cancelLabel))
|
||||
opts = append(opts, zenity.ExtraButton(extraButton))
|
||||
if noWrap {
|
||||
options = append(options, zenity.NoWrap())
|
||||
opts = append(opts, zenity.NoWrap())
|
||||
}
|
||||
if ellipsize {
|
||||
options = append(options, zenity.Ellipsize())
|
||||
opts = append(opts, zenity.Ellipsize())
|
||||
}
|
||||
if defaultCancel {
|
||||
options = append(options, zenity.DefaultCancel())
|
||||
opts = append(opts, zenity.DefaultCancel())
|
||||
}
|
||||
|
||||
// File selection options
|
||||
|
||||
options = append(options, fileFilters)
|
||||
opts = append(opts, fileFilters)
|
||||
if filename != "" {
|
||||
options = append(options, zenity.Filename(ingestPath(filename)))
|
||||
opts = append(opts, zenity.Filename(ingestPath(filename)))
|
||||
}
|
||||
if directory {
|
||||
options = append(options, zenity.Directory())
|
||||
opts = append(opts, zenity.Directory())
|
||||
}
|
||||
if confirmOverwrite {
|
||||
options = append(options, zenity.ConfirmOverwrite())
|
||||
opts = append(opts, zenity.ConfirmOverwrite())
|
||||
}
|
||||
if confirmCreate {
|
||||
options = append(options, zenity.ConfirmCreate())
|
||||
opts = append(opts, zenity.ConfirmCreate())
|
||||
}
|
||||
if showHidden {
|
||||
options = append(options, zenity.ShowHidden())
|
||||
opts = append(opts, zenity.ShowHidden())
|
||||
}
|
||||
|
||||
zenutil.Separator = separator
|
||||
|
||||
// Color selection options
|
||||
|
||||
if defaultColor != "" {
|
||||
options = append(options, zenity.Color(zenutil.ParseColor(defaultColor)))
|
||||
opts = append(opts, zenity.Color(zenutil.ParseColor(defaultColor)))
|
||||
}
|
||||
if showPalette {
|
||||
options = append(options, zenity.ShowPalette())
|
||||
opts = append(opts, zenity.ShowPalette())
|
||||
}
|
||||
|
||||
return options
|
||||
return opts
|
||||
}
|
||||
|
||||
func errResult(err error) {
|
||||
|
@ -289,7 +298,7 @@ func listResult(l []string, err error) {
|
|||
os.Stderr.WriteString(zenutil.LineBreak)
|
||||
os.Exit(-1)
|
||||
}
|
||||
os.Stdout.WriteString(strings.Join(l, separator))
|
||||
os.Stdout.WriteString(strings.Join(l, zenutil.Separator))
|
||||
os.Stdout.WriteString(zenutil.LineBreak)
|
||||
if l == nil {
|
||||
os.Exit(1)
|
||||
|
|
|
@ -16,7 +16,7 @@ func selectColor(options []Option) (color.Color, error) {
|
|||
data.Color = []uint16{n.R, n.G, n.B}
|
||||
}
|
||||
|
||||
out, err := zenutil.Run("color", data)
|
||||
out, err := zenutil.Run(opts.ctx, "color", data)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ func selectColor(options []Option) (color.Color, error) {
|
|||
args = append(args, "--show-palette")
|
||||
}
|
||||
|
||||
out, err := zenutil.Run(args)
|
||||
out, err := zenutil.Run(opts.ctx, args)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() != 255 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ func selectFile(options []Option) (string, error) {
|
|||
}
|
||||
data.Location, _ = splitDirAndName(opts.filename)
|
||||
|
||||
out, err := zenutil.Run("file", data)
|
||||
out, err := zenutil.Run(opts.ctx, "file", data)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
|
||||
return "", nil
|
||||
}
|
||||
|
@ -41,8 +41,8 @@ func selectFileMutiple(options []Option) ([]string, error) {
|
|||
data := zenutil.File{
|
||||
Prompt: opts.title,
|
||||
Invisibles: opts.showHidden,
|
||||
Multiple: true,
|
||||
Separator: zenutil.Separator,
|
||||
Multiple: true,
|
||||
}
|
||||
if opts.directory {
|
||||
data.Operation = "chooseFolder"
|
||||
|
@ -52,7 +52,7 @@ func selectFileMutiple(options []Option) ([]string, error) {
|
|||
}
|
||||
data.Location, _ = splitDirAndName(opts.filename)
|
||||
|
||||
out, err := zenutil.Run("file", data)
|
||||
out, err := zenutil.Run(opts.ctx, "file", data)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ func selectFileSave(options []Option) (string, error) {
|
|||
}
|
||||
data.Location, data.Name = splitDirAndName(opts.filename)
|
||||
|
||||
out, err := zenutil.Run("file", data)
|
||||
out, err := zenutil.Run(opts.ctx, "file", data)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ func selectFile(options []Option) (string, error) {
|
|||
}
|
||||
args = append(args, initFilters(opts.fileFilters)...)
|
||||
|
||||
out, err := zenutil.Run(args)
|
||||
out, err := zenutil.Run(opts.ctx, args)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() != 255 {
|
||||
return "", nil
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ func selectFileMutiple(options []Option) ([]string, error) {
|
|||
}
|
||||
args = append(args, initFilters(opts.fileFilters)...)
|
||||
|
||||
out, err := zenutil.Run(args)
|
||||
out, err := zenutil.Run(opts.ctx, args)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() != 255 {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ func selectFileSave(options []Option) (string, error) {
|
|||
}
|
||||
args = append(args, initFilters(opts.fileFilters)...)
|
||||
|
||||
out, err := zenutil.Run(args)
|
||||
out, err := zenutil.Run(opts.ctx, args)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() != 255 {
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -3,4 +3,5 @@ package zenutil
|
|||
const LineBreak = "\n"
|
||||
|
||||
var Command bool
|
||||
var Timeout int
|
||||
var Separator = "\x00"
|
||||
|
|
|
@ -6,4 +6,5 @@ package zenutil
|
|||
const LineBreak = "\n"
|
||||
|
||||
var Command bool
|
||||
var Timeout int
|
||||
var Separator = "\x1e"
|
||||
|
|
|
@ -3,4 +3,5 @@ package zenutil
|
|||
const LineBreak = "\r\n"
|
||||
|
||||
var Command bool
|
||||
var Timeout int
|
||||
var Separator string
|
||||
|
|
|
@ -68,14 +68,25 @@ opts.withIcon = {{json .Icon}}
|
|||
{{if .Buttons -}}
|
||||
opts.buttons = {{json .Buttons}}
|
||||
{{end -}}
|
||||
{{if .Default -}}
|
||||
opts.defaultButton = {{json .Default}}
|
||||
{{end -}}
|
||||
{{if .Cancel -}}
|
||||
opts.cancelButton = {{json .Cancel}}
|
||||
{{end -}}
|
||||
var res = app[{{json .Operation}}]({{json .Text}}, opts).buttonReturned
|
||||
res === {{json .Extra}} ? res : void 0
|
||||
{{if .Default -}}
|
||||
opts.defaultButton = {{json .Default}}
|
||||
{{end -}}
|
||||
{{if .Timeout -}}
|
||||
opts.givingUpAfter = {{json .Timeout}}
|
||||
{{end -}}
|
||||
var res = app[{{json .Operation}}]({{json .Text}}, opts)
|
||||
if (res.gaveUp) {
|
||||
ObjC.import("stdlib")
|
||||
$.exit(5)
|
||||
}
|
||||
if (res.buttonReturned === {{json .Extra}}) {
|
||||
res
|
||||
} else {
|
||||
void 0
|
||||
}
|
||||
{{- end}}
|
||||
{{define "notify" -}}var app = Application.currentApplication()
|
||||
app.includeStandardAdditions = true
|
||||
|
|
|
@ -19,12 +19,23 @@ var opts = {}
|
|||
{{if .Buttons -}}
|
||||
opts.buttons = {{json .Buttons}}
|
||||
{{end -}}
|
||||
{{if .Default -}}
|
||||
opts.defaultButton = {{json .Default}}
|
||||
{{end -}}
|
||||
{{if .Cancel -}}
|
||||
opts.cancelButton = {{json .Cancel}}
|
||||
{{end -}}
|
||||
{{if .Default -}}
|
||||
opts.defaultButton = {{json .Default}}
|
||||
{{end -}}
|
||||
{{if .Timeout -}}
|
||||
opts.givingUpAfter = {{json .Timeout}}
|
||||
{{end -}}
|
||||
|
||||
var res = app[{{json .Operation}}]({{json .Text}}, opts).buttonReturned
|
||||
res === {{json .Extra}} ? res : void 0
|
||||
var res = app[{{json .Operation}}]({{json .Text}}, opts)
|
||||
if (res.gaveUp) {
|
||||
ObjC.import("stdlib")
|
||||
$.exit(5)
|
||||
}
|
||||
if (res.buttonReturned === {{json .Extra}}) {
|
||||
res
|
||||
} else {
|
||||
void 0
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
package zenutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Run(script string, data interface{}) ([]byte, error) {
|
||||
func Run(ctx context.Context, script string, data interface{}) ([]byte, error) {
|
||||
var buf strings.Builder
|
||||
|
||||
err := scripts.ExecuteTemplate(&buf, script, data)
|
||||
|
@ -29,6 +30,11 @@ func Run(script string, data interface{}) ([]byte, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if ctx != nil {
|
||||
cmd := exec.CommandContext(ctx, "osascript", "-l", lang)
|
||||
cmd.Stdin = strings.NewReader(script)
|
||||
return cmd.Output()
|
||||
}
|
||||
cmd := exec.Command("osascript", "-l", lang)
|
||||
cmd.Stdin = strings.NewReader(script)
|
||||
return cmd.Output()
|
||||
|
@ -60,6 +66,7 @@ type Msg struct {
|
|||
Buttons []string
|
||||
Cancel int
|
||||
Default int
|
||||
Timeout int
|
||||
}
|
||||
|
||||
type Notify struct {
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
package zenutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
|
@ -20,9 +22,16 @@ func init() {
|
|||
tool = "zenity"
|
||||
}
|
||||
|
||||
func Run(args []string) ([]byte, error) {
|
||||
func Run(ctx context.Context, args []string) ([]byte, error) {
|
||||
if Command && path != "" {
|
||||
if Timeout > 0 {
|
||||
args = append(args, "--timeout", strconv.Itoa(Timeout))
|
||||
}
|
||||
syscall.Exec(path, append([]string{tool}, args...), os.Environ())
|
||||
}
|
||||
|
||||
if ctx != nil {
|
||||
return exec.CommandContext(ctx, tool, args...).Output()
|
||||
}
|
||||
return exec.Command(tool, args...).Output()
|
||||
}
|
||||
|
|
2
msg.go
2
msg.go
|
@ -2,7 +2,7 @@ package zenity
|
|||
|
||||
// ErrExtraButton is returned by dialog functions when the extra button is
|
||||
// pressed.
|
||||
const ErrExtraButton = constError("Extra button pressed.")
|
||||
const ErrExtraButton = constError("Extra button pressed")
|
||||
|
||||
// Question displays the question dialog.
|
||||
//
|
||||
|
|
|
@ -8,7 +8,10 @@ import (
|
|||
|
||||
func message(kind messageKind, text string, options []Option) (bool, error) {
|
||||
opts := applyOptions(options)
|
||||
data := zenutil.Msg{Text: text}
|
||||
data := zenutil.Msg{
|
||||
Text: text,
|
||||
Timeout: zenutil.Timeout,
|
||||
}
|
||||
dialog := kind == questionKind || opts.icon != 0
|
||||
|
||||
if dialog {
|
||||
|
@ -83,7 +86,7 @@ func message(kind messageKind, text string, options []Option) (bool, error) {
|
|||
}
|
||||
}
|
||||
|
||||
out, err := zenutil.Run("msg", data)
|
||||
out, err := zenutil.Run(opts.ctx, "msg", data)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ func message(kind messageKind, text string, options []Option) (bool, error) {
|
|||
args = append(args, "--window-icon=question", "--icon-name=dialog-question")
|
||||
}
|
||||
|
||||
out, err := zenutil.Run(args)
|
||||
out, err := zenutil.Run(opts.ctx, args)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() != 255 {
|
||||
if len(out) > 0 && string(out[:len(out)-1]) == opts.extraButton {
|
||||
return false, ErrExtraButton
|
||||
|
|
|
@ -16,7 +16,7 @@ func notify(text string, options []Option) error {
|
|||
data.Subtitle = text[:i]
|
||||
data.Text = text[i+1:]
|
||||
}
|
||||
_, err := zenutil.Run("notify", data)
|
||||
_, err := zenutil.Run(opts.ctx, "notify", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ func notify(text string, options []Option) error {
|
|||
args = append(args, "--window-icon=question")
|
||||
}
|
||||
|
||||
_, err := zenutil.Run(args)
|
||||
_, err := zenutil.Run(opts.ctx, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
12
zenity.go
12
zenity.go
|
@ -10,7 +10,10 @@
|
|||
// initialization requirements.
|
||||
package zenity
|
||||
|
||||
import "image/color"
|
||||
import (
|
||||
"context"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
type constError string
|
||||
|
||||
|
@ -40,6 +43,9 @@ type options struct {
|
|||
noWrap bool
|
||||
ellipsize bool
|
||||
defaultCancel bool
|
||||
|
||||
// Context for timeout
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// An Option is an argument passed to dialog functions to customize their
|
||||
|
@ -79,3 +85,7 @@ const (
|
|||
func Icon(icon DialogIcon) Option {
|
||||
return funcOption(func(o *options) { o.icon = icon })
|
||||
}
|
||||
|
||||
func Context(ctx context.Context) Option {
|
||||
return funcOption(func(o *options) { o.ctx = ctx })
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue