Skip to content

Commit

Permalink
zenity package for Linux (#23619)
Browse files Browse the repository at this point in the history
  • Loading branch information
mostlikelee authored Nov 20, 2024
1 parent d33d6f0 commit 161da90
Show file tree
Hide file tree
Showing 8 changed files with 664 additions and 20 deletions.
66 changes: 66 additions & 0 deletions orbit/pkg/dialog/dialog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package dialog

import (
"context"
"errors"
"time"
)

var (
// ErrCanceled is returned when the dialog is canceled by the cancel button.
ErrCanceled = errors.New("dialog canceled")
// ErrTimeout is returned when the dialog is automatically closed due to a timeout.
ErrTimeout = errors.New("dialog timed out")
// ErrUnknown is returned when an unknown error occurs.
ErrUnknown = errors.New("unknown error")
)

// Dialog represents a UI dialog that can be displayed to the end user
// on a host
type Dialog interface {
// ShowEntry displays a dialog that accepts end user input. It returns the entered
// text or errors ErrCanceled, ErrTimeout, or ErrUnknown.
ShowEntry(ctx context.Context, opts EntryOptions) ([]byte, error)
// ShowInfo displays a dialog that displays information. It returns an error if the dialog
// could not be displayed.
ShowInfo(ctx context.Context, opts InfoOptions) error
// Progress displays a dialog that shows progress. It waits until the
// context is cancelled.
ShowProgress(ctx context.Context, opts ProgressOptions) error
}

// EntryOptions represents options for a dialog that accepts end user input.
type EntryOptions struct {
// Title sets the title of the dialog.
Title string

// Text sets the text of the dialog.
Text string

// HideText hides the text entered by the user.
HideText bool

// TimeOut sets the time in seconds before the dialog is automatically closed.
TimeOut time.Duration
}

// InfoOptions represents options for a dialog that displays information.
type InfoOptions struct {
// Title sets the title of the dialog.
Title string

// Text sets the text of the dialog.
Text string

// Timeout sets the time in seconds before the dialog is automatically closed.
TimeOut time.Duration
}

// ProgressOptions represents options for a dialog that shows progress.
type ProgressOptions struct {
// Title sets the title of the dialog.
Title string

// Text sets the text of the dialog.
Text string
}
30 changes: 26 additions & 4 deletions orbit/pkg/execuser/execuser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// SYSTEM service on Windows) as the current login user.
package execuser

import "context"

type eopts struct {
env [][2]string
args [][2]string
Expand All @@ -19,10 +21,6 @@ func WithEnv(name, value string) Option {
}

// WithArg sets command line arguments for the application.
//
// TODO: for now CLI arguments are only used by the darwin
// implementation, just because it's the only platform that needs
// them.
func WithArg(name, value string) Option {
return func(a *eopts) {
a.args = append(a.args, [2]string{name, value})
Expand All @@ -40,3 +38,27 @@ func Run(path string, opts ...Option) (lastLogs string, err error) {
}
return run(path, o)
}

// RunWithOutput runs an application as the current login user and returns its output.
// It assumes the caller is running with high privileges (root on UNIX).
//
// It blocks until the child process exits.
// Non ExitError errors return with a -1 exitCode.
func RunWithOutput(path string, opts ...Option) (output []byte, exitCode int, err error) {
var o eopts
for _, fn := range opts {
fn(&o)
}
return runWithOutput(path, o)
}

// RunWithWait runs an application as the current login user and waits for it to finish
// or to be canceled by the context. Canceling the context will not return an error.
// It assumes the caller is running with high privileges (root on UNIX).
func RunWithWait(ctx context.Context, path string, opts ...Option) error {
var o eopts
for _, fn := range opts {
fn(&o)
}
return runWithWait(ctx, path, o)
}
10 changes: 10 additions & 0 deletions orbit/pkg/execuser/execuser_darwin.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package execuser

import (
"context"
"errors"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -47,3 +49,11 @@ func run(path string, opts eopts) (lastLogs string, err error) {
}
return tw.String(), nil
}

func runWithOutput(path string, opts eopts) (output []byte, exitCode int, err error) {
return nil, 0, errors.New("not implemented")
}

func runWithWait(ctx context.Context, path string, opts eopts) error {
return errors.New("not implemented")
}
106 changes: 90 additions & 16 deletions orbit/pkg/execuser/execuser_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package execuser
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
Expand All @@ -18,9 +19,96 @@ import (

// run uses sudo to run the given path as login user.
func run(path string, opts eopts) (lastLogs string, err error) {
args, err := getUserAndDisplayArgs(path, opts)
if err != nil {
return "", fmt.Errorf("get args: %w", err)
}

args = append(args,
// Append the packaged libayatana-appindicator3 libraries path to LD_LIBRARY_PATH.
//
// Fleet Desktop doesn't use libayatana-appindicator3 since 1.18.3, but we need to
// keep this to support older versions of Fleet Desktop.
fmt.Sprintf("LD_LIBRARY_PATH=%s:%s", filepath.Dir(path), os.ExpandEnv("$LD_LIBRARY_PATH")),
path,
)

cmd := exec.Command("sudo", args...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
log.Printf("cmd=%s", cmd.String())

if err := cmd.Start(); err != nil {
return "", fmt.Errorf("open path %q: %w", path, err)
}
return "", nil
}

// run uses sudo to run the given path as login user and waits for the process to finish.
func runWithOutput(path string, opts eopts) (output []byte, exitCode int, err error) {
args, err := getUserAndDisplayArgs(path, opts)
if err != nil {
return nil, -1, fmt.Errorf("get args: %w", err)
}

args = append(args, path)

if len(opts.args) > 0 {
for _, arg := range opts.args {
args = append(args, arg[0], arg[1])
}
}

cmd := exec.Command("sudo", args...)
log.Printf("cmd=%s", cmd.String())

output, err = cmd.Output()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
exitCode = exitErr.ExitCode()
return output, exitCode, fmt.Errorf("%q exited with code %d: %w", path, exitCode, err)
}
return output, -1, fmt.Errorf("%q error: %w", path, err)
}

return output, exitCode, nil
}

func runWithWait(ctx context.Context, path string, opts eopts) error {
args, err := getUserAndDisplayArgs(path, opts)
if err != nil {
return fmt.Errorf("get args: %w", err)
}

args = append(args, path)

if len(opts.args) > 0 {
for _, arg := range opts.args {
args = append(args, arg[0], arg[1])
}
}

cmd := exec.CommandContext(ctx, "sudo", args...)
log.Printf("cmd=%s", cmd.String())

if err := cmd.Start(); err != nil {
return fmt.Errorf("cmd start %q: %w", path, err)
}

if err := cmd.Wait(); err != nil {
if errors.Is(ctx.Err(), context.Canceled) {
return nil
}
return fmt.Errorf("cmd wait %q: %w", path, err)
}

return nil
}

func getUserAndDisplayArgs(path string, opts eopts) ([]string, error) {
user, err := getLoginUID()
if err != nil {
return "", fmt.Errorf("get user: %w", err)
return nil, fmt.Errorf("get user: %w", err)
}

// TODO(lucas): Default to display :0 if user DISPLAY environment variable
Expand Down Expand Up @@ -68,23 +156,9 @@ func run(path string, opts eopts) (lastLogs string, err error) {
// This is required for Ubuntu 18, and not required for Ubuntu 21/22
// (because it's already part of the user).
fmt.Sprintf("DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%d/bus", user.id),
// Append the packaged libayatana-appindicator3 libraries path to LD_LIBRARY_PATH.
//
// Fleet Desktop doesn't use libayatana-appindicator3 since 1.18.3, but we need to
// keep this to support older versions of Fleet Desktop.
fmt.Sprintf("LD_LIBRARY_PATH=%s:%s", filepath.Dir(path), os.ExpandEnv("$LD_LIBRARY_PATH")),
path,
)

cmd := exec.Command("sudo", args...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
log.Printf("cmd=%s", cmd.String())

if err := cmd.Start(); err != nil {
return "", fmt.Errorf("open path %q: %w", path, err)
}
return "", nil
return args, nil
}

type user struct {
Expand Down
9 changes: 9 additions & 0 deletions orbit/pkg/execuser/execuser_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package execuser
// To view what was modified/added, you can use the execuser_windows_diff.sh script.

import (
"context"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -117,6 +118,14 @@ func run(path string, opts eopts) (lastLogs string, err error) {
return "", startProcessAsCurrentUser(path, "", "")
}

func runWithOutput(path string, opts eopts) (output []byte, exitCode int, err error) {
return nil, 0, errors.New("not implemented")
}

func runWithWait(ctx context.Context, path string, opts eopts) error {
return errors.New("not implemented")
}

// getCurrentUserSessionId will attempt to resolve
// the session ID of the user currently active on
// the system.
Expand Down
Loading

0 comments on commit 161da90

Please sign in to comment.