New milestone: can launch and display an xterm
[illi] / initialization.go
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)
}
}