fleet/orbit/pkg/zenity/zenity.go

114 lines
2.9 KiB
Go

package zenity
import (
"bytes"
"errors"
"fmt"
"github.com/fleetdm/fleet/v4/orbit/pkg/dialog"
"github.com/fleetdm/fleet/v4/orbit/pkg/execuser"
)
const zenityProcessName = "zenity"
type Zenity struct {
// cmdWithOutput can be set in tests to mock execution of the dialog.
cmdWithOutput func(args ...string) ([]byte, int, error)
// cmdWithWait can be set in tests to mock execution of the dialog.
cmdWithCancel func(args ...string) (func() error, error)
}
// New creates a new Zenity dialog instance for zenity v4 on Linux.
// Zenity implements the Dialog interface.
func New() *Zenity {
return &Zenity{
cmdWithOutput: execCmdWithOutput,
cmdWithCancel: execCmdWithCancel,
}
}
// ShowEntry displays an dialog that accepts end user input. It returns the entered
// text or errors ErrCanceled, ErrTimeout, or ErrUnknown.
func (z *Zenity) ShowEntry(opts dialog.EntryOptions) ([]byte, error) {
args := []string{"--entry"}
if opts.Title != "" {
args = append(args, fmt.Sprintf("--title=%s", opts.Title))
}
if opts.Text != "" {
args = append(args, fmt.Sprintf("--text=%s", opts.Text))
}
if opts.HideText {
args = append(args, "--hide-text")
}
if opts.TimeOut > 0 {
args = append(args, fmt.Sprintf("--timeout=%d", int(opts.TimeOut.Seconds())))
}
output, statusCode, err := z.cmdWithOutput(args...)
if err != nil {
switch statusCode {
case 1:
return nil, dialog.ErrCanceled
case 5:
return nil, dialog.ErrTimeout
default:
return nil, errors.Join(dialog.ErrUnknown, err)
}
}
return output, nil
}
// ShowInfo displays an information dialog. It returns errors ErrTimeout or ErrUnknown.
func (z *Zenity) ShowInfo(opts dialog.InfoOptions) error {
args := []string{"--info"}
if opts.Title != "" {
args = append(args, fmt.Sprintf("--title=%s", opts.Title))
}
if opts.Text != "" {
args = append(args, fmt.Sprintf("--text=%s", opts.Text))
}
if opts.TimeOut > 0 {
args = append(args, fmt.Sprintf("--timeout=%d", int(opts.TimeOut.Seconds())))
}
_, statusCode, err := z.cmdWithOutput(args...)
if err != nil {
switch statusCode {
case 5:
return dialog.ErrTimeout
default:
return errors.Join(dialog.ErrUnknown, err)
}
}
return nil
}
func execCmdWithOutput(args ...string) ([]byte, int, error) {
var opts []execuser.Option
for _, arg := range args {
opts = append(opts, execuser.WithArg(arg, "")) // Using empty value for positional args
}
output, exitCode, err := execuser.RunWithOutput(zenityProcessName, opts...)
// Trim the newline from zenity output
output = bytes.TrimSuffix(output, []byte("\n"))
return output, exitCode, err
}
func execCmdWithCancel(args ...string) (func() error, error) {
var opts []execuser.Option
for _, arg := range args {
opts = append(opts, execuser.WithArg(arg, "")) // Using empty value for positional args
}
stdin, err := execuser.RunWithStdin(zenityProcessName, opts...)
if err != nil {
return nil, err
}
return stdin.Close, err
}