WIP: progress (unix).

This commit is contained in:
Nuno Cruces 2021-04-26 00:36:15 +01:00
parent 374ba8a90a
commit 10c3d63ca5
10 changed files with 209 additions and 82 deletions

View file

@ -32,7 +32,7 @@ func selectColor(opts options) (color.Color, error) {
if opts.color != nil { if opts.color != nil {
args.Flags |= 0x1 // CC_RGBINIT args.Flags |= 0x1 // CC_RGBINIT
n := color.NRGBAModel.Convert(opts.color).(color.NRGBA) n := color.NRGBAModel.Convert(opts.color).(color.NRGBA)
args.RgbResult = uint32(n.R) | (uint32(n.G) << 8) | (uint32(n.B) << 16) args.RgbResult = uint32(n.R) | uint32(n.G)<<8 | uint32(n.B)<<16
} }
if opts.showPalette { if opts.showPalette {
args.Flags |= 0x4 // CC_PREVENTFULLOPEN args.Flags |= 0x4 // CC_PREVENTFULLOPEN

View file

@ -50,7 +50,7 @@ app.includeStandardAdditions=true
void app.displayNotification({{json .Text}},{{json .Options}}) void app.displayNotification({{json .Text}},{{json .Options}})
{{- end}}`)) {{- end}}`))
var progress =` var progress = `
var app=Application.currentApplication() var app=Application.currentApplication()
app.includeStandardAdditions=true app.includeStandardAdditions=true
app.activate() app.activate()

View file

@ -118,4 +118,4 @@ var scripts = template.Must(template.New("").Funcs(template.FuncMap{"json": func
return string(b), err return string(b), err
}}).Parse(` + "`{{.Templates}}`" + `)) }}).Parse(` + "`{{.Templates}}`" + `))
var progress =` + "`\n{{.Progress}}`\n")) var progress = ` + "`\n{{.Progress}}`\n"))

View file

@ -7,7 +7,6 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
@ -51,7 +50,8 @@ func Run(ctx context.Context, script string, data interface{}) ([]byte, error) {
return cmd.Output() return cmd.Output()
} }
func RunProgress(ctx context.Context, env []string) (m *progressDialog, err error) { // RunProgress is internal.
func RunProgress(ctx context.Context, max int, env []string) (m *progressDialog, err error) {
t, err := ioutil.TempDir("", "") t, err := ioutil.TempDir("", "")
if err != nil { if err != nil {
return nil, err return nil, err
@ -108,10 +108,9 @@ func RunProgress(ctx context.Context, env []string) (m *progressDialog, err erro
m = &progressDialog{ m = &progressDialog{
done: make(chan struct{}), done: make(chan struct{}),
lines: make(chan string), lines: make(chan string),
max: max,
} }
go func() { go func() {
defer func() {
pipe.Close()
err := cmd.Wait() err := cmd.Wait()
if cerr := ctx.Err(); cerr != nil { if cerr := ctx.Err(); cerr != nil {
err = cerr err = cerr
@ -120,6 +119,8 @@ func RunProgress(ctx context.Context, env []string) (m *progressDialog, err erro
close(m.done) close(m.done)
os.RemoveAll(t) os.RemoveAll(t)
}() }()
go func() {
defer pipe.Close()
for { for {
var line string var line string
select { select {
@ -140,52 +141,6 @@ func RunProgress(ctx context.Context, env []string) (m *progressDialog, err erro
return return
} }
type progressDialog struct {
err error
done chan struct{}
lines chan string
}
func (m *progressDialog) send(line string) error {
select {
case m.lines <- line:
return nil
case <-m.done:
return m.err
}
}
func (m *progressDialog) Close() error {
close(m.lines)
<-m.done
return m.err
}
func (m *progressDialog) Text(text string) error {
return m.send("#" + text)
}
func (m *progressDialog) Value(value int) error {
return m.send(strconv.Itoa(value))
}
// File is internal.
type File struct {
Operation string
Separator string
Options FileOptions
}
// FileOptions is internal.
type FileOptions struct {
Prompt *string `json:"withPrompt,omitempty"`
Type []string `json:"ofType,omitempty"`
Name string `json:"defaultName,omitempty"`
Location string `json:"defaultLocation,omitempty"`
Multiple bool `json:"multipleSelectionsAllowed,omitempty"`
Invisibles bool `json:"invisibles,omitempty"`
}
// Dialog is internal. // Dialog is internal.
type Dialog struct { type Dialog struct {
Operation string Operation string
@ -208,6 +163,25 @@ type DialogOptions struct {
Timeout int `json:"givingUpAfter,omitempty"` Timeout int `json:"givingUpAfter,omitempty"`
} }
// DialogButtons is internal.
type DialogButtons struct {
Buttons []string
Default int
Cancel int
Extra int
}
// SetButtons is internal.
func (d *Dialog) SetButtons(btns DialogButtons) {
d.Options.Buttons = btns.Buttons
d.Options.Default = btns.Default
d.Options.Cancel = btns.Cancel
if btns.Extra > 0 {
name := btns.Buttons[btns.Extra-1]
d.Extra = &name
}
}
// List is internal. // List is internal.
type List struct { type List struct {
Items []string Items []string
@ -226,6 +200,23 @@ type ListOptions struct {
Empty bool `json:"emptySelectionAllowed,omitempty"` Empty bool `json:"emptySelectionAllowed,omitempty"`
} }
// File is internal.
type File struct {
Operation string
Separator string
Options FileOptions
}
// FileOptions is internal.
type FileOptions struct {
Prompt *string `json:"withPrompt,omitempty"`
Type []string `json:"ofType,omitempty"`
Name string `json:"defaultName,omitempty"`
Location string `json:"defaultLocation,omitempty"`
Multiple bool `json:"multipleSelectionsAllowed,omitempty"`
Invisibles bool `json:"invisibles,omitempty"`
}
// Notify is internal. // Notify is internal.
type Notify struct { type Notify struct {
Text string Text string
@ -237,20 +228,3 @@ type NotifyOptions struct {
Title *string `json:"withTitle,omitempty"` Title *string `json:"withTitle,omitempty"`
Subtitle string `json:"subtitle,omitempty"` Subtitle string `json:"subtitle,omitempty"`
} }
type Buttons struct {
Buttons []string
Default int
Cancel int
Extra int
}
func (d *Dialog) SetButtons(btns Buttons) {
d.Options.Buttons = btns.Buttons
d.Options.Default = btns.Default
d.Options.Cancel = btns.Cancel
if btns.Extra > 0 {
name := btns.Buttons[btns.Extra-1]
d.Extra = &name
}
}

View file

@ -0,0 +1,50 @@
// +build !windows,!js
package zenutil
import (
"strconv"
)
type progressDialog struct {
err error
done chan struct{}
lines chan string
percent bool
max int
}
func (m *progressDialog) send(line string) error {
select {
case m.lines <- line:
return nil
case <-m.done:
return m.err
}
}
func (m *progressDialog) Close() error {
close(m.lines)
<-m.done
return m.err
}
func (m *progressDialog) Text(text string) error {
return m.send("#" + text)
}
func (m *progressDialog) Value(value int) error {
if m.percent {
return m.send(strconv.FormatFloat(100*float64(value)/float64(m.max), 'f', -1, 64))
} else {
return m.send(strconv.Itoa(value))
}
}
func (m *progressDialog) MaxValue() int {
return m.max
}
func (m *progressDialog) Done() <-chan struct{} {
return m.done
}

View file

@ -40,3 +40,66 @@ func Run(ctx context.Context, args []string) ([]byte, error) {
} }
return exec.Command(tool, args...).Output() return exec.Command(tool, args...).Output()
} }
// RunProgress is internal.
func RunProgress(ctx context.Context, max int, args []string) (m *progressDialog, err error) {
if Command && path != "" {
if Timeout > 0 {
args = append(args, "--timeout", strconv.Itoa(Timeout))
}
syscall.Exec(path, append([]string{tool}, args...), os.Environ())
}
cmd := exec.Command(tool, args...)
pipe, err := cmd.StdinPipe()
if err != nil {
return nil, err
}
if err = cmd.Start(); err != nil {
return nil, err
}
if ctx == nil {
ctx = context.Background()
}
m = &progressDialog{
done: make(chan struct{}),
lines: make(chan string),
percent: true,
max: max,
}
go func() {
err := cmd.Wait()
select {
case _, ok := <-m.lines:
if !ok {
err = nil
}
default:
}
if cerr := ctx.Err(); cerr != nil {
err = cerr
}
m.err = err
close(m.done)
}()
go func() {
defer cmd.Process.Signal(syscall.SIGTERM)
for {
var line string
select {
case s, ok := <-m.lines:
if !ok {
return
}
line = s
case <-ctx.Done():
return
}
if _, err := pipe.Write([]byte(line + "\n")); err != nil {
return
}
}
}()
return
}

View file

@ -8,14 +8,26 @@ func Progress(options ...Option) (ProgressDialog, error) {
return progress(applyOptions(options)) return progress(applyOptions(options))
} }
// ProgressDialog allows you to interact with the progress indication dialog.
type ProgressDialog interface { type ProgressDialog interface {
// Text sets the dialog text.
Text(string) error Text(string) error
// Value sets how much of the task has been completed.
Value(int) error Value(int) error
// MaxValue gets how much work the task requires in total.
MaxValue() int
// Close closes the dialog.
Close() error Close() error
// Done returns a channel that's closed when the dialog is closed.
Done() <-chan struct{}
} }
// MaxValue returns an Option to set the maximum value (macOS only). // MaxValue returns an Option to set the maximum value (macOS only).
// The default 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 })
} }

View file

@ -17,5 +17,5 @@ func progress(opts options) (ProgressDialog, error) {
if opts.maxValue >= 0 { if opts.maxValue >= 0 {
env = append(env, "total="+strconv.Itoa(opts.maxValue)) env = append(env, "total="+strconv.Itoa(opts.maxValue))
} }
return zenutil.RunProgress(opts.ctx, env) return zenutil.RunProgress(opts.ctx, opts.maxValue, env)
} }

28
progress_unix.go Normal file
View file

@ -0,0 +1,28 @@
// +build !windows,!darwin,!js
package zenity
import (
"github.com/ncruces/zenity/internal/zenutil"
)
func progress(opts options) (ProgressDialog, error) {
args := []string{"--progress"}
args = appendTitle(args, opts)
args = appendButtons(args, opts)
args = appendWidthHeight(args, opts)
args = appendIcon(args, opts)
if opts.maxValue == 0 {
opts.maxValue = 100
}
if opts.maxValue < 0 {
args = append(args, "--pulsate")
}
if opts.noCancel {
args = append(args, "--no-cancel")
}
if opts.timeRemaining {
args = append(args, "--time-remaining")
}
return zenutil.RunProgress(opts.ctx, opts.maxValue, args)
}

View file

@ -2,13 +2,13 @@ package zenity
import "github.com/ncruces/zenity/internal/zenutil" import "github.com/ncruces/zenity/internal/zenutil"
func getButtons(dialog, okcancel bool, opts options) (btns zenutil.Buttons) { func getButtons(dialog, okcancel bool, opts options) (btns zenutil.DialogButtons) {
if !okcancel { if !okcancel {
opts.cancelLabel = nil opts.cancelLabel = nil
opts.defaultCancel = false opts.defaultCancel = false
} }
if opts.okLabel != nil || opts.cancelLabel != nil || opts.extraButton != nil || (dialog != okcancel) { if opts.okLabel != nil || opts.cancelLabel != nil || opts.extraButton != nil || dialog != okcancel {
if opts.okLabel == nil { if opts.okLabel == nil {
opts.okLabel = stringPtr("OK") opts.okLabel = stringPtr("OK")
} }