Timeouts, cancellation (macos, linux).

This commit is contained in:
Nuno Cruces 2020-01-28 12:46:43 +00:00
parent e211121451
commit 1fdf2f7d73
19 changed files with 132 additions and 57 deletions

12
cmd/zenity/build.sh Executable file
View 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

View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"context"
"flag" "flag"
"image/color" "image/color"
"os" "os"
@ -8,6 +9,7 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"time"
"github.com/ncruces/zenity" "github.com/ncruces/zenity"
"github.com/ncruces/zenity/internal/zenutil" "github.com/ncruces/zenity/internal/zenutil"
@ -46,7 +48,6 @@ var (
confirmCreate bool confirmCreate bool
showHidden bool showHidden bool
filename string filename string
separator string
fileFilters FileFilters fileFilters FileFilters
// Color selection options // Color selection options
@ -58,12 +59,25 @@ var (
wslpath bool wslpath bool
) )
func init() {
prevUsage := flag.Usage
flag.Usage = func() {
prevUsage()
os.Exit(-1)
}
}
func main() { func main() {
setupFlags() setupFlags()
flag.Parse() flag.Parse()
validateFlags() validateFlags()
opts := loadFlags() opts := loadFlags()
zenutil.Command = true 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 { switch {
case notification: case notification:
@ -93,12 +107,10 @@ func main() {
} }
flag.Usage() flag.Usage()
os.Exit(-1)
} }
func setupFlags() { func setupFlags() {
// Application Options // Application Options
flag.BoolVar(&notification, "notification", false, "Display notification") flag.BoolVar(&notification, "notification", false, "Display notification")
flag.BoolVar(&errorDlg, "error", false, "Display error dialog") flag.BoolVar(&errorDlg, "error", false, "Display error dialog")
flag.BoolVar(&infoDlg, "info", false, "Display info 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") flag.BoolVar(&colorSelectionDlg, "color-selection", false, "Display color selection dialog")
// General options // General options
flag.StringVar(&title, "title", "", "Set the dialog title") flag.StringVar(&title, "title", "", "Set the dialog title")
flag.StringVar(&icon, "window-icon", "", "Set the window icon (error, info, question, warning)") flag.StringVar(&icon, "window-icon", "", "Set the window icon (error, info, question, warning)")
// Message options // Message options
flag.StringVar(&text, "text", "", "Set the dialog text") flag.StringVar(&text, "text", "", "Set the dialog text")
flag.StringVar(&icon, "icon-name", "", "Set the dialog icon (error, info, question, warning)") 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") 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") flag.BoolVar(&defaultCancel, "default-cancel", false, "Give Cancel button focus by default")
// File selection options // File selection options
flag.BoolVar(&save, "save", false, "Activate save mode") flag.BoolVar(&save, "save", false, "Activate save mode")
flag.BoolVar(&multiple, "multiple", false, "Allow multiple files to be selected") flag.BoolVar(&multiple, "multiple", false, "Allow multiple files to be selected")
flag.BoolVar(&directory, "directory", false, "Activate directory-only selection") 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(&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.BoolVar(&showHidden, "show-hidden", false, "Show hidden files (Windows and macOS only)")
flag.StringVar(&filename, "filename", "", "Set the filename") 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 ...)") flag.Var(&fileFilters, "file-filter", "Set a filename filter (NAME | PATTERN1 PATTERN2 ...)")
// Color selection options // Color selection options
@ -144,6 +152,10 @@ func setupFlags() {
flag.BoolVar(&cygpath, "cygpath", false, "Use cygpath for path translation (Windows only)") flag.BoolVar(&cygpath, "cygpath", false, "Use cygpath for path translation (Windows only)")
flag.BoolVar(&wslpath, "wslpath", false, "Use wslpath 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() { func validateFlags() {
@ -171,16 +183,15 @@ func validateFlags() {
} }
if n != 1 { if n != 1 {
flag.Usage() flag.Usage()
os.Exit(-1)
} }
} }
func loadFlags() []zenity.Option { func loadFlags() []zenity.Option {
var options []zenity.Option var opts []zenity.Option
// General options // General options
options = append(options, zenity.Title(title)) opts = append(opts, zenity.Title(title))
// Message options // Message options
@ -196,51 +207,49 @@ func loadFlags() []zenity.Option {
ico = zenity.WarningIcon ico = zenity.WarningIcon
} }
options = append(options, zenity.Icon(ico)) opts = append(opts, zenity.Icon(ico))
options = append(options, zenity.OKLabel(okLabel)) opts = append(opts, zenity.OKLabel(okLabel))
options = append(options, zenity.CancelLabel(cancelLabel)) opts = append(opts, zenity.CancelLabel(cancelLabel))
options = append(options, zenity.ExtraButton(extraButton)) opts = append(opts, zenity.ExtraButton(extraButton))
if noWrap { if noWrap {
options = append(options, zenity.NoWrap()) opts = append(opts, zenity.NoWrap())
} }
if ellipsize { if ellipsize {
options = append(options, zenity.Ellipsize()) opts = append(opts, zenity.Ellipsize())
} }
if defaultCancel { if defaultCancel {
options = append(options, zenity.DefaultCancel()) opts = append(opts, zenity.DefaultCancel())
} }
// File selection options // File selection options
options = append(options, fileFilters) opts = append(opts, fileFilters)
if filename != "" { if filename != "" {
options = append(options, zenity.Filename(ingestPath(filename))) opts = append(opts, zenity.Filename(ingestPath(filename)))
} }
if directory { if directory {
options = append(options, zenity.Directory()) opts = append(opts, zenity.Directory())
} }
if confirmOverwrite { if confirmOverwrite {
options = append(options, zenity.ConfirmOverwrite()) opts = append(opts, zenity.ConfirmOverwrite())
} }
if confirmCreate { if confirmCreate {
options = append(options, zenity.ConfirmCreate()) opts = append(opts, zenity.ConfirmCreate())
} }
if showHidden { if showHidden {
options = append(options, zenity.ShowHidden()) opts = append(opts, zenity.ShowHidden())
} }
zenutil.Separator = separator
// Color selection options // Color selection options
if defaultColor != "" { if defaultColor != "" {
options = append(options, zenity.Color(zenutil.ParseColor(defaultColor))) opts = append(opts, zenity.Color(zenutil.ParseColor(defaultColor)))
} }
if showPalette { if showPalette {
options = append(options, zenity.ShowPalette()) opts = append(opts, zenity.ShowPalette())
} }
return options return opts
} }
func errResult(err error) { func errResult(err error) {
@ -289,7 +298,7 @@ func listResult(l []string, err error) {
os.Stderr.WriteString(zenutil.LineBreak) os.Stderr.WriteString(zenutil.LineBreak)
os.Exit(-1) os.Exit(-1)
} }
os.Stdout.WriteString(strings.Join(l, separator)) os.Stdout.WriteString(strings.Join(l, zenutil.Separator))
os.Stdout.WriteString(zenutil.LineBreak) os.Stdout.WriteString(zenutil.LineBreak)
if l == nil { if l == nil {
os.Exit(1) os.Exit(1)

View file

@ -16,7 +16,7 @@ func selectColor(options []Option) (color.Color, error) {
data.Color = []uint16{n.R, n.G, n.B} 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 { if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
return nil, nil return nil, nil
} }

View file

@ -24,7 +24,7 @@ func selectColor(options []Option) (color.Color, error) {
args = append(args, "--show-palette") 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 { if err, ok := err.(*exec.ExitError); ok && err.ExitCode() != 255 {
return nil, nil return nil, nil
} }

View file

@ -22,7 +22,7 @@ func selectFile(options []Option) (string, error) {
} }
data.Location, _ = splitDirAndName(opts.filename) 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 { if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
return "", nil return "", nil
} }
@ -41,8 +41,8 @@ func selectFileMutiple(options []Option) ([]string, error) {
data := zenutil.File{ data := zenutil.File{
Prompt: opts.title, Prompt: opts.title,
Invisibles: opts.showHidden, Invisibles: opts.showHidden,
Multiple: true,
Separator: zenutil.Separator, Separator: zenutil.Separator,
Multiple: true,
} }
if opts.directory { if opts.directory {
data.Operation = "chooseFolder" data.Operation = "chooseFolder"
@ -52,7 +52,7 @@ func selectFileMutiple(options []Option) ([]string, error) {
} }
data.Location, _ = splitDirAndName(opts.filename) 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 { if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
return nil, nil return nil, nil
} }
@ -82,7 +82,7 @@ func selectFileSave(options []Option) (string, error) {
} }
data.Location, data.Name = splitDirAndName(opts.filename) 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 { if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
return "", nil return "", nil
} }

View file

@ -24,7 +24,7 @@ func selectFile(options []Option) (string, error) {
} }
args = append(args, initFilters(opts.fileFilters)...) 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 { if err, ok := err.(*exec.ExitError); ok && err.ExitCode() != 255 {
return "", nil return "", nil
} }
@ -52,7 +52,7 @@ func selectFileMutiple(options []Option) ([]string, error) {
} }
args = append(args, initFilters(opts.fileFilters)...) 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 { if err, ok := err.(*exec.ExitError); ok && err.ExitCode() != 255 {
return nil, nil return nil, nil
} }
@ -83,7 +83,7 @@ func selectFileSave(options []Option) (string, error) {
} }
args = append(args, initFilters(opts.fileFilters)...) 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 { if err, ok := err.(*exec.ExitError); ok && err.ExitCode() != 255 {
return "", nil return "", nil
} }

View file

@ -3,4 +3,5 @@ package zenutil
const LineBreak = "\n" const LineBreak = "\n"
var Command bool var Command bool
var Timeout int
var Separator = "\x00" var Separator = "\x00"

View file

@ -6,4 +6,5 @@ package zenutil
const LineBreak = "\n" const LineBreak = "\n"
var Command bool var Command bool
var Timeout int
var Separator = "\x1e" var Separator = "\x1e"

View file

@ -3,4 +3,5 @@ package zenutil
const LineBreak = "\r\n" const LineBreak = "\r\n"
var Command bool var Command bool
var Timeout int
var Separator string var Separator string

View file

@ -68,14 +68,25 @@ opts.withIcon = {{json .Icon}}
{{if .Buttons -}} {{if .Buttons -}}
opts.buttons = {{json .Buttons}} opts.buttons = {{json .Buttons}}
{{end -}} {{end -}}
{{if .Default -}}
opts.defaultButton = {{json .Default}}
{{end -}}
{{if .Cancel -}} {{if .Cancel -}}
opts.cancelButton = {{json .Cancel}} opts.cancelButton = {{json .Cancel}}
{{end -}} {{end -}}
var res = app[{{json .Operation}}]({{json .Text}}, opts).buttonReturned {{if .Default -}}
res === {{json .Extra}} ? res : void 0 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}} {{- end}}
{{define "notify" -}}var app = Application.currentApplication() {{define "notify" -}}var app = Application.currentApplication()
app.includeStandardAdditions = true app.includeStandardAdditions = true

View file

@ -19,12 +19,23 @@ var opts = {}
{{if .Buttons -}} {{if .Buttons -}}
opts.buttons = {{json .Buttons}} opts.buttons = {{json .Buttons}}
{{end -}} {{end -}}
{{if .Default -}}
opts.defaultButton = {{json .Default}}
{{end -}}
{{if .Cancel -}} {{if .Cancel -}}
opts.cancelButton = {{json .Cancel}} opts.cancelButton = {{json .Cancel}}
{{end -}} {{end -}}
{{if .Default -}}
opts.defaultButton = {{json .Default}}
{{end -}}
{{if .Timeout -}}
opts.givingUpAfter = {{json .Timeout}}
{{end -}}
var res = app[{{json .Operation}}]({{json .Text}}, opts).buttonReturned var res = app[{{json .Operation}}]({{json .Text}}, opts)
res === {{json .Extra}} ? res : void 0 if (res.gaveUp) {
ObjC.import("stdlib")
$.exit(5)
}
if (res.buttonReturned === {{json .Extra}}) {
res
} else {
void 0
}

View file

@ -1,13 +1,14 @@
package zenutil package zenutil
import ( import (
"context"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"syscall" "syscall"
) )
func Run(script string, data interface{}) ([]byte, error) { func Run(ctx context.Context, script string, data interface{}) ([]byte, error) {
var buf strings.Builder var buf strings.Builder
err := scripts.ExecuteTemplate(&buf, script, data) 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 := exec.Command("osascript", "-l", lang)
cmd.Stdin = strings.NewReader(script) cmd.Stdin = strings.NewReader(script)
return cmd.Output() return cmd.Output()
@ -60,6 +66,7 @@ type Msg struct {
Buttons []string Buttons []string
Cancel int Cancel int
Default int Default int
Timeout int
} }
type Notify struct { type Notify struct {

View file

@ -3,8 +3,10 @@
package zenutil package zenutil
import ( import (
"context"
"os" "os"
"os/exec" "os/exec"
"strconv"
"syscall" "syscall"
) )
@ -20,9 +22,16 @@ func init() {
tool = "zenity" tool = "zenity"
} }
func Run(args []string) ([]byte, error) { func Run(ctx context.Context, args []string) ([]byte, error) {
if Command && path != "" { if Command && path != "" {
if Timeout > 0 {
args = append(args, "--timeout", strconv.Itoa(Timeout))
}
syscall.Exec(path, append([]string{tool}, args...), os.Environ()) 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() return exec.Command(tool, args...).Output()
} }

2
msg.go
View file

@ -2,7 +2,7 @@ package zenity
// ErrExtraButton is returned by dialog functions when the extra button is // ErrExtraButton is returned by dialog functions when the extra button is
// pressed. // pressed.
const ErrExtraButton = constError("Extra button pressed.") const ErrExtraButton = constError("Extra button pressed")
// Question displays the question dialog. // Question displays the question dialog.
// //

View file

@ -8,7 +8,10 @@ import (
func message(kind messageKind, text string, options []Option) (bool, error) { func message(kind messageKind, text string, options []Option) (bool, error) {
opts := applyOptions(options) opts := applyOptions(options)
data := zenutil.Msg{Text: text} data := zenutil.Msg{
Text: text,
Timeout: zenutil.Timeout,
}
dialog := kind == questionKind || opts.icon != 0 dialog := kind == questionKind || opts.icon != 0
if dialog { 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 { if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
return false, nil return false, nil
} }

View file

@ -57,7 +57,7 @@ func message(kind messageKind, text string, options []Option) (bool, error) {
args = append(args, "--window-icon=question", "--icon-name=dialog-question") 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 err, ok := err.(*exec.ExitError); ok && err.ExitCode() != 255 {
if len(out) > 0 && string(out[:len(out)-1]) == opts.extraButton { if len(out) > 0 && string(out[:len(out)-1]) == opts.extraButton {
return false, ErrExtraButton return false, ErrExtraButton

View file

@ -16,7 +16,7 @@ func notify(text string, options []Option) error {
data.Subtitle = text[:i] data.Subtitle = text[:i]
data.Text = text[i+1:] data.Text = text[i+1:]
} }
_, err := zenutil.Run("notify", data) _, err := zenutil.Run(opts.ctx, "notify", data)
if err != nil { if err != nil {
return err return err
} }

View file

@ -28,7 +28,7 @@ func notify(text string, options []Option) error {
args = append(args, "--window-icon=question") args = append(args, "--window-icon=question")
} }
_, err := zenutil.Run(args) _, err := zenutil.Run(opts.ctx, args)
if err != nil { if err != nil {
return err return err
} }

View file

@ -10,7 +10,10 @@
// initialization requirements. // initialization requirements.
package zenity package zenity
import "image/color" import (
"context"
"image/color"
)
type constError string type constError string
@ -40,6 +43,9 @@ type options struct {
noWrap bool noWrap bool
ellipsize bool ellipsize bool
defaultCancel bool defaultCancel bool
// Context for timeout
ctx context.Context
} }
// An Option is an argument passed to dialog functions to customize their // An Option is an argument passed to dialog functions to customize their
@ -79,3 +85,7 @@ const (
func Icon(icon DialogIcon) Option { func Icon(icon DialogIcon) Option {
return funcOption(func(o *options) { o.icon = icon }) return funcOption(func(o *options) { o.icon = icon })
} }
func Context(ctx context.Context) Option {
return funcOption(func(o *options) { o.ctx = ctx })
}