1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
// monitor switches between the monitor layouts of this laptop.
//
// It replaces a pipeline that used to be spread over node (to parse xrandr),
// dhall (to type-check the parser's own JSON) and python (for the layout
// arithmetic), glued together with execline.
//
// Usage:
//
// monitor home # external monitor primary, laptop panel to its left
// monitor laptop-only # just the built-in panel
// monitor parse # the parsed xrandr output as JSON, for debugging
//
// `--dry-run` prints the xrandr invocation instead of running it.
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"os/exec"
"strings"
)
func usage() {
fmt.Fprint(os.Stderr, `usage: monitor [--dry-run] <command>
commands:
home external monitor primary, laptop panel to its left, bottom-aligned
laptop-only only the built-in laptop panel, at its native resolution
parse print the parsed xrandr output as JSON
options:
--dry-run print the xrandr command instead of running it
`)
}
func main() {
dryRun := flag.Bool("dry-run", false, "print the xrandr command instead of running it")
flag.Usage = usage
flag.Parse()
if flag.NArg() != 1 {
usage()
os.Exit(1)
}
if err := run(flag.Arg(0), *dryRun); err != nil {
fmt.Fprintf(os.Stderr, "monitor: %s\n", err)
os.Exit(1)
}
}
func run(command string, dryRun bool) error {
outputs, err := queryXrandr()
if err != nil {
return err
}
var args []string
switch command {
case "parse":
return printJSON(outputs)
case "home":
args, err = TwoMonitorArgs(outputs)
case "laptop-only":
args, err = LaptopOnlyArgs(outputs)
default:
usage()
return fmt.Errorf("unknown command %q", command)
}
if err != nil {
return err
}
if dryRun {
fmt.Printf("xrandr %s\n", strings.Join(args, " "))
return nil
}
return applyXrandr(args)
}
// queryXrandr runs `xrandr` and parses its output.
func queryXrandr() ([]Output, error) {
out, err := exec.Command("xrandr").Output()
if err != nil {
return nil, fmt.Errorf("running xrandr: %w", err)
}
return Parse(string(out)), nil
}
// applyXrandr runs `xrandr` with the computed arguments, passing its output
// through. A non-zero exit status is propagated to our own, because the callers
// of this tool -- the xbindkeys bindings -- chain further commands with `&&`.
func applyXrandr(args []string) error {
cmd := exec.Command("xrandr", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("xrandr %s: %w", strings.Join(args, " "), err)
}
return nil
}
// printJSON writes the outputs as a JSON object keyed by connector name.
//
// The shape matches what the node parser used to emit, so that the two could be
// compared directly while this tool was replacing it.
func printJSON(outputs []Output) error {
byName := make(map[string]Output, len(outputs))
for _, o := range outputs {
byName[o.Name] = o
}
encoded, err := json.MarshalIndent(byName, "", " ")
if err != nil {
return fmt.Errorf("encoding JSON: %w", err)
}
fmt.Println(string(encoded))
return nil
}
|