New milestone: can launch and display an xterm
[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"
ec873d07 10 "github.com/jezek/xgb/xinerama"
6c708f25
AT
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
ec873d07
AT
20type display struct {
21 screen xinerama.ScreenInfo
22 windows []xproto.Window
23}
24
6c708f25 25func main() {
ec873d07 26 // Set ourselves up as the new window manager ------------------------------
effccebe 27
19cbf0a1 28 xconn := connectToXServer()
19cbf0a1 29 xroot := getXRoot(xconn)
effccebe 30 keymap := getKeyboardMap(xconn)
19cbf0a1 31 registerForKeyEvents(xconn, xroot, keymap)
12099ed6 32
19cbf0a1 33 becomeWM(xconn, xroot)
12099ed6 34
ec873d07 35 // Build a list of output devices ------------------------------------------
6c708f25 36
ec873d07
AT
37 var attachedDisplays []display
38 for _, screen := range getAttachedScreens(xconn) {
39 attachedDisplays = append(attachedDisplays, display{screen: screen})
6c708f25 40 }
ec873d07
AT
41 // TODO: Verify some displays actually exist and we don't have an empty array. Otherwise, die here.
42 // TODO: Should this all go in its own function and simply return attachedDisplays[] here instead? What will my fundamental data structure be?
43
44 // Build a list of windows needing management ------------------------------
6c708f25 45
ec873d07 46 // TODO: The following should all be an initialization function. It should not be here.
effccebe 47 tree, err := xproto.QueryTree(xconn, xroot.Root).Reply()
6c708f25 48 if err != nil {
ec873d07 49 // TODO: Better error
6c708f25
AT
50 log.Fatal(err)
51 }
52 if tree != nil {
6c708f25 53 for _, c := range tree.Children {
effccebe 54 if isMappedWindow(xconn, c) {
ec873d07
AT
55 // For now, dump any pre-existing windows into the first
56 // display. Later we can add functionality for tracking window
57 // attributes across window manager restarts.
58 attachedDisplays[0].windows = append(attachedDisplays[0].windows, c)
6c708f25
AT
59 }
60
61 }
ec873d07 62 // TODO: Try tiling/displaying/whatever with any existing windows before entering the event loop.
6c708f25
AT
63 }
64
12099ed6
AT
65 // Main program loop reacts to X events ------------------------------------
66
6c708f25
AT
67eventloop:
68 for {
ec873d07
AT
69 fmt.Printf("ILLI: attachedDisplays: ")
70 fmt.Println(attachedDisplays)
effccebe 71 xevent, err := xconn.WaitForEvent()
6c708f25
AT
72 if err != nil {
73 log.Println(err)
74 continue
75 }
76 switch e := xevent.(type) {
77 case xproto.KeyPressEvent:
ec873d07 78 fmt.Printf("ILLI: Received xproto.KeyPressEvent\n")
effccebe 79 if err := handleKeyPressEvent(keymap, e); err != nil {
6c708f25
AT
80 break eventloop
81 }
ec873d07
AT
82 case xproto.KeyReleaseEvent:
83 fmt.Printf("ILLI: Received xproto.KeyReleaseEvent\n")
84 case xproto.DestroyNotifyEvent:
85 fmt.Printf("ILLI: Received xproto.DestroyNotifyEvent\n")
86 case xproto.ConfigureRequestEvent:
87 fmt.Printf("ILLI: Received xproto.ConfigureRequestEvent\n")
88 rewrittenEvent := xproto.ConfigureNotifyEvent{
89 Event: e.Window,
90 Window: e.Window,
91 AboveSibling: 0,
92 X: e.X,
93 Y: e.Y,
94 Width: e.Width,
95 Height: e.Height,
96 BorderWidth: 0,
97 OverrideRedirect: false,
98 }
99 xproto.SendEventChecked(xconn, false, e.Window, xproto.EventMaskStructureNotify, string(rewrittenEvent.Bytes()))
100 case xproto.MapRequestEvent:
101 fmt.Printf("ILLI: Received xproto.MapRequestEvent\n")
102 if windowAttributes, err := xproto.GetWindowAttributes(xconn, e.Window).Reply(); err != nil || !windowAttributes.OverrideRedirect {
103 xproto.MapWindowChecked(xconn, e.Window)
104 attachedDisplays[0].windows = append(attachedDisplays[0].windows, e.Window)
105
106 if err := xproto.ConfigureWindowChecked (
107 xconn,
108 attachedDisplays[0].windows[0],
109 xproto.ConfigWindowX |
110 xproto.ConfigWindowY |
111 xproto.ConfigWindowWidth |
112 xproto.ConfigWindowHeight,
113 []uint32{
114 uint32(100), // TODO: Temporarily hardcoding for testing.
115 uint32(100), // TODO: Temporarily hardcoding for testing.
116 uint32(1720), // TODO: Temporarily hardcoding for testing.
117 uint32(1000), // TODO: Temporarily hardcoding for testing.
118 }).Check(); err != nil {
119 fmt.Println("ILLI: Failed to handle MapRequestEvent")
120 }
121
122 }
123 case xproto.EnterNotifyEvent:
124 fmt.Printf("ILLI: Received xproto.EnterNotifyEvent\n")
6c708f25 125 default:
ec873d07 126 fmt.Printf("ILLI: Unknown X event received: %s\n", xevent)
6c708f25
AT
127 }
128 }
129}
130
12099ed6
AT
131// The client list appears to have some entries for windows that shouldn't
132// actually be viewable. For now, it appears that only windows with a
133// `MapState` value of 2 should be viewable. TODO: Verify this.
134// - https://github.com/BurntSushi/xgb/blob/master/xproto/xproto.go#L3772
135// - https://github.com/BurntSushi/xgb/blob/master/xproto/xproto.go#L10287
effccebe
AT
136func isMappedWindow(conn *xgb.Conn, windowID xproto.Window) bool {
137 reply, err := xproto.GetWindowAttributes(conn, windowID).Reply()
6c708f25
AT
138 if err != nil {
139 log.Fatal(err)
140 }
6c708f25
AT
141 if reply != nil && reply.MapState == 2 {
142 return true
143 }
144 return false
145}
146
12099ed6
AT
147// TODO: Determine how I want to split the main event loop from the various
148// event handlers (like this keypress handler). It shouldn't grow too
149// fragmented, nor should it grow into a monolithic beast, but the balance
150// needs to be selected after the handlers are built out more completely.
effccebe 151func handleKeyPressEvent(keymap [256][]xproto.Keysym, key xproto.KeyPressEvent) error {
6c708f25
AT
152 switch keymap[key.Detail][0] {
153 case XK_q:
154 switch key.State {
155 case xproto.ModMask1 | xproto.ModMaskShift:
156 // TODO: Where and how do I want to handle exit/restart?
157 // -------------------------
158 // We must manually close the X connection since we used
159 // `defer` when setting it up and os.Exit() short-circuits
160 // that deferral.
effccebe 161 //xc.Close()
6c708f25
AT
162 os.Exit(0)
163 }
164 case XK_Return:
165 switch key.State {
166 case xproto.ModMask1 | xproto.ModMaskShift:
167 cmd := exec.Command("xterm")
168 err := cmd.Start()
169 if err != nil {
170 log.Fatal(err)
171 }
172 }
173 default:
174 return nil
175 }
12099ed6 176 return nil // TODO: What do I actually want to return here?
6c708f25 177}