From c3c0b1269b1324d3f2c8aeb22da9efbabeff0c38 Mon Sep 17 00:00:00 2001 From: a Date: Sun, 25 Dec 2022 23:06:40 -0600 Subject: [PATCH] protocol --- .env | 1 + .gitignore | 5 +++ Makefile | 12 ++---- cmd/bspc/main.go | 2 +- cmd/bspwm/main.go | 42 +++++++++++++++++++ src/bsp/node.go | 1 + src/bspc/domains.go | 17 -------- src/cmd/cmd.go | 12 ------ src/copies/errors.go | 35 ++++++++++++++++ src/handler/domains/echo.go | 19 +++++++++ src/handler/domains/node.go | 13 ++++++ src/handler/domains/query.go | 13 ++++++ src/handler/handler.go | 55 +++++++++++++++++++++++++ src/sock/msg.go | 73 +++++++++++++++++++++++++++++++++ src/sock/server.go | 79 ++++++++++++++++++++++++++++++++++++ src/sock/sock.go | 33 ++++++++------- 16 files changed, 360 insertions(+), 52 deletions(-) create mode 100755 .env create mode 100644 .gitignore create mode 100644 cmd/bspwm/main.go create mode 100644 src/bsp/node.go delete mode 100644 src/bspc/domains.go delete mode 100644 src/cmd/cmd.go create mode 100644 src/copies/errors.go create mode 100644 src/handler/domains/echo.go create mode 100644 src/handler/domains/node.go create mode 100644 src/handler/domains/query.go create mode 100644 src/handler/handler.go create mode 100644 src/sock/msg.go create mode 100644 src/sock/server.go diff --git a/.env b/.env new file mode 100755 index 0000000..d563759 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +export diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d109d43 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/bspwm +/bspc + +*.log +*.sock diff --git a/Makefile b/Makefile index ca2de46..24b1589 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -WINDOWSIZE=1024:768 +WINDOWSIZE=1280x720 VERCMD ?= git describe --tags 2> /dev/null @@ -19,17 +19,13 @@ all: bspwm bspc VPATH=src -include Sourcedeps - -$(WM_OBJ) $(CLI_OBJ): Makefile - -#bspwm: $(WM_OBJ) +bspwm: bspc: cmd/bspc src - go build ./cmd/bspc -o bspc + go build -o bspc ./cmd/bspc xephyr: - Xephyr -br ac -noreset ${WINDOWSIZE} :1 + Xephyr :11 -br -ac -noreset -screen ${WINDOWSIZE} install: mkdir -p "$(DESTDIR)$(BINPREFIX)" diff --git a/cmd/bspc/main.go b/cmd/bspc/main.go index 43fcd98..866f4b9 100644 --- a/cmd/bspc/main.go +++ b/cmd/bspc/main.go @@ -8,7 +8,7 @@ import ( ) func main() { - s, err := sock.Default() + s, err := sock.Client("") errExit(err) resp, err := s.Send(os.Args[1:]...) errExit(err) diff --git a/cmd/bspwm/main.go b/cmd/bspwm/main.go new file mode 100644 index 0000000..fe40792 --- /dev/null +++ b/cmd/bspwm/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "os/signal" + "syscall" + + "tuxpa.in/t/wm/src/handler" + "tuxpa.in/t/wm/src/sock" +) + +func main() { + ln, err := sock.Server("./bspwm.sock") + if err != nil { + panic(err) + } + defer ln.Close() + log.Printf("starting bspwm") + + ctx, stop := signal.NotifyContext(context.Background(), + os.Interrupt, + syscall.SIGTERM, + syscall.SIGQUIT) + defer stop() + + h := &handler.Handler{} + + for { + select { + case m := <-ln.Msg(): + log.Printf("got cmd: %s", m.Args()) + h.Run(m) + case <-ctx.Done(): + fmt.Println() + log.Printf("bspwm shutting down...") + return + } + } +} diff --git a/src/bsp/node.go b/src/bsp/node.go new file mode 100644 index 0000000..27a6bb9 --- /dev/null +++ b/src/bsp/node.go @@ -0,0 +1 @@ +package bsp diff --git a/src/bspc/domains.go b/src/bspc/domains.go deleted file mode 100644 index ac35ae5..0000000 --- a/src/bspc/domains.go +++ /dev/null @@ -1,17 +0,0 @@ -package bspc - -import "tuxpa.in/t/wm/src/sock" - -type NODE_SEL string - -type Node struct { - Sel NODE_SEL -} - -func (n *Node) Focus(node NODE_SEL) error { - s, err := sock.Default() - if err != nil { - return err - } - return s.Send("node", "-l") -} diff --git a/src/cmd/cmd.go b/src/cmd/cmd.go deleted file mode 100644 index 9239286..0000000 --- a/src/cmd/cmd.go +++ /dev/null @@ -1,12 +0,0 @@ -package cmd - -import "context" - -type Context struct { - Debug bool - context.Context -} - -type Cmd interface { - Run(ctx *Context) error -} diff --git a/src/copies/errors.go b/src/copies/errors.go new file mode 100644 index 0000000..203d50e --- /dev/null +++ b/src/copies/errors.go @@ -0,0 +1,35 @@ +package copies + +import "fmt" + +type ErrUnknownDomainOrCommand struct { + Str string +} + +func (u *ErrUnknownDomainOrCommand) Error() string { + return fmt.Sprintf(`Unknown Command: '%s'.`, u.Str) +} + +type ErrUnknownCommand struct { + Cmd string +} + +func (u *ErrUnknownCommand) Error() string { + return fmt.Sprintf(`Unknown Command: '%s'.`, u.Cmd) +} + +type ErrMissingArguments struct { +} + +func (m *ErrMissingArguments) Error() string { + return `Missing Arguments` +} + +type ErrTODO struct { + Kind string + Name string +} + +func (e *ErrTODO) Error() string { + return fmt.Sprintf(`'%s' not implemented: '%s'.`, e.Kind, e.Name) +} diff --git a/src/handler/domains/echo.go b/src/handler/domains/echo.go new file mode 100644 index 0000000..f9464e7 --- /dev/null +++ b/src/handler/domains/echo.go @@ -0,0 +1,19 @@ +package domains + +import ( + "strings" + + "tuxpa.in/t/wm/src/copies" + "tuxpa.in/t/wm/src/sock" +) + +type Echo struct { +} + +func (n *Echo) Run(msg *sock.Msg) ([]byte, error) { + if !msg.HasNext() { + return nil, &copies.ErrMissingArguments{} + } + out := strings.Join(msg.Args(), " ") + return []byte(out), nil +} diff --git a/src/handler/domains/node.go b/src/handler/domains/node.go new file mode 100644 index 0000000..34db0ba --- /dev/null +++ b/src/handler/domains/node.go @@ -0,0 +1,13 @@ +package domains + +import "tuxpa.in/t/wm/src/copies" + +type Node struct { +} + +func (n *Node) Run(args ...string) error { + if len(args) == 0 { + return &copies.ErrMissingArguments{} + } + return nil +} diff --git a/src/handler/domains/query.go b/src/handler/domains/query.go new file mode 100644 index 0000000..fba7d63 --- /dev/null +++ b/src/handler/domains/query.go @@ -0,0 +1,13 @@ +package domains + +import "tuxpa.in/t/wm/src/copies" + +type Query struct { +} + +func (n *Query) Run(args ...string) error { + if len(args) == 0 { + return &copies.ErrMissingArguments{} + } + return nil +} diff --git a/src/handler/handler.go b/src/handler/handler.go new file mode 100644 index 0000000..102b85c --- /dev/null +++ b/src/handler/handler.go @@ -0,0 +1,55 @@ +package handler + +import ( + "fmt" + + "tuxpa.in/t/wm/src/copies" + "tuxpa.in/t/wm/src/handler/domains" + "tuxpa.in/t/wm/src/sock" +) + +type Handler struct { +} + +func (h *Handler) Run(msg *sock.Msg) { + resp, err := h.run(msg) + if msg.Err(err) { + return + } + err = msg.Reply(string(resp)) + if msg.Err(err) { + return + } + msg.Reply("") +} + +func (h *Handler) run(msg *sock.Msg) ([]byte, error) { + if !msg.HasNext() { + return nil, &copies.ErrMissingArguments{} + } + cmd := msg.Next() + switch cmd { + case "node", + "desktop", + "monitor", + "query", + "wm", + "rule", + "config", + "subscribe", + "quit": + return nil, &copies.ErrTODO{Kind: "domain", Name: cmd} + case "echo": + return h.runDomain(cmd, msg, (&domains.Echo{}).Run) + default: + return nil, &copies.ErrUnknownDomainOrCommand{Str: cmd} + } +} + +func (h *Handler) runDomain(name string, msg *sock.Msg, fn func(*sock.Msg) ([]byte, error)) ([]byte, error) { + str, err := fn(msg) + if err != nil { + return nil, fmt.Errorf("%s: %w", name, err) + } + return str, nil +} diff --git a/src/sock/msg.go b/src/sock/msg.go new file mode 100644 index 0000000..02646c3 --- /dev/null +++ b/src/sock/msg.go @@ -0,0 +1,73 @@ +package sock + +import ( + "bufio" + "net" + "sync/atomic" +) + +type Msg struct { + c *net.UnixConn + args []string + closed atomic.Bool + cur int +} + +func (m *Msg) HasNext() bool { + return m.Has(0) +} +func (m *Msg) Peek() string { + return m.Get(0) +} +func (m *Msg) Next() string { + if m.Has(m.cur) { + m.cur = m.cur + 1 + return m.args[m.cur-1] + } + return "" +} +func (m *Msg) Has(i int) bool { + return len(m.args) > (i + m.cur) +} +func (m *Msg) Get(i int) string { + if m.Has(i) { + return m.args[i+m.cur] + } + return "" +} +func (m *Msg) Args() []string { + if m.HasNext() { + return m.args[m.cur:] + } + return nil +} +func (m *Msg) Err(e error) bool { + if e == nil { + return false + } + if !m.closed.CompareAndSwap(false, true) { + return true + } + defer m.c.Close() + wr := bufio.NewWriter(m.c) + wr.Write([]byte{7}) + wr.Write([]byte(e.Error())) + wr.Write([]byte("\n")) + wr.Flush() + return true +} +func (m *Msg) Reply(ans string) error { + if !m.closed.CompareAndSwap(false, true) { + return nil + } + defer m.c.Close() + wr := bufio.NewWriter(m.c) + _, err := wr.Write([]byte(ans)) + if err != nil { + return err + } + wr.Write([]byte("\n")) + wr.Write([]byte{0}) + wr.Flush() + return nil +} diff --git a/src/sock/server.go b/src/sock/server.go new file mode 100644 index 0000000..ee30787 --- /dev/null +++ b/src/sock/server.go @@ -0,0 +1,79 @@ +package sock + +import ( + "bytes" + "fmt" + "net" + "os" + + "github.com/jezek/xgb" +) + +type SockListener struct { + xgb *xgb.Conn + l *net.UnixListener + + ch chan *Msg +} + +func (s *SockListener) Msg() <-chan *Msg { + return s.ch +} +func Server(path string) (*SockListener, error) { + s := &SockListener{} + s.ch = make(chan *Msg, 1) + xc, err := xgb.NewConnDisplay("") + if err != nil { + return nil, err + } + s.xgb = xc + if path == "" { + path = os.Getenv(SOCKET_ENV_VAR) + } + if path == "" { + path = fmt.Sprintf(SOCKET_PATH_TPL, "", xc.DisplayNumber, xc.DefaultScreen) + } + addr, err := net.ResolveUnixAddr("unix", path) + if err != nil { + return nil, err + } + conn, err := net.ListenUnix("unix", addr) + if err != nil { + return nil, err + } + s.l = conn + go func() { + for { + conn, err := s.l.AcceptUnix() + if err != nil { + continue + } + go func() { + err = s.acceptConn(conn) + if err != nil { + fmt.Printf("fail accept conn: %s\n", err) + } + }() + } + }() + return s, nil +} +func (s *SockListener) Close() error { + return s.l.Close() +} +func (s *SockListener) acceptConn(c *net.UnixConn) error { + msg := &Msg{} + bts := make([]byte, 4096) + n, err := c.Read(bts) + if err != nil { + return err + } + bts = bts[:n] + splt := bytes.Split(bts, []byte{0}) + for _, v := range splt { + msg.args = append(msg.args, string(v)) + } + msg.c = c + s.ch <- msg + return nil +} diff --git a/src/sock/sock.go b/src/sock/sock.go index 1bdd822..3fb626d 100644 --- a/src/sock/sock.go +++ b/src/sock/sock.go @@ -1,6 +1,7 @@ package sock import ( + "bufio" "fmt" "io" "net" @@ -11,28 +12,28 @@ import ( const SOCKET_PATH_TPL = "/tmp/bspwm%s_%d_%d-socket" +const SOCKET_ENV_VAR = "BSPWM_SOCKET" + type Sock struct { - C net.Conn + C *net.UnixConn } func (s *Sock) Send(args ...string) (string, error) { if len(args) == 0 { return "", s.C.Close() } + wr := bufio.NewWriter(s.C) for _, msg := range args { - _, err := s.C.Write([]byte(msg)) - if err != nil { - return "", err - } - _, err = s.C.Write([]byte{0}) - if err != nil { - return "", err - } + wr.WriteString(msg) + wr.WriteByte(0) } + wr.Flush() + s.C.CloseWrite() bts, err := io.ReadAll(s.C) if err != nil { return "", err } + s.C.CloseRead() if len(bts) > 0 { if bts[0] == 7 { return "", fmt.Errorf(string(bts[1:])) @@ -41,19 +42,23 @@ func (s *Sock) Send(args ...string) (string, error) { return string(bts), nil } -func Default() (*Sock, error) { - return New(os.Getenv("BSPWM_SOCKET")) -} -func New(path string) (*Sock, error) { +func Client(path string) (*Sock, error) { xc, err := xgb.NewConn() if err != nil { return nil, err } defer xc.Close() + if path == "" { + path = os.Getenv(SOCKET_ENV_VAR) + } if path == "" { path = fmt.Sprintf(SOCKET_PATH_TPL, "", xc.DisplayNumber, xc.DefaultScreen) } - conn, err := net.Dial("unix", path) + addr, err := net.ResolveUnixAddr("unix", path) + if err != nil { + return nil, err + } + conn, err := net.DialUnix("unix", nil, addr) if err != nil { return nil, err }