i3wm: Using i3-ipc to Float Windows
Budlabs showing how to float windows by
using an i3 ipc listener script.
My coding of an i3 ipc listener which automatically
scales floating windows in proportion to screen
Update 2021-03-20: Updated Code! The Python code shown below has been updated to use a better method of reading ipc to find the floating mode. Use this or get the latest listener i3-script from github.com/aB9IL
Recently, I have been tweaking the i3 configuration in my machines, seeking to reduce latency and achieve smoother operation. The defaults and conventional setup are fine (no problems), but my intention is to rice the system and do numerous little things which can add latency reveal quirks in an otherwise smooth and fast window manager. Let us look into the issue as it relates to opening some specific applications in floating windows while other applications open in the tabbed or tiled layout.
It is the i3 config file which is used by most people to set the default layout options and any settings which differ for specific applications. For example, my preference is for terminals, web and file browsers, and certain utilities to open floating above the other tiled windows. To open certain windows in floating mode, the config file would have lines like this:
for_window [class="gnome-terminal"] floating enable for_window [class="URxvt"] floating enable
Budlabs posted a Youtube video describing a phenomenon where a visible glitch occurs when a new window is opened or if the title or class changes. What happens is that i3 begins to make the changes associated with the new window or changed title, but takes a number of milliseconds to read all of the the for_window rules in the config file. Those rules are searched to determine if there is one applicable to the window being opened or changed. What can be done to eliminate the glitch?
Budlabs crafted a clever solution in which the for_window rules are replaced with a short bit of code in a separate script, using interprocess communication. This means it is necessary to run an i3-ipc process to monitor what i3 is doing and intervene with a command to handle the opening of a new window. The solution is fast, efficient, and smooth. Any of the available ipc listeners may be used to recognize the time when a new window is being opened. Then, I use a Python script similar to what Budlabs uses, but with changes to limit floating mode to specific windows.
Mister Bud used the following code, within a listener script, to find window opening events:
Here is the Python function to actually enable all new windows to float instead of tile or tab:
def set_floating(i3, event): event.container.command('floating enable')
Being one who prefers to have most windows not float, I set out to find a way to cause the funtion to only enable floating if a new window was in a list given in the script. Here is what works well:
# a hard coded list of windows to float by default FLOATERS = ['Mate-terminal', 'Firefox', 'vlc', 'Caja', 'URxvt', 'UXTerm', 'xterm'] def window_resize(i3, event): focused = i3.get_tree().find_focused().floating if focused == "user_on": event.container.command('floating enable, resize set 1000 700, \ move absolute position center') break
The essential idea is to compare the new window class to names in the list, and if there is a match, then forget about the remainder of the list and get on with floating the window. In fact, float the window, resize it moderately, and center it on the screen.
Automatic Scaling for i3 Floating Windows
To make the linux system more usable across different machines, or for convenience if changing monitors / screen resolution, use a different setup and add another Python module to the listener script.
Put the desired percent scaling (width and height) into the i3 config file as arguments to call the i3-listener:
# use a listener script to invoke tools using i3-ipc; include # arguments for percentage scaling of floating window WIDTH and HEIGHT /usr/bin/i3-listener 75 85
Install the PyAutoGUI python module, which will be used to get the current screen resolution. Then, use the following version of i3-listener to calculate floating window dimensions and execute a function to center and resize floating windows. In this script, windows will not float by default. Instead, the user must manually toggle floating for a window. When the window renders, it will be scaled and centered.
#!/usr/bin/env python3 import sys import pyautogui from i3ipc import Connection, Event i3 = Connection() SCREEN_WIDTH, SCREEN_HEIGHT = pyautogui.size() WINDOW_WIDTH = int(int(SCREEN_WIDTH) * (float(sys.argv) * 0.01)) WINDOW_HEIGHT = int(int(SCREEN_HEIGHT) * (float(sys.argv) * 0.01)) WINDOW_COMMAND = 'resize set ' + WINDOW_WIDTH + ' ' + WINDOW_HEIGHT + \ ', move absolute position center' def window_resize(i3, event): focused = i3.get_tree().find_focused().floating if focused == "user_on": event.container.command(WINDOW_COMMAND) # Float windows manually, but center and size as # a proportion of screen resolution. i3.on('window::floating', window_resize) i3.main()
There are possible ways to make this more efficient or user friendly. One way is to change from a hard coded list to a user editable list kept in the home directory. That could be read on login, for example. For faster execution, these tasks could be accomplished in a binary compiled from Go instead of running in Python. In this instance the latency in question is a matter of milliseconds, but milliseconds add up. If the entire system is crafted with the intention of minimizing delays, it can be beautifully responsive.