"os/exec"
"github.com/jezek/xgb"
- "github.com/jezek/xgb/xinerama"
"github.com/jezek/xgb/xproto"
)
// 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)
}
}
-// 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.
--- /dev/null
+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)
+ }
+}
+