diff --git a/.goreleaser.yaml b/.goreleaser.yaml deleted file mode 100644 index 69620bc..0000000 --- a/.goreleaser.yaml +++ /dev/null @@ -1,65 +0,0 @@ -# vim: set ts=2 sw=2 tw=0 fo=cnqoj - -version: 2 - -before: - hooks: - - go mod tidy - -gitea_urls: - api: https://codeberg.org/api/v1 - download: https://codeberg.org - -builds: - - env: - - CGO_ENABLED=0 - goos: - - linux - - freebsd - -archives: - - formats: [tar.gz] - # this name template makes the OS and Arch compatible with the results of `uname`. - name_template: >- - {{ .ProjectName }}_ - {{- title .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} - {{- if .Arm }}v{{ .Arm }}{{ end }}_{{ .Tag }} - # use zip for windows archives - format_overrides: - - goos: windows - formats: [zip] - - goos: linux - formats: [tar.gz,binary] - files: - - src: "*.md" - strip_parent: true - - src: Makefile.dist - dst: Makefile - wrap_in_directory: true - -changelog: - sort: asc - filters: - exclude: - - "^docs:" - - "^test:" - groups: - - title: Improved - regexp: '^.*?(feat|add|new)(\([[:word:]]+\))??!?:.+$' - order: 0 - - title: Fixed - regexp: '^.*?(bug|fix)(\([[:word:]]+\))??!?:.+$' - order: 1 - - title: Changed - order: 999 - -release: - header: "# Release Notes" - footer: >- - - --- - - Full Changelog: [{{ .PreviousTag }}...{{ .Tag }}](https://codeberg.org/scip/swaycycle/compare/{{ .PreviousTag }}...{{ .Tag }}) diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml deleted file mode 100644 index e20cc6f..0000000 --- a/.woodpecker/build.yaml +++ /dev/null @@ -1,36 +0,0 @@ -matrix: - platform: - - linux/amd64 - goversion: - - 1.24 - -labels: - platform: ${platform} - -steps: - build: - when: - event: [push] - image: golang:${goversion} - commands: - - go get - - go build - - linter: - when: - 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 - - golangci-lint --version - - golangci-lint run ./... - depends_on: [build] - - test: - when: - event: [push] - image: golang:${goversion} - commands: - - go get - - go test -v -cover - depends_on: [build,linter] diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml deleted file mode 100644 index 916c008..0000000 --- a/.woodpecker/release.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# build release - -labels: - platform: linux/amd64 - -steps: - goreleaser: - image: goreleaser/goreleaser - when: - event: [tag] - environment: - GITEA_TOKEN: - from_secret: DEPLOY_TOKEN - commands: - - goreleaser release --clean --verbose diff --git a/Makefile b/Makefile deleted file mode 100644 index a3539bc..0000000 --- a/Makefile +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright © 2025 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 -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -# -# no need to modify anything below -tool = swaycycle -VERSION = $(shell grep VERSION main.go | head -1 | cut -d '"' -f2) -archs = darwin freebsd linux -PREFIX = /usr/local -UID = root -GID = 0 -HAVE_POD := $(shell pod2text -h 2>/dev/null) - -all: buildlocal - - -buildlocal: - CGO_LDFLAGS='-static' go build -tags osusergo,netgo -ldflags "-extldflags=-static" -o $(tool) - -install: buildlocal - install -d -o $(UID) -g $(GID) $(PREFIX)/bin - install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1 - install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/ - install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/ - -clean: - rm -rf $(tool) coverage.out testdata t/out - -test: clean - mkdir -p t/out - go test ./... $(ARGS) - -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 - -testfuzzy: clean - go test -fuzz ./... $(ARGS) - -singletest: - @echo "Call like this: make singletest TEST=TestPrepareColumns ARGS=-v" - go test -run $(TEST) $(ARGS) - -cover-report: - go test ./... -cover -coverprofile=coverage.out - go tool cover -html=coverage.out - -goupdate: - go get -t -u=patch ./... - -buildall: - ./mkrel.sh $(tool) $(VERSION) - -release: - gh release create v$(VERSION) --generate-notes - -show-versions: buildlocal - @echo "### swaycycle version:" - @./swaycycle -V - - @echo - @echo "### go module versions:" - @go list -m all - - @echo - @echo "### go version used for building:" - @grep -m 1 go go.mod - -# lint: -# golangci-lint run -p bugs -p unused diff --git a/Makefile.dist b/Makefile.dist deleted file mode 100644 index 55d2f38..0000000 --- a/Makefile.dist +++ /dev/null @@ -1,18 +0,0 @@ -# -*-make-*- - -.PHONY: install all - -tool = rpn -PREFIX = /usr/local -UID = root -GID = 0 - -all: - @echo "Type 'sudo make install' to install the tool." - @echo "To change prefix, type 'sudo make install PREFIX=/opt'" - -install: - install -d -o $(UID) -g $(GID) $(PREFIX)/bin - install -d -o $(UID) -g $(GID) $(PREFIX)/share/doc - install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/ - install -o $(UID) -g $(GID) -m 444 *.md $(PREFIX)/share/doc/ diff --git a/README.md b/README.md index 538323e..223e7d8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ [![status-badge](https://ci.codeberg.org/api/badges/15562/status.svg)](https://ci.codeberg.org/repos/15562) [![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://codeberg.org/scip/swaycycle/raw/branch/main/LICENSE) -[![Go Report Card](https://goreportcard.com/badge/codeberg.org/scip/epuppy)](https://goreportcard.com/report/codeberg.org/scip/swaycycle) +[![Go Report Card](https://goreportcard.com/badge/codeberg.org/scip/epuppy)](https://goreportcard.com/report/codeberg.org/scip/swaycycleg) + +> [!CAUTION] +> This software is now being maintained on [Codeberg](https://codeberg.org/scip/swaycycle/). # swaycycle diff --git a/go.mod b/go.mod deleted file mode 100644 index b9f0774..0000000 --- a/go.mod +++ /dev/null @@ -1,16 +0,0 @@ -module swaycycle - -go 1.23 - -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 - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index ed36845..0000000 --- a/go.sum +++ /dev/null @@ -1,25 +0,0 @@ -github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= -github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -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/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-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/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/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= -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 deleted file mode 100644 index 8b18307..0000000 --- a/main.go +++ /dev/null @@ -1,358 +0,0 @@ -/* -Copyright © 2025 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 -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package main - -import ( - "errors" - "fmt" - "io" - "log" - "log/slog" - "os" - "runtime/debug" - "sort" - - "github.com/lmittmann/tint" - "github.com/mattn/go-isatty" - "github.com/tlinden/i3ipc" - "github.com/tlinden/yadu" - - flag "github.com/spf13/pflag" -) - -const ( - root = iota + 1 - output - workspace - con - floating - - 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 -) - -var ( - Visibles = []*i3ipc.Node{} - CurrentWorkspace = "" - Previous = false - Debug = false - Dumptree = false - Dumpvisibles = false - Version = false - Verbose = false - Notswitch = false - Showhelp = false - Logfile = "" -) - -const Usage string = `This is swaycycle - cycle focus through all visible windows on a sway workspace. - -Usage: swaycycle [-vdDn] [-l ] - -Options: - -p, --prev cycle backward - -n, --no-switch do not switch windows - -d, --debug enable debugging - -D, --dump dump the sway tree (needs -d as well) - --dump-visibles dump a list of visible windows on current workspace (needs -d) - -l, --logfile string write output to logfile - -v, --version show program version - -Copyleft (L) 2025 Thomas von Dein. -Licensed under the terms of the GNU GPL version 3.` - -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(&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") - - flag.StringVarP(&Logfile, "logfile", "l", "", "write output to logfile") - flag.Parse() - - if Version { - fmt.Printf("This is swaycycle version %s\n", VERSION) - os.Exit(0) - } - - if Showhelp { - fmt.Println(Usage) - os.Exit(0) - } - - // setup logging - if Logfile != "" { - file, err := os.OpenFile(Logfile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) - if err != nil { - log.Fatalf("Failed to open logfile %s: %s", Logfile, err) - } - defer func() { - if err := file.Close(); err != nil { - log.Fatalf("failed to close log file: %s", err) - } - }() - setupLogging(file) - } else { - setupLogging(os.Stdout) - } - - // connect to sway unix socket - ipc := i3ipc.NewI3ipc() - - err := ipc.Connect() - if err != nil { - log.Fatal(err) - } - defer ipc.Close() - - sway, err := ipc.GetTree() - if err != nil { - log.Fatal(err) - } - - // traverse the tree and find visible windows - if err := processJSON(sway); err != nil { - log.Fatalf("%s", err) - } - - if len(Visibles) == 0 { - os.Exit(0) - } - - id := 0 - if Previous { - id = findPrevWindow() - slog.Debug("findPrevWindow", "nextid", id) - } else { - id = findNextWindow() - slog.Debug("findNextWindow", "nextid", id) - } - - if id > 0 && !Notswitch { - if err := switchFocus(id, ipc); err != nil { - log.Fatalf("%s", err) - } - } -} - -// 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 { - if !istype(sway, root) && len(sway.Nodes) == 0 { - return errors.New("invalid or empty JSON structure") - } - - if Dumptree { - slog.Debug("processed sway tree", "sway", sway) - } - - for _, node := range sway.Nodes { - if node.Current_workspace != "" { - // this is an output node containing the current workspace - CurrentWorkspace = node.Current_workspace - recurseNodes(node.Nodes) - break - } - } - - if Dumpvisibles { - dumpVisibles() - } - - return nil -} - -// find the next window after the one with current focus. if the last -// one has focus, return the first -func findNextWindow() int { - if len(Visibles) == 0 { - return 0 - } - - seenfocused := false - - for _, node := range Visibles { - if node.Focused { - seenfocused = true - continue - } - - if seenfocused { - return node.Id - } - } - - if seenfocused { - return Visibles[0].Id - } - - return 0 -} - -func findPrevWindow() int { - vislen := len(Visibles) - if vislen == 0 { - return 0 - } - - prevnode := Visibles[vislen-1].Id - - for _, node := range Visibles { - if node.Focused { - return prevnode - } - prevnode = node.Id - } - - return 0 -} - -// actually switch focus using a swaymsg command -func switchFocus(id int, ipc *i3ipc.I3ipc) error { - responses, err := ipc.RunContainerCommand(id, "focus") - if err != nil { - log.Fatalf("failed to send focus command to container %d: %s (%s)", - id, responses[0].Error, err) - } - - slog.Info("switched focus", "con_id", id) - - return nil -} - -// iterate recursively over given node list extracting visible windows -func recurseNodes(nodes []*i3ipc.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. - FloatVis := node.FloatingNodes - sort.Slice(FloatVis, func(i, j int) bool { - return FloatVis[i].Id < FloatVis[j].Id - }) - //now we can handle nodes and floating_nodes identical - node.Nodes = append(node.Nodes, FloatVis...) - recurseNodes(node.Nodes) - return - } - - // ignore other workspaces - continue - } - - // the first nodes seen are workspaces, so if we see a con - // node, we are already inside the current workspace - if (istype(node, con) || istype(node, floating)) && - (node.Window > 0 || node.X11Window != "") { - Visibles = append(Visibles, node) - } else { - recurseNodes(node.Nodes) - } - } -} - -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) - } - - slog.Debug("visible windows on current workspace", "visibles", windows) -} - -// we use line wise logging, unless debugging is enabled -func setupLogging(output io.Writer) { - logLevel := &slog.LevelVar{} - - if !Debug { - // default logging - opts := &tint.Options{ - Level: logLevel, - AddSource: false, - NoColor: IsNoTty(), - } - - logLevel.Set(slog.LevelInfo) - - handler := tint.NewHandler(output, opts) - logger := slog.New(handler) - - slog.SetDefault(logger) - } else { - // we're using a more verbose logger in debug mode - buildInfo, _ := debug.ReadBuildInfo() - opts := &yadu.Options{ - Level: logLevel, - AddSource: true, - } - - logLevel.Set(slog.LevelDebug) - - handler := yadu.NewHandler(output, opts) - debuglogger := slog.New(handler).With( - slog.Group("program_info", - slog.Int("pid", os.Getpid()), - slog.String("go_version", buildInfo.GoVersion), - ), - ) - - slog.SetDefault(debuglogger) - } -} - -// little helper to distinguish sway tree node types -func istype(nd *i3ipc.Node, which int) bool { - switch nd.Type { - case "root": - return which == root - case "output": - return which == output - case "workspace": - return which == workspace - case "con": - return which == con - case "floating_con": - return which == floating - } - - return false -} - -// returns TRUE if stdout is NOT a tty or windows -func IsNoTty() bool { - if !isatty.IsTerminal(os.Stdout.Fd()) { - return true - } - - // it is a tty - return false -} diff --git a/mkrel.sh b/mkrel.sh deleted file mode 100755 index de7113b..0000000 --- a/mkrel.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash - -# Copyright © 2025 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 -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -# get list with: go tool dist list -DIST="darwin/amd64 -freebsd/amd64 -linux/amd64 -freebsd/arm64 -linux/arm64" - -tool="$1" -version="$2" - -if test -z "$version"; then - echo "Usage: $0 " - exit 1 -fi - -rm -rf releases -mkdir -p releases - - -for D in $DIST; do - os=${D/\/*/} - arch=${D/*\//} - binfile="releases/${tool}-${os}-${arch}-${version}" - - if test "$os" = "windows"; then - binfile="${binfile}.exe" - fi - - tardir="${tool}-${os}-${arch}-${version}" - tarfile="releases/${tool}-${os}-${arch}-${version}.tar.gz" - set -x - GOOS=${os} GOARCH=${arch} go build -tags osusergo,netgo -ldflags "-extldflags=-static" -o ${binfile} - mkdir -p ${tardir} - cp ${binfile} README.md LICENSE ${tardir}/ - echo 'tool = swaycycle -PREFIX = /usr/local -UID = root -GID = 0 - -install: - install -d -o $(UID) -g $(GID) $(PREFIX)/bin - install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1 - install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/ - install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/' > ${tardir}/Makefile - tar cpzf ${tarfile} ${tardir} - sha256sum ${binfile} | cut -d' ' -f1 > ${binfile}.sha256 - sha256sum ${tarfile} | cut -d' ' -f1 > ${tarfile}.sha256 - rm -rf ${tardir} - set +x -done -