[Thinlinc-technical] Dynamically resize single application sessions using ThinLinc

David Morlitz david at morlitz.com
Thu Dec 9 00:45:31 CET 2010

Peter: I apologize for the delay in getting this information posted,
but I wanted to clean up my scripts a little.

I apologize if this E-Mail is difficult to read in plain text - I have
posted a formatted copy of this at
which may be easier to read.

The development team at ThinLinc has focussed their attention on
sharing entire desktops  by leveraging TigerVNC technology.  This
provides remarkably good response time over even low-speed networks,
and means that clients are readily available on both Linux, Windows,
Mac OS and others. One weakness in remotely displaying only a single
application is that ThinLinc itself provides no method for resizing
the application after it has launched.  To augment ThinLinc, I have
written some code which will allow you to resize applications once
they have been launched.  It is this application that I will be
sharing now. My solution consists of the following 3 files, which will
be included at the bottom of this post:

ThinLincLaunchApp.sh - this is the actual script which ThinLinc will
invoke to start any single application
ThinLincMonitor.sh - this script will monitor the application you have
launched and terminate the ThinLinc session when the application you
are running ends
ThinLincResize.py - this Python code creates buttons at the top of
your window which will allow you to resize the application.  This code
includes some default sizes, and a text box for you to enter any size
that you desire.
Here are the steps required to make ThinLinc realize this new feature:

1) Create a directory to store the 3 scripts above – on my system I
have chosen to use /opt/thinlinc/launcher
2) OPTIONAL: Edit the ThinLincResize.py file to change the default
sizes presented as buttons.  Look for the lines which read
‘self.createButton(“1920×1100″)’ and edit them as necessary.  Please
respect the “DO NOT EDIT BELOW THIS LINE” warning
3) Make all 3 scripts executable (chmod 755 /opt/thinlinc/launcher/*)
4) Verify that ThinLincResize.py works by launching it locally on your
ThinLinc server by running /opt/thinlinc/launcher/ThinLincResize.py.
You will know it worked if a button bar appears at the top of your
screen.  Simply click on any button and the selected size will appear
in your terminal and this script ends
5)Edit your profiles.conf file to add the appropriate application
entries.  (The default location for this file is /opt /thinlinc /etc
/conf.d – and remove the spaces) For example, if you wish to launch
Google Chrome you can add:
Name=Google Chrome
Description=Google Chrome
cmdline=/opt/thinlinc/launcher/ThinLincLaunchApp.sh /usr/bin/google-chrome
testcmd=type /usr/bin/google-chrome
6) Make sure to add your new profile to the “order” line in the
profiles.hconf file, so it will appear in the selection list.  The
name to add is the word after /profiles/ in the brackets – in my
example, this is chrome.
7) You should be all set – just connect to your ThinLinc server from
your client, and select your new profile.  You will see your single
application appear with buttons that allow you to resize the window
hovering above the title bar.  To quit the application, simply hit the
“X” button of your application and within 5 seconds the buttons will
disappear and the ThinLinc session will terminate.
Some words of caution.  This application requires certain Linux
programs to be available to work properly.  Some of these
pre-requisites are xrandr, metacity, and wmctrl.  On my Ubuntu 10.10
image, these programs are provided by the packages libxrandr2,
metacity, and wmctrl respectively.  Please consult your Linux
distribution’s documentation to find the appropriate packages if you
are on a different distribution.

And now for the files…..


DIRECTORY=$(cd `dirname $0` && pwd) #Figure out what directory we are in
LAUNCHER_PID=`echo $$`

#Verify that the program passed to us is valid
if [ "$1" = "" ]; then
   echo ERROR: Program name is required as the first and only parameter
   exit 0

if [ ! -x "$1" ]; then
   echo ERROR: The program "$1" could not be located or is not executable
   exit 0

#Launch the Matchbox window manager
/usr/bin/metacity &

#Launch gnome-terminal
#/usr/bin/gnome-terminal &

#Launch the program and capture the resulting PID
$1 &
PROGRAM_PID=`echo $!`

#It seems like there needs to be a delay while the program opens
sleep 2

#zenity --info --text "Program PID is $PROGRAM_PID"

#Get the current geometry of the session
CURRENT_WIDTH=`xrandr | grep current | cut -f2 -d, | cut -f3 -d" "`
CURRENT_HEIGHT=`xrandr | grep current | cut -f2 -d, | cut -f5 -d" "`

#Move the window into the proper position
wmctrl -i -r $WINDOW_ID -b remove,maximized_vert,maximized_horz
WINDOW_ID=`wmctrl -l -p | awk '{$1=$1}1' OFS=" "`
WINDOW_ID=`echo $WINDOW_ID | cut -f1-3 -d" " | egrep $PROGRAM_PID\$ |
cut -f1 -d" "`
wmctrl -i -r $WINDOW_ID -e 0,1,1,`expr $CURRENT_WIDTH - 2`,`expr

#zenity --info --text "Toolbar program PID = $TOOLBAR_PID"
#zenity --info --text "Launched program PID = $PROGRAM_PID"

#Make sure that the process is running
NUMRUNNING=`ps x | sed 's/^[ \t]*//' | cut -f1 -d" " |grep $PROGRAM_PID | wc -l`

#Start a monitor job to kill the toolbar is the launched program ends
$DIRECTORY/ThinLincMonitor.sh $PROGRAM_PID &

#zenity --info --text "NUMRUNNING = $NUMRUNNING"

#Keep the toolbar running while the program is running
while [ $NUMRUNNING -gt 0 ];
   #Recheck to see if the program is still running
   NUMRUNNING=`ps x | sed 's/^[ \t]*//' | cut -f1 -d" " |grep
$PROGRAM_PID | wc -l`
   if [ $NUMRUNNING -gt 0 ]; then
      NEWSIZE=`$DIRECTORY/ThinLincResize.py` #Wait for the user to
select a new size

      #Get the window ID
      WINDOW_ID=`wmctrl -l -p | awk '{$1=$1}1' OFS=" "`
      WINDOW_ID=`echo $WINDOW_ID | cut -f1-3 -d" " | egrep
$PROGRAM_PID\$ | cut -f1 -d" "`

      #Check to see if the requested resolution exists
      RANDR_X_RES=`echo $NEWSIZE | sed -e 's/,/x/g'`
      RANDR_SPACE_RES=`echo $NEWSIZE | sed -e 's/,/ /g'`
      DESIRED_RESOLUTION=`xrandr | grep $RANDR_X_RES | wc -l`
      CVT=`cvt $RANDR_SPACE_RES | egrep ^Modeline | awk '{$1=$1}1'
OFS=" " | cut -f3 -d\"`
      SCREEN_NAME=`xrandr | grep connected | grep -v minimum | cut -f1 -d" "`
      REFRESH=`echo $MODELINE | cut -f2 -d\" | cut -f2 -d_`

      echo $MODELINE

      #Add the new resolution, if required
      if [ $DESIRED_RESOLUTION -eq 0 ]; then
         echo xrandr --newmode $MODELINE
         xrandr --newmode $MODELINE
         echo xrandr --addmode $SCREEN_NAME $RANDR_X_RES
         xrandr --addmode $SCREEN_NAME $RANDR_X_RES

      #Change the resolution
      xrandr --output $SCREEN_NAME --mode $RANDR_X_RES

      #echo Resizing window $WINDOW_ID to $NEWSIZE
      NEW_WIDTH=`echo $NEWSIZE | cut -f1 -d,`
      NEW_HEIGHT=`echo $NEWSIZE | cut -f2 -d,`
      #Resize the window
      wmctrl -i -r $WINDOW_ID -b remove,maximized_vert,maximized_horz
      wmctrl -i -r $WINDOW_ID -e 0,1,1,`expr $NEW_WIDTH - 2`,`expr

   fi #An application is running which needs resizing

while [ `ps | cut -f1 -d" " | grep $1 | wc -l` -gt 0 ];
   sleep 5
killall ThinLincResize.py
echo Done

# Load in pygtk and gtk

import pygtk
import gtk

class Whc:
    def addButtons(self):
        ### ADD RESOLUTIONS HERE ###

    def createButton(self,res):
        self.nextbutton = gtk.Button(res)
        self.nextbutton.connect("clicked", self.resize, res)
        self.box1.pack_start(self.nextbutton, True, True, 0)

    def __init__(self):
        # Window and framework
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.connect("destroy", self.resize)

        # Box for multiple widgits
        self.box1 = gtk.HBox(False, 0)

        # Add user buttons

        # Create the custom field
        self.customsize = gtk.Entry()
        self.customsize.connect("activate", self.enter_callback,
        self.box1.pack_start(self.customsize, True, True, 0)

        # Show the box

        # Show the window

        # Position the window
        width, height = self.window.get_size()
        # Top-right with space for close buttons
        self.window.move(gtk.gdk.screen_width() - width - 75, 0)
        #self.window.move((gtk.gdk.screen_width() - width)/2, 0) # Top-center

# Callback function for use when the button is pressed

# Destroy method causes appliaction to exit
# when main window closed

    def enter_callback(self, widget, entry):
   	        entry_text = entry.get_text()
   	        print entry_text.replace('x',',')

    def resize(self, widget, data=None):
        print data.replace('x',',')

# All PyGTK applications need a main method - event loop

    def main(self):

if __name__ == "__main__":
    base = Whc()

More information about the Thinlinc-technical mailing list