Initial code commit. At this point we connect to the X server, declare ourselves...
[illi] / illi.go
package main
import (
"fmt"
"log"
"os"
"os/exec"
"github.com/jezek/xgb"
"github.com/jezek/xgb/xinerama"
"github.com/jezek/xgb/xproto"
)
// TODO: Make a separate keysym file and fully populate it.
const (
XK_q = 0x0071
XK_Return = 0xff0d
)
var xc *xgb.Conn
var attachedScreens []xinerama.ScreenInfo
var xroot xproto.ScreenInfo
var keymap [256][]xproto.Keysym
func main() {
xConn, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
xc = xConn
defer xc.Close()
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
}
}
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]
// Attempt to register as the 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.")
// -----------------------------------------------------------------------------
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)
}
}
}
// -----------------------------------------------------------------------------
tree, err := xproto.QueryTree(xc, xroot.Root).Reply()
if err != nil {
log.Fatal(err)
}
if tree != nil {
//workspaces = make(map[string]*Workspace)
//defaultw := &Workspace{mu: &sync.Mutex{}}
for _, c := range tree.Children {
if isMappedWindow(c) {
// err := defaultw.Add(c)
// if err != nil {
// log.Println(err)
// }
fmt.Println("ILLI: Found a client.")
}
}
// if len(attachedScreens) > 0 {
// defaultw.Screen = &attachedScreens[0]
// }
//
// workspaces["default"] = defaultw
//
// if err := defaultw.TileWindows(); err != nil {
// log.Println(err)
// }
}
eventloop:
for {
xevent, err := xc.WaitForEvent()
if err != nil {
log.Println(err)
continue
}
switch e := xevent.(type) {
case xproto.KeyPressEvent:
if err := handleKeyPressEvent(e); err != nil {
break eventloop
}
default:
log.Println(xevent)
}
}
}
func TakeWMOwnership() error {
return xproto.ChangeWindowAttributesChecked(
xc,
xroot.Root,
xproto.CwEventMask,
[]uint32{
xproto.EventMaskKeyPress |
xproto.EventMaskKeyRelease |
xproto.EventMaskButtonPress |
xproto.EventMaskButtonRelease |
xproto.EventMaskStructureNotify |
xproto.EventMaskSubstructureRedirect,
}).Check()
}
func isMappedWindow(windowID xproto.Window) bool {
reply, err := xproto.GetWindowAttributes(xc, windowID).Reply()
if err != nil {
log.Fatal(err)
}
// TODO: Verify this.
// It appears that only windows with a `MapState` value of 2 should be 'viewable'.
// - https://github.com/BurntSushi/xgb/blob/master/xproto/xproto.go#L3772
// - https://github.com/BurntSushi/xgb/blob/master/xproto/xproto.go#L10287
if reply != nil && reply.MapState == 2 {
return true
}
return false
}
func handleKeyPressEvent(key xproto.KeyPressEvent) error {
switch keymap[key.Detail][0] {
case XK_q:
switch key.State {
case xproto.ModMask1 | xproto.ModMaskShift:
// TODO: Where and how do I want to handle exit/restart?
// -------------------------
// We must manually close the X connection since we used
// `defer` when setting it up and os.Exit() short-circuits
// that deferral.
xc.Close()
os.Exit(0)
}
case XK_Return:
switch key.State {
case xproto.ModMask1 | xproto.ModMaskShift:
cmd := exec.Command("xterm")
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
}
default:
return nil
}
return nil // What do I actually want to return here?
}