Skip to content

Commit 67a4cbc

Browse files
authored
General improvements for Wayland (#105)
Wayland environment variables are now exposed to the profile as well as the workloads for improved UX. The changes also introduces the headless subcommand, which seems to be a better fit when running on top of Wayland atm. But removes isolation/grouping of workloads. Now if the resolution can't be detected by running `xrand`, it will attempt `wlr-randr`. If that also fails, defaults to `1440x1080`, instead of silently failing. Relates to #2.
2 parents 209c5bb + eea689c commit 67a4cbc

File tree

16 files changed

+174
-40
lines changed

16 files changed

+174
-40
lines changed

.golangci.yaml

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
version: "2"
12
linters:
2-
disable-all: true
3+
default: none
34
enable:
45
- asasalint
56
- asciicheck
@@ -22,13 +23,10 @@ linters:
2223
- gochecknoinits
2324
- gochecksumtype
2425
- goconst
25-
- gofmt
2626
- goheader
27-
- goimports
2827
- gomodguard
2928
- goprintffuncname
3029
- gosec
31-
- gosimple
3230
- gosmopolitan
3331
- govet
3432
- grouper
@@ -53,18 +51,38 @@ linters:
5351
- sloglint
5452
- spancheck
5553
- sqlclosecheck
56-
- stylecheck
54+
- staticcheck
5755
- tagalign
5856
- tagliatelle
59-
- tenv
6057
- testableexamples
6158
- thelper
6259
- tparallel
63-
- typecheck
6460
- unconvert
6561
- unparam
6662
- unused
6763
- usestdlibvars
64+
- usetesting
6865
- wastedassign
6966
- whitespace
7067
- zerologlint
68+
exclusions:
69+
generated: lax
70+
presets:
71+
- comments
72+
- common-false-positives
73+
- legacy
74+
- std-error-handling
75+
paths:
76+
- third_party$
77+
- builtin$
78+
- examples$
79+
formatters:
80+
enable:
81+
- gofmt
82+
- goimports
83+
exclusions:
84+
generated: lax
85+
paths:
86+
- third_party$
87+
- builtin$
88+
- examples$

cmd/cli/headless.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
6+
"github.com/qubesome/cli/internal/qubesome"
7+
"github.com/urfave/cli/v3"
8+
)
9+
10+
var conf string
11+
12+
func headlessCommand() *cli.Command {
13+
cmd := &cli.Command{
14+
Name: "headless",
15+
Usage: "execute workloads in headless mode",
16+
Description: `Examples:
17+
18+
qubesome headless -profile <path> <workload>
19+
qubesome headless -profile ~/git/dotfiles chrome
20+
`,
21+
Arguments: []cli.Argument{
22+
&cli.StringArg{
23+
Name: "workload",
24+
Destination: &workload,
25+
},
26+
},
27+
Flags: []cli.Flag{
28+
&cli.StringFlag{
29+
Name: "config",
30+
Destination: &conf,
31+
},
32+
&cli.StringFlag{
33+
Name: "profile",
34+
Destination: &targetProfile,
35+
},
36+
&cli.StringFlag{
37+
Name: "runner",
38+
Destination: &runner,
39+
},
40+
},
41+
Action: func(ctx context.Context, cmd *cli.Command) error {
42+
cfg := config(conf)
43+
44+
return qubesome.Run(
45+
qubesome.WithHeadless(),
46+
qubesome.WithWorkload(workload),
47+
qubesome.WithProfile(targetProfile),
48+
qubesome.WithConfig(cfg),
49+
qubesome.WithRunner(runner),
50+
qubesome.WithExtraArgs(cmd.Args().Slice()),
51+
)
52+
},
53+
}
54+
return cmd
55+
}

cmd/cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func RootCommand() *cli.Command {
4141
completionCommand(),
4242
hostRunCommand(),
4343
flatpakCommand(),
44+
headlessCommand(),
4445
},
4546
}
4647

hack/base.mk

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
GOLANGCI_VERSION ?= v1.64.5
2-
PROTOC_VERSION ?= 29.3
1+
GOLANGCI_VERSION ?= v2.5.0
2+
PROTOC_VERSION ?= 32.1
33
TOOLS_BIN := $(shell mkdir -p build/tools && realpath build/tools)
44

55
ifneq ($(shell git status --porcelain --untracked-files=no),)

internal/files/binaries.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const (
1010
XclipBinary = "/usr/bin/xclip"
1111
FireCrackerBinary = "/usr/bin/firecracker"
1212
XrandrBinary = "/usr/bin/xrandr"
13+
WlrRandrBinary = "/usr/bin/wlr-randr"
1314
DbusBinary = "/usr/bin/dbus-send"
1415
PodmanBinary = "/usr/bin/podman"
1516
DockerBinary = "/usr/bin/docker"

internal/flatpak/flatpak.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func Run(opts ...command.Option[Options]) error {
5454
args := []string{"run", o.Name}
5555
args = append(args, o.ExtraArgs...)
5656

57-
c := exec.Command("/usr/bin/flatpak", args...)
57+
c := exec.CommandContext(context.TODO(), "/usr/bin/flatpak", args...)
5858
c.Env = append(os.Environ(), fmt.Sprintf("DISPLAY=:%d", prof.Display))
5959
out, err := c.CombinedOutput()
6060
fmt.Println(string(out))
@@ -86,7 +86,7 @@ func Install(opts ...command.Option[Options]) error {
8686
fmt.Println("installing Flatpak", name)
8787
args := []string{"install", "flathub", name}
8888

89-
c := exec.Command("/usr/bin/flatpak", args...)
89+
c := exec.CommandContext(context.TODO(), "/usr/bin/flatpak", args...)
9090
c.Stdin = os.Stdin
9191
c.Stdout = os.Stdout
9292
c.Stderr = os.Stderr

internal/profiles/profiles.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,12 @@ func Run(opts ...command.Option[Options]) error {
8181
if err != nil {
8282
return err
8383
}
84-
cfg.RootDir = filepath.Dir(path)
8584

8685
if cfg == nil {
8786
return fmt.Errorf("cannot start profile: nil config")
8887
}
88+
89+
cfg.RootDir = filepath.Dir(path)
8990
profile, ok := cfg.Profile(o.Profile)
9091
if !ok {
9192
return fmt.Errorf("cannot start profile: profile %q not found", o.Profile)
@@ -336,6 +337,7 @@ func Start(runner string, profile *types.Profile, cfg *types.Config, interactive
336337
creds.CA, creds.ClientPEM, creds.ClientKeyPEM,
337338
profile, strconv.Itoa(int(profile.Display)), interactive, cfg)
338339
if err != nil {
340+
slog.Warn("failed to create display", "error", err)
339341
return err
340342
}
341343

@@ -412,12 +414,13 @@ func createMagicCookie(profile *types.Profile) error {
412414

413415
xauthority := os.Getenv("XAUTHORITY")
414416
if xauthority == "" {
415-
return fmt.Errorf("XAUTHORITY not defined")
417+
xauthority = os.ExpandEnv("${HOME}/.XAUTHORITY")
416418
}
417419

418420
slog.Debug("opening parent xauthority", "path", xauthority)
419421
parent, err := os.Open(xauthority)
420422
if err != nil {
423+
slog.Debug("failed to open parent xauthority", "error", err)
421424
return err
422425
}
423426
defer parent.Close()
@@ -470,6 +473,7 @@ func createNewDisplay(bin string, ca, cert, key []byte, profile *types.Profile,
470473
"-tst",
471474
"-nolisten", "tcp",
472475
"-auth", "/home/xorg-user/.Xserver",
476+
"-verbose", "9",
473477
"--",
474478
strings.TrimPrefix(profile.WindowManager, "exec ")}
475479
}
@@ -549,7 +553,6 @@ func createNewDisplay(bin string, ca, cert, key []byte, profile *types.Profile,
549553
"--rm",
550554
// rely on currently set DISPLAY.
551555
"-e", "DISPLAY",
552-
"-e", "XDG_SESSION_TYPE=X11",
553556
"-e", "Q_MTLS_CA",
554557
"-e", "Q_MTLS_CERT",
555558
"-e", "Q_MTLS_KEY",
@@ -578,10 +581,20 @@ func createNewDisplay(bin string, ca, cert, key []byte, profile *types.Profile,
578581
}
579582

580583
// TODO: Investigate ways to avoid sharing /run/user/1000 on Wayland.
581-
dockerArgs = append(dockerArgs, "-e XDG_RUNTIME_DIR")
584+
dockerArgs = append(dockerArgs, "-e", "XDG_RUNTIME_DIR")
585+
dockerArgs = append(dockerArgs, "-e", "XDG_BACKEND")
586+
dockerArgs = append(dockerArgs, "-e", "XDG_SEAT")
587+
dockerArgs = append(dockerArgs, "-e", "XDG_SESSION_TYPE")
588+
dockerArgs = append(dockerArgs, "-e", "XDG_SESSION_ID")
589+
dockerArgs = append(dockerArgs, "-e", "XDG_SESSION_CLASS")
590+
dockerArgs = append(dockerArgs, "-e", "XDG_SESSION_DESKTOP")
591+
dockerArgs = append(dockerArgs, "-e", "WAYLAND_DISPLAY")
592+
dockerArgs = append(dockerArgs, "-e", "HYPRLAND_INSTANCE_SIGNATURE")
582593
dockerArgs = append(dockerArgs, "-v="+xdgRuntimeDir+":/run/user/1000")
594+
} else {
595+
dockerArgs = append(dockerArgs, "-e", "XDG_SESSION_TYPE=X11")
583596
}
584-
if profile.HostAccess.Gpus != "" {
597+
if profile.Gpus != "" {
585598
if gpus, ok := gpu.Supported(profile.Runner); ok {
586599
dockerArgs = append(dockerArgs, gpus)
587600
}
@@ -623,6 +636,7 @@ func createNewDisplay(bin string, ca, cert, key []byte, profile *types.Profile,
623636

624637
paths = append(paths, fmt.Sprintf("-v=%s:/dev/shm", filepath.Join(userDir, "shm")))
625638
if profile.Dbus {
639+
paths = append(paths, "-v=/run/dbus/system_bus_socket:/run/dbus/system_bus_socket")
626640
paths = append(paths, "-v=/etc/machine-id:/etc/machine-id:ro")
627641
} else {
628642
paths = append(paths, fmt.Sprintf("-v=%s:/run/user/1000", userDir))
@@ -657,6 +671,8 @@ func createNewDisplay(bin string, ca, cert, key []byte, profile *types.Profile,
657671

658672
slog.Debug("exec: "+bin, "args", dockerArgs)
659673
cmd := execabs.Command(bin, dockerArgs...)
674+
cmd.Env = append(cmd.Env, os.Environ()...)
675+
660676
cmd.Env = append(os.Environ(), "Q_MTLS_CA="+string(ca))
661677
cmd.Env = append(cmd.Env, "Q_MTLS_CERT="+string(cert))
662678
cmd.Env = append(cmd.Env, "Q_MTLS_KEY="+string(key))

internal/qubesome/mime.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func (q *Qubesome) HandleMime(in *WorkloadInfo, args []string, runnerOverride st
3131
return fmt.Errorf("cannot handle schemeless mime type: default mime handler is not set")
3232
}
3333

34-
return q.runner(q.defaultWorkload(in, args), runnerOverride)
34+
return q.runner(q.defaultWorkload(in, args), runnerOverride, false)
3535
}
3636

3737
if m, ok := in.Config.MimeHandlers[u.Scheme]; ok {
@@ -43,7 +43,7 @@ func (q *Qubesome) HandleMime(in *WorkloadInfo, args []string, runnerOverride st
4343
}
4444

4545
q.overrideWithProfile(in, &wi)
46-
return q.runner(wi, runnerOverride)
46+
return q.runner(wi, runnerOverride, false)
4747
}
4848

4949
if in.Config.DefaultMimeHandler == nil {
@@ -53,7 +53,7 @@ func (q *Qubesome) HandleMime(in *WorkloadInfo, args []string, runnerOverride st
5353
slog.Debug("no scheme specific handler: falling back to default mime handler")
5454

5555
// falls back to default
56-
return q.runner(q.defaultWorkload(in, args), runnerOverride)
56+
return q.runner(q.defaultWorkload(in, args), runnerOverride, false)
5757
}
5858

5959
func (q *Qubesome) overrideWithProfile(in *WorkloadInfo, wi *WorkloadInfo) {

internal/qubesome/mime_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func Test_HandleMime(t *testing.T) {
143143
called := 0
144144

145145
q := New()
146-
q.runner = func(wi WorkloadInfo, _ string) error {
146+
q.runner = func(wi WorkloadInfo, _ string, _ bool) error {
147147
actual = &wi
148148
called++
149149
return nil

internal/qubesome/options.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type Options struct {
1313
Profile string
1414
Runner string
1515
ExtraArgs []string
16+
Headless bool
1617
}
1718

1819
func WithExtraArgs(args []string) command.Option[Options] {
@@ -44,6 +45,11 @@ func WithConfig(cfg *types.Config) command.Option[Options] {
4445
o.Config = cfg
4546
}
4647
}
48+
func WithHeadless() command.Option[Options] {
49+
return func(o *Options) {
50+
o.Headless = true
51+
}
52+
}
4753

4854
func (o *Options) Validate() error {
4955
if o.Config == nil {

0 commit comments

Comments
 (0)