How can I start up an application with a pre-defined window size and position?
What you will run into
If you want to first call an application and, subsequently, place its window on a specific position and size, the time between calling the application and the moment the window actually appears, is unpredictable. If your system is occupied, it can be significantly longer than if it is idle.
You need a "smart" way to make sure the positioning/resizing is done (immediately) after the window appears.
Script to call an application, wait for it to appear and position it on the screen
With the script below, you can call an application and set the position and size it should appear on with the command:
<script> <application> <x-position> <y-position> <h-size> <v-size>
An few examples:
To call
gnome-terminal
and resize its window to 50% and place it on the right half:<script> gnome-terminal 840 0 50 100
To call
gedit
, place its window on the left and callgnome-terminal
, place it on the right (setting itsv-size
46% to give it a little space between the windows):<script> gedit 0 0 46 100&&<script> gnome-terminal 860 0 46 100
To call Inkscape, place its window in the left/upper quarter of the screen:
<script> inkscape 0 0 50 50
The script and how to use it
install both
xdotool
andwmctrl
. I used both since resizing withwmctrl
can cause some peculiarities on (specifically)Unity
.sudo apt-get install wmctrl sudo apt-get install xdotool
- Copy the script below into an empty file, save it as
setwindow
(no extension) in~/bin
; create the directory if necessary. - Make the script executable (!)
- If you just created
~bin
, run:source ~/.profile
Test-run the script with the command (e.g.)
setwindow gnome-terminal 0 0 50 100
In other words:
setwindow <application> <horizontal-position> <vertical-position> <horizontal-size (%)> <vertical-size (%)>
If all works fine, use the command wherever you need it.
The script
#!/usr/bin/env python3
import subprocess
import time
import sys
app = sys.argv[1]
get = lambda x: subprocess.check_output(["/bin/bash", "-c", x]).decode("utf-8")
ws1 = get("wmctrl -lp"); t = 0
subprocess.Popen(["/bin/bash", "-c", app])
while t < 30:
ws2 = [w.split()[0:3] for w in get("wmctrl -lp").splitlines() if not w in ws1]
procs = [[(p, w[0]) for p in get("ps -e ww").splitlines() \
if app in p and w[2] in p] for w in ws2]
if len(procs) > 0:
w_id = procs[0][0][1]
cmd1 = "wmctrl -ir "+w_id+" -b remove,maximized_horz"
cmd2 = "wmctrl -ir "+w_id+" -b remove,maximized_vert"
cmd3 = "xdotool windowsize --sync "+procs[0][0][1]+" "+sys.argv[4]+"% "+sys.argv[5]+"%"
cmd4 = "xdotool windowmove "+procs[0][0][1]+" "+sys.argv[2]+" "+sys.argv[3]
for cmd in [cmd1, cmd2, cmd3, cmd4]:
subprocess.call(["/bin/bash", "-c", cmd])
break
time.sleep(0.5)
t = t+1
What it does
When the script is called, it:
- starts up the application
- keeps an eye on the window list (using
wmctrl -lp
) - if a new window appears, it checks if the new window belongs to the called application (using
ps -ef ww
, comparing the pid of the window to the pid of the application) - if so, it sets the size and position, according to your arguments. In case an application does not "show up" within appr. 15 seconds, the script assumes the application will not run due to an error. The script then terminates to prevent waiting for the new window infinitely.
Minor issue
In Unity, when you (re-)position and (re-)size a window with either wmctrl
or xdotool
, the window will always keep a small marge to the borders of your screen, unless you set it to 100%. You can see that in the image (3) above; while the inkscape
window was placed on x
position 0, you can still see a minor marge between the Unity Launcher and the inkscape
window.
You can do this using xdotool
.
To install xdotool
you can run:
sudo apt-get update && sudo apt-get install xdotool
Then to send a Ctrl+Alt+<keypad_key> keystroke to the terminal X
window you can run:
xdotool key Ctrl+Alt+<keypad_key_value>
*<keypad_key_value> = keypad key's value in the list below
To run a GUI program and send the keystroke to its X
window (which in this case is the active window at the time of the xdotool
command execution) you can run:
<command> && window="$(xdotool getactivewindow)" xdotool key --delay <delay> --window "$window" <keypad_key_value>
*<command> = command that opens the window you want to send the keystroke to; <delay> = time to wait in milliseconds before sending the keystroke; <keypad_key_value> = keypad key's value in the list below
Notice that in most cases you'll need to run the command you're issuing as a stand-alone process (e.g. by running nohup <command> &
instead of <command>
in the example above), otherwise xdotool
won't be run until <command>
's execution is complete.
Also you'll need to set some delay, otherwise the keystroke will be sent before the target window is fully loaded in X
(a delay around 500ms
should do).
The possible values for <keypad_key_value>
are:
- 0:
90
- 1:
87
- 2:
88
- 3:
89
- 4:
83
- 5:
84
- 6:
85
- 7:
79
- 8:
80
- 9:
81
As a thumb rule, to find out the the value of any key on the keyboard within the X
environment, one can run xev
and hit the key to output its value inside the terminal.
The actual command you want is something like
wmctrl -r :ACTIVE: -b add,maximized_vert &&
wmctrl -r :ACTIVE: -e 0,0,0,$HALF,-1
That will make the current window take up half the screen (change $HALF
to the dimensions of your screen) and snap to the left hand side. To snap to the right, use
wmctrl -r :ACTIVE: -b add,maximized_vert &&
wmctrl -r :ACTIVE: -e 0,$HALF,0,$HALF,-1
You can also play with wmctrl
to get the ID of the windows you're interested in instead of using :ACTIVE:
. I can't help there though since that depends on the windows in question. Have a look at man wmctrl
for more.
I've written a script for that. I don't use Unity so I can't guarantee that it will work with it, but I see no reason why not. It needs wmctrl
, xdpyinfo
and disper
to be installed:
sudo apt-get install wmctrl x11-utils disper
Then, save the script below as ~/bin/snap_windows.sh
, make it executable with chmod a+x ~/bin/snap_windows.sh
and you can run
snap_windows.sh r
To snap to the right hand side. Use l
for the left side and no arguments to maximize the window. Note that it runs on the current window so you'll need to assign a shortcut to it if you want it to run on anything but the terminal.
The script is a bit more complicated than what you ask for because I've written it to work on both single and dual-monitor setups.
#!/usr/bin/env bash
## If no side has been given, maximize the current window and exit
if [ ! $1 ]
then
wmctrl -r :ACTIVE: -b toggle,maximized_vert,maximized_horz
exit
fi
## If a side has been given, continue
side=$1;
## How many screens are there?
screens=`disper -l | grep -c display`
## Get screen dimensions
WIDTH=`xdpyinfo | grep 'dimensions:' | cut -f 2 -d ':' | cut -f 1 -d 'x'`;
HALF=$(($WIDTH/2));
## If we are running on one screen, snap to edge of screen
if [ $screens == '1' ]
then
## Snap to the left hand side
if [ $side == 'l' ]
then
## wmctrl format: gravity,posx,posy,width,height
wmctrl -r :ACTIVE: -b add,maximized_vert && wmctrl -r :ACTIVE: -e 0,0,0,$HALF,-1
## Snap to the right hand side
else
wmctrl -r :ACTIVE: -b add,maximized_vert && wmctrl -r :ACTIVE: -e 0,$HALF,0,$HALF,-1
fi
## If we are running on two screens, snap to edge of right hand screen
## I use 1600 because I know it is the size of my laptop display
## and that it is not the same as that of my 2nd monitor.
else
LAPTOP=1600; ## Change this as approrpiate for your setup.
let "WIDTH-=LAPTOP";
SCREEN=$LAPTOP;
HALF=$(($WIDTH/2));
if [ $side == 'l' ]
then
wmctrl -r :ACTIVE: -b add,maximized_vert && wmctrl -r :ACTIVE: -e 0,$LAPTOP,0,$HALF,-1
else
let "SCREEN += HALF+2";
wmctrl -r :ACTIVE: -b add,maximized_vert && wmctrl -r :ACTIVE: -e 0,$SCREEN,0,$HALF,-1;
fi
fi