diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..99f12d2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "monthly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..c18b40f --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,87 @@ +name: build-release +on: + push: + tags: + - "v*.*.*" + +jobs: + release: + name: Build Release Assets + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.23.5 + + - name: Build the executables + run: ./mkrel.sh descratch ${{ 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}} diff --git a/README.md b/README.md new file mode 100644 index 0000000..d40ce4f --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# sway-descratch + +This is a more practical example for [swayipc module +usage](https://github.com/TLINDEN/swayipc). With sway you can move +windows to a "scratchpad", i.e. like iconify it. There may be an +official way to get back such windows, but I didn't find a good +one. There's the "scratchpad show" command, but it doesn't allow you +to select a window, it just shows the next one (and it keeps it in the +floating state). + +So, this example program lists all windows currently garaged on the +scratchpad. When called with a windows id, it gets back the window +to the current workspace and gives it focus - thus descratching it. + +To add comfort to the process I added a small script which you can +use as a ui to it. It uses rofi which makes a handy ui. To use it, +compile descratch with "go build", copy the descratch binary to +some location within your $PATH and run the script. + +## Install + +Copy the binary and rofi script for your platform to your `$PATH`. Add +something like this to your sway config: + +```default +# mv container to scratchpad +bindsym $mod+k move scratchpad + +# interactively get container back to current workspace +bindsym $mod+b exec descratcher-rofi.sh +``` + +## Getting help + +Although I'm happy to hear from sway-descratch users in private email, that's the +best way for me to forget to do something. + +In order to report a bug, unexpected behavior, feature requests or to +submit a patch, please open an issue on github: +https://github.com/tlinden/sway-descratch/issues. + +## See also + +- [swayipc golang sway binding](https://github.com/tlinden/swayipc) +- [sway-ipc(7) manpage](https://www.mankier.com/7/sway-ipc) +- [swaywm](https://github.com/swaywm/sway/) +- [swayfx](https://github.com/WillPower3309/swayfx) + +## Copyright and license + +This software is licensed under the GNU GENERAL PUBLIC LICENSE version 3. + +## Authors + +T.v.Dein + +## Project homepage + +https://github.com/tlinden/sway-descratch + +## Copyright and License + +Licensed under the GNU GENERAL PUBLIC LICENSE version 3. + +## Author + +T.v.Dein diff --git a/descratcher-rofi.sh b/descratcher-rofi.sh new file mode 100755 index 0000000..5a7fede --- /dev/null +++ b/descratcher-rofi.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +descratch \ + | rofi -matching fuzzy -dmenu -p "Select window from scratchpad to show" \ + | cut -d' ' -f1 | xargs -I{} --no-run-if-empty "descratch" {} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2e279f0 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module sway-descratch + +go 1.23.5 diff --git a/main.go b/main.go new file mode 100644 index 0000000..58b2783 --- /dev/null +++ b/main.go @@ -0,0 +1,106 @@ +package main + +/* + This is a more practical example. With sway you can move windows to + a "scratchpad", i.e. like iconify it. There may be an official way + to get back such windows, but I didn't find a good one. There's the + "scratchpad show" command, but it doesn't allow you to select a + window, it just shows the next one (and it keeps it in the floating + state). + + So, this example program lists all windows currently garaged on the + scratchpad. When called with a windows id, it gets back the window + to the current workspace and gives it focus - thus descratching it. + + To add comfort to the process I added a small script which you can + use as a ui to it. It uses rofi which makes a handy ui. To use it, + compile descratch with "go build", copy the descratch binary to + some location within your $PATH and run the script. +*/ + +import ( + "fmt" + "log" + "os" + "strconv" + + "github.com/alecthomas/repr" + "github.com/tlinden/swayipc" +) + +func main() { + // we need a session to sway via IPC + ipc := swayipc.NewSwayIPC() + + err := ipc.Connect() + if err != nil { + log.Fatal(err) + } + defer ipc.Close() + + // first, retrieve the whole sway root node + root, err := ipc.GetTree() + if err != nil { + log.Fatal(err) + } + + // get the hidden scratchpad workspace + scratch := getScratch(root) + + if len(os.Args) > 1 { + // called with an arg, consider it to be a container id + id, err := strconv.Atoi(os.Args[1]) + if err != nil { + log.Fatalf("failed to convert arg %s to integer: %w", os.Args[1], err) + } + + // switch to it + retrieveWindow(ipc, scratch, id) + } else { + // no args, just list scratched windows + listScratchedContainer(scratch) + } +} + +func retrieveWindow(ipc *swayipc.SwayIPC, scratch *swayipc.Node, id int) { + // make sure the id exists + var exists bool + for _, con := range scratch.FloatingNodes { + if con.Id == id { + exists = true + } + } + + if !exists { + log.Fatalf("no window with id %d exists on the scratchpad", id) + } + + // scratched windows are floating, so we move it to current + // workspace, disable the floating state and switch focus to it + responses, err := ipc.RunContainerCommand(id, "move workspace current", "floating toggle", "focus") + if err != nil { + repr.Println(responses) + log.Fatal(err) + } + +} + +func listScratchedContainer(scratch *swayipc.Node) { + // list the windows + for _, con := range scratch.FloatingNodes { + fmt.Printf("%d %s\n", con.Id, con.Name) + } +} + +func getScratch(root *swayipc.Node) *swayipc.Node { + // the root node only has output nodes, we iterate over them and + // look for the internal one named__i3. This in turn only has one + // workspace, the scratchpad workspace, which we return. + for _, output := range root.Nodes { + if output.Name == "__i3" { + return output.Nodes[0] + } + } + + return nil +} diff --git a/mkrel.sh b/mkrel.sh new file mode 100755 index 0000000..66f3381 --- /dev/null +++ b/mkrel.sh @@ -0,0 +1,69 @@ +#!/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 = descratch +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 +