diff options
Diffstat (limited to 'main.go')
| -rw-r--r-- | main.go | 290 |
1 files changed, 236 insertions, 54 deletions
@@ -2,8 +2,8 @@ package main import ( "log" - "os" "os/exec" + "fmt" "github.com/BurntSushi/xgb" "github.com/BurntSushi/xgb/xproto" @@ -30,6 +30,13 @@ type Client struct { var keys []Key var clients = make(map[xproto.Window]*Client) +var focused xproto.Window + +// bar +var barWindow xproto.Window +var barGC xproto.Gcontext +var barHeight uint16 = 20 // bar height in pixels + func main() { conn, err := xgb.NewConn() @@ -40,25 +47,35 @@ func main() { setup(conn) for { - log.Println("test") ev, err := conn.WaitForEvent() if err != nil { log.Fatal(err) } - + // event loop switch e := ev.(type) { case xproto.KeyPressEvent: handleKeyPress(conn, e) - case xproto.MapRequestEvent: manageWindow(conn, e.Window) - case xproto.DestroyNotifyEvent: unmanageWindow(conn, e.Window) - case xproto.UnmapNotifyEvent: unmanageWindow(conn, e.Window) - + case xproto.EnterNotifyEvent: + focused = e.Event + setInputFocus(conn, focused) + // update bar + title := getWindowTitle(conn, focused) + drawBarText(conn, fmt.Sprintf("[%s]", title)) + case xproto.ExposeEvent: + if e.Window == barWindow { + title := getWindowTitle(conn, focused) + if title == "None" { + title = "wm" + } + drawBarText(conn, fmt.Sprintf("[%s]", title)) + + } default: log.Println("event: ", e) } @@ -67,6 +84,7 @@ func main() { } func setup(conn *xgb.Conn) { + log.Println("setup") root := xproto.Setup(conn).DefaultScreen(conn).Root err := xproto.ChangeWindowAttributesChecked( @@ -84,6 +102,7 @@ func setup(conn *xgb.Conn) { } setupKeys(conn) + createBar(conn) } func spawn(cmd string, args ...string) { @@ -96,22 +115,75 @@ func spawn(cmd string, args ...string) { } } +func killActiveWindow(conn *xgb.Conn, win xproto.Window) { + if win == 0 { + log.Println("no focused window to kill") + return + } + + // try to use WM_DELETE_WINDOW to close + wmProtocols, err := xproto.InternAtom(conn, true, + uint16(len("WM_PROTOCOLS")), "WM_PROTOCOLS").Reply() + if err != nil { + log.Println("failed to get WM_PROTOCOLS atom", err) + return + } + + wmDelete, err := xproto.InternAtom(conn, true, + uint16(len("WM_DELETE_WINDOW")), "WM_DELETE_WINDOW").Reply() + if err != nil { + log.Println("failed to get WM_DELETE_WINDOW atom", err) + return + } + + data := xproto.ClientMessageDataUnionData32New([]uint32{ + uint32(wmDelete.Atom), uint32(xproto.TimeCurrentTime), 0, 0, 0, + }) + + ev := xproto.ClientMessageEvent{ + Format: 32, + Window: win, + Type: wmProtocols.Atom, + Data: data, + } + + // send event + err = xproto.SendEventChecked(conn, false, win, xproto.EventMaskNoEvent, string(ev.Bytes())).Check() + if err != nil { + // force close if WM_DELETE_WINDOW doesnt work + log.Println("force closing:", err) + xproto.DestroyWindow(conn, win) + } + + delete(clients, win) + focused = 0 + tile(conn) +} + func setupKeys(conn *xgb.Conn) { - mod := uint16(xproto.ModMask1) // Alt + mod := uint16(xproto.ModMask1) + // keybinds keys = []Key{ { Mod: mod, Sym: keysym.XK_Return, - Fn: func(c *xgb.Conn) { + Fn: func(conn *xgb.Conn) { spawn("st") }, }, { Mod: mod, - Sym: keysym.XK_Q, - Fn: func(c *xgb.Conn) { - os.Exit(0) + Sym: keysym.XK_q, + Fn: func(conn *xgb.Conn) { + killActiveWindow(conn, focused) + }, + }, + { + Mod: mod, + Sym: keysym.XK_d, + Fn: func(conn *xgb.Conn) { + spawn("dmenu_launch") }, }, } @@ -120,23 +192,32 @@ func setupKeys(conn *xgb.Conn) { } func grabKeys(conn *xgb.Conn) { - root := xproto.Setup(conn).DefaultScreen(conn).Root - - for _, k := range keys { - keycode := keysymToKeycode(conn, k.Sym) - if keycode == 0 { - continue - } - - xproto.GrabKey(conn, - true, - root, - k.Mod, - keycode, - xproto.GrabModeAsync, - xproto.GrabModeAsync, - ) - } + root := xproto.Setup(conn).DefaultScreen(conn).Root + + for _, k := range keys { + keycode := keysymToKeycode(conn, k.Sym) + if keycode == 0 { + continue + } + + ignoredMods := []uint16{ + 0, + xproto.ModMaskLock, + xproto.ModMask2, + xproto.ModMaskLock | xproto.ModMask2, + } + + for _, m := range ignoredMods { + xproto.GrabKey(conn, + true, + root, + k.Mod|m, + keycode, + xproto.GrabModeAsync, + xproto.GrabModeAsync, + ) + } + } } func keysymToKeycode(conn *xgb.Conn, sym uint32) xproto.Keycode { @@ -164,23 +245,36 @@ func keysymToKeycode(conn *xgb.Conn, sym uint32) xproto.Keycode { } func handleKeyPress(conn *xgb.Conn, e xproto.KeyPressEvent) { - log.Println("key pressed") reply, err := xproto.GetKeyboardMapping(conn, e.Detail, 1).Reply() if err != nil || len(reply.Keysyms) == 0 { return } - + log.Println(reply) sym := reply.Keysyms[0] for _, k := range keys { - if uint32(sym) == k.Sym && (e.State&k.Mod) == k.Mod { - k.Fn(conn) + if uint32(sym) != k.Sym { + continue + } + + ignoredMods := uint16(xproto.ModMaskLock) | uint16(xproto.ModMask2) + modifiers := e.State &^ ignoredMods + + if modifiers == k.Mod { + k.Fn(conn) } } } +func setInputFocus(conn *xgb.Conn, win xproto.Window) { + if win == 0 { + return + } + xproto.SetInputFocus(conn, xproto.InputFocusPointerRoot, win, xproto.TimeCurrentTime) +} + func manageWindow(conn *xgb.Conn, win xproto.Window) { geom, err := xproto.GetGeometry(conn, xproto.Drawable(win)).Reply() if err != nil { @@ -211,6 +305,9 @@ func manageWindow(conn *xgb.Conn, win xproto.Window) { // map (show) window xproto.MapWindow(conn, win) + focused = win + setInputFocus(conn, focused) + // tile all windows tile(conn) } @@ -224,32 +321,117 @@ func unmanageWindow(conn *xgb.Conn, win xproto.Window) { } func tile(conn *xgb.Conn) { - log.Println("tile") + log.Println("tile") + screen := xproto.Setup(conn).DefaultScreen(conn) + screenWidth := int(screen.WidthInPixels) + + usableHeight := int(screen.HeightInPixels) - int(barHeight) + + n := len(clients) + if n == 0 { + return + } + + heightPerWin := usableHeight / n + + i := 0 + for _, c := range clients { + yOffset := int(barHeight) + (i * heightPerWin) + + xproto.ConfigureWindow(conn, c.Win, + xproto.ConfigWindowX| + xproto.ConfigWindowY| + xproto.ConfigWindowWidth| + xproto.ConfigWindowHeight, + []uint32{ + 0, // x + uint32(yOffset), // y + uint32(screenWidth), + uint32(heightPerWin), + }) + + i++ + } +} + +func createBar(conn *xgb.Conn) { screen := xproto.Setup(conn).DefaultScreen(conn) - screenWidth := int(screen.WidthInPixels) - screenHeight := int(screen.HeightInPixels) + root := screen.Root - n := len(clients) - if n == 0 { - return + // create the window + cw := []uint32{ + xproto.EventMaskExposure, // redraw on expose + uint32(0xFFFFFF), // white background } - i := 0 - for _, c := range clients { - height := screenHeight / n + barWindow, _ = xproto.NewWindowId(conn) + xproto.CreateWindow( + conn, + screen.RootDepth, + barWindow, + root, + 0, 0, // x, y + screen.WidthInPixels, barHeight, + 0, // border + xproto.WindowClassInputOutput, + screen.RootVisual, + xproto.CwBackPixel|xproto.CwEventMask, + cw, + ) + + // map bar window + xproto.MapWindow(conn, barWindow) + + // create bar gc + barGC, _ = xproto.NewGcontextId(conn) + xproto.CreateGC(conn, barGC, xproto.Drawable(barWindow), xproto.GcForeground|xproto.GcBackground, []uint32{0x000000, 0xFFFFFF}) +} + +func drawBarText(conn *xgb.Conn, text string) { + if text == "" { + text = " " + } - xproto.ConfigureWindow(conn, c.Win, - xproto.ConfigWindowX| - xproto.ConfigWindowY| - xproto.ConfigWindowWidth| - xproto.ConfigWindowHeight, - []uint32{ - 0, - uint32(i * height), - uint32(screenWidth), - uint32(height), - }) + screen := xproto.Setup(conn).DefaultScreen(conn) + xproto.ClearArea(conn, false, barWindow, 0, 0, screen.WidthInPixels, barHeight) - i++ + // ascii only because FUCK POLYTEXT8 + ascii := make([]byte, 0, len(text)) + for i := 0; i < len(text); i++ { + if text[i] >= 32 && text[i] <= 126 { + ascii = append(ascii, text[i]) + } } + if len(ascii) == 0 { + ascii = []byte(" ") + } + + textItem := make([]byte, len(ascii)+2) + textItem[0] = byte(len(ascii)) // string len + textItem[1] = 0 // offset + copy(textItem[2:], ascii) + + err := xproto.PolyText8Checked(conn, xproto.Drawable(barWindow), barGC, 10, int16(barHeight-5), textItem).Check() + if err != nil { + log.Println("polytext failed again :D err:", err) + } } + +func getWindowTitle(conn *xgb.Conn, win xproto.Window) string { + if focused == 0 { + return "None" + } + + prop, err := xproto.GetProperty(conn, false, focused, xproto.AtomWmName, xproto.AtomString, 0, 100).Reply() + + if err != nil { + log.Println("failed to get window title:", err) + return "Unknown" + } + + if prop == nil || len(prop.Value) == 0 { + return "Unnamed" + } + + return string(prop.Value) +}
\ No newline at end of file |