WIP: progress (unix).
This commit is contained in:
parent
374ba8a90a
commit
10c3d63ca5
10 changed files with 209 additions and 82 deletions
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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,18 +108,19 @@ 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() {
|
err := cmd.Wait()
|
||||||
pipe.Close()
|
if cerr := ctx.Err(); cerr != nil {
|
||||||
err := cmd.Wait()
|
err = cerr
|
||||||
if cerr := ctx.Err(); cerr != nil {
|
}
|
||||||
err = cerr
|
m.err = err
|
||||||
}
|
close(m.done)
|
||||||
m.err = err
|
os.RemoveAll(t)
|
||||||
close(m.done)
|
}()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
50
internal/zenutil/run_progress.go
Normal file
50
internal/zenutil/run_progress.go
Normal 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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
14
progress.go
14
progress.go
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
28
progress_unix.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// +build !windows,!darwin,!js
|
||||||
|
|
||||||
|
package zenity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func progress(opts options) (ProgressDialog, error) {
|
||||||
|
args := []string{"--progress"}
|
||||||
|
args = appendTitle(args, opts)
|
||||||
|
args = appendButtons(args, opts)
|
||||||
|
args = appendWidthHeight(args, opts)
|
||||||
|
args = appendIcon(args, opts)
|
||||||
|
if opts.maxValue == 0 {
|
||||||
|
opts.maxValue = 100
|
||||||
|
}
|
||||||
|
if opts.maxValue < 0 {
|
||||||
|
args = append(args, "--pulsate")
|
||||||
|
}
|
||||||
|
if opts.noCancel {
|
||||||
|
args = append(args, "--no-cancel")
|
||||||
|
}
|
||||||
|
if opts.timeRemaining {
|
||||||
|
args = append(args, "--time-remaining")
|
||||||
|
}
|
||||||
|
return zenutil.RunProgress(opts.ctx, opts.maxValue, args)
|
||||||
|
}
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue