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"
|
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"
|
2020-01-06 19:57:00 -05:00
|
|
|
"syscall"
|
2020-01-05 07:37:45 -05:00
|
|
|
)
|
|
|
|
|
2020-01-29 11:45:40 -05:00
|
|
|
// Run is internal.
|
2022-06-02 07:49:31 -04:00
|
|
|
func Run(ctx context.Context, script string, data any) ([]byte, error) {
|
2021-04-30 14:19:14 -04:00
|
|
|
var buf bytes.Buffer
|
2020-01-05 07:37:45 -05:00
|
|
|
err := scripts.ExecuteTemplate(&buf, script, data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
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 {
|
2022-05-13 07:19:46 -04:00
|
|
|
} else if t, err := os.CreateTemp("", ""); err != nil {
|
2021-04-08 12:23:17 -04:00
|
|
|
} else if err := os.Remove(t.Name()); err != nil {
|
2021-04-30 14:19:14 -04:00
|
|
|
} else if _, err := t.Write(buf.Bytes()); err != nil {
|
2021-03-03 07:15:29 -05:00
|
|
|
} else if _, err := t.Seek(0, 0); err != nil {
|
|
|
|
} else if err := os.Stderr.Close(); err != nil {
|
2022-05-13 07:19:46 -04:00
|
|
|
} else if err := syscall.Dup2(int(t.Fd()), syscall.Stdin); err != nil {
|
2021-03-03 07:15:29 -05:00
|
|
|
} 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")
|
2021-04-30 14:19:14 -04:00
|
|
|
cmd.Stdin = &buf
|
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")
|
2021-04-30 14:19:14 -04:00
|
|
|
cmd.Stdin = &buf
|
2020-01-05 07:37:45 -05:00
|
|
|
return cmd.Output()
|
|
|
|
}
|
|
|
|
|
2021-04-25 19:36:15 -04:00
|
|
|
// RunProgress is internal.
|
2021-04-30 14:19:14 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-05-13 07:19:46 -04:00
|
|
|
t, err := os.MkdirTemp("", "")
|
2021-04-22 10:03:08 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
2021-04-30 14:19:14 -04:00
|
|
|
if ctx != nil && ctx.Err() != nil {
|
|
|
|
err = ctx.Err()
|
|
|
|
}
|
2021-04-22 10:03:08 -04:00
|
|
|
os.RemoveAll(t)
|
|
|
|
}
|
|
|
|
}()
|
2021-04-30 14:19:14 -04:00
|
|
|
if ctx == nil {
|
|
|
|
ctx = context.Background()
|
|
|
|
}
|
2021-04-22 10:03:08 -04:00
|
|
|
|
|
|
|
var cmd *exec.Cmd
|
|
|
|
name := filepath.Join(t, "progress.app")
|
|
|
|
|
2021-04-30 14:19:14 -04:00
|
|
|
cmd = exec.CommandContext(ctx, "osacompile", "-l", "JavaScript", "-o", name)
|
|
|
|
cmd.Stdin = &buf
|
2021-04-22 10:03:08 -04:00
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
plist := filepath.Join(name, "Contents/Info.plist")
|
|
|
|
|
2021-04-30 14:19:14 -04:00
|
|
|
cmd = exec.CommandContext(ctx, "defaults", "write", plist, "LSUIElement", "true")
|
2021-04-22 10:03:08 -04:00
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-04-30 14:19:14 -04:00
|
|
|
cmd = exec.CommandContext(ctx, "defaults", "write", plist, "CFBundleName", "")
|
2021-04-22 10:03:08 -04:00
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var executable string
|
2021-04-30 14:19:14 -04:00
|
|
|
cmd = exec.CommandContext(ctx, "defaults", "read", plist, "CFBundleExecutable")
|
2021-04-22 10:03:08 -04:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2021-04-30 14:19:14 -04:00
|
|
|
cmd = exec.CommandContext(ctx, executable)
|
2021-04-22 10:03:08 -04:00
|
|
|
pipe, err := cmd.StdinPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-04-29 11:05:28 -04:00
|
|
|
if err := cmd.Start(); err != nil {
|
2021-04-22 10:03:08 -04:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-04-30 14:19:14 -04:00
|
|
|
dlg = &progressDialog{
|
|
|
|
ctx: ctx,
|
2021-04-30 14:05:49 -04:00
|
|
|
cmd: cmd,
|
2021-04-25 19:36:15 -04:00
|
|
|
max: max,
|
2021-04-30 14:05:49 -04:00
|
|
|
lines: make(chan string),
|
|
|
|
done: make(chan struct{}),
|
2021-04-22 10:03:08 -04:00
|
|
|
}
|
2021-04-30 14:05:49 -04:00
|
|
|
go dlg.pipe(pipe)
|
2021-04-22 10:03:08 -04:00
|
|
|
go func() {
|
2021-04-30 14:05:49 -04:00
|
|
|
defer os.RemoveAll(t)
|
2021-04-30 14:19:14 -04:00
|
|
|
dlg.wait(nil, nil)
|
2021-04-22 10:03:08 -04:00
|
|
|
}()
|
2021-04-29 11:05:28 -04:00
|
|
|
return dlg, nil
|
2021-04-22 10:03:08 -04:00
|
|
|
}
|
|
|
|
|
2022-06-01 19:00:08 -04:00
|
|
|
type common struct {
|
2022-06-02 07:49:31 -04:00
|
|
|
Application any
|
|
|
|
WindowIcon string
|
2022-06-01 19:00:08 -04: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
|
2022-05-05 08:04:27 -04:00
|
|
|
IconPath string
|
2022-06-01 19:00:08 -04:00
|
|
|
common
|
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"`
|
|
|
|
}
|
|
|
|
|
2022-05-11 12:49:15 -04:00
|
|
|
// Password is internal.
|
|
|
|
type Password struct {
|
|
|
|
Separator string
|
|
|
|
Extra *string
|
|
|
|
Options PasswordOptions
|
|
|
|
IconPath string
|
2022-06-01 19:00:08 -04:00
|
|
|
common
|
2022-05-11 12:49:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// PasswordOptions is internal.
|
|
|
|
type PasswordOptions struct {
|
|
|
|
Title *string `json:"withTitle,omitempty"`
|
|
|
|
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-25 19:36:15 -04:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-11 12:49:15 -04:00
|
|
|
// SetButtons is internal.
|
|
|
|
func (d *Password) 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-07 09:16:35 -04:00
|
|
|
// List is internal.
|
|
|
|
type List struct {
|
|
|
|
Items []string
|
|
|
|
Separator string
|
|
|
|
Options ListOptions
|
2022-06-01 19:00:08 -04:00
|
|
|
common
|
2021-04-07 09:16:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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-04-25 19:36:15 -04:00
|
|
|
// File is internal.
|
|
|
|
type File struct {
|
|
|
|
Operation string
|
|
|
|
Separator string
|
|
|
|
Options FileOptions
|
2022-06-01 19:00:08 -04:00
|
|
|
common
|
2021-04-25 19:36:15 -04:00
|
|
|
}
|
|
|
|
|
2022-03-28 15:22:39 -04:00
|
|
|
// FileOptions is internal.
|
2021-04-25 19:36:15 -04:00
|
|
|
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"`
|
|
|
|
}
|
|
|
|
|
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-30 14:19:14 -04:00
|
|
|
|
2022-06-01 19:00:08 -04:00
|
|
|
// Color is internal.
|
|
|
|
type Color struct {
|
|
|
|
Color [3]float32
|
|
|
|
common
|
2021-04-30 14:19:14 -04:00
|
|
|
}
|
2022-03-24 12:15:11 -04:00
|
|
|
|
|
|
|
// Date is internal.
|
|
|
|
type Date struct {
|
2022-03-30 10:36:50 -04:00
|
|
|
Date *int64
|
2022-03-28 15:22:39 -04:00
|
|
|
Text string
|
|
|
|
Info string
|
|
|
|
Format string
|
|
|
|
OK string
|
|
|
|
Cancel string
|
|
|
|
Extra *string
|
2022-06-01 19:00:08 -04:00
|
|
|
common
|
|
|
|
}
|
|
|
|
|
|
|
|
// Progress is internal.
|
|
|
|
type Progress struct {
|
|
|
|
Description *string
|
|
|
|
Total *int
|
|
|
|
common
|
2022-03-24 12:15:11 -04:00
|
|
|
}
|