Last active
December 11, 2025 00:20
-
-
Save eliliam/0902fbdba2918a0676df3b94576d6b73 to your computer and use it in GitHub Desktop.
Bspwm Window Inspector: A Clean CLI Tool for Inspecting Window Properties
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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 |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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:
bspc ruletemplate for easy configurationHighlights
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 0x02A00004Print info for all bspwm windows
Using the
-aor--allflag, the tool iterates through every managed window and displays a separate information block for each.Example:
window-info --allColorized output
Automatically disabled when piping or redirecting.
Example Use
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.