Adding some comments after re-reading last night's code commit.
[illi] / illi.go
CommitLineData
6c708f25
AT
1package main
2
3import (
4 "fmt"
5 "log"
6 "os"
7 "os/exec"
8
9 "github.com/jezek/xgb"
10 "github.com/jezek/xgb/xinerama"
11 "github.com/jezek/xgb/xproto"
12)
13
14// TODO: Make a separate keysym file and fully populate it.
15const (
16 XK_q = 0x0071
17 XK_Return = 0xff0d
18)
19
12099ed6
AT
20// TODO: These module-level global variables have arisen while establishing
21// basic functionality. Before proceeding much further, figure out what to do
22// with them.
6c708f25
AT
23var xc *xgb.Conn
24var attachedScreens []xinerama.ScreenInfo
25var xroot xproto.ScreenInfo
26var keymap [256][]xproto.Keysym
27
28func main() {
12099ed6
AT
29
30 // Build a connection to the X server --------------------------------------
31
6c708f25
AT
32 xConn, err := xgb.NewConn()
33 if err != nil {
34 log.Fatal(err)
35 }
36 xc = xConn
37 defer xc.Close()
38
12099ed6
AT
39 // Determine what sort of screen(s) will display output --------------------
40
6c708f25
AT
41 setup := xproto.Setup(xc)
42 if setup == nil || len(setup.Roots) < 1 {
43 log.Fatal("ILLI: Unable to parse received SetupInfo from X11 server.")
44 }
45
46 if err := xinerama.Init(xc); err != nil {
47 log.Fatal(err)
48 }
49
50 if r, err := xinerama.QueryScreens(xc).Reply(); err != nil {
51 log.Fatal(err)
52 } else {
53 if len(r.ScreenInfo) == 0 {
54 attachedScreens = []xinerama.ScreenInfo{
55 xinerama.ScreenInfo{
56 Width: setup.Roots[0].WidthInPixels,
57 Height: setup.Roots[0].HeightInPixels,
58 },
59 }
60 } else {
61 attachedScreens = r.ScreenInfo
62 }
63 }
64
12099ed6
AT
65 // Establish a root for the tree -------------------------------------------
66
6c708f25
AT
67 connInfo := xproto.Setup(xc)
68 if connInfo == nil {
69 log.Fatal("ILLI: Unable to parse X connection information")
70 }
71 if len(connInfo.Roots) != 1 {
72 log.Fatal("ILLI: Inappropriate number of roots. Did xinerama initialize correctly?")
73 }
74 xroot = connInfo.Roots[0]
75
12099ed6
AT
76 // Register with the X server as the new window manager --------------------
77
6c708f25
AT
78 if err := TakeWMOwnership(); err != nil {
79 if _, ok := err.(xproto.AccessError); ok {
80 log.Fatal("ILLI: Unable to register as window manager with X server. Perhaps another WM is already running?")
81 }
82 log.Fatal(err)
83 }
84
85 fmt.Println("ILLI: Successfully registered as WM with X server.")
86
12099ed6 87 // Setup keysym mappings and register for WM-relevant key events -----------
6c708f25
AT
88
89 const (
90 loKey = 8
91 hiKey = 255
92 )
93
94 m := xproto.GetKeyboardMapping(xc, loKey, hiKey-loKey+1)
95 reply, err := m.Reply()
96 if err != nil {
97 log.Fatal(err)
98 }
99 if reply == nil {
100 log.Fatal("Could not load keyboard map")
101 }
102
103 for i := 0; i < hiKey-loKey+1; i++ {
104 keymap[loKey+i] = reply.Keysyms[i*int(reply.KeysymsPerKeycode) : (i+1)*int(reply.KeysymsPerKeycode)]
105 }
106 grabs := []struct {
107 sym xproto.Keysym
108 modifiers uint16
109 codes []xproto.Keycode
110 }{
111 {
112 sym: XK_q,
113 modifiers: xproto.ModMask1 | xproto.ModMaskShift,
114 },
115 {
116 sym: XK_Return,
117 modifiers: xproto.ModMask1 | xproto.ModMaskShift,
118 },
119 }
120
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 for _, grabbed := range grabs {
131 for _, code := range grabbed.codes {
132 if err := xproto.GrabKeyChecked(
133 xc,
134 false,
135 xroot.Root,
136 grabbed.modifiers,
137 code,
138 xproto.GrabModeAsync,
139 xproto.GrabModeAsync,
140 ).Check(); err != nil {
141 log.Print(err)
142 }
143
144 }
145 }
146
12099ed6 147 // Build a list of windows needing management ------------------------------
6c708f25
AT
148
149 tree, err := xproto.QueryTree(xc, xroot.Root).Reply()
150 if err != nil {
151 log.Fatal(err)
152 }
153 if tree != nil {
154 //workspaces = make(map[string]*Workspace)
155 //defaultw := &Workspace{mu: &sync.Mutex{}}
156 for _, c := range tree.Children {
157 if isMappedWindow(c) {
158// err := defaultw.Add(c)
159// if err != nil {
160// log.Println(err)
161// }
162 fmt.Println("ILLI: Found a client.")
163 }
164
165 }
166
167// if len(attachedScreens) > 0 {
168// defaultw.Screen = &attachedScreens[0]
169// }
170//
171// workspaces["default"] = defaultw
172//
173// if err := defaultw.TileWindows(); err != nil {
174// log.Println(err)
175// }
176
177 }
178
12099ed6
AT
179 // Main program loop reacts to X events ------------------------------------
180
6c708f25
AT
181eventloop:
182 for {
183 xevent, err := xc.WaitForEvent()
184 if err != nil {
185 log.Println(err)
186 continue
187 }
188 switch e := xevent.(type) {
189 case xproto.KeyPressEvent:
190 if err := handleKeyPressEvent(e); err != nil {
191 break eventloop
192 }
193 default:
194 log.Println(xevent)
195 }
196 }
197}
198
12099ed6
AT
199// In addition to the basic key/button press/release events, by registering for
200// events like SubstructureRedirect, we are declaring our intent to act as
201// window manager for this X server. Only one WM is allowed per X server, a
202// policy enforced on the X server's side by refusing the registration request
203// for events like SubstructureRedirect if an existing process already receives
204// those events (i.e. is already the window manager).
6c708f25
AT
205func TakeWMOwnership() error {
206 return xproto.ChangeWindowAttributesChecked(
207 xc,
208 xroot.Root,
209 xproto.CwEventMask,
210 []uint32{
211 xproto.EventMaskKeyPress |
212 xproto.EventMaskKeyRelease |
213 xproto.EventMaskButtonPress |
214 xproto.EventMaskButtonRelease |
215 xproto.EventMaskStructureNotify |
216 xproto.EventMaskSubstructureRedirect,
12099ed6
AT
217 // TODO: Should we also register for EventMaskSubstructureNotify ?
218 // Where is the authoritative list of events located?
6c708f25
AT
219 }).Check()
220}
221
12099ed6
AT
222// The client list appears to have some entries for windows that shouldn't
223// actually be viewable. For now, it appears that only windows with a
224// `MapState` value of 2 should be viewable. TODO: Verify this.
225// - https://github.com/BurntSushi/xgb/blob/master/xproto/xproto.go#L3772
226// - https://github.com/BurntSushi/xgb/blob/master/xproto/xproto.go#L10287
6c708f25
AT
227func isMappedWindow(windowID xproto.Window) bool {
228 reply, err := xproto.GetWindowAttributes(xc, windowID).Reply()
229 if err != nil {
230 log.Fatal(err)
231 }
6c708f25
AT
232 if reply != nil && reply.MapState == 2 {
233 return true
234 }
235 return false
236}
237
12099ed6
AT
238// TODO: Determine how I want to split the main event loop from the various
239// event handlers (like this keypress handler). It shouldn't grow too
240// fragmented, nor should it grow into a monolithic beast, but the balance
241// needs to be selected after the handlers are built out more completely.
6c708f25
AT
242func handleKeyPressEvent(key xproto.KeyPressEvent) error {
243 switch keymap[key.Detail][0] {
244 case XK_q:
245 switch key.State {
246 case xproto.ModMask1 | xproto.ModMaskShift:
247 // TODO: Where and how do I want to handle exit/restart?
248 // -------------------------
249 // We must manually close the X connection since we used
250 // `defer` when setting it up and os.Exit() short-circuits
251 // that deferral.
252 xc.Close()
253 os.Exit(0)
254 }
255 case XK_Return:
256 switch key.State {
257 case xproto.ModMask1 | xproto.ModMaskShift:
258 cmd := exec.Command("xterm")
259 err := cmd.Start()
260 if err != nil {
261 log.Fatal(err)
262 }
263 }
264 default:
265 return nil
266 }
12099ed6 267 return nil // TODO: What do I actually want to return here?
6c708f25 268}