aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrecher <crecher@crecher.net>2026-10-19 17:02:47 +0200
committercrecher <crecher@crecher.net>2026-10-19 17:02:47 +0200
commit4539e476b6644e961a4a96e3253c615bc4673b0c (patch)
treeea39d68fa6f5c5cb643e95ce78bdfbcc53812615
parent1c631b6c718e572cb4a38b4aa0f30e198818a67a (diff)
downloadwm-4539e476b6644e961a4a96e3253c615bc4673b0c.tar.gz
wm-4539e476b6644e961a4a96e3253c615bc4673b0c.zip
stuff idk i dont remember when i even made these changes tbh
-rw-r--r--config.go16
-rw-r--r--main.go293
-rwxr-xr-xstart.sh4
-rw-r--r--xft.go131
4 files changed, 392 insertions, 52 deletions
diff --git a/config.go b/config.go
index 1f70409..5f8c4e2 100644
--- a/config.go
+++ b/config.go
@@ -1,8 +1,6 @@
package main
import (
- "os"
-
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/xproto"
@@ -12,7 +10,15 @@ import (
// appearance
var barHeight uint16 = 20
var gapSize int = 10
-var barColor uint32 = 0x000000
+var barColor uint32 = 0x222222
+var barFg uint32 = 0xBBBBBB
+var selBg uint32 = 0x005577
+var selFg uint32 = 0xEEEEEE
+var fontName = "JetBrainsMono Nerd Font:size=16"
+var tagCount = 9
+
+// shows focused window title
+var windowTitle = 0
var mod = uint16(xproto.ModMask1)
@@ -24,14 +30,14 @@ var keys = []Key{
Mod: mod,
Sym: keysym.XK_Return,
Fn: func(conn *xgb.Conn) {
- spawn("st")
+ spawn("kitty")
},
},
{
Mod: mod | xproto.ModMaskShift,
Sym: keysym.XK_q,
Fn: func(conn *xgb.Conn) {
- os.Exit(0)
+ quit()
},
},
{
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)
}
}
diff --git a/start.sh b/start.sh
index 368faf0..418927a 100755
--- a/start.sh
+++ b/start.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-Xephyr -screen 1280x800 :2 &
+Xephyr -screen 1280x800 :3 &
go build
-DISPLAY=:2 ./wm
+date | DISPLAY=:3 ./wm
pkill Xephyr
diff --git a/xft.go b/xft.go
new file mode 100644
index 0000000..73ab52b
--- /dev/null
+++ b/xft.go
@@ -0,0 +1,131 @@
+package main
+
+/*
+#cgo pkg-config: xft xrender x11 fontconfig
+#include <stdlib.h>
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrender.h>
+#include <X11/Xft/Xft.h>
+
+static Display *_dpy;
+static XftDraw *_drw;
+static XftFont *_ft;
+static Visual *_vis;
+static Colormap _cmap;
+static XftColor _fg;
+static int _scr;
+
+static int wm_xft_init(const char *pattern)
+{
+ int event_basep, error_basep;
+ _dpy = XOpenDisplay(NULL);
+ if (!_dpy)
+ return -1;
+ if (!XRenderQueryExtension(_dpy, &event_basep, &error_basep))
+ return -3;
+ XftInit(NULL);
+ _scr = DefaultScreen(_dpy);
+ _vis = DefaultVisual(_dpy, _scr);
+ _cmap = DefaultColormap(_dpy, _scr);
+ _ft = XftFontOpenName(_dpy, _scr, pattern);
+ if (!_ft)
+ return -2;
+ return 0;
+}
+
+static void wm_xft_setfg(unsigned int px)
+{
+ XRenderColor c;
+ c.red = ((px >> 16) & 0xff) << 8;
+ c.green = ((px >> 8) & 0xff) << 8;
+ c.blue = (px & 0xff) << 8;
+ c.alpha = 0xffff;
+ XftColorAllocValue(_dpy, _vis, _cmap, &c, &_fg);
+}
+
+static int wm_xft_width(const char *s, int n)
+{
+ XGlyphInfo gi;
+ XftTextExtentsUtf8(_dpy, _ft, (const FcChar8 *)s, n, &gi);
+ return gi.xOff;
+}
+
+static void wm_xft_draw(unsigned int win, int x, int y,
+ const char *s, int n, unsigned int fg)
+{
+ if (!_dpy || !_ft || !win)
+ return;
+ if (!_drw)
+ _drw = XftDrawCreate(_dpy, (Drawable)win, _vis, _cmap);
+ if (!_drw)
+ return;
+ XftDrawChange(_drw, (Drawable)win);
+ wm_xft_setfg(fg);
+ XftDrawStringUtf8(_drw, &_fg, _ft, x, y, (const FcChar8 *)s, n);
+ XFlush(_dpy);
+}
+
+static int wm_xft_ascent(void) { return _ft ? _ft->ascent : 0; }
+static int wm_xft_descent(void) { return _ft ? _ft->descent : 0; }
+*/
+import "C"
+
+import (
+ "unsafe"
+
+ "github.com/BurntSushi/xgb/xproto"
+)
+
+var xftOK bool
+
+func xftInit(pattern string) error {
+ cp := C.CString(pattern)
+ defer C.free(unsafe.Pointer(cp))
+
+ switch rc := int(C.wm_xft_init(cp)); rc {
+ case 0:
+ xftOK = true
+ return nil
+ case -1:
+ return errXftNoDisplay
+ case -2:
+ return errXftNoFont
+ default:
+ return errXftNoRender
+ }
+}
+
+var (
+ errXftNoDisplay = errXft("xft: cannot open display")
+ errXftNoFont = errXft("xft: cannot load font pattern")
+ errXftNoRender = errXft("xft: no RENDER extension")
+)
+
+type errXft string
+
+func (e errXft) Error() string { return string(e) }
+
+func xftTextWidth(s string) int {
+ if !xftOK {
+ return 0
+ }
+ cs := C.CString(s)
+ defer C.free(unsafe.Pointer(cs))
+ return int(C.wm_xft_width(cs, C.int(len(s))))
+}
+
+func xftDrawText(win xproto.Window, x, baseline int, s string, fg uint32) {
+ if !xftOK || win == 0 {
+ return
+ }
+ cs := C.CString(s)
+ defer C.free(unsafe.Pointer(cs))
+ C.wm_xft_draw(C.uint(win), C.int(x), C.int(baseline), cs, C.int(len(s)), C.uint(fg))
+}
+
+func xftMetrics() (ascent, descent int) {
+ if !xftOK {
+ return 0, 0
+ }
+ return int(C.wm_xft_ascent()), int(C.wm_xft_descent())
+}