From 5f2faec53cd88d723d8e7572e2f2a73949d6025a Mon Sep 17 00:00:00 2001 From: crecher Date: Wed, 21 Oct 2026 22:16:49 +0000 Subject: fix: crash on startup, feat: hot reload Signed-off-by: crecher --- config.go | 22 ++++++++++--- main.go | 69 +++++++++++++++++---------------------- restart.go | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 43 deletions(-) create mode 100644 restart.go diff --git a/config.go b/config.go index 5f8c4e2..67ef920 100644 --- a/config.go +++ b/config.go @@ -10,9 +10,9 @@ import ( // appearance var barHeight uint16 = 20 var gapSize int = 10 -var barColor uint32 = 0x222222 -var barFg uint32 = 0xBBBBBB -var selBg uint32 = 0x005577 +var barColor uint32 = 0x000000 +var barFg uint32 = 0x888888 +var selBg uint32 = 0x285577 var selFg uint32 = 0xEEEEEE var fontName = "JetBrainsMono Nerd Font:size=16" var tagCount = 9 @@ -30,7 +30,14 @@ var keys = []Key{ Mod: mod, Sym: keysym.XK_Return, Fn: func(conn *xgb.Conn) { - spawn("kitty") + spawn("st") + }, + }, + { + Mod: mod, + Sym: keysym.XK_w, + Fn: func(conn *xgb.Conn) { + spawn("qutebrowser") }, }, { @@ -40,6 +47,13 @@ var keys = []Key{ quit() }, }, + { + Mod: mod | xproto.ModMaskShift, + Sym: keysym.XK_r, + Fn: func(conn *xgb.Conn) { + restart() + }, + }, { Mod: mod, Sym: keysym.XK_q, diff --git a/main.go b/main.go index 30c514f..f0f954c 100644 --- a/main.go +++ b/main.go @@ -146,6 +146,10 @@ func setup(conn *xgb.Conn) { startStatus() + if restoreState(conn) { + return + } + for _, cmd := range startup { exec.Command(cmd) log.Println("Running startup command: " + cmd) @@ -170,9 +174,11 @@ 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) - } + defer func() { + if err := sc.Err(); err != nil { + log.Println("scanner failed:", err) + } + }() for sc.Scan() { text := sc.Text() select { @@ -404,7 +410,7 @@ func tileVertical(conn *xgb.Conn) { i := 0 for _, c := range ActiveClients { - yOffset := int(barHeight) + (i * heightPerWin) + yOffset := i * heightPerWin height := heightPerWin - gapSize if i == len(ActiveClients)-1 { height = heightPerWin - gapSize*2 @@ -449,8 +455,8 @@ func tileHorizontal(conn *xgb.Conn) { xproto.ConfigWindowWidth| xproto.ConfigWindowHeight, []uint32{ - uint32(xOffset + gapSize), // x - uint32(int(barHeight) + gapSize), // y + uint32(xOffset + gapSize), // x + uint32(gapSize), // y uint32(width), uint32(screenHeight - int(barHeight) - gapSize*2), }) @@ -485,9 +491,9 @@ func tileWithMaster(conn *xgb.Conn) { xproto.ConfigWindowWidth| xproto.ConfigWindowHeight, []uint32{ - uint32(0), // x - uint32(int(barHeight)), // y - uint32(screenWidth), // width + uint32(0), // x + uint32(0), // y + uint32(screenWidth), // width uint32(screenHeight - int(barHeight)), }) } else { @@ -498,14 +504,14 @@ func tileWithMaster(conn *xgb.Conn) { xproto.ConfigWindowHeight, []uint32{ uint32(0 + gapSize), // x - uint32(int(barHeight) + gapSize), // y + uint32(gapSize), // y uint32(screenWidth/2 - int(gapSize)), // width uint32(screenHeight - int(barHeight) - gapSize*2), }) } } else { heightPerWin := usableHeight / (n - 1) - yOffset := int(barHeight) + (i * heightPerWin) + gapSize + yOffset := (i * heightPerWin) + gapSize xOffset := uint32(widthPerWin + gapSize) height := heightPerWin - gapSize*2 // log.Println(len(ActiveClients), i) @@ -548,7 +554,7 @@ func createBar(conn *xgb.Conn) { screen.RootDepth, barWindow, root, - 0, 0, // x, y + 0, int16(int(screen.HeightInPixels)-int(barHeight)), // x, y screen.WidthInPixels, barHeight, 0, // border xproto.WindowClassInputOutput, @@ -568,7 +574,8 @@ func createBar(conn *xgb.Conn) { if h := fontAscent + fontDescent; h > 0 && uint16(h) != barHeight { barHeight = uint16(h) xproto.ConfigureWindow(conn, barWindow, - xproto.ConfigWindowHeight, []uint32{uint32(barHeight)}) + xproto.ConfigWindowY|xproto.ConfigWindowHeight, + []uint32{uint32(int(screen.HeightInPixels) - int(barHeight)), uint32(barHeight)}) } } @@ -677,42 +684,26 @@ func drawBar(conn *xgb.Conn) { drawBarText(conn, barWidth-stw, 0, int(barHeight), statusText, barFg, barColor) } - var occ, focc BitMask - if c, ok := clients[focused]; ok { - focc = c.Tags - } + var occ BitMask 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}) + if !sel && occ&mask == 0 { + continue } - 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}) - } + label := strconv.Itoa(i + 1) + fg := barFg + if sel { + label += "*" + fg = selFg } - x += w + drawBarText(conn, x+int(barHeight)/3, 0, int(barHeight), label, fg, barColor) + x += textWidth(conn, label) + int(barHeight)*2/3 } if windowTitle != 0 { diff --git a/restart.go b/restart.go new file mode 100644 index 0000000..87c6c41 --- /dev/null +++ b/restart.go @@ -0,0 +1,108 @@ +package main + +import ( + "encoding/json" + "log" + "os" + "strings" + "syscall" + + "github.com/BurntSushi/xgb" + "github.com/BurntSushi/xgb/xproto" +) + +const stateEnv = "WM_STATE" + +type savedClient struct { + Win xproto.Window + Floating bool + Tags BitMask +} + +type savedState struct { + Clients []savedClient + GlobalTags BitMask + Layout string + Focused xproto.Window +} + +func restart() { + st := savedState{GlobalTags: GlobalTags, Layout: layout, Focused: focused} + for _, c := range clientList { + st.Clients = append(st.Clients, savedClient{c.Win, c.Floating, c.Tags}) + } + data, err := json.Marshal(st) + if err != nil { + log.Println("restart: failed to save state:", err) + return + } + + exe, err := os.Executable() + if err != nil { + exe = os.Args[0] + } + exe = strings.TrimSuffix(exe, " (deleted)") + + env := append(os.Environ(), stateEnv+"="+string(data)) + log.Println("restarting", exe) + if err := syscall.Exec(exe, os.Args, env); err != nil { + log.Println("restart: exec failed:", err) + } +} + +func restoreState(conn *xgb.Conn) bool { + raw, ok := os.LookupEnv(stateEnv) + if !ok { + return false + } + os.Unsetenv(stateEnv) + + var st savedState + if err := json.Unmarshal([]byte(raw), &st); err != nil { + log.Println("restore: bad state:", err) + return false + } + + GlobalTags = st.GlobalTags + if st.Layout != "" { + layout = st.Layout + } + + for _, sc := range st.Clients { + geom, err := xproto.GetGeometry(conn, xproto.Drawable(sc.Win)).Reply() + if err != nil { + continue + } + client := &Client{ + Win: sc.Win, + X: geom.X, + Y: geom.Y, + Width: geom.Width, + Height: geom.Height, + Floating: sc.Floating, + Tags: sc.Tags, + } + clients[sc.Win] = client + clientList = append(clientList, client) + + xproto.ChangeWindowAttributes(conn, sc.Win, + xproto.CwEventMask, + []uint32{ + xproto.EventMaskEnterWindow | + xproto.EventMaskFocusChange | + xproto.EventMaskPropertyChange, + }) + } + + if _, ok := clients[st.Focused]; ok { + focused = st.Focused + setInputFocus(conn, focused) + } + + for _, c := range clients { + xproto.UnmapWindow(conn, c.Win) + } + updateWindows(conn) + drawBar(conn) + return true +} -- cgit v1.2.3