commit 79a80fbbf278e0eb743a79e2c0c252817537434f
parent 0543020038edb5de1162863e7401ea36f8a8584c
Author: hhvn <dev@hhvn.uk>
Date: Mon, 14 Aug 2023 14:04:41 +0100
Creating and destroying the windows
Diffstat:
8 files changed, 301 insertions(+), 37 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+hbspbar
diff --git a/Makefile b/Makefile
@@ -0,0 +1,2 @@
+build:
+ go build
diff --git a/bar/bar.go b/bar/bar.go
@@ -0,0 +1,200 @@
+package bar
+
+import (
+ "fmt"
+ "container/list"
+
+ "hhvn.uk/hbspbar/common"
+ "hhvn.uk/hbspbar/bspc"
+
+ "github.com/jezek/xgb"
+ "github.com/jezek/xgb/xproto"
+
+ "github.com/jezek/xgbutil"
+ "github.com/jezek/xgbutil/icccm"
+ "github.com/jezek/xgbutil/ewmh"
+)
+
+var conf = struct {
+ H uint
+ Bg uint32
+ Fg uint32
+ Sel uint32
+ Urg uint32
+} { 16,
+ 0xff050a10,
+ 0xffeeeeee,
+ 0xff1b364b,
+ 0xff90222b }
+
+var bars *list.List
+
+type bar struct {
+ X *xgb.Conn
+ Xu *xgbutil.XUtil
+ Scr *xproto.ScreenInfo
+ Mon *bspc.Monitor
+ w xproto.Window
+}
+
+func (b *bar) Init() error {
+ var err error
+
+ b.w, err = xproto.NewWindowId(b.X)
+ if err != nil {
+ return common.Perror("xproto.NewWindowId", err)
+ }
+
+ geom := b.Mon.Rectangle
+ xproto.CreateWindow(b.X, b.Scr.RootDepth, b.w, b.Scr.Root,
+ int16(geom.X), int16(geom.Y), uint16(geom.Width), uint16(conf.H), 0,
+ xproto.WindowClassInputOutput, b.Scr.RootVisual,
+ xproto.CwBackPixel|xproto.CwEventMask, []uint32{
+ conf.Bg,
+ xproto.EventMaskStructureNotify })
+
+ class := icccm.WmClass{"hbspbar", "hbspbar"}
+ icccm.WmClassSet(b.Xu, b.w, &class)
+
+ ewmh.WmWindowTypeSet(b.Xu, b.w, []string{"_NET_WM_WINDOW_TYPE_DOCK"})
+ ewmh.WmStateSet(b.Xu, b.w, []string{"_NET_WM_STATE_STICKY", "_NET_WM_STATE_ABOVE"})
+
+ err = xproto.MapWindowChecked(b.X, b.w).Check()
+ if err != nil {
+ return common.Perror("xproto.MapWindow", err)
+ }
+
+ go b.Mon.TopPadding(conf.H)
+
+ return nil
+}
+
+func (b *bar) Destroy() {
+ b.Mon.TopPadding(0)
+}
+
+type eventwrap struct {
+ Ev xgb.Event
+ Err error
+}
+
+// External interface
+type Handle struct {
+ Create chan int
+ Destroy chan int
+ NewState chan *bspc.State
+ Err chan error
+}
+
+func Init(state *bspc.State) (*Handle, error) {
+ bars = list.New()
+
+ x, err := xgb.NewConn()
+ if err != nil { return nil, err }
+
+ xu, err := xgbutil.NewConnXgb(x)
+ if err != nil { return nil, err }
+
+ setup := xproto.Setup(x)
+ screen := setup.DefaultScreen(x)
+
+ var h Handle
+ h.Create = make(chan int)
+ h.Destroy = make(chan int)
+ h.NewState = make(chan *bspc.State)
+ h.Err = make(chan error)
+
+ events := make(chan *eventwrap)
+
+ go func() { for {
+ ev, err := x.WaitForEvent()
+ w := eventwrap{ev, err}
+ events <- &w
+
+ select {
+ case <- events: return
+ default:
+ }
+ }}()
+
+ go func() {
+ defer func() { events <- nil }()
+
+ for {
+ select {
+ case e := <- events:
+ if e.Ev == nil && e.Err == nil {
+ h.Err <- fmt.Errorf("X connection clossed")
+ return
+ }
+
+ if e.Err != nil {
+ common.Perror("x.WaitForEvents", err)
+ continue
+ }
+
+ switch e.Ev.(type) {
+ case xproto.DestroyNotifyEvent:
+ h.Err <- fmt.Errorf("Window destroyed")
+ return
+ }
+ case id := <- h.Destroy:
+ destroy(id)
+ case id := <- h.Create:
+ if err := create(x, xu, screen, state.GetMon(id)); err != nil {
+ h.Err <- fmt.Errorf("Couldn't create window: %s\n", err)
+ return
+ }
+ case state := <- h.NewState:
+ for e := bars.Front(); e != nil; e = e.Next() {
+ v := e.Value.(*bar)
+ v.Mon = state.GetMon(v.Mon.ID)
+ }
+ default:
+ }
+ }
+ }()
+
+ return &h, nil
+}
+
+func create(x *xgb.Conn, xu *xgbutil.XUtil,
+ scr *xproto.ScreenInfo, m *bspc.Monitor) (error) {
+ var b bar
+
+ b.X = x
+ b.Xu = xu
+ b.Scr = scr
+ b.Mon = m
+
+ if err := b.Init(); err != nil {
+ return err
+ }
+
+ bars.PushBack(&b)
+ return nil
+}
+
+func destroy(monid int) (bool) {
+ var next *list.Element
+
+ for e := bars.Front(); e != nil; e = next {
+ next = e.Next()
+ v := e.Value.(*bar)
+
+ /* A monid of -1 means that we're doing
+ the bar.Cleanup schermoodle today */
+ if monid == -1 || monid > 0 && v.Mon.ID == int(monid) {
+ v.Destroy()
+ bars.Remove(e)
+ if monid != -1 { return true }
+ }
+ }
+
+ if monid == -1 { return true }
+ return false
+}
+
+func Cleanup() {
+ destroy(-1)
+}
diff --git a/bspc/bspc.go b/bspc/bspc.go
@@ -1,6 +1,7 @@
package bspc
import (
+ "fmt"
"io"
"bufio"
"strings"
@@ -14,17 +15,48 @@ type State struct {
Monitors []*Monitor
}
+func (s *State) GetMon(id int) (*Monitor) {
+ for _, m := range s.Monitors {
+ if m.ID == id {
+ return m
+ }
+ }
+
+ return nil
+}
+
type Monitor struct {
ID int
Rectangle *Rect
Desktops []*Desktop
}
+func (m *Monitor) TopPadding(px uint) {
+ cmd := exec.Command("bspc", "config",
+ "-m", fmt.Sprintf("%d", m.ID),
+ "top_padding", fmt.Sprintf("%d", px))
+
+ pipe, _ := cmd.StderrPipe()
+ scan := bufio.NewScanner(pipe)
+
+ cmd.Start()
+
+ for scan.Scan() {
+ common.Error("%s\n", scan.Text())
+ }
+
+ if err := scan.Err(); err != nil {
+ common.Perror("scan.Err", err)
+ }
+
+ cmd.Wait()
+}
+
type Rect struct {
- X int
- Y int
- Width int
- Height int
+ X uint
+ Y uint
+ Width uint
+ Height uint
}
type Desktop struct {
@@ -95,25 +127,20 @@ func Subscribe() (*Subscriber, error) {
s.pipe = pipe
s.scanner = bufio.NewScanner(s.pipe)
- go func() {
- for {
- select {
- case <- s.cleanup:
- s.cmd.Wait()
- return
- default:
- if event := s.get(); event != nil {
- s.Event <- event
- }
+ go func() { for {
+ select {
+ default:
+ if event := s.get(); event != nil {
+ s.Event <- event
}
}
- }()
+ }}()
return &s, nil
}
func (s *Subscriber) Close() {
- s.cleanup <- true
+ s.cmd.Process.Kill()
}
func (s *Subscriber) getLine() (string, error) {
@@ -133,7 +160,7 @@ func (s *Subscriber) get() (*Event) {
var err error
for {
line, err = s.getLine()
- if line[0] != 'W' { break }
+ if len(line) > 0 && line[0] != 'W' { break }
}
if line == "" { return nil }
diff --git a/common/common.go b/common/common.go
@@ -12,11 +12,7 @@ func Perror(function string, err error) error {
func Error(format string, a ... any) (int, error) {
fmt.Fprintf(os.Stderr, "error: ")
- if (len(a) == 0) {
- return fmt.Fprintf(os.Stderr, format)
- } else {
- return fmt.Fprintf(os.Stderr, format, a)
- }
+ return fmt.Fprintf(os.Stderr, format, a...)
}
func Intify(s string) (int, error) {
diff --git a/go.mod b/go.mod
@@ -3,6 +3,6 @@ module hhvn.uk/hbspbar
go 1.20
require (
- github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc // indirect
- github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 // indirect
+ github.com/jezek/xgb v1.1.0 // indirect
+ github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0 // indirect
)
diff --git a/go.sum b/go.sum
@@ -1,4 +1,6 @@
-github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk=
-github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF7V5IcmiE2sMFV2q3J47BEirxbXJAdzA=
-github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k=
+github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
+github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
+github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
+github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
+github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0 h1:Pf/0BAbppEOq4azPH6fnvUX2dycAwZdGkdxFn25j44c=
+github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0/go.mod h1:AHecLyFNy6AN9f/+0AH/h1MI7X1+JL5bmCz4XlVZk7Y=
diff --git a/main.go b/main.go
@@ -1,16 +1,14 @@
package main
import (
+ "os"
"strings"
+ "syscall"
+ "os/signal"
"hhvn.uk/hbspbar/common"
"hhvn.uk/hbspbar/bspc"
"hhvn.uk/hbspbar/bar"
-
- /*
- "github.com/BurntSushi/xgb"
- "github.com/BurntSushi/xgb/xproto"
- */
)
var reloadEvents = []string{
@@ -40,7 +38,7 @@ func reloadState(current *bspc.State) (*bspc.State, error) {
}
}
-func handleEvent(events *bspc.Subscriber) (bool) {
+func handleEvent(events *bspc.Subscriber, state *bspc.State, bar *bar.Handle) (bool) {
var event *bspc.Event
select {
@@ -55,6 +53,16 @@ func handleEvent(events *bspc.Subscriber) (bool) {
return false
}
+ if strings.HasPrefix(event.Name, "monitor_") {
+ id, err := common.Intify(event.Tokens[0])
+ if err == nil {
+ switch (event.Name) {
+ case "monitor_add": bar.Create <- id
+ case "monitor_remove": bar.Destroy <- id
+ }
+ }
+ }
+
for i := 0; i < len(reloadEvents); i++ {
if event.Name == reloadEvents[i] {
return true
@@ -65,26 +73,54 @@ func handleEvent(events *bspc.Subscriber) (bool) {
}
func main() {
+ signals := make(chan os.Signal, 1)
+ signal.Notify(signals,
+ syscall.SIGINT,
+ syscall.SIGHUP,
+ syscall.SIGTERM)
+
state, err := bspc.LoadState()
- if (err != nil) {
+ if err != nil {
common.Error("Couldn't load bspwm state: %s\n", err)
return
}
events, err := bspc.Subscribe()
- if (err != nil) {
+ if err != nil {
common.Error("Couldn't subscribe to bspwm: %s\n", err)
return
}
defer events.Close()
+ barhandle, err := bar.Init(state)
+ if err != nil {
+ common.Error("Couldn't initialize X: %s\n", err)
+ return
+ }
+
+ for _, m := range state.Monitors {
+ barhandle.Create <- m.ID
+ }
+ defer bar.Cleanup()
for {
- if reload := handleEvent(events); reload {
+ if reload := handleEvent(events, state, barhandle); reload {
state, err = reloadState(state)
if err != nil {
common.Error("Couldn't reload bspwm state: %s\n", err)
}
+
+ barhandle.NewState <- state
+ }
+
+ select {
+ case s := <- signals:
+ common.Error("%s\n", s)
+ return
+ case err := <- barhandle.Err:
+ common.Error("%s\n", err)
+ return
+ default:
}
}
}