Progress.
This commit is contained in:
parent
aeaa608758
commit
71541c249a
33 changed files with 464 additions and 177 deletions
|
@ -13,8 +13,9 @@ 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.
|
||||||
|
|
|
@ -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,7 +103,7 @@ 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 {
|
||||||
|
@ -135,12 +143,16 @@ func main() {
|
||||||
case colorSelectionDlg:
|
case colorSelectionDlg:
|
||||||
colResult(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() {
|
||||||
// Application Options
|
// Application Options
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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() {}
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ncruces/zenity"
|
"github.com/ncruces/zenity"
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleEntry() {
|
func ExampleEntry() {
|
||||||
|
@ -16,18 +17,19 @@ 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()
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ func entry(text string, opts options) (out string, 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
|
||||||
|
@ -36,7 +37,6 @@ func entry(text string, opts options) (out string, 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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
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) }
|
|
@ -10,5 +10,4 @@ var (
|
||||||
Command bool
|
Command bool
|
||||||
Timeout int
|
Timeout int
|
||||||
Separator = "\x00"
|
Separator = "\x00"
|
||||||
Canceled error
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,5 +13,4 @@ var (
|
||||||
Command bool
|
Command bool
|
||||||
Timeout int
|
Timeout int
|
||||||
Separator = "\x1e"
|
Separator = "\x1e"
|
||||||
Canceled error
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,5 +10,4 @@ var (
|
||||||
Command bool
|
Command bool
|
||||||
Timeout int
|
Timeout int
|
||||||
Separator string
|
Separator string
|
||||||
Canceled error
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -48,16 +48,19 @@ 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}}`))
|
{{- end}}
|
||||||
|
{{define "progress" -}}
|
||||||
var progress = `
|
|
||||||
var app=Application.currentApplication()
|
var app=Application.currentApplication()
|
||||||
app.includeStandardAdditions=true
|
app.includeStandardAdditions=true
|
||||||
app.activate()
|
app.activate()
|
||||||
ObjC.import('stdlib')
|
ObjC.import('stdlib')
|
||||||
ObjC.import('readline')
|
ObjC.import('readline')
|
||||||
try{Progress.totalUnitCount=$.getenv('total')}catch{}
|
{{- if .Total}}
|
||||||
try{Progress.description=$.getenv('description')}catch{}
|
Progress.totalUnitCount={{.Total}}
|
||||||
|
{{- end}}
|
||||||
|
{{- if .Description}}
|
||||||
|
Progress.description={{json .Description}}
|
||||||
|
{{- end}}
|
||||||
while(true){var s
|
while(true){var s
|
||||||
try{s=$.readline('')}catch(e){if(e.errorNumber===-128)$.exit(1)
|
try{s=$.readline('')}catch(e){if(e.errorNumber===-128)$.exit(1)
|
||||||
break}
|
break}
|
||||||
|
@ -65,4 +68,5 @@ if(s.indexOf('#')===0){Progress.additionalDescription=s.slice(1)
|
||||||
continue}
|
continue}
|
||||||
var i=parseInt(s)
|
var i=parseInt(s)
|
||||||
if(i>=0&&Progress.totalUnitCount>0){Progress.completedUnitCount=i
|
if(i>=0&&Progress.totalUnitCount>0){Progress.completedUnitCount=i
|
||||||
continue}}`
|
continue}}
|
||||||
|
{{- end}}`))
|
||||||
|
|
|
@ -21,11 +21,6 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var args struct {
|
|
||||||
Templates string
|
|
||||||
Progress string
|
|
||||||
}
|
|
||||||
|
|
||||||
var str strings.Builder
|
var str strings.Builder
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
|
@ -39,25 +34,19 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
name = strings.TrimSuffix(name, filepath.Ext(name))
|
|
||||||
if name == "progress" {
|
|
||||||
args.Progress = string(data)
|
|
||||||
} else {
|
|
||||||
str.WriteString("\n" + `{{define "`)
|
str.WriteString("\n" + `{{define "`)
|
||||||
str.WriteString(name)
|
str.WriteString(strings.TrimSuffix(name, filepath.Ext(name)))
|
||||||
str.WriteString(`" -}}` + "\n")
|
str.WriteString(`" -}}` + "\n")
|
||||||
str.Write(data)
|
str.Write(data)
|
||||||
str.WriteString("\n{{- end}}")
|
str.WriteString("\n{{- end}}")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
out, err := os.Create("osa_generated.go")
|
out, err := os.Create("osa_generated.go")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Templates = str.String()
|
err = generator.Execute(out, str.String())
|
||||||
err = generator.Execute(out, args)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -116,6 +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(` + "`{{.Templates}}`" + `))
|
}}).Parse(` + "`{{.}}`))\n"))
|
||||||
|
|
||||||
var progress = ` + "`\n{{.Progress}}`\n"))
|
|
||||||
|
|
|
@ -5,8 +5,12 @@ app.activate()
|
||||||
ObjC.import('stdlib')
|
ObjC.import('stdlib')
|
||||||
ObjC.import('readline')
|
ObjC.import('readline')
|
||||||
|
|
||||||
try { Progress.totalUnitCount = $.getenv('total') } catch { }
|
{{- if .Total}}
|
||||||
try { Progress.description = $.getenv('description') } catch { }
|
Progress.totalUnitCount = {{.Total}}
|
||||||
|
{{- end}}
|
||||||
|
{{- if .Description}}
|
||||||
|
Progress.description = {{json .Description}}
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var s
|
var s
|
|
@ -3,6 +3,7 @@
|
||||||
package zenutil
|
package zenutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -54,13 +55,9 @@ func (d *progressDialog) Done() <-chan struct{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *progressDialog) Complete() error {
|
func (d *progressDialog) Complete() error {
|
||||||
|
err := d.Value(d.max)
|
||||||
close(d.lines)
|
close(d.lines)
|
||||||
select {
|
return err
|
||||||
case <-d.done:
|
|
||||||
return d.err
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *progressDialog) Close() error {
|
func (d *progressDialog) Close() error {
|
||||||
|
@ -70,7 +67,7 @@ func (d *progressDialog) Close() error {
|
||||||
return d.err
|
return d.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *progressDialog) wait() {
|
func (d *progressDialog) wait(extra *string, out *bytes.Buffer) {
|
||||||
err := d.cmd.Wait()
|
err := d.cmd.Wait()
|
||||||
if cerr := d.ctx.Err(); cerr != nil {
|
if cerr := d.ctx.Err(); cerr != nil {
|
||||||
err = cerr
|
err = cerr
|
||||||
|
@ -80,7 +77,11 @@ func (d *progressDialog) wait() {
|
||||||
case eerr.ExitCode() == -1 && atomic.LoadInt32(&d.closed) != 0:
|
case eerr.ExitCode() == -1 && atomic.LoadInt32(&d.closed) != 0:
|
||||||
err = nil
|
err = nil
|
||||||
case eerr.ExitCode() == 1:
|
case eerr.ExitCode() == 1:
|
||||||
err = Canceled
|
if extra != nil && *extra+"\n" == string(out.Bytes()) {
|
||||||
|
err = ErrExtraButton
|
||||||
|
} else {
|
||||||
|
err = ErrCanceled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.err = err
|
d.err = err
|
||||||
|
@ -103,7 +104,10 @@ func (d *progressDialog) pipe(w io.WriteCloser) {
|
||||||
line = s
|
line = s
|
||||||
case <-d.ctx.Done():
|
case <-d.ctx.Done():
|
||||||
return
|
return
|
||||||
|
case <-d.done:
|
||||||
|
return
|
||||||
case <-time.After(timeout):
|
case <-time.After(timeout):
|
||||||
|
// line = ""
|
||||||
}
|
}
|
||||||
if _, err := w.Write([]byte(line + "\n")); err != nil {
|
if _, err := w.Write([]byte(line + "\n")); err != nil {
|
||||||
return
|
return
|
|
@ -7,27 +7,23 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 {
|
||||||
|
@ -38,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()
|
||||||
|
@ -46,45 +42,57 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunProgress is internal.
|
// RunProgress is internal.
|
||||||
func RunProgress(ctx context.Context, max int, env []string) (*progressDialog, error) {
|
func RunProgress(ctx context.Context, max int, data Progress) (dlg *progressDialog, err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = scripts.ExecuteTemplate(&buf, "progress", data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
t, err := ioutil.TempDir("", "")
|
t, err := ioutil.TempDir("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if ctx != nil && ctx.Err() != nil {
|
||||||
|
err = ctx.Err()
|
||||||
|
}
|
||||||
os.RemoveAll(t)
|
os.RemoveAll(t)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
name := filepath.Join(t, "progress.app")
|
name := filepath.Join(t, "progress.app")
|
||||||
|
|
||||||
cmd = exec.Command("osacompile", "-l", "JavaScript", "-o", name)
|
cmd = exec.CommandContext(ctx, "osacompile", "-l", "JavaScript", "-o", name)
|
||||||
cmd.Stdin = strings.NewReader(progress)
|
cmd.Stdin = &buf
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
plist := filepath.Join(name, "Contents/Info.plist")
|
plist := filepath.Join(name, "Contents/Info.plist")
|
||||||
|
|
||||||
cmd = exec.Command("defaults", "write", plist, "LSUIElement", "true")
|
cmd = exec.CommandContext(ctx, "defaults", "write", plist, "LSUIElement", "true")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = exec.Command("defaults", "write", plist, "CFBundleName", "")
|
cmd = exec.CommandContext(ctx, "defaults", "write", plist, "CFBundleName", "")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var executable string
|
var executable string
|
||||||
cmd = exec.Command("defaults", "read", plist, "CFBundleExecutable")
|
cmd = exec.CommandContext(ctx, "defaults", "read", plist, "CFBundleExecutable")
|
||||||
if out, err := cmd.Output(); err != nil {
|
if out, err := cmd.Output(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
|
@ -92,8 +100,7 @@ func RunProgress(ctx context.Context, max int, env []string) (*progressDialog, e
|
||||||
executable = filepath.Join(name, "Contents/MacOS", string(out))
|
executable = filepath.Join(name, "Contents/MacOS", string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = exec.Command(executable)
|
cmd = exec.CommandContext(ctx, executable)
|
||||||
cmd.Env = env
|
|
||||||
pipe, err := cmd.StdinPipe()
|
pipe, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -101,11 +108,9 @@ func RunProgress(ctx context.Context, max int, env []string) (*progressDialog, e
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if ctx == nil {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
dlg := &progressDialog{
|
dlg = &progressDialog{
|
||||||
|
ctx: ctx,
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
max: max,
|
max: max,
|
||||||
lines: make(chan string),
|
lines: make(chan string),
|
||||||
|
@ -114,7 +119,7 @@ func RunProgress(ctx context.Context, max int, env []string) (*progressDialog, e
|
||||||
go dlg.pipe(pipe)
|
go dlg.pipe(pipe)
|
||||||
go func() {
|
go func() {
|
||||||
defer os.RemoveAll(t)
|
defer os.RemoveAll(t)
|
||||||
dlg.wait()
|
dlg.wait(nil, nil)
|
||||||
}()
|
}()
|
||||||
return dlg, nil
|
return dlg, nil
|
||||||
}
|
}
|
||||||
|
@ -185,7 +190,6 @@ type File struct {
|
||||||
Options FileOptions
|
Options FileOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileOptions is internal.
|
|
||||||
type FileOptions struct {
|
type FileOptions struct {
|
||||||
Prompt *string `json:"withPrompt,omitempty"`
|
Prompt *string `json:"withPrompt,omitempty"`
|
||||||
Type []string `json:"ofType,omitempty"`
|
Type []string `json:"ofType,omitempty"`
|
||||||
|
@ -206,3 +210,9 @@ type NotifyOptions struct {
|
||||||
Title *string `json:"withTitle,omitempty"`
|
Title *string `json:"withTitle,omitempty"`
|
||||||
Subtitle string `json:"subtitle,omitempty"`
|
Subtitle string `json:"subtitle,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Progress is internal.
|
||||||
|
type Progress struct {
|
||||||
|
Description *string
|
||||||
|
Total *int
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package zenutil
|
package zenutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -42,25 +43,30 @@ func Run(ctx context.Context, args []string) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunProgress is internal.
|
// RunProgress is internal.
|
||||||
func RunProgress(ctx context.Context, max int, args []string) (*progressDialog, error) {
|
func RunProgress(ctx context.Context, max int, extra *string, args []string) (*progressDialog, error) {
|
||||||
if Command && path != "" {
|
if Command && path != "" {
|
||||||
if Timeout > 0 {
|
if Timeout > 0 {
|
||||||
args = append(args, "--timeout", strconv.Itoa(Timeout))
|
args = append(args, "--timeout", strconv.Itoa(Timeout))
|
||||||
}
|
}
|
||||||
syscall.Exec(path, append([]string{tool}, args...), os.Environ())
|
syscall.Exec(path, append([]string{tool}, args...), os.Environ())
|
||||||
}
|
}
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command(tool, args...)
|
cmd := exec.CommandContext(ctx, tool, args...)
|
||||||
pipe, err := cmd.StdinPipe()
|
pipe, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
var out *bytes.Buffer
|
||||||
|
if extra != nil {
|
||||||
|
out = &bytes.Buffer{}
|
||||||
|
cmd.Stdout = out
|
||||||
|
}
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if ctx == nil {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
dlg := &progressDialog{
|
dlg := &progressDialog{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
@ -71,6 +77,6 @@ func RunProgress(ctx context.Context, max int, args []string) (*progressDialog,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
go dlg.pipe(pipe)
|
go dlg.pipe(pipe)
|
||||||
go dlg.wait()
|
go dlg.wait(extra, out)
|
||||||
return dlg, nil
|
return dlg, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func list(text string, items []string, opts options) (string, 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, 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
|
||||||
|
|
10
list_test.go
10
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,18 +45,19 @@ 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()
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ func listDlg(text string, items []string, multiple bool, opts options) (out []st
|
||||||
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
|
||||||
|
@ -49,7 +50,6 @@ func listDlg(text string, items []string, multiple bool, opts options) (out []st
|
||||||
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
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ncruces/zenity"
|
"github.com/ncruces/zenity"
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleError() {
|
func ExampleError() {
|
||||||
|
@ -45,7 +46,7 @@ var msgFuncs = []func(string, ...zenity.Option) error{
|
||||||
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)
|
||||||
|
|
||||||
|
@ -55,10 +56,12 @@ func TestMessageTimeout(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ type ProgressDialog interface {
|
||||||
Done() <-chan struct{}
|
Done() <-chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxValue returns an Option to set the maximum value (macOS only).
|
// MaxValue returns an Option to set the maximum value (Windows and macOS only).
|
||||||
// The default maximum value is 100.
|
// The default maximum value is 100.
|
||||||
func MaxValue(value int) Option {
|
func MaxValue(value int) Option {
|
||||||
return funcOption(func(o *options) { o.maxValue = value })
|
return funcOption(func(o *options) { o.maxValue = value })
|
||||||
|
@ -40,7 +40,7 @@ func Pulsate() Option {
|
||||||
return funcOption(func(o *options) { o.maxValue = -1 })
|
return funcOption(func(o *options) { o.maxValue = -1 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoCancel returns an Option to hide the Cancel button (Unix only).
|
// NoCancel returns an Option to hide the Cancel button (Windows and Unix only).
|
||||||
func NoCancel() Option {
|
func NoCancel() Option {
|
||||||
return funcOption(func(o *options) { o.noCancel = true })
|
return funcOption(func(o *options) { o.noCancel = true })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
package zenity
|
package zenity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/ncruces/zenity/internal/zenutil"
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func progress(opts options) (ProgressDialog, error) {
|
func progress(opts options) (ProgressDialog, error) {
|
||||||
var env []string
|
if opts.extraButton != nil {
|
||||||
if opts.title != nil {
|
return nil, ErrUnsupported
|
||||||
env = append(env, "description="+*opts.title)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var data zenutil.Progress
|
||||||
|
data.Description = opts.title
|
||||||
if opts.maxValue == 0 {
|
if opts.maxValue == 0 {
|
||||||
opts.maxValue = 100
|
opts.maxValue = 100
|
||||||
}
|
}
|
||||||
if opts.maxValue >= 0 {
|
if opts.maxValue >= 0 {
|
||||||
env = append(env, "total="+strconv.Itoa(opts.maxValue))
|
data.Total = &opts.maxValue
|
||||||
}
|
}
|
||||||
return zenutil.RunProgress(opts.ctx, opts.maxValue, env)
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,5 +24,5 @@ func progress(opts options) (ProgressDialog, error) {
|
||||||
if opts.timeRemaining {
|
if opts.timeRemaining {
|
||||||
args = append(args, "--time-remaining")
|
args = append(args, "--time-remaining")
|
||||||
}
|
}
|
||||||
return zenutil.RunProgress(opts.ctx, opts.maxValue, args)
|
return zenutil.RunProgress(opts.ctx, opts.maxValue, opts.extraButton, args)
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,25 +48,31 @@ func progressDlg(opts options, dlg *progressDialog) (err error) {
|
||||||
defer font.Delete()
|
defer font.Delete()
|
||||||
defWindowProc := defWindowProc.Addr()
|
defWindowProc := defWindowProc.Addr()
|
||||||
|
|
||||||
var wnd, textCtl, progCtl uintptr
|
|
||||||
var okBtn, cancelBtn, extraBtn uintptr
|
|
||||||
|
|
||||||
layout := func(dpi dpi) {
|
layout := func(dpi dpi) {
|
||||||
hfont := font.ForDPI(dpi)
|
hfont := font.ForDPI(dpi)
|
||||||
sendMessage.Call(textCtl, 0x0030 /* WM_SETFONT */, hfont, 1)
|
sendMessage.Call(dlg.textCtl, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
sendMessage.Call(okBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
sendMessage.Call(dlg.okBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
sendMessage.Call(cancelBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
sendMessage.Call(dlg.cancelBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
setWindowPos.Call(wnd, 0, 0, 0, dpi.Scale(281), dpi.Scale(141), 0x6) // SWP_NOZORDER|SWP_NOMOVE
|
sendMessage.Call(dlg.extraBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||||
setWindowPos.Call(textCtl, 0, dpi.Scale(12), dpi.Scale(10), dpi.Scale(241), dpi.Scale(16), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(dlg.wnd, 0, 0, 0, dpi.Scale(281), dpi.Scale(141), 0x6) // SWP_NOZORDER|SWP_NOMOVE
|
||||||
setWindowPos.Call(progCtl, 0, dpi.Scale(12), dpi.Scale(30), dpi.Scale(241), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
setWindowPos.Call(dlg.textCtl, 0, dpi.Scale(12), dpi.Scale(10), dpi.Scale(241), dpi.Scale(16), 0x4) // SWP_NOZORDER
|
||||||
if extraBtn == 0 {
|
setWindowPos.Call(dlg.progCtl, 0, dpi.Scale(12), dpi.Scale(30), dpi.Scale(241), 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
|
if dlg.extraBtn == 0 {
|
||||||
setWindowPos.Call(cancelBtn, 0, dpi.Scale(178), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
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 {
|
} else {
|
||||||
sendMessage.Call(extraBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
setWindowPos.Call(dlg.okBtn, 0, dpi.Scale(95), 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(dlg.cancelBtn, 0, dpi.Scale(178), 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
|
} 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,54 +124,52 @@ func progressDlg(opts options, dlg *progressDialog) (err error) {
|
||||||
}
|
}
|
||||||
defer unregisterClass.Call(cls, instance)
|
defer unregisterClass.Call(cls, instance)
|
||||||
|
|
||||||
wnd, _, _ = createWindowEx.Call(0x10101, // WS_EX_CONTROLPARENT|WS_EX_WINDOWEDGE|WS_EX_DLGMODALFRAME
|
dlg.wnd, _, _ = createWindowEx.Call(0x10101, // WS_EX_CONTROLPARENT|WS_EX_WINDOWEDGE|WS_EX_DLGMODALFRAME
|
||||||
cls, strptr(*opts.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, 0)
|
281, 141, 0, 0, instance, 0)
|
||||||
|
|
||||||
textCtl, _, _ = createWindowEx.Call(0,
|
dlg.textCtl, _, _ = createWindowEx.Call(0,
|
||||||
strptr("STATIC"), 0,
|
strptr("STATIC"), 0,
|
||||||
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, 0)
|
12, 10, 241, 16, dlg.wnd, 0, instance, 0)
|
||||||
|
|
||||||
var flags uintptr = 0x50000001 // WS_CHILD|WS_VISIBLE|PBS_SMOOTH
|
var flags uintptr = 0x50000001 // WS_CHILD|WS_VISIBLE|PBS_SMOOTH
|
||||||
if opts.maxValue < 0 {
|
if opts.maxValue < 0 {
|
||||||
flags |= 0x8 // PBS_MARQUEE
|
flags |= 0x8 // PBS_MARQUEE
|
||||||
}
|
}
|
||||||
progCtl, _, _ = createWindowEx.Call(0,
|
dlg.progCtl, _, _ = createWindowEx.Call(0,
|
||||||
strptr("msctls_progress32"), // PROGRESS_CLASS
|
strptr("msctls_progress32"), // PROGRESS_CLASS
|
||||||
0, flags,
|
0, flags,
|
||||||
12, 30, 241, 24, wnd, 0, instance, 0)
|
12, 30, 241, 24, dlg.wnd, 0, instance, 0)
|
||||||
|
|
||||||
okBtn, _, _ = createWindowEx.Call(0,
|
dlg.okBtn, _, _ = createWindowEx.Call(0,
|
||||||
strptr("BUTTON"), strptr(*opts.okLabel),
|
strptr("BUTTON"), strptr(*opts.okLabel),
|
||||||
0x58030001, // WS_CHILD|WS_VISIBLE|WS_DISABLED|WS_GROUP|WS_TABSTOP|BS_DEFPUSHBUTTON
|
0x58030001, // WS_CHILD|WS_VISIBLE|WS_DISABLED|WS_GROUP|WS_TABSTOP|BS_DEFPUSHBUTTON
|
||||||
12, 66, 75, 24, wnd, 1 /* IDOK */, instance, 0)
|
12, 66, 75, 24, dlg.wnd, 1 /* IDOK */, instance, 0)
|
||||||
cancelBtn, _, _ = createWindowEx.Call(0,
|
if !opts.noCancel {
|
||||||
|
dlg.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, 0)
|
12, 66, 75, 24, dlg.wnd, 2 /* IDCANCEL */, instance, 0)
|
||||||
|
}
|
||||||
if opts.extraButton != nil {
|
if opts.extraButton != nil {
|
||||||
extraBtn, _, _ = createWindowEx.Call(0,
|
dlg.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, 0)
|
12, 66, 75, 24, dlg.wnd, 7 /* IDNO */, instance, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
layout(getDPI(wnd))
|
layout(getDPI(dlg.wnd))
|
||||||
centerWindow(wnd)
|
centerWindow(dlg.wnd)
|
||||||
showWindow.Call(wnd, 1 /* SW_SHOWNORMAL */, 0)
|
showWindow.Call(dlg.wnd, 1 /* SW_SHOWNORMAL */, 0)
|
||||||
if opts.maxValue < 0 {
|
if opts.maxValue < 0 {
|
||||||
sendMessage.Call(progCtl, 0x40a /* PBM_SETMARQUEE */, 1, 0)
|
sendMessage.Call(dlg.progCtl, 0x40a /* PBM_SETMARQUEE */, 1, 0)
|
||||||
} else {
|
} else {
|
||||||
sendMessage.Call(progCtl, 0x406 /* PBM_SETRANGE32 */, 0, uintptr(opts.maxValue))
|
sendMessage.Call(dlg.progCtl, 0x406 /* PBM_SETRANGE32 */, 0, uintptr(opts.maxValue))
|
||||||
}
|
}
|
||||||
dlg.prog = progCtl
|
|
||||||
dlg.text = textCtl
|
|
||||||
dlg.ok = okBtn
|
|
||||||
dlg.wnd = wnd
|
|
||||||
dlg.init.Done()
|
dlg.init.Done()
|
||||||
|
|
||||||
if opts.ctx != nil {
|
if opts.ctx != nil {
|
||||||
|
@ -174,7 +178,7 @@ func progressDlg(opts options, dlg *progressDialog) (err error) {
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-opts.ctx.Done():
|
case <-opts.ctx.Done():
|
||||||
sendMessage.Call(wnd, 0x0112 /* WM_SYSCOMMAND */, 0xf060 /* SC_CLOSE */, 0)
|
sendMessage.Call(dlg.wnd, 0x0112 /* WM_SYSCOMMAND */, 0xf060 /* SC_CLOSE */, 0)
|
||||||
case <-wait:
|
case <-wait:
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -183,7 +187,7 @@ func progressDlg(opts options, dlg *progressDialog) (err error) {
|
||||||
// set default values
|
// set default values
|
||||||
err = nil
|
err = nil
|
||||||
|
|
||||||
if err := messageLoop(wnd); err != nil {
|
if err := messageLoop(dlg.wnd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if opts.ctx != nil && opts.ctx.Err() != nil {
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
|
@ -193,26 +197,22 @@ func progressDlg(opts options, dlg *progressDialog) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type progressDialog struct {
|
type progressDialog struct {
|
||||||
err error
|
max int
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
init sync.WaitGroup
|
init sync.WaitGroup
|
||||||
prog uintptr
|
|
||||||
text uintptr
|
|
||||||
wnd uintptr
|
wnd uintptr
|
||||||
ok uintptr
|
textCtl uintptr
|
||||||
max int
|
progCtl uintptr
|
||||||
}
|
okBtn uintptr
|
||||||
|
cancelBtn uintptr
|
||||||
func (d *progressDialog) Close() error {
|
extraBtn uintptr
|
||||||
sendMessage.Call(d.wnd, 0x0112 /* WM_SYSCOMMAND */, 0xf060 /* SC_CLOSE */, 0)
|
err error
|
||||||
<-d.done
|
|
||||||
return d.err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *progressDialog) Text(text string) error {
|
func (d *progressDialog) Text(text string) error {
|
||||||
select {
|
select {
|
||||||
default:
|
default:
|
||||||
setWindowText.Call(d.text, strptr(text))
|
setWindowText.Call(d.textCtl, strptr(text))
|
||||||
return nil
|
return nil
|
||||||
case <-d.done:
|
case <-d.done:
|
||||||
return d.err
|
return d.err
|
||||||
|
@ -222,9 +222,9 @@ func (d *progressDialog) Text(text string) error {
|
||||||
func (d *progressDialog) Value(value int) error {
|
func (d *progressDialog) Value(value int) error {
|
||||||
select {
|
select {
|
||||||
default:
|
default:
|
||||||
sendMessage.Call(d.prog, 0x402 /* PBM_SETPOS */, uintptr(value), 0)
|
sendMessage.Call(d.progCtl, 0x402 /* PBM_SETPOS */, uintptr(value), 0)
|
||||||
if value >= d.max {
|
if value >= d.max {
|
||||||
enableWindow.Call(d.ok, 1)
|
enableWindow.Call(d.okBtn, 1)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case <-d.done:
|
case <-d.done:
|
||||||
|
@ -239,3 +239,23 @@ func (d *progressDialog) MaxValue() int {
|
||||||
func (d *progressDialog) Done() <-chan struct{} {
|
func (d *progressDialog) Done() <-chan struct{} {
|
||||||
return d.done
|
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
|
||||||
|
}
|
||||||
|
|
10
pwd_test.go
10
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,18 +16,19 @@ 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()
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
package zenity
|
package zenity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -56,9 +55,8 @@ func appendIcon(args []string, opts options) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func strResult(opts options, out []byte, err error) (string, 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 "", ErrExtraButton
|
return "", ErrExtraButton
|
||||||
}
|
}
|
||||||
return "", ErrCanceled
|
return "", ErrCanceled
|
||||||
|
|
|
@ -66,6 +66,7 @@ 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")
|
||||||
|
|
14
zenity.go
14
zenity.go
|
@ -17,25 +17,17 @@ import (
|
||||||
"github.com/ncruces/zenity/internal/zenutil"
|
"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,
|
// ErrCanceled is returned when the cancel button is pressed,
|
||||||
// or window functions are used to close the dialog.
|
// or window functions are used to close the dialog.
|
||||||
const ErrCanceled = stringErr("dialog canceled")
|
const ErrCanceled = zenutil.ErrCanceled
|
||||||
|
|
||||||
// ErrExtraButton is returned when the extra button is pressed.
|
// ErrExtraButton is returned when the extra button is pressed.
|
||||||
const ErrExtraButton = stringErr("extra button pressed")
|
const ErrExtraButton = zenutil.ErrExtraButton
|
||||||
|
|
||||||
// ErrUnsupported is returned when a combination of options is not supported.
|
// ErrUnsupported is returned when a combination of options is not supported.
|
||||||
const ErrUnsupported = stringErr("unsupported option")
|
const ErrUnsupported = zenutil.ErrUnsupported
|
||||||
|
|
||||||
func init() {
|
|
||||||
zenutil.Canceled = ErrCanceled
|
|
||||||
}
|
|
||||||
|
|
||||||
type options struct {
|
type options struct {
|
||||||
// General options
|
// General options
|
||||||
|
|
Loading…
Reference in a new issue