From ce6d836234acecc0bae9a2f10f79e97cf292f546 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Wed, 20 Aug 2025 22:25:40 +0200 Subject: [PATCH] add descratcher example --- README.md | 5 +- _examples/descratch/descratcher-rofi.sh | 5 ++ _examples/descratch/main.go | 106 ++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100755 _examples/descratch/descratcher-rofi.sh create mode 100644 _examples/descratch/main.go diff --git a/README.md b/README.md index be5cd85..7f9351f 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,10 @@ func main() { } ``` -Also take a look into the **_examples** folder for more examples. +Also take a look into the **_examples** folder for more examples. For +a more comprehensive and practical example look at the +`_examples/descratcher` program which you can use to selectively +retrieve a window from the scratchpad. You may take a look at the [tool swaycycle](https://github.com/tlinden/swaycycle) which is using this module. diff --git a/_examples/descratch/descratcher-rofi.sh b/_examples/descratch/descratcher-rofi.sh new file mode 100755 index 0000000..5a7fede --- /dev/null +++ b/_examples/descratch/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/_examples/descratch/main.go b/_examples/descratch/main.go new file mode 100644 index 0000000..a293de8 --- /dev/null +++ b/_examples/descratch/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 +}