commit
4d3f930ac7
51 changed files with 1221 additions and 310 deletions
11
README.md
11
README.md
|
@ -13,14 +13,14 @@ Implemented dialogs:
|
||||||
* [text entry](https://github.com/ncruces/zenity/wiki/Text-Entry-dialog)
|
* [text entry](https://github.com/ncruces/zenity/wiki/Text-Entry-dialog)
|
||||||
* [list](https://github.com/ncruces/zenity/wiki/List-dialog) (simple)
|
* [list](https://github.com/ncruces/zenity/wiki/List-dialog) (simple)
|
||||||
* [password](https://github.com/ncruces/zenity/wiki/Password-dialog)
|
* [password](https://github.com/ncruces/zenity/wiki/Password-dialog)
|
||||||
* [file selection](https://github.com/ncruces/zenity/wiki/File-Selection-dialog)
|
* [file selection](https://github.com/ncruces/zenity/wiki/File-selection-dialog)
|
||||||
* [color selection](https://github.com/ncruces/zenity/wiki/Color-Selection-dialog)
|
* [color selection](https://github.com/ncruces/zenity/wiki/Color-selection-dialog)
|
||||||
|
* [progress](https://github.com/ncruces/zenity/wiki/Progress-dialog)
|
||||||
* [notification](https://github.com/ncruces/zenity/wiki/Notification)
|
* [notification](https://github.com/ncruces/zenity/wiki/Notification)
|
||||||
|
|
||||||
Behavior on Windows, macOS and other Unixes might differ slightly.
|
Behavior on Windows, macOS and other Unixes might differ slightly.
|
||||||
Some of that is intended (reflecting platform differences),
|
Some of that is intended (reflecting platform differences),
|
||||||
other bits are unfortunate limitations,
|
other bits are unfortunate limitations.
|
||||||
others still are open to be fixed.
|
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
|
@ -37,7 +37,8 @@ Why reinvent this particular wheel?
|
||||||
* Explorer shell not required
|
* Explorer shell not required
|
||||||
* works in Server Core
|
* works in Server Core
|
||||||
* Unicode support
|
* Unicode support
|
||||||
* High DPI support (no manifest required)
|
* High DPI (no manifest required)
|
||||||
|
* Visual Styles (no manifest required)
|
||||||
* WSL/Cygwin/MSYS2 [support](https://github.com/ncruces/zenity/wiki/Zenity-for-WSL,-Cygwin,-MSYS2)
|
* WSL/Cygwin/MSYS2 [support](https://github.com/ncruces/zenity/wiki/Zenity-for-WSL,-Cygwin,-MSYS2)
|
||||||
* on macOS:
|
* on macOS:
|
||||||
* only dependency is `osascript`
|
* only dependency is `osascript`
|
||||||
|
|
|
@ -34,6 +34,7 @@ var (
|
||||||
passwordDlg bool
|
passwordDlg bool
|
||||||
fileSelectionDlg bool
|
fileSelectionDlg bool
|
||||||
colorSelectionDlg bool
|
colorSelectionDlg bool
|
||||||
|
progressDlg bool
|
||||||
notification bool
|
notification bool
|
||||||
|
|
||||||
// General options
|
// General options
|
||||||
|
@ -73,6 +74,13 @@ var (
|
||||||
defaultColor string
|
defaultColor string
|
||||||
showPalette bool
|
showPalette bool
|
||||||
|
|
||||||
|
// Progress options
|
||||||
|
percentage float64
|
||||||
|
pulsate bool
|
||||||
|
autoClose bool
|
||||||
|
autoKill bool
|
||||||
|
noCancel bool
|
||||||
|
|
||||||
// Windows specific options
|
// Windows specific options
|
||||||
cygpath bool
|
cygpath bool
|
||||||
wslpath bool
|
wslpath bool
|
||||||
|
@ -95,32 +103,32 @@ func main() {
|
||||||
if zenutil.Timeout > 0 {
|
if zenutil.Timeout > 0 {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(zenutil.Timeout)*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(zenutil.Timeout)*time.Second)
|
||||||
opts = append(opts, zenity.Context(ctx))
|
opts = append(opts, zenity.Context(ctx))
|
||||||
_ = cancel
|
defer cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case errorDlg:
|
case errorDlg:
|
||||||
okResult(zenity.Error(text, opts...))
|
errResult(zenity.Error(text, opts...))
|
||||||
case infoDlg:
|
case infoDlg:
|
||||||
okResult(zenity.Info(text, opts...))
|
errResult(zenity.Info(text, opts...))
|
||||||
case warningDlg:
|
case warningDlg:
|
||||||
okResult(zenity.Warning(text, opts...))
|
errResult(zenity.Warning(text, opts...))
|
||||||
case questionDlg:
|
case questionDlg:
|
||||||
okResult(zenity.Question(text, opts...))
|
errResult(zenity.Question(text, opts...))
|
||||||
|
|
||||||
case entryDlg:
|
case entryDlg:
|
||||||
strOKResult(zenity.Entry(text, opts...))
|
strResult(zenity.Entry(text, opts...))
|
||||||
|
|
||||||
case listDlg:
|
case listDlg:
|
||||||
if multiple {
|
if multiple {
|
||||||
listResult(zenity.ListMultiple(text, flag.Args(), opts...))
|
lstResult(zenity.ListMultiple(text, flag.Args(), opts...))
|
||||||
} else {
|
} else {
|
||||||
strOKResult(zenity.List(text, flag.Args(), opts...))
|
strResult(zenity.List(text, flag.Args(), opts...))
|
||||||
}
|
}
|
||||||
|
|
||||||
case passwordDlg:
|
case passwordDlg:
|
||||||
_, pw, ok, err := zenity.Password(opts...)
|
_, pw, err := zenity.Password(opts...)
|
||||||
strOKResult(pw, ok, err)
|
strResult(pw, err)
|
||||||
|
|
||||||
case fileSelectionDlg:
|
case fileSelectionDlg:
|
||||||
switch {
|
switch {
|
||||||
|
@ -129,17 +137,21 @@ func main() {
|
||||||
case save:
|
case save:
|
||||||
strResult(egestPath(zenity.SelectFileSave(opts...)))
|
strResult(egestPath(zenity.SelectFileSave(opts...)))
|
||||||
case multiple:
|
case multiple:
|
||||||
listResult(egestPaths(zenity.SelectFileMutiple(opts...)))
|
lstResult(egestPaths(zenity.SelectFileMutiple(opts...)))
|
||||||
}
|
}
|
||||||
|
|
||||||
case colorSelectionDlg:
|
case colorSelectionDlg:
|
||||||
colorResult(zenity.SelectColor(opts...))
|
colResult(zenity.SelectColor(opts...))
|
||||||
|
|
||||||
|
case progressDlg:
|
||||||
|
errResult(progress(opts...))
|
||||||
|
|
||||||
case notification:
|
case notification:
|
||||||
errResult(zenity.Notify(text, opts...))
|
errResult(zenity.Notify(text, opts...))
|
||||||
}
|
|
||||||
|
|
||||||
|
default:
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupFlags() {
|
func setupFlags() {
|
||||||
|
@ -153,6 +165,7 @@ func setupFlags() {
|
||||||
flag.BoolVar(&passwordDlg, "password", false, "Display password dialog")
|
flag.BoolVar(&passwordDlg, "password", false, "Display password dialog")
|
||||||
flag.BoolVar(&fileSelectionDlg, "file-selection", false, "Display file selection dialog")
|
flag.BoolVar(&fileSelectionDlg, "file-selection", false, "Display file selection dialog")
|
||||||
flag.BoolVar(&colorSelectionDlg, "color-selection", false, "Display color selection dialog")
|
flag.BoolVar(&colorSelectionDlg, "color-selection", false, "Display color selection dialog")
|
||||||
|
flag.BoolVar(&progressDlg, "progress", false, "Display progress indication dialog")
|
||||||
flag.BoolVar(¬ification, "notification", false, "Display notification")
|
flag.BoolVar(¬ification, "notification", false, "Display notification")
|
||||||
|
|
||||||
// General options
|
// General options
|
||||||
|
@ -165,12 +178,12 @@ func setupFlags() {
|
||||||
flag.StringVar(&text, "text", "", "Set the dialog `text`")
|
flag.StringVar(&text, "text", "", "Set the dialog `text`")
|
||||||
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)")
|
||||||
flag.BoolVar(&multiple, "multiple", false, "Allow multiple items to be selected")
|
flag.BoolVar(&multiple, "multiple", false, "Allow multiple items to be selected")
|
||||||
|
flag.BoolVar(&defaultCancel, "default-cancel", false, "Give Cancel button focus by default")
|
||||||
|
|
||||||
// Message options
|
// Message options
|
||||||
flag.StringVar(&icon, "icon-name", "", "Set the dialog `icon` (dialog-error, dialog-information, dialog-question, dialog-warning)")
|
flag.StringVar(&icon, "icon-name", "", "Set the dialog `icon` (dialog-error, dialog-information, dialog-question, dialog-warning)")
|
||||||
flag.BoolVar(&noWrap, "no-wrap", false, "Do not enable text wrapping")
|
flag.BoolVar(&noWrap, "no-wrap", false, "Do not enable text wrapping")
|
||||||
flag.BoolVar(&ellipsize, "ellipsize", false, "Enable ellipsizing in the dialog text")
|
flag.BoolVar(&ellipsize, "ellipsize", false, "Enable ellipsizing in the dialog text")
|
||||||
flag.BoolVar(&defaultCancel, "default-cancel", false, "Give Cancel button focus by default")
|
|
||||||
|
|
||||||
// Entry options
|
// Entry options
|
||||||
flag.StringVar(&entryText, "entry-text", "", "Set the entry `text`")
|
flag.StringVar(&entryText, "entry-text", "", "Set the entry `text`")
|
||||||
|
@ -194,6 +207,15 @@ func setupFlags() {
|
||||||
flag.StringVar(&defaultColor, "color", "", "Set the `color`")
|
flag.StringVar(&defaultColor, "color", "", "Set the `color`")
|
||||||
flag.BoolVar(&showPalette, "show-palette", false, "Show the palette")
|
flag.BoolVar(&showPalette, "show-palette", false, "Show the palette")
|
||||||
|
|
||||||
|
// Progress options
|
||||||
|
flag.Float64Var(&percentage, "percentage", 0, "Set initial `percentage`")
|
||||||
|
flag.BoolVar(&pulsate, "pulsate", false, "Pulsate progress bar")
|
||||||
|
flag.BoolVar(&noCancel, "no-cancel", false, "Hide Cancel button (Windows and Unix only)")
|
||||||
|
flag.BoolVar(&autoClose, "auto-close", false, "Dismiss the dialog when 100% has been reached")
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
flag.BoolVar(&autoKill, "auto-kill", false, "Kill parent process if Cancel button is pressed (macOS and Unix only)")
|
||||||
|
}
|
||||||
|
|
||||||
// Windows specific options
|
// Windows specific options
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
flag.BoolVar(&cygpath, "cygpath", false, "Use cygpath for path translation (Windows only)")
|
flag.BoolVar(&cygpath, "cygpath", false, "Use cygpath for path translation (Windows only)")
|
||||||
|
@ -242,6 +264,9 @@ func validateFlags() {
|
||||||
if colorSelectionDlg {
|
if colorSelectionDlg {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
if progressDlg {
|
||||||
|
n++
|
||||||
|
}
|
||||||
if notification {
|
if notification {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
@ -297,6 +322,11 @@ func loadFlags() []zenity.Option {
|
||||||
setDefault(&icon, "dialog-password")
|
setDefault(&icon, "dialog-password")
|
||||||
setDefault(&okLabel, "OK")
|
setDefault(&okLabel, "OK")
|
||||||
setDefault(&cancelLabel, "Cancel")
|
setDefault(&cancelLabel, "Cancel")
|
||||||
|
case progressDlg:
|
||||||
|
setDefault(&title, "Progress")
|
||||||
|
setDefault(&text, "Running...")
|
||||||
|
setDefault(&okLabel, "OK")
|
||||||
|
setDefault(&cancelLabel, "Cancel")
|
||||||
default:
|
default:
|
||||||
setDefault(&text, "")
|
setDefault(&text, "")
|
||||||
}
|
}
|
||||||
|
@ -388,6 +418,15 @@ func loadFlags() []zenity.Option {
|
||||||
opts = append(opts, zenity.ShowPalette())
|
opts = append(opts, zenity.ShowPalette())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Progress options
|
||||||
|
|
||||||
|
if pulsate {
|
||||||
|
opts = append(opts, zenity.Pulsate())
|
||||||
|
}
|
||||||
|
if noCancel {
|
||||||
|
opts = append(opts, zenity.NoCancel())
|
||||||
|
}
|
||||||
|
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,6 +434,9 @@ func errResult(err error) {
|
||||||
if os.IsTimeout(err) {
|
if os.IsTimeout(err) {
|
||||||
os.Exit(5)
|
os.Exit(5)
|
||||||
}
|
}
|
||||||
|
if err == zenity.ErrCanceled {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
if err == zenity.ErrExtraButton {
|
if err == zenity.ErrExtraButton {
|
||||||
os.Stdout.WriteString(extraButton)
|
os.Stdout.WriteString(extraButton)
|
||||||
os.Stdout.WriteString(zenutil.LineBreak)
|
os.Stdout.WriteString(zenutil.LineBreak)
|
||||||
|
@ -408,64 +450,33 @@ func errResult(err error) {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func okResult(ok bool, err error) {
|
|
||||||
if err != nil {
|
|
||||||
errResult(err)
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func strResult(s string, err error) {
|
func strResult(s string, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errResult(err)
|
errResult(err)
|
||||||
}
|
}
|
||||||
if s == "" {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Stdout.WriteString(s)
|
os.Stdout.WriteString(s)
|
||||||
os.Stdout.WriteString(zenutil.LineBreak)
|
os.Stdout.WriteString(zenutil.LineBreak)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listResult(l []string, err error) {
|
func lstResult(l []string, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errResult(err)
|
errResult(err)
|
||||||
}
|
}
|
||||||
if l == nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Stdout.WriteString(strings.Join(l, zenutil.Separator))
|
os.Stdout.WriteString(strings.Join(l, zenutil.Separator))
|
||||||
os.Stdout.WriteString(zenutil.LineBreak)
|
os.Stdout.WriteString(zenutil.LineBreak)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func colorResult(c color.Color, err error) {
|
func colResult(c color.Color, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errResult(err)
|
errResult(err)
|
||||||
}
|
}
|
||||||
if c == nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Stdout.WriteString(zenutil.UnparseColor(c))
|
os.Stdout.WriteString(zenutil.UnparseColor(c))
|
||||||
os.Stdout.WriteString(zenutil.LineBreak)
|
os.Stdout.WriteString(zenutil.LineBreak)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func strOKResult(s string, ok bool, err error) {
|
|
||||||
if err != nil {
|
|
||||||
errResult(err)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Stdout.WriteString(s)
|
|
||||||
os.Stdout.WriteString(zenutil.LineBreak)
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ingestPath(path string) string {
|
func ingestPath(path string) string {
|
||||||
if runtime.GOOS == "windows" && path != "" {
|
if runtime.GOOS == "windows" && path != "" {
|
||||||
var args []string
|
var args []string
|
||||||
|
|
72
cmd/zenity/progress.go
Normal file
72
cmd/zenity/progress.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ncruces/zenity"
|
||||||
|
)
|
||||||
|
|
||||||
|
func progress(opts ...zenity.Option) (err error) {
|
||||||
|
dlg, err := zenity.Progress(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if autoKill {
|
||||||
|
defer func() {
|
||||||
|
if err == zenity.ErrCanceled {
|
||||||
|
killParent()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dlg.Text(text); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := dlg.Value(int(math.Round(percentage))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := make(chan string)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(lines)
|
||||||
|
for scanner := bufio.NewScanner(os.Stdin); scanner.Scan(); {
|
||||||
|
lines <- scanner.Text()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case line, ok := <-lines:
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(line) > 1 && line[0] == '#' {
|
||||||
|
if err := dlg.Text(strings.TrimSpace(line[1:])); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if v, err := strconv.ParseFloat(line, 64); err == nil {
|
||||||
|
if err := dlg.Value(int(math.Round(v))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if v >= 100 && autoClose {
|
||||||
|
return dlg.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case <-dlg.Done():
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dlg.Complete(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
<-dlg.Done()
|
||||||
|
return dlg.Close()
|
||||||
|
}
|
12
cmd/zenity/progress_unix.go
Normal file
12
cmd/zenity/progress_unix.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// +build !windows,!js
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func killParent() {
|
||||||
|
syscall.Kill(os.Getppid(), syscall.SIGHUP)
|
||||||
|
}
|
3
cmd/zenity/progress_windows.go
Normal file
3
cmd/zenity/progress_windows.go
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
func killParent() {}
|
2
color.go
2
color.go
|
@ -4,8 +4,6 @@ import "image/color"
|
||||||
|
|
||||||
// SelectColor displays the color selection dialog.
|
// SelectColor displays the color selection dialog.
|
||||||
//
|
//
|
||||||
// Returns nil on cancel.
|
|
||||||
//
|
|
||||||
// Valid options: Title, Color, ShowPalette.
|
// Valid options: Title, Color, ShowPalette.
|
||||||
func SelectColor(options ...Option) (color.Color, error) {
|
func SelectColor(options ...Option) (color.Color, error) {
|
||||||
return selectColor(applyOptions(options))
|
return selectColor(applyOptions(options))
|
||||||
|
|
|
@ -20,9 +20,9 @@ func selectColor(opts options) (color.Color, error) {
|
||||||
float32(g) / 0xffff,
|
float32(g) / 0xffff,
|
||||||
float32(b) / 0xffff,
|
float32(b) / 0xffff,
|
||||||
})
|
})
|
||||||
str, ok, err := strResult(opts, out, err)
|
str, err := strResult(opts, out, err)
|
||||||
if ok {
|
if err != nil {
|
||||||
return zenutil.ParseColor(str), nil
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
|
}
|
||||||
|
return zenutil.ParseColor(str), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ncruces/zenity"
|
"github.com/ncruces/zenity"
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleSelectColor() {
|
func ExampleSelectColor() {
|
||||||
|
@ -24,18 +25,19 @@ func ExampleSelectColor_palette() {
|
||||||
// Output:
|
// Output:
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelectColorTimeout(t *testing.T) {
|
func TestSelectColor_timeout(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
_, err := zenity.SelectColor(zenity.Context(ctx))
|
_, err := zenity.SelectColor(zenity.Context(ctx))
|
||||||
if !os.IsTimeout(err) {
|
if !os.IsTimeout(err) {
|
||||||
t.Error("did not timeout:", err)
|
t.Error("did not timeout:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelectColorCancel(t *testing.T) {
|
func TestSelectColor_cancel(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,9 @@ func selectColor(opts options) (color.Color, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := zenutil.Run(opts.ctx, args)
|
out, err := zenutil.Run(opts.ctx, args)
|
||||||
str, ok, err := strResult(opts, out, err)
|
str, err := strResult(opts, out, err)
|
||||||
if ok {
|
if err != nil {
|
||||||
return zenutil.ParseColor(str), nil
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
|
}
|
||||||
|
return zenutil.ParseColor(str), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
var (
|
var (
|
||||||
chooseColor = comdlg32.NewProc("ChooseColorW")
|
chooseColor = comdlg32.NewProc("ChooseColorW")
|
||||||
|
|
||||||
savedColors = [16]uint32{}
|
savedColors [16]uint32
|
||||||
colorsMutex sync.Mutex
|
colorsMutex sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ func selectColor(opts options) (color.Color, error) {
|
||||||
if opts.color != nil {
|
if opts.color != nil {
|
||||||
args.Flags |= 0x1 // CC_RGBINIT
|
args.Flags |= 0x1 // CC_RGBINIT
|
||||||
n := color.NRGBAModel.Convert(opts.color).(color.NRGBA)
|
n := color.NRGBAModel.Convert(opts.color).(color.NRGBA)
|
||||||
args.RgbResult = uint32(n.R) | (uint32(n.G) << 8) | (uint32(n.B) << 16)
|
args.RgbResult = uint32(n.R) | uint32(n.G)<<8 | uint32(n.B)<<16
|
||||||
}
|
}
|
||||||
if opts.showPalette {
|
if opts.showPalette {
|
||||||
args.Flags |= 0x4 // CC_PREVENTFULLOPEN
|
args.Flags |= 0x4 // CC_PREVENTFULLOPEN
|
||||||
|
|
4
entry.go
4
entry.go
|
@ -2,11 +2,9 @@ package zenity
|
||||||
|
|
||||||
// Entry displays the text entry dialog.
|
// Entry displays the text entry dialog.
|
||||||
//
|
//
|
||||||
// Returns false on cancel, or ErrExtraButton.
|
|
||||||
//
|
|
||||||
// Valid options: Title, Width, Height, OKLabel, CancelLabel, ExtraButton,
|
// Valid options: Title, Width, Height, OKLabel, CancelLabel, ExtraButton,
|
||||||
// Icon, EntryText, HideText.
|
// Icon, EntryText, HideText.
|
||||||
func Entry(text string, options ...Option) (string, bool, error) {
|
func Entry(text string, options ...Option) (string, error) {
|
||||||
return entry(text, applyOptions(options))
|
return entry(text, applyOptions(options))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"github.com/ncruces/zenity/internal/zenutil"
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func entry(text string, opts options) (string, bool, error) {
|
func entry(text string, opts options) (string, error) {
|
||||||
var data zenutil.Dialog
|
var data zenutil.Dialog
|
||||||
data.Text = text
|
data.Text = text
|
||||||
data.Operation = "displayDialog"
|
data.Operation = "displayDialog"
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ncruces/zenity"
|
"github.com/ncruces/zenity"
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleEntry() {
|
func ExampleEntry() {
|
||||||
|
@ -16,22 +17,23 @@ func ExampleEntry() {
|
||||||
// Output:
|
// Output:
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEntryTimeout(t *testing.T) {
|
func TestEntry_timeout(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
_, _, err := zenity.Entry("", zenity.Context(ctx))
|
_, err := zenity.Entry("", zenity.Context(ctx))
|
||||||
if !os.IsTimeout(err) {
|
if !os.IsTimeout(err) {
|
||||||
t.Error("did not timeout:", err)
|
t.Error("did not timeout:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEntryCancel(t *testing.T) {
|
func TestEntry_cancel(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
_, _, err := zenity.Entry("", zenity.Context(ctx))
|
_, err := zenity.Entry("", zenity.Context(ctx))
|
||||||
if !errors.Is(err, context.Canceled) {
|
if !errors.Is(err, context.Canceled) {
|
||||||
t.Error("was not canceled:", err)
|
t.Error("was not canceled:", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/ncruces/zenity/internal/zenutil"
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func entry(text string, opts options) (string, bool, error) {
|
func entry(text string, opts options) (string, error) {
|
||||||
args := []string{"--entry", "--text", text}
|
args := []string{"--entry", "--text", text}
|
||||||
args = appendTitle(args, opts)
|
args = appendTitle(args, opts)
|
||||||
args = appendButtons(args, opts)
|
args = appendButtons(args, opts)
|
||||||
|
|
|
@ -4,10 +4,9 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func entry(text string, opts options) (out string, ok bool, err error) {
|
func entry(text string, opts options) (out string, err error) {
|
||||||
var title string
|
if opts.title == nil {
|
||||||
if opts.title != nil {
|
opts.title = stringPtr("")
|
||||||
title = *opts.title
|
|
||||||
}
|
}
|
||||||
if opts.okLabel == nil {
|
if opts.okLabel == nil {
|
||||||
opts.okLabel = stringPtr("OK")
|
opts.okLabel = stringPtr("OK")
|
||||||
|
@ -15,10 +14,7 @@ func entry(text string, opts options) (out string, ok bool, err error) {
|
||||||
if opts.cancelLabel == nil {
|
if opts.cancelLabel == nil {
|
||||||
opts.cancelLabel = stringPtr("Cancel")
|
opts.cancelLabel = stringPtr("Cancel")
|
||||||
}
|
}
|
||||||
return entryDlg(title, text, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func entryDlg(title, text string, opts options) (out string, ok bool, err error) {
|
|
||||||
defer setup()()
|
defer setup()()
|
||||||
font := getFont()
|
font := getFont()
|
||||||
defer font.Delete()
|
defer font.Delete()
|
||||||
|
@ -33,6 +29,7 @@ func entryDlg(title, text string, opts options) (out string, ok bool, err error)
|
||||||
sendMessage.Call(editCtl, 0x0030 /* WM_SETFONT */, hfont, 1)
|
sendMessage.Call(editCtl, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
sendMessage.Call(okBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
sendMessage.Call(okBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
sendMessage.Call(cancelBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
sendMessage.Call(cancelBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
|
sendMessage.Call(extraBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
setWindowPos.Call(wnd, 0, 0, 0, dpi.Scale(281), dpi.Scale(141), 0x6) // SWP_NOZORDER|SWP_NOMOVE
|
setWindowPos.Call(wnd, 0, 0, 0, dpi.Scale(281), dpi.Scale(141), 0x6) // SWP_NOZORDER|SWP_NOMOVE
|
||||||
setWindowPos.Call(textCtl, 0, dpi.Scale(12), dpi.Scale(10), dpi.Scale(241), dpi.Scale(16), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(textCtl, 0, dpi.Scale(12), dpi.Scale(10), dpi.Scale(241), dpi.Scale(16), 0x4) // SWP_NOZORDER
|
||||||
setWindowPos.Call(editCtl, 0, dpi.Scale(12), dpi.Scale(30), dpi.Scale(241), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(editCtl, 0, dpi.Scale(12), dpi.Scale(30), dpi.Scale(241), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
|
@ -40,7 +37,6 @@ func entryDlg(title, text string, opts options) (out string, ok bool, err error)
|
||||||
setWindowPos.Call(okBtn, 0, dpi.Scale(95), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(okBtn, 0, dpi.Scale(95), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
setWindowPos.Call(cancelBtn, 0, dpi.Scale(178), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(cancelBtn, 0, dpi.Scale(178), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
} else {
|
} else {
|
||||||
sendMessage.Call(extraBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
|
||||||
setWindowPos.Call(okBtn, 0, dpi.Scale(12), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(okBtn, 0, dpi.Scale(12), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
setWindowPos.Call(extraBtn, 0, dpi.Scale(95), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(extraBtn, 0, dpi.Scale(95), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
setWindowPos.Call(cancelBtn, 0, dpi.Scale(178), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(cancelBtn, 0, dpi.Scale(178), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
|
@ -53,6 +49,7 @@ func entryDlg(title, text string, opts options) (out string, ok bool, err error)
|
||||||
postQuitMessage.Call(0)
|
postQuitMessage.Call(0)
|
||||||
|
|
||||||
case 0x0010: // WM_CLOSE
|
case 0x0010: // WM_CLOSE
|
||||||
|
err = ErrCanceled
|
||||||
destroyWindow.Call(wnd)
|
destroyWindow.Call(wnd)
|
||||||
|
|
||||||
case 0x0111: // WM_COMMAND
|
case 0x0111: // WM_COMMAND
|
||||||
|
@ -61,8 +58,8 @@ func entryDlg(title, text string, opts options) (out string, ok bool, err error)
|
||||||
return 1
|
return 1
|
||||||
case 1, 6: // IDOK, IDYES
|
case 1, 6: // IDOK, IDYES
|
||||||
out = getWindowString(editCtl)
|
out = getWindowString(editCtl)
|
||||||
ok = true
|
|
||||||
case 2: // IDCANCEL
|
case 2: // IDCANCEL
|
||||||
|
err = ErrCanceled
|
||||||
case 7: // IDNO
|
case 7: // IDNO
|
||||||
err = ErrExtraButton
|
err = ErrExtraButton
|
||||||
}
|
}
|
||||||
|
@ -72,39 +69,39 @@ func entryDlg(title, text string, opts options) (out string, ok bool, err error)
|
||||||
layout(dpi(uint32(wparam) >> 16))
|
layout(dpi(uint32(wparam) >> 16))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ret, _, _ := syscall.Syscall6(defWindowProc, 4, wnd, uintptr(msg), wparam, lparam, 0, 0)
|
res, _, _ := syscall.Syscall6(defWindowProc, 4, wnd, uintptr(msg), wparam, lparam, 0, 0)
|
||||||
return ret
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.ctx != nil && opts.ctx.Err() != nil {
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
return "", false, opts.ctx.Err()
|
return "", opts.ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
instance, _, err := getModuleHandle.Call(0)
|
instance, _, err := getModuleHandle.Call(0)
|
||||||
if instance == 0 {
|
if instance == 0 {
|
||||||
return "", false, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
cls, err := registerClass(instance, syscall.NewCallback(proc))
|
cls, err := registerClass(instance, syscall.NewCallback(proc))
|
||||||
if cls == 0 {
|
if cls == 0 {
|
||||||
return "", false, err
|
return "", err
|
||||||
}
|
}
|
||||||
defer unregisterClass.Call(cls, instance)
|
defer unregisterClass.Call(cls, instance)
|
||||||
|
|
||||||
wnd, _, _ = createWindowEx.Call(0x10101, // WS_EX_CONTROLPARENT|WS_EX_WINDOWEDGE|WS_EX_DLGMODALFRAME
|
wnd, _, _ = createWindowEx.Call(0x10101, // WS_EX_CONTROLPARENT|WS_EX_WINDOWEDGE|WS_EX_DLGMODALFRAME
|
||||||
cls, strptr(title),
|
cls, strptr(*opts.title),
|
||||||
0x84c80000, // WS_POPUPWINDOW|WS_CLIPSIBLINGS|WS_DLGFRAME
|
0x84c80000, // WS_POPUPWINDOW|WS_CLIPSIBLINGS|WS_DLGFRAME
|
||||||
0x80000000, // CW_USEDEFAULT
|
0x80000000, // CW_USEDEFAULT
|
||||||
0x80000000, // CW_USEDEFAULT
|
0x80000000, // CW_USEDEFAULT
|
||||||
281, 141, 0, 0, instance)
|
281, 141, 0, 0, instance, 0)
|
||||||
|
|
||||||
textCtl, _, _ = createWindowEx.Call(0,
|
textCtl, _, _ = createWindowEx.Call(0,
|
||||||
strptr("STATIC"), strptr(text),
|
strptr("STATIC"), strptr(text),
|
||||||
0x5002e080, // WS_CHILD|WS_VISIBLE|WS_GROUP|SS_WORDELLIPSIS|SS_EDITCONTROL|SS_NOPREFIX
|
0x5002e080, // WS_CHILD|WS_VISIBLE|WS_GROUP|SS_WORDELLIPSIS|SS_EDITCONTROL|SS_NOPREFIX
|
||||||
12, 10, 241, 16, wnd, 0, instance)
|
12, 10, 241, 16, wnd, 0, instance, 0)
|
||||||
|
|
||||||
var flags uintptr = 0x50030080 // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP|ES_AUTOHSCROLL
|
var flags uintptr = 0x50030080 // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP|ES_AUTOHSCROLL
|
||||||
if opts.hideText {
|
if opts.hideText {
|
||||||
|
@ -113,21 +110,21 @@ func entryDlg(title, text string, opts options) (out string, ok bool, err error)
|
||||||
editCtl, _, _ = createWindowEx.Call(0x200, // WS_EX_CLIENTEDGE
|
editCtl, _, _ = createWindowEx.Call(0x200, // WS_EX_CLIENTEDGE
|
||||||
strptr("EDIT"), strptr(opts.entryText),
|
strptr("EDIT"), strptr(opts.entryText),
|
||||||
flags,
|
flags,
|
||||||
12, 30, 241, 24, wnd, 0, instance)
|
12, 30, 241, 24, wnd, 0, instance, 0)
|
||||||
|
|
||||||
okBtn, _, _ = createWindowEx.Call(0,
|
okBtn, _, _ = createWindowEx.Call(0,
|
||||||
strptr("BUTTON"), strptr(*opts.okLabel),
|
strptr("BUTTON"), strptr(*opts.okLabel),
|
||||||
0x50030001, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP|BS_DEFPUSHBUTTON
|
0x50030001, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP|BS_DEFPUSHBUTTON
|
||||||
12, 66, 75, 24, wnd, 1 /* IDOK */, instance)
|
12, 66, 75, 24, wnd, 1 /* IDOK */, instance, 0)
|
||||||
cancelBtn, _, _ = createWindowEx.Call(0,
|
cancelBtn, _, _ = createWindowEx.Call(0,
|
||||||
strptr("BUTTON"), strptr(*opts.cancelLabel),
|
strptr("BUTTON"), strptr(*opts.cancelLabel),
|
||||||
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
|
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
|
||||||
12, 66, 75, 24, wnd, 2 /* IDCANCEL */, instance)
|
12, 66, 75, 24, wnd, 2 /* IDCANCEL */, instance, 0)
|
||||||
if opts.extraButton != nil {
|
if opts.extraButton != nil {
|
||||||
extraBtn, _, _ = createWindowEx.Call(0,
|
extraBtn, _, _ = createWindowEx.Call(0,
|
||||||
strptr("BUTTON"), strptr(*opts.extraButton),
|
strptr("BUTTON"), strptr(*opts.extraButton),
|
||||||
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
|
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
|
||||||
12, 66, 75, 24, wnd, 7 /* IDNO */, instance)
|
12, 66, 75, 24, wnd, 7 /* IDNO */, instance, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
layout(getDPI(wnd))
|
layout(getDPI(wnd))
|
||||||
|
@ -149,13 +146,13 @@ func entryDlg(title, text string, opts options) (out string, ok bool, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set default values
|
// set default values
|
||||||
out, ok, err = "", false, nil
|
out, err = "", nil
|
||||||
|
|
||||||
if err := messageLoop(wnd); err != nil {
|
if err := messageLoop(wnd); err != nil {
|
||||||
return "", false, err
|
return "", err
|
||||||
}
|
}
|
||||||
if opts.ctx != nil && opts.ctx.Err() != nil {
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
return "", false, opts.ctx.Err()
|
return "", opts.ctx.Err()
|
||||||
}
|
}
|
||||||
return out, ok, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
9
file.go
9
file.go
|
@ -8,8 +8,6 @@ import (
|
||||||
|
|
||||||
// SelectFile displays the file selection dialog.
|
// SelectFile displays the file selection dialog.
|
||||||
//
|
//
|
||||||
// Returns an empty string on cancel.
|
|
||||||
//
|
|
||||||
// Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s).
|
// Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s).
|
||||||
func SelectFile(options ...Option) (string, error) {
|
func SelectFile(options ...Option) (string, error) {
|
||||||
return selectFile(applyOptions(options))
|
return selectFile(applyOptions(options))
|
||||||
|
@ -17,8 +15,6 @@ func SelectFile(options ...Option) (string, error) {
|
||||||
|
|
||||||
// SelectFileMutiple displays the multiple file selection dialog.
|
// SelectFileMutiple displays the multiple file selection dialog.
|
||||||
//
|
//
|
||||||
// Returns a nil slice on cancel.
|
|
||||||
//
|
|
||||||
// Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s).
|
// Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s).
|
||||||
func SelectFileMutiple(options ...Option) ([]string, error) {
|
func SelectFileMutiple(options ...Option) ([]string, error) {
|
||||||
return selectFileMutiple(applyOptions(options))
|
return selectFileMutiple(applyOptions(options))
|
||||||
|
@ -26,8 +22,6 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
|
||||||
|
|
||||||
// SelectFileSave displays the save file selection dialog.
|
// SelectFileSave displays the save file selection dialog.
|
||||||
//
|
//
|
||||||
// Returns an empty string on cancel.
|
|
||||||
//
|
|
||||||
// Valid options: Title, Filename, ConfirmOverwrite, ConfirmCreate, ShowHidden,
|
// Valid options: Title, Filename, ConfirmOverwrite, ConfirmCreate, ShowHidden,
|
||||||
// FileFilter(s).
|
// FileFilter(s).
|
||||||
func SelectFileSave(options ...Option) (string, error) {
|
func SelectFileSave(options ...Option) (string, error) {
|
||||||
|
@ -69,6 +63,9 @@ func Filename(filename string) Option {
|
||||||
//
|
//
|
||||||
// macOS hides filename filters from the user,
|
// macOS hides filename filters from the user,
|
||||||
// and only supports filtering by extension (or "type").
|
// and only supports filtering by extension (or "type").
|
||||||
|
//
|
||||||
|
// Patterns may use the GTK syntax on all platforms:
|
||||||
|
// https://developer.gnome.org/pygtk/stable/class-gtkfilefilter.html#method-gtkfilefilter--add-pattern
|
||||||
type FileFilter struct {
|
type FileFilter struct {
|
||||||
Name string // display string that describes the filter (optional)
|
Name string // display string that describes the filter (optional)
|
||||||
Patterns []string // filter patterns for the display string
|
Patterns []string // filter patterns for the display string
|
||||||
|
|
|
@ -18,8 +18,7 @@ func selectFile(opts options) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := zenutil.Run(opts.ctx, "file", data)
|
out, err := zenutil.Run(opts.ctx, "file", data)
|
||||||
str, _, err := strResult(opts, out, err)
|
return strResult(opts, out, err)
|
||||||
return str, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func selectFileMutiple(opts options) ([]string, error) {
|
func selectFileMutiple(opts options) ([]string, error) {
|
||||||
|
@ -54,6 +53,5 @@ func selectFileSave(opts options) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := zenutil.Run(opts.ctx, "file", data)
|
out, err := zenutil.Run(opts.ctx, "file", data)
|
||||||
str, _, err := strResult(opts, out, err)
|
return strResult(opts, out, err)
|
||||||
return str, err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ncruces/zenity"
|
"github.com/ncruces/zenity"
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultPath = ``
|
const defaultPath = ``
|
||||||
|
@ -77,7 +78,7 @@ var fileFuncs = []func(...zenity.Option) (string, error){
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileTimeout(t *testing.T) {
|
func TestFile_timeout(t *testing.T) {
|
||||||
for _, f := range fileFuncs {
|
for _, f := range fileFuncs {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
||||||
|
|
||||||
|
@ -87,10 +88,12 @@ func TestFileTimeout(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
|
goleak.VerifyNone(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileCancel(t *testing.T) {
|
func TestFile_cancel(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,7 @@ func selectFile(opts options) (string, error) {
|
||||||
args = appendFileArgs(args, opts)
|
args = appendFileArgs(args, opts)
|
||||||
|
|
||||||
out, err := zenutil.Run(opts.ctx, args)
|
out, err := zenutil.Run(opts.ctx, args)
|
||||||
str, _, err := strResult(opts, out, err)
|
return strResult(opts, out, err)
|
||||||
return str, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func selectFileMutiple(opts options) ([]string, error) {
|
func selectFileMutiple(opts options) ([]string, error) {
|
||||||
|
@ -33,8 +32,7 @@ func selectFileSave(opts options) (string, error) {
|
||||||
args = appendFileArgs(args, opts)
|
args = appendFileArgs(args, opts)
|
||||||
|
|
||||||
out, err := zenutil.Run(opts.ctx, args)
|
out, err := zenutil.Run(opts.ctx, args)
|
||||||
str, _, err := strResult(opts, out, err)
|
return strResult(opts, out, err)
|
||||||
return str, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initFilters(filters []FileFilter) []string {
|
func initFilters(filters []FileFilter) []string {
|
||||||
|
|
|
@ -37,7 +37,7 @@ func selectFile(opts options) (string, error) {
|
||||||
args.Filter = &initFilters(opts.fileFilters)[0]
|
args.Filter = &initFilters(opts.fileFilters)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
res := [32768]uint16{}
|
var res [32768]uint16
|
||||||
args.File = &res[0]
|
args.File = &res[0]
|
||||||
args.MaxFile = uint32(len(res))
|
args.MaxFile = uint32(len(res))
|
||||||
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
|
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
|
||||||
|
@ -82,7 +82,7 @@ func selectFileMutiple(opts options) ([]string, error) {
|
||||||
args.Filter = &initFilters(opts.fileFilters)[0]
|
args.Filter = &initFilters(opts.fileFilters)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
res := [32768 + 1024*256]uint16{}
|
var res [32768 + 1024*256]uint16
|
||||||
args.File = &res[0]
|
args.File = &res[0]
|
||||||
args.MaxFile = uint32(len(res))
|
args.MaxFile = uint32(len(res))
|
||||||
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
|
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
|
||||||
|
@ -158,7 +158,7 @@ func selectFileSave(opts options) (string, error) {
|
||||||
args.Filter = &initFilters(opts.fileFilters)[0]
|
args.Filter = &initFilters(opts.fileFilters)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
res := [32768]uint16{}
|
var res [32768]uint16
|
||||||
args.File = &res[0]
|
args.File = &res[0]
|
||||||
args.MaxFile = uint32(len(res))
|
args.MaxFile = uint32(len(res))
|
||||||
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
|
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
|
||||||
|
@ -251,7 +251,7 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error)
|
||||||
return "", nil, opts.ctx.Err()
|
return "", nil, opts.ctx.Err()
|
||||||
}
|
}
|
||||||
if hr == 0x800704c7 { // ERROR_CANCELLED
|
if hr == 0x800704c7 { // ERROR_CANCELLED
|
||||||
return "", nil, nil
|
return "", nil, ErrCanceled
|
||||||
}
|
}
|
||||||
if int32(hr) < 0 {
|
if int32(hr) < 0 {
|
||||||
return "", nil, syscall.Errno(hr)
|
return "", nil, syscall.Errno(hr)
|
||||||
|
@ -335,11 +335,11 @@ func browseForFolder(opts options) (string, []string, error) {
|
||||||
return "", nil, opts.ctx.Err()
|
return "", nil, opts.ctx.Err()
|
||||||
}
|
}
|
||||||
if ptr == 0 {
|
if ptr == 0 {
|
||||||
return "", nil, nil
|
return "", nil, ErrCanceled
|
||||||
}
|
}
|
||||||
defer coTaskMemFree.Call(ptr)
|
defer coTaskMemFree.Call(ptr)
|
||||||
|
|
||||||
res := [32768]uint16{}
|
var res [32768]uint16
|
||||||
shGetPathFromIDListEx.Call(ptr, uintptr(unsafe.Pointer(&res[0])), uintptr(len(res)), 0)
|
shGetPathFromIDListEx.Call(ptr, uintptr(unsafe.Pointer(&res[0])), uintptr(len(res)), 0)
|
||||||
|
|
||||||
str := syscall.UTF16ToString(res[:])
|
str := syscall.UTF16ToString(res[:])
|
||||||
|
|
12
internal/zenutil/env.go
Normal file
12
internal/zenutil/env.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package zenutil
|
||||||
|
|
||||||
|
// These are internal.
|
||||||
|
const (
|
||||||
|
ErrCanceled = stringErr("dialog canceled")
|
||||||
|
ErrExtraButton = stringErr("extra button pressed")
|
||||||
|
ErrUnsupported = stringErr("unsupported option")
|
||||||
|
)
|
||||||
|
|
||||||
|
type stringErr string
|
||||||
|
|
||||||
|
func (e stringErr) Error() string { return string(e) }
|
|
@ -48,4 +48,25 @@ res.join({{json .Separator}})
|
||||||
var app=Application.currentApplication()
|
var app=Application.currentApplication()
|
||||||
app.includeStandardAdditions=true
|
app.includeStandardAdditions=true
|
||||||
void app.displayNotification({{json .Text}},{{json .Options}})
|
void app.displayNotification({{json .Text}},{{json .Options}})
|
||||||
|
{{- end}}
|
||||||
|
{{define "progress" -}}
|
||||||
|
var app=Application.currentApplication()
|
||||||
|
app.includeStandardAdditions=true
|
||||||
|
app.activate()
|
||||||
|
ObjC.import('stdlib')
|
||||||
|
ObjC.import('readline')
|
||||||
|
{{- if .Total}}
|
||||||
|
Progress.totalUnitCount={{.Total}}
|
||||||
|
{{- end}}
|
||||||
|
{{- if .Description}}
|
||||||
|
Progress.description={{json .Description}}
|
||||||
|
{{- end}}
|
||||||
|
while(true){var s
|
||||||
|
try{s=$.readline('')}catch(e){if(e.errorNumber===-128)$.exit(1)
|
||||||
|
break}
|
||||||
|
if(s.indexOf('#')===0){Progress.additionalDescription=s.slice(1)
|
||||||
|
continue}
|
||||||
|
var i=parseInt(s)
|
||||||
|
if(i>=0&&Progress.totalUnitCount>0){Progress.completedUnitCount=i
|
||||||
|
continue}}
|
||||||
{{- end}}`))
|
{{- end}}`))
|
||||||
|
|
|
@ -4,7 +4,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -17,7 +16,7 @@ import (
|
||||||
func main() {
|
func main() {
|
||||||
dir := os.Args[1]
|
dir := os.Args[1]
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(dir)
|
files, err := os.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -26,12 +25,7 @@ func main() {
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
name := file.Name()
|
name := file.Name()
|
||||||
|
data, err := os.ReadFile(filepath.Join(dir, name))
|
||||||
str.WriteString("\n" + `{{define "`)
|
|
||||||
str.WriteString(strings.TrimSuffix(name, filepath.Ext(name)))
|
|
||||||
str.WriteString(`" -}}` + "\n")
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(filepath.Join(dir, name))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -40,6 +34,9 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
str.WriteString("\n" + `{{define "`)
|
||||||
|
str.WriteString(strings.TrimSuffix(name, filepath.Ext(name)))
|
||||||
|
str.WriteString(`" -}}` + "\n")
|
||||||
str.Write(data)
|
str.Write(data)
|
||||||
str.WriteString("\n{{- end}}")
|
str.WriteString("\n{{- end}}")
|
||||||
}
|
}
|
||||||
|
@ -108,5 +105,4 @@ import (
|
||||||
var scripts = template.Must(template.New("").Funcs(template.FuncMap{"json": func(v interface{}) (string, error) {
|
var scripts = template.Must(template.New("").Funcs(template.FuncMap{"json": func(v interface{}) (string, error) {
|
||||||
b, err := json.Marshal(v)
|
b, err := json.Marshal(v)
|
||||||
return string(b), err
|
return string(b), err
|
||||||
}}).Parse(` + "`{{.}}`" + `))
|
}}).Parse(` + "`{{.}}`))\n"))
|
||||||
`))
|
|
||||||
|
|
34
internal/zenutil/osascripts/progress.gojs
Normal file
34
internal/zenutil/osascripts/progress.gojs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
var app = Application.currentApplication()
|
||||||
|
app.includeStandardAdditions = true
|
||||||
|
app.activate()
|
||||||
|
|
||||||
|
ObjC.import('stdlib')
|
||||||
|
ObjC.import('readline')
|
||||||
|
|
||||||
|
{{- if .Total}}
|
||||||
|
Progress.totalUnitCount = {{.Total}}
|
||||||
|
{{- end}}
|
||||||
|
{{- if .Description}}
|
||||||
|
Progress.description = {{json .Description}}
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
var s
|
||||||
|
try {
|
||||||
|
s = $.readline('')
|
||||||
|
} catch (e) {
|
||||||
|
if (e.errorNumber === -128) $.exit(1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.indexOf('#') === 0) {
|
||||||
|
Progress.additionalDescription = s.slice(1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = parseInt(s)
|
||||||
|
if (i >= 0 && Progress.totalUnitCount > 0) {
|
||||||
|
Progress.completedUnitCount = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
116
internal/zenutil/progress_unix.go
Normal file
116
internal/zenutil/progress_unix.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
// +build !windows,!js
|
||||||
|
|
||||||
|
package zenutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type progressDialog struct {
|
||||||
|
ctx context.Context
|
||||||
|
cmd *exec.Cmd
|
||||||
|
max int
|
||||||
|
percent bool
|
||||||
|
closed int32
|
||||||
|
lines chan string
|
||||||
|
done chan struct{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) send(line string) error {
|
||||||
|
select {
|
||||||
|
case d.lines <- line:
|
||||||
|
return nil
|
||||||
|
case <-d.done:
|
||||||
|
return d.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) Text(text string) error {
|
||||||
|
return d.send("#" + text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) Value(value int) error {
|
||||||
|
if d.percent {
|
||||||
|
return d.send(strconv.FormatFloat(100*float64(value)/float64(d.max), 'f', -1, 64))
|
||||||
|
} else {
|
||||||
|
return d.send(strconv.Itoa(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) MaxValue() int {
|
||||||
|
return d.max
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) Done() <-chan struct{} {
|
||||||
|
return d.done
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) Complete() error {
|
||||||
|
err := d.Value(d.max)
|
||||||
|
close(d.lines)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) Close() error {
|
||||||
|
atomic.StoreInt32(&d.closed, 1)
|
||||||
|
d.cmd.Process.Signal(os.Interrupt)
|
||||||
|
<-d.done
|
||||||
|
return d.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) wait(extra *string, out *bytes.Buffer) {
|
||||||
|
err := d.cmd.Wait()
|
||||||
|
if cerr := d.ctx.Err(); cerr != nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
if eerr, ok := err.(*exec.ExitError); ok {
|
||||||
|
switch {
|
||||||
|
case eerr.ExitCode() == -1 && atomic.LoadInt32(&d.closed) != 0:
|
||||||
|
err = nil
|
||||||
|
case eerr.ExitCode() == 1:
|
||||||
|
if extra != nil && *extra+"\n" == string(out.Bytes()) {
|
||||||
|
err = ErrExtraButton
|
||||||
|
} else {
|
||||||
|
err = ErrCanceled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.err = err
|
||||||
|
close(d.done)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) pipe(w io.WriteCloser) {
|
||||||
|
defer w.Close()
|
||||||
|
var timeout = time.Second
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
timeout = 40 * time.Millisecond
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
var line string
|
||||||
|
select {
|
||||||
|
case s, ok := <-d.lines:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
line = s
|
||||||
|
case <-d.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-d.done:
|
||||||
|
return
|
||||||
|
case <-time.After(timeout):
|
||||||
|
// line = ""
|
||||||
|
}
|
||||||
|
if _, err := w.Write([]byte(line + "\n")); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,30 +1,29 @@
|
||||||
package zenutil
|
package zenutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Run is internal.
|
// Run is internal.
|
||||||
func Run(ctx context.Context, script string, data interface{}) ([]byte, error) {
|
func Run(ctx context.Context, script string, data interface{}) ([]byte, error) {
|
||||||
var buf strings.Builder
|
var buf bytes.Buffer
|
||||||
|
|
||||||
err := scripts.ExecuteTemplate(&buf, script, data)
|
err := scripts.ExecuteTemplate(&buf, script, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
script = buf.String()
|
|
||||||
if Command {
|
if Command {
|
||||||
// Try to use syscall.Exec, fallback to exec.Command.
|
// Try to use syscall.Exec, fallback to exec.Command.
|
||||||
if path, err := exec.LookPath("osascript"); err != nil {
|
if path, err := exec.LookPath("osascript"); err != nil {
|
||||||
} else if t, err := ioutil.TempFile("", ""); err != nil {
|
} else if t, err := ioutil.TempFile("", ""); err != nil {
|
||||||
} else if err := os.Remove(t.Name()); err != nil {
|
} else if err := os.Remove(t.Name()); err != nil {
|
||||||
} else if _, err := t.WriteString(script); err != nil {
|
} else if _, err := t.Write(buf.Bytes()); err != nil {
|
||||||
} else if _, err := t.Seek(0, 0); err != nil {
|
} else if _, err := t.Seek(0, 0); err != nil {
|
||||||
} else if err := syscall.Dup2(int(t.Fd()), syscall.Stdin); err != nil {
|
} else if err := syscall.Dup2(int(t.Fd()), syscall.Stdin); err != nil {
|
||||||
} else if err := os.Stderr.Close(); err != nil {
|
} else if err := os.Stderr.Close(); err != nil {
|
||||||
|
@ -35,7 +34,7 @@ func Run(ctx context.Context, script string, data interface{}) ([]byte, error) {
|
||||||
|
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
cmd := exec.CommandContext(ctx, "osascript", "-l", "JavaScript")
|
cmd := exec.CommandContext(ctx, "osascript", "-l", "JavaScript")
|
||||||
cmd.Stdin = strings.NewReader(script)
|
cmd.Stdin = &buf
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
err = ctx.Err()
|
err = ctx.Err()
|
||||||
|
@ -43,25 +42,86 @@ func Run(ctx context.Context, script string, data interface{}) ([]byte, error) {
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
cmd := exec.Command("osascript", "-l", "JavaScript")
|
cmd := exec.Command("osascript", "-l", "JavaScript")
|
||||||
cmd.Stdin = strings.NewReader(script)
|
cmd.Stdin = &buf
|
||||||
return cmd.Output()
|
return cmd.Output()
|
||||||
}
|
}
|
||||||
|
|
||||||
// File is internal.
|
// RunProgress is internal.
|
||||||
type File struct {
|
func RunProgress(ctx context.Context, max int, data Progress) (dlg *progressDialog, err error) {
|
||||||
Operation string
|
var buf bytes.Buffer
|
||||||
Separator string
|
err = scripts.ExecuteTemplate(&buf, "progress", data)
|
||||||
Options FileOptions
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// FileOptions is internal.
|
t, err := ioutil.TempDir("", "")
|
||||||
type FileOptions struct {
|
if err != nil {
|
||||||
Prompt *string `json:"withPrompt,omitempty"`
|
return nil, err
|
||||||
Type []string `json:"ofType,omitempty"`
|
}
|
||||||
Name string `json:"defaultName,omitempty"`
|
defer func() {
|
||||||
Location string `json:"defaultLocation,omitempty"`
|
if err != nil {
|
||||||
Multiple bool `json:"multipleSelectionsAllowed,omitempty"`
|
if ctx != nil && ctx.Err() != nil {
|
||||||
Invisibles bool `json:"invisibles,omitempty"`
|
err = ctx.Err()
|
||||||
|
}
|
||||||
|
os.RemoveAll(t)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
name := filepath.Join(t, "progress.app")
|
||||||
|
|
||||||
|
cmd = exec.CommandContext(ctx, "osacompile", "-l", "JavaScript", "-o", name)
|
||||||
|
cmd.Stdin = &buf
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
plist := filepath.Join(name, "Contents/Info.plist")
|
||||||
|
|
||||||
|
cmd = exec.CommandContext(ctx, "defaults", "write", plist, "LSUIElement", "true")
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = exec.CommandContext(ctx, "defaults", "write", plist, "CFBundleName", "")
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var executable string
|
||||||
|
cmd = exec.CommandContext(ctx, "defaults", "read", plist, "CFBundleExecutable")
|
||||||
|
if out, err := cmd.Output(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
out = bytes.TrimSuffix(out, []byte{'\n'})
|
||||||
|
executable = filepath.Join(name, "Contents/MacOS", string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = exec.CommandContext(ctx, executable)
|
||||||
|
pipe, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dlg = &progressDialog{
|
||||||
|
ctx: ctx,
|
||||||
|
cmd: cmd,
|
||||||
|
max: max,
|
||||||
|
lines: make(chan string),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
go dlg.pipe(pipe)
|
||||||
|
go func() {
|
||||||
|
defer os.RemoveAll(t)
|
||||||
|
dlg.wait(nil, nil)
|
||||||
|
}()
|
||||||
|
return dlg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dialog is internal.
|
// Dialog is internal.
|
||||||
|
@ -86,6 +146,25 @@ type DialogOptions struct {
|
||||||
Timeout int `json:"givingUpAfter,omitempty"`
|
Timeout int `json:"givingUpAfter,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialogButtons is internal.
|
||||||
|
type DialogButtons struct {
|
||||||
|
Buttons []string
|
||||||
|
Default int
|
||||||
|
Cancel int
|
||||||
|
Extra int
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetButtons is internal.
|
||||||
|
func (d *Dialog) SetButtons(btns DialogButtons) {
|
||||||
|
d.Options.Buttons = btns.Buttons
|
||||||
|
d.Options.Default = btns.Default
|
||||||
|
d.Options.Cancel = btns.Cancel
|
||||||
|
if btns.Extra > 0 {
|
||||||
|
name := btns.Buttons[btns.Extra-1]
|
||||||
|
d.Extra = &name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// List is internal.
|
// List is internal.
|
||||||
type List struct {
|
type List struct {
|
||||||
Items []string
|
Items []string
|
||||||
|
@ -104,6 +183,22 @@ type ListOptions struct {
|
||||||
Empty bool `json:"emptySelectionAllowed,omitempty"`
|
Empty bool `json:"emptySelectionAllowed,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// File is internal.
|
||||||
|
type File struct {
|
||||||
|
Operation string
|
||||||
|
Separator string
|
||||||
|
Options FileOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileOptions struct {
|
||||||
|
Prompt *string `json:"withPrompt,omitempty"`
|
||||||
|
Type []string `json:"ofType,omitempty"`
|
||||||
|
Name string `json:"defaultName,omitempty"`
|
||||||
|
Location string `json:"defaultLocation,omitempty"`
|
||||||
|
Multiple bool `json:"multipleSelectionsAllowed,omitempty"`
|
||||||
|
Invisibles bool `json:"invisibles,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Notify is internal.
|
// Notify is internal.
|
||||||
type Notify struct {
|
type Notify struct {
|
||||||
Text string
|
Text string
|
||||||
|
@ -116,19 +211,8 @@ type NotifyOptions struct {
|
||||||
Subtitle string `json:"subtitle,omitempty"`
|
Subtitle string `json:"subtitle,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Buttons struct {
|
// Progress is internal.
|
||||||
Buttons []string
|
type Progress struct {
|
||||||
Default int
|
Description *string
|
||||||
Cancel int
|
Total *int
|
||||||
Extra int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dialog) SetButtons(btns Buttons) {
|
|
||||||
d.Options.Buttons = btns.Buttons
|
|
||||||
d.Options.Default = btns.Default
|
|
||||||
d.Options.Cancel = btns.Cancel
|
|
||||||
if btns.Extra > 0 {
|
|
||||||
name := btns.Buttons[btns.Extra-1]
|
|
||||||
d.Extra = &name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package zenutil
|
package zenutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -40,3 +41,42 @@ func Run(ctx context.Context, args []string) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return exec.Command(tool, args...).Output()
|
return exec.Command(tool, args...).Output()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunProgress is internal.
|
||||||
|
func RunProgress(ctx context.Context, max int, extra *string, args []string) (*progressDialog, 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 {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, tool, args...)
|
||||||
|
pipe, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var out *bytes.Buffer
|
||||||
|
if extra != nil {
|
||||||
|
out = &bytes.Buffer{}
|
||||||
|
cmd.Stdout = out
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dlg := &progressDialog{
|
||||||
|
ctx: ctx,
|
||||||
|
cmd: cmd,
|
||||||
|
max: max,
|
||||||
|
percent: true,
|
||||||
|
lines: make(chan string),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
go dlg.pipe(pipe)
|
||||||
|
go dlg.wait(extra, out)
|
||||||
|
return dlg, nil
|
||||||
|
}
|
||||||
|
|
12
list.go
12
list.go
|
@ -2,25 +2,19 @@ package zenity
|
||||||
|
|
||||||
// List displays the list dialog.
|
// List displays the list dialog.
|
||||||
//
|
//
|
||||||
// Returns false on cancel, or ErrExtraButton.
|
|
||||||
//
|
|
||||||
// Valid options: Title, Width, Height, OKLabel, CancelLabel, ExtraButton,
|
// Valid options: Title, Width, Height, OKLabel, CancelLabel, ExtraButton,
|
||||||
// Icon, DefaultItems, DisallowEmpty.
|
// Icon, DefaultItems, DisallowEmpty.
|
||||||
func List(text string, items []string, options ...Option) (string, bool, error) {
|
func List(text string, items []string, options ...Option) (string, error) {
|
||||||
return list(text, items, applyOptions(options))
|
return list(text, items, applyOptions(options))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListItems displays the list dialog.
|
// ListItems displays the list dialog.
|
||||||
//
|
func ListItems(text string, items ...string) (string, error) {
|
||||||
// Returns false on cancel, or ErrExtraButton.
|
|
||||||
func ListItems(text string, items ...string) (string, bool, error) {
|
|
||||||
return List(text, items)
|
return List(text, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListMultiple displays the list dialog, allowing multiple items to be selected.
|
// ListMultiple displays the list dialog, allowing multiple items to be selected.
|
||||||
//
|
//
|
||||||
// Returns a nil slice on cancel, or ErrExtraButton.
|
|
||||||
//
|
|
||||||
// Valid options: Title, Width, Height, OKLabel, CancelLabel, ExtraButton,
|
// Valid options: Title, Width, Height, OKLabel, CancelLabel, ExtraButton,
|
||||||
// Icon, DefaultItems, DisallowEmpty.
|
// Icon, DefaultItems, DisallowEmpty.
|
||||||
func ListMultiple(text string, items []string, options ...Option) ([]string, error) {
|
func ListMultiple(text string, items []string, options ...Option) ([]string, error) {
|
||||||
|
@ -28,8 +22,6 @@ func ListMultiple(text string, items []string, options ...Option) ([]string, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListMultipleItems displays the list dialog, allowing multiple items to be selected.
|
// ListMultipleItems displays the list dialog, allowing multiple items to be selected.
|
||||||
//
|
|
||||||
// Returns a nil slice on cancel, or ErrExtraButton.
|
|
||||||
func ListMultipleItems(text string, items ...string) ([]string, error) {
|
func ListMultipleItems(text string, items ...string) ([]string, error) {
|
||||||
return ListMultiple(text, items)
|
return ListMultiple(text, items)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,11 @@ import (
|
||||||
"github.com/ncruces/zenity/internal/zenutil"
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func list(text string, items []string, opts options) (string, bool, error) {
|
func list(text string, items []string, opts options) (string, error) {
|
||||||
|
if opts.extraButton != nil {
|
||||||
|
return "", ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
var data zenutil.List
|
var data zenutil.List
|
||||||
data.Items = items
|
data.Items = items
|
||||||
data.Options.Prompt = &text
|
data.Options.Prompt = &text
|
||||||
|
@ -19,6 +23,10 @@ func list(text string, items []string, opts options) (string, bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func listMultiple(text string, items []string, opts options) ([]string, error) {
|
func listMultiple(text string, items []string, opts options) ([]string, error) {
|
||||||
|
if opts.extraButton != nil {
|
||||||
|
return nil, ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
var data zenutil.List
|
var data zenutil.List
|
||||||
data.Items = items
|
data.Items = items
|
||||||
data.Options.Prompt = &text
|
data.Options.Prompt = &text
|
||||||
|
|
14
list_test.go
14
list_test.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ncruces/zenity"
|
"github.com/ncruces/zenity"
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleList() {
|
func ExampleList() {
|
||||||
|
@ -44,22 +45,23 @@ func ExampleListMultipleItems() {
|
||||||
// Output:
|
// Output:
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListTimeout(t *testing.T) {
|
func TestList_timeout(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
_, _, err := zenity.List("", nil, zenity.Context(ctx))
|
_, err := zenity.List("", nil, zenity.Context(ctx))
|
||||||
if !os.IsTimeout(err) {
|
if !os.IsTimeout(err) {
|
||||||
t.Error("did not timeout:", err)
|
t.Error("did not timeout:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListCancel(t *testing.T) {
|
func TestList_cancel(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
_, _, err := zenity.List("", nil, zenity.Context(ctx))
|
_, err := zenity.List("", nil, zenity.Context(ctx))
|
||||||
if !errors.Is(err, context.Canceled) {
|
if !errors.Is(err, context.Canceled) {
|
||||||
t.Error("was not canceled:", err)
|
t.Error("was not canceled:", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/ncruces/zenity/internal/zenutil"
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func list(text string, items []string, opts options) (string, bool, error) {
|
func list(text string, items []string, opts options) (string, error) {
|
||||||
args := []string{"--list", "--column=", "--hide-header", "--text", text}
|
args := []string{"--list", "--column=", "--hide-header", "--text", text}
|
||||||
args = appendTitle(args, opts)
|
args = appendTitle(args, opts)
|
||||||
args = appendButtons(args, opts)
|
args = appendButtons(args, opts)
|
||||||
|
|
|
@ -5,28 +5,21 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
func list(text string, items []string, opts options) (string, bool, error) {
|
func list(text string, items []string, opts options) (string, error) {
|
||||||
var title string
|
items, err := listDlg(text, items, false, opts)
|
||||||
if opts.title != nil {
|
|
||||||
title = *opts.title
|
|
||||||
}
|
|
||||||
if opts.okLabel == nil {
|
|
||||||
opts.okLabel = stringPtr("OK")
|
|
||||||
}
|
|
||||||
if opts.cancelLabel == nil {
|
|
||||||
opts.cancelLabel = stringPtr("Cancel")
|
|
||||||
}
|
|
||||||
items, err := listDlg(title, text, items, false, opts)
|
|
||||||
if len(items) == 1 {
|
if len(items) == 1 {
|
||||||
return items[0], true, err
|
return items[0], err
|
||||||
}
|
}
|
||||||
return "", false, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
func listMultiple(text string, items []string, opts options) ([]string, error) {
|
func listMultiple(text string, items []string, opts options) ([]string, error) {
|
||||||
var title string
|
return listDlg(text, items, true, opts)
|
||||||
if opts.title != nil {
|
}
|
||||||
title = *opts.title
|
|
||||||
|
func listDlg(text string, items []string, multiple bool, opts options) (out []string, err error) {
|
||||||
|
if opts.title == nil {
|
||||||
|
opts.title = stringPtr("")
|
||||||
}
|
}
|
||||||
if opts.okLabel == nil {
|
if opts.okLabel == nil {
|
||||||
opts.okLabel = stringPtr("OK")
|
opts.okLabel = stringPtr("OK")
|
||||||
|
@ -34,10 +27,7 @@ func listMultiple(text string, items []string, opts options) ([]string, error) {
|
||||||
if opts.cancelLabel == nil {
|
if opts.cancelLabel == nil {
|
||||||
opts.cancelLabel = stringPtr("Cancel")
|
opts.cancelLabel = stringPtr("Cancel")
|
||||||
}
|
}
|
||||||
return listDlg(title, text, items, true, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listDlg(title, text string, items []string, multiple bool, opts options) (out []string, err error) {
|
|
||||||
defer setup()()
|
defer setup()()
|
||||||
font := getFont()
|
font := getFont()
|
||||||
defer font.Delete()
|
defer font.Delete()
|
||||||
|
@ -52,6 +42,7 @@ func listDlg(title, text string, items []string, multiple bool, opts options) (o
|
||||||
sendMessage.Call(listCtl, 0x0030 /* WM_SETFONT */, hfont, 1)
|
sendMessage.Call(listCtl, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
sendMessage.Call(okBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
sendMessage.Call(okBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
sendMessage.Call(cancelBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
sendMessage.Call(cancelBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
|
sendMessage.Call(extraBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
setWindowPos.Call(wnd, 0, 0, 0, dpi.Scale(281), dpi.Scale(281), 0x6) // SWP_NOZORDER|SWP_NOMOVE
|
setWindowPos.Call(wnd, 0, 0, 0, dpi.Scale(281), dpi.Scale(281), 0x6) // SWP_NOZORDER|SWP_NOMOVE
|
||||||
setWindowPos.Call(textCtl, 0, dpi.Scale(12), dpi.Scale(10), dpi.Scale(241), dpi.Scale(16), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(textCtl, 0, dpi.Scale(12), dpi.Scale(10), dpi.Scale(241), dpi.Scale(16), 0x4) // SWP_NOZORDER
|
||||||
setWindowPos.Call(listCtl, 0, dpi.Scale(12), dpi.Scale(30), dpi.Scale(241), dpi.Scale(164), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(listCtl, 0, dpi.Scale(12), dpi.Scale(30), dpi.Scale(241), dpi.Scale(164), 0x4) // SWP_NOZORDER
|
||||||
|
@ -59,7 +50,6 @@ func listDlg(title, text string, items []string, multiple bool, opts options) (o
|
||||||
setWindowPos.Call(okBtn, 0, dpi.Scale(95), dpi.Scale(206), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(okBtn, 0, dpi.Scale(95), dpi.Scale(206), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
setWindowPos.Call(cancelBtn, 0, dpi.Scale(178), dpi.Scale(206), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(cancelBtn, 0, dpi.Scale(178), dpi.Scale(206), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
} else {
|
} else {
|
||||||
sendMessage.Call(extraBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
|
||||||
setWindowPos.Call(okBtn, 0, dpi.Scale(12), dpi.Scale(206), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(okBtn, 0, dpi.Scale(12), dpi.Scale(206), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
setWindowPos.Call(extraBtn, 0, dpi.Scale(95), dpi.Scale(206), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(extraBtn, 0, dpi.Scale(95), dpi.Scale(206), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
setWindowPos.Call(cancelBtn, 0, dpi.Scale(178), dpi.Scale(206), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(cancelBtn, 0, dpi.Scale(178), dpi.Scale(206), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
|
@ -72,6 +62,7 @@ func listDlg(title, text string, items []string, multiple bool, opts options) (o
|
||||||
postQuitMessage.Call(0)
|
postQuitMessage.Call(0)
|
||||||
|
|
||||||
case 0x0010: // WM_CLOSE
|
case 0x0010: // WM_CLOSE
|
||||||
|
err = ErrCanceled
|
||||||
destroyWindow.Call(wnd)
|
destroyWindow.Call(wnd)
|
||||||
|
|
||||||
case 0x0111: // WM_COMMAND
|
case 0x0111: // WM_COMMAND
|
||||||
|
@ -98,6 +89,7 @@ func listDlg(title, text string, items []string, multiple bool, opts options) (o
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 2: // IDCANCEL
|
case 2: // IDCANCEL
|
||||||
|
err = ErrCanceled
|
||||||
case 7: // IDNO
|
case 7: // IDNO
|
||||||
err = ErrExtraButton
|
err = ErrExtraButton
|
||||||
}
|
}
|
||||||
|
@ -107,8 +99,8 @@ func listDlg(title, text string, items []string, multiple bool, opts options) (o
|
||||||
layout(dpi(uint32(wparam) >> 16))
|
layout(dpi(uint32(wparam) >> 16))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ret, _, _ := syscall.Syscall6(defWindowProc, 4, wnd, uintptr(msg), wparam, lparam, 0, 0)
|
res, _, _ := syscall.Syscall6(defWindowProc, 4, wnd, uintptr(msg), wparam, lparam, 0, 0)
|
||||||
return ret
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
@ -130,16 +122,16 @@ func listDlg(title, text string, items []string, multiple bool, opts options) (o
|
||||||
defer unregisterClass.Call(cls, instance)
|
defer unregisterClass.Call(cls, instance)
|
||||||
|
|
||||||
wnd, _, _ = createWindowEx.Call(0x10101, // WS_EX_CONTROLPARENT|WS_EX_WINDOWEDGE|WS_EX_DLGMODALFRAME
|
wnd, _, _ = createWindowEx.Call(0x10101, // WS_EX_CONTROLPARENT|WS_EX_WINDOWEDGE|WS_EX_DLGMODALFRAME
|
||||||
cls, strptr(title),
|
cls, strptr(*opts.title),
|
||||||
0x84c80000, // WS_POPUPWINDOW|WS_CLIPSIBLINGS|WS_DLGFRAME
|
0x84c80000, // WS_POPUPWINDOW|WS_CLIPSIBLINGS|WS_DLGFRAME
|
||||||
0x80000000, // CW_USEDEFAULT
|
0x80000000, // CW_USEDEFAULT
|
||||||
0x80000000, // CW_USEDEFAULT
|
0x80000000, // CW_USEDEFAULT
|
||||||
281, 281, 0, 0, instance)
|
281, 281, 0, 0, instance, 0)
|
||||||
|
|
||||||
textCtl, _, _ = createWindowEx.Call(0,
|
textCtl, _, _ = createWindowEx.Call(0,
|
||||||
strptr("STATIC"), strptr(text),
|
strptr("STATIC"), strptr(text),
|
||||||
0x5002e080, // WS_CHILD|WS_VISIBLE|WS_GROUP|SS_WORDELLIPSIS|SS_EDITCONTROL|SS_NOPREFIX
|
0x5002e080, // WS_CHILD|WS_VISIBLE|WS_GROUP|SS_WORDELLIPSIS|SS_EDITCONTROL|SS_NOPREFIX
|
||||||
12, 10, 241, 16, wnd, 0, instance)
|
12, 10, 241, 16, wnd, 0, instance, 0)
|
||||||
|
|
||||||
var flags uintptr = 0x50320000 // WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_GROUP|WS_TABSTOP
|
var flags uintptr = 0x50320000 // WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_GROUP|WS_TABSTOP
|
||||||
if multiple {
|
if multiple {
|
||||||
|
@ -148,21 +140,21 @@ func listDlg(title, text string, items []string, multiple bool, opts options) (o
|
||||||
listCtl, _, _ = createWindowEx.Call(0x200, // WS_EX_CLIENTEDGE
|
listCtl, _, _ = createWindowEx.Call(0x200, // WS_EX_CLIENTEDGE
|
||||||
strptr("LISTBOX"), strptr(opts.entryText),
|
strptr("LISTBOX"), strptr(opts.entryText),
|
||||||
flags,
|
flags,
|
||||||
12, 30, 241, 164, wnd, 0, instance)
|
12, 30, 241, 164, wnd, 0, instance, 0)
|
||||||
|
|
||||||
okBtn, _, _ = createWindowEx.Call(0,
|
okBtn, _, _ = createWindowEx.Call(0,
|
||||||
strptr("BUTTON"), strptr(*opts.okLabel),
|
strptr("BUTTON"), strptr(*opts.okLabel),
|
||||||
0x50030001, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP|BS_DEFPUSHBUTTON
|
0x50030001, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP|BS_DEFPUSHBUTTON
|
||||||
12, 206, 75, 24, wnd, 1 /* IDOK */, instance)
|
12, 206, 75, 24, wnd, 1 /* IDOK */, instance, 0)
|
||||||
cancelBtn, _, _ = createWindowEx.Call(0,
|
cancelBtn, _, _ = createWindowEx.Call(0,
|
||||||
strptr("BUTTON"), strptr(*opts.cancelLabel),
|
strptr("BUTTON"), strptr(*opts.cancelLabel),
|
||||||
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
|
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
|
||||||
12, 206, 75, 24, wnd, 2 /* IDCANCEL */, instance)
|
12, 206, 75, 24, wnd, 2 /* IDCANCEL */, instance, 0)
|
||||||
if opts.extraButton != nil {
|
if opts.extraButton != nil {
|
||||||
extraBtn, _, _ = createWindowEx.Call(0,
|
extraBtn, _, _ = createWindowEx.Call(0,
|
||||||
strptr("BUTTON"), strptr(*opts.extraButton),
|
strptr("BUTTON"), strptr(*opts.extraButton),
|
||||||
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
|
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
|
||||||
12, 206, 75, 24, wnd, 7 /* IDNO */, instance)
|
12, 206, 75, 24, wnd, 7 /* IDNO */, instance, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
|
|
20
msg.go
20
msg.go
|
@ -1,46 +1,34 @@
|
||||||
package zenity
|
package zenity
|
||||||
|
|
||||||
// ErrExtraButton is returned by dialog functions when the extra button is
|
|
||||||
// pressed.
|
|
||||||
const ErrExtraButton = stringErr("extra button pressed")
|
|
||||||
|
|
||||||
// Question displays the question dialog.
|
// Question displays the question dialog.
|
||||||
//
|
//
|
||||||
// Returns true on OK, false on Cancel, or ErrExtraButton.
|
|
||||||
//
|
|
||||||
// Valid options: Title, Width, Height, OKLabel, CancelLabel, ExtraButton,
|
// Valid options: Title, Width, Height, OKLabel, CancelLabel, ExtraButton,
|
||||||
// Icon, NoWrap, Ellipsize, DefaultCancel.
|
// Icon, NoWrap, Ellipsize, DefaultCancel.
|
||||||
func Question(text string, options ...Option) (bool, error) {
|
func Question(text string, options ...Option) error {
|
||||||
return message(questionKind, text, applyOptions(options))
|
return message(questionKind, text, applyOptions(options))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info displays the info dialog.
|
// Info displays the info dialog.
|
||||||
//
|
//
|
||||||
// Returns true on OK, false on dismiss, or ErrExtraButton.
|
|
||||||
//
|
|
||||||
// Valid options: Title, Width, Height, OKLabel, ExtraButton, Icon,
|
// Valid options: Title, Width, Height, OKLabel, ExtraButton, Icon,
|
||||||
// NoWrap, Ellipsize.
|
// NoWrap, Ellipsize.
|
||||||
func Info(text string, options ...Option) (bool, error) {
|
func Info(text string, options ...Option) error {
|
||||||
return message(infoKind, text, applyOptions(options))
|
return message(infoKind, text, applyOptions(options))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning displays the warning dialog.
|
// Warning displays the warning dialog.
|
||||||
//
|
//
|
||||||
// Returns true on OK, false on dismiss, or ErrExtraButton.
|
|
||||||
//
|
|
||||||
// Valid options: Title, Width, Height, OKLabel, ExtraButton, Icon,
|
// Valid options: Title, Width, Height, OKLabel, ExtraButton, Icon,
|
||||||
// NoWrap, Ellipsize.
|
// NoWrap, Ellipsize.
|
||||||
func Warning(text string, options ...Option) (bool, error) {
|
func Warning(text string, options ...Option) error {
|
||||||
return message(warningKind, text, applyOptions(options))
|
return message(warningKind, text, applyOptions(options))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error displays the error dialog.
|
// Error displays the error dialog.
|
||||||
//
|
//
|
||||||
// Returns true on OK, false on dismiss, or ErrExtraButton.
|
|
||||||
//
|
|
||||||
// Valid options: Title, Width, Height, OKLabel, ExtraButton, Icon,
|
// Valid options: Title, Width, Height, OKLabel, ExtraButton, Icon,
|
||||||
// NoWrap, Ellipsize.
|
// NoWrap, Ellipsize.
|
||||||
func Error(text string, options ...Option) (bool, error) {
|
func Error(text string, options ...Option) error {
|
||||||
return message(errorKind, text, applyOptions(options))
|
return message(errorKind, text, applyOptions(options))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"github.com/ncruces/zenity/internal/zenutil"
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func message(kind messageKind, text string, opts options) (bool, error) {
|
func message(kind messageKind, text string, opts options) error {
|
||||||
var data zenutil.Dialog
|
var data zenutil.Dialog
|
||||||
data.Text = text
|
data.Text = text
|
||||||
data.Options.Timeout = zenutil.Timeout
|
data.Options.Timeout = zenutil.Timeout
|
||||||
|
@ -33,6 +33,6 @@ func message(kind messageKind, text string, opts options) (bool, error) {
|
||||||
data.SetButtons(getButtons(dialog, kind == questionKind, opts))
|
data.SetButtons(getButtons(dialog, kind == questionKind, opts))
|
||||||
|
|
||||||
out, err := zenutil.Run(opts.ctx, "dialog", data)
|
out, err := zenutil.Run(opts.ctx, "dialog", data)
|
||||||
_, ok, err := strResult(opts, out, err)
|
_, err = strResult(opts, out, err)
|
||||||
return ok, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
13
msg_test.go
13
msg_test.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ncruces/zenity"
|
"github.com/ncruces/zenity"
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleError() {
|
func ExampleError() {
|
||||||
|
@ -38,32 +39,34 @@ func ExampleQuestion() {
|
||||||
// Output:
|
// Output:
|
||||||
}
|
}
|
||||||
|
|
||||||
var msgFuncs = []func(string, ...zenity.Option) (bool, error){
|
var msgFuncs = []func(string, ...zenity.Option) error{
|
||||||
zenity.Error,
|
zenity.Error,
|
||||||
zenity.Info,
|
zenity.Info,
|
||||||
zenity.Warning,
|
zenity.Warning,
|
||||||
zenity.Question,
|
zenity.Question,
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageTimeout(t *testing.T) {
|
func TestMessage_timeout(t *testing.T) {
|
||||||
for _, f := range msgFuncs {
|
for _, f := range msgFuncs {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
||||||
|
|
||||||
_, err := f("text", zenity.Context(ctx))
|
err := f("text", zenity.Context(ctx))
|
||||||
if !os.IsTimeout(err) {
|
if !os.IsTimeout(err) {
|
||||||
t.Error("did not timeout:", err)
|
t.Error("did not timeout:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
|
goleak.VerifyNone(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageCancel(t *testing.T) {
|
func TestMessage_cancel(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
for _, f := range msgFuncs {
|
for _, f := range msgFuncs {
|
||||||
_, err := f("text", zenity.Context(ctx))
|
err := f("text", zenity.Context(ctx))
|
||||||
if !errors.Is(err, context.Canceled) {
|
if !errors.Is(err, context.Canceled) {
|
||||||
t.Error("was not canceled:", err)
|
t.Error("was not canceled:", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/ncruces/zenity/internal/zenutil"
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func message(kind messageKind, text string, opts options) (bool, error) {
|
func message(kind messageKind, text string, opts options) error {
|
||||||
args := []string{"--text", text, "--no-markup"}
|
args := []string{"--text", text, "--no-markup"}
|
||||||
switch kind {
|
switch kind {
|
||||||
case questionKind:
|
case questionKind:
|
||||||
|
@ -47,6 +47,6 @@ func message(kind messageKind, text string, opts options) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := zenutil.Run(opts.ctx, args)
|
out, err := zenutil.Run(opts.ctx, args)
|
||||||
_, ok, err := strResult(opts, out, err)
|
_, err = strResult(opts, out, err)
|
||||||
return ok, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ var (
|
||||||
getDlgCtrlID = user32.NewProc("GetDlgCtrlID")
|
getDlgCtrlID = user32.NewProc("GetDlgCtrlID")
|
||||||
)
|
)
|
||||||
|
|
||||||
func message(kind messageKind, text string, opts options) (bool, error) {
|
func message(kind messageKind, text string, opts options) error {
|
||||||
var flags uintptr
|
var flags uintptr
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -48,7 +48,7 @@ func message(kind messageKind, text string, opts options) (bool, error) {
|
||||||
if opts.ctx != nil || opts.okLabel != nil || opts.cancelLabel != nil || opts.extraButton != nil {
|
if opts.ctx != nil || opts.okLabel != nil || opts.cancelLabel != nil || opts.extraButton != nil {
|
||||||
unhook, err := hookMessageLabels(kind, opts)
|
unhook, err := hookMessageLabels(kind, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return err
|
||||||
}
|
}
|
||||||
defer unhook()
|
defer unhook()
|
||||||
}
|
}
|
||||||
|
@ -62,17 +62,17 @@ func message(kind messageKind, text string, opts options) (bool, error) {
|
||||||
s, _, err := messageBox.Call(0, strptr(text), title, flags)
|
s, _, err := messageBox.Call(0, strptr(text), title, flags)
|
||||||
|
|
||||||
if opts.ctx != nil && opts.ctx.Err() != nil {
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
return false, opts.ctx.Err()
|
return opts.ctx.Err()
|
||||||
}
|
}
|
||||||
switch s {
|
switch s {
|
||||||
case 1, 6: // IDOK, IDYES
|
case 1, 6: // IDOK, IDYES
|
||||||
return true, nil
|
return nil
|
||||||
case 2: // IDCANCEL
|
case 2: // IDCANCEL
|
||||||
return false, nil
|
return ErrCanceled
|
||||||
case 7: // IDNO
|
case 7: // IDNO
|
||||||
return false, ErrExtraButton
|
return ErrExtraButton
|
||||||
default:
|
default:
|
||||||
return false, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ func hookMessageLabels(kind messageKind, opts options) (unhook context.CancelFun
|
||||||
return hookDialog(opts.ctx, func(wnd uintptr) {
|
return hookDialog(opts.ctx, func(wnd uintptr) {
|
||||||
enumChildWindows.Call(wnd,
|
enumChildWindows.Call(wnd,
|
||||||
syscall.NewCallback(func(wnd, lparam uintptr) uintptr {
|
syscall.NewCallback(func(wnd, lparam uintptr) uintptr {
|
||||||
name := [8]uint16{}
|
var name [8]uint16
|
||||||
getClassName.Call(wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name)))
|
getClassName.Call(wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name)))
|
||||||
if syscall.UTF16ToString(name[:]) == "Button" {
|
if syscall.UTF16ToString(name[:]) == "Button" {
|
||||||
ctl, _, _ := getDlgCtrlID.Call(wnd)
|
ctl, _, _ := getDlgCtrlID.Call(wnd)
|
||||||
|
@ -89,11 +89,7 @@ func hookMessageLabels(kind messageKind, opts options) (unhook context.CancelFun
|
||||||
case 1, 6: // IDOK, IDYES
|
case 1, 6: // IDOK, IDYES
|
||||||
text = opts.okLabel
|
text = opts.okLabel
|
||||||
case 2: // IDCANCEL
|
case 2: // IDCANCEL
|
||||||
if kind == questionKind {
|
|
||||||
text = opts.cancelLabel
|
text = opts.cancelLabel
|
||||||
} else {
|
|
||||||
text = opts.okLabel
|
|
||||||
}
|
|
||||||
case 7: // IDNO
|
case 7: // IDNO
|
||||||
text = opts.extraButton
|
text = opts.extraButton
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ncruces/zenity"
|
"github.com/ncruces/zenity"
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleNotify() {
|
func ExampleNotify() {
|
||||||
|
@ -15,7 +16,8 @@ func ExampleNotify() {
|
||||||
// Output:
|
// Output:
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotifyCancel(t *testing.T) {
|
func TestNotify_cancel(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
|
|
51
progress.go
Normal file
51
progress.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package zenity
|
||||||
|
|
||||||
|
// Progress displays the progress indication dialog.
|
||||||
|
//
|
||||||
|
// Valid options: Title, Width, Height, OKLabel, CancelLabel, ExtraButton,
|
||||||
|
// Icon, MaxValue, Pulsate, NoCancel, TimeRemaining.
|
||||||
|
func Progress(options ...Option) (ProgressDialog, error) {
|
||||||
|
return progress(applyOptions(options))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProgressDialog allows you to interact with the progress indication dialog.
|
||||||
|
type ProgressDialog interface {
|
||||||
|
// Text sets the dialog text.
|
||||||
|
Text(string) error
|
||||||
|
|
||||||
|
// Value sets how much of the task has been completed.
|
||||||
|
Value(int) error
|
||||||
|
|
||||||
|
// MaxValue gets how much work the task requires in total.
|
||||||
|
MaxValue() int
|
||||||
|
|
||||||
|
// Complete marks the task completed.
|
||||||
|
Complete() error
|
||||||
|
|
||||||
|
// Close closes the dialog.
|
||||||
|
Close() error
|
||||||
|
|
||||||
|
// Done returns a channel that's closed when the dialog is closed.
|
||||||
|
Done() <-chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxValue returns an Option to set the maximum value (Windows and macOS only).
|
||||||
|
// The default maximum value is 100.
|
||||||
|
func MaxValue(value int) Option {
|
||||||
|
return funcOption(func(o *options) { o.maxValue = value })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pulsate returns an Option to pulsate the progress bar.
|
||||||
|
func Pulsate() Option {
|
||||||
|
return funcOption(func(o *options) { o.maxValue = -1 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoCancel returns an Option to hide the Cancel button (Windows and Unix only).
|
||||||
|
func NoCancel() Option {
|
||||||
|
return funcOption(func(o *options) { o.noCancel = true })
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeRemaining returns an Option to estimate when progress will reach 100% (Unix only).
|
||||||
|
func TimeRemaining() Option {
|
||||||
|
return funcOption(func(o *options) { o.timeRemaining = true })
|
||||||
|
}
|
22
progress_darwin.go
Normal file
22
progress_darwin.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package zenity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func progress(opts options) (ProgressDialog, error) {
|
||||||
|
if opts.extraButton != nil {
|
||||||
|
return nil, ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
var data zenutil.Progress
|
||||||
|
data.Description = opts.title
|
||||||
|
if opts.maxValue == 0 {
|
||||||
|
opts.maxValue = 100
|
||||||
|
}
|
||||||
|
if opts.maxValue >= 0 {
|
||||||
|
data.Total = &opts.maxValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return zenutil.RunProgress(opts.ctx, opts.maxValue, data)
|
||||||
|
}
|
100
progress_test.go
Normal file
100
progress_test.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package zenity_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncruces/zenity"
|
||||||
|
"go.uber.org/goleak"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleProgress() {
|
||||||
|
dlg, err := zenity.Progress(
|
||||||
|
zenity.Title("Update System Logs"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer dlg.Close()
|
||||||
|
|
||||||
|
dlg.Text("Scanning mail logs...")
|
||||||
|
dlg.Value(0)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
dlg.Value(25)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
dlg.Text("Updating mail logs...")
|
||||||
|
dlg.Value(50)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
dlg.Text("Resetting cron jobs...")
|
||||||
|
dlg.Value(75)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
dlg.Text("Rebooting system...")
|
||||||
|
dlg.Value(100)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
dlg.Complete()
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleProgress_pulsate() {
|
||||||
|
dlg, err := zenity.Progress(
|
||||||
|
zenity.Title("Update System Logs"),
|
||||||
|
zenity.Pulsate())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer dlg.Close()
|
||||||
|
|
||||||
|
dlg.Text("Scanning mail logs...")
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
dlg.Text("Updating mail logs...")
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
dlg.Text("Resetting cron jobs...")
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
dlg.Text("Rebooting system...")
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
dlg.Complete()
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProgress_cancel(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
_, err := zenity.Progress(zenity.Context(ctx))
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
|
t.Error("was not canceled:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProgress_cancelAfter(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
dlg, err := zenity.Progress(zenity.Context(ctx))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go cancel()
|
||||||
|
<-dlg.Done()
|
||||||
|
err = dlg.Close()
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
|
t.Error("was not canceled:", err)
|
||||||
|
}
|
||||||
|
}
|
28
progress_unix.go
Normal file
28
progress_unix.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// +build !windows,!darwin,!js
|
||||||
|
|
||||||
|
package zenity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func progress(opts options) (ProgressDialog, error) {
|
||||||
|
args := []string{"--progress"}
|
||||||
|
args = appendTitle(args, opts)
|
||||||
|
args = appendButtons(args, opts)
|
||||||
|
args = appendWidthHeight(args, opts)
|
||||||
|
args = appendIcon(args, opts)
|
||||||
|
if opts.maxValue == 0 {
|
||||||
|
opts.maxValue = 100
|
||||||
|
}
|
||||||
|
if opts.maxValue < 0 {
|
||||||
|
args = append(args, "--pulsate")
|
||||||
|
}
|
||||||
|
if opts.noCancel {
|
||||||
|
args = append(args, "--no-cancel")
|
||||||
|
}
|
||||||
|
if opts.timeRemaining {
|
||||||
|
args = append(args, "--time-remaining")
|
||||||
|
}
|
||||||
|
return zenutil.RunProgress(opts.ctx, opts.maxValue, opts.extraButton, args)
|
||||||
|
}
|
261
progress_windows.go
Normal file
261
progress_windows.go
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
package zenity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func progress(opts options) (ProgressDialog, error) {
|
||||||
|
if opts.title == nil {
|
||||||
|
opts.title = stringPtr("")
|
||||||
|
}
|
||||||
|
if opts.okLabel == nil {
|
||||||
|
opts.okLabel = stringPtr("OK")
|
||||||
|
}
|
||||||
|
if opts.cancelLabel == nil {
|
||||||
|
opts.cancelLabel = stringPtr("Cancel")
|
||||||
|
}
|
||||||
|
if opts.maxValue == 0 {
|
||||||
|
opts.maxValue = 100
|
||||||
|
}
|
||||||
|
if opts.ctx == nil {
|
||||||
|
opts.ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
dlg := &progressDialog{
|
||||||
|
done: make(chan struct{}),
|
||||||
|
max: opts.maxValue,
|
||||||
|
}
|
||||||
|
dlg.init.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := progressDlg(opts, dlg)
|
||||||
|
if cerr := opts.ctx.Err(); cerr != nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
dlg.err = err
|
||||||
|
close(dlg.done)
|
||||||
|
}()
|
||||||
|
|
||||||
|
dlg.init.Wait()
|
||||||
|
return dlg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func progressDlg(opts options, dlg *progressDialog) (err error) {
|
||||||
|
defer setup()()
|
||||||
|
font := getFont()
|
||||||
|
defer font.Delete()
|
||||||
|
defWindowProc := defWindowProc.Addr()
|
||||||
|
|
||||||
|
layout := func(dpi dpi) {
|
||||||
|
hfont := font.ForDPI(dpi)
|
||||||
|
sendMessage.Call(dlg.textCtl, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
|
sendMessage.Call(dlg.okBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
|
sendMessage.Call(dlg.cancelBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
|
sendMessage.Call(dlg.extraBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
|
setWindowPos.Call(dlg.wnd, 0, 0, 0, dpi.Scale(281), dpi.Scale(141), 0x6) // SWP_NOZORDER|SWP_NOMOVE
|
||||||
|
setWindowPos.Call(dlg.textCtl, 0, dpi.Scale(12), dpi.Scale(10), dpi.Scale(241), dpi.Scale(16), 0x4) // SWP_NOZORDER
|
||||||
|
setWindowPos.Call(dlg.progCtl, 0, dpi.Scale(12), dpi.Scale(30), dpi.Scale(241), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
|
if dlg.extraBtn == 0 {
|
||||||
|
if dlg.cancelBtn == 0 {
|
||||||
|
setWindowPos.Call(dlg.okBtn, 0, dpi.Scale(178), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
|
} else {
|
||||||
|
setWindowPos.Call(dlg.okBtn, 0, dpi.Scale(95), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
|
setWindowPos.Call(dlg.cancelBtn, 0, dpi.Scale(178), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if dlg.cancelBtn == 0 {
|
||||||
|
setWindowPos.Call(dlg.okBtn, 0, dpi.Scale(95), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
|
setWindowPos.Call(dlg.extraBtn, 0, dpi.Scale(178), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
|
} else {
|
||||||
|
setWindowPos.Call(dlg.okBtn, 0, dpi.Scale(12), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
|
setWindowPos.Call(dlg.extraBtn, 0, dpi.Scale(95), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
|
setWindowPos.Call(dlg.cancelBtn, 0, dpi.Scale(178), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proc := func(wnd uintptr, msg uint32, wparam, lparam uintptr) uintptr {
|
||||||
|
switch msg {
|
||||||
|
case 0x0002: // WM_DESTROY
|
||||||
|
postQuitMessage.Call(0)
|
||||||
|
|
||||||
|
case 0x0010: // WM_CLOSE
|
||||||
|
err = ErrCanceled
|
||||||
|
destroyWindow.Call(wnd)
|
||||||
|
|
||||||
|
case 0x0111: // WM_COMMAND
|
||||||
|
switch wparam {
|
||||||
|
default:
|
||||||
|
return 1
|
||||||
|
case 1, 6: // IDOK, IDYES
|
||||||
|
//
|
||||||
|
case 2: // IDCANCEL
|
||||||
|
err = ErrCanceled
|
||||||
|
case 7: // IDNO
|
||||||
|
err = ErrExtraButton
|
||||||
|
}
|
||||||
|
destroyWindow.Call(wnd)
|
||||||
|
|
||||||
|
case 0x02e0: // WM_DPICHANGED
|
||||||
|
layout(dpi(uint32(wparam) >> 16))
|
||||||
|
|
||||||
|
default:
|
||||||
|
res, _, _ := syscall.Syscall6(defWindowProc, 4, wnd, uintptr(msg), wparam, lparam, 0, 0)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
|
return opts.ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
instance, _, err := getModuleHandle.Call(0)
|
||||||
|
if instance == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cls, err := registerClass(instance, syscall.NewCallback(proc))
|
||||||
|
if cls == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer unregisterClass.Call(cls, instance)
|
||||||
|
|
||||||
|
dlg.wnd, _, _ = createWindowEx.Call(0x10101, // WS_EX_CONTROLPARENT|WS_EX_WINDOWEDGE|WS_EX_DLGMODALFRAME
|
||||||
|
cls, strptr(*opts.title),
|
||||||
|
0x84c80000, // WS_POPUPWINDOW|WS_CLIPSIBLINGS|WS_DLGFRAME
|
||||||
|
0x80000000, // CW_USEDEFAULT
|
||||||
|
0x80000000, // CW_USEDEFAULT
|
||||||
|
281, 141, 0, 0, instance, 0)
|
||||||
|
|
||||||
|
dlg.textCtl, _, _ = createWindowEx.Call(0,
|
||||||
|
strptr("STATIC"), 0,
|
||||||
|
0x5002e080, // WS_CHILD|WS_VISIBLE|WS_GROUP|SS_WORDELLIPSIS|SS_EDITCONTROL|SS_NOPREFIX
|
||||||
|
12, 10, 241, 16, dlg.wnd, 0, instance, 0)
|
||||||
|
|
||||||
|
var flags uintptr = 0x50000001 // WS_CHILD|WS_VISIBLE|PBS_SMOOTH
|
||||||
|
if opts.maxValue < 0 {
|
||||||
|
flags |= 0x8 // PBS_MARQUEE
|
||||||
|
}
|
||||||
|
dlg.progCtl, _, _ = createWindowEx.Call(0,
|
||||||
|
strptr("msctls_progress32"), // PROGRESS_CLASS
|
||||||
|
0, flags,
|
||||||
|
12, 30, 241, 24, dlg.wnd, 0, instance, 0)
|
||||||
|
|
||||||
|
dlg.okBtn, _, _ = createWindowEx.Call(0,
|
||||||
|
strptr("BUTTON"), strptr(*opts.okLabel),
|
||||||
|
0x58030001, // WS_CHILD|WS_VISIBLE|WS_DISABLED|WS_GROUP|WS_TABSTOP|BS_DEFPUSHBUTTON
|
||||||
|
12, 66, 75, 24, dlg.wnd, 1 /* IDOK */, instance, 0)
|
||||||
|
if !opts.noCancel {
|
||||||
|
dlg.cancelBtn, _, _ = createWindowEx.Call(0,
|
||||||
|
strptr("BUTTON"), strptr(*opts.cancelLabel),
|
||||||
|
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
|
||||||
|
12, 66, 75, 24, dlg.wnd, 2 /* IDCANCEL */, instance, 0)
|
||||||
|
}
|
||||||
|
if opts.extraButton != nil {
|
||||||
|
dlg.extraBtn, _, _ = createWindowEx.Call(0,
|
||||||
|
strptr("BUTTON"), strptr(*opts.extraButton),
|
||||||
|
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
|
||||||
|
12, 66, 75, 24, dlg.wnd, 7 /* IDNO */, instance, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
layout(getDPI(dlg.wnd))
|
||||||
|
centerWindow(dlg.wnd)
|
||||||
|
showWindow.Call(dlg.wnd, 1 /* SW_SHOWNORMAL */, 0)
|
||||||
|
if opts.maxValue < 0 {
|
||||||
|
sendMessage.Call(dlg.progCtl, 0x40a /* PBM_SETMARQUEE */, 1, 0)
|
||||||
|
} else {
|
||||||
|
sendMessage.Call(dlg.progCtl, 0x406 /* PBM_SETRANGE32 */, 0, uintptr(opts.maxValue))
|
||||||
|
}
|
||||||
|
dlg.init.Done()
|
||||||
|
|
||||||
|
if opts.ctx != nil {
|
||||||
|
wait := make(chan struct{})
|
||||||
|
defer close(wait)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-opts.ctx.Done():
|
||||||
|
sendMessage.Call(dlg.wnd, 0x0112 /* WM_SYSCOMMAND */, 0xf060 /* SC_CLOSE */, 0)
|
||||||
|
case <-wait:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// set default values
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
if err := messageLoop(dlg.wnd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
|
return opts.ctx.Err()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type progressDialog struct {
|
||||||
|
max int
|
||||||
|
done chan struct{}
|
||||||
|
init sync.WaitGroup
|
||||||
|
wnd uintptr
|
||||||
|
textCtl uintptr
|
||||||
|
progCtl uintptr
|
||||||
|
okBtn uintptr
|
||||||
|
cancelBtn uintptr
|
||||||
|
extraBtn uintptr
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) Text(text string) error {
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
setWindowText.Call(d.textCtl, strptr(text))
|
||||||
|
return nil
|
||||||
|
case <-d.done:
|
||||||
|
return d.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) Value(value int) error {
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
sendMessage.Call(d.progCtl, 0x402 /* PBM_SETPOS */, uintptr(value), 0)
|
||||||
|
if value >= d.max {
|
||||||
|
enableWindow.Call(d.okBtn, 1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case <-d.done:
|
||||||
|
return d.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) MaxValue() int {
|
||||||
|
return d.max
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) Done() <-chan struct{} {
|
||||||
|
return d.done
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) Complete() error {
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
setWindowLong.Call(d.progCtl, intptr(-16) /* GWL_STYLE */, 0x50000001 /* WS_CHILD|WS_VISIBLE|PBS_SMOOTH */)
|
||||||
|
sendMessage.Call(d.progCtl, 0x406 /* PBM_SETRANGE32 */, 0, 1)
|
||||||
|
sendMessage.Call(d.progCtl, 0x402 /* PBM_SETPOS */, 1, 0)
|
||||||
|
enableWindow.Call(d.okBtn, 1)
|
||||||
|
enableWindow.Call(d.cancelBtn, 0)
|
||||||
|
return nil
|
||||||
|
case <-d.done:
|
||||||
|
return d.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *progressDialog) Close() error {
|
||||||
|
sendMessage.Call(d.wnd, 0x0112 /* WM_SYSCOMMAND */, 0xf060 /* SC_CLOSE */, 0)
|
||||||
|
<-d.done
|
||||||
|
return d.err
|
||||||
|
}
|
4
pwd.go
4
pwd.go
|
@ -2,10 +2,8 @@ package zenity
|
||||||
|
|
||||||
// Password displays the password dialog.
|
// Password displays the password dialog.
|
||||||
//
|
//
|
||||||
// Returns false on cancel, or ErrExtraButton.
|
|
||||||
//
|
|
||||||
// Valid options: Title, OKLabel, CancelLabel, ExtraButton, Icon, Username.
|
// Valid options: Title, OKLabel, CancelLabel, ExtraButton, Icon, Username.
|
||||||
func Password(options ...Option) (usr string, pw string, ok bool, err error) {
|
func Password(options ...Option) (usr string, pw string, err error) {
|
||||||
return password(applyOptions(options))
|
return password(applyOptions(options))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,11 @@
|
||||||
|
|
||||||
package zenity
|
package zenity
|
||||||
|
|
||||||
func password(opts options) (string, string, bool, error) {
|
func password(opts options) (string, string, error) {
|
||||||
|
if opts.username {
|
||||||
|
return "", "", ErrUnsupported
|
||||||
|
}
|
||||||
opts.hideText = true
|
opts.hideText = true
|
||||||
str, ok, err := entry("Password:", opts)
|
str, err := entry("Password:", opts)
|
||||||
return "", str, ok, err
|
return "", str, err
|
||||||
}
|
}
|
||||||
|
|
14
pwd_test.go
14
pwd_test.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ncruces/zenity"
|
"github.com/ncruces/zenity"
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExamplePassword() {
|
func ExamplePassword() {
|
||||||
|
@ -15,22 +16,23 @@ func ExamplePassword() {
|
||||||
// Output:
|
// Output:
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPasswordTimeout(t *testing.T) {
|
func TestPassword_timeout(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
_, _, _, err := zenity.Password(zenity.Context(ctx))
|
_, _, err := zenity.Password(zenity.Context(ctx))
|
||||||
if !os.IsTimeout(err) {
|
if !os.IsTimeout(err) {
|
||||||
t.Error("did not timeout:", err)
|
t.Error("did not timeout:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPasswordCancel(t *testing.T) {
|
func TestPassword_cancel(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
_, _, _, err := zenity.Password(zenity.Context(ctx))
|
_, _, err := zenity.Password(zenity.Context(ctx))
|
||||||
if !errors.Is(err, context.Canceled) {
|
if !errors.Is(err, context.Canceled) {
|
||||||
t.Error("was not canceled:", err)
|
t.Error("was not canceled:", err)
|
||||||
}
|
}
|
||||||
|
|
10
pwd_unix.go
10
pwd_unix.go
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/ncruces/zenity/internal/zenutil"
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func password(opts options) (string, string, bool, error) {
|
func password(opts options) (string, string, error) {
|
||||||
args := []string{"--password"}
|
args := []string{"--password"}
|
||||||
args = appendTitle(args, opts)
|
args = appendTitle(args, opts)
|
||||||
args = appendButtons(args, opts)
|
args = appendButtons(args, opts)
|
||||||
|
@ -17,11 +17,11 @@ func password(opts options) (string, string, bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := zenutil.Run(opts.ctx, args)
|
out, err := zenutil.Run(opts.ctx, args)
|
||||||
str, ok, err := strResult(opts, out, err)
|
str, err := strResult(opts, out, err)
|
||||||
if ok && opts.username {
|
if err == nil && opts.username {
|
||||||
if split := strings.SplitN(string(out), "|", 2); len(split) == 2 {
|
if split := strings.SplitN(string(out), "|", 2); len(split) == 2 {
|
||||||
return split[0], split[1], true, nil
|
return split[0], split[1], nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", str, ok, err
|
return "", str, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@ package zenity
|
||||||
|
|
||||||
import "github.com/ncruces/zenity/internal/zenutil"
|
import "github.com/ncruces/zenity/internal/zenutil"
|
||||||
|
|
||||||
func getButtons(dialog, okcancel bool, opts options) (btns zenutil.Buttons) {
|
func getButtons(dialog, okcancel bool, opts options) (btns zenutil.DialogButtons) {
|
||||||
if !okcancel {
|
if !okcancel {
|
||||||
opts.cancelLabel = nil
|
opts.cancelLabel = nil
|
||||||
opts.defaultCancel = false
|
opts.defaultCancel = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.okLabel != nil || opts.cancelLabel != nil || opts.extraButton != nil || (dialog != okcancel) {
|
if opts.okLabel != nil || opts.cancelLabel != nil || opts.extraButton != nil || dialog != okcancel {
|
||||||
if opts.okLabel == nil {
|
if opts.okLabel == nil {
|
||||||
opts.okLabel = stringPtr("OK")
|
opts.okLabel = stringPtr("OK")
|
||||||
}
|
}
|
||||||
|
|
18
util_unix.go
18
util_unix.go
|
@ -3,7 +3,6 @@
|
||||||
package zenity
|
package zenity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -55,23 +54,22 @@ func appendIcon(args []string, opts options) []string {
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
func strResult(opts options, out []byte, err error) (string, bool, error) {
|
func strResult(opts options, out []byte, err error) (string, error) {
|
||||||
out = bytes.TrimSuffix(out, []byte{'\n'})
|
|
||||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
|
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
|
||||||
if opts.extraButton != nil && *opts.extraButton == string(out) {
|
if opts.extraButton != nil && *opts.extraButton+"\n" == string(out) {
|
||||||
return "", false, ErrExtraButton
|
return "", ErrExtraButton
|
||||||
}
|
}
|
||||||
return "", false, nil
|
return "", ErrCanceled
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, err
|
return "", err
|
||||||
}
|
}
|
||||||
return string(out), true, nil
|
return string(out), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func lstResult(opts options, out []byte, err error) ([]string, error) {
|
func lstResult(opts options, out []byte, err error) ([]string, error) {
|
||||||
str, ok, err := strResult(opts, out, err)
|
str, err := strResult(opts, out, err)
|
||||||
if ok {
|
if err == nil {
|
||||||
return strings.Split(str, zenutil.Separator), nil
|
return strings.Split(str, zenutil.Separator), nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
comctl32 = syscall.NewLazyDLL("comctl32.dll")
|
||||||
comdlg32 = syscall.NewLazyDLL("comdlg32.dll")
|
comdlg32 = syscall.NewLazyDLL("comdlg32.dll")
|
||||||
gdi32 = syscall.NewLazyDLL("gdi32.dll")
|
gdi32 = syscall.NewLazyDLL("gdi32.dll")
|
||||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
@ -21,6 +22,7 @@ var (
|
||||||
user32 = syscall.NewLazyDLL("user32.dll")
|
user32 = syscall.NewLazyDLL("user32.dll")
|
||||||
wtsapi32 = syscall.NewLazyDLL("wtsapi32.dll")
|
wtsapi32 = syscall.NewLazyDLL("wtsapi32.dll")
|
||||||
|
|
||||||
|
initCommonControlsEx = comctl32.NewProc("InitCommonControlsEx")
|
||||||
commDlgExtendedError = comdlg32.NewProc("CommDlgExtendedError")
|
commDlgExtendedError = comdlg32.NewProc("CommDlgExtendedError")
|
||||||
|
|
||||||
deleteObject = gdi32.NewProc("DeleteObject")
|
deleteObject = gdi32.NewProc("DeleteObject")
|
||||||
|
@ -30,6 +32,10 @@ var (
|
||||||
getModuleHandle = kernel32.NewProc("GetModuleHandleW")
|
getModuleHandle = kernel32.NewProc("GetModuleHandleW")
|
||||||
getCurrentThreadId = kernel32.NewProc("GetCurrentThreadId")
|
getCurrentThreadId = kernel32.NewProc("GetCurrentThreadId")
|
||||||
getConsoleWindow = kernel32.NewProc("GetConsoleWindow")
|
getConsoleWindow = kernel32.NewProc("GetConsoleWindow")
|
||||||
|
getSystemDirectory = kernel32.NewProc("GetSystemDirectoryW")
|
||||||
|
createActCtx = kernel32.NewProc("CreateActCtxW")
|
||||||
|
activateActCtx = kernel32.NewProc("ActivateActCtx")
|
||||||
|
deactivateActCtx = kernel32.NewProc("DeactivateActCtx")
|
||||||
|
|
||||||
coInitializeEx = ole32.NewProc("CoInitializeEx")
|
coInitializeEx = ole32.NewProc("CoInitializeEx")
|
||||||
coUninitialize = ole32.NewProc("CoUninitialize")
|
coUninitialize = ole32.NewProc("CoUninitialize")
|
||||||
|
@ -60,12 +66,14 @@ var (
|
||||||
systemParametersInfo = user32.NewProc("SystemParametersInfoW")
|
systemParametersInfo = user32.NewProc("SystemParametersInfoW")
|
||||||
setWindowPos = user32.NewProc("SetWindowPos")
|
setWindowPos = user32.NewProc("SetWindowPos")
|
||||||
getWindowRect = user32.NewProc("GetWindowRect")
|
getWindowRect = user32.NewProc("GetWindowRect")
|
||||||
|
setWindowLong = user32.NewProc("SetWindowLongPtrW")
|
||||||
getSystemMetrics = user32.NewProc("GetSystemMetrics")
|
getSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||||
unregisterClass = user32.NewProc("UnregisterClassW")
|
unregisterClass = user32.NewProc("UnregisterClassW")
|
||||||
registerClassEx = user32.NewProc("RegisterClassExW")
|
registerClassEx = user32.NewProc("RegisterClassExW")
|
||||||
destroyWindow = user32.NewProc("DestroyWindow")
|
destroyWindow = user32.NewProc("DestroyWindow")
|
||||||
createWindowEx = user32.NewProc("CreateWindowExW")
|
createWindowEx = user32.NewProc("CreateWindowExW")
|
||||||
showWindow = user32.NewProc("ShowWindow")
|
showWindow = user32.NewProc("ShowWindow")
|
||||||
|
enableWindow = user32.NewProc("EnableWindow")
|
||||||
setFocus = user32.NewProc("SetFocus")
|
setFocus = user32.NewProc("SetFocus")
|
||||||
defWindowProc = user32.NewProc("DefWindowProcW")
|
defWindowProc = user32.NewProc("DefWindowProcW")
|
||||||
)
|
)
|
||||||
|
@ -96,24 +104,33 @@ func setup() context.CancelFunc {
|
||||||
setForegroundWindow.Call(hwnd)
|
setForegroundWindow.Call(hwnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
var old uintptr
|
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
var restore uintptr
|
||||||
|
cookie := enableVisualStyles()
|
||||||
if setThreadDpiAwarenessContext.Find() == nil {
|
if setThreadDpiAwarenessContext.Find() == nil {
|
||||||
// try:
|
// try:
|
||||||
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
|
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
|
||||||
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE
|
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE
|
||||||
// DPI_AWARENESS_CONTEXT_SYSTEM_AWARE
|
// DPI_AWARENESS_CONTEXT_SYSTEM_AWARE
|
||||||
for i := -4; i <= -2; i++ {
|
for i := -4; i <= -2; i++ {
|
||||||
restore, _, _ := setThreadDpiAwarenessContext.Call(uintptr(i))
|
restore, _, _ = setThreadDpiAwarenessContext.Call(uintptr(i))
|
||||||
if restore != 0 {
|
if restore != 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var icc _INITCOMMONCONTROLSEX
|
||||||
|
icc.Size = uint32(unsafe.Sizeof(icc))
|
||||||
|
icc.ICC = 0x00004020 // ICC_STANDARD_CLASSES|ICC_PROGRESS_CLASS
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
if old != 0 {
|
if restore != 0 {
|
||||||
setThreadDpiAwarenessContext.Call(old)
|
setThreadDpiAwarenessContext.Call(restore)
|
||||||
|
}
|
||||||
|
if cookie != 0 {
|
||||||
|
deactivateActCtx.Call(cookie)
|
||||||
}
|
}
|
||||||
runtime.UnlockOSThread()
|
runtime.UnlockOSThread()
|
||||||
}
|
}
|
||||||
|
@ -122,7 +139,7 @@ func setup() context.CancelFunc {
|
||||||
func commDlgError() error {
|
func commDlgError() error {
|
||||||
s, _, _ := commDlgExtendedError.Call()
|
s, _, _ := commDlgExtendedError.Call()
|
||||||
if s == 0 {
|
if s == 0 {
|
||||||
return nil
|
return ErrCanceled
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Common Dialog error: %x", s)
|
return fmt.Errorf("Common Dialog error: %x", s)
|
||||||
}
|
}
|
||||||
|
@ -139,7 +156,7 @@ func hookDialog(ctx context.Context, initDialog func(wnd uintptr)) (unhook conte
|
||||||
hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET
|
hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET
|
||||||
syscall.NewCallback(func(code int32, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr {
|
syscall.NewCallback(func(code int32, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr {
|
||||||
if lparam.Message == 0x0110 { // WM_INITDIALOG
|
if lparam.Message == 0x0110 { // WM_INITDIALOG
|
||||||
name := [8]uint16{}
|
var name [8]uint16
|
||||||
getClassName.Call(lparam.Wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name)))
|
getClassName.Call(lparam.Wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name)))
|
||||||
if syscall.UTF16ToString(name[:]) == "#32770" { // The class for a dialog box
|
if syscall.UTF16ToString(name[:]) == "#32770" { // The class for a dialog box
|
||||||
var close bool
|
var close bool
|
||||||
|
@ -252,8 +269,8 @@ func (f *font) Delete() {
|
||||||
|
|
||||||
func centerWindow(wnd uintptr) {
|
func centerWindow(wnd uintptr) {
|
||||||
getMetric := func(i uintptr) int32 {
|
getMetric := func(i uintptr) int32 {
|
||||||
ret, _, _ := getSystemMetrics.Call(i)
|
n, _, _ := getSystemMetrics.Call(i)
|
||||||
return int32(ret)
|
return int32(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rect _RECT
|
var rect _RECT
|
||||||
|
@ -280,8 +297,8 @@ func registerClass(instance, proc uintptr) (uintptr, error) {
|
||||||
wcx.Background = 5 // COLOR_WINDOW
|
wcx.Background = 5 // COLOR_WINDOW
|
||||||
wcx.ClassName = syscall.StringToUTF16Ptr(name)
|
wcx.ClassName = syscall.StringToUTF16Ptr(name)
|
||||||
|
|
||||||
ret, _, err := registerClassEx.Call(uintptr(unsafe.Pointer(&wcx)))
|
atom, _, err := registerClassEx.Call(uintptr(unsafe.Pointer(&wcx)))
|
||||||
return ret, err
|
return atom, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues
|
// https://docs.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues
|
||||||
|
@ -293,22 +310,62 @@ func messageLoop(wnd uintptr) error {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var msg _MSG
|
var msg _MSG
|
||||||
ret, _, err := syscall.Syscall6(getMessage, 4, uintptr(unsafe.Pointer(&msg)), 0, 0, 0, 0, 0)
|
s, _, err := syscall.Syscall6(getMessage, 4, uintptr(unsafe.Pointer(&msg)), 0, 0, 0, 0, 0)
|
||||||
if int32(ret) == -1 {
|
if int32(s) == -1 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if ret == 0 {
|
if s == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ret, _, _ = syscall.Syscall(isDialogMessage, 2, wnd, uintptr(unsafe.Pointer(&msg)), 0)
|
s, _, _ = syscall.Syscall(isDialogMessage, 2, wnd, uintptr(unsafe.Pointer(&msg)), 0)
|
||||||
if ret == 0 {
|
if s == 0 {
|
||||||
syscall.Syscall(translateMessage, 1, uintptr(unsafe.Pointer(&msg)), 0, 0)
|
syscall.Syscall(translateMessage, 1, uintptr(unsafe.Pointer(&msg)), 0, 0)
|
||||||
syscall.Syscall(dispatchMessage, 1, uintptr(unsafe.Pointer(&msg)), 0, 0)
|
syscall.Syscall(dispatchMessage, 1, uintptr(unsafe.Pointer(&msg)), 0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/4308503/how-to-enable-visual-styles-without-a-manifest
|
||||||
|
func enableVisualStyles() (cookie uintptr) {
|
||||||
|
var dir [260]uint16
|
||||||
|
n, _, _ := getSystemDirectory.Call(uintptr(unsafe.Pointer(&dir[0])), uintptr(len(dir)))
|
||||||
|
if n == 0 || int(n) >= len(dir) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctx _ACTCTX
|
||||||
|
ctx.Size = uint32(unsafe.Sizeof(ctx))
|
||||||
|
ctx.Flags = 0x01c // ACTCTX_FLAG_RESOURCE_NAME_VALID|ACTCTX_FLAG_SET_PROCESS_DEFAULT|ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID
|
||||||
|
ctx.Source = syscall.StringToUTF16Ptr("shell32.dll")
|
||||||
|
ctx.AssemblyDirectory = &dir[0]
|
||||||
|
ctx.ResourceName = 124
|
||||||
|
|
||||||
|
if h, _, _ := createActCtx.Call(uintptr(unsafe.Pointer(&ctx))); h != 0 {
|
||||||
|
activateActCtx.Call(h, uintptr(unsafe.Pointer(&cookie)))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-actctxw
|
||||||
|
type _ACTCTX struct {
|
||||||
|
Size uint32
|
||||||
|
Flags uint32
|
||||||
|
Source *uint16
|
||||||
|
ProcessorArchitecture uint16
|
||||||
|
LangId uint16
|
||||||
|
AssemblyDirectory *uint16
|
||||||
|
ResourceName uintptr
|
||||||
|
ApplicationName *uint16
|
||||||
|
Module uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/commctrl/ns-commctrl-initcommoncontrolsex
|
||||||
|
type _INITCOMMONCONTROLSEX struct {
|
||||||
|
Size uint32
|
||||||
|
ICC uint32
|
||||||
|
}
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-cwpretstruct
|
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-cwpretstruct
|
||||||
type _CWPRETSTRUCT struct {
|
type _CWPRETSTRUCT struct {
|
||||||
Result uintptr
|
Result uintptr
|
||||||
|
|
21
zenity.go
21
zenity.go
|
@ -13,14 +13,22 @@ package zenity
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stringErr string
|
|
||||||
|
|
||||||
func (e stringErr) Error() string { return string(e) }
|
|
||||||
|
|
||||||
func stringPtr(s string) *string { return &s }
|
func stringPtr(s string) *string { return &s }
|
||||||
|
|
||||||
|
// ErrCanceled is returned when the cancel button is pressed,
|
||||||
|
// or window functions are used to close the dialog.
|
||||||
|
const ErrCanceled = zenutil.ErrCanceled
|
||||||
|
|
||||||
|
// ErrExtraButton is returned when the extra button is pressed.
|
||||||
|
const ErrExtraButton = zenutil.ErrExtraButton
|
||||||
|
|
||||||
|
// ErrUnsupported is returned when a combination of options is not supported.
|
||||||
|
const ErrUnsupported = zenutil.ErrUnsupported
|
||||||
|
|
||||||
type options struct {
|
type options struct {
|
||||||
// General options
|
// General options
|
||||||
title *string
|
title *string
|
||||||
|
@ -57,6 +65,11 @@ type options struct {
|
||||||
color color.Color
|
color color.Color
|
||||||
showPalette bool
|
showPalette bool
|
||||||
|
|
||||||
|
// Progress indication options
|
||||||
|
maxValue int
|
||||||
|
noCancel bool
|
||||||
|
timeRemaining bool
|
||||||
|
|
||||||
// Context for timeout
|
// Context for timeout
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue