Skip to content

Instantly share code, notes, and snippets.

@eliliam
Last active December 11, 2025 00:20
Show Gist options
  • Select an option

  • Save eliliam/0902fbdba2918a0676df3b94576d6b73 to your computer and use it in GitHub Desktop.

Select an option

Save eliliam/0902fbdba2918a0676df3b94576d6b73 to your computer and use it in GitHub Desktop.
Bspwm Window Inspector: A Clean CLI Tool for Inspecting Window Properties
#!/usr/bin/env bash
#
# bspwm Window Inspector
#
# A clean CLI tool for inspecting window properties in bspwm.
# Click any window and the script prints detailed information including:
# - Window ID (decimal and hex)
# - WM_CLASS (instance and class)
# - bspwm state (tiled, floating, fullscreen, etc.)
# - Focused / shown / hidden / urgent flags
# - Monitor, desktop, and node id
# - Auto generated "bspc rule" example for configuration
#
# Requirements:
# - xdotool
# - xprop
# - bspwm (bspc)
# - jq
#
# Installation:
# Save this script as "window-info" (or any name)
# Make it executable:
# chmod +x window-info
# Place it somewhere in your PATH, for example:
# ~/.local/bin or /usr/local/bin
#
# Usage:
# window-info
# Prompts you to click a window and prints information about it.
#
# window-info <window-id>
# Skips the click prompt and inspects the given window id directly.
#
set -o errexit -o nounset -o pipefail
# Dependencies
for cmd in xdotool xprop bspc jq; do
if ! command -v "$cmd" >/dev/null 2>&1; then
printf "Required command '%s' not found in PATH\n" "$cmd" >&2
exit 1
fi
done
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
GRAY='\033[0;37m'
BOLD='\033[1m'
UNDERLINE='\033[4m'
NC='\033[0m'
# Disable colors when stdout is not a TTY
if [[ ! -t 1 ]]; then
RED='' GREEN='' YELLOW='' BLUE='' MAGENTA='' CYAN='' GRAY='' BOLD='' UNDERLINE='' NC=''
fi
###############################################
# Scroll safe window selector
###############################################
select_window() {
while true; do
printf "${CYAN}${BOLD}Click the window you want to inspect...${NC}\n" >&2
wid=$(xdotool selectwindow 2>/dev/null) || {
printf "${YELLOW}Selection ignored (scroll or non window event). Try again.${NC}\n" >&2
continue
}
if [[ "$wid" =~ ^[0-9]+$ ]]; then
printf "${GREEN}Window selected.${NC}\n\n" >&2
echo "$wid"
return 0
fi
printf "${YELLOW}Invalid event captured. Try again.${NC}\n" >&2
done
}
###############################################
# Boolean printer
###############################################
bool() {
if [[ "$1" == "true" ]]; then
printf "${BOLD}true${NC}"
else
printf "${GRAY}false${NC}"
fi
}
###############################################
# Value formatters
###############################################
state_fmt() {
case "$1" in
tiled) printf "${BLUE}tiled${NC}" ;;
floating) printf "${MAGENTA}floating${NC}" ;;
fullscreen) printf "${YELLOW}fullscreen${NC}" ;;
pseudo_tiled) printf "${CYAN}pseudo_tiled${NC}" ;;
*) printf "%s" "$1" ;;
esac
}
layer_fmt() {
case "$1" in
below) printf "${RED}below${NC}" ;;
normal) printf "${GREEN}normal${NC}" ;;
above) printf "${BLUE}above${NC}" ;;
*) printf "%s" "$1" ;;
esac
}
###############################################
# Inspect a single window
###############################################
inspect_window() {
local win_id_raw="$1"
local single_output_mode="${2:-true}"
local win_id_dec=$((win_id_raw))
local win_id_hex
printf -v win_id_hex "0x%x" "$win_id_dec"
# Run xprop; if it fails or returns empty, skip this window
xprop_output=$(xprop -id "$win_id_dec" 2>/dev/null || true)
if [[ -z "$xprop_output" ]]; then
return
fi
title="$(xtitle "$win_id_dec")"
# BSPWM info
info=$(bspc query -T -n "$win_id_dec" 2>/dev/null || true)
if [[ -z "$info" || "$info" == "null" ]]; then
printf "${RED}Window %s is not managed by bspwm.${NC}\n" "$win_id_dec"
return
fi
# Client state values
layer=$(jq -r '.client.layer // "unknown"' <<<"$info")
state=$(jq -r '.client.state // "unknown"' <<<"$info")
class_name=$(jq -r '.client.className // "unknown"' <<<"$info")
instance_name=$(jq -r '.client.instanceName // "unknown"' <<<"$info")
urgent=$(jq -r '.client.urgent // false' <<<"$info")
shown=$(jq -r '.client.shown // false' <<<"$info")
hidden=$(jq -r '.hidden // false' <<<"$info")
focused=$(jq -r '.focused // false' <<<"$info")
# Additional requested states
sticky=$(jq -r '.sticky // false' <<<"$info")
locked=$(jq -r '.locked // false' <<<"$info")
private=$(jq -r '.private // false' <<<"$info")
marked=$(jq -r '.marked // false' <<<"$info")
vacant=$(jq -r '.vacant // false' <<<"$info")
# Geometry (rectangle)
x=$(jq -r '.rectangle.x' <<<"$info")
y=$(jq -r '.rectangle.y' <<<"$info")
width=$(jq -r '.rectangle.width' <<<"$info")
height=$(jq -r '.rectangle.height' <<<"$info")
# Desktop and monitor resolution
desktop_id=$(bspc query -D -n "$win_id_dec" | head -n 1)
monitor_id=$(bspc query -M -n "$win_id_dec" | head -n 1)
if [[ -n "$desktop_id" ]]; then
desktop_name=$(bspc query -T -d "$desktop_id" | jq -r '.name')
else
desktop_name="unknown"
fi
if [[ -n "$monitor_id" ]]; then
monitor_name=$(bspc query -T -m "$monitor_id" | jq -r '.name')
else
monitor_name="unknown"
fi
node_id=$(jq -r '.id' <<<"$info")
###############################################
# Output block
###############################################
# printf "${UNDERLINE}${BOLD}Window Information${NC}\n"
if [[ "$single_output_mode" != 'true' ]]; then
printf "\n${MAGENTA}--------------------------------------------${NC}\n\n"
fi
printf "${BOLD}Window ID${NC}\n"
printf " %-15s: ${CYAN}%s${NC}\n" "Decimal" "$win_id_dec"
printf " %-15s: ${CYAN}%s${NC}\n" "Hex" "$win_id_hex"
printf "${BOLD}Window Info${NC}\n"
printf " %-15s: ${CYAN}%s${NC}\n" "Node ID" "$node_id"
printf " %-15s: %s\n" "Class" "$class_name"
printf " %-15s: %s\n" "Instance" "$instance_name"
printf " %-15s: %s\n" "Title" "$title"
printf "${BOLD}Geometry${NC}\n"
printf " %-15s: (%s, %s)\n" "X / Y" "$x" "$y"
printf " %-15s: %s\n" "Width" "$width px"
printf " %-15s: %s\n" "Height" "$height px"
printf "${BOLD}Location${NC}\n"
printf " %-15s: %s\n" "Monitor" "$monitor_name"
printf " %-15s: %s\n" "Desktop" "$desktop_name"
printf "${BOLD}BSPWM Node State${NC}\n"
# Core states
printf " %-15s: %s\n" "State" "$(state_fmt "$state")"
printf " %-15s: %s\n" "Layer" "$(layer_fmt "$layer")"
printf " %-15s: " "Shown"
bool "$shown"
printf "\n"
printf " %-15s: " "Focused"
bool "$focused"
printf "\n"
printf " %-15s: " "Hidden"
bool "$hidden"
printf "\n"
printf " %-15s: " "Urgent"
bool "$urgent"
printf "\n"
printf " %-15s: " "Sticky"
bool "$sticky"
printf "\n"
printf " %-15s: " "Locked"
bool "$locked"
printf "\n"
printf " %-15s: " "Private"
bool "$private"
printf "\n"
printf " %-15s: " "Marked"
bool "$marked"
printf "\n"
printf " %-15s: " "Vacant"
bool "$vacant"
printf "\n"
# printf "${BOLD}BSPWM Instance Name${NC}\n"
# printf " %s\n" "$instance_name"
if [[ "$single_output_mode" == "true" ]]; then
printf "\n${BOLD}Example bspwm rule${NC}\n"
printf " bspc rule -a '%s:%s' state=%s layer=%s\n" \
"$class_name" "$instance_name" "$state" "$layer"
fi
}
###############################################
# CLI flags
###############################################
ALL_MODE=false
while [[ $# -gt 0 ]]; do
case "$1" in
-a | --all)
ALL_MODE=true
shift
;;
*)
# If user passes a window id manually
win_id_raw="$1"
shift
;;
esac
done
###############################################
# Run modes
###############################################
# Mode 1: all windows
if [[ "$ALL_MODE" == true ]]; then
for wid in $(bspc query -N -n .window); do
wid_dec=$((wid))
inspect_window "$wid_dec" false
done
exit 0
fi
# Mode 2: single window (selected or provided)
if [[ ! ${win_id_raw+x} ]]; then
win_id_raw=$(select_window)
fi
inspect_window "$win_id_raw" true
@eliliam
Copy link
Author

eliliam commented Dec 10, 2025

Bspwm Window Inspector

A simple and readable command line utility for inspecting window information on bspwm.
This tool lets you click any window or supply a window ID and instantly displays:

  • Window ID (decimal and hex)
  • Node window class and instance values (properly formatted and deduplicated)
  • Monitor, desktop, and bspwm node ID
  • Geometry (position and size)
  • Full bspwm client state flags (tiled, floating, fullscreen, sticky, locked, private, marked, vacant, etc.)
  • Focused, shown, hidden, urgent flags
  • Class and instance names
  • An auto generated bspc rule template for easy configuration

Highlights

  • Interactive window picker
    Click any window to inspect it.

  • Pass a window ID manually
    Useful for scripting, keybindings, or when you already know the ID.
    Example: window-info 0x02A00004

  • Print info for all bspwm windows
    Using the -a or --all flag, the tool iterates through every managed window and displays a separate information block for each.
    Example: window-info --all

  • Colorized output
    Automatically disabled when piping or redirecting.

Example Use

$ window-info
Click the window you want to inspect...
Window selected.

Window ID
  Decimal        : 39845899
  Hex            : 0x260000b
Window Info
  Node ID        : 39845899
  Class          : kitty
  Instance       : kitty
  Title          : window-info

Geometry
  X / Y          : (15, 60)
  Width          : 1420 px
  Height         : 2500 px

Location
  Monitor        : DP-2
  Desktop        : 2

BSPWM Node State
  State          : tiled
  Layer          : normal
  Shown          : true
  Focused        : false
  Hidden         : false
  Urgent         : false
  Sticky         : false
  Locked         : false
  Private        : false
  Marked         : false
  Vacant         : false

Example bspwm rule
  bspc rule -a 'kitty:kitty' state=tiled layer=normal

Why this exists

Bspwm exposes detailed node information through bspc query -T, but the raw JSON can be verbose, and it requires knowing the node ID in advance.
This script makes the most important information easily readable, works interactively or non-interactively, and provides a ready to copy rule for quick configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment