[closed] Scratchpad (hide) any window, any X11 based wm

I looked for a solution on the forum to send in the scratchpad any window and why not several of them regardless of the window manager even dk, but without success.
So I got to work.

Dependencies: xdo, xorg-xset, xorg-xprop, wmctrl, libnotify, polybar & rofi (optionals)

We need xset to switch on/off a led (number 3) on the keyboard, feel free to change it in the script and enabling ipc feature in your polybar config: enanle-ipc = true

Options to launch the script scratchpad -h (hide), scratchpad -u (unhide the first and so on) and for a rofi menu scratchpad -r

#!/bin/bash

# This script hide and show focused windows whithin you favorite
# window manager or DE by using xdo to proceed
# We can also use rofi to select and show back hidden windows

_Dependencies() {
    DEPS=( "polybar" "rofi" "wmctrl" "xdo" "xprop" "xset" )

    for pkg in "${DEPS[@]}"; do
        [[ -z $(which $pkg) ]] \
        && notify-send "$pkg is not installed...exiting" \
        && exit 127
    done
}

# Place your polybar module in bar-3
BARPID=$(pgrep --full $(wmctrl -m | awk '/Name/{print $2}')-bar-3)

_Indicator() {
    case "$1" in
        off)    xset -led 3
                polybar-msg -p "$BARPID" \
                action "#scratchpad-ipc.hook.0" >/dev/null 2>&1
                notify-send 'nothing else in scratchpad'
        ;;
        on )    xset led 3
                polybar-msg -p "$BARPID" \
                action "#scratchpad-ipc.hook.1" >/dev/null 2>&1
        ;;
    esac
}

TARGET=$(xdo id ${1})
ID_LIST="$XDG_RUNTIME_DIR/scratchpad_hidden.list"
ID_LIST_ALL="$XDG_RUNTIME_DIR/scratchpad_all.list"

case "$1" in
    -h) # sending all existing windows ids/instances/classes in a file to keep track
        _Dependencies
        wmctrl -lx | awk '{print $1" "$3}' > "$ID_LIST_ALL"
        # using lowercases will match ids in $ID_LIST_ALL
        echo "$TARGET" | tr [:upper:] [:lower:] >> "$ID_LIST"
        xdo hide "$TARGET"
        _Indicator on
    ;;
    -u) if [[ $(xprop -id $(sed -n '1p' "$ID_LIST") \
            | awk '/window state: / {print $3}') = Withdrawn ]]; then
            # show the first entry
            xdo show "$(sed -n '1p' "$ID_LIST")"
            # remove it from the list
            sed -i '1d' "$ID_LIST"
            if (( $(grep "" -c "$ID_LIST") > 0 )); then
                _Indicator on
            else
                _Indicator off
            fi
        else
            _Indicator off
        fi
    ;;
    -r) if (( $(grep "" -c $ID_LIST) > 0 )); then

            HIDDEN=$(cat $ID_LIST)

            # making a rofi menu
            N=$(for w in $HIDDEN; do
                NAME=$(xprop -id "$w" WM_CLASS 2>/dev/null | sed -r 's/.+ "(.+)"$/\1/')
                TITLE=$(xprop -id "$w" _NET_WM_NAME 2>/dev/null | sed -r 's/.+ "(.+)"$/\1/')
                [[ "$NAME" = "WM_CLASS" ]] && echo "$w" || echo "$NAME  \"$TITLE\""
            done | rofi -dmenu -no-custom -format i -p 'Unhide a window: ')

            [[ -n "$N" ]] && {

                ID=$(echo "$HIDDEN" | sed -n "$((N+1))p")
                xdo show "$ID"

                sed -i "/${ID}/d;/^\s*$/d" "$ID_LIST"

                if (( $(grep "" -c $ID_LIST) > 0 )); then
                    _Indicator on
                else
                    _Indicator off
                fi
            }

        else
            _Indicator off
        fi
    ;;
esac

Polybar module:

[module/scratchpad-ipc]
type = custom/ipc
format = <output>
format-padding = 1
hook-0 = echo ""
hook-1 = echo "  $(grep "" -c "$XDG_RUNTIME_DIR"/scratchpad_hidden.list)"
initial = 1

I prefer custom/ipc modules over studip intervals to execute a command.
Of course add it in your bar section eg: modules-left = scratchpad-ipc
Hope you like it :smile:

7 Likes
1 Like

Thanks for contributing these scripts.

2 Likes

You’re welcome

scratchpad in the i3 sense of the term, I believe.

Like a hidden desktop.

But this script is window manager agnostic, so it will also work as (for example) as a tint2 executor in openbox.

1 Like

Thank you for taking the time to clarify and make me understand.
Very kind of you.

2 Likes

Top work again @archus

1 Like

Have tried your scratchpad hide in dk not sure if i’m using the right command just errors.

I found this he is using Spectrwm with hide scratchpad script.
# Spectrwm [hide scratchpad]

hi, can you tell me which errors please, maybe missing dependencies?

I’m not sure I under the command for dk.

super + shift + q
	 rofi -show drun scratchpad -r ~/.config/dk/scripts/dk-scratchpad.dk

-h & -u and this part.

Launch it from a terminal and you’ll get output or put it in your PATH

.sh… why .dk?

Wtf just late night DK on the brain. :face_with_spiral_eyes:

Thanks archus

Okey dokey I fixed the typo but I do not understand the hide / unhide bit.

Yes it opens a scratchpad.

super + shift + q
kitty --class ~/.config/dk/scripts/dk-scratchpad.sh

dkcmd rule class=“^/home/koo/.config/dk/scripts/dk-scratchpad.sh$” float=true x=800 y=28 w=1000 h=600

Plus I have no interest in the bar side of things. I use lemonbar & a lua script conky.

hi, well my friend I guess you don’t really know how to execute it
It will send or hide the focused window in the “scratchpad”
First create a sxhkd shortcut, for example:

alt + shift + {h,u,r}
    {\
    ~/.config/dk/scripts/dk-scratchpad.sh -h, \
    ~/.config/dk/scripts/dk-scratchpad.sh -u, \
    ~/.config/dk/scripts/dk-scratchpad.sh -r \
    }

Then reload the config, it will be better if you store it in your $PATH so you can execute any script or binary from it.

# you need rofi to use the -r option
alt + shift + {h,u,r}
    {\
    scratchpad.sh -h, \
    scratchpad.sh -u, \
    scratchpad.sh -r \
    }

and you don’t need any rules

WoW i like this its so fun. Also I never understood that it works on any focused window . {scratchpad}
Thanks for the info. :+1:

I need to learn more script commands

I’m having trouble getting below grep command working in conky | lemonbar. I have it working in terminal but not conky.
grep “” -c “$XDG_RUNTIME_DIR”/scratchpad_hidden.list

${grep “” -c “$XDG_RUNTIME_DIR”/scratchpad_hidden.list} have tried many different combinations.

TY

Maybe something like this…

${exec grep “” -c “$XDG_RUNTIME_DIR”/scratchpad_hidden.list}

Keep in mind that all of these - except for execi and pre_exec will run every time conky ‘ticks’ - which can be very resource consuming, depending on your script.

See the complete list and more information in the list of Conky objects:
you can also specify the execution interval by using: execi <seconds>
Sorry I maybe wong on this because I don’t use conky often.

Don’t forget that you should have led 3 (scroll_lock) activated by the script as an indicator just so you know you have a hidden window, and rofi (option -r).

1 Like

Thanks for the extra info with conky & your time. All fixed

${exec grep “” -c “$XDG_RUNTIME_DIR”/scratchpad_hidden.list} works fine in lemonbar :grinning:

Terminal
$ grep “” -c “$XDG_RUNTIME_DIR”/scratchpad_hidden.list
3

Winner :+1:

1 Like

That was yesterday now it has stopped working with no file or directory.
Yet the files are in /run/user/1000…

$ grep “” -c “$XDG_RUNTIME_DIR”/scratchpad_hidden.list
grep: “/run/user/1000”/scratchpad_hidden.list: No such file or directory

But strange thing is the hidden workspaces are listed in scratchpad_hidden.list file.

0x01a0000e
0x01c0000e
0x0260000e

Here is my dk-scratchpad.sh

#!/bin/bash

BARPID=$(pgrep --full lemonbar)

_empty() {
    xset -led 3
    polybar-msg -p "$BARPID" \
    action "#scratchpad-ipc.hook.0" >/dev/null 2>&1
    notify-send 'nothing else in scratchpad'
}

_indicator() {
    xset led 3
    polybar-msg -p "$BARPID" \
    action "#scratchpad-ipc.hook.1" >/dev/null 2>&1
}

TARGET=$(xdo id ${1})
ID_LIST="$XDG_RUNTIME_DIR/scratchpad_hidden.list"
ID_LIST_ALL="$XDG_RUNTIME_DIR/scratchpad_all.list"

case "$1" in
    -h) # sending all existing windows ids/instances/classes in a file to keep track
        wmctrl -lx | awk '{print $1" "$3}' >> "$ID_LIST_ALL"
        # using lowercases will match ids in $ID_LIST_ALL
        echo "$TARGET" | tr [:upper:] [:lower:] >> "$ID_LIST"
        xdo hide "$TARGET"
        sed -i -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d;
        s/\n//; h; P' "$ID_LIST_ALL"
        _indicator
    ;;
    -u) if [[ $(xprop -id $(sed -n '1p' "$ID_LIST") \
            | awk '/window state: / {print $3}') = Withdrawn ]]; then
            # show the first entry
            xdo show $(sed -n '1p' "$ID_LIST")
            # remove it from the list
            sed -i '1d' "$ID_LIST"
            if (( $(grep "" -c "$ID_LIST") > 0 )); then
                _indicator
            else
                _empty
            fi
        else
            _empty
        fi
    ;;
    -r) if (( $(grep "" -c $ID_LIST) > 0 )); then

            HIDDEN=$(cat $ID_LIST)

            N=$(for w in $HIDDEN; do
                NAME=$(xprop -id "$w" WM_CLASS 2>/dev/null | sed -r 's/.+ "(.+)"$/\1/')
                TITLE=$(xprop -id "$w" _NET_WM_NAME 2>/dev/null | sed -r 's/.+ "(.+)"$/\1/')
                [[ "$NAME" = "WM_CLASS" ]] && echo "$w" || echo "$NAME  \"$TITLE\""
            done | rofi -dmenu -no-custom -format  i -p 'Unhide a window: ')

            [[ -n "$N" ]] && {

                ID=$(echo "$HIDDEN" | sed -n "$((N+1))p")
                xdo show "$ID"

                mapfile -t < <(wmctrl -lx | awk '{print $1}')
                for line in "${MAPFILE[@]}"; do
                    if [[ $(grep $line "$ID_LIST") ]]; then
                        # removing the right line
                        sed -i "/${line}/d" "$ID_LIST"
                        # removing blank lines
                        sed -i '/^\s*$/d' "$ID_LIST"
                        break
                    else
                        continue
                    fi
                done

                if (( $(grep "" -c $ID_LIST) > 0 )); then
                    _indicator
                else
                    _empty
                fi
            }
        else
            _empty
        fi
    ;;
esac

Thanks

Don’t quote this if you use absolute path

/run/user/1000/scratchpad_hidden.list

Ok maybe you have to export this variable when you login, put this in your .bashrc or .profile, you have to completely exit, meaning your shell must not run in the background.

export XDG_RUNTIME_DIR=“/run/user/1000”

then log back in and in a terminal type

echo “$XDG_RUNTIME_DIR”

This should return /run/user/1000

You don’t need this variable in the script because you don’t use polybar (see line 7 & 14), these lines send a signal to polybar to update the module…, just keep the xset lines.
You should learn a little bit of shell scripting then you could understand the script as you read it before using it :roll_eyes:
The less a script return errors the better it runs :sunglasses: don’t give up

1 Like