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
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(&notification, "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)

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

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

View file

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

View file

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

View file

@ -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

View file

@ -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
}

View file

@ -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 {

View file

@ -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
View file

@ -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.
//

View file

@ -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
}

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")
}
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

View file

@ -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
}

View file

@ -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
}

View file

@ -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 })
}