New milestone: can launch and display an xterm
[illi] / illi.go
diff --git a/illi.go b/illi.go
index 19c99b2..e527d65 100644 (file)
--- a/illi.go
+++ b/illi.go
@@ -17,215 +17,124 @@ const (
     XK_Return = 0xff0d
 )
 
     XK_Return = 0xff0d
 )
 
-// TODO: These module-level global variables have arisen while establishing
-// basic functionality. Before proceeding much further, figure out what to do
-// with them.
-var xc *xgb.Conn
-var attachedScreens []xinerama.ScreenInfo
-var xroot xproto.ScreenInfo
-var keymap [256][]xproto.Keysym
+type display struct {
+    screen    xinerama.ScreenInfo
+    windows   []xproto.Window
+}
 
 func main() {
 
 func main() {
+    // Set ourselves up as the new window manager ------------------------------
 
 
-    // Build a connection to the X server --------------------------------------
+    xconn := connectToXServer()
+    xroot := getXRoot(xconn)
+    keymap := getKeyboardMap(xconn)
+    registerForKeyEvents(xconn, xroot, keymap)
 
 
-    xConn, err := xgb.NewConn()
-    if err != nil {
-        log.Fatal(err)
-    }
-    xc = xConn
-    defer xc.Close()
+    becomeWM(xconn, xroot)
 
 
-    // Determine what sort of screen(s) will display output --------------------
+    // Build a list of output devices ------------------------------------------
 
 
-    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
-        }
-    }
-
-    // Establish a root for the tree -------------------------------------------
-
-    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]
-
-    // Register with the X server as the new 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.")
-
-    // Setup keysym mappings and register for WM-relevant key events -----------
-
-    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)
-            }
-
-        }
+    var attachedDisplays []display
+    for _, screen := range getAttachedScreens(xconn) {
+        attachedDisplays = append(attachedDisplays, display{screen: screen})
     }
     }
+    // TODO: Verify some displays actually exist and we don't have an empty array. Otherwise, die here.
+    // TODO: Should this all go in its own function and simply return attachedDisplays[] here instead? What will my fundamental data structure be?
 
     // Build a list of windows needing management ------------------------------
 
 
     // Build a list of windows needing management ------------------------------
 
-    tree, err := xproto.QueryTree(xc, xroot.Root).Reply()
+    // TODO: The following should all be an initialization function. It should not be here.
+    tree, err := xproto.QueryTree(xconn, xroot.Root).Reply()
     if err != nil {
     if err != nil {
+        // TODO: Better error
         log.Fatal(err)
     }
     if tree != nil {
         log.Fatal(err)
     }
     if tree != nil {
-        //workspaces = make(map[string]*Workspace)
-        //defaultw := &Workspace{mu: &sync.Mutex{}}
         for _, c := range tree.Children {
         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 isMappedWindow(xconn, c) {
+                // For now, dump any pre-existing windows into the first
+                // display. Later we can add functionality for tracking window
+                // attributes across window manager restarts.
+                attachedDisplays[0].windows = append(attachedDisplays[0].windows, c)
             }
 
         }
             }
 
         }
-
-//        if len(attachedScreens) > 0 {
-//            defaultw.Screen = &attachedScreens[0]
-//        }
-//
-//        workspaces["default"] = defaultw
-//
-//        if err := defaultw.TileWindows(); err != nil {
-//            log.Println(err)
-//        }
-
+        // TODO: Try tiling/displaying/whatever with any existing windows before entering the event loop.
     }
 
     // Main program loop reacts to X events ------------------------------------
 
 eventloop:
     for {
     }
 
     // Main program loop reacts to X events ------------------------------------
 
 eventloop:
     for {
-        xevent, err := xc.WaitForEvent()
+        fmt.Printf("ILLI: attachedDisplays: ")
+        fmt.Println(attachedDisplays)
+        xevent, err := xconn.WaitForEvent()
         if err != nil {
             log.Println(err)
             continue
         }
         switch e := xevent.(type) {
             case xproto.KeyPressEvent:
         if err != nil {
             log.Println(err)
             continue
         }
         switch e := xevent.(type) {
             case xproto.KeyPressEvent:
-                if err := handleKeyPressEvent(e); err != nil {
+                fmt.Printf("ILLI: Received xproto.KeyPressEvent\n")
+                if err := handleKeyPressEvent(keymap, e); err != nil {
                     break eventloop
                 }
                     break eventloop
                 }
+            case xproto.KeyReleaseEvent:
+                fmt.Printf("ILLI: Received xproto.KeyReleaseEvent\n")
+            case xproto.DestroyNotifyEvent:
+                fmt.Printf("ILLI: Received xproto.DestroyNotifyEvent\n")
+            case xproto.ConfigureRequestEvent:
+                fmt.Printf("ILLI: Received xproto.ConfigureRequestEvent\n")
+                rewrittenEvent := xproto.ConfigureNotifyEvent{
+                    Event:            e.Window,
+                    Window:           e.Window,
+                    AboveSibling:     0,
+                    X:                e.X,
+                    Y:                e.Y,
+                    Width:            e.Width,
+                    Height:           e.Height,
+                    BorderWidth:      0,
+                    OverrideRedirect: false,
+                }
+                xproto.SendEventChecked(xconn, false, e.Window, xproto.EventMaskStructureNotify, string(rewrittenEvent.Bytes()))
+            case xproto.MapRequestEvent:
+                fmt.Printf("ILLI: Received xproto.MapRequestEvent\n")
+                if windowAttributes, err := xproto.GetWindowAttributes(xconn, e.Window).Reply(); err != nil || !windowAttributes.OverrideRedirect {
+                    xproto.MapWindowChecked(xconn, e.Window)
+                    attachedDisplays[0].windows = append(attachedDisplays[0].windows, e.Window)
+
+                    if err := xproto.ConfigureWindowChecked (
+                        xconn,
+                        attachedDisplays[0].windows[0],
+                        xproto.ConfigWindowX |
+                            xproto.ConfigWindowY |
+                            xproto.ConfigWindowWidth |
+                            xproto.ConfigWindowHeight,
+                        []uint32{
+                            uint32(100), // TODO: Temporarily hardcoding for testing.
+                            uint32(100), // TODO: Temporarily hardcoding for testing.
+                            uint32(1720), // TODO: Temporarily hardcoding for testing.
+                            uint32(1000), // TODO: Temporarily hardcoding for testing.
+                        }).Check(); err != nil {
+                        fmt.Println("ILLI: Failed to handle MapRequestEvent")
+                    }
+
+                }
+            case xproto.EnterNotifyEvent:
+                fmt.Printf("ILLI: Received xproto.EnterNotifyEvent\n")
             default:
             default:
-                log.Println(xevent)
+                fmt.Printf("ILLI: Unknown X event received: %s\n", xevent)
         }
     }
 }
 
         }
     }
 }
 
-// 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 TakeWMOwnership() error {
-    return xproto.ChangeWindowAttributesChecked(
-        xc,
-        xroot.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()
-}
-
 // The client list appears to have some entries for windows that shouldn't
 // actually be viewable. For now, it appears that only windows with a
 // `MapState` value of 2 should be viewable. TODO: Verify this.
 //   - https://github.com/BurntSushi/xgb/blob/master/xproto/xproto.go#L3772
 //   - https://github.com/BurntSushi/xgb/blob/master/xproto/xproto.go#L10287
 // The client list appears to have some entries for windows that shouldn't
 // actually be viewable. For now, it appears that only windows with a
 // `MapState` value of 2 should be viewable. TODO: Verify this.
 //   - https://github.com/BurntSushi/xgb/blob/master/xproto/xproto.go#L3772
 //   - https://github.com/BurntSushi/xgb/blob/master/xproto/xproto.go#L10287
-func isMappedWindow(windowID xproto.Window) bool {
-    reply, err := xproto.GetWindowAttributes(xc, windowID).Reply()
+func isMappedWindow(conn *xgb.Conn, windowID xproto.Window) bool {
+    reply, err := xproto.GetWindowAttributes(conn, windowID).Reply()
     if err != nil {
         log.Fatal(err)
     }
     if err != nil {
         log.Fatal(err)
     }
@@ -239,7 +148,7 @@ func isMappedWindow(windowID xproto.Window) bool {
 // event handlers (like this keypress handler). It shouldn't grow too
 // fragmented, nor should it grow into a monolithic beast, but the balance
 // needs to be selected after the handlers are built out more completely.
 // event handlers (like this keypress handler). It shouldn't grow too
 // fragmented, nor should it grow into a monolithic beast, but the balance
 // needs to be selected after the handlers are built out more completely.
-func handleKeyPressEvent(key xproto.KeyPressEvent) error {
+func handleKeyPressEvent(keymap [256][]xproto.Keysym, key xproto.KeyPressEvent) error {
     switch keymap[key.Detail][0] {
         case XK_q:
             switch key.State {
     switch keymap[key.Detail][0] {
         case XK_q:
             switch key.State {
@@ -249,7 +158,7 @@ func handleKeyPressEvent(key xproto.KeyPressEvent) error {
                     // We must manually close the X connection since we used
                     // `defer` when setting it up and os.Exit() short-circuits
                     // that deferral.
                     // We must manually close the X connection since we used
                     // `defer` when setting it up and os.Exit() short-circuits
                     // that deferral.
-                    xc.Close()
+                    //xc.Close()
                     os.Exit(0)
             }
         case XK_Return:
                     os.Exit(0)
             }
         case XK_Return: