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 ) type display struct { screen xinerama.ScreenInfo windows []xproto.Window } func main() { // Set ourselves up as the new window manager ------------------------------ xconn := connectToXServer() xroot := getXRoot(xconn) keymap := getKeyboardMap(xconn) registerForKeyEvents(xconn, xroot, keymap) becomeWM(xconn, xroot) // Build a list of output devices ------------------------------------------ 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 ------------------------------ // 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 { // TODO: Better error log.Fatal(err) } if tree != nil { for _, c := range tree.Children { 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) } } // TODO: Try tiling/displaying/whatever with any existing windows before entering the event loop. } // Main program loop reacts to X events ------------------------------------ eventloop: for { 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: fmt.Printf("ILLI: Received xproto.KeyPressEvent\n") if err := handleKeyPressEvent(keymap, e); err != nil { 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: fmt.Printf("ILLI: Unknown X event received: %s\n", xevent) } } } // 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(conn *xgb.Conn, windowID xproto.Window) bool { reply, err := xproto.GetWindowAttributes(conn, windowID).Reply() if err != nil { log.Fatal(err) } if reply != nil && reply.MapState == 2 { return true } return false } // TODO: Determine how I want to split the main event loop from the various // 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(keymap [256][]xproto.Keysym, 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 // TODO: What do I actually want to return here? }