aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go293
1 files changed, 248 insertions, 45 deletions
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)
}
}