From ce5fa1a020d4ba4cd683b75e5440440fb09686d7 Mon Sep 17 00:00:00 2001 From: "T. von Dein" Date: Sat, 22 Aug 2026 20:55:41 +0200 Subject: [PATCH] update swayipc, go, add linter cfg, satisfy linter (#2) --- .golangci.yml | 104 +++++++++++++++++++++++++++++++++++++++++ .woodpecker/build.yaml | 6 ++- Makefile | 5 +- go.mod | 20 ++++---- go.sum | 20 ++++++-- main.go | 97 +++++++++++++++++++++----------------- 6 files changed, 188 insertions(+), 64 deletions(-) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..ac03f44 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,104 @@ +version: "2" + +linters: + enable: + - errcheck + - govet + - ineffassign + - staticcheck + - unused + - arangolint + - asasalint + - asciicheck + - bidichk + - bodyclose + - canonicalheader + - clickhouselint + - containedctx + - copyloopvar + - cyclop + - decorder + - dogsled + - dupword + - durationcheck + - embeddedstructfieldcheck + - errchkjson + - errname + - exhaustive + - exptostd + - fatcontext + - funcorder + - funlen + - ginkgolinter + - gocheckcompilerdirectives + - gochecknoinits + - gochecksumtype + - gocritic + - gocyclo + - godoclint + - goheader + - gomoddirectives + - gomodguard_v2 + - goprintffuncname + - gosmopolitan + - grouper + - iface + - importas + - inamedparam + - interfacebloat + - intrange + - iotamixing + - lll + - loggercheck + - makezero + - misspell + - modernize + - nakedret + - nestif + - nilerr + - nilnesserr + - nlreturn + - nonamedreturns + - nosprintfhostport + - paralleltest + - perfsprint + - prealloc + - promlinter + - protogetter + - reassign + - recvcheck + - rowserrcheck + - sloglint + - spancheck + - sqlclosecheck + - tagalign + - testableexamples + - testifylint + - testpackage + - thelper + - tparallel + - unconvert + - unparam + - unqueryvet + - usestdlibvars + - usetesting + - varnamelen + - wastedassign + - whitespace + - wsl_v5 + - zerologlint + + settings: + varnamelen: + ignore-names: + - err + - wg + - mu + - ts + - to + - es + - op + - fd + - id + - fn + diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml index e20cc6f..9b8393b 100644 --- a/.woodpecker/build.yaml +++ b/.woodpecker/build.yaml @@ -2,7 +2,9 @@ matrix: platform: - linux/amd64 goversion: - - 1.24 + - 1.26 + lintversion: + - v2.12.2 labels: platform: ${platform} @@ -21,7 +23,7 @@ steps: event: [push] image: golang:${goversion} commands: - - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0 + - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin ${lintversion} - golangci-lint --version - golangci-lint run ./... depends_on: [build] diff --git a/Makefile b/Makefile index a3539bc..9254d74 100644 --- a/Makefile +++ b/Makefile @@ -46,10 +46,7 @@ test: clean testlint: test lint lint: - golangci-lint run - -lint-full: - golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest + golangci-lint run --show-stats=false testfuzzy: clean go test -fuzz ./... $(ARGS) diff --git a/go.mod b/go.mod index b9f0774..9206140 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,18 @@ module swaycycle -go 1.23 +go 1.26 + +require ( + codeberg.org/scip/swayipc/v2 v2.1.0 + github.com/lmittmann/tint v1.1.3 + github.com/mattn/go-isatty v0.0.24 + github.com/spf13/pflag v1.0.10 + github.com/tlinden/yadu v0.1.3 +) require ( - github.com/alecthomas/repr v0.5.1 // indirect github.com/fatih/color v1.16.0 // indirect - github.com/lmittmann/tint v1.1.2 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/spf13/pflag v1.0.7 // indirect - github.com/tlinden/i3ipc v0.0.0-20250815101608-4f7e27528be3 // indirect - github.com/tlinden/yadu v0.1.3 // indirect - golang.org/x/sys v0.14.0 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect + golang.org/x/sys v0.29.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ed36845..8a95376 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,35 @@ -github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= -github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +codeberg.org/scip/swayipc/v2 v2.1.0 h1:tBiOBOUFDOA38+acZwx4BO5Yo/VVwxbqkL+R2/zmW6c= +codeberg.org/scip/swayipc/v2 v2.1.0/go.mod h1:cqicjgMgPS7tGTJgjqjSMvnLeMA+J3to/YnufcysCns= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I= +github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/tlinden/i3ipc v0.0.0-20250815101608-4f7e27528be3 h1:/kIZO4852sAVemXtqnsBid0r4Q1h87jDwHa8f7v1h5I= -github.com/tlinden/i3ipc v0.0.0-20250815101608-4f7e27528be3/go.mod h1:mc0toDHmgqgX6FpE69U5yMPnHuLTdekHRslSLDp8xSE= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/tlinden/swayipc v0.3.0 h1:hGNWeEZUZIHfeP+MxpAKsUzPf3YSJ0FYX2XEu/yqNXA= +github.com/tlinden/swayipc v0.3.0/go.mod h1:JwlMIC7eBwV8soCt2UDqlAyBudobLo07ZvepIA0irY8= github.com/tlinden/yadu v0.1.3 h1:5cRCUmj+l5yvlM2irtpFBIJwVV2DPEgYSaWvF19FtcY= github.com/tlinden/yadu v0.1.3/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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/main.go b/main.go index 8b18307..2b868a1 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,5 @@ /* -Copyright © 2025 Thomas von Dein +Copyright © 2025-2026 Thomas von Dein This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,9 +27,9 @@ import ( "runtime/debug" "sort" + "codeberg.org/scip/swayipc/v2" "github.com/lmittmann/tint" "github.com/mattn/go-isatty" - "github.com/tlinden/i3ipc" "github.com/tlinden/yadu" flag "github.com/spf13/pflag" @@ -44,18 +44,11 @@ const ( LevelNotice = slog.Level(2) - VERSION = "v0.3.1" - - IPC_HEADER_SIZE = 14 - IPC_MAGIC = "i3-ipc" - - // message types - IPC_GET_TREE = 4 - IPC_RUN_COMMAND = 0 + VERSION = "v0.3.2" ) var ( - Visibles = []*i3ipc.Node{} + Visibles = []*swayipc.Node{} CurrentWorkspace = "" Previous = false Debug = false @@ -88,7 +81,7 @@ func main() { flag.BoolVarP(&Previous, "prev", "p", false, "cycle backward") flag.BoolVarP(&Debug, "debug", "d", false, "enable debugging") flag.BoolVarP(&Dumptree, "dump", "D", false, "dump the sway tree (needs -d as well)") - flag.BoolVarP(&Dumpvisibles, "dump-visibles", "", false, "dump a list of visible windows on current workspace (needs -d)") + flag.BoolVarP(&Dumpvisibles, "dump-visibles", "", false, "dump the sway tree (needs -d as well)") flag.BoolVarP(&Notswitch, "no-switch", "n", false, "do not switch windows") flag.BoolVarP(&Version, "version", "v", false, "show program version") flag.BoolVarP(&Showhelp, "help", "h", Showhelp, "show help") @@ -117,35 +110,46 @@ func main() { log.Fatalf("failed to close log file: %s", err) } }() + setupLogging(file) } else { setupLogging(os.Stdout) } + if err := cycle(); err != nil { + log.Fatal(err) // nolint:gocritic + } +} + +func cycle() error { // connect to sway unix socket - ipc := i3ipc.NewI3ipc() + ipc := swayipc.NewSwayIPC() err := ipc.Connect() if err != nil { - log.Fatal(err) + return err } - defer ipc.Close() + defer func() { + if err := ipc.Close(); err != nil { + log.Fatal(err) + } + }() sway, err := ipc.GetTree() if err != nil { - log.Fatal(err) + return err } // traverse the tree and find visible windows if err := processJSON(sway); err != nil { - log.Fatalf("%s", err) + return err } if len(Visibles) == 0 { - os.Exit(0) + return nil } - id := 0 + var id int if Previous { id = findPrevWindow() slog.Debug("findPrevWindow", "nextid", id) @@ -156,14 +160,16 @@ func main() { if id > 0 && !Notswitch { if err := switchFocus(id, ipc); err != nil { - log.Fatalf("%s", err) + return err } } + + return nil } // get into the sway tree, determine current workspace and extract all // its visible windows, store them in the global var Visibles -func processJSON(sway *i3ipc.Node) error { +func processJSON(sway *swayipc.Node) error { if !istype(sway, root) && len(sway.Nodes) == 0 { return errors.New("invalid or empty JSON structure") } @@ -173,10 +179,11 @@ func processJSON(sway *i3ipc.Node) error { } for _, node := range sway.Nodes { - if node.Current_workspace != "" { + if node.CurrentWorkspace != "" { // this is an output node containing the current workspace - CurrentWorkspace = node.Current_workspace + CurrentWorkspace = node.CurrentWorkspace recurseNodes(node.Nodes) + break } } @@ -200,16 +207,17 @@ func findNextWindow() int { for _, node := range Visibles { if node.Focused { seenfocused = true + continue } if seenfocused { - return node.Id + return node.ID } } if seenfocused { - return Visibles[0].Id + return Visibles[0].ID } return 0 @@ -221,23 +229,24 @@ func findPrevWindow() int { return 0 } - prevnode := Visibles[vislen-1].Id + prevnode := Visibles[vislen-1].ID for _, node := range Visibles { if node.Focused { return prevnode } - prevnode = node.Id + + prevnode = node.ID } return 0 } // actually switch focus using a swaymsg command -func switchFocus(id int, ipc *i3ipc.I3ipc) error { +func switchFocus(id int, ipc *swayipc.SwayIPC) error { responses, err := ipc.RunContainerCommand(id, "focus") if err != nil { - log.Fatalf("failed to send focus command to container %d: %s (%s)", + return fmt.Errorf("failed to send focus command to container %d: %s (%s)", id, responses[0].Error, err) } @@ -247,20 +256,20 @@ func switchFocus(id int, ipc *i3ipc.I3ipc) error { } // iterate recursively over given node list extracting visible windows -func recurseNodes(nodes []*i3ipc.Node) { +func recurseNodes(nodes []*swayipc.Node) { for _, node := range nodes { - if istype(node, workspace) { if node.Name == CurrentWorkspace { - //floating_nodes need to be sorted because - //order changes each time they are focused. + // floating_nodes need to be sorted because + // order changes each time they are focused. FloatVis := node.FloatingNodes sort.Slice(FloatVis, func(i, j int) bool { - return FloatVis[i].Id < FloatVis[j].Id + return FloatVis[i].ID < FloatVis[j].ID }) - //now we can handle nodes and floating_nodes identical + // now we can handle nodes and floating_nodes identical node.Nodes = append(node.Nodes, FloatVis...) recurseNodes(node.Nodes) + return } @@ -283,7 +292,7 @@ func dumpVisibles() { windows := make([]string, len(Visibles)) for idx, node := range Visibles { - windows[idx] = fmt.Sprintf("id: %02d, focus: %5t, name: %s", node.Id, node.Focused, node.Name) + windows[idx] = fmt.Sprintf("id: %02d, focus: %5t, name: %s", node.ID, node.Focused, node.Name) } slog.Debug("visible windows on current workspace", "visibles", windows) @@ -329,25 +338,25 @@ func setupLogging(output io.Writer) { } } -// little helper to distinguish sway tree node types -func istype(nd *i3ipc.Node, which int) bool { +// istype is a little helper to distinguish sway tree node types +func istype(nd *swayipc.Node, which int) bool { switch nd.Type { case "root": - return which == root + return which == swayipc.NodeTypeRoot case "output": - return which == output + return which == swayipc.NodeTypeOutput case "workspace": - return which == workspace + return which == swayipc.NodeTypeWorkspace case "con": - return which == con + return which == swayipc.NodeTypeCon case "floating_con": - return which == floating + return which == swayipc.NodeTypeFloating } return false } -// returns TRUE if stdout is NOT a tty or windows +// IsNoTty returns TRUE if stdout is NOT a tty or windows func IsNoTty() bool { if !isatty.IsTerminal(os.Stdout.Fd()) { return true