zenity/internal/zenutil/run_darwin.go

257 lines
5.7 KiB
Go
Raw Normal View History

2020-01-19 06:57:05 -05:00
package zenutil
2020-01-05 07:37:45 -05:00
import (
2021-04-22 10:03:08 -04:00
"bytes"
2020-01-28 07:46:43 -05:00
"context"
2021-03-03 07:15:29 -05:00
"io/ioutil"
2020-01-06 19:57:00 -05:00
"os"
2020-01-05 07:37:45 -05:00
"os/exec"
2021-04-22 10:03:08 -04:00
"path/filepath"
"strconv"
2020-01-05 07:37:45 -05:00
"strings"
2020-01-06 19:57:00 -05:00
"syscall"
2021-04-22 10:03:08 -04:00
"time"
2020-01-05 07:37:45 -05:00
)
2020-01-29 11:45:40 -05:00
// Run is internal.
2020-01-28 07:46:43 -05:00
func Run(ctx context.Context, script string, data interface{}) ([]byte, error) {
2020-01-05 07:37:45 -05:00
var buf strings.Builder
err := scripts.ExecuteTemplate(&buf, script, data)
if err != nil {
return nil, err
}
2020-01-21 07:03:58 -05:00
script = buf.String()
2020-01-19 06:57:05 -05:00
if Command {
2021-03-03 07:15:29 -05:00
// 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 {
2021-04-08 12:23:17 -04:00
} else if err := os.Remove(t.Name()); err != nil {
2021-03-03 07:15:29 -05:00
} else if _, err := t.WriteString(script); 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 {
} else {
syscall.Exec(path, []string{"osascript", "-l", "JavaScript"}, nil)
2020-01-06 19:57:00 -05:00
}
}
2020-01-28 07:46:43 -05:00
if ctx != nil {
2021-02-18 21:20:57 -05:00
cmd := exec.CommandContext(ctx, "osascript", "-l", "JavaScript")
2020-01-28 07:46:43 -05:00
cmd.Stdin = strings.NewReader(script)
2020-01-29 06:09:06 -05:00
out, err := cmd.Output()
if ctx.Err() != nil {
err = ctx.Err()
}
return out, err
2020-01-28 07:46:43 -05:00
}
2021-02-18 21:20:57 -05:00
cmd := exec.Command("osascript", "-l", "JavaScript")
2020-01-21 07:03:58 -05:00
cmd.Stdin = strings.NewReader(script)
2020-01-05 07:37:45 -05:00
return cmd.Output()
}
2021-04-25 13:34:56 -04:00
func RunProgress(ctx context.Context, env []string) (m *progressDialog, err error) {
2021-04-22 10:03:08 -04:00
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)
2021-04-25 13:34:56 -04:00
cmd.Env = env
2021-04-22 10:03:08 -04:00
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()
}
2021-04-25 13:34:56 -04:00
m = &progressDialog{
2021-04-22 10:03:08 -04:00
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
}
2021-04-25 13:34:56 -04:00
type progressDialog struct {
2021-04-22 10:03:08 -04:00
err error
done chan struct{}
lines chan string
}
2021-04-25 13:34:56 -04:00
func (m *progressDialog) send(line string) error {
2021-04-22 10:03:08 -04:00
select {
case m.lines <- line:
return nil
case <-m.done:
return m.err
}
}
2021-04-25 13:34:56 -04:00
func (m *progressDialog) Close() error {
2021-04-22 10:03:08 -04:00
close(m.lines)
<-m.done
return m.err
}
2021-04-25 13:34:56 -04:00
func (m *progressDialog) Text(text string) error {
return m.send("#" + text)
2021-04-22 10:03:08 -04:00
}
2021-04-25 13:34:56 -04:00
func (m *progressDialog) Value(value int) error {
return m.send(strconv.Itoa(value))
2021-04-22 10:03:08 -04:00
}
2021-02-19 12:46:47 -05:00
// File is internal.
2020-01-05 07:37:45 -05:00
type File struct {
2021-02-19 12:46:47 -05:00
Operation string
Separator string
Options FileOptions
}
// FileOptions is internal.
type FileOptions struct {
2021-03-03 21:52:05 -05:00
Prompt *string `json:"withPrompt,omitempty"`
2021-02-19 12:46:47 -05:00
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"`
2020-01-05 07:37:45 -05:00
}
2021-03-04 21:01:59 -05:00
// Dialog is internal.
type Dialog struct {
2020-01-06 07:01:51 -05:00
Operation string
Text string
2021-03-03 21:52:05 -05:00
Extra *string
2021-03-04 21:01:59 -05:00
Options DialogOptions
2020-01-05 07:37:45 -05:00
}
2020-01-26 11:04:49 -05:00
2021-03-04 21:01:59 -05:00
// DialogOptions is internal.
type DialogOptions struct {
2021-02-19 12:46:47 -05:00
Message string `json:"message,omitempty"`
As string `json:"as,omitempty"`
2021-03-04 21:01:59 -05:00
Answer *string `json:"defaultAnswer,omitempty"`
Hidden bool `json:"hiddenAnswer,omitempty"`
2021-03-03 21:52:05 -05:00
Title *string `json:"withTitle,omitempty"`
2021-02-19 12:46:47 -05:00
Icon string `json:"withIcon,omitempty"`
Buttons []string `json:"buttons,omitempty"`
Cancel int `json:"cancelButton,omitempty"`
Default int `json:"defaultButton,omitempty"`
Timeout int `json:"givingUpAfter,omitempty"`
}
2021-04-07 09:16:35 -04:00
// List is internal.
type List struct {
Items []string
Separator string
Options ListOptions
}
// ListOptions is internal.
type ListOptions struct {
Title *string `json:"withTitle,omitempty"`
Prompt *string `json:"withPrompt,omitempty"`
OK *string `json:"okButtonName,omitempty"`
Cancel *string `json:"cancelButtonName,omitempty"`
Default []string `json:"defaultItems,omitempty"`
Multiple bool `json:"multipleSelectionsAllowed,omitempty"`
Empty bool `json:"emptySelectionAllowed,omitempty"`
}
2021-02-19 12:46:47 -05:00
// Notify is internal.
2020-01-26 11:04:49 -05:00
type Notify struct {
2021-02-19 12:46:47 -05:00
Text string
Options NotifyOptions
}
// NotifyOptions is internal.
type NotifyOptions struct {
2021-03-03 21:52:05 -05:00
Title *string `json:"withTitle,omitempty"`
Subtitle string `json:"subtitle,omitempty"`
2020-01-26 11:04:49 -05:00
}
2021-04-06 07:35:22 -04:00
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
}
}