pin application window to specific monitor in linux

At work, I use an external monitor connected to docking station. While all my work happens on the external 24″ monitor, I wanted my email window on the laptop screen so that I have quick view of email any time I need without switching windows. Usually, this is a one time exercise if you don’t disconnect your laptop from the docking station after coming to work. But in most cases, you will end up undocking and docking several times each day. It was a pain to rearrange the windows each time and here I will explain how I automated it today.

I use 15.04 on Lenovo Thinkpad T450 with a Lenovo docking station and the settings described below are for these. Others might need minor changes.

Detecting the docking station

Earlier docking stations could be detected with a line similar to the following in /etc/udev/rules.d/85-dockingstation.rules

SUBSYSTEM=="platform", KERNEL=="dock.0", ACTION=="change", RUN+="/etc/thinkpad/dock.sh"

This method didn’t work on my docking station. Then I had to look for something else that could help me identify the docking station.

To get all the udev events happening while the laptop is docked, use the following command.

sudo udevadm monitor --kernel --property --udev

This would print  both kernel uevents as well as udev events with the properties. In my case, it gave me a bunch of events. I just chose this one.

ACTION=add
BUSNUM=001
DEVNAME=/dev/bus/usb/001/032
DEVNUM=032
DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-3
DEVTYPE=usb_device
DRIVER=usb
ID_BUS=usb
ID_FOR_SEAT=usb-pci-0000_00_14_0-usb-0_3
ID_MODEL=Lenovo_ThinkPad_Dock
ID_MODEL_ENC=Lenovo\x20ThinkPad\x20Dock\x20\x20\x20
ID_MODEL_ID=1012
ID_PATH=pci-0000:00:14.0-usb-0:3
ID_PATH_TAG=pci-0000_00_14_0-usb-0_3
ID_REVISION=5040
ID_SERIAL=LENOVO_Lenovo_ThinkPad_Dock
ID_USB_INTERFACES=:090000:
ID_VENDOR=LENOVO
ID_VENDOR_ENC=LENOVO\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
ID_VENDOR_FROM_DATABASE=Lenovo
ID_VENDOR_ID=17ef
MAJOR=189
MINOR=31
PRODUCT=17ef/1012/5040
SUBSYSTEM=usb
TAGS=:seat:
TYPE=9/0/1
USEC_INITIALIZED=633458755

Create the udev rule

In the above output, pick a few parameters which are unique for this to create the udev rule for this event. Create a file /etc/udev/rules.d/85-thinkpad-dock.rules and paste the following into that.

ACTION=="add", SUBSYSTEMS=="usb", ENV{ID_MODEL_ID}=="1012", ENV{ID_MODEL}=="Lenovo_ThinkPad_Dock", ENV{ID_VENDOR_ID}=="17ef", ENV{PRODUCT}=="17ef/1012/5040" RUN+="/bin/su jemshad -c '/etc/thinkpad/dock.sh'"

On the occurrence of the event, whatever command mentioned in RUN will be executed. /bin/su makes sure it is executed as required user than root.

ACTION==”add” – Execute only when the “add” event occurs.

Rest everything is meant for uniquely identifying this event.

Script to detect state of docking

Crate /etc/thinkpad/dock.sh with something similar to

#!/bin/bash -x
#

# Log everything in syslog
exec &> >(logger -t '[laptop-dock]')

echo "laptop docking state changed"

DOCKED=$(cat /sys/devices/platform/dock.0/docked)

case "$DOCKED" in

    "0")
        #undocked event
        [[ -x /home/jemshad/bin/undocked ]] && timeout 120 /home/jemshad/bin/undocked
        ;;
    "1")
        #docked event
        [[ -x /home/jemshad/bin/docked ]] && timeout 120 /home/jemshad/bin/docked
    ;;
esac
exit 0
  • The exec line makes sure that I get all the activity logged in the syslog (/var/log/syslog on Ubuntu)
  • timeout makes sure that the script gets terminated after 2 minutes. This is to prevent it from running endlessly in case the display is not available or ‘Google Chrome’ is not opened (in my case).

Script to move the required window

#!/bin/bash -x

export DISPLAY=:0

# Wait for some time for the external monitor to turn on
while (( num_displays < 2 ))
do
    displays=$(xrandr -q 2>/dev/null | grep -wF 'connected')
    echo "Displays: $displays"
    num_displays=$(wc -l <<<"$displays")
    sleep 2
done

# Is it really the docking station connected
# Don't want to project my email to everyone when connected in a meeting
if [[ $displays == *"1366x768+1920+25"* ]]; then
# Wait till Google Chrome is opened
while :
do
    is_chrome_open=$(wmctrl -l -F | grep 'Google Chrome')
    if [[ -z $is_chrome_open ]]; then
        sleep 5
    else
        wmctrl -r 'Google Chrome' -e '0,1920,25,1366,1128'
        break
    fi
done

fi

DISPLAY variable need to be exported for the to work properly. In my case, I open my work email in ‘Google Chrome’ always. I check for a couple of things in the script here:

  • External monitor takes a while to switch on and therefore I need to wait for it.
  • Make sure that it is the docking station which gave me an external monitor and not just a projector connected to the laptop by checking for the resolution of the monitors.
  • As a next step, wait for Google Chrome to run and using wmctrl, move it to the other monitor (laptop).
    • The values to be given there can be easily obtained by moving the application window manually to the required position first and then running wmctrl -l -G. The output would look something like
      0x05200007 0 1476 864 444 312 IM0713-L0 Software Updater
      0x01800007 0 0 0 1920 24 IM0713-L0 Top Expanded Edge Panel
      0x02e0000b -1 0 0 3286 1200 IM0713-L0 Desktop
      0x03400027 -1 0 1036 1920 164 IM0713-L0 Docky
      0x04200001 0 1920 25 1366 1128 IM0713-L0 Sign in - Google Accounts - Google Chrome
      0x04e0000b 0 6 52 1908 1094 IM0713-L0 Terminal
      0x0420007b 0 1570 246 350 440 IM0713-L0 Hangouts -xxxx@xxxxx.com
      0x05400001 0 0 24 1920 1128 IM0713-L0 Chapter 7: Forms - Chromium
      0x05400001 0 IM0713-L0 Chapter 7: Forms - Chromium

      Take the values after the first field in order. 0, 1920, 25, 1366, 1128 in this case for Google Chrome and provide them in your script.

That should be it!. Let me know if it helped you 🙂

References:

This entry was posted in linux, tech and tagged , , . Bookmark the permalink.

Leave a Reply