diff --git a/cmd/zenity/main.go b/cmd/zenity/main.go index cb5b8ee..974ae9d 100644 --- a/cmd/zenity/main.go +++ b/cmd/zenity/main.go @@ -17,6 +17,10 @@ import ( //go:generate go run github.com/josephspurrier/goversioninfo/cmd/goversioninfo -platform-specific -manifest=win.manifest +const ( + unspecified = "\x00" +) + var ( // Application Options notification bool @@ -122,14 +126,14 @@ 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)") - flag.UintVar(&width, "width", 0, "Set the width") - flag.UintVar(&height, "height", 0, "Set the height") + flag.StringVar(&title, "title", "", "Set the dialog `title`") + flag.StringVar(&icon, "window-icon", "", "Set the window `icon` (error, info, question, warning)") + flag.UintVar(&width, "width", 0, "Set the `width`") + flag.UintVar(&height, "height", 0, "Set the `height`") // Message options - flag.StringVar(&text, "text", "", "Set the dialog text") - flag.StringVar(&icon, "icon-name", "", "Set the dialog icon (dialog-error, dialog-information, dialog-question, dialog-warning)") + flag.StringVar(&text, "text", "", "Set the dialog `text`") + flag.StringVar(&icon, "icon-name", "", "Set the dialog `icon` (dialog-error, dialog-information, dialog-question, dialog-warning)") flag.StringVar(&okLabel, "ok-label", "", "Set the label of the OK button") flag.StringVar(&cancelLabel, "cancel-label", "", "Set the label of the Cancel button") flag.StringVar(&extraButton, "extra-button", "", "Add an extra button") @@ -144,11 +148,11 @@ func setupFlags() { flag.BoolVar(&confirmOverwrite, "confirm-overwrite", false, "Confirm file selection if filename already exists") 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(&filename, "filename", "", "Set the `filename`") flag.Var(&fileFilters, "file-filter", "Set a filename filter (NAME | PATTERN1 PATTERN2 ...)") // Color selection options - flag.StringVar(&defaultColor, "color", "", "Set the color") + flag.StringVar(&defaultColor, "color", "", "Set the `color`") flag.BoolVar(&showPalette, "show-palette", false, "Show the palette") // Windows specific options @@ -157,9 +161,17 @@ func setupFlags() { 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") + // Command options + flag.IntVar(&zenutil.Timeout, "timeout", 0, "Set dialog `timeout` in seconds") + flag.StringVar(&zenutil.Separator, "separator", "|", "Set output `separator` character") + + // Detect unspecified values + title = unspecified + icon = unspecified + text = unspecified + okLabel = unspecified + cancelLabel = unspecified + extraButton = unspecified } func validateFlags() { @@ -193,26 +205,53 @@ func validateFlags() { func loadFlags() []zenity.Option { var opts []zenity.Option + // Defaults + + setDefault := func(s *string, val string) { + if *s == unspecified { + *s = val + } + } + switch { + case errorDlg: + setDefault(&title, "Error") + setDefault(&icon, "dialog-error") + setDefault(&text, "An error has occurred.") + setDefault(&okLabel, "OK") + case infoDlg: + setDefault(&title, "Information") + setDefault(&icon, "dialog-information") + setDefault(&text, "All updates are complete.") + setDefault(&okLabel, "OK") + case warningDlg: + setDefault(&title, "Warning") + setDefault(&icon, "dialog-warning") + setDefault(&text, "Are you sure you want to proceed?") + setDefault(&okLabel, "OK") + case questionDlg: + setDefault(&title, "Question") + setDefault(&icon, "dialog-question") + setDefault(&text, "Are you sure you want to proceed?") + setDefault(&okLabel, "Yes") + setDefault(&cancelLabel, "No") + default: + setDefault(&text, "") + } + // General options - opts = append(opts, zenity.Title(title)) + if title != unspecified { + opts = append(opts, zenity.Title(title)) + } opts = append(opts, zenity.Width(width)) opts = append(opts, zenity.Height(height)) // Message options var ico zenity.DialogIcon - switch { - case errorDlg: - ico = zenity.ErrorIcon - case infoDlg: - ico = zenity.InfoIcon - case warningDlg: - ico = zenity.WarningIcon - case questionDlg: - ico = zenity.QuestionIcon - } switch icon { + case "": + ico = zenity.NoIcon case "error", "dialog-error": ico = zenity.ErrorIcon case "info", "dialog-information": @@ -224,9 +263,15 @@ func loadFlags() []zenity.Option { } 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 okLabel != unspecified { + opts = append(opts, zenity.OKLabel(okLabel)) + } + if cancelLabel != unspecified { + opts = append(opts, zenity.CancelLabel(cancelLabel)) + } + if extraButton != unspecified { + opts = append(opts, zenity.ExtraButton(extraButton)) + } if noWrap { opts = append(opts, zenity.NoWrap()) } diff --git a/color.go b/color.go index 6efb616..f9978de 100644 --- a/color.go +++ b/color.go @@ -8,7 +8,7 @@ import "image/color" // // Valid options: Title, Color, ShowPalette. func SelectColor(options ...Option) (color.Color, error) { - return selectColor(options) + return selectColor(applyOptions(options)) } // Color returns an Option to set the color. diff --git a/color_darwin.go b/color_darwin.go index f724dff..138c305 100644 --- a/color_darwin.go +++ b/color_darwin.go @@ -7,9 +7,7 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -func selectColor(options []Option) (color.Color, error) { - opts := applyOptions(options) - +func selectColor(opts options) (color.Color, error) { var col color.Color if opts.color != nil { col = opts.color diff --git a/color_unix.go b/color_unix.go index ef155ac..1df04e4 100644 --- a/color_unix.go +++ b/color_unix.go @@ -9,13 +9,11 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -func selectColor(options []Option) (color.Color, error) { - opts := applyOptions(options) - +func selectColor(opts options) (color.Color, error) { args := []string{"--color-selection"} - if opts.title != "" { - args = append(args, "--title", opts.title) + if opts.title != nil { + args = append(args, "--title", *opts.title) } if opts.color != nil { args = append(args, "--color", zenutil.UnparseColor(opts.color)) diff --git a/color_windows.go b/color_windows.go index 49c7dd6..2641afd 100644 --- a/color_windows.go +++ b/color_windows.go @@ -20,9 +20,7 @@ func init() { } } -func selectColor(options []Option) (color.Color, error) { - opts := applyOptions(options) - +func selectColor(opts options) (color.Color, error) { // load custom colors colorsMutex.Lock() customColors := savedColors @@ -46,7 +44,7 @@ func selectColor(options []Option) (color.Color, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - if opts.ctx != nil || opts.title != "" { + if opts.ctx != nil || opts.title != nil { unhook, err := hookDialogTitle(opts.ctx, opts.title) if err != nil { return nil, err diff --git a/file.go b/file.go index eeafa15..9201b86 100644 --- a/file.go +++ b/file.go @@ -11,7 +11,7 @@ import ( // // Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s). func SelectFile(options ...Option) (string, error) { - return selectFile(options) + return selectFile(applyOptions(options)) } // SelectFileMutiple displays the multiple file selection dialog. @@ -20,7 +20,7 @@ func SelectFile(options ...Option) (string, error) { // // Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s). func SelectFileMutiple(options ...Option) ([]string, error) { - return selectFileMutiple(options) + return selectFileMutiple(applyOptions(options)) } // SelectFileSave displays the save file selection dialog. @@ -30,7 +30,7 @@ func SelectFileMutiple(options ...Option) ([]string, error) { // Valid options: Title, Filename, ConfirmOverwrite, ConfirmCreate, ShowHidden, // FileFilter(s). func SelectFileSave(options ...Option) (string, error) { - return selectFileSave(options) + return selectFileSave(applyOptions(options)) } // Filename returns an Option to set the filename. diff --git a/file_darwin.go b/file_darwin.go index 999d9f6..7e1b92c 100644 --- a/file_darwin.go +++ b/file_darwin.go @@ -7,9 +7,7 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -func selectFile(options []Option) (string, error) { - opts := applyOptions(options) - +func selectFile(opts options) (string, error) { var data zenutil.File data.Options.Prompt = opts.title data.Options.Invisibles = opts.showHidden @@ -35,9 +33,7 @@ func selectFile(options []Option) (string, error) { return string(out), nil } -func selectFileMutiple(options []Option) ([]string, error) { - opts := applyOptions(options) - +func selectFileMutiple(opts options) ([]string, error) { var data zenutil.File data.Options.Prompt = opts.title data.Options.Invisibles = opts.showHidden @@ -68,9 +64,7 @@ func selectFileMutiple(options []Option) ([]string, error) { return strings.Split(string(out), zenutil.Separator), nil } -func selectFileSave(options []Option) (string, error) { - opts := applyOptions(options) - +func selectFileSave(opts options) (string, error) { var data zenutil.File data.Options.Prompt = opts.title data.Options.Invisibles = opts.showHidden diff --git a/file_unix.go b/file_unix.go index 55e375f..0d828ba 100644 --- a/file_unix.go +++ b/file_unix.go @@ -9,15 +9,13 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -func selectFile(options []Option) (string, error) { - opts := applyOptions(options) - +func selectFile(opts options) (string, error) { args := []string{"--file-selection"} if opts.directory { args = append(args, "--directory") } - if opts.title != "" { - args = append(args, "--title", opts.title) + if opts.title != nil { + args = append(args, "--title", *opts.title) } if opts.filename != "" { args = append(args, "--filename", opts.filename) @@ -37,15 +35,13 @@ func selectFile(options []Option) (string, error) { return string(out), nil } -func selectFileMutiple(options []Option) ([]string, error) { - opts := applyOptions(options) - +func selectFileMutiple(opts options) ([]string, error) { args := []string{"--file-selection", "--multiple", "--separator", zenutil.Separator} if opts.directory { args = append(args, "--directory") } - if opts.title != "" { - args = append(args, "--title", opts.title) + if opts.title != nil { + args = append(args, "--title", *opts.title) } if opts.filename != "" { args = append(args, "--filename", opts.filename) @@ -65,15 +61,13 @@ func selectFileMutiple(options []Option) ([]string, error) { return strings.Split(string(out), zenutil.Separator), nil } -func selectFileSave(options []Option) (string, error) { - opts := applyOptions(options) - +func selectFileSave(opts options) (string, error) { args := []string{"--file-selection", "--save"} if opts.directory { args = append(args, "--directory") } - if opts.title != "" { - args = append(args, "--title", opts.title) + if opts.title != nil { + args = append(args, "--title", *opts.title) } if opts.filename != "" { args = append(args, "--filename", opts.filename) diff --git a/file_windows.go b/file_windows.go index e6bad4f..5a7d70a 100644 --- a/file_windows.go +++ b/file_windows.go @@ -17,8 +17,7 @@ var ( shCreateItemFromParsingName = shell32.NewProc("SHCreateItemFromParsingName") ) -func selectFile(options []Option) (string, error) { - opts := applyOptions(options) +func selectFile(opts options) (string, error) { if opts.directory { res, _, err := pickFolders(opts, false) return res, err @@ -28,8 +27,8 @@ func selectFile(options []Option) (string, error) { args.StructSize = uint32(unsafe.Sizeof(args)) args.Flags = 0x81008 // OFN_NOCHANGEDIR|OFN_FILEMUSTEXIST|OFN_EXPLORER - if opts.title != "" { - args.Title = syscall.StringToUTF16Ptr(opts.title) + if opts.title != nil { + args.Title = syscall.StringToUTF16Ptr(*opts.title) } if opts.showHidden { args.Flags |= 0x10000000 // OFN_FORCESHOWHIDDEN @@ -65,8 +64,7 @@ func selectFile(options []Option) (string, error) { return syscall.UTF16ToString(res[:]), nil } -func selectFileMutiple(options []Option) ([]string, error) { - opts := applyOptions(options) +func selectFileMutiple(opts options) ([]string, error) { if opts.directory { _, res, err := pickFolders(opts, true) return res, err @@ -76,8 +74,8 @@ func selectFileMutiple(options []Option) ([]string, error) { args.StructSize = uint32(unsafe.Sizeof(args)) args.Flags = 0x81208 // OFN_NOCHANGEDIR|OFN_ALLOWMULTISELECT|OFN_FILEMUSTEXIST|OFN_EXPLORER - if opts.title != "" { - args.Title = syscall.StringToUTF16Ptr(opts.title) + if opts.title != nil { + args.Title = syscall.StringToUTF16Ptr(*opts.title) } if opts.showHidden { args.Flags |= 0x10000000 // OFN_FORCESHOWHIDDEN @@ -138,8 +136,7 @@ func selectFileMutiple(options []Option) ([]string, error) { return split, nil } -func selectFileSave(options []Option) (string, error) { - opts := applyOptions(options) +func selectFileSave(opts options) (string, error) { if opts.directory { res, _, err := pickFolders(opts, false) return res, err @@ -149,8 +146,8 @@ func selectFileSave(options []Option) (string, error) { args.StructSize = uint32(unsafe.Sizeof(args)) args.Flags = 0x88808 // OFN_NOCHANGEDIR|OFN_PATHMUSTEXIST|OFN_NOREADONLYRETURN|OFN_EXPLORER - if opts.title != "" { - args.Title = syscall.StringToUTF16Ptr(opts.title) + if opts.title != nil { + args.Title = syscall.StringToUTF16Ptr(*opts.title) } if opts.confirmOverwrite { args.Flags |= 0x2 // OFN_OVERWRITEPROMPT @@ -229,8 +226,8 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error) return "", nil, syscall.Errno(hr) } - if opts.title != "" { - ptr := syscall.StringToUTF16Ptr(opts.title) + if opts.title != nil { + ptr := syscall.StringToUTF16Ptr(*opts.title) dialog.Call(dialog.vtbl.SetTitle, uintptr(unsafe.Pointer(ptr))) } @@ -319,8 +316,8 @@ func browseForFolder(opts options) (string, []string, error) { var args _BROWSEINFO args.Flags = 0x1 // BIF_RETURNONLYFSDIRS - if opts.title != "" { - args.Title = syscall.StringToUTF16Ptr(opts.title) + if opts.title != nil { + args.Title = syscall.StringToUTF16Ptr(*opts.title) } if opts.filename != "" { ptr := syscall.StringToUTF16Ptr(opts.filename) diff --git a/internal/zenutil/osa_generated.go b/internal/zenutil/osa_generated.go index 2c7dc4d..555a84b 100644 --- a/internal/zenutil/osa_generated.go +++ b/internal/zenutil/osa_generated.go @@ -28,10 +28,13 @@ if(Array.isArray(res)){res.join({{json .Separator}})}else{res.toString()} var app=Application.currentApplication() app.includeStandardAdditions=true app.activate() +ObjC.import("stdlib") +ObjC.import("stdio") var res=app.{{.Operation}}({{json .Text}},{{json .Options}}) -if(res.gaveUp){ObjC.import("stdlib") -$.exit(5)} -if(res.buttonReturned==={{json .Extra}}){res.buttonReturned}else{void 0} +if(res.gaveUp){$.exit(5)} +if(res.buttonReturned==={{json .Extra}}){$.puts(res.buttonReturned) +$.exit(1)} +void 0 {{- end}} {{define "notify" -}} var app=Application.currentApplication() diff --git a/internal/zenutil/osascripts/msg.gojs b/internal/zenutil/osascripts/msg.gojs index 428c554..aca3d83 100644 --- a/internal/zenutil/osascripts/msg.gojs +++ b/internal/zenutil/osascripts/msg.gojs @@ -2,13 +2,14 @@ var app = Application.currentApplication() app.includeStandardAdditions = true app.activate() +ObjC.import("stdlib") +ObjC.import("stdio") + var res = app.{{.Operation}}({{json .Text}}, {{json .Options}}) if (res.gaveUp) { - ObjC.import("stdlib") $.exit(5) } if (res.buttonReturned === {{json .Extra}}) { - res.buttonReturned -} else { - void 0 + $.puts(res.buttonReturned) + $.exit(1) } \ No newline at end of file diff --git a/internal/zenutil/run_darwin.go b/internal/zenutil/run_darwin.go index bb2fd88..3764d5a 100644 --- a/internal/zenutil/run_darwin.go +++ b/internal/zenutil/run_darwin.go @@ -56,7 +56,7 @@ type File struct { // FileOptions is internal. type FileOptions struct { - Prompt string `json:"withPrompt,omitempty"` + Prompt *string `json:"withPrompt,omitempty"` Type []string `json:"ofType,omitempty"` Name string `json:"defaultName,omitempty"` Location string `json:"defaultLocation,omitempty"` @@ -68,7 +68,7 @@ type FileOptions struct { type Msg struct { Operation string Text string - Extra string + Extra *string Options MsgOptions } @@ -76,7 +76,7 @@ type Msg struct { type MsgOptions struct { Message string `json:"message,omitempty"` As string `json:"as,omitempty"` - Title string `json:"withTitle,omitempty"` + Title *string `json:"withTitle,omitempty"` Icon string `json:"withIcon,omitempty"` Buttons []string `json:"buttons,omitempty"` Cancel int `json:"cancelButton,omitempty"` @@ -92,6 +92,6 @@ type Notify struct { // NotifyOptions is internal. type NotifyOptions struct { - Title string `json:"withTitle,omitempty"` - Subtitle string `json:"subtitle,omitempty"` + Title *string `json:"withTitle,omitempty"` + Subtitle string `json:"subtitle,omitempty"` } diff --git a/msg.go b/msg.go index 6f7884c..a9ab5c4 100644 --- a/msg.go +++ b/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 = stringErr("Extra button pressed") // Question displays the question dialog. // @@ -11,7 +11,7 @@ const ErrExtraButton = constError("Extra button pressed") // Valid options: Title, Width, Height, Icon, OKLabel, CancelLabel, // ExtraButton, NoWrap, Ellipsize, DefaultCancel. func Question(text string, options ...Option) (bool, error) { - return message(questionKind, text, options) + return message(questionKind, text, applyOptions(options)) } // Info displays the info dialog. @@ -21,7 +21,7 @@ func Question(text string, options ...Option) (bool, error) { // Valid options: Title, Width, Height, Icon, OKLabel, ExtraButton, // NoWrap, Ellipsize. func Info(text string, options ...Option) (bool, error) { - return message(infoKind, text, options) + return message(infoKind, text, applyOptions(options)) } // Warning displays the warning dialog. @@ -31,7 +31,7 @@ func Info(text string, options ...Option) (bool, error) { // Valid options: Title, Width, Height, Icon, OKLabel, ExtraButton, // NoWrap, Ellipsize. func Warning(text string, options ...Option) (bool, error) { - return message(warningKind, text, options) + return message(warningKind, text, applyOptions(options)) } // Error displays the error dialog. @@ -41,7 +41,7 @@ func Warning(text string, options ...Option) (bool, error) { // Valid options: Title, Width, Height, Icon, OKLabel, ExtraButton, // NoWrap, Ellipsize. func Error(text string, options ...Option) (bool, error) { - return message(errorKind, text, options) + return message(errorKind, text, applyOptions(options)) } type messageKind int @@ -55,30 +55,30 @@ const ( // OKLabel returns an Option to set the label of the OK button. func OKLabel(ok string) Option { - return funcOption(func(o *options) { o.okLabel = ok }) + return funcOption(func(o *options) { o.okLabel = &ok }) } // CancelLabel returns an Option to set the label of the Cancel button. func CancelLabel(cancel string) Option { - return funcOption(func(o *options) { o.cancelLabel = cancel }) + return funcOption(func(o *options) { o.cancelLabel = &cancel }) } // ExtraButton returns an Option to add an extra button. func ExtraButton(extra string) Option { - return funcOption(func(o *options) { o.extraButton = extra }) + return funcOption(func(o *options) { o.extraButton = &extra }) } -// NoWrap returns an Option to disable enable text wrapping. +// NoWrap returns an Option to disable enable text wrapping (Unix only). func NoWrap() Option { return funcOption(func(o *options) { o.noWrap = true }) } -// Ellipsize returns an Option to enable ellipsizing in the dialog text. +// Ellipsize returns an Option to enable ellipsizing in the dialog text (Unix only). func Ellipsize() Option { return funcOption(func(o *options) { o.ellipsize = true }) } -// DefaultCancel returns an Option to give Cancel button focus by default. +// DefaultCancel returns an Option to give the Cancel button focus by default. func DefaultCancel() Option { return funcOption(func(o *options) { o.defaultCancel = true }) } diff --git a/msg_darwin.go b/msg_darwin.go index 845846c..1e7642e 100644 --- a/msg_darwin.go +++ b/msg_darwin.go @@ -6,14 +6,18 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -func message(kind messageKind, text string, options []Option) (bool, error) { - opts := applyOptions(options) - +func message(kind messageKind, text string, opts options) (bool, error) { var data zenutil.Msg data.Text = text data.Options.Timeout = zenutil.Timeout - dialog := kind == questionKind || opts.icon != 0 + // dialog is more flexible, alert prettier + var dialog bool + if opts.icon != 0 { // use if we want to show a specific icon + dialog = true + } else if kind == questionKind && opts.cancelLabel == nil { // use for questions with default buttons + dialog = true + } if dialog { data.Operation = "displayDialog" @@ -29,9 +33,9 @@ func message(kind messageKind, text string, options []Option) (bool, error) { } } else { data.Operation = "displayAlert" - if opts.title != "" { + if opts.title != nil { + data.Text = *opts.title data.Options.Message = text - data.Text = opts.title } switch kind { @@ -44,58 +48,67 @@ func message(kind messageKind, text string, options []Option) (bool, error) { } } - if kind != questionKind { - if dialog { - opts.okLabel = "OK" + if kind == questionKind { + // alert defaults to a single button, we need two + if opts.cancelLabel == nil && !dialog { + opts.cancelLabel = stringPtr("Cancel") } - opts.cancelLabel = "" + } else { + // dialog defaults to two buttons, we need one + if opts.okLabel == nil && dialog { + opts.okLabel = stringPtr("OK") + } + // only questions have cancel + opts.cancelLabel = nil } - if opts.okLabel != "" || opts.cancelLabel != "" || opts.extraButton != "" { - if opts.okLabel == "" { - opts.okLabel = "OK" - } - if opts.cancelLabel == "" { - opts.cancelLabel = "Cancel" + + if opts.okLabel != nil || opts.cancelLabel != nil || opts.extraButton != nil { + if opts.okLabel == nil { + opts.okLabel = stringPtr("OK") } if kind == questionKind { - if opts.extraButton == "" { - data.Options.Buttons = []string{opts.cancelLabel, opts.okLabel} + if opts.cancelLabel == nil { + opts.cancelLabel = stringPtr("Cancel") + } + if opts.extraButton == nil { + data.Options.Buttons = []string{*opts.cancelLabel, *opts.okLabel} data.Options.Default = 2 data.Options.Cancel = 1 } else { - data.Options.Buttons = []string{opts.extraButton, opts.cancelLabel, opts.okLabel} + data.Options.Buttons = []string{*opts.extraButton, *opts.cancelLabel, *opts.okLabel} data.Options.Default = 3 data.Options.Cancel = 2 } } else { - if opts.extraButton == "" { - data.Options.Buttons = []string{opts.okLabel} + if opts.extraButton == nil { + data.Options.Buttons = []string{*opts.okLabel} data.Options.Default = 1 } else { - data.Options.Buttons = []string{opts.extraButton, opts.okLabel} + data.Options.Buttons = []string{*opts.extraButton, *opts.okLabel} data.Options.Default = 2 } } data.Extra = opts.extraButton } - if opts.defaultCancel { + + if kind == questionKind && opts.defaultCancel { if data.Options.Cancel != 0 { data.Options.Default = data.Options.Cancel - } - if dialog && data.Options.Buttons == nil { + } else { data.Options.Default = 1 } } out, err := zenutil.Run(opts.ctx, "msg", data) + if len(out) > 0 && opts.extraButton != nil && + string(out[:len(out)-1]) == *opts.extraButton { + return false, ErrExtraButton + } if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 { return false, nil } if err != nil { return false, err } - if len(out) > 0 && string(out[:len(out)-1]) == opts.extraButton { - return false, ErrExtraButton - } return true, err } diff --git a/msg_unix.go b/msg_unix.go index eba957e..79d0c78 100644 --- a/msg_unix.go +++ b/msg_unix.go @@ -9,9 +9,7 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -func message(kind messageKind, text string, options []Option) (bool, error) { - opts := applyOptions(options) - +func message(kind messageKind, text string, opts options) (bool, error) { var args []string switch kind { case questionKind: @@ -26,8 +24,8 @@ func message(kind messageKind, text string, options []Option) (bool, error) { if text != "" { args = append(args, "--text", text, "--no-markup") } - if opts.title != "" { - args = append(args, "--title", opts.title) + if opts.title != nil { + args = append(args, "--title", *opts.title) } if opts.width > 0 { args = append(args, "--width", strconv.FormatUint(uint64(opts.width), 10)) @@ -35,14 +33,14 @@ func message(kind messageKind, text string, options []Option) (bool, error) { if opts.height > 0 { args = append(args, "--height", strconv.FormatUint(uint64(opts.height), 10)) } - if opts.okLabel != "" { - args = append(args, "--ok-label", opts.okLabel) + if opts.okLabel != nil { + args = append(args, "--ok-label", *opts.okLabel) } - if opts.cancelLabel != "" { - args = append(args, "--cancel-label", opts.cancelLabel) + if opts.cancelLabel != nil { + args = append(args, "--cancel-label", *opts.cancelLabel) } - if opts.extraButton != "" { - args = append(args, "--extra-button", opts.extraButton) + if opts.extraButton != nil { + args = append(args, "--extra-button", *opts.extraButton) } if opts.noWrap { args = append(args, "--no-wrap") @@ -54,6 +52,8 @@ func message(kind messageKind, text string, options []Option) (bool, error) { args = append(args, "--default-cancel") } switch opts.icon { + case NoIcon: + args = append(args, "--icon-name=") case ErrorIcon: args = append(args, "--window-icon=error", "--icon-name=dialog-error") case WarningIcon: @@ -65,10 +65,11 @@ func message(kind messageKind, text string, options []Option) (bool, error) { } 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 - } + if len(out) > 0 && opts.extraButton != nil && + string(out[:len(out)-1]) == *opts.extraButton { + return false, ErrExtraButton + } + if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 { return false, nil } if err != nil { diff --git a/msg_windows.go b/msg_windows.go index bc24820..8342b3e 100644 --- a/msg_windows.go +++ b/msg_windows.go @@ -11,16 +11,16 @@ var ( messageBox = user32.NewProc("MessageBoxW") ) -func message(kind messageKind, text string, options []Option) (bool, error) { - opts := applyOptions(options) - +func message(kind messageKind, text string, opts options) (bool, error) { var flags uintptr switch { - case kind == questionKind && opts.extraButton != "": + case kind == questionKind && opts.extraButton != nil: flags |= 0x3 // MB_YESNOCANCEL - case kind == questionKind || opts.extraButton != "": + case kind == questionKind: flags |= 0x1 // MB_OKCANCEL + case opts.extraButton != nil: + flags |= 0x4 // MB_YESNO } switch opts.icon { @@ -35,14 +35,14 @@ func message(kind messageKind, text string, options []Option) (bool, error) { } if kind == questionKind && opts.defaultCancel { - if opts.extraButton == "" { + if opts.extraButton == nil { flags |= 0x100 // MB_DEFBUTTON2 } else { flags |= 0x200 // MB_DEFBUTTON3 } } - if opts.ctx != nil || opts.okLabel != "" || opts.cancelLabel != "" || opts.extraButton != "" { + if opts.ctx != nil || opts.okLabel != nil || opts.cancelLabel != nil || opts.extraButton != nil { runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -53,24 +53,29 @@ func message(kind messageKind, text string, options []Option) (bool, error) { defer unhook() } + var title *uint16 + if opts.title != nil { + title = syscall.StringToUTF16Ptr(*opts.title) + } + activate() s, _, err := messageBox.Call(0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text))), - uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(opts.title))), flags) + uintptr(unsafe.Pointer(title)), flags) if opts.ctx != nil && opts.ctx.Err() != nil { return false, opts.ctx.Err() } - if s == 0 { + switch s { + case 1, 6: // IDOK, IDYES + return true, nil + case 2: // IDCANCEL + return false, nil + case 7: // IDNO + return false, ErrExtraButton + default: return false, err } - if s == 7 || s == 2 && kind != questionKind { // IDNO - return false, ErrExtraButton - } - if s == 1 || s == 6 { // IDOK, IDYES - return true, nil - } - return false, nil } func hookMessageLabels(kind messageKind, opts options) (unhook context.CancelFunc, err error) { @@ -81,23 +86,21 @@ func hookMessageLabels(kind messageKind, opts options) (unhook context.CancelFun getClassName.Call(wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name))) if syscall.UTF16ToString(name[:]) == "Button" { ctl, _, _ := getDlgCtrlID.Call(wnd) - var text string + var text *string switch ctl { case 1, 6: // IDOK, IDYES text = opts.okLabel case 2: // IDCANCEL if kind == questionKind { text = opts.cancelLabel - } else if opts.extraButton != "" { - text = opts.extraButton } else { text = opts.okLabel } case 7: // IDNO text = opts.extraButton } - if text != "" { - ptr := syscall.StringToUTF16Ptr(text) + if text != nil { + ptr := syscall.StringToUTF16Ptr(*text) setWindowText.Call(wnd, uintptr(unsafe.Pointer(ptr))) } } diff --git a/notify.go b/notify.go index 0049c71..ca4695a 100644 --- a/notify.go +++ b/notify.go @@ -4,5 +4,5 @@ package zenity // // Valid options: Title, Icon. func Notify(text string, options ...Option) error { - return notify(text, options) + return notify(text, applyOptions(options)) } diff --git a/notify_darwin.go b/notify_darwin.go index 268fdbc..e15ee0e 100644 --- a/notify_darwin.go +++ b/notify_darwin.go @@ -6,9 +6,7 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -func notify(text string, options []Option) error { - opts := applyOptions(options) - +func notify(text string, opts options) error { var data zenutil.Notify data.Text = text data.Options.Title = opts.title diff --git a/notify_unix.go b/notify_unix.go index a8ac60b..9630f8e 100644 --- a/notify_unix.go +++ b/notify_unix.go @@ -6,26 +6,26 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -func notify(text string, options []Option) error { - opts := applyOptions(options) - +func notify(text string, opts options) error { args := []string{"--notification"} if text != "" { args = append(args, "--text", text, "--no-markup") } - if opts.title != "" { - args = append(args, "--title", opts.title) + if opts.title != nil { + args = append(args, "--title", *opts.title) } switch opts.icon { + case NoIcon: + args = append(args, "--window-icon=dialog") case ErrorIcon: - args = append(args, "--window-icon=error") + args = append(args, "--window-icon=dialog-error") case WarningIcon: - args = append(args, "--window-icon=warning") + args = append(args, "--window-icon=dialog-warning") case InfoIcon: - args = append(args, "--window-icon=info") + args = append(args, "--window-icon=dialog-information") case QuestionIcon: - args = append(args, "--window-icon=question") + args = append(args, "--window-icon=dialog-question") } _, err := zenutil.Run(opts.ctx, args) diff --git a/notify_windows.go b/notify_windows.go index 266c101..d3f4afc 100644 --- a/notify_windows.go +++ b/notify_windows.go @@ -13,9 +13,7 @@ var ( wtsSendMessage = wtsapi32.NewProc("WTSSendMessageW") ) -func notify(text string, options []Option) error { - opts := applyOptions(options) - +func notify(text string, opts options) error { if opts.ctx != nil && opts.ctx.Err() != nil { return opts.ctx.Err() } @@ -29,11 +27,13 @@ func notify(text string, options []Option) error { info := syscall.StringToUTF16(text) copy(args.Info[:len(args.Info)-1], info) - title := syscall.StringToUTF16(opts.title) - copy(args.InfoTitle[:len(args.InfoTitle)-1], title) + if opts.title != nil { + title := syscall.StringToUTF16(*opts.title) + copy(args.InfoTitle[:len(args.InfoTitle)-1], title) + } switch opts.icon { - case InfoIcon: + case InfoIcon, QuestionIcon: args.InfoFlags |= 0x1 // NIIF_INFO case WarningIcon: args.InfoFlags |= 0x2 // NIIF_WARNING @@ -71,8 +71,8 @@ func wtsMessage(text string, opts options) error { } title := opts.title - if title == "" { - title = "Notification" + if title == nil { + title = stringPtr("Notification") } timeout := zenutil.Timeout @@ -81,7 +81,7 @@ func wtsMessage(text string, opts options) error { } ptext := syscall.StringToUTF16(text) - ptitle := syscall.StringToUTF16(title) + ptitle := syscall.StringToUTF16(*title) var res uint32 s, _, err := wtsSendMessage.Call( diff --git a/util_windows.go b/util_windows.go index 5aaee26..ec42e80 100644 --- a/util_windows.go +++ b/util_windows.go @@ -138,11 +138,15 @@ func hookDialog(ctx context.Context, initDialog func(wnd uintptr)) (unhook conte }, nil } -func hookDialogTitle(ctx context.Context, title string) (unhook context.CancelFunc, err error) { - return hookDialog(ctx, func(wnd uintptr) { - ptr := syscall.StringToUTF16Ptr(title) - setWindowText.Call(wnd, uintptr(unsafe.Pointer(ptr))) - }) +func hookDialogTitle(ctx context.Context, title *string) (unhook context.CancelFunc, err error) { + var init func(wnd uintptr) + if title != nil { + init = func(wnd uintptr) { + ptr := syscall.StringToUTF16Ptr(*title) + setWindowText.Call(wnd, uintptr(unsafe.Pointer(ptr))) + } + } + return hookDialog(ctx, init) } type _COMObject struct{} diff --git a/zenity.go b/zenity.go index 373a643..fce49cc 100644 --- a/zenity.go +++ b/zenity.go @@ -15,13 +15,15 @@ import ( "image/color" ) -type constError string +type stringErr string -func (e constError) Error() string { return string(e) } +func (e stringErr) Error() string { return string(e) } + +func stringPtr(s string) *string { return &s } type options struct { // General options - title string + title *string width uint height uint @@ -39,9 +41,9 @@ type options struct { // Message options icon DialogIcon - okLabel string - cancelLabel string - extraButton string + okLabel *string + cancelLabel *string + extraButton *string noWrap bool ellipsize bool defaultCancel bool @@ -69,7 +71,7 @@ func applyOptions(options []Option) (res options) { // Title returns an Option to set the dialog title. func Title(title string) Option { - return funcOption(func(o *options) { o.title = title }) + return funcOption(func(o *options) { o.title = &title }) } // Width returns an Option to set the dialog width (Unix only). @@ -95,6 +97,7 @@ const ( WarningIcon InfoIcon QuestionIcon + NoIcon ) // Icon returns an Option to set the dialog icon.