From c74a75a4ca0f953b9c4c3b3b3e2e9da03f598c15 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Thu, 22 Apr 2021 15:03:08 +0100 Subject: [PATCH] WIP: progress (macOS). --- internal/zenutil/osa_generated.go | 20 ++++ internal/zenutil/osa_generator.go | 35 ++++--- internal/zenutil/osascripts/progress.js | 3 +- internal/zenutil/run_darwin.go | 121 ++++++++++++++++++++++++ progress.go | 7 ++ 5 files changed, 171 insertions(+), 15 deletions(-) create mode 100644 progress.go diff --git a/internal/zenutil/osa_generated.go b/internal/zenutil/osa_generated.go index 4396954..367e8bc 100644 --- a/internal/zenutil/osa_generated.go +++ b/internal/zenutil/osa_generated.go @@ -49,3 +49,23 @@ var app=Application.currentApplication() app.includeStandardAdditions=true void app.displayNotification({{json .Text}},{{json .Options}}) {{- end}}`)) + +var progress =` +var app=Application.currentApplication() +app.includeStandardAdditions=true +app.activate() +ObjC.import('stdlib') +ObjC.import('readline') +function run(args){Progress.totalUnitCount=100 +Progress.completedUnitCount=0 +Progress.description=args[0]||"Progress" +Progress.additionalDescription=args[1]||"Running..." +while(true){var s +try{s=$.readline('')}catch(e){if(e.errorNumber===-128)$.exit(1) +break} +if(s.indexOf('#')===0){Progress.additionalDescription=s.slice(1).trim() +continue} +var i=parseInt(s) +if(Number.isSafeInteger(i)){Progress.completedUnitCount=i +continue}} +Progress.completedUnitCount=100}` diff --git a/internal/zenutil/osa_generator.go b/internal/zenutil/osa_generator.go index 0270174..79ebd53 100644 --- a/internal/zenutil/osa_generator.go +++ b/internal/zenutil/osa_generator.go @@ -4,7 +4,6 @@ package main import ( "bytes" - "io/ioutil" "log" "os" "path/filepath" @@ -17,21 +16,21 @@ import ( func main() { dir := os.Args[1] - files, err := ioutil.ReadDir(dir) + files, err := os.ReadDir(dir) if err != nil { log.Fatal(err) } + var args struct { + Templates string + Progress string + } + var str strings.Builder for _, file := range files { name := file.Name() - - str.WriteString("\n" + `{{define "`) - str.WriteString(strings.TrimSuffix(name, filepath.Ext(name))) - str.WriteString(`" -}}` + "\n") - - data, err := ioutil.ReadFile(filepath.Join(dir, name)) + data, err := os.ReadFile(filepath.Join(dir, name)) if err != nil { log.Fatal(err) } @@ -40,8 +39,16 @@ func main() { log.Fatal(err) } - str.Write(data) - str.WriteString("\n{{- end}}") + 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}}") + } } out, err := os.Create("osa_generated.go") @@ -49,7 +56,8 @@ func main() { log.Fatal(err) } - err = generator.Execute(out, str.String()) + args.Templates = str.String() + err = generator.Execute(out, args) if err != nil { log.Fatal(err) } @@ -108,5 +116,6 @@ 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(` + "`{{.}}`" + `)) -`)) +}}).Parse(` + "`{{.Templates}}`" + `)) + +var progress =` + "`\n{{.Progress}}`\n")) diff --git a/internal/zenutil/osascripts/progress.js b/internal/zenutil/osascripts/progress.js index d6509ec..08b8a8e 100644 --- a/internal/zenutil/osascripts/progress.js +++ b/internal/zenutil/osascripts/progress.js @@ -15,8 +15,7 @@ function run(args) { var s try { s = $.readline('') - } - catch (e) { + } catch (e) { if (e.errorNumber === -128) $.exit(1) break } diff --git a/internal/zenutil/run_darwin.go b/internal/zenutil/run_darwin.go index 80c862e..0104c18 100644 --- a/internal/zenutil/run_darwin.go +++ b/internal/zenutil/run_darwin.go @@ -1,12 +1,16 @@ package zenutil import ( + "bytes" "context" "io/ioutil" "os" "os/exec" + "path/filepath" + "strconv" "strings" "syscall" + "time" ) // Run is internal. @@ -47,6 +51,123 @@ func Run(ctx context.Context, script string, data interface{}) ([]byte, error) { return cmd.Output() } +func RunProgress(ctx context.Context) (m *progressMonitor, err error) { + t, err := ioutil.TempDir("", "") + if err != nil { + return nil, err + } + defer func() { + if err != nil { + os.RemoveAll(t) + } + }() + + var cmd *exec.Cmd + name := filepath.Join(t, "progress.app") + + cmd = exec.Command("osacompile", "-l", "JavaScript", "-o", name) + cmd.Stdin = strings.NewReader(progress) + if err := cmd.Run(); err != nil { + return nil, err + } + + plist := filepath.Join(name, "Contents/Info.plist") + + cmd = exec.Command("defaults", "write", plist, "LSUIElement", "true") + if err := cmd.Run(); err != nil { + return nil, err + } + + cmd = exec.Command("defaults", "write", plist, "CFBundleName", "") + if err := cmd.Run(); err != nil { + return nil, err + } + + var executable string + cmd = exec.Command("defaults", "read", plist, "CFBundleExecutable") + if out, err := cmd.Output(); err != nil { + return nil, err + } else { + out = bytes.TrimSuffix(out, []byte{'\n'}) + executable = filepath.Join(name, "Contents/MacOS", string(out)) + } + + cmd = exec.Command(executable) + 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 = &progressMonitor{ + done: make(chan struct{}), + lines: make(chan string), + } + go func() { + defer func() { + pipe.Close() + err := cmd.Wait() + if cerr := ctx.Err(); cerr != nil { + err = cerr + } + m.err = err + close(m.done) + os.RemoveAll(t) + }() + for { + var line string + select { + case s, ok := <-m.lines: + if !ok { + return + } + line = s + case <-ctx.Done(): + return + case <-time.After(40 * time.Millisecond): + } + if _, err := pipe.Write([]byte(line + "\n")); err != nil { + return + } + } + }() + return +} + +type progressMonitor struct { + err error + done chan struct{} + lines chan string +} + +func (m *progressMonitor) send(line string) error { + select { + case m.lines <- line: + return nil + case <-m.done: + return m.err + } +} + +func (m *progressMonitor) Close() error { + close(m.lines) + <-m.done + return m.err +} + +func (m *progressMonitor) Message(msg string) error { + return m.send("#" + msg) +} + +func (m *progressMonitor) Progress(progress int) error { + return m.send(strconv.Itoa(progress)) +} + // File is internal. type File struct { Operation string diff --git a/progress.go b/progress.go new file mode 100644 index 0000000..dbbffb0 --- /dev/null +++ b/progress.go @@ -0,0 +1,7 @@ +package zenity + +type ProgressMonitor interface { + Message(string) error + Progress(int) error + Close() error +}