From 19cbf0a1d58d81bdabfdd3a7c83e9f356ea836a5 Mon Sep 17 00:00:00 2001 From: Aaron Taylor Date: Wed, 8 Feb 2023 16:40:24 -0800 Subject: [PATCH] Refactored the some of the initialization code out of main() and into its own file/functions. --- illi.go | 151 +++----------------------------------- initialization.go | 181 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 141 deletions(-) create mode 100644 initialization.go diff --git a/illi.go b/illi.go index 19c99b2..e535cc5 100644 --- a/illi.go +++ b/illi.go @@ -7,7 +7,6 @@ import ( "os/exec" "github.com/jezek/xgb" - "github.com/jezek/xgb/xinerama" "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 -var attachedScreens []xinerama.ScreenInfo -var xroot xproto.ScreenInfo 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) @@ -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. diff --git a/initialization.go b/initialization.go new file mode 100644 index 0000000..7f2a50a --- /dev/null +++ b/initialization.go @@ -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) + } +} + -- 2.20.1