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. // ----------------------------------------------------------------------------- // Note: 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) { // 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: Why? 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) } }