A typical use case is when an editor is setting its window title to the full long path name such that the actual important part, the file name, falls off the edge on the right and becomes invisible. Unfortunately the X11 window title is normally set internally by the application and its format is hard coded. A solution is to monitor the editor window title and modify it if its becomes too long.
There are command line tools (xprop, xdotool,etc.) that could be used in a bash script to query and modify X11 properties, but they are rather unwieldy to use. A short Python programme is probably a better solution:
sudo apt install python3-xlib
cat << EOF > ~/.local/bin/update_window_title_v1.0.py
#!/usr/bin/python3
"""
Rename X11 windows if they have too long names
Written by Z J Laczik
Modify the following defines to fine tune behaviour :
src_pattern = r"/.+/"
replacement = "/.../"
limit = 16
period = 2
N.B. Be careful modifying src_pattern and replacement!
Wrong choice may result in the modification of all window names,
setting window names to '', or ending up with infinitely long
window names ptoentially crashing your window manager.
Change log
Date: 20220609 Version: 1.0
First release
"""
import time
import re
from Xlib import display
# change debug to True to enable debug messages
debug = False
# debug = True
def get_window_name( window ) :
# try and get utf-8 window name first, if that fails, try and get legacy name
for ( atom, prop_type ) in ( ( NET_WM_NAME, UTF8_STRING), ( WM_NAME, STRING ) ) :
try :
prop = window.get_full_property( atom, prop_type )
except UnicodeDecodeError :
window_name = "<could not decode characters>"
else :
# check whether get_full_property was succesful
# if not, set default name and try next atom
if prop :
# extract value data from property object
window_name = prop.value
# at this point window_name should be a string or a byte array
# it it is bytes then convert to string
if isinstance( window_name, bytes ) :
if ( atom == NET_WM_NAME ) :
# decode bytes as UTF-8
window_name = window_name.decode( 'utf-8', 'replace' )
else :
# decode bytes as ASCII
window_name = window_name.decode( 'ascii', 'replace' )
return window_name
else :
# set default return value
window_name = "<unnamed window>"
# we should only get here if neither atoms worked, hence return default
return window_name
def set_window_name( window, name ) :
try :
# set UTF-8 window name
window.change_property( NET_WM_NAME, UTF8_STRING, 8, name.encode( 'utf-8', 'replace' ) )
# set legacy ASCII name
window.change_property( WM_NAME, STRING, 8, name.encode( 'ascii', 'replace' ) )
except :
pass
def check( window, src_pattern = r"/.+/", replacement = "/.../", limit = 16 ) :
# get list of child windows
window_list = window.query_tree().children
# check each child window
for window in window_list :
# get current window name
old_name = get_window_name( window )
# check if length is above limit and if it matches replacement regex
if ( len( old_name ) > limit ) :
( new_name, matches ) = re.subn( src_pattern, replacement, old_name );
if ( matches > 0 and new_name != old_name ) :
set_window_name( window, new_name )
if debug :
print( "> Replaced '%s' with '%s'" % ( old_name, new_name ) )
# recursively check child windows below current window
check( window, src_pattern, replacement, limit )
# get display and root window
dsp = display.Display()
root_window = dsp.screen().root
# get various atoms
UTF8_STRING = dsp.get_atom( 'UTF8_STRING' ) # property type utf-8
STRING = dsp.get_atom( 'STRING' ) # property type string
NET_WM_NAME = dsp.get_atom( '_NET_WM_NAME' ) # utf-8 encoded window name
WM_NAME = dsp.get_atom( 'WM_NAME' ) # legacy ascii encoded window name
# define regex pattern to use for window name matching
src_pattern = r"/.+/"
# define replacement string
replacement = "/.../"
# length limit, replace window name only if longer than limit
limit = 16
# repeat check every 'period' seconds
period = 2
# start main loop checking all window names and
# changing them if there is a match and they are too long
while True :
# check() starts with the root window and then recursively traverses all child windows
check( root_window, src_pattern, replacement, limit )
# wait until next check
time.sleep( period )
EOF
chmod +x ~/.local/bin/update_window_title_v1.0.py
update_window_title_v1.0.py
The above Python code can also be downloaded here.
Examples for using the standard command line tools:
xprop | grep -i id
xprop | grep -i name
xdotool set_window --name ZJL 79698557
xprop -format WM_NAME 8s -set WM_NAME "new name"
xprop -format _NET_WM_NAME 8u -set _NET_WM_NAME "new name"
These commands can also be handy when testing the Python code.