diff --git a/cmd/bspwm/main.go b/cmd/bspwm/main.go index 1b99a4d..68094c8 100644 --- a/cmd/bspwm/main.go +++ b/cmd/bspwm/main.go @@ -2,11 +2,14 @@ package main import ( "context" + "fmt" "log" + "net" "os" "os/signal" "syscall" + "github.com/jezek/xgbutil" "tuxpa.in/t/wm/src/bsp" "tuxpa.in/t/wm/src/handler" "tuxpa.in/t/wm/src/handler/domains" @@ -14,13 +17,32 @@ import ( ) func main() { - // create socket - ln, err := sock.Server("./bspwm.sock") + + err := _main() if err != nil { panic(err) } - defer ln.Close() + +} +func _main() error { + // connect to the x server + log.Printf("connecting to xorg") + x11, err := xgbutil.NewConn() + if err != nil { + return err + } + defer x11.Conn().Close() + + addr := parsePath(x11, "./bspwm.sock") + // create socket log.Printf("starting bspwm") + ln, err := sock.Server(addr) + if err != nil { + return err + } + defer ln.Close() + + // construct context ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, @@ -28,31 +50,49 @@ func main() { syscall.SIGINT, ) defer stop() + // initialize WM state w := bsp.NewWM() - // create a wm manager - xwm := bsp.NewXWM(w, ln.X11()) - // install the handler + // create a wm-x11 connection + xwm := bsp.NewXWM(w, x11) + // create a handler h := &handler.Handler{ XWM: xwm, } + // install the handlers handler.AddDomain[domains.Todo](h, "node") handler.AddDomain[domains.Todo](h, "desktop") handler.AddDomain[domains.Todo](h, "monitor") - handler.AddDomain[domains.Todo](h, "wm") + handler.AddDomain[domains.Wm](h, "wm") handler.AddDomain[domains.Todo](h, "rule") handler.AddDomain[domains.Todo](h, "config") handler.AddDomain[domains.Todo](h, "subscribe") handler.AddDomain[domains.Todo](h, "quit") handler.AddDomain[domains.Query](h, "query") handler.AddDomain[domains.Echo](h, "echo") + + // message listen loop for { select { case m := <-ln.Msg(): h.Run(m) case <-ctx.Done(): log.Println("bspwm shutting down...") - return + return nil } } } + +func parsePath(xc *xgbutil.XUtil, path string) *net.UnixAddr { + if path == "" { + path = os.Getenv(sock.SOCKET_ENV_VAR) + } + if path == "" { + path = fmt.Sprintf(sock.SOCKET_PATH_TPL, "", xc.Conn().DisplayNumber, xc.Conn().DefaultScreen) + } + addr, err := net.ResolveUnixAddr("unix", path) + if err != nil { + panic(err) + } + return addr +} diff --git a/go.mod b/go.mod index 163d28b..66e53c6 100644 --- a/go.mod +++ b/go.mod @@ -8,4 +8,13 @@ replace github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0 => ./xgbutil require github.com/jezek/xgb v1.1.0 -require github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0 +require ( + github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index e99f9ea..0ac6493 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,12 @@ 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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/bsp/cfg/cfg.go b/src/bsp/cfg/cfg.go new file mode 100644 index 0000000..b22d399 --- /dev/null +++ b/src/bsp/cfg/cfg.go @@ -0,0 +1,173 @@ +package cfg + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + "tuxpa.in/t/wm/src/copies" +) + +// Read will return the first non nil value or the default value +func Read[T any](xs ...*T) T { + for _, v := range xs { + if v != nil { + return *v + } + } + // zero value if all else fails + return *new(T) +} + +func ReadFunc[T any, V any](fn func(*T) *V, xs ...*T) V { + for _, v := range xs { + if v == nil { + continue + } + vv := fn(v) + if vv != nil { + return *vv + } + } + return *new(V) +} + +type Modifier[T any] struct { + Ref T + + setters map[string]func(v string) error + getters map[string]func() (string, error) +} + +func NewModifier[T any](start T) *Modifier[T] { + m := &Modifier[T]{ + Ref: start, + setters: map[string]func(v string) error{}, + getters: map[string]func() (string, error){}, + } + m.setup() + return m +} + +func (m *Modifier[T]) Set(k, v string) error { + fn, ok := m.setters[k] + if !ok { + // TODO: some error here + return nil + } + return fn(v) +} +func (m *Modifier[T]) GetString(k string) (string, error) { + fn, ok := m.getters[k] + if !ok { + // TODO: some error here + return "", fmt.Errorf("config key '%s' not found", k) + } + return fn() +} + +func (m *Modifier[T]) FillDefaults() { + rt := reflect.TypeOf(m.Ref).Elem() + for i := 0; i < rt.NumField(); i++ { + ft := rt.Field(i) + k := ft.Tag.Get("cfg") + dv := ft.Tag.Get("default") + if dv == "" { + continue + } + m.Set(k, dv) + } + return +} + +func (m *Modifier[T]) setup() { + // grab the value + rv := reflect.ValueOf(m.Ref).Elem() + rt := reflect.TypeOf(m.Ref).Elem() + for i := 0; i < rv.NumField(); i++ { + fv := rv.Field(i) + ft := rt.Field(i) + k := ft.Tag.Get("cfg") + kind := ft.Type.Elem().Kind() + switch kind { + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + m.setters[k] = func(v string) error { + val, err := strconv.ParseUint(v, 10, 64) + if err != nil { + return copies.NewInvalidValueErr(k, v) + } + if fv.IsNil() { + fv.Set(reflect.New(ft.Type.Elem())) + } + fv.Elem().SetUint(uint64(val)) + return nil + } + m.getters[k] = func() (string, error) { + if fv.IsNil() { + return "", nil + } + return fmt.Sprintf("%v", fv.Elem()), nil + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + m.setters[k] = func(v string) error { + val, err := strconv.Atoi(v) + if err != nil { + return copies.NewInvalidValueErr(k, v) + } + if fv.IsNil() { + fv.Set(reflect.New(ft.Type.Elem())) + } + fv.Elem().SetInt(int64(val)) + return nil + } + m.getters[k] = func() (string, error) { + if fv.IsNil() { + return "", nil + } + return fmt.Sprintf("%v", fv.Elem()), nil + } + case reflect.Bool: + m.setters[k] = func(v string) error { + var b bool + switch strings.ToLower(v) { + case "true", "on": + b = true + case "false", "off": + b = false + default: + return copies.NewInvalidValueErr(k, v) + } + if fv.IsNil() { + fv.Set(reflect.New(ft.Type.Elem())) + } + fv.Elem().SetBool(b) + return nil + } + m.getters[k] = func() (string, error) { + if fv.IsNil() { + return "", nil + } + return fmt.Sprintf("%v", fv.Elem()), nil + } + case reflect.String: + m.setters[k] = func(v string) error { + if fv.IsNil() { + fv.Set(reflect.New(ft.Type.Elem())) + } + fv.Elem().SetString(strings.TrimSpace(v)) + return nil + } + m.getters[k] = func() (string, error) { + if fv.IsNil() { + return "", nil + } + return fmt.Sprintf("%v", fv.Elem()), nil + } + } + } +} + +func getStructTag(f reflect.StructField, tagName string) string { + return string(f.Tag.Get(tagName)) +} diff --git a/src/bsp/cfg/cfg_test.go b/src/bsp/cfg/cfg_test.go new file mode 100644 index 0000000..4b2b4f1 --- /dev/null +++ b/src/bsp/cfg/cfg_test.go @@ -0,0 +1,36 @@ +package cfg_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "tuxpa.in/t/wm/src/bsp/cfg" +) + +type E int + +const ( + D0 E = 0 + D1 +) + +type s1 struct { + A *int `cfg:"a" default:"4"` + B *bool `cfg:"b"` + C *string `cfg:"c" default:"crabs"` + D *E `cfg:"d"` +} + +func TestModifierS1(t *testing.T) { + s := &s1{} + m := cfg.NewModifier(s) + m.FillDefaults() + assert.EqualValues(t, *m.Ref.C, "crabs") + + m.Set("b", "on") + assert.EqualValues(t, *m.Ref.B, true) + assert.Nil(t, m.Ref.D) + m.Set("d", "442") + assert.EqualValues(t, *m.Ref.D, 442) + +} diff --git a/src/bsp/color.go b/src/bsp/color.go new file mode 100644 index 0000000..40674c8 --- /dev/null +++ b/src/bsp/color.go @@ -0,0 +1,3 @@ +package bsp + +type ColorCode string diff --git a/src/bsp/config.go b/src/bsp/config.go new file mode 100644 index 0000000..929e5c2 --- /dev/null +++ b/src/bsp/config.go @@ -0,0 +1,108 @@ +package bsp + +type AUTOMATIC_SCHEME_T int + +const ( + SCHEME_LONGEST_SIDE AUTOMATIC_SCHEME_T = iota + SCHEME_ALTERNATE + SCHEME_SPIRAL +) + +type HONOR_SIZE_HINTS_MODE_T int + +const ( + HONOR_SIZE_HINTS_NO HONOR_SIZE_HINTS_MODE_T = iota + HONOR_SIZE_HINTS_YES + HONOR_SIZE_HINTS_FLOATING + HONOR_SIZE_HINTS_TILED + HONOR_SIZE_HINTS_DEFAULT +) + +type TIGHTNESS_T int + +const ( + TIGHTNESS_LOW TIGHTNESS_T = iota + TIGHTNESS_HIGH +) + +type CHILD_POLARITY_T int + +const ( + FIRST_CHILD CHILD_POLARITY_T = iota + SECOND_CHILD +) + +type Config struct { + + // pointer options + PointerAction1 *string `cfg:"pointer_action1"` + PointerAction2 *string `cfg:"pointer_action2"` + PointerAction3 *string `cfg:"pointer_action3"` + + PointerFollowsMonitor *bool `cfg:"pointer_follows_monitor"` + FocusFollowsPointer *bool `cfg:"focus_follows_pointer"` + PointerFollowsFocus *bool `cfg:"pointer_follows_focus"` + PointerMotionInterval *string `cfg:"pointer_motion_interval"` + + // control + PointerModifier *string `cfg:"pointer_modifier" default:"XCB_MOD_MASK_4"` + StatusPrefix *string `cfg:"status_prefix" default:"W"` + ExternalRulesCommand *string `cfg:"external_rules_command" default:""` + + // click + ClickToFocus *string `cfg:"click_to_focus" default:"XCB_BUTTON_INDEX_1"` + SwallowFirstClick *bool `cfg:"swallow_first_click"` + + // ewmh + IgnoreEwmhFocus *bool `cfg:"ignore_ewmh_focus"` + IgnoreEwmhFullscreen *bool `cfg:"ignore_ewmh_fullscreen"` + IgnoreEwmhStruts *bool `cfg:"ignore_ewmh_struts"` + + // monocole + PreselFeedback *bool `cfg:"presel_feedback" default:"true"` + GaplessMonocle *bool `cfg:"gapless_monocle"` + BorderlessMonocle *bool `cfg:"borderless_monocle"` + SingleMonocle *bool `cfg:"single_monocle"` + + // padding + TopPadding *int `cfg:"top_padding"` + BottomPadding *int `cfg:"bottom_padding"` + LeftPadding *int `cfg:"left_padding"` + RightPadding *int `cfg:"right_padding"` + + // monocle padding + TopMonoclePadding *int `cfg:"top_monocle_padding"` + BottomMonoclePadding *int `cfg:"bottom_monocle_padding"` + LeftMonoclePadding *int `cfg:"left_monocle_padding"` + RightMonoclePadding *int `cfg:"right_monocle_padding"` + + // splits + SplitRatio *float64 `cfg:"split_ratio" default:"0.5"` + AutomaticScheme *AUTOMATIC_SCHEME_T `cfg:"automatic_scheme"` + RemovalAdjustment *string `cfg:"removal_adjustment"` + + // gap + WindowGap *int `cfg:"window_gap"` + BorderlessSingleton *bool `cfg:"borderless_singleton"` + BorderWidth *bool `cfg:"border_width"` + + //colors + ActiveBorderColor *ColorCode `cfg:"active_border_color"` + PreselFeedbackColor *ColorCode `cfg:"presel_feedback_color"` + FocusedBorderColor *ColorCode `cfg:"focused_border_color"` + NormalBorderColor *ColorCode `cfg:"normal_border_color"` + + //monitor + RemoveDisabledMonitors *bool `cfg:"remove_disabled_monitors"` + RemoveUnpluggedMonitors *bool `cfg:"remove_unplugged_monitors"` + MergeOverlappingMonitors *bool `cfg:"merge_overlapping_monitors"` + + // more + CenterPseudoTiled *bool `cfg:"center_pseudo_tiled" default:"true"` + HonorSizeHints *HONOR_SIZE_HINTS_MODE_T `cfg:"honor_size_hints"` + MappingEventsCount *int `cfg:"mapping_events_count" default:"1"` + + //etc + DirectionalFocusTightness *TIGHTNESS_T `cfg:"directional_focus_tightness"` + InitialPolarity *CHILD_POLARITY_T `cfg:"initial_polarity"` +} diff --git a/src/bsp/loop.go b/src/bsp/loop.go index 791271c..2830461 100644 --- a/src/bsp/loop.go +++ b/src/bsp/loop.go @@ -7,14 +7,14 @@ import ( ) type XWM struct { - w *WM - x *xgbutil.XUtil + W *WM + X *xgbutil.XUtil } func NewXWM(w *WM, x *xgbutil.XUtil) *XWM { xwm := &XWM{ - w: w, - x: x, + W: w, + X: x, } return xwm } diff --git a/src/bsp/wm.go b/src/bsp/wm.go index 10a3a6f..e67b7fe 100644 --- a/src/bsp/wm.go +++ b/src/bsp/wm.go @@ -2,22 +2,36 @@ package bsp import ( "sync" + + "tuxpa.in/t/wm/src/bsp/cfg" ) type WM struct { Desktops []*Desktop Monitors []*Monitor + Cfg *cfg.Modifier[*Config] + mu sync.RWMutex } type Desktop struct { Name string Monitor *Monitor + + Cfg *cfg.Modifier[Config] } type Monitor struct { Name string + + Cfg *cfg.Modifier[Config] +} + +type Node struct { + Name string + + Cfg *cfg.Modifier[Config] } func (w *WM) Mutate(fn func() error) { @@ -41,6 +55,10 @@ func (w *WM) AddDesktop(name string) { } func NewWM() *WM { - w := &WM{} + w := &WM{ + Cfg: cfg.NewModifier(&Config{}), + } + w.Cfg.FillDefaults() + return w } diff --git a/src/copies/errors.go b/src/copies/errors.go index 9aed45d..3ce01a6 100644 --- a/src/copies/errors.go +++ b/src/copies/errors.go @@ -2,6 +2,31 @@ package copies import "fmt" +type ErrInvalidArgumentCount struct { + Name string + Value int +} + +func (u *ErrInvalidArgumentCount) Error() string { + return fmt.Sprintf(`Was expecting %s arguments, received %d.\n`, u.Name, u.Value) +} + +type ErrInvalidValue struct { + Name string + Value string +} + +func (u *ErrInvalidValue) Error() string { + return fmt.Sprintf(`%s: Invalid value: '%s'.\n`, u.Name, u.Value) +} + +func NewInvalidValueErr(n, v string) *ErrInvalidValue { + return &ErrInvalidValue{ + Name: n, + Value: v, + } +} + type ErrUnknownDomainOrCommand struct { Str string } diff --git a/src/handler/domains/common.go b/src/handler/domains/common.go index bda266e..e84cccf 100644 --- a/src/handler/domains/common.go +++ b/src/handler/domains/common.go @@ -1,15 +1,72 @@ package domains -import "tuxpa.in/t/wm/src/bsp" +import ( + "fmt" + + "tuxpa.in/t/wm/src/bsp" + "tuxpa.in/t/wm/src/copies" + "tuxpa.in/t/wm/src/sock" +) type inject struct { xwm } +type desktop_sel struct { + desktopsel string +} + +func (n *desktop_sel) readDesktopSel(msg *sock.Msg) error { + if !msg.HasNext() { + return &copies.ErrMissingArguments{} + } + str := msg.Next() + switch str { + case "any", "first_ancestor", + "last", "newest", "older", "newer", + "focused", "pointed", "biggest", "smallest": + n.desktopsel = str + } + return nil +} + +type node_sel struct { + nodesel string +} + +func (n *node_sel) readNodeSel(msg *sock.Msg) error { + if !msg.HasNext() { + return &copies.ErrMissingArguments{} + } + str := msg.Next() + switch str { + case "any", "first_ancestor", + "last", "newest", "older", "newer", + "focused", "pointed", "biggest", "smallest": + n.nodesel = str + } + return nil +} + +type cmd struct { + Command string +} + +func (c *cmd) SetCommand(x string) { + c.Command = x +} + +func (n *cmd) readCommand(msg *sock.Msg, c string) (bool, error) { + if n.Command == "" { + n.Command = c + return msg.HasNext(), nil + } + return false, fmt.Errorf("multiple commands given") +} type xwm struct { XWM *bsp.XWM } -func (x xwm) SetXWM(z *bsp.XWM) { +func (x *xwm) SetXWM(z *bsp.XWM) { x.XWM = z } diff --git a/src/handler/domains/config.go b/src/handler/domains/config.go new file mode 100644 index 0000000..13782b3 --- /dev/null +++ b/src/handler/domains/config.go @@ -0,0 +1,66 @@ +package domains + +import ( + "fmt" + + "tuxpa.in/t/wm/src/copies" + "tuxpa.in/t/wm/src/sock" +) + +type Config struct { + UseNames bool + + configKey string + configValue string + + cmd + inject +} + +func (n *Config) Run(msg *sock.Msg) ([]byte, error) { + if !msg.HasNext() { + return nil, &copies.ErrMissingArguments{} + } + for { + ok, err := n.parse(msg) + if err != nil { + return nil, err + } + if !ok { + break + } + } + if n.configValue == "" { + str, err := n.XWM.W.Cfg.GetString(n.configKey) + return []byte(str), err + } + err := n.XWM.W.Cfg.Set(n.configKey, n.configValue) + if err != nil { + return nil, err + } + return nil, nil +} + +func (n *Config) parse(msg *sock.Msg) (bool, error) { + if !msg.HasNext() { + return false, &copies.ErrMissingArguments{} + } + arg := msg.Next() + switch arg { + case "-d": + return n.readCommand(msg, "desktops") + case "-m": + return n.readCommand(msg, "monitors") + case "-n": + return n.readCommand(msg, "nodes") + default: + n.configKey = arg + if msg.HasNext() { + n.configValue = msg.Next() + } + if msg.HasNext() { + return false, &copies.ErrInvalidArgumentCount{Name: "2 or 3", Value: len(msg.Args())} + } + return false, fmt.Errorf(`unknown option: '%s'`, arg) + } +} diff --git a/src/handler/domains/node.go b/src/handler/domains/node.go index 96bfdd5..f584559 100644 --- a/src/handler/domains/node.go +++ b/src/handler/domains/node.go @@ -1,14 +1,58 @@ package domains -import "tuxpa.in/t/wm/src/copies" +import ( + "fmt" + + "tuxpa.in/t/wm/src/copies" + "tuxpa.in/t/wm/src/sock" +) type Node struct { + UseNames bool + inject + + cmd } -func (n Node) Run(args ...string) error { - if len(args) == 0 { - return &copies.ErrMissingArguments{} +func (n *Node) Run(msg *sock.Msg) ([]byte, error) { + if !msg.HasNext() { + return nil, &copies.ErrMissingArguments{} } - return nil + for { + ok, err := n.parse(msg) + if err != nil { + return nil, err + } + if !ok { + break + } + } + + return nil, nil +} + +func (n *Node) parse(msg *sock.Msg) (bool, error) { + if !msg.HasNext() { + return false, &copies.ErrMissingArguments{} + } + arg := msg.Next() + switch arg { + case "--desktop", "-d": + case "--desktops", "-D": + return n.readCommand(msg, "desktops") + case "--monitor", "-m": + case "--monitors", "-M": + return n.readCommand(msg, "monitors") + case "--names": + n.UseNames = true + case "--node", "-n": + case "--nodes", "-N": + return n.readCommand(msg, "nodes") + case "--tree", "-T": + return n.readCommand(msg, "tree") + default: + return false, fmt.Errorf(`unknown option: '%s'`, arg) + } + return msg.HasNext(), nil } diff --git a/src/handler/domains/query.go b/src/handler/domains/query.go index 4256f31..ef80712 100644 --- a/src/handler/domains/query.go +++ b/src/handler/domains/query.go @@ -9,14 +9,14 @@ import ( ) type Query struct { - Command string - UseNames bool inject + + cmd } -func (n Query) Run(msg *sock.Msg) ([]byte, error) { +func (n *Query) Run(msg *sock.Msg) ([]byte, error) { if !msg.HasNext() { return nil, &copies.ErrMissingArguments{} } @@ -41,7 +41,6 @@ func (n *Query) desktops(msg *sock.Msg) ([]byte, error) { o := new(bytes.Buffer) o.WriteString("hi there") return o.Bytes(), nil - } func (n *Query) parse(msg *sock.Msg) (bool, error) { @@ -68,11 +67,3 @@ func (n *Query) parse(msg *sock.Msg) (bool, error) { } return msg.HasNext(), nil } - -func (n *Query) readCommand(msg *sock.Msg, c string) (bool, error) { - if n.Command == "" { - n.Command = c - return msg.HasNext(), nil - } - return false, fmt.Errorf("multiple commands given") -} diff --git a/src/handler/domains/wm.go b/src/handler/domains/wm.go new file mode 100644 index 0000000..7919411 --- /dev/null +++ b/src/handler/domains/wm.go @@ -0,0 +1,58 @@ +package domains + +import ( + "fmt" + + "tuxpa.in/t/wm/src/copies" + "tuxpa.in/t/wm/src/sock" +) + +type Wm struct { + UseNames bool + + inject + + cmd +} + +func (n *Wm) Run(msg *sock.Msg) ([]byte, error) { + if !msg.HasNext() { + return nil, &copies.ErrMissingArguments{} + } + for { + ok, err := n.parse(msg) + if err != nil { + return nil, err + } + if !ok { + break + } + } + + return nil, nil +} + +func (n *Wm) parse(msg *sock.Msg) (bool, error) { + if !msg.HasNext() { + return false, &copies.ErrMissingArguments{} + } + arg := msg.Next() + switch arg { + case "--desktop", "-d": + case "--desktops", "-D": + return n.readCommand(msg, "desktops") + case "--monitor", "-m": + case "--monitors", "-M": + return n.readCommand(msg, "monitors") + case "--names": + n.UseNames = true + case "--node", "-n": + case "--nodes", "-N": + return n.readCommand(msg, "nodes") + case "--tree", "-T": + return n.readCommand(msg, "tree") + default: + return false, fmt.Errorf(`unknown option: '%s'`, arg) + } + return msg.HasNext(), nil +} diff --git a/src/sock/server.go b/src/sock/server.go index 4d6c355..9059b39 100644 --- a/src/sock/server.go +++ b/src/sock/server.go @@ -4,15 +4,11 @@ import ( "bytes" "fmt" "net" - "os" "strings" - - "github.com/jezek/xgbutil" ) type SockListener struct { - xgb *xgbutil.XUtil - l *net.UnixListener + l *net.UnixListener ch chan *Msg } @@ -20,32 +16,10 @@ type SockListener struct { func (s *SockListener) Msg() <-chan *Msg { return s.ch } -func (s *SockListener) X11() *xgbutil.XUtil { - return s.xgb -} -func getUnixAddr(xc *xgbutil.XUtil, path string) *net.UnixAddr { - if path == "" { - path = os.Getenv(SOCKET_ENV_VAR) - } - if path == "" { - path = fmt.Sprintf(SOCKET_PATH_TPL, "", xc.Conn().DisplayNumber, xc.Conn().DefaultScreen) - } - addr, err := net.ResolveUnixAddr("unix", path) - if err != nil { - panic(err) - } - return addr -} -func Server(path string) (*SockListener, error) { +func Server(addr *net.UnixAddr) (*SockListener, error) { s := &SockListener{} s.ch = make(chan *Msg, 1) - xc, err := xgbutil.NewConn() - if err != nil { - return nil, err - } - s.xgb = xc - addr := getUnixAddr(xc, path) conn, err := net.ListenUnix("unix", addr) if err != nil { return nil, err