aboutsummaryrefslogtreecommitdiff
path: root/linux/home/.config/bspwm
diff options
context:
space:
mode:
authorsrdusr <trevorgray@srdusr.com>2025-08-30 19:22:59 +0200
committersrdusr <trevorgray@srdusr.com>2025-08-30 19:22:59 +0200
commit19120d4f9761c67d99ed1ce3da6084b83f5a49c9 (patch)
treef234cad1bdad88114a63c9702144da487024967a /linux/home/.config/bspwm
parent5928998af5404ae2be84c6cecc10ebf84bd3f3ed (diff)
downloaddotfiles-19120d4f9761c67d99ed1ce3da6084b83f5a49c9.tar.gz
dotfiles-19120d4f9761c67d99ed1ce3da6084b83f5a49c9.zip
Linux-specific dotfiles
Diffstat (limited to 'linux/home/.config/bspwm')
-rwxr-xr-xlinux/home/.config/bspwm/bspwmrc275
-rwxr-xr-xlinux/home/.config/bspwm/scripts/bspdragtofloat46
-rwxr-xr-xlinux/home/.config/bspwm/scripts/bspswallow12
-rwxr-xr-xlinux/home/.config/bspwm/scripts/bspwindows14
-rwxr-xr-xlinux/home/.config/bspwm/scripts/bspwm-monitor-setup59
-rwxr-xr-xlinux/home/.config/bspwm/scripts/bspwm-toggle-visibility.sh23
-rwxr-xr-xlinux/home/.config/bspwm/scripts/close-window11
-rwxr-xr-xlinux/home/.config/bspwm/scripts/drag-float42
-rwxr-xr-xlinux/home/.config/bspwm/scripts/external_rules.sh71
-rwxr-xr-xlinux/home/.config/bspwm/scripts/hide-window38
10 files changed, 591 insertions, 0 deletions
diff --git a/linux/home/.config/bspwm/bspwmrc b/linux/home/.config/bspwm/bspwmrc
new file mode 100755
index 0000000..d30d6db
--- /dev/null
+++ b/linux/home/.config/bspwm/bspwmrc
@@ -0,0 +1,275 @@
+#! /bin/sh
+
+################################################################################
+# ██████╗ ███████╗██████╗ ██╗ ██╗███╗ ███╗ #
+# ██╔══██╗██╔════╝██╔══██╗██║ ██║████╗ ████║ #
+# ██████╔╝███████╗██████╔╝██║ █╗ ██║██╔████╔██║ #
+# ██╔══██╗╚════██║██╔═══╝ ██║███╗██║██║╚██╔╝██║ #
+# ██████╔╝███████║██║ ╚███╔███╔╝██║ ╚═╝ ██║ #
+# ╚═════╝ ╚══════╝╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝ #
+################################## By: srdusr ##################################
+
+# ##############################################################################
+# # ENV VARS #
+# ##############################################################################
+
+## Environments
+export PATH="${PATH}:${HOME}/.config/bspwm/bin"
+
+# Get the name of the primary monitor
+mainmonitor=$(xrandr --query | awk '/ primary/{print $1}')
+
+## Monitors
+# If no primary monitor is identified, use the first connected monitor
+if [ "$mainmonitor" = "" ]; then
+ mainmonitor=$(xrandr --query | awk '/ connected/ {print $1; exit}')
+fi
+
+# Set up workspaces on the primary monitor
+bspc monitor "$mainmonitor" -d 󰲡 󰲣 󰲥 󰲧 󰲩 #1 2 3 4 5
+
+# Check the number of connected monitors
+connected_monitors=$(xrandr --query | grep -c " connected")
+
+if [ "$connected_monitors" -gt 1 ]; then
+ # Get the name of the secondary monitor (exclude the primary monitor)
+ secondmonitor=$(xrandr --query | awk '/ connected/ && $1 != "'"$mainmonitor"'" {print $1; exit}')
+
+ # Set up workspaces on the secondary monitor
+ bspc monitor "$secondmonitor" -d 󰲫 󰲭 󰲯 󰲱 󰿭 #6 7 8 9 10
+ # Check if the secondary monitor is connected and configure the layout
+ if [ "$secondmonitor" != "" ]; then
+ xrandr --output "$mainmonitor" --primary --auto --output "$secondmonitor" --auto --right-of "$mainmonitor"
+ fi
+fi
+
+#INTERNAL_MONITOR="LVDS-1"
+#EXTERNAL_MONITOR="HDMI-1"
+## on first load setup default workspaces
+#if [[ "$1" = 0 ]]; then
+# if [[ $(xrandr -q | grep "${EXTERNAL_MONITOR} connected") ]]; then
+# bspc monitor "$EXTERNAL_MONITOR" -d 1 2 3 4 5
+# bspc monitor "$INTERNAL_MONITOR" -d 6 7 8 9 10
+# bspc wm -O "$EXTERNAL_MONITOR" "$INTERNAL_MONITOR"
+# else
+# bspc monitor "$INTERNAL_MONITOR" -d 1 2 3 4 5 6 7 8 9 10
+# fi
+#fi
+
+# ##############################################################################
+# # FUNCTIONS #
+# ##############################################################################
+
+config() { bspc config "$@" & }
+rule() { bspc rule -a "$@" & }
+run_once() {
+ if [ ! "$(pgrep -f "$1")" ]; then
+ "$@" &
+ fi
+}
+
+# ##############################################################################
+# # WINDOW RULES #
+# ##############################################################################
+
+## Rules
+bspc rule -r *:* # remove all rules first
+rule '*' --one-shot state=below private=border_width:10
+#rule '*:Tiled' --one-shot state=tiled rectangle=50x50+0+50
+#rule '*' --one-shot state=floating rectangle=1028x374+0+50
+#rule \* rectangle=680x700+340+40
+rule '*:*:Picture-in-Picture' state=floating sticky=on layer=above
+rule '*:*:Picture in picture' state=floating sticky=on layer=above
+rule firefox:Toolkit focus=on state=floating sticky=on layer=above rectangle=400x280+955+475 #320x190+1030+480 #522x316-10+280
+rule "https://www.youtube.com - Enhancer for YouTube™ — Mozilla Firefox" state=floating sticky=on layer=above
+rule Wezterm state=floating
+rule Zathura state=tiled
+rule Pavucontrol state=floating rectangle=490x260+862+37
+rule Blueman-manager state=floating rectangle=536x420+818+37 #490x260-9+37
+rule scratchpad sticky=on state=floating # SCRATCHPAD
+rule heads-up-display sticky=on state=floating rectangle=360x160+990+35 # Heads Up Display (scratchpad)
+rule Onboard sticky=on state=floating rectangle=700x205+480-89 # Virtual keyboard
+rule Plank layer=above border=off
+rule Protonvpn state=floating
+rule qBittorrent desktop='^2'
+rule discord desktop='^4'
+rule firefox -o desktop=^1
+rule stalonetray state=floating manage=off
+#bspc rule -a Spotify:spotify desktop='^󰲥' state=tiled
+#bspc rule -a '*:spotify' desktop='^3' state=tiled
+
+# ##############################################################################
+# # AUTOSTART APPS #
+# ##############################################################################
+
+# Clear cache
+#rm "$HOME"/.cache/dunst.log
+#rm "$HOME"/.cache/fake_battery_capacity
+#rm "$HOME"/.cache/eww-calendar.lock
+#rm "$HOME"/.cache/eww-escreen.lock
+#rm "$HOME"/.cache/eww-control-center.lock
+#rm -r "$HOME"/.cache/dunst/
+
+# Autostart applications
+#"$HOME"/.config/bspwm/scripts/bspwm_setup_monitors &
+pgrep -x sxhkd > /dev/null || sxhkd &
+pgrep -x plank > /dev/null || plank &
+pgrep -x jgmenu > /dev/null || bspc rule -a jgmenu desktop='^H' state=floating hidden=on && jgmenu --hide-on-startup &
+picom --config "$HOME"/.config/picom/picom.conf &
+rm "$HOME"/.jgmenu-lockfile
+"$HOME"/.config/polybar/launch.sh &
+run_once unclutter & # Remove mouse when idle
+run_once "$HOME"/.scripts/lockscreen-wallpaper &
+run_once xss-lock -- betterlockscreen -l &
+nitrogen --force-setter=xinerama --restore &
+run_once redshift &
+run_once low-bat-notifier &
+pkill persistentQuickUtilities.sh; "$HOME"/.config/bspwm/scripts/persistentQuickUtilities.sh &
+#xfce4-panel --disable-wm-check &
+
+# Start polkit agent
+#[ "$(pidof xfce-polkit)" != "" ] || /usr/lib/xfce-polkit/xfce-polkit &
+run_once /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 &
+
+# Volume and brightness indicator (xob)
+source "$HOME"/.virtualenvs/bin/activate # Activate virtual environment
+run_once "$HOME"/.config/xob/launch.sh &
+deactivate # Deactivate virtual environment
+
+# Eww
+pkill eww
+eww daemon
+
+xset m 0 0 # Disable mouse drift
+
+pgrep -x plank.sh > /dev/null || plank.sh &
+
+#wmname LG3D # Fixes Java applications
+# Solve java apps issues (e.g. JetBrains IDEs like PyCharm, CLion, etc). #
+#export _JAVA_AWT_WM_NONREPARENTING=1
+
+#export QT_QPA_PLATFORMTHEME="qt5ct" # Use qt5ct to set Qt theme
+
+# Start MPD and mpDris2
+#exec mpd &
+#exec mpDris2 &
+
+# Systray
+#run_once signal-desktop --start-in-tray & #--use-tray-icon
+#run_once onboard --not-show-in=DESKTOPS &
+if ! pgrep -x "stalonetray" > /dev/null; then
+ stalonetray &
+fi
+
+declare -a restart=(clipit blueman-applet caffeine)
+for i in "${restart[@]}"; do
+ pgrep -x "$i" | xargs kill
+ sleep 0.5
+ eval "$i" &
+done
+
+while ! pgrep -x "clipit" > /dev/null || ! pgrep -x "blueman-applet" > /dev/null || ! pgrep -x "caffeine" > /dev/null; do
+ #sleep 0.5
+ systray &
+done
+xdo hide -N stalonetray
+touch "/tmp/syshide.lock"
+
+# ##############################################################################
+# # CONFIGURATION #
+# ##############################################################################
+
+## Config
+PANEL_HEIGHT=24
+config top_padding "$PANEL_HEIGHT"
+config honor_size_hints true
+config automatic_scheme alternate
+config initial_polarity second_child
+config pointer_modifier mod4
+config click_to_focus none
+config pointer_action1 move
+config pointer_action2 resize_side
+config pointer_action3 resize_corner
+config focus_follows_pointer true
+config remove_disabled_monitors true
+config remove_unplugged_monitors true
+config merge_overlapping_monitors true
+config border_width 4
+config border_radius 10
+config window_gap 10
+config split_ratio 0.52
+config borderless_monocle true
+config gapless_monocle true
+config swallow_first_click false
+config normal_border_color "#000000"
+config focused_border_color "#000000"
+config active_border_color "#000000"
+config presel_feedback_color "#BF616A"
+
+# ##############################################################################
+# # MISCELLANEOUS #
+# ##############################################################################
+
+## Fullscreen
+bspc subscribe node_state | while read -r _ _ _ _ state flag; do
+ if [[ "$state" != fullscreen ]]; then continue; fi
+ if [[ "$flag" == on ]]; then
+ xdo lower -N Plank
+ #"$(which eww)" -c "$HOME"/.config/eww close-all
+ else
+ xdo raise -N Plank
+ #"$(which eww)" -c "$HOME"/.config/eww open bar
+ fi
+done &
+
+## Title-bar
+rm -rf /tmp/title-bar_debug.log
+rm -rf /tmp/title-bar.lock
+
+processes=("title-bar" "update-title" "lemonbar")
+
+for process in "${processes[@]}"; do
+ if pidof -q "$process"; then
+ pkill -x "$process" > /dev/null; sleep 0.1
+ fi
+done
+
+# Check if title-bar is already running
+if ! pgrep -x "title-bar" >/dev/null; then
+ # Create a lock file
+ lockfile="/tmp/title-bar.lock"
+
+ # Check if the lock file exists
+ if [ ! -e "$lockfile" ]; then
+ # Create the lock file
+ touch "$lockfile"
+
+ # Function to handle BSPWM events
+ handle_bspwm_events() {
+ while read; do
+ if ! pgrep -x "title-bar" >/dev/null; then
+ bash "$HOME/.scripts/title-bar" &
+ fi
+ done
+ }
+
+ # Start bspc subscribe in the background and pass events to the handler function
+ bspc subscribe | handle_bspwm_events &
+
+ # Remove the lock file when the script exits
+ trap 'rm -f "$lockfile"' EXIT
+ else
+ echo "title-bar is already running."
+ fi
+fi
+
+start_dunst() {
+ # stop dunst if it has been started by any application that called notify-send
+ killall -q dunst
+ # Wait until the processes have been shut down
+ while pgrep -u "$UID" -x dunst >/dev/null; do sleep .05; done
+ while ! pgrep -u "$UID" -x dunst >/dev/null; do sleep .05; done
+}
+start_dunst &
+
+config external_rules_command ~/.config/bspwm/scripts/external_rules.sh &
diff --git a/linux/home/.config/bspwm/scripts/bspdragtofloat b/linux/home/.config/bspwm/scripts/bspdragtofloat
new file mode 100755
index 0000000..e2f88a2
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/bspdragtofloat
@@ -0,0 +1,46 @@
+#!/bin/env bash
+
+: "${BSPWM_DIR:="${XDG_CONFIG_HOME:-$HOME/.config}/bspwm"}"
+
+status_file="$BSPWM_DIR/tmp/drag_to_float"
+
+[[ "$1" = stop ]] && {
+ [[ -e "$status_file" ]] \
+ && rm -r -- "$status_file"
+ exit
+}
+
+[[ -e "$status_file" ]] \
+ && exit
+
+< <(bspc query -T -n pointed.window | jq -r '"\(.id) \(.client.state)"') read -r node node_state
+
+[[ -z "$node" ]] \
+ && exit
+
+case "$node_state" in
+ floating)
+ ;;
+ tiled|pseudo_tiled)
+ node_tiled_rect=($(bspc query -T -n "$node" | jq -r '.client.tiledRectangle[]'))
+ bspc node "$node" -t floating
+ xdo move -x "${node_tiled_rect[0]}" -y "${node_tiled_rect[1]}" "$node"
+ xdo resize -w "${node_tiled_rect[2]}" -h "${node_tiled_rect[3]}" "$node" ;;
+ *) # fullscreen
+ exit ;;
+esac
+
+eval "$(xdotool getmouselocation --shell)"
+x="$X" y="$Y"
+touch -- "$status_file"
+while [[ -e "$status_file" ]]; do
+ eval "$(xdotool getmouselocation --shell)"
+ (( X != x || Y != y )) && {
+ bspc node "$node" -v "$((X - x))" "$((Y - y))"
+ x="$X" y="$Y"
+ }
+ sleep .01
+done
+
+[[ -e "$status_file" ]] \
+ && rm -r -- "$status_file"
diff --git a/linux/home/.config/bspwm/scripts/bspswallow b/linux/home/.config/bspwm/scripts/bspswallow
new file mode 100755
index 0000000..dea343f
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/bspswallow
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+NODE_CURRENT=$(bspc query -N -n focused)
+$@ &
+PID_COMMAND=$!
+WATCH=$(bspc subscribe -c 1 node_add)
+NODE_NEW=${WATCH%% *}
+bspc node -s $NODE_CURRENT
+bspc node $NODE_CURRENT --flag hidden=on
+wait $PID_COMMAND
+bspc node $NODE_CURRENT --flag hidden=off
+bspc node $NODE_CURRENT --focus
diff --git a/linux/home/.config/bspwm/scripts/bspwindows b/linux/home/.config/bspwm/scripts/bspwindows
new file mode 100755
index 0000000..26deeab
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/bspwindows
@@ -0,0 +1,14 @@
+#!/bin/sh
+# bspwindows
+# get targets for drawing borders/whatever on in bspwm
+
+target="${1:-active}"
+
+case "$target" in
+ active)
+ bspc query -N -n .local.descendant_of.window.leaf.!fullscreen
+ ;;
+ inactive)
+ bspc query -N -n .local.!descendant_of.window.leaf.!fullscreen
+ ;;
+esac
diff --git a/linux/home/.config/bspwm/scripts/bspwm-monitor-setup b/linux/home/.config/bspwm/scripts/bspwm-monitor-setup
new file mode 100755
index 0000000..6e38bb7
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/bspwm-monitor-setup
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+
+INTERNAL_MONITOR="eDP"
+EXTERNAL_MONITOR="HDMI-A-0"
+
+monitor_add() {
+ # Move first 5 desktops to external monitor
+ for desktop in $(bspc query -D --names -m "$INTERNAL_MONITOR" | sed 5q); do
+ bspc desktop "$desktop" --to-monitor "$EXTERNAL_MONITOR"
+ done
+ # Remove default desktop created by bspwm
+ bspc desktop Desktop --remove
+ # reorder monitors
+ bspc wm -O "$EXTERNAL_MONITOR" "$INTERNAL_MONITOR"
+}
+
+monitor_remove() {
+ # Add default temp desktop because a minimum of one desktop is required per monitor
+ bspc monitor "$EXTERNAL_MONITOR" -a Desktop
+
+ # Move all desktops except the last default desktop to internal monitor
+ for desktop in $(bspc query -D -m "$EXTERNAL_MONITOR"); do
+ bspc desktop "$desktop" --to-monitor "$INTERNAL_MONITOR"
+ done
+
+ # delete default desktops
+ bspc desktop Desktop --remove
+ # reorder desktops
+ bspc monitor "$INTERNAL_MONITOR" -o 1 2 3 4 5 6 7 8 9 10
+}
+
+if [[ $(xrandr -q | grep "${EXTERNAL_MONITOR} connected") ]]; then
+ # set xrandr rules for docked setup
+ xrandr --output "$INTERNAL_MONITOR" --mode 1920x1080 --pos 0x0 --rotate normal --output "$EXTERNAL_MONITOR" --primary --mode 1920x1080 --pos 1920x780 --rotate normal
+ if [[ $(bspc query -D -m "${EXTERNAL_MONITOR}" | wc -l) -ne 5 ]]; then
+ monitor_add
+ fi
+ bspc wm -O "$EXTERNAL_MONITOR" "$INTERNAL_MONITOR"
+else
+ # set xrandr rules for mobile setup
+ xrandr --output "$INTERNAL_MONITOR" --primary --mode 1920x1080 --pos 0x0 --rotate normal --output "$EXTERNAL_MONITOR" --off
+ if [[ $(bspc query -D -m "${INTERNAL_MONITOR}" | wc -l) -ne 10 ]]; then
+ monitor_remove
+ fi
+fi
+
+# Set wallpaper
+~/.local/bin/setbg.sh &
+
+# Kill and relaunch polybar
+kill -9 $(pgrep -f 'polybar') >/dev/null 2>&1
+polybar-msg cmd quit >/dev/null 2>&1
+while pgrep -u $UID -x polybar >/dev/null; do sleep 1; done
+if [[ $(xrandr -q | grep "${EXTERNAL_MONITOR} connected") ]]; then
+ polybar --reload primary -c ~/.config/polybar/config.ini </dev/null >/var/tmp/polybar-primary.log 2>&1 200>&- &
+ polybar --reload secondary -c ~/.config/polybar/config.ini </dev/null >/var/tmp/polybar-secondary.log 2>&1 200>&- &
+else
+ polybar --reload primary -c ~/.config/polybar/config.ini </dev/null >/var/tmp/polybar-primary.log 2>&1 200>&- &
+fi
diff --git a/linux/home/.config/bspwm/scripts/bspwm-toggle-visibility.sh b/linux/home/.config/bspwm/scripts/bspwm-toggle-visibility.sh
new file mode 100755
index 0000000..45a4c53
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/bspwm-toggle-visibility.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# Created By: srdusr
+# Created On: Mon 18 Sep 2023 18:37:21 CAT
+# Project: Bspwm script to toggle visibility of initial window and bring focus back to it
+
+# Get the ID of the currently focused desktop
+current_desktop_id=$(bspc query -D -d focused --names)
+
+# Get the ID of the first hidden window in the current desktop
+hidden_window_id=$(bspc query -N -d "$current_desktop_id" -n .hidden | head -n 1)
+
+# Check if there's a hidden window in the current desktop
+if [[ -n "$hidden_window_id" ]]; then
+ # There's a hidden window, so unhide it
+ bspc node "$hidden_window_id" -g hidden=off
+ # Bring focus back to the previously hidden window
+ bspc node -f "$hidden_window_id"
+else
+ # There's no hidden window in the current desktop, hide the first available window
+ first_window_id=$(bspc query -N -n focused.window)
+ bspc node "$first_window_id" -g hidden=on
+fi
diff --git a/linux/home/.config/bspwm/scripts/close-window b/linux/home/.config/bspwm/scripts/close-window
new file mode 100755
index 0000000..8f7f36b
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/close-window
@@ -0,0 +1,11 @@
+#!/bin/sh
+#
+# close or kill a bspwm window
+
+# show wallpaper if last tile
+bspc query -N -n .focused.tiled.window &> /dev/null \
+ && ! bspc query -N -n .!focused.local.tiled.window &> /dev/null \
+ && show-wallpaper -d
+
+# close focused window
+[ "$1" = "kill" ] && bspc node -k || bspc node -c
diff --git a/linux/home/.config/bspwm/scripts/drag-float b/linux/home/.config/bspwm/scripts/drag-float
new file mode 100755
index 0000000..788e978
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/drag-float
@@ -0,0 +1,42 @@
+#!/bin/env bash
+
+: "${BUTTON:=1}"
+
+node="$(bspc query -N -n pointed)"
+
+die() {
+ jobs -p | xargs -r -n1 -I{} kill {}
+ exit
+}
+
+trap 'die' USR1
+
+{ bspc subscribe node_focus | while read -r _ _ _ wid; do
+ (( wid != node )) && break; done; kill -USR1 "$$" ;} &
+{ while xinput list \
+ | sed -nE 's,.*id=([0-9]+).*slave\s+pointer.*,\1,p' \
+ | xargs -r -n1 -I{} xinput query-state {} 2> /dev/null \
+ | grep -qF "button[${BUTTON}]=down"; do sleep .3; done; kill -USR1 "$$" ;} &
+
+if bspc node "$node.tiled" -f; then
+ node_tiled_rect=($(bspc query -T -n "$node" | jq -r '.client.tiledRectangle[]'))
+ bspc node "$node" -t floating
+ xdo move -x "${node_tiled_rect[0]}" -y "${node_tiled_rect[1]}" "$node"
+ xdo resize -w "${node_tiled_rect[2]}" -h "${node_tiled_rect[3]}" "$node"
+elif bspc node "$node.floating" -f; then
+ :
+else
+ die
+fi
+
+eval "$(xdotool getmouselocation --shell)"
+x="$X" y="$Y"
+while :; do
+ eval "$(xdotool getmouselocation --shell)"
+ (( X != x || Y != y )) && {
+ bspc node "$node" -v "$((X - x))" "$((Y - y))"
+ x="$X" y="$Y"
+ }
+done
+
+wait
diff --git a/linux/home/.config/bspwm/scripts/external_rules.sh b/linux/home/.config/bspwm/scripts/external_rules.sh
new file mode 100755
index 0000000..7b07ae8
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/external_rules.sh
@@ -0,0 +1,71 @@
+#!/bin/env bash
+#
+# external_rules_command
+#
+# Absolute path to the command used to retrieve rule consequences.
+# The command will receive the following arguments: window ID, class
+# name, instance name, and intermediate consequences. The output of
+# that command must have the following format: key1=value1
+# key2=value2 ... (the valid key/value pairs are given in the
+# description of the rule command).
+#
+#
+# Rule
+# General Syntax
+# rule COMMANDS
+#
+# Commands
+# -a, --add (<class_name>|*)[:(<instance_name>|*)] [-o|--one-shot]
+# [monitor=MONITOR_SEL|desktop=DESKTOP_SEL|node=NODE_SEL]
+# [state=STATE] [layer=LAYER] [split_dir=DIR] [split_ratio=RATIO]
+# [(hidden|sticky|private|locked|marked|center|follow|manage|focus|border)=(on|off)]
+# [rectangle=WxH+X+Y]
+# Create a new rule.
+#
+# -r, --remove
+# ^<n>|head|tail|(<class_name>|*)[:(<instance_name>|*)]...
+# Remove the given rules.
+#
+# -l, --list
+# List the rules.
+
+# Programs to specific desktops
+wid=$1
+class=$2
+instance=$3
+consequences=$4
+
+main() {
+ case "$class" in
+ firefox)
+ if [ "$instance" = "Toolkit" ]; then
+ echo "state=floating sticky=on"
+ fi
+ ;;
+ Spotify)
+ echo desktop=^5 follow=on focus=on
+ ;;
+ "")
+ sleep 0.5
+
+ wm_class=("$(xprop -id "$wid" | grep "WM_CLASS" | grep -Po '"\K[^,"]+')")
+
+ class=${wm_class[-1]}
+
+ [[ ${#wm_class[@]} == "2" ]] && instance=${wm_class[0]}
+
+ [[ -n "$class" ]] && main
+ ;;
+ esac
+}
+
+main
+
+# Allow floating windows over fullscreen
+wid="$1"
+class="$2"
+instance="$3"
+eval "$4"
+
+[[ "$state" = floating ]] &&
+ echo 'layer=above'
diff --git a/linux/home/.config/bspwm/scripts/hide-window b/linux/home/.config/bspwm/scripts/hide-window
new file mode 100755
index 0000000..c350a0e
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/hide-window
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+CMD=${1:-help}
+shift
+
+help() {
+ echo "Available commands:"
+ echo " * unhide - select and unhide window"
+}
+
+unhide() {
+ action=${1:-list}
+ case $action in
+ "list")
+ selection=$(for id in "$(bspc query -N -n .hidden)"; do
+ title=$(xtitle "$id")
+ [[ -z "$title" ]] && title="<unnamed>"
+ echo "$id" "$title"
+ done | rofi -dmenu -i -p "Hidden windows" | cut -f1 -d' ')
+
+ [[ -z "$selection" ]] && exit 1
+
+ bspc node "$selection" -g hidden=off
+ ;;
+ esac
+ }
+
+ case $CMD in
+ "help")
+ help
+ ;;
+ "unhide")
+ unhide "$1"
+ ;;
+ *)
+ help
+ ;;
+ esac