3 Commits

Author SHA1 Message Date
25d5c571ec update swayipc, go, add linter cfg, satisfy linter 2026-08-22 20:51:17 +02:00
60081f9dce update to go 1.26, update to SwayIPC v2 2026-08-22 20:26:53 +02:00
T. von Dein
9703fd4ad0 migrate to codeberg (#1) 2025-11-10 20:14:32 +01:00
11 changed files with 337 additions and 156 deletions

View File

@@ -1,87 +0,0 @@
name: build-release
on:
push:
tags:
- "v*.*.*"
jobs:
release:
name: Build Release Assets
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.23.5
- name: Build the executables
run: ./mkrel.sh swaycycle ${{ github.ref_name}}
- name: List the executables
run: ls -l ./releases
- name: Upload the binaries
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref_name }}
file: ./releases/*
file_glob: true
- name: Build Changelog
id: github_release
uses: mikepenz/release-changelog-builder-action@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
mode: "PR"
configurationJson: |
{
"template": "#{{CHANGELOG}}\n\n**Full Changelog**: #{{RELEASE_DIFF}}",
"pr_template": "- #{{TITLE}} (##{{NUMBER}}) by #{{AUTHOR}}\n#{{BODY}}",
"empty_template": "- no changes",
"categories": [
{
"title": "## New Features",
"labels": ["add", "feature"]
},
{
"title": "## Bug Fixes",
"labels": ["fix", "bug", "revert"]
},
{
"title": "## Documentation Enhancements",
"labels": ["doc"]
},
{
"title": "## Refactoring Efforts",
"labels": ["refactor"]
},
{
"title": "## Miscellaneus Changes",
"labels": []
}
],
"ignore_labels": [
"duplicate", "good first issue", "help wanted", "invalid", "question", "wontfix"
],
"label_extractor": [
{
"pattern": "(.) (.+)",
"target": "$1"
},
{
"pattern": "(.) (.+)",
"target": "$1",
"on_property": "title"
}
]
}
- name: Create Release
uses: softprops/action-gh-release@v2
with:
body: ${{steps.github_release.outputs.changelog}}

104
.golangci.yml Normal file
View File

@@ -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

65
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,65 @@
# 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 }})

38
.woodpecker/build.yaml Normal file
View File

@@ -0,0 +1,38 @@
matrix:
platform:
- linux/amd64
goversion:
- 1.26
lintversion:
- v2.12.2
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 ${lintversion}
- 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]

15
.woodpecker/release.yaml Normal file
View File

@@ -0,0 +1,15 @@
# 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

View File

@@ -46,10 +46,7 @@ test: clean
testlint: test lint testlint: test lint
lint: lint:
golangci-lint run golangci-lint run --show-stats=false
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 testfuzzy: clean
go test -fuzz ./... $(ARGS) go test -fuzz ./... $(ARGS)

18
Makefile.dist Normal file
View File

@@ -0,0 +1,18 @@
# -*-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/

View File

@@ -1,3 +1,7 @@
[![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)
# swaycycle # swaycycle
Cycle through all visible windows on a sway[fx] workspace including Cycle through all visible windows on a sway[fx] workspace including
@@ -9,7 +13,7 @@ the tool to `ALT-tab` and there you go.
## Installation ## Installation
Download the binary for your architecture from the [release Download the binary for your architecture from the [release
page](https://github.com/TLINDEN/swaycycle/releases) and copy to page](https://codeberg.org/scip/swaycycle/releases) and copy to
some location within your `$PATH`. some location within your `$PATH`.
To build the tool from source, checkout the repo and execute To build the tool from source, checkout the repo and execute
@@ -76,7 +80,7 @@ best way for me to forget to do something.
In order to report a bug, unexpected behavior, feature requests or to In order to report a bug, unexpected behavior, feature requests or to
submit a patch, please open an issue on github: submit a patch, please open an issue on github:
https://github.com/tlinden/swaycycle/issues. https://codeberg.org/scip/swaycycle/issues.
## See also ## See also
@@ -94,7 +98,7 @@ T.v.Dein <tom AT vondein DOT org>
## Project homepage ## Project homepage
https://github.com/tlinden/swaycycle https://codeberg.org/scip/swaycycle
## Copyright and License ## Copyright and License

20
go.mod
View File

@@ -1,16 +1,18 @@
module swaycycle 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 ( require (
github.com/alecthomas/repr v0.5.1 // indirect
github.com/fatih/color v1.16.0 // indirect github.com/fatih/color v1.16.0 // indirect
github.com/lmittmann/tint v1.1.2 // indirect github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect golang.org/x/sys v0.29.0 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect
) )

20
go.sum
View File

@@ -1,25 +1,35 @@
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= codeberg.org/scip/swayipc/v2 v2.1.0 h1:tBiOBOUFDOA38+acZwx4BO5Yo/VVwxbqkL+R2/zmW6c=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 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 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 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 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= 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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 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.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 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.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 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/tlinden/i3ipc v0.0.0-20250815101608-4f7e27528be3 h1:/kIZO4852sAVemXtqnsBid0r4Q1h87jDwHa8f7v1h5I= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/tlinden/i3ipc v0.0.0-20250815101608-4f7e27528be3/go.mod h1:mc0toDHmgqgX6FpE69U5yMPnHuLTdekHRslSLDp8xSE= 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 h1:5cRCUmj+l5yvlM2irtpFBIJwVV2DPEgYSaWvF19FtcY=
github.com/tlinden/yadu v0.1.3/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA= 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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= 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.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/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

109
main.go
View File

@@ -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 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 it under the terms of the GNU General Public License as published by
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package main package main
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
@@ -26,9 +27,9 @@ import (
"runtime/debug" "runtime/debug"
"sort" "sort"
"codeberg.org/scip/swayipc/v2"
"github.com/lmittmann/tint" "github.com/lmittmann/tint"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/tlinden/i3ipc"
"github.com/tlinden/yadu" "github.com/tlinden/yadu"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
@@ -43,18 +44,11 @@ const (
LevelNotice = slog.Level(2) LevelNotice = slog.Level(2)
VERSION = "v0.3.1" VERSION = "v0.3.2"
IPC_HEADER_SIZE = 14
IPC_MAGIC = "i3-ipc"
// message types
IPC_GET_TREE = 4
IPC_RUN_COMMAND = 0
) )
var ( var (
Visibles = []*i3ipc.Node{} Visibles = []*swayipc.Node{}
CurrentWorkspace = "" CurrentWorkspace = ""
Previous = false Previous = false
Debug = false Debug = false
@@ -81,14 +75,13 @@ Options:
-v, --version show program version -v, --version show program version
Copyleft (L) 2025 Thomas von Dein. Copyleft (L) 2025 Thomas von Dein.
Licensed under the terms of the GNU GPL version 3. Licensed under the terms of the GNU GPL version 3.`
`
func main() { func main() {
flag.BoolVarP(&Previous, "prev", "p", false, "cycle backward") flag.BoolVarP(&Previous, "prev", "p", false, "cycle backward")
flag.BoolVarP(&Debug, "debug", "d", false, "enable debugging") flag.BoolVarP(&Debug, "debug", "d", false, "enable debugging")
flag.BoolVarP(&Dumptree, "dump", "D", false, "dump the sway tree (needs -d as well)") 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(&Notswitch, "no-switch", "n", false, "do not switch windows")
flag.BoolVarP(&Version, "version", "v", false, "show program version") flag.BoolVarP(&Version, "version", "v", false, "show program version")
flag.BoolVarP(&Showhelp, "help", "h", Showhelp, "show help") flag.BoolVarP(&Showhelp, "help", "h", Showhelp, "show help")
@@ -112,36 +105,51 @@ func main() {
if err != nil { if err != nil {
log.Fatalf("Failed to open logfile %s: %s", Logfile, err) log.Fatalf("Failed to open logfile %s: %s", Logfile, err)
} }
defer file.Close() defer func() {
if err := file.Close(); err != nil {
log.Fatalf("failed to close log file: %s", err)
}
}()
setupLogging(file) setupLogging(file)
} else { } else {
setupLogging(os.Stdout) setupLogging(os.Stdout)
} }
if err := cycle(); err != nil {
log.Fatal(err) // nolint:gocritic
}
}
func cycle() error {
// connect to sway unix socket // connect to sway unix socket
ipc := i3ipc.NewI3ipc() ipc := swayipc.NewSwayIPC()
err := ipc.Connect() err := ipc.Connect()
if err != nil { if err != nil {
return err
}
defer func() {
if err := ipc.Close(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer ipc.Close() }()
sway, err := ipc.GetTree() sway, err := ipc.GetTree()
if err != nil { if err != nil {
log.Fatal(err) return err
} }
// traverse the tree and find visible windows // traverse the tree and find visible windows
if err := processJSON(sway); err != nil { if err := processJSON(sway); err != nil {
log.Fatalf("%s", err) return err
} }
if len(Visibles) == 0 { if len(Visibles) == 0 {
os.Exit(0) return nil
} }
id := 0 var id int
if Previous { if Previous {
id = findPrevWindow() id = findPrevWindow()
slog.Debug("findPrevWindow", "nextid", id) slog.Debug("findPrevWindow", "nextid", id)
@@ -151,15 +159,19 @@ func main() {
} }
if id > 0 && !Notswitch { if id > 0 && !Notswitch {
switchFocus(id, ipc) if err := switchFocus(id, ipc); err != nil {
return err
} }
}
return nil
} }
// get into the sway tree, determine current workspace and extract all // get into the sway tree, determine current workspace and extract all
// its visible windows, store them in the global var Visibles // 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 { if !istype(sway, root) && len(sway.Nodes) == 0 {
return fmt.Errorf("Invalid or empty JSON structure") return errors.New("invalid or empty JSON structure")
} }
if Dumptree { if Dumptree {
@@ -167,10 +179,11 @@ func processJSON(sway *i3ipc.Node) error {
} }
for _, node := range sway.Nodes { for _, node := range sway.Nodes {
if node.Current_workspace != "" { if node.CurrentWorkspace != "" {
// this is an output node containing the current workspace // this is an output node containing the current workspace
CurrentWorkspace = node.Current_workspace CurrentWorkspace = node.CurrentWorkspace
recurseNodes(node.Nodes) recurseNodes(node.Nodes)
break break
} }
} }
@@ -194,16 +207,17 @@ func findNextWindow() int {
for _, node := range Visibles { for _, node := range Visibles {
if node.Focused { if node.Focused {
seenfocused = true seenfocused = true
continue continue
} }
if seenfocused { if seenfocused {
return node.Id return node.ID
} }
} }
if seenfocused { if seenfocused {
return Visibles[0].Id return Visibles[0].ID
} }
return 0 return 0
@@ -215,23 +229,24 @@ func findPrevWindow() int {
return 0 return 0
} }
prevnode := Visibles[vislen-1].Id prevnode := Visibles[vislen-1].ID
for _, node := range Visibles { for _, node := range Visibles {
if node.Focused { if node.Focused {
return prevnode return prevnode
} }
prevnode = node.Id
prevnode = node.ID
} }
return 0 return 0
} }
// actually switch focus using a swaymsg command // 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") responses, err := ipc.RunContainerCommand(id, "focus")
if err != nil { if err != nil {
log.Fatalf("failed to send focus command to container %d: %w (%s)", return fmt.Errorf("failed to send focus command to container %d: %s (%s)",
id, responses[0].Error, err) id, responses[0].Error, err)
} }
@@ -241,20 +256,20 @@ func switchFocus(id int, ipc *i3ipc.I3ipc) error {
} }
// iterate recursively over given node list extracting visible windows // iterate recursively over given node list extracting visible windows
func recurseNodes(nodes []*i3ipc.Node) { func recurseNodes(nodes []*swayipc.Node) {
for _, node := range nodes { for _, node := range nodes {
if istype(node, workspace) { if istype(node, workspace) {
if node.Name == CurrentWorkspace { if node.Name == CurrentWorkspace {
//floating_nodes need to be sorted because // floating_nodes need to be sorted because
//order changes each time they are focused. // order changes each time they are focused.
FloatVis := node.FloatingNodes FloatVis := node.FloatingNodes
sort.Slice(FloatVis, func(i, j int) bool { 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...) node.Nodes = append(node.Nodes, FloatVis...)
recurseNodes(node.Nodes) recurseNodes(node.Nodes)
return return
} }
@@ -277,7 +292,7 @@ func dumpVisibles() {
windows := make([]string, len(Visibles)) windows := make([]string, len(Visibles))
for idx, node := range 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) slog.Debug("visible windows on current workspace", "visibles", windows)
@@ -323,25 +338,25 @@ func setupLogging(output io.Writer) {
} }
} }
// little helper to distinguish sway tree node types // istype is a little helper to distinguish sway tree node types
func istype(nd *i3ipc.Node, which int) bool { func istype(nd *swayipc.Node, which int) bool {
switch nd.Type { switch nd.Type {
case "root": case "root":
return which == root return which == swayipc.NodeTypeRoot
case "output": case "output":
return which == output return which == swayipc.NodeTypeOutput
case "workspace": case "workspace":
return which == workspace return which == swayipc.NodeTypeWorkspace
case "con": case "con":
return which == con return which == swayipc.NodeTypeCon
case "floating_con": case "floating_con":
return which == floating return which == swayipc.NodeTypeFloating
} }
return false 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 { func IsNoTty() bool {
if !isatty.IsTerminal(os.Stdout.Fd()) { if !isatty.IsTerminal(os.Stdout.Fd()) {
return true return true