Script for Window tiling in openbox

I wanted to use a tiling window manager but didn't want to leave openbox. So I started using PyTyle and PyTyle2 but they both took up more RAM than I could afford to let them use in my lowly desktop. So I wrote a script to do my tiling for me using wmctrl and xwininfo. And here are the results:GRID mode:

The script itself is rather poorly written as I don't have much experience in bash.

#!/bin/bash
# use wmctrl and xwininfo to tile windows
#currently supports the "LEFT_MASTER" mode (default),
# "RIGHT_MASTER" mode (-r) ,
# "TOP_MASTER" mode (-t),
#"BOTTOM_MASTER" mode (-b) ,
#"GRID" mode (-g),
#"MAX" mode (-m) ,
# "VERTICAL_SPLIT" mode (-v) and
#"HORIZONTAL_SPLIT" mode (-h)
#NOTE: wmctrl sets the properties of the window itself i.e. the window manager's borders
#and title bars are not taken into account while setting the coordinates. So for best results
#set the window manager to display all windows undecorated
#or manually set the title bar height here
#List of things to do:
# 1. Get the values of the TOP_MARGIN, BOTTOM_MARGIN, LEFT_MARGIN and RIGHT_MARGIN from the window manager
# 2. Get the MASTER_WIDTH for the default LEFT_MASTER, RIGHT_MASTER layouts as a parameter , set a default if not provided
# 3. Get the MASTER_HEIGHT for the default TOP_MASTER, BOTTOM_MASTER layouts as a parameter , set a default if not provided
# 4. Get the NUMBER_OF_ROWS for the grid mode as a parameter, set a default if not provided
# 5. Get the TITLE_BAR_HEIGHT from the window manager
# 6. Restructure the code to use functions
# 7. Provide mechanisms to increase/decrease the MASTER area
# 8. Provide mechanisms to increase/decrease the number of windows in the MASTER area
#List of bugs
# 1. If called from a key combination, there may not be a :ACTIVE: window and in which case the results are not as desired.
# 2. The title bar plays havoc with the height of windows
#need to find a way to get these from the window manager
TOP_MARGIN=18
BOTTOM_MARGIN=18
LEFT_MARGIN=0
RIGHT_MARGIN=0
TITLE_BAR_HEIGHT=17 #works best if all the windows are border less and this is set to zero
#we are now using xwininfo to get these
#HEIGHT=1080
#WIDTH=1920
#either set these in a file or calculate them from the screen properties
MASTER_WIDTH=1344
MASTER_HEIGHT=600
#set the number of rows we want for the grid mode
NUMBER_OF_ROWS=2
#looks nice :)
USELESS_GAPS=1
#see what the user wants to do
case $1 in
"-g")
MODE="GRID"
;;
"-m")
MODE="MAX"
;;
"-v")
MODE="VERTICAL_SPLIT"
;;
"-h")
MODE="HORIZONTAL_SPLIT"
;;
"-t")
MODE="TOP_MASTER"
;;
"-b")
MODE="BOTTOM_MASTER"
;;
"-r")
MODE="RIGHT_MASTER"
;;
*)
MODE="LEFT_MASTER"
;;
esac
#get the desktop parameters
HEIGHT=`xwininfo -root | grep 'Height:' | awk '{print $2}'`
WIDTH=`xwininfo -root | grep 'Width:' | awk '{print $2}'`
#get the window parameters
#get the current desktop
CURR_DESK=` wmctrl -d | grep '* DG:'| awk '{print $1}'`
#get the total number of windows.
#NOTE: we are not directly using grep to get the windows from the current desktop as it may serve up some false positives
TOTAL_WINDOWS=`wmctrl -lx | wc -l`
#counter
i=1
#Assume that there are no windows in the current desktop
WINDOWS_IN_DESK=0
while [ $i -le $TOTAL_WINDOWS ] ; do
CURR_LINE=`wmctrl -lx | head -n $i | tail -n 1`
WIN_DESK=`echo $CURR_LINE | awk '{print $2}'`
if [ $WIN_DESK -eq $CURR_DESK ] ; then
#save the various window properties as supplied by wmctrl . Un comment rest if necessary. Include more if necessary
WIN_XID[$WINDOWS_IN_DESK]=`echo $CURR_LINE | awk '{print $1}'`
# WIN_XOFF[$WINDOWS_IN_DESK]=`echo $CURR_LINE | awk '{print $2}'`
# WIN_YOFF[$WINDOWS_IN_DESK]=`echo $CURR_LINE | awk '{print $3}'`
# WIN_WIDTH[$WINDOWS_IN_DESK]=`echo $CURR_LINE | awk '{print $4}'`
# WIN_HEIGHT[$WINDOWS_IN_DESK]=`echo $CURR_LINE | awk '{print $5}'`
#see if the window is "IsViewable" or "IsUnMapped" i.e minimized
MAP_STATE=`xwininfo -id ${WIN_XID[$WINDOWS_IN_DESK]} | grep "Map State:" | awk '{print $3}'`
#we don't want the minimized windows to be considered while allocating the space
if [ "$MAP_STATE" == "IsViewable" ]; then
WINDOWS_IN_DESK=$((WINDOWS_IN_DESK+1))
fi
fi
i=$((i+1))
done
#get the xid of the active window.
ACTIVE_WIN=`xprop -root | awk '/_NET_ACTIVE_WINDOW\(WINDOW\)/{print $NF}'`
#set the selected layout
#NOTE: these would look better if they were individual functions :)
case $MODE in
"LEFT_MASTER")
#define the properties of the master area
X=$LEFT_MARGIN
Y=$TOP_MARGIN
#set the width to the default MASTER_WIDTH
W=$MASTER_WIDTH
H=$(( HEIGHT -TOP_MARGIN - BOTTOM_MARGIN -TITLE_BAR_HEIGHT -USELESS_GAPS))
#set the active window to the "master "area
wmctrl -r :ACTIVE: -e "0,$X,$Y,$W,$H"
#now that the master window has been set all further windows would have to start from here
X=$((MASTER_WIDTH+USELESS_GAPS))
#get whatever width is left
W=$((WIDTH - MASTER_WIDTH -USELESS_GAPS))
#the height would be equally shared by the rest of the windows
H=$((H/(WINDOWS_IN_DESK - 1) - TITLE_BAR_HEIGHT -USELESS_GAPS ))
i=0
while [ "$i" -le "$WINDOWS_IN_DESK" ] ; do
#avoid setting the attributes of the active window again
if [[ "${WIN_XID[$i]}" -ne "$ACTIVE_WIN" ]] ; then
#set the attributes
wmctrl -i -r ${WIN_XID[$i]} -e "0,$X,$Y,$W,$H"
#set the Y co-ordinate for the next window.
Y=$((Y+H+TITLE_BAR_HEIGHT+USELESS_GAPS))
fi
#preselect the next window
i=$((i+1))
done
;;
"RIGHT_MASTER")
#define the properties of the master area
X=$LEFT_MARGIN
Y=$TOP_MARGIN
#get whatever width is left
W=$((WIDTH - MASTER_WIDTH -USELESS_GAPS))
#the height would be equally shared by the non master windows
H=$(((HEIGHT -TOP_MARGIN -BOTTOM_MARGIN )/(WINDOWS_IN_DESK-1) -TITLE_BAR_HEIGHT -USELESS_GAPS))
i=0
while [ "$i" -le "$WINDOWS_IN_DESK" ] ; do
#avoid setting the attributes of the active window
if [[ "${WIN_XID[$i]}" -ne "$ACTIVE_WIN" ]] ; then
#set the attributes
wmctrl -i -r ${WIN_XID[$i]} -e "0,$X,$Y,$W,$H"
#set the Y co-ordinate for the next window.
Y=$((Y+H+TITLE_BAR_HEIGHT+USELESS_GAPS))
fi
#preselect the next window
i=$((i+1))
done
#set the co-ordinates for the MASTER_WINDOW
X=$((W+USELESS_GAPS))
Y=$TOP_MARGIN
W=$MASTER_WIDTH
H=$(( HEIGHT -TOP_MARGIN - BOTTOM_MARGIN -TITLE_BAR_HEIGHT -USELESS_GAPS))
#set the active window to the "master "area
wmctrl -r :ACTIVE: -e "0,$X,$Y,$W,$H"
;;
"TOP_MASTER")
#define the properties of the master area
X=$LEFT_MARGIN
Y=$TOP_MARGIN
#set the width taking into acount the margins
W=$((WIDTH-LEFT_MARGIN-RIGHT_MARGIN))
H=$MASTER_HEIGHT
#set the active window to the "master "area
wmctrl -r :ACTIVE: -e "0,$X,$Y,$W,$H"
#set the y co-ordinate
Y=$((Y+H+USELESS_GAPS+TITLE_BAR_HEIGHT))
#Distribute the width amon the remaining windows
W=$((W/(WINDOWS_IN_DESK-1)))
#set the new height
H=$((HEIGHT-MASTER_HEIGHT-TOP_MARGIN-BOTTOM_MARGIN-TITLE_BAR_HEIGHT-USELESS_GAPS))
i=0
while [ "$i" -le "$WINDOWS_IN_DESK" ] ; do
#avoid setting the attributes of the active window again
if [[ "${WIN_XID[$i]}" -ne "$ACTIVE_WIN" ]] ; then
#set the attributes
wmctrl -i -r ${WIN_XID[$i]} -e "0,$X,$Y,$W,$H"
#set the X co-ordinate for the next window.
X=$((X+W+USELESS_GAPS))
fi
#preselect the next window
i=$((i+1))
done
;;
"BOTTOM_MASTER")
#define the properties of the master area
X=$LEFT_MARGIN
Y=$TOP_MARGIN
#Distribute the width among the non master windows
W=$(((WIDTH-LEFT_MARGIN-RIGHT_MARGIN)/(WINDOWS_IN_DESK-1)))
#set the new height
H=$((HEIGHT-MASTER_HEIGHT-TOP_MARGIN-BOTTOM_MARGIN-TITLE_BAR_HEIGHT-USELESS_GAPS))
i=0
while [ "$i" -le "$WINDOWS_IN_DESK" ] ; do
#avoid setting the attributes of the active window again
if [[ "${WIN_XID[$i]}" -ne "$ACTIVE_WIN" ]] ; then
#set the attributes
wmctrl -i -r ${WIN_XID[$i]} -e "0,$X,$Y,$W,$H"
#set the X co-ordinate for the next window.
X=$((X+W+USELESS_GAPS))
fi
#preselect the next window
i=$((i+1))
done
#set the co-ordinates
X=$LEFT_MARGIN
Y=$((Y+H+USELESS_GAPS+TITLE_BAR_HEIGHT))
W=$((WIDTH-LEFT_MARGIN-RIGHT_MARGIN))
H=$MASTER_HEIGHT
#set the active window to the "master "area
wmctrl -r :ACTIVE: -e "0,$X,$Y,$W,$H"
;;
"GRID")
#find the number of windows in the top row and in each subsequent row except for the bottom row.
NORMAL_ROW_WINDOWS=$((WINDOWS_IN_DESK/NUMBER_OF_ROWS))
#the bottom row ould have as many windows as the top row and any left over
BOTTOM_ROW_WINDOWS=$((NORMAL_ROW_WINDOWS + WINDOWS_IN_DESK%NUMBER_OF_ROWS))
WINDOWS_NOT_IN_BOTTOM_ROW=$((WINDOWS_IN_DESK-BOTTOM_ROW_WINDOWS))
#set the co-ordinates for the top row
X=$LEFT_MARGIN
Y=$TOP_MARGIN
#the height of each window would remain the same regardless of which row it is in
H=$(((HEIGHT-TOP_MARGIN-BOTTOM_MARGIN)/NUMBER_OF_ROWS - TITLE_BAR_HEIGHT))
#Find the width of each window in the top row, this would be the same for each row except for the bottom row which may contain more windows
NORMAL_ROW_WIDTH=$((((WIDTH-LEFT_MARGIN-RIGHT_MARGIN)/NORMAL_ROW_WINDOWS)-USELESS_GAPS*NORMAL_ROW_WINDOWS))
BOTTOM_ROW_WIDTH=$((((WIDTH-LEFT_MARGIN-RIGHT_MARGIN)/BOTTOM_ROW_WINDOWS)-USELESS_GAPS*NORMAL_ROW_WINDOWS))
#start counting from zero
i=0
#we havent processed any windows yet
CURRENT_ROW_WINDOWS=0
#we will be processing the 1st row
CURRENT_ROW=1
while [ "$i" -le "$WINDOWS_IN_DESK" ] ; do
if [[ "$CURRENT_ROW" -lt "$NUMBER_OF_ROWS" ]]; then
if [[ "$CURRENT_ROW_WINDOWS" -eq "NORMAL_ROW_WINDOWS " ]]; then
CURRENT_ROW=$((CURRENT_ROW+1))
if [[ "$CURRENT_ROW" -eq "$NUMBER_OF_ROWS" ]] ; then
X=$LEFT_MARGIN
Y=$((Y+H+TITLE_BAR_HEIGHT+USELESS_GAPS))
W=$BOTTOM_ROW_WIDTH
else
CURRENT_ROW_WINDOWS=0
fi
fi
if [[ "$CURRENT_ROW_WINDOWS" -eq "0" ]] ; then
X=$LEFT_MARGIN
W=$NORMAL_ROW_WIDTH
if [[ "$CURRENT_ROW" -ne "1" ]]; then
Y=$((Y+H+TITLE_BAR_HEIGHT+USELESS_GAPS))
fi
fi
fi
wmctrl -i -r ${WIN_XID[$i]} -e "0,$X,$Y,$W,$H"
X=$((X+W+USELESS_GAPS))
CURRENT_ROW_WINDOWS=$((CURRENT_ROW_WINDOWS+1))
i=$((i+1))
done
;;
"MAX")
X=$LEFT_MARGIN
Y=$TOP_MARGIN
H=$((HEIGHT-TOP_MARGIN-BOTTOM_MARGIN))
W=$((WIDTH-LEFT_MARGIN-RIGHT_MARGIN))
i=0
while [ "$i" -le "$WINDOWS_IN_DESK" ] ; do
#avoid setting the attributes of the active window
if [[ "${WIN_XID[$i]}" -ne "$ACTIVE_WIN" ]] ; then
#set the attributes
wmctrl -i -r ${WIN_XID[$i]} -e "0,$X,$Y,$W,$H"
fi
#preselect the next window
i=$((i+1))
done
#now that all the windows have been set set the master on top
wmctrl -r :ACTIVE: -e "0,$X,$Y,$W,$H"
;;
"VERTICAL_SPLIT")
X=$LEFT_MARGIN
Y=$TOP_MARGIN
H=$((HEIGHT-TOP_MARGIN-BOTTOM_MARGIN))
W=$(((WIDTH-LEFT_MARGIN-RIGHT_MARGIN)/WINDOWS_IN_DESK - USELESS_GAPS))
i=0
while [ "$i" -le "$WINDOWS_IN_DESK" ] ; do
wmctrl -i -r ${WIN_XID[$i]} -e "0,$X,$Y,$W,$H"
#preselect the next window
X=$((X+W+USELESS_GAPS))
i=$((i+1))
done
;;
"HORIZONTAL_SPLIT")
X=$LEFT_MARGIN
Y=$TOP_MARGIN
H=$(((HEIGHT-TOP_MARGIN-BOTTOM_MARGIN)/WINDOWS_IN_DESK -TITLE_BAR_HEIGHT -USELESS_GAPS))
W=$((WIDTH-LEFT_MARGIN-RIGHT_MARGIN))
i=0
while [ "$i" -le "$WINDOWS_IN_DESK" ] ; do
wmctrl -i -r ${WIN_XID[$i]} -e "0,$X,$Y,$W,$H"
#preselect the next window
Y=$((Y+H+TITLE_BAR_HEIGHT+USELESS_GAPS))
i=$((i+1))
done
;;
esac
#say bye
exit 0

Re: Script for Window tiling in openbox

Thanks for your work.But the window resizing in vertical mode needs some work.For eg when chromium and terminals are open wmtiler -v put the terminal on the top of chromium and chromium is not getting resized.

Re: Script for Window tiling in openbox

Could you post a screenshot of the problem? I couldn't replicate it from what you said. Technically if you mention the TITLE_BAR_HEIGHT and use chromium without system borders then there is going to be a mismatch in the height. Also I tried to make the script generic but i haven't yet tested it on different monitor resolutions, so there should be more bugs than I have come across :(

Re: Script for Window tiling in openbox

Hey, thanks for writing a script like that. Openbox needs a little love in the department auf ondemand tiling.I know of only one other script like yours, it's written in python2 and called 'simple window tiler' or stiler - an arch contribution too, but no longer developed.

But to get back to your script, for me, the following layouts work flawlessly (most of the time ):horizontal_splitvertical_splitgridmax

As you can see, one window doesn't get tiled with the others. Also, left/right and top/bottom are identical.

My settings:

TOP_MARGIN=0
BOTTOM_MARGIN=0
LEFT_MARGIN=0
RIGHT_MARGIN=0
TITLE_BAR_HEIGHT=0
#we are now using xwininfo to get these
#HEIGHT=1080
#WIDTH=1920
#either set these in a file or calculate them from the screen properties
#MASTER_WIDTH=700
#MASTER_HEIGHT=450
#set the number of rows we want for the grid mode
NUMBER_OF_ROWS=2
#looks nice :)
USELESS_GAPS=0

wmtiler also seems to have trouble with maximized windows, for instance maximized windows sometimes won't be tiled. some other random occurences:* two windows were killed while trying to switch to grid from a maximized window (the maximized one survived and went to the proper place in the grid layout, the other two were gone)* one window was locked vertically. It used the whole width and i could not resize it. only move it up and down, not left or right or anything inbetween.* one time, urxvt showed

I assume these are mostly bugs with wmctrl/xdotool but you might want to know about them.

Re: Script for Window tiling in openbox

I see that you have the master width and master height values commented out

#MASTER_WIDTH=700
#MASTER_HEIGHT=450

Did the tiling fail with these un-commented too? Currently the script requires these parameters to be set manually as I haven't yet got round to incorporating the increase/decrease master area by x% functions.

Regarding the maximized windows not being tiled, I noticed this bug after my initial post. I think wmctrl can be used to fix this. I'll get back to you as soon as I can get it working.EDIT: Hopefully fixed! Please try it now : wmtiler.sh

Regarding the other issues, could you give me a bit more details as to what windows were open etc, so that I can try to replicate and hopefully fix the bugs.

Re: Script for Window tiling in openbox

I'll keep on using wmtiler and see if i can put together more elaborate "bug reports". What kind of information exactly would help besides the description? Logs, command outputs?

As for the master thing i feel really stupid now. I should have tried uncommenting them. Now the master layouts work, too.

In the last few minutes of using the new version with various layouts with varying amounts of windows open I didn't run into any bugs so I guess that's good. Tiling maximised windows works too now.

If you take feature requests, I really miss the following features compared to xmonad/awesome etc:* untiling of all windows* increasing/decreasing amount of master windows* increasing/decreasing width/height of master window* tiling just the focused window to 50% of the screen width at max height (like windows 7 does when you move a window over left/right screen edge). it's actually the tiling feature i use the most and something i miss in tiling window managers. it can be done with stiler though so there's no need to have it in wmtiler too - unless you want to of course.This is what that would look like:left http://i53.tinypic.com/144aip.jpgright http://i56.tinypic.com/2lmsxn6.jpg

Regards,demian

P.S.: Regarding the bugs i described in the previous post. I never had more than 3 windows open. One of them was always firefox, one other rxvt-unicode and the last one was either pcmanfm, medit or another instance of rxvt-unicode.

One bug(?) I noticed just now is that iconified windows won't get tiled. However, they still get taken into account so they occupy space but don't show up.

As you can see, for me , the "Map State:" is "IsUnMapped" for the iconified windows and this set of commands is exactly what I am doing to ignore iconified windows. What I need to know is that is there some other type of map state that I need to ignore.

As for the feature requests I already had it in my mind to do the first three points that you mentioned. Since the script is intentionally designed not to run as a background process, there has to be some sort of a backing store like maybe a simple text file in which to save the state and read it from there on the next command invocation. The advantage would be it would take a momentary spike in CPU utilization and no sustained RAM utilization like I mentioned before. Disadvantage being the user would have to issue a command to actually get the tiling done.

Regarding your fourth point, did you mean setting the window to 50% width automatically on moving it to the screen edge like it is done in KWIn and Win7? If you want it to be done automatically then that would involve tracking the movement of the mouse and windows on the screen continuously. This is something I'd rather do in some other, tinier faster, script and call this script from that on specific events. However, if you are willing to press a key while focused on a window and have it resized to max height and 50% of screen width either on the right side or on the left side, depending on the key combination, while leaving the other windows unaffected, then that can be arranged very simply.

Re: Script for Window tiling in openbox

I. Steps to reproduce 1. Open at least two windows 2. Tile them at least once 3. Open a new window 4. Iconify that window 5. Now tile a few times, with different layouts

II. The last opened window, when restored, should now be somewhere at a bottom corner of the screen, with only the top third visible.

B. Second partIf you have just one open window and you tile it with any other layout than max all, vertical_split or horizontal split the window will only occupy the master space. I assumed a different behaviour which is maximizing the window to use the whole screen.So what must have happened is that i had 1 window open, then opened another one, which I iconified. Then the "bug" described in A. occured where the iconified window would move to the bottom right corner of the screen, resized to fit the slave part of the screen (as opposed to the master part). When I moved it i noticed it fit the slave space perfectly and assumed the tiling had gone wrong.

However, if you are willing to press a key while focused on a window and have it resized to max height and 50% of screen width either on the right side or on the left side, depending on the key combination, while leaving the other windows unaffected, then that can be arranged very simply.

Yup, that's what i meant. It works quite nicely in combination with

<followMouse>yes</followMouse>

It's very handy for any master layout as you just have to move your mouse over the window you want as master then press the hotkey for the desired layout.

P.S.:If I could make one more suggestion for a feature it would be to swap windows (meaning place as well as size get swapped with the window that's focused while calling the function).

Re: Script for Window tiling in openbox

Fixed the bug!! Thanks for the detailed description. Hopefully the fix didn't introduce any new bug.Latest version : wmtiler.sh

The latest version also contains the ACTIVE_LEFT (wmtiler.sh -q) and ACTIVE_RIGHT (wmtiler.sh -e) modes that you asked for. ACTIVE_LEFT simply puts the currently active window to the left part of the screen taking up max height minus any margins and 50% of the available width, while leaving the other windows unaffected. ACTIVE_RIGHT does the same on the right.

demian wrote:

It's very handy for any master layout as you just have to move your mouse over the window you want as master then press the hotkey for the desired layout.

All the master layouts already behave in the way you mentioned

demian wrote:

If I could make one more suggestion for a feature it would be to swap windows (meaning place as well as size get swapped with the window that's focused while calling the function).

Well, currently. the focused window would get to be the master while the previous master would be put somewhere in the slave area depending on the order in which wmctrl reports it. Which I guess is kind of what you are asking for. Unless of course you want just the two of them to be swapped while leaving the others unaffected.

Re: Script for Window tiling in openbox

Hey ans5685,

thank you for implementing the active_{left,right} options. They work perfectly. The iconify bug seems to have disappeared too.

i have to say, wmtiler is priceless. openbox with ondemand tiling is the exact right combination of floating and tiling window manager for me. Now, if only Openbox had some of the new functionality of the gnome-shell (workspace management (miniatures, dragging), screenedge actions, expose-like window-tabbing mode). A fella can dream, right?

But to answer your question regarding the swap feature:

ans5685 wrote:

Unless of course you want just the two of them to be swapped while leaving the others unaffected.

Yup, that's what i meant. I don't always have all my windows tiled and with floating windows it's sometimes very useful to be able to swap just two windows and ignoring any of the others.

That's it, I'm out of suggestions for now. If I stumble upon any other bugs I'll let you know. Thanks for sharing this script though, seriously.

Re: Script for Window tiling in openbox

I wrote something quite similar to this a few weeks ago in zsh, but I didn't implement any sort of layout; I took influence from Subtle WM, and the manual tiling approach. I didn't share it, as I got bored of it, before I deemed it usable, but feel free to check it out. https://github.com/tacticalbread/shtile

It's sort of written in zsh, but I'm pretty sure the only non bash combatible thing is the decimal division. (bash only supports integer math).