diff --git a/cmd/bspwm/main.go b/cmd/bspwm/main.go index fa77b00..4068ef5 100644 --- a/cmd/bspwm/main.go +++ b/cmd/bspwm/main.go @@ -10,6 +10,7 @@ import ( "syscall" "github.com/jezek/xgbutil" + "github.com/jezek/xgbutil/xevent" "tuxpa.in/a/zlog/log" "tuxpa.in/t/wm/src/bsp" "tuxpa.in/t/wm/src/handler" @@ -102,14 +103,19 @@ func _main() (code int, err error) { return d }) + beforeCh, afterCh, quitCh := xevent.MainPing(xwm.X) // message listen loop for { select { + case <-beforeCh: + <-afterCh case m := <-ln.Msg(): h.Run(m) case cint := <-codeCh: stop() return cint, nil + case <-quitCh: + stop() case <-ctx.Done(): return 0, nil } diff --git a/example.sh b/example.sh new file mode 100755 index 0000000..06506fb --- /dev/null +++ b/example.sh @@ -0,0 +1,5 @@ +#!/bin/sh + + + + diff --git a/src/bsp/client.go b/src/bsp/client.go new file mode 100644 index 0000000..5761b6d --- /dev/null +++ b/src/bsp/client.go @@ -0,0 +1,77 @@ +package bsp + +import ( + "github.com/jezek/xgb/xproto" + "github.com/jezek/xgbutil/ewmh" + "github.com/jezek/xgbutil/icccm" + "github.com/jezek/xgbutil/xwindow" + "tuxpa.in/a/zlog/log" +) + +type Client struct { + win *xwindow.Window + winId xproto.Window + + name string +} + +// registers a client to be obtained later +func (xwm *XWM) RegisterClient(wid xproto.Window) *Client { + c := &Client{ + winId: wid, + } + xwm.X.Grab() + defer xwm.X.Ungrab() + // dont duplicate registration + if xwm.FindClient(wid) != nil { + log.Trace().Any("id", wid).Msg("duplicate client registration") + return nil + } + // ok this is a new one, so we need to make a new client + c.win = xwindow.New(xwm.X, wid) + if _, err := c.win.Geometry(); err != nil { + log.Err(err).Any("id", wid).Msg("get geometry") + return nil + } + err := xproto.ChangeSaveSetChecked(xwm.X.Conn(), xproto.SetModeInsert, c.winId).Check() + if err != nil { + log.Err(err).Any("id", wid).Msg("change save set checked") + return nil + } + xwm.initClient(c) + return c +} + +func (xwm *XWM) initClient(c *Client) { + xwm.updateName(c) +} +func (xwm *XWM) updateName(c *Client) { + newName := func() (n string) { + n, _ = ewmh.WmNameGet(xwm.X, c.winId) + if len(n) > 0 { + return + } + + n, _ = icccm.WmNameGet(xwm.X, c.winId) + if len(n) > 0 { + return + } + return + }() + + if newName != c.name { + c.name = newName + ewmh.WmVisibleNameSet(xwm.X, c.winId, c.name) + } +} + +func (xwm *XWM) FindClient(wid xproto.Window) (c *Client) { + xwm.W.View(func() error { + val, ok := xwm.W.Clients[wid] + if ok { + c = val + } + return nil + }) + return +} diff --git a/src/bsp/config.go b/src/bsp/config.go index 929e5c2..467dd3d 100644 --- a/src/bsp/config.go +++ b/src/bsp/config.go @@ -83,8 +83,8 @@ type Config struct { // gap WindowGap *int `cfg:"window_gap"` + BorderWidth *int `cfg:"border_width"` BorderlessSingleton *bool `cfg:"borderless_singleton"` - BorderWidth *bool `cfg:"border_width"` //colors ActiveBorderColor *ColorCode `cfg:"active_border_color"` diff --git a/src/bsp/loop.go b/src/bsp/loop.go index 7af0b57..89f6a04 100644 --- a/src/bsp/loop.go +++ b/src/bsp/loop.go @@ -2,12 +2,16 @@ package bsp import ( "context" + "fmt" + "github.com/jezek/xgb/randr" + "github.com/jezek/xgb/xinerama" "github.com/jezek/xgb/xproto" "github.com/jezek/xgbutil" "github.com/jezek/xgbutil/keybind" "github.com/jezek/xgbutil/mousebind" "github.com/jezek/xgbutil/xevent" + "github.com/jezek/xgbutil/xwindow" "tuxpa.in/a/zlog/log" ) @@ -24,10 +28,97 @@ func NewXWM(w *WM, x *xgbutil.XUtil) *XWM { return xwm } -func (xwm *XWM) Start(ctx context.Context) error { +func (xwm *XWM) initBinding(ctx context.Context) error { + randr.Init(xwm.X.Conn()) + xinerama.Init(xwm.X.Conn()) keybind.Initialize(xwm.X) - mousebind.Initialize(xwm.X) + return nil +} + +func (xwm *XWM) Start(ctx context.Context) error { + if err := xwm.initBinding(ctx); err != nil { + return err + } + + if err := xwm.initMonitors(ctx); err != nil { + return err + } + + if err := xwm.initRoot(ctx); err != nil { + return err + } + + if err := xwm.initExistingWindows(ctx); err != nil { + return err + } + // for { + // err := xwm.run(ctx) + // if err != nil { + // return err + // } + // } + return nil +} + +func (xwm *XWM) initMonitors(ctx context.Context) error { + xwm.X.Grab() + defer xwm.X.Ungrab() + res, err := randr.GetScreenResources(xwm.X.Conn(), xwm.X.RootWin()).Reply() + if err != nil { + return err + } + for _, output := range res.Outputs { + info, err := randr.GetOutputInfo(xwm.X.Conn(), output, res.ConfigTimestamp).Reply() + if err != nil { + log.Err(err).Any("output", output).Msg("fail get output info") + continue + } + monitorId, err := xproto.NewMonitorId(xwm.X.Conn()) + if err != nil { + return err + } + xwm.W.Mutate(func() error { + xwm.W.Monitors = append(xwm.W.Monitors, &Monitor{ + Name: string(info.Name), + Id: monitorId, + }) + return nil + }) + } + return nil +} + +func (xwm *XWM) initRoot(ctx context.Context) error { + // important masks + + evMasks := xproto.EventMaskPropertyChange | + xproto.EventMaskFocusChange | + xproto.EventMaskButtonPress | + xproto.EventMaskButtonRelease | + xproto.EventMaskStructureNotify | + xproto.EventMaskSubstructureNotify | + xproto.EventMaskSubstructureRedirect | + xproto.EventMaskPointerMotion + + err := xwindow.New(xwm.X, xwm.X.RootWin()).Listen(evMasks) + if err != nil { + return fmt.Errorf("cant listen to root events: %w", err) + } + + // TODO: need to add listener when root window changes size + + // attach root window + if err = xwm.W.Mutate(func() error { + xwm.W.Root = xwindow.New(xwm.X, xwm.X.RootWin()) + return nil + }); err != nil { + return err + } + + // map requests + xevent.MapRequestFun(func(X *xgbutil.XUtil, ev xevent.MapRequestEvent) { + }).Connect(xwm.X, xwm.W.Root.Id) captureCombos := []string{ "Mod4-1", @@ -35,7 +126,6 @@ func (xwm *XWM) Start(ctx context.Context) error { "Mod2-1", "Mod1-1", } - for _, combo := range captureCombos { err := mousebind.ButtonPressFun(func(xu *xgbutil.XUtil, event xevent.ButtonPressEvent) { log.Trace().Str("name", event.String()).Str("mod", combo).Msg("press") @@ -50,6 +140,15 @@ func (xwm *XWM) Start(ctx context.Context) error { return err } } + + xevent.ConfigureNotifyFun(func(xu *xgbutil.XUtil, event xevent.ConfigureNotifyEvent) { + log.Trace().Str("name", event.String()).Msg("notify event") + }).Connect(xwm.X, xwm.X.RootWin()) + + return nil +} + +func (xwm *XWM) initExistingWindows(ctx context.Context) error { tree, err := xproto.QueryTree(xwm.X.Conn(), xwm.X.RootWin()).Reply() if err != nil { return err @@ -65,24 +164,12 @@ func (xwm *XWM) Start(ctx context.Context) error { if attrs.MapState == xproto.MapStateUnmapped { continue } - - log.Trace(). - Uint16("class", attrs.Class). - Msg("found existing window") - log.Println(attrs) + c := xwm.RegisterClient(v) + if c != nil { + log.Printf("%+v", c) + } } - xevent.ConfigureNotifyFun(func(xu *xgbutil.XUtil, event xevent.ConfigureNotifyEvent) { - log.Trace().Str("name", event.String()).Msg("notify event") - }).Connect(xwm.X, xwm.X.RootWin()) - xevent.Main(xwm.X) - - // for { - // err := xwm.run(ctx) - // if err != nil { - // return err - // } - // } return nil } diff --git a/src/bsp/wm.go b/src/bsp/wm.go index f9f6f41..822c899 100644 --- a/src/bsp/wm.go +++ b/src/bsp/wm.go @@ -1,9 +1,15 @@ package bsp import ( + "bytes" + "encoding/binary" + "encoding/hex" "fmt" "sync" + "github.com/jezek/xgb/randr" + "github.com/jezek/xgb/xproto" + "github.com/jezek/xgbutil/xwindow" "tuxpa.in/t/wm/src/bsp/cfg" ) @@ -13,6 +19,10 @@ type WM struct { Cfg *cfg.Modifier[*Config] + Root *xwindow.Window + + Clients map[xproto.Window]*Client + mu sync.RWMutex } @@ -25,10 +35,19 @@ type Desktop struct { type Monitor struct { Name string + Id xproto.Monitor + + raw *randr.GetOutputInfoReply Cfg *cfg.Modifier[*Config] } +func (m *Monitor) HexId() []byte { + o := bytes.NewBuffer([]byte("0x")) + binary.Write(hex.NewEncoder(o), binary.LittleEndian, m.Id) + return o.Bytes() +} + type Node struct { Name string @@ -53,15 +72,6 @@ func (w *WM) View(fn func() error) error { return nil } -func (w *WM) AddMonitor(name string) error { - return w.Mutate(func() error { - w.Monitors = append(w.Monitors, &Monitor{ - Name: name, - Cfg: cfg.NewModifier(&Config{}), - }) - return nil - }) -} func (w *WM) AddDesktop(name string, monitorName string) error { return w.Mutate(func() error { var monitor *Monitor @@ -84,7 +94,8 @@ func (w *WM) AddDesktop(name string, monitorName string) error { func NewWM() *WM { w := &WM{ - Cfg: cfg.NewModifier(&Config{}), + Cfg: cfg.NewModifier(&Config{}), + Clients: make(map[xproto.Window]*Client), } w.Cfg.FillDefaults() diff --git a/src/handler/domains/config.go b/src/handler/domains/config.go index a29b12f..0577af1 100644 --- a/src/handler/domains/config.go +++ b/src/handler/domains/config.go @@ -1,6 +1,7 @@ package domains import ( + "tuxpa.in/a/zlog/log" "tuxpa.in/t/wm/src/copies" "tuxpa.in/t/wm/src/sock" ) @@ -36,6 +37,7 @@ func (n *Config) Run(msg *sock.Msg) ([]byte, error) { if err != nil { return nil, err } + log.Trace().Str("key", n.configKey).Any("string", n.configValue).Msg("config set") return nil, nil } diff --git a/src/handler/domains/query.go b/src/handler/domains/query.go index ef80712..10e9679 100644 --- a/src/handler/domains/query.go +++ b/src/handler/domains/query.go @@ -32,11 +32,28 @@ func (n *Query) Run(msg *sock.Msg) ([]byte, error) { switch n.Command { case "desktops": return n.desktops(msg) + case "monitors": + return n.monitors(msg) default: return nil, &copies.ErrTODO{} } } +func (n *Query) monitors(msg *sock.Msg) ([]byte, error) { + o := new(bytes.Buffer) + n.XWM.W.View(func() error { + for _, v := range n.XWM.W.Monitors { + if n.UseNames { + o.WriteString(v.Name) + } else { + o.Write(v.HexId()) + } + o.WriteRune('\n') + } + return nil + }) + return o.Bytes(), nil +} func (n *Query) desktops(msg *sock.Msg) ([]byte, error) { o := new(bytes.Buffer) o.WriteString("hi there") diff --git a/src/sock/msg.go b/src/sock/msg.go index 754633e..a32886c 100644 --- a/src/sock/msg.go +++ b/src/sock/msg.go @@ -66,7 +66,7 @@ func (m *Msg) Reply(xs []byte) error { if err != nil { return err } - if len(xs) != 0 { + if len(xs) != 0 && xs[len(xs)-1] != '\n' { wr.Write([]byte("\n")) } wr.Write([]byte{0}) diff --git a/vend/xgb/xproto/xproto.go b/vend/xgb/xproto/xproto.go index ddd3ca9..8ce12db 100644 --- a/vend/xgb/xproto/xproto.go +++ b/vend/xgb/xproto/xproto.go @@ -6394,6 +6394,16 @@ func VisualInfoListBytes(buf []byte, list []VisualInfo) int { type Visualid uint32 +type Monitor uint32 + +func NewMonitorId(c *xgb.Conn) (Monitor, error) { + id, err := c.NewId() + if err != nil { + return 0, err + } + return Monitor(id), nil +} + type Window uint32 func NewWindowId(c *xgb.Conn) (Window, error) {