-
Notifications
You must be signed in to change notification settings - Fork 17
/
xrandr.go
148 lines (132 loc) · 3.62 KB
/
xrandr.go
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package wallutils
import (
"errors"
"fmt"
"image"
"os"
"strconv"
"strings"
"github.com/fatih/color"
)
// XRandr can contain information that has been extracted by running the xrandr executable
type XRandr struct {
hasOverlap bool
resolutionLines []string
verbose bool
hasChecked bool
}
// NewXRandr creates a new XRandr struct and fills it with information
// by running "xrandr". An error is returned if xrandr could not be found.
func NewXRandr(verbose bool) (*XRandr, error) {
if which("xrandr") == "" {
return nil, errors.New("could not find the xrandr executable")
}
x := &XRandr{verbose: verbose}
// TODO: Let CheckOverlap return an error if something went wrong
x.CheckOverlap()
return x, nil
}
// Reset the XRander check, for preparing to run xrandr again
func (x *XRandr) Reset() {
x.hasChecked = false
x.resolutionLines = []string{}
}
// CheckOverlap checks if the displays listed by "xrandr" overlaps, or not
// A slice of relevant lines from the xrandr output is stored in the struct.
func (x *XRandr) CheckOverlap() {
if x.hasChecked {
return
}
if x.verbose {
fmt.Print("Running ")
}
xrandrOutput := output("xrandr", []string{}, x.verbose)
rects := make([]image.Rectangle, 0)
for _, line := range strings.Split(xrandrOutput, "\n") {
words := strings.Fields(line)
if len(words) < 3 {
continue
}
if words[1] != "connected" {
continue
}
for _, res := range words[2:] {
if !strings.Contains(res, "x") {
continue
}
if strings.Count(res, "+") != 2 {
continue
}
fields := strings.SplitN(res, "x", 2)
ws, tail := fields[0], fields[1]
fields = strings.SplitN(tail, "+", 2)
hs, tail := fields[0], fields[1]
fields = strings.SplitN(tail, "+", 2)
xs, ys := fields[0], fields[1]
x.resolutionLines = append(x.resolutionLines, line)
// Convert coordinates from string to int
x, err := strconv.Atoi(xs)
if err != nil {
continue
}
y, err := strconv.Atoi(ys)
if err != nil {
continue
}
width, err := strconv.Atoi(ws)
if err != nil {
continue
}
height, err := strconv.Atoi(hs)
if err != nil {
continue
}
// Create a new Rect struct and append it to the collection
r := NewRect(uint(x), uint(y), uint(width), uint(height))
rects = append(rects, r)
// Don't examine the rest of the words, but skip to the next line
break
}
}
x.hasOverlap = Overlaps(rects)
x.hasChecked = true
}
// String returns a multiline string with the collected
// resolution lines from xrandr (if any).
func (x *XRandr) String() string {
return strings.Join(x.resolutionLines, "\n")
}
// QuitIfOverlap will quit with an error if monitor configurations overlap
func (x *XRandr) QuitIfOverlap() {
if x.hasOverlap {
red := color.New(color.FgRed)
white := color.New(color.FgWhite, color.Bold)
red.Fprint(os.Stderr, "ERROR: ")
fmt.Fprintln(os.Stderr, "xrandr shows overlapping monitor configurations:")
white.Fprintln(os.Stderr, x)
fmt.Fprintln(os.Stderr, "Please check your display settings.")
os.Exit(1)
}
}
var cachedXRandr *XRandr
// NoXRandrOverlapOrExit is a convenience function for making sure monitor
// configurations are not overlapping, as reported by "xrandr".
func NoXRandrOverlapOrExit(verbose bool) {
var (
err error
initialRun bool
)
if cachedXRandr == nil {
cachedXRandr, err = NewXRandr(verbose)
if err != nil {
// Could not check, just return
return
}
initialRun = true
}
// Exit with an error if monitor configurations are overlapping
cachedXRandr.QuitIfOverlap()
if initialRun && cachedXRandr.verbose {
fmt.Println("Detected no overlapping monitor configurations.")
}
}