Initial code commit. At this point we connect to the X server, declare ourselves...
[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
20var xc *xgb.Conn
21var attachedScreens []xinerama.ScreenInfo
22var xroot xproto.ScreenInfo
23var keymap [256][]xproto.Keysym
24
25func main() {
26 xConn, err := xgb.NewConn()
27 if err != nil {
28 log.Fatal(err)
29 }
30 xc = xConn
31 defer xc.Close()
32
33 setup := xproto.Setup(xc)
34 if setup == nil || len(setup.Roots) < 1 {
35 log.Fatal("ILLI: Unable to parse received SetupInfo from X11 server.")
36 }
37
38 if err := xinerama.Init(xc); err != nil {
39 log.Fatal(err)
40 }
41
42 if r, err := xinerama.QueryScreens(xc).Reply(); err != nil {
43 log.Fatal(err)
44 } else {
45 if len(r.ScreenInfo) == 0 {
46 attachedScreens = []xinerama.ScreenInfo{
47 xinerama.ScreenInfo{
48 Width: setup.Roots[0].WidthInPixels,
49 Height: setup.Roots[0].HeightInPixels,
50 },
51 }
52 } else {
53 attachedScreens = r.ScreenInfo
54 }
55 }
56
57 connInfo := xproto.Setup(xc)
58 if connInfo == nil {
59 log.Fatal("ILLI: Unable to parse X connection information")
60 }
61 if len(connInfo.Roots) != 1 {
62 log.Fatal("ILLI: Inappropriate number of roots. Did xinerama initialize correctly?")
63 }
64 xroot = connInfo.Roots[0]
65
66 // Attempt to register as the window manager
67 if err := TakeWMOwnership(); err != nil {
68 if _, ok := err.(xproto.AccessError); ok {
69 log.Fatal("ILLI: Unable to register as window manager with X server. Perhaps another WM is already running?")
70 }
71 log.Fatal(err)
72 }
73
74 fmt.Println("ILLI: Successfully registered as WM with X server.")
75
76// -----------------------------------------------------------------------------
77
78 const (
79 loKey = 8
80 hiKey = 255
81 )
82
83 m := xproto.GetKeyboardMapping(xc, loKey, hiKey-loKey+1)
84 reply, err := m.Reply()
85 if err != nil {
86 log.Fatal(err)
87 }
88 if reply == nil {
89 log.Fatal("Could not load keyboard map")
90 }
91
92 for i := 0; i < hiKey-loKey+1; i++ {
93 keymap[loKey+i] = reply.Keysyms[i*int(reply.KeysymsPerKeycode) : (i+1)*int(reply.KeysymsPerKeycode)]
94 }
95 grabs := []struct {
96 sym xproto.Keysym
97 modifiers uint16
98 codes []xproto.Keycode
99 }{
100 {
101 sym: XK_q,
102 modifiers: xproto.ModMask1 | xproto.ModMaskShift,
103 },
104 {
105 sym: XK_Return,
106 modifiers: xproto.ModMask1 | xproto.ModMaskShift,
107 },
108 }
109
110 for i, syms := range keymap {
111 for _, sym := range syms {
112 for c := range grabs {
113 if grabs[c].sym == sym {
114 grabs[c].codes = append(grabs[c].codes, xproto.Keycode(i))
115 }
116 }
117 }
118 }
119 for _, grabbed := range grabs {
120 for _, code := range grabbed.codes {
121 if err := xproto.GrabKeyChecked(
122 xc,
123 false,
124 xroot.Root,
125 grabbed.modifiers,
126 code,
127 xproto.GrabModeAsync,
128 xproto.GrabModeAsync,
129 ).Check(); err != nil {
130 log.Print(err)
131 }
132
133 }
134 }
135
136// -----------------------------------------------------------------------------
137
138 tree, err := xproto.QueryTree(xc, xroot.Root).Reply()
139 if err != nil {
140 log.Fatal(err)
141 }
142 if tree != nil {
143 //workspaces = make(map[string]*Workspace)
144 //defaultw := &Workspace{mu: &sync.Mutex{}}
145 for _, c := range tree.Children {
146 if isMappedWindow(c) {
147// err := defaultw.Add(c)
148// if err != nil {
149// log.Println(err)
150// }
151 fmt.Println("ILLI: Found a client.")
152 }
153
154 }
155
156// if len(attachedScreens) > 0 {
157// defaultw.Screen = &attachedScreens[0]
158// }
159//
160// workspaces["default"] = defaultw
161//
162// if err := defaultw.TileWindows(); err != nil {
163// log.Println(err)
164// }
165
166 }
167
168eventloop:
169 for {
170 xevent, err := xc.WaitForEvent()
171 if err != nil {
172 log.Println(err)
173 continue
174 }
175 switch e := xevent.(type) {
176 case xproto.KeyPressEvent:
177 if err := handleKeyPressEvent(e); err != nil {
178 break eventloop
179 }
180 default:
181 log.Println(xevent)
182 }
183 }
184}
185
186func TakeWMOwnership() error {
187 return xproto.ChangeWindowAttributesChecked(
188 xc,
189 xroot.Root,
190 xproto.CwEventMask,
191 []uint32{
192 xproto.EventMaskKeyPress |
193 xproto.EventMaskKeyRelease |
194 xproto.EventMaskButtonPress |
195 xproto.EventMaskButtonRelease |
196 xproto.EventMaskStructureNotify |
197 xproto.EventMaskSubstructureRedirect,
198 }).Check()
199}
200
201func isMappedWindow(windowID xproto.Window) bool {
202 reply, err := xproto.GetWindowAttributes(xc, windowID).Reply()
203 if err != nil {
204 log.Fatal(err)
205 }
206 // TODO: Verify this.
207 // It appears that only windows with a `MapState` value of 2 should be 'viewable'.
208 // - https://github.com/BurntSushi/xgb/blob/master/xproto/xproto.go#L3772
209 // - https://github.com/BurntSushi/xgb/blob/master/xproto/xproto.go#L10287
210 if reply != nil && reply.MapState == 2 {
211 return true
212 }
213 return false
214}
215
216func handleKeyPressEvent(key xproto.KeyPressEvent) error {
217 switch keymap[key.Detail][0] {
218 case XK_q:
219 switch key.State {
220 case xproto.ModMask1 | xproto.ModMaskShift:
221 // TODO: Where and how do I want to handle exit/restart?
222 // -------------------------
223 // We must manually close the X connection since we used
224 // `defer` when setting it up and os.Exit() short-circuits
225 // that deferral.
226 xc.Close()
227 os.Exit(0)
228 }
229 case XK_Return:
230 switch key.State {
231 case xproto.ModMask1 | xproto.ModMaskShift:
232 cmd := exec.Command("xterm")
233 err := cmd.Start()
234 if err != nil {
235 log.Fatal(err)
236 }
237 }
238 default:
239 return nil
240 }
241 return nil // What do I actually want to return here?
242}