From 6c708f255a57437500f06919974c66011c911abf Mon Sep 17 00:00:00 2001 From: Aaron Taylor Date: Mon, 6 Feb 2023 22:25:25 -0800 Subject: [PATCH] Initial code commit. At this point we connect to the X server, declare ourselves to be the new WM, get a client list, register for keypress events, and either launch an xterm or terminate ourselves upon receiving the correct keypress. Nothing is actually drawn onscreen at this time. --- illi.go | 242 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 illi.go diff --git a/illi.go b/illi.go new file mode 100644 index 0000000..5cb1641 --- /dev/null +++ b/illi.go @@ -0,0 +1,242 @@ +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? +} -- 2.20.1