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)
|
||||
* [list](https://github.com/ncruces/zenity/wiki/List-dialog) (simple)
|
||||
* [password](https://github.com/ncruces/zenity/wiki/Password-dialog)
|
||||
* [file selection](https://github.com/ncruces/zenity/wiki/File-Selection-dialog)
|
||||
* [color selection](https://github.com/ncruces/zenity/wiki/Color-Selection-dialog)
|
||||
* [file selection](https://github.com/ncruces/zenity/wiki/File-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)
|
||||
|
||||
Behavior on Windows, macOS and other Unixes might differ slightly.
|
||||
|
|
|
@ -34,6 +34,7 @@ var (
|
|||
passwordDlg bool
|
||||
fileSelectionDlg bool
|
||||
colorSelectionDlg bool
|
||||
progressDlg bool
|
||||
notification bool
|
||||
|
||||
// General options
|
||||
|
@ -73,6 +74,13 @@ var (
|
|||
defaultColor string
|
||||
showPalette bool
|
||||
|
||||
// Progress options
|
||||
percentage float64
|
||||
pulsate bool
|
||||
autoClose bool
|
||||
autoKill bool
|
||||
noCancel bool
|
||||
|
||||
// Windows specific options
|
||||
cygpath bool
|
||||
wslpath bool
|
||||
|
@ -95,7 +103,7 @@ func main() {
|
|||
if zenutil.Timeout > 0 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(zenutil.Timeout)*time.Second)
|
||||
opts = append(opts, zenity.Context(ctx))
|
||||
_ = cancel
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
switch {
|
||||
|
@ -135,11 +143,15 @@ func main() {
|
|||
case colorSelectionDlg:
|
||||
colResult(zenity.SelectColor(opts...))
|
||||
|
||||
case progressDlg:
|
||||
errResult(progress(opts...))
|
||||
|
||||
case notification:
|
||||
errResult(zenity.Notify(text, opts...))
|
||||
}
|
||||
|
||||
flag.Usage()
|
||||
default:
|
||||
flag.Usage()
|
||||
}
|
||||
}
|
||||
|
||||
func setupFlags() {
|
||||
|
@ -153,6 +165,7 @@ func setupFlags() {
|
|||
flag.BoolVar(&passwordDlg, "password", false, "Display password dialog")
|
||||
flag.BoolVar(&fileSelectionDlg, "file-selection", false, "Display file 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")
|
||||
|
||||
// General options
|
||||
|
@ -165,12 +178,12 @@ func setupFlags() {
|
|||
flag.StringVar(&text, "text", "", "Set the dialog `text`")
|
||||
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(&defaultCancel, "default-cancel", false, "Give Cancel button focus by default")
|
||||
|
||||
// Message options
|
||||
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(&ellipsize, "ellipsize", false, "Enable ellipsizing in the dialog text")
|
||||
flag.BoolVar(&defaultCancel, "default-cancel", false, "Give Cancel button focus by default")
|
||||
|
||||
// Entry options
|
||||
flag.StringVar(&entryText, "entry-text", "", "Set the entry `text`")
|
||||
|
@ -194,6 +207,15 @@ func setupFlags() {
|
|||
flag.StringVar(&defaultColor, "color", "", "Set the `color`")
|
||||
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
|
||||
if runtime.GOOS == "windows" {
|
||||
flag.BoolVar(&cygpath, "cygpath", false, "Use cygpath for path translation (Windows only)")
|
||||
|
@ -242,6 +264,9 @@ func validateFlags() {
|
|||
if colorSelectionDlg {
|
||||
n++
|
||||
}
|
||||
if progressDlg {
|
||||
n++
|
||||
}
|
||||
if notification {
|
||||
n++
|
||||
}
|
||||
|
@ -297,6 +322,11 @@ func loadFlags() []zenity.Option {
|
|||
setDefault(&icon, "dialog-password")
|
||||
setDefault(&okLabel, "OK")
|
||||
setDefault(&cancelLabel, "Cancel")
|
||||
case progressDlg:
|
||||
setDefault(&title, "Progress")
|
||||
setDefault(&text, "Running...")
|
||||
setDefault(&okLabel, "OK")
|
||||
setDefault(&cancelLabel, "Cancel")
|
||||
default:
|
||||
setDefault(&text, "")
|
||||
}
|
||||
|
@ -388,6 +418,15 @@ func loadFlags() []zenity.Option {
|
|||
opts = append(opts, zenity.ShowPalette())
|
||||
}
|
||||
|
||||
// Progress options
|
||||
|
||||
if pulsate {
|
||||
opts = append(opts, zenity.Pulsate())
|
||||
}
|
||||
if noCancel {
|
||||
opts = append(opts, zenity.NoCancel())
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
"github.com/ncruces/zenity"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func ExampleSelectColor() {
|
||||
|
@ -24,18 +25,19 @@ func ExampleSelectColor_palette() {
|
|||
// 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)
|
||||
defer cancel()
|
||||
|
||||
_, err := zenity.SelectColor(zenity.Context(ctx))
|
||||
if !os.IsTimeout(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())
|
||||
cancel()
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ncruces/zenity"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func ExampleEntry() {
|
||||
|
@ -16,18 +17,19 @@ func ExampleEntry() {
|
|||
// 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)
|
||||
defer cancel()
|
||||
|
||||
_, err := zenity.Entry("", zenity.Context(ctx))
|
||||
if !os.IsTimeout(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())
|
||||
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(okBtn, 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(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
|
||||
|
@ -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(cancelBtn, 0, dpi.Scale(178), dpi.Scale(66), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||
} 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(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
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ncruces/zenity"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
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 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
||||
|
||||
|
@ -87,10 +88,12 @@ func TestFileTimeout(t *testing.T) {
|
|||
}
|
||||
|
||||
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())
|
||||
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
|
||||
Timeout int
|
||||
Separator = "\x00"
|
||||
Canceled error
|
||||
)
|
||||
|
|
|
@ -13,5 +13,4 @@ var (
|
|||
Command bool
|
||||
Timeout int
|
||||
Separator = "\x1e"
|
||||
Canceled error
|
||||
)
|
||||
|
|
|
@ -10,5 +10,4 @@ var (
|
|||
Command bool
|
||||
Timeout int
|
||||
Separator string
|
||||
Canceled error
|
||||
)
|
||||
|
|
|
@ -48,16 +48,19 @@ res.join({{json .Separator}})
|
|||
var app=Application.currentApplication()
|
||||
app.includeStandardAdditions=true
|
||||
void app.displayNotification({{json .Text}},{{json .Options}})
|
||||
{{- end}}`))
|
||||
|
||||
var progress = `
|
||||
{{- end}}
|
||||
{{define "progress" -}}
|
||||
var app=Application.currentApplication()
|
||||
app.includeStandardAdditions=true
|
||||
app.activate()
|
||||
ObjC.import('stdlib')
|
||||
ObjC.import('readline')
|
||||
try{Progress.totalUnitCount=$.getenv('total')}catch{}
|
||||
try{Progress.description=$.getenv('description')}catch{}
|
||||
{{- 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}
|
||||
|
@ -65,4 +68,5 @@ if(s.indexOf('#')===0){Progress.additionalDescription=s.slice(1)
|
|||
continue}
|
||||
var i=parseInt(s)
|
||||
if(i>=0&&Progress.totalUnitCount>0){Progress.completedUnitCount=i
|
||||
continue}}`
|
||||
continue}}
|
||||
{{- end}}`))
|
||||
|
|
|
@ -21,11 +21,6 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var args struct {
|
||||
Templates string
|
||||
Progress string
|
||||
}
|
||||
|
||||
var str strings.Builder
|
||||
|
||||
for _, file := range files {
|
||||
|
@ -39,16 +34,11 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
name = strings.TrimSuffix(name, filepath.Ext(name))
|
||||
if name == "progress" {
|
||||
args.Progress = string(data)
|
||||
} else {
|
||||
str.WriteString("\n" + `{{define "`)
|
||||
str.WriteString(name)
|
||||
str.WriteString(`" -}}` + "\n")
|
||||
str.Write(data)
|
||||
str.WriteString("\n{{- end}}")
|
||||
}
|
||||
str.WriteString("\n" + `{{define "`)
|
||||
str.WriteString(strings.TrimSuffix(name, filepath.Ext(name)))
|
||||
str.WriteString(`" -}}` + "\n")
|
||||
str.Write(data)
|
||||
str.WriteString("\n{{- end}}")
|
||||
}
|
||||
|
||||
out, err := os.Create("osa_generated.go")
|
||||
|
@ -56,8 +46,7 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
args.Templates = str.String()
|
||||
err = generator.Execute(out, args)
|
||||
err = generator.Execute(out, str.String())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -116,6 +105,4 @@ import (
|
|||
var scripts = template.Must(template.New("").Funcs(template.FuncMap{"json": func(v interface{}) (string, error) {
|
||||
b, err := json.Marshal(v)
|
||||
return string(b), err
|
||||
}}).Parse(` + "`{{.Templates}}`" + `))
|
||||
|
||||
var progress = ` + "`\n{{.Progress}}`\n"))
|
||||
}}).Parse(` + "`{{.}}`))\n"))
|
||||
|
|
|
@ -5,8 +5,12 @@ app.activate()
|
|||
ObjC.import('stdlib')
|
||||
ObjC.import('readline')
|
||||
|
||||
try { Progress.totalUnitCount = $.getenv('total') } catch { }
|
||||
try { Progress.description = $.getenv('description') } catch { }
|
||||
{{- if .Total}}
|
||||
Progress.totalUnitCount = {{.Total}}
|
||||
{{- end}}
|
||||
{{- if .Description}}
|
||||
Progress.description = {{json .Description}}
|
||||
{{- end}}
|
||||
|
||||
while (true) {
|
||||
var s
|
|
@ -3,6 +3,7 @@
|
|||
package zenutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
|
@ -54,13 +55,9 @@ func (d *progressDialog) Done() <-chan struct{} {
|
|||
}
|
||||
|
||||
func (d *progressDialog) Complete() error {
|
||||
err := d.Value(d.max)
|
||||
close(d.lines)
|
||||
select {
|
||||
case <-d.done:
|
||||
return d.err
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *progressDialog) Close() error {
|
||||
|
@ -70,7 +67,7 @@ func (d *progressDialog) Close() error {
|
|||
return d.err
|
||||
}
|
||||
|
||||
func (d *progressDialog) wait() {
|
||||
func (d *progressDialog) wait(extra *string, out *bytes.Buffer) {
|
||||
err := d.cmd.Wait()
|
||||
if cerr := d.ctx.Err(); cerr != nil {
|
||||
err = cerr
|
||||
|
@ -80,7 +77,11 @@ func (d *progressDialog) wait() {
|
|||
case eerr.ExitCode() == -1 && atomic.LoadInt32(&d.closed) != 0:
|
||||
err = nil
|
||||
case eerr.ExitCode() == 1:
|
||||
err = Canceled
|
||||
if extra != nil && *extra+"\n" == string(out.Bytes()) {
|
||||
err = ErrExtraButton
|
||||
} else {
|
||||
err = ErrCanceled
|
||||
}
|
||||
}
|
||||
}
|
||||
d.err = err
|
||||
|
@ -103,7 +104,10 @@ func (d *progressDialog) pipe(w io.WriteCloser) {
|
|||
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
|
|
@ -7,27 +7,23 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Run is internal.
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
script = buf.String()
|
||||
if Command {
|
||||
// Try to use syscall.Exec, fallback to exec.Command.
|
||||
if path, err := exec.LookPath("osascript"); err != nil {
|
||||
} else if t, err := ioutil.TempFile("", ""); 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 := syscall.Dup2(int(t.Fd()), syscall.Stdin); 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 {
|
||||
cmd := exec.CommandContext(ctx, "osascript", "-l", "JavaScript")
|
||||
cmd.Stdin = strings.NewReader(script)
|
||||
cmd.Stdin = &buf
|
||||
out, err := cmd.Output()
|
||||
if ctx.Err() != nil {
|
||||
err = ctx.Err()
|
||||
|
@ -46,45 +42,57 @@ func Run(ctx context.Context, script string, data interface{}) ([]byte, error) {
|
|||
return out, err
|
||||
}
|
||||
cmd := exec.Command("osascript", "-l", "JavaScript")
|
||||
cmd.Stdin = strings.NewReader(script)
|
||||
cmd.Stdin = &buf
|
||||
return cmd.Output()
|
||||
}
|
||||
|
||||
// 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("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if ctx != nil && ctx.Err() != nil {
|
||||
err = ctx.Err()
|
||||
}
|
||||
os.RemoveAll(t)
|
||||
}
|
||||
}()
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
name := filepath.Join(t, "progress.app")
|
||||
|
||||
cmd = exec.Command("osacompile", "-l", "JavaScript", "-o", name)
|
||||
cmd.Stdin = strings.NewReader(progress)
|
||||
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.Command("defaults", "write", plist, "LSUIElement", "true")
|
||||
cmd = exec.CommandContext(ctx, "defaults", "write", plist, "LSUIElement", "true")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd = exec.Command("defaults", "write", plist, "CFBundleName", "")
|
||||
cmd = exec.CommandContext(ctx, "defaults", "write", plist, "CFBundleName", "")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
} else {
|
||||
|
@ -92,8 +100,7 @@ func RunProgress(ctx context.Context, max int, env []string) (*progressDialog, e
|
|||
executable = filepath.Join(name, "Contents/MacOS", string(out))
|
||||
}
|
||||
|
||||
cmd = exec.Command(executable)
|
||||
cmd.Env = env
|
||||
cmd = exec.CommandContext(ctx, executable)
|
||||
pipe, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -101,11 +108,9 @@ func RunProgress(ctx context.Context, max int, env []string) (*progressDialog, e
|
|||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
dlg := &progressDialog{
|
||||
dlg = &progressDialog{
|
||||
ctx: ctx,
|
||||
cmd: cmd,
|
||||
max: max,
|
||||
lines: make(chan string),
|
||||
|
@ -114,7 +119,7 @@ func RunProgress(ctx context.Context, max int, env []string) (*progressDialog, e
|
|||
go dlg.pipe(pipe)
|
||||
go func() {
|
||||
defer os.RemoveAll(t)
|
||||
dlg.wait()
|
||||
dlg.wait(nil, nil)
|
||||
}()
|
||||
return dlg, nil
|
||||
}
|
||||
|
@ -185,7 +190,6 @@ type File struct {
|
|||
Options FileOptions
|
||||
}
|
||||
|
||||
// FileOptions is internal.
|
||||
type FileOptions struct {
|
||||
Prompt *string `json:"withPrompt,omitempty"`
|
||||
Type []string `json:"ofType,omitempty"`
|
||||
|
@ -206,3 +210,9 @@ type NotifyOptions struct {
|
|||
Title *string `json:"withTitle,omitempty"`
|
||||
Subtitle string `json:"subtitle,omitempty"`
|
||||
}
|
||||
|
||||
// Progress is internal.
|
||||
type Progress struct {
|
||||
Description *string
|
||||
Total *int
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
package zenutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -42,25 +43,30 @@ func Run(ctx context.Context, args []string) ([]byte, error) {
|
|||
}
|
||||
|
||||
// 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 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.Command(tool, args...)
|
||||
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
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
dlg := &progressDialog{
|
||||
ctx: ctx,
|
||||
|
@ -71,6 +77,6 @@ func RunProgress(ctx context.Context, max int, args []string) (*progressDialog,
|
|||
done: make(chan struct{}),
|
||||
}
|
||||
go dlg.pipe(pipe)
|
||||
go dlg.wait()
|
||||
go dlg.wait(extra, out)
|
||||
return dlg, nil
|
||||
}
|
||||
|
|
|
@ -5,6 +5,10 @@ import (
|
|||
)
|
||||
|
||||
func list(text string, items []string, opts options) (string, error) {
|
||||
if opts.extraButton != nil {
|
||||
return "", ErrUnsupported
|
||||
}
|
||||
|
||||
var data zenutil.List
|
||||
data.Items = items
|
||||
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) {
|
||||
if opts.extraButton != nil {
|
||||
return nil, ErrUnsupported
|
||||
}
|
||||
|
||||
var data zenutil.List
|
||||
data.Items = items
|
||||
data.Options.Prompt = &text
|
||||
|
|
10
list_test.go
10
list_test.go
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ncruces/zenity"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func ExampleList() {
|
||||
|
@ -44,18 +45,19 @@ func ExampleListMultipleItems() {
|
|||
// 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)
|
||||
defer cancel()
|
||||
|
||||
_, err := zenity.List("", nil, zenity.Context(ctx))
|
||||
if !os.IsTimeout(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())
|
||||
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(okBtn, 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(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
|
||||
|
@ -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(cancelBtn, 0, dpi.Scale(178), dpi.Scale(206), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||
} 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(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
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ncruces/zenity"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func ExampleError() {
|
||||
|
@ -45,7 +46,7 @@ var msgFuncs = []func(string, ...zenity.Option) error{
|
|||
zenity.Question,
|
||||
}
|
||||
|
||||
func TestMessageTimeout(t *testing.T) {
|
||||
func TestMessage_timeout(t *testing.T) {
|
||||
for _, f := range msgFuncs {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second/10)
|
||||
|
||||
|
@ -55,10 +56,12 @@ func TestMessageTimeout(t *testing.T) {
|
|||
}
|
||||
|
||||
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())
|
||||
cancel()
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ncruces/zenity"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func ExampleNotify() {
|
||||
|
@ -15,7 +16,8 @@ func ExampleNotify() {
|
|||
// Output:
|
||||
}
|
||||
|
||||
func TestNotifyCancel(t *testing.T) {
|
||||
func TestNotify_cancel(t *testing.T) {
|
||||
defer goleak.VerifyNone(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ type ProgressDialog interface {
|
|||
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.
|
||||
func MaxValue(value int) Option {
|
||||
return funcOption(func(o *options) { o.maxValue = value })
|
||||
|
@ -40,7 +40,7 @@ func Pulsate() Option {
|
|||
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 {
|
||||
return funcOption(func(o *options) { o.noCancel = true })
|
||||
}
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
package zenity
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/ncruces/zenity/internal/zenutil"
|
||||
)
|
||||
|
||||
func progress(opts options) (ProgressDialog, error) {
|
||||
var env []string
|
||||
if opts.title != nil {
|
||||
env = append(env, "description="+*opts.title)
|
||||
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 {
|
||||
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 {
|
||||
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()
|
||||
defWindowProc := defWindowProc.Addr()
|
||||
|
||||
var wnd, textCtl, progCtl uintptr
|
||||
var okBtn, cancelBtn, extraBtn uintptr
|
||||
|
||||
layout := func(dpi dpi) {
|
||||
hfont := font.ForDPI(dpi)
|
||||
sendMessage.Call(textCtl, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||
sendMessage.Call(okBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||
sendMessage.Call(cancelBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
|
||||
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(progCtl, 0, dpi.Scale(12), dpi.Scale(30), dpi.Scale(241), dpi.Scale(24), 0x4) // SWP_NOZORDER
|
||||
if extraBtn == 0 {
|
||||
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
|
||||
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 {
|
||||
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(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
|
||||
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)
|
||||
|
||||
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),
|
||||
0x84c80000, // WS_POPUPWINDOW|WS_CLIPSIBLINGS|WS_DLGFRAME
|
||||
0x80000000, // CW_USEDEFAULT
|
||||
0x80000000, // CW_USEDEFAULT
|
||||
281, 141, 0, 0, instance, 0)
|
||||
|
||||
textCtl, _, _ = createWindowEx.Call(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, wnd, 0, instance, 0)
|
||||
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
|
||||
}
|
||||
progCtl, _, _ = createWindowEx.Call(0,
|
||||
dlg.progCtl, _, _ = createWindowEx.Call(0,
|
||||
strptr("msctls_progress32"), // PROGRESS_CLASS
|
||||
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),
|
||||
0x58030001, // WS_CHILD|WS_VISIBLE|WS_DISABLED|WS_GROUP|WS_TABSTOP|BS_DEFPUSHBUTTON
|
||||
12, 66, 75, 24, wnd, 1 /* IDOK */, instance, 0)
|
||||
cancelBtn, _, _ = createWindowEx.Call(0,
|
||||
strptr("BUTTON"), strptr(*opts.cancelLabel),
|
||||
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
|
||||
12, 66, 75, 24, wnd, 2 /* IDCANCEL */, instance, 0)
|
||||
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 {
|
||||
extraBtn, _, _ = createWindowEx.Call(0,
|
||||
dlg.extraBtn, _, _ = createWindowEx.Call(0,
|
||||
strptr("BUTTON"), strptr(*opts.extraButton),
|
||||
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))
|
||||
centerWindow(wnd)
|
||||
showWindow.Call(wnd, 1 /* SW_SHOWNORMAL */, 0)
|
||||
layout(getDPI(dlg.wnd))
|
||||
centerWindow(dlg.wnd)
|
||||
showWindow.Call(dlg.wnd, 1 /* SW_SHOWNORMAL */, 0)
|
||||
if opts.maxValue < 0 {
|
||||
sendMessage.Call(progCtl, 0x40a /* PBM_SETMARQUEE */, 1, 0)
|
||||
sendMessage.Call(dlg.progCtl, 0x40a /* PBM_SETMARQUEE */, 1, 0)
|
||||
} 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()
|
||||
|
||||
if opts.ctx != nil {
|
||||
|
@ -174,7 +178,7 @@ func progressDlg(opts options, dlg *progressDialog) (err error) {
|
|||
go func() {
|
||||
select {
|
||||
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:
|
||||
}
|
||||
}()
|
||||
|
@ -183,7 +187,7 @@ func progressDlg(opts options, dlg *progressDialog) (err error) {
|
|||
// set default values
|
||||
err = nil
|
||||
|
||||
if err := messageLoop(wnd); err != nil {
|
||||
if err := messageLoop(dlg.wnd); err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||
|
@ -193,26 +197,22 @@ func progressDlg(opts options, dlg *progressDialog) (err error) {
|
|||
}
|
||||
|
||||
type progressDialog struct {
|
||||
err error
|
||||
done chan struct{}
|
||||
init sync.WaitGroup
|
||||
prog uintptr
|
||||
text uintptr
|
||||
wnd uintptr
|
||||
ok uintptr
|
||||
max int
|
||||
}
|
||||
|
||||
func (d *progressDialog) Close() error {
|
||||
sendMessage.Call(d.wnd, 0x0112 /* WM_SYSCOMMAND */, 0xf060 /* SC_CLOSE */, 0)
|
||||
<-d.done
|
||||
return d.err
|
||||
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.text, strptr(text))
|
||||
setWindowText.Call(d.textCtl, strptr(text))
|
||||
return nil
|
||||
case <-d.done:
|
||||
return d.err
|
||||
|
@ -222,9 +222,9 @@ func (d *progressDialog) Text(text string) error {
|
|||
func (d *progressDialog) Value(value int) error {
|
||||
select {
|
||||
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 {
|
||||
enableWindow.Call(d.ok, 1)
|
||||
enableWindow.Call(d.okBtn, 1)
|
||||
}
|
||||
return nil
|
||||
case <-d.done:
|
||||
|
@ -239,3 +239,23 @@ func (d *progressDialog) MaxValue() int {
|
|||
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
|
||||
}
|
||||
|
|
10
pwd_test.go
10
pwd_test.go
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ncruces/zenity"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func ExamplePassword() {
|
||||
|
@ -15,18 +16,19 @@ func ExamplePassword() {
|
|||
// 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)
|
||||
defer cancel()
|
||||
|
||||
_, _, err := zenity.Password(zenity.Context(ctx))
|
||||
if !os.IsTimeout(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())
|
||||
cancel()
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
package zenity
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -56,9 +55,8 @@ func appendIcon(args []string, opts options) []string {
|
|||
}
|
||||
|
||||
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 opts.extraButton != nil && *opts.extraButton == string(out) {
|
||||
if opts.extraButton != nil && *opts.extraButton+"\n" == string(out) {
|
||||
return "", ErrExtraButton
|
||||
}
|
||||
return "", ErrCanceled
|
||||
|
|
|
@ -66,6 +66,7 @@ var (
|
|||
systemParametersInfo = user32.NewProc("SystemParametersInfoW")
|
||||
setWindowPos = user32.NewProc("SetWindowPos")
|
||||
getWindowRect = user32.NewProc("GetWindowRect")
|
||||
setWindowLong = user32.NewProc("SetWindowLongPtrW")
|
||||
getSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||
unregisterClass = user32.NewProc("UnregisterClassW")
|
||||
registerClassEx = user32.NewProc("RegisterClassExW")
|
||||
|
|
14
zenity.go
14
zenity.go
|
@ -17,25 +17,17 @@ import (
|
|||
"github.com/ncruces/zenity/internal/zenutil"
|
||||
)
|
||||
|
||||
type stringErr string
|
||||
|
||||
func (e stringErr) Error() string { return string(e) }
|
||||
|
||||
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 = stringErr("dialog canceled")
|
||||
const ErrCanceled = zenutil.ErrCanceled
|
||||
|
||||
// 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.
|
||||
const ErrUnsupported = stringErr("unsupported option")
|
||||
|
||||
func init() {
|
||||
zenutil.Canceled = ErrCanceled
|
||||
}
|
||||
const ErrUnsupported = zenutil.ErrUnsupported
|
||||
|
||||
type options struct {
|
||||
// General options
|
||||
|
|
Loading…
Reference in a new issue