From 4539e476b6644e961a4a96e3253c615bc4673b0c Mon Sep 17 00:00:00 2001 From: crecher Date: Mon, 19 Oct 2026 17:02:47 +0200 Subject: stuff idk i dont remember when i even made these changes tbh --- main.go | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 248 insertions(+), 45 deletions(-) (limited to 'main.go') diff --git a/main.go b/main.go index f48ce61..30c514f 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,12 @@ package main import ( - "fmt" + "bufio" "log" + "os" "os/exec" + "strconv" + "time" "github.com/BurntSushi/xgb" "github.com/BurntSushi/xgb/xproto" @@ -19,7 +22,6 @@ type Key struct { Fn func(*xgb.Conn) } -// TODO rewrite tagging system type Client struct { Win xproto.Window X int16 @@ -41,8 +43,14 @@ var ( // bar var ( - barWindow xproto.Window - barGC xproto.Gcontext + barWindow xproto.Window + barGC xproto.Gcontext + fontID xproto.Font + fontOK bool + fontAscent int + fontDescent int + statusText string + statusChan chan string ) // layout @@ -61,10 +69,33 @@ func main() { } setup(conn) for { - ev, err := conn.WaitForEvent() - if err != nil { - log.Fatal(err) + dirty := false + drain: + for { + select { + case s, ok := <-statusChan: + if !ok { + break drain + } + statusText = s + dirty = true + default: + break drain + } + } + if dirty { + drawBar(conn) } + + ev, xerr := conn.PollForEvent() + if xerr != nil { + log.Fatal(xerr) + } + if ev == nil { + time.Sleep(50 * time.Millisecond) + continue + } + // event loop switch e := ev.(type) { case xproto.KeyPressEvent: @@ -73,28 +104,20 @@ func main() { manageWindow(conn, e.Window) case xproto.DestroyNotifyEvent: unmanageWindow(conn, e.Window) - // case xproto.UnmapNotifyEvent: - // unmanageWindow(conn, e.Window) + case xproto.PropertyNotifyEvent: + if e.Window == focused { + drawBar(conn) + } case xproto.EnterNotifyEvent: // change focused window event focused = e.Event setInputFocus(conn, focused) - // update bar - if barHeight != 0 { - title := getWindowTitle(conn, focused) - drawBarText(conn, fmt.Sprintf("[%s]", title)) - } + drawBar(conn) case xproto.ExposeEvent: if e.Window == barWindow { - title := getWindowTitle(conn, focused) - if title == "None" { - title = "wm" - } - if barHeight != 0 { - drawBarText(conn, fmt.Sprintf("[%s]", title)) - } + drawBar(conn) } default: - log.Println("event: ", e) + // log.Println("event: ", e) } } @@ -118,8 +141,11 @@ func setup(conn *xgb.Conn) { grabKeys(conn) if barHeight != 0 { createBar(conn) + drawBar(conn) } + startStatus() + for _, cmd := range startup { exec.Command(cmd) log.Println("Running startup command: " + cmd) @@ -136,6 +162,32 @@ func spawn(cmd string, args ...string) { } } +func quit() { + os.Exit(0) +} + +func startStatus() { + statusChan = make(chan string, 1) + go func() { + sc := bufio.NewScanner(os.Stdin) + if sc.Err != nil { + log.Println("scanner failed: ", sc.Err().Error) + } + for sc.Scan() { + text := sc.Text() + select { + case statusChan <- text: + default: + select { + case <-statusChan: + default: + } + statusChan <- text + } + } + }() +} + func killActiveWindow(conn *xgb.Conn, win xproto.Window) { if win == 0 { log.Println("no focused window to kill") @@ -283,7 +335,7 @@ func manageWindow(conn *xgb.Conn, win xproto.Window) { clients[win] = client clientList = append([]*Client{client}, clientList...) - log.Println("managing window:", win) + // log.Println("managing window:", win) // listen for events on this window xproto.ChangeWindowAttributes(conn, win, @@ -299,6 +351,7 @@ func manageWindow(conn *xgb.Conn, win xproto.Window) { xproto.MapWindow(conn, win) focused = win setInputFocus(conn, focused) + drawBar(conn) } // xproto.MapWindow(conn, win) @@ -317,7 +370,7 @@ func removeClient(win xproto.Window) { func unmanageWindow(conn *xgb.Conn, win xproto.Window) { if _, ok := clients[win]; ok { - log.Println("unmanaging window:", win) + // log.Println("unmanaging window:", win) removeClient(win) delete(clients, win) updateWindows(conn) @@ -328,12 +381,12 @@ func switchTags(conn *xgb.Conn, tags BitMask) { GlobalTags = tags // unmap everything - for _, client := range clients { xproto.UnmapWindow(conn, client.Win) } // update windows updateWindows(conn) + drawBar(conn) } func tileVertical(conn *xgb.Conn) { @@ -386,7 +439,7 @@ func tileHorizontal(conn *xgb.Conn) { for _, c := range ActiveClients { xOffset := int(i * widthPerWin) width := widthPerWin - gapSize - log.Println(i, len(ActiveClients)) + // log.Println(i, len(ActiveClients)) if i == len(ActiveClients)-1 { width = widthPerWin - gapSize*2 } @@ -455,7 +508,7 @@ func tileWithMaster(conn *xgb.Conn) { yOffset := int(barHeight) + (i * heightPerWin) + gapSize xOffset := uint32(widthPerWin + gapSize) height := heightPerWin - gapSize*2 - log.Println(len(ActiveClients), i) + // log.Println(len(ActiveClients), i) if n > 2 { height = heightPerWin - gapSize } @@ -507,21 +560,90 @@ func createBar(conn *xgb.Conn) { // map bar window xproto.MapWindow(conn, barWindow) + if err := xftInit(fontName); err != nil { + log.Println("xft unavailable, falling back to core font:", err) + } else { + fontAscent, fontDescent = xftMetrics() + fontOK = true + if h := fontAscent + fontDescent; h > 0 && uint16(h) != barHeight { + barHeight = uint16(h) + xproto.ConfigureWindow(conn, barWindow, + xproto.ConfigWindowHeight, []uint32{uint32(barHeight)}) + } + } + + // fallback + if !xftOK { + for _, name := range []string{"fixed"} { + if f, ok := openFont(conn, name); ok { + fontID = f + fontOK = true + break + } + } + } + // create bar gc + masks := uint32(xproto.GcForeground | xproto.GcBackground) + vals := []uint32{barFg, barColor} + if !xftOK && fontOK { + masks |= xproto.GcFont + vals = append(vals, uint32(fontID)) + if r, err := xproto.QueryTextExtents(conn, xproto.Fontable(fontID), asciiChars("M"), 1).Reply(); err == nil && r != nil { + fontAscent = int(r.OverallAscent) + fontDescent = int(r.OverallDescent) + } + } barGC, _ = xproto.NewGcontextId(conn) - xproto.CreateGC(conn, barGC, xproto.Drawable(barWindow), xproto.GcForeground|xproto.GcBackground, []uint32{0xFFFFFFFF, 0xFFFFFFFF}) + xproto.CreateGC(conn, barGC, xproto.Drawable(barWindow), masks, vals) +} + +func openFont(conn *xgb.Conn, name string) (xproto.Font, bool) { + fid, err := xproto.NewFontId(conn) + if err != nil { + return 0, false + } + if err := xproto.OpenFontChecked(conn, fid, uint16(len(name)), name).Check(); err != nil { + return 0, false + } + return fid, true } -func drawBarText(conn *xgb.Conn, text string) { +func textWidth(conn *xgb.Conn, text string) int { if text == "" { - text = " " + return 0 + } + if xftOK { + return xftTextWidth(text) } + if fontOK { + if r, err := xproto.QueryTextExtents(conn, xproto.Fontable(fontID), asciiChars(text), uint16(len(text))).Reply(); err == nil && r != nil { + return int(r.OverallWidth) + } + } + return len(text) * 6 +} - screen := xproto.Setup(conn).DefaultScreen(conn) - xproto.ClearArea(conn, false, barWindow, 0, 0, screen.WidthInPixels, barHeight) - screenWidth := int(screen.WidthInPixels) +func asciiChars(text string) []xproto.Char2b { + chars := make([]xproto.Char2b, len(text)) + for i := 0; i < len(text); i++ { + chars[i] = xproto.Char2b{Byte1: 0, Byte2: text[i]} + } + return chars +} + +func drawBarText(conn *xgb.Conn, x, top, height int, text string, fg, bg uint32) { + if text == "" { + return + } + + baseline := top + (height+fontAscent-fontDescent)/2 + + if xftOK { + xftDrawText(barWindow, x, baseline, text, fg) + return + } - // 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 { @@ -529,46 +651,127 @@ func drawBarText(conn *xgb.Conn, text string) { } } if len(ascii) == 0 { - ascii = []byte(" ") + return } 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, int16(screenWidth/2-len(ascii)*2), int16(barHeight-5), textItem).Check() - if err != nil { - log.Println("polytext failed again :D err:", err) + xproto.ChangeGC(conn, barGC, xproto.GcForeground|xproto.GcBackground, []uint32{fg, bg}) + xproto.PolyText8(conn, xproto.Drawable(barWindow), barGC, int16(x), int16(baseline), textItem) +} + +func drawBar(conn *xgb.Conn) { + if barWindow == 0 || barHeight == 0 { + return + } + screen := xproto.Setup(conn).DefaultScreen(conn) + barWidth := int(screen.WidthInPixels) + + xproto.ClearArea(conn, false, barWindow, 0, 0, screen.WidthInPixels, barHeight) + + stw := 0 + if statusText != "" { + stw = textWidth(conn, statusText) + 2 // 2px right padding like dwl + drawBarText(conn, barWidth-stw, 0, int(barHeight), statusText, barFg, barColor) + } + + var occ, focc BitMask + if c, ok := clients[focused]; ok { + focc = c.Tags + } + for _, c := range clients { + occ |= c.Tags + } + + boxs := int(barHeight) / 9 + boxw := int(barHeight)/6 + 2 + + x := 0 + for i := 0; i < tagCount; i++ { + var mask BitMask = 1 << uint(i) + digit := strconv.Itoa(i + 1) + w := textWidth(conn, digit) + int(barHeight) + sel := (GlobalTags & mask) != 0 + fg, bg := barFg, barColor + if sel { + fg, bg = selFg, selBg + rect := xproto.Rectangle{X: int16(x), Y: 0, Width: uint16(w), Height: barHeight} + xproto.ChangeGC(conn, barGC, xproto.GcForeground|xproto.GcBackground, []uint32{selBg, selBg}) + xproto.PolyFillRectangle(conn, xproto.Drawable(barWindow), barGC, []xproto.Rectangle{rect}) + } + drawBarText(conn, x+int(barHeight)/2, 0, int(barHeight), digit, fg, bg) + if occ&mask != 0 { + rect := xproto.Rectangle{X: int16(x + boxs), Y: int16(boxs), Width: uint16(boxw), Height: uint16(boxw)} + if focc&mask != 0 { + xproto.ChangeGC(conn, barGC, xproto.GcForeground|xproto.GcBackground, []uint32{fg, fg}) + xproto.PolyFillRectangle(conn, xproto.Drawable(barWindow), barGC, []xproto.Rectangle{rect}) + } else { + xproto.ChangeGC(conn, barGC, xproto.GcForeground|xproto.GcBackground, []uint32{fg, bg}) + xproto.PolyRectangle(conn, xproto.Drawable(barWindow), barGC, []xproto.Rectangle{rect}) + } + } + x += w + } + + if windowTitle != 0 { + mw := barWidth - stw - x + if mw > int(barHeight) { + title := getWindowTitle(conn, focused) + if title == "None" || title == "Unknown" { + title = "wm" + } + fg, bg := barFg, barColor + if _, ok := clients[focused]; ok { + fg, bg = selFg, selBg + } + drawBarText(conn, x, 0, int(barHeight), title, fg, bg) + } } } func getWindowTitle(conn *xgb.Conn, win xproto.Window) string { - if focused == 0 { + if win == 0 { return "None" } - prop, err := xproto.GetProperty(conn, false, focused, xproto.AtomWmName, xproto.AtomString, 0, 100).Reply() + netWmName, err := xproto.InternAtom(conn, true, uint16(len("_NET_WM_NAME")), "_NET_WM_NAME").Reply() + if err == nil && netWmName != nil && netWmName.Atom != 0 { + prop, err := xproto.GetProperty(conn, false, win, netWmName.Atom, xproto.AtomAny, 0, 100).Reply() + if err == nil && prop != nil && len(prop.Value) > 0 && prop.Value[0] != 0 { + return trimTitle(prop.Value) + } + } + + prop, err := xproto.GetProperty(conn, false, win, xproto.AtomWmName, xproto.AtomAny, 0, 100).Reply() if err != nil { log.Println("failed to get window title:", err) return "Unknown" } - if prop == nil || len(prop.Value) == 0 { + if prop == nil || len(prop.Value) == 0 || prop.Value[0] == 0 { return "Unknown" } - return string(prop.Value) + return trimTitle(prop.Value) +} + +func trimTitle(value []byte) string { + for len(value) > 0 && value[len(value)-1] == 0 { + value = value[:len(value)-1] + } + return string(value) } func updateWindows(conn *xgb.Conn) { ActiveClients = ActiveClients[:0] for _, client := range clientList { - log.Println(client.Tags & GlobalTags) + // log.Println(client.Tags & GlobalTags) if (client.Tags & GlobalTags) >= 1 { xproto.MapWindow(conn, client.Win) ActiveClients = append(ActiveClients, client) - log.Println("Mapping window", client.Win) + // log.Println("Mapping window", client.Win) } } -- cgit v1.2.3