Commit | Line | Data |
---|---|---|
19cbf0a1 AT |
1 | package main |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "os" | |
6 | ||
7 | "github.com/jezek/xgb" | |
8 | "github.com/jezek/xgb/xinerama" | |
9 | "github.com/jezek/xgb/xproto" | |
10 | ) | |
11 | ||
12 | ||
13 | ||
14 | // ----------------------------------------------------------------------------- | |
15 | // Note: Since functions in this file are related to initialization and thus | |
16 | // execute at a time when the program is still building toward a viable state, | |
17 | // these functions intentionally exit via a series of explicit commands, | |
18 | // cleaning up only what has already been initialized rather than simply | |
19 | // calling the normal illi die/restart functions. | |
20 | // ----------------------------------------------------------------------------- | |
21 | ||
22 | ||
23 | ||
effccebe | 24 | // Note: The caller is responsible for tracking the lifetime of the returned X |
19cbf0a1 AT |
25 | // connection and calling connection.Close() when appropriate. |
26 | func connectToXServer() *xgb.Conn { | |
27 | connection, err := xgb.NewConn() | |
28 | if err != nil { | |
29 | fmt.Fprintf(os.Stderr, "ILLI: Failed to connect to X server: %s\n", err.Error()) | |
30 | os.Exit(1) | |
31 | } | |
32 | return connection | |
33 | } | |
34 | ||
35 | func getAttachedScreens(conn *xgb.Conn) (screens []xinerama.ScreenInfo) { | |
effccebe | 36 | // Attempt to use xinerama to obtain screen information. |
19cbf0a1 AT |
37 | err := xinerama.Init(conn) |
38 | if err != nil { | |
39 | fmt.Fprintf(os.Stderr, "ILLI: Unable to initialize xinerama: %s\n", err.Error()) | |
40 | } else { // Xinerama successfully initialized. | |
41 | reply, err := xinerama.QueryScreens(conn).Reply() | |
42 | if err != nil { | |
43 | fmt.Fprintf(os.Stderr, "ILLI: Unusable result when querying screens through xinerama: %s\n", err.Error()) | |
44 | } else { | |
45 | screens = reply.ScreenInfo | |
46 | return | |
47 | } | |
48 | } | |
49 | ||
50 | // Since we were unable to obtain the screen information via xinerama, we | |
51 | // fall back to requesting ScreenInfo directly from the X server. | |
52 | setup := xproto.Setup(conn) | |
53 | if setup == nil || len(setup.Roots) < 1 { | |
54 | fmt.Fprintf(os.Stderr, "ILLI: Unusable ScreenInfo received from X server.\n") | |
55 | conn.Close() | |
56 | os.Exit(1) | |
57 | } else { | |
58 | screens = []xinerama.ScreenInfo{ | |
59 | xinerama.ScreenInfo{ | |
60 | Width: setup.Roots[0].WidthInPixels, | |
61 | Height: setup.Roots[0].HeightInPixels, | |
62 | }, | |
63 | } | |
64 | return | |
65 | } | |
66 | return // Should be unreachable | |
67 | } | |
68 | ||
69 | func getXRoot(conn *xgb.Conn) xproto.ScreenInfo { | |
70 | setup := xproto.Setup(conn) | |
71 | if setup == nil || len(setup.Roots) < 1 { | |
72 | fmt.Fprintf(os.Stderr, "ILLI: Failed to determine X root.\n") | |
73 | conn.Close() | |
74 | os.Exit(1) | |
75 | } | |
76 | return setup.Roots[0] | |
77 | } | |
78 | ||
79 | func getKeyboardMap(conn *xgb.Conn) (keymap [256][]xproto.Keysym) { // TODO: Why 256? | |
effccebe | 80 | const ( // TODO: Why? How does the keymap work under the hood? |
19cbf0a1 AT |
81 | loKey = 8 |
82 | hiKey = 255 | |
83 | ) | |
84 | ||
85 | m := xproto.GetKeyboardMapping(conn, loKey, hiKey-loKey+1) | |
86 | r, err := m.Reply() | |
87 | if err != nil || r == nil { | |
88 | fmt.Fprintf(os.Stderr, "ILLI: Failed to load keymap from X server: %s\n.", err.Error()) | |
89 | conn.Close() | |
90 | os.Exit(1) | |
91 | } | |
92 | ||
93 | for i := 0; i < hiKey-loKey+1; i++ { | |
94 | keymap[loKey+i] = r.Keysyms[i*int(r.KeysymsPerKeycode) : (i+1)*int(r.KeysymsPerKeycode)] | |
95 | } | |
96 | return keymap | |
97 | } | |
98 | ||
99 | func registerForKeyEvents(conn *xgb.Conn, root xproto.ScreenInfo, keymap [256][]xproto.Keysym) { | |
100 | // First we populate []grabs with the keysym and modifiers we seek. | |
101 | grabs := []struct { | |
102 | sym xproto.Keysym | |
103 | modifiers uint16 | |
104 | codes []xproto.Keycode | |
105 | }{ | |
106 | // TODO: Need to define a config table and do both keysym grabs and | |
107 | // main event loop using the config table rather than hardcoding key | |
108 | // assignments throughout the code. | |
109 | { | |
110 | sym: XK_q, | |
111 | modifiers: xproto.ModMask1 | xproto.ModMaskShift, | |
112 | }, | |
113 | { | |
114 | sym: XK_Return, | |
115 | modifiers: xproto.ModMask1 | xproto.ModMaskShift, | |
116 | }, | |
117 | } | |
118 | ||
119 | // Next we use the keymap provided by the X server to identify the keycodes | |
120 | // corresponding to the keysyms in []grabs. | |
121 | for i, syms := range keymap { | |
122 | for _, sym := range syms { | |
123 | for c := range grabs { | |
124 | if grabs[c].sym == sym { | |
125 | grabs[c].codes = append(grabs[c].codes, xproto.Keycode(i)) | |
126 | } | |
127 | } | |
128 | } | |
129 | } | |
130 | ||
131 | // Finally, we register with the X server for each combo of | |
132 | // keycode+modifiers in []grabs. | |
133 | for _, grabbed := range grabs { | |
134 | for _, code := range grabbed.codes { | |
135 | err := xproto.GrabKeyChecked( | |
136 | conn, | |
137 | false, | |
138 | root.Root, | |
139 | grabbed.modifiers, | |
140 | code, | |
141 | xproto.GrabModeAsync, | |
142 | xproto.GrabModeAsync, | |
143 | ).Check() | |
144 | if err != nil { | |
145 | fmt.Fprintf(os.Stderr, | |
146 | "ILLI: Failed to register keycode 0x%X with modifier(s) 0x%X: %s\n", | |
147 | code, grabbed.modifiers, err.Error()) | |
148 | } | |
149 | ||
150 | } | |
151 | } | |
152 | } | |
153 | ||
154 | // In addition to the basic key/button press/release events, by registering for | |
155 | // events like SubstructureRedirect, we are declaring our intent to act as | |
156 | // window manager for this X server. Only one WM is allowed per X server, a | |
157 | // policy enforced on the X server's side by refusing the registration request | |
158 | // for events like SubstructureRedirect if an existing process already receives | |
159 | // those events (i.e. is already the window manager). | |
160 | func becomeWM(conn *xgb.Conn, root xproto.ScreenInfo) { | |
161 | err := xproto.ChangeWindowAttributesChecked( | |
162 | conn, | |
163 | root.Root, | |
164 | xproto.CwEventMask, | |
165 | []uint32{ | |
166 | xproto.EventMaskKeyPress | | |
167 | xproto.EventMaskKeyRelease | | |
168 | xproto.EventMaskButtonPress | | |
169 | xproto.EventMaskButtonRelease | | |
170 | xproto.EventMaskStructureNotify | | |
171 | xproto.EventMaskSubstructureRedirect, | |
172 | // TODO: Should we also register for EventMaskSubstructureNotify ? | |
173 | // Where is the authoritative list of events located? | |
174 | }).Check() | |
175 | if err != nil { | |
176 | fmt.Fprintf(os.Stderr, "ILLI: Unable to register as WM with X server: %s\n", err.Error()) | |
177 | conn.Close() | |
178 | os.Exit(1) | |
179 | } | |
180 | } | |
181 |