+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/jezek/xgb"
+ "github.com/jezek/xgb/xinerama"
+ "github.com/jezek/xgb/xproto"
+)
+
+
+
+// -----------------------------------------------------------------------------
+// Note: Since functions in this file are related to initialization and thus
+// execute at a time when the program is still building toward a viable state,
+// these functions intentionally exit via a series of explicit commands,
+// cleaning up only what has already been initialized rather than simply
+// calling the normal illi die/restart functions.
+// -----------------------------------------------------------------------------
+
+
+
+// The caller is responsible for tracking the lifetime of the returned X
+// connection and calling connection.Close() when appropriate.
+func connectToXServer() *xgb.Conn {
+ connection, err := xgb.NewConn()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "ILLI: Failed to connect to X server: %s\n", err.Error())
+ os.Exit(1)
+ }
+ return connection
+}
+
+func getAttachedScreens(conn *xgb.Conn) (screens []xinerama.ScreenInfo) {
+ // First, attempt to use xinerama to obtain screen information.
+ err := xinerama.Init(conn)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "ILLI: Unable to initialize xinerama: %s\n", err.Error())
+ } else { // Xinerama successfully initialized.
+ reply, err := xinerama.QueryScreens(conn).Reply()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "ILLI: Unusable result when querying screens through xinerama: %s\n", err.Error())
+ } else {
+ screens = reply.ScreenInfo
+ return
+ }
+ }
+
+ // Since we were unable to obtain the screen information via xinerama, we
+ // fall back to requesting ScreenInfo directly from the X server.
+ setup := xproto.Setup(conn)
+ if setup == nil || len(setup.Roots) < 1 {
+ fmt.Fprintf(os.Stderr, "ILLI: Unusable ScreenInfo received from X server.\n")
+ conn.Close()
+ os.Exit(1)
+ } else {
+ screens = []xinerama.ScreenInfo{
+ xinerama.ScreenInfo{
+ Width: setup.Roots[0].WidthInPixels,
+ Height: setup.Roots[0].HeightInPixels,
+ },
+ }
+ return
+ }
+ return // Should be unreachable
+}
+
+func getXRoot(conn *xgb.Conn) xproto.ScreenInfo {
+ setup := xproto.Setup(conn)
+ if setup == nil || len(setup.Roots) < 1 {
+ fmt.Fprintf(os.Stderr, "ILLI: Failed to determine X root.\n")
+ conn.Close()
+ os.Exit(1)
+ }
+ return setup.Roots[0]
+}
+
+func getKeyboardMap(conn *xgb.Conn) (keymap [256][]xproto.Keysym) { // TODO: Why 256?
+ const ( // TODO: WhyTF? How does the keymap work under the hood?
+ loKey = 8
+ hiKey = 255
+ )
+
+ m := xproto.GetKeyboardMapping(conn, loKey, hiKey-loKey+1)
+ r, err := m.Reply()
+ if err != nil || r == nil {
+ fmt.Fprintf(os.Stderr, "ILLI: Failed to load keymap from X server: %s\n.", err.Error())
+ conn.Close()
+ os.Exit(1)
+ }
+
+ for i := 0; i < hiKey-loKey+1; i++ {
+ keymap[loKey+i] = r.Keysyms[i*int(r.KeysymsPerKeycode) : (i+1)*int(r.KeysymsPerKeycode)]
+ }
+ return keymap
+}
+
+func registerForKeyEvents(conn *xgb.Conn, root xproto.ScreenInfo, keymap [256][]xproto.Keysym) {
+ // First we populate []grabs with the keysym and modifiers we seek.
+ grabs := []struct {
+ sym xproto.Keysym
+ modifiers uint16
+ codes []xproto.Keycode
+ }{
+ // TODO: Need to define a config table and do both keysym grabs and
+ // main event loop using the config table rather than hardcoding key
+ // assignments throughout the code.
+ {
+ sym: XK_q,
+ modifiers: xproto.ModMask1 | xproto.ModMaskShift,
+ },
+ {
+ sym: XK_Return,
+ modifiers: xproto.ModMask1 | xproto.ModMaskShift,
+ },
+ }
+
+ // Next we use the keymap provided by the X server to identify the keycodes
+ // corresponding to the keysyms in []grabs.
+ for i, syms := range keymap {
+ for _, sym := range syms {
+ for c := range grabs {
+ if grabs[c].sym == sym {
+ grabs[c].codes = append(grabs[c].codes, xproto.Keycode(i))
+ }
+ }
+ }
+ }
+
+ // Finally, we register with the X server for each combo of
+ // keycode+modifiers in []grabs.
+ for _, grabbed := range grabs {
+ for _, code := range grabbed.codes {
+ err := xproto.GrabKeyChecked(
+ conn,
+ false,
+ root.Root,
+ grabbed.modifiers,
+ code,
+ xproto.GrabModeAsync,
+ xproto.GrabModeAsync,
+ ).Check()
+ if err != nil {
+ fmt.Fprintf(os.Stderr,
+ "ILLI: Failed to register keycode 0x%X with modifier(s) 0x%X: %s\n",
+ code, grabbed.modifiers, err.Error())
+ }
+
+ }
+ }
+}
+
+// In addition to the basic key/button press/release events, by registering for
+// events like SubstructureRedirect, we are declaring our intent to act as
+// window manager for this X server. Only one WM is allowed per X server, a
+// policy enforced on the X server's side by refusing the registration request
+// for events like SubstructureRedirect if an existing process already receives
+// those events (i.e. is already the window manager).
+func becomeWM(conn *xgb.Conn, root xproto.ScreenInfo) {
+ err := xproto.ChangeWindowAttributesChecked(
+ conn,
+ root.Root,
+ xproto.CwEventMask,
+ []uint32{
+ xproto.EventMaskKeyPress |
+ xproto.EventMaskKeyRelease |
+ xproto.EventMaskButtonPress |
+ xproto.EventMaskButtonRelease |
+ xproto.EventMaskStructureNotify |
+ xproto.EventMaskSubstructureRedirect,
+ // TODO: Should we also register for EventMaskSubstructureNotify ?
+ // Where is the authoritative list of events located?
+ }).Check()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "ILLI: Unable to register as WM with X server: %s\n", err.Error())
+ conn.Close()
+ os.Exit(1)
+ }
+}
+