Initial code commit. At this point we connect to the X server, declare ourselves...
authorAaron Taylor <ataylor@subgeniuskitty.com>
Tue, 7 Feb 2023 06:25:25 +0000 (22:25 -0800)
committerAaron Taylor <ataylor@subgeniuskitty.com>
Tue, 7 Feb 2023 06:25:25 +0000 (22:25 -0800)
illi.go [new file with mode: 0644]

diff --git a/illi.go b/illi.go
new file mode 100644 (file)
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?
+}