Refactored the some of the initialization code out of main() and into its own file...
authorAaron Taylor <ataylor@subgeniuskitty.com>
Thu, 9 Feb 2023 00:40:24 +0000 (16:40 -0800)
committerAaron Taylor <ataylor@subgeniuskitty.com>
Thu, 9 Feb 2023 00:40:24 +0000 (16:40 -0800)
illi.go
initialization.go [new file with mode: 0644]

diff --git a/illi.go b/illi.go
index 19c99b2..e535cc5 100644 (file)
--- a/illi.go
+++ b/illi.go
@@ -7,7 +7,6 @@ import (
     "os/exec"
 
     "github.com/jezek/xgb"
     "os/exec"
 
     "github.com/jezek/xgb"
-    "github.com/jezek/xgb/xinerama"
     "github.com/jezek/xgb/xproto"
 )
 
     "github.com/jezek/xgb/xproto"
 )
 
@@ -21,131 +20,24 @@ const (
 // basic functionality. Before proceeding much further, figure out what to do
 // with them.
 var xc *xgb.Conn
 // basic functionality. Before proceeding much further, figure out what to do
 // with them.
 var xc *xgb.Conn
-var attachedScreens []xinerama.ScreenInfo
-var xroot xproto.ScreenInfo
 var keymap [256][]xproto.Keysym
 
 func main() {
 var keymap [256][]xproto.Keysym
 
 func main() {
+    xconn := connectToXServer()
+    attachedScreens := getAttachedScreens(xconn)
+    xroot := getXRoot(xconn)
+    keymap = getKeyboardMap(xconn)
+    registerForKeyEvents(xconn, xroot, keymap)
 
 
-    // Build a connection to the X server --------------------------------------
+    becomeWM(xconn, xroot)
 
 
-    xConn, err := xgb.NewConn()
-    if err != nil {
-        log.Fatal(err)
-    }
-    xc = xConn
-    defer xc.Close()
-
-    // Determine what sort of screen(s) will display output --------------------
-
-    setup := xproto.Setup(xc)
-    if setup == nil || len(setup.Roots) < 1 {
-        log.Fatal("ILLI: Unable to parse received SetupInfo from X11 server.")
-    }
-
-    if err := xinerama.Init(xc); err != nil {
-        log.Fatal(err)
-    }
-
-    if r, err := xinerama.QueryScreens(xc).Reply(); err != nil {
-        log.Fatal(err)
-    } else {
-        if len(r.ScreenInfo) == 0 {
-            attachedScreens = []xinerama.ScreenInfo{
-                xinerama.ScreenInfo{
-                    Width:  setup.Roots[0].WidthInPixels,
-                    Height: setup.Roots[0].HeightInPixels,
-                },
-            }
-        } else {
-            attachedScreens = r.ScreenInfo
-        }
-    }
-
-    // Establish a root for the tree -------------------------------------------
-
-    connInfo := xproto.Setup(xc)
-    if connInfo == nil {
-        log.Fatal("ILLI: Unable to parse X connection information")
-    }
-    if len(connInfo.Roots) != 1 {
-        log.Fatal("ILLI: Inappropriate number of roots. Did xinerama initialize correctly?")
-    }
-    xroot = connInfo.Roots[0]
-
-    // Register with the X server as the new window manager --------------------
-
-    if err := TakeWMOwnership(); err != nil {
-        if _, ok := err.(xproto.AccessError); ok {
-            log.Fatal("ILLI: Unable to register as window manager with X server. Perhaps another WM is already running?")
-        }
-        log.Fatal(err)
-    }
-
-    fmt.Println("ILLI: Successfully registered as WM with X server.")
-
-    // Setup keysym mappings and register for WM-relevant key events -----------
-
-    const (
-        loKey = 8
-        hiKey = 255
-    )
-
-    m := xproto.GetKeyboardMapping(xc, loKey, hiKey-loKey+1)
-    reply, err := m.Reply()
-    if err != nil {
-        log.Fatal(err)
-    }
-    if reply == nil {
-        log.Fatal("Could not load keyboard map")
-    }
-
-    for i := 0; i < hiKey-loKey+1; i++ {
-        keymap[loKey+i] = reply.Keysyms[i*int(reply.KeysymsPerKeycode) : (i+1)*int(reply.KeysymsPerKeycode)]
-    }
-    grabs := []struct {
-        sym       xproto.Keysym
-        modifiers uint16
-        codes     []xproto.Keycode
-    }{
-        {
-            sym:       XK_q,
-            modifiers: xproto.ModMask1 | xproto.ModMaskShift,
-        },
-        {
-            sym:       XK_Return,
-            modifiers: xproto.ModMask1 | xproto.ModMaskShift,
-        },
-    }
-
-    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))
-                }
-            }
-        }
-    }
-    for _, grabbed := range grabs {
-        for _, code := range grabbed.codes {
-            if err := xproto.GrabKeyChecked(
-                xc,
-                false,
-                xroot.Root,
-                grabbed.modifiers,
-                code,
-                xproto.GrabModeAsync,
-                xproto.GrabModeAsync,
-            ).Check(); err != nil {
-                log.Print(err)
-            }
+    // Build a list of windows needing management ------------------------------
 
 
-        }
+    xc = xconn
+    if len(attachedScreens) > 0 {
+        fmt.Println("The Go compiler is waaaaay too picky about unused variables...")
     }
 
     }
 
-    // Build a list of windows needing management ------------------------------
-
     tree, err := xproto.QueryTree(xc, xroot.Root).Reply()
     if err != nil {
         log.Fatal(err)
     tree, err := xproto.QueryTree(xc, xroot.Root).Reply()
     if err != nil {
         log.Fatal(err)
@@ -196,29 +88,6 @@ eventloop:
     }
 }
 
     }
 }
 
-// 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 TakeWMOwnership() error {
-    return xproto.ChangeWindowAttributesChecked(
-        xc,
-        xroot.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()
-}
-
 // The client list appears to have some entries for windows that shouldn't
 // actually be viewable. For now, it appears that only windows with a
 // `MapState` value of 2 should be viewable. TODO: Verify this.
 // The client list appears to have some entries for windows that shouldn't
 // actually be viewable. For now, it appears that only windows with a
 // `MapState` value of 2 should be viewable. TODO: Verify this.
diff --git a/initialization.go b/initialization.go
new file mode 100644 (file)
index 0000000..7f2a50a
--- /dev/null
@@ -0,0 +1,181 @@
+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)
+    }
+}
+