Area mapping

From linuxwacom
Jump to: navigation, search

The GTK+ toolkit is known to have have problems working with tablets in a multi-head Xinerama setup. These bugs manifest themselves as an offset between the position of the pointer (as drawn by the X server) and the position of the brush (as interpreted by e.g. GIMP). Several bugs exist in the GNOME bugzilla (see Bug 68813, Bug 634977, and Bug 602733) but no solution has yet been accepted upstream.

In some cases, it is possible to work around offset issues triggered by using 'MapToOutput'. This page describes the process of using the 'Area' property to achieve the same effect.


The wacom driver scales and translates the raw tablet coordinates to fit onto your desktop. To work around the mapping issue, we can choose a very specific value for 'Area' which will result in the driver scaling and translating the tablet coordinates to fit onto a single monitor instead.

For this to work, you will need to know the resolution of your desktop, the resolution of the target monitor, and the resolution of your tablet. To get the third, run the following command:

 xsetwacom get <id> area

The four numbers "a b c d" printed out are the points (a,b) and (c,d) corresponding to the top-left and bottom-right of your tablet. The resolution will be (c-a) by (d-b) (e.g. "0 0 60960 45720" would be a resolution of 60960x45720). If you're running an old version of linuxwacom, you may need to run the following four commands to get the same data instead:

 xsetwacom get <id> topx
 xsetwacom get <id> topy
 xsetwacom get <id> bottomx
 xsetwacom get <id> bottomy

Lets assume I want to constrain my tablet to the right monitor in the following setup:

  • Intuos3 9x12 (resolution: 60960x45720)
  • Left monitor (resolution: 1600x1200, located at 0,0)
  • Right monitor (resolution: 1920x1080, located at 1600,0)

To find the necessary "virtual" tablet width and height, we must first know the fraction of the desktop width and height the right monitor takes up. The fractional monitor width will be <math>(1920/(1600+1920)) = 0.54545454</math>, and the fractional monitor height will be <math>(1080/1200) = 0.9</math>. To make the tablet appear larger than it actually is to the driver, we'll need to divide the physical resolution by these fractions to obtain the "virtual" resolution. In this case, we'd have a virtual width of <math>(60960 / 0.54545454) = 111760</math>, and a virtual height of <math>(45720 / 0.9) = 50800</math>.

To find the origin of our "virtual" tablet, we need to know the fraction of the desktop the right monitor is offset from the desktop's origin. The fractional monitor X origin will be <math>(1600/(1600+1920)) = 0.45454545</math>, and the fractional monitor Y origin will be <math>(0 / 1200) = 0</math>. We'll offset the virtual tablet's origin by these fractional amounts, for a virtual X origin of <math>(-(111760 * 0.45454545)) = -50800</math> and a virtual Y origin of <math>(-(50800*0)) = 0</math>

Finally, using the origin and the width of our virtual tablet, we need to obtain the location of it's bottom-right coordinate (since xsetwacom uses these two points to define the area). The bottom-right x coordinate will be <math>(-50800 + 111760) = 60960</math>, and the bottom-right y-coordinate will be <math>(0 + 50800) = 50800</math>. We'll set this area using xsetwacom as follows:

 xsetwacom set <stylus id> area -50800 0 60960 50800
 xsetwacom set <eraser id> area -50800 0 60960 50800

Again, if you're running an old version of linuxwacom, you may need to use 'set' with each of TopX, TopY, BottomX, and BottomY instead to save your user-defined area.

To Have the Settings Last Through a Reboot

You have to apply the xsetwacom command with each restart or rotation. You should be able to add it to your xsetwacom start up script, if you have one. Otherwise run it from a convenient start up script. A sample start up script is shown in Sample Runtime Script. Add the appropriate command for each device (stylus, eraser, and cursor) to its section.

Python implementation

The short Python program below can be copy/pasted and run to determine the correct numbers. You'll need to set the tablet and monitor resolutions to match your own setup.

#!/usr/bin/env python

from __future__ import division
def main():
    tablet = Device("60960x45720")         # Replace with the resolution of your tablet

    monitor_0 = Device("1600x1200")        # Replace with the resolution of your left screen
    monitor_1 = Device("1920x1080")        # Replace with the resolution of your right screen
    monitors = [monitor_0, monitor_1]

    desktop = bounding_box(monitors)

    area = map_tablet_onto(tablet, desktop, monitor_1) # Specify monitor to map tablet to here

    cmd  = "xsetwacom set <id> area %d %d %d %d" % (area.p1[0], area.p1[1], area.p2[0], area.p2[1])

## Do not edit below this line.

class Vector:
    def __init__(self, *data):
        self.data = data
    def __repr__(self):
        return repr(self.data) 
    def __add__(self, other):
        return Vector( *[a+b for a,b in zip(self.data, other.data) ] )  
    def __sub__(self, other):
        return Vector( *[a-b for a,b in zip(self.data, other.data) ] )
    def __mul__(self, other):
        return Vector( *[a*b for a,b in zip(self.data, other.data) ] )
    def __truediv__(self, other):
        return Vector( *[1.0*a/b for a,b in zip(self.data, other.data) ] )
    def __iter__(self):
        return iter(self.data)
    def __getitem__(self, index):
        return self.data[index]

class Rectangle:
    def __init__(self, p1=(0,0), p2=(0,0)):
        self.p1, self.p2 = Vector(*p1), Vector(*p2)
    def __repr__(self):
        return repr((self.p1, self.p2))
    def __add__(self, point):
        return Rectangle(self.p1 + point, self.p2 + point)
    def __sub__(self, point):
        return Rectangle(self.p1 - point,  self.p2 - point)
    def getSize(self):
        return self.p2 - self.p1
    def setSize(self, size):
        self.p2 = self.p1 + size
    def getOrigin(self):
        return self.p1
    def setOrigin(self, point):
        self.p2, self.p1 = self.size + point, point
    size = property(getSize, setSize)
    origin = property(getOrigin, setOrigin)

class Device(Rectangle):
    def __init__(self, resolution):
        x, y = resolution.split("x")
        Rectangle.__init__(self, (0,0), Vector(int(x), int(y)))
    def place_right_of(self, other):
        self.setOrigin(other.origin + Vector(other.size[0], 0))
    def place_left_of(self, other):
        self.setOrigin(other.origin - Vector(self.size[0], 0))
    def place_above(self, other):
        self.setOrigin(other.origin - Vector(0, self.size[1]))
    def place_below(self, other):
        self.setOrigin(other.origin + Vector(0, other.size[1]))

def bounding_box(rectangles):
    points = [rect.p1 for rect in rectangles]
    min_x = min([x for x,y in points])
    min_y = min([y for x,y in points])

    points = [rect.p2 for rect in rectangles]
    max_x = max([x for x,y in points])
    max_y = max([y for x,y in points])

    return Rectangle((min_x, min_y), (max_x, max_y))

def normalize(desktop, monitor):
    rect = Rectangle([ (monitor.p1 - desktop.p1)[i] / desktop.size[i] for i in [0,1] ])
    rect.setSize(monitor.size / desktop.size)
    return rect

def map_tablet_onto(tablet, desktop, monitor):
    norm = normalize(desktop, monitor)
    rect = Rectangle()
    rect.setSize(tablet.size / norm.size)
    rect.setOrigin ( tablet.origin - (rect.size * norm.origin) )
    return rect

if __name__=="__main__":
Personal tools