2025-08-21 13:12:03 +02:00
|
|
|
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"
|
|
|
|
|
|
2025-12-05 21:12:33 +01:00
|
|
|
"codeberg.org/scip/swayipc/v2"
|
2025-08-21 13:12:03 +02:00
|
|
|
"github.com/alecthomas/repr"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
// we need a session to sway via IPC
|
|
|
|
|
ipc := swayipc.NewSwayIPC()
|
|
|
|
|
|
|
|
|
|
err := ipc.Connect()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
2025-12-05 21:12:33 +01:00
|
|
|
defer func() {
|
|
|
|
|
if err := ipc.Close(); err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
2025-08-21 13:12:03 +02:00
|
|
|
|
|
|
|
|
// 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 {
|
2025-12-05 21:12:33 +01:00
|
|
|
log.Fatalf("failed to convert arg %s to integer: %s", os.Args[1], err)
|
2025-08-21 13:12:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|