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.