Some of our users prefer to have a tablet area that matches their monitor(s) aspect ratio - i.e. so a circle on the tablet is a circle on screen etc - even when using 2 monitors (which means only half the tablet area is used) - but is also useful when just using one monitor that doesn't match the aspect ratio of the tablet
wacom-gui current assumes the tablet is in landscape mode which (usually) maps to monitor(s) also in landscape mode. The selectable Aspect Mapping setting are:
However, if the monitor(s) are in a portrait layout (screen width is less than height), then the Aspect Mappings will map to the Right, Middle and Left parts of the tablet area - corresponding to the Top, Middle and Bottom selections (there isn't any support if the tablet is used in portrait mode ...)
To make life a lot easier, the patch also adds support for 'preferences' that are saved and loaded when wacom-gui exits/starts - which saves having to probe the wacom device for settings and attempting to match with what wacom-gui expects.
I've added preferences not only for the "Aspect Mapping" settings, but for the "Screen Area" and "Tablet Orientation" settings - if there are no existing saved settings for these, it will fall-back to probing the device as before. Adding preferences for "Tablet Orientation" is not actually needed for Aspect Mapping support, but as I had added it for "Screen Area", it seemed silly not to :-)
These preferences are saved in a file 'prefs.json' in the same directory as the 'default.sh' script for each tablet type
AFAIK, it all appears to work as expected - however, python is not my first language, so there may well be more pythonesque ways of doing what it does ...
--- ./wacom-gui/help.html.dist 2018-06-19 16:08:27.000000000 +0100
+++ ./wacom-gui/help.html 2018-07-31 12:05:57.343886905 +0100
@@ -134,6 +134,7 @@ section ul {
<section id="other"><h2>Other Settings</h2>
<ul>
<li>Map tablet input to a specific monitor or all (if more than one)</li>
+ <li>Map tablet area keeping the aspect ratio of the screen(s) using the Top, Middle or Bottom of the tablet - <b>Note</b>: if screen(s) are in 'portrait' mode, then Top will map to the Left of the tablet and Bottom to the Right of the tablet</li>
<li>Switch tablet between left and right orientation</li>
<li>Restore tablet to all default settings</li>
</ul>
--- ./wacom-gui/options.py.dist 2018-06-19 16:08:27.000000000 +0100
+++ ./wacom-gui/options.py 2018-07-31 12:05:57.344886918 +0100
@@ -7,7 +7,7 @@ import re
class otherOptions(QtGui.QWidget):
- def __init__(self, deviceNames, parent=None):
+ def __init__(self, deviceNames, prefs, parent=None):
QtGui.QWidget.__init__(self, parent)
#self.tabletName = tabletName.replace('Pad', 'Pen')
# use the detected device names
@@ -17,16 +17,20 @@ class otherOptions(QtGui.QWidget):
self.tabletTouch = deviceNames['touch']
self.tabletPad = deviceNames['pad']
self.deviceNames = deviceNames
+ self.prefs = prefs
+ #print prefs
self.initUI()
def initUI(self):
self.devices = []
self.tabletActiveArea = ""
+ self.tabletAspectArea = "-1 -1 -1 -1"
self.orient = ''
# layout code
self.mainLayout = QtGui.QHBoxLayout()
self.mainLayout.setAlignment(QtCore.Qt.AlignLeft)
+ self.mainLayout.addWidget(self.aspectOptions())
screens = self.screenOptions()
if screens:
self.mainLayout.addWidget(screens)
@@ -35,25 +39,64 @@ class otherOptions(QtGui.QWidget):
self.setLayout(self.mainLayout)
+ def aspectOptions(self):
+ groupBox = QtGui.QGroupBox("Aspect Mapping")
+ groupBox.setAlignment(QtCore.Qt.AlignHCenter)
+ groupBox.setFixedHeight(120)
+ self.aspectGroup = QtGui.QButtonGroup(groupBox)
+ self.aspectNone = QtGui.QRadioButton("None")
+ self.aspectTop = QtGui.QRadioButton("Top")
+ self.aspectMiddle = QtGui.QRadioButton("Middle")
+ self.aspectBottom = QtGui.QRadioButton("Bottom")
+ self.aspectGroup.addButton(self.aspectNone)
+ self.aspectGroup.addButton(self.aspectTop)
+ self.aspectGroup.addButton(self.aspectMiddle)
+ self.aspectGroup.addButton(self.aspectBottom)
+ aspectLayout = QtGui.QVBoxLayout()
+ aspectLayout.addWidget(self.aspectNone)
+ aspectLayout.addWidget(self.aspectTop)
+ aspectLayout.addWidget(self.aspectMiddle)
+ aspectLayout.addWidget(self.aspectBottom)
+ aspectLayout.addStretch(1)
+ # See if we have saved prefs
+ if "Aspect Mapping" in self.prefs:
+ if self.prefs['Aspect Mapping'] == "None":
+ self.aspectNone.setChecked(1)
+ elif self.prefs['Aspect Mapping'] == "Top":
+ self.aspectTop.setChecked(1)
+ elif self.prefs['Aspect Mapping'] == "Middle":
+ self.aspectMiddle.setChecked(1)
+ elif self.prefs['Aspect Mapping'] == "Bottom":
+ self.aspectBottom.setChecked(1)
+ else:
+ # Default is 'None'
+ self.aspectNone.setChecked(1)
+ self.prefs['Aspect Mapping'] = "None"
+ self.aspectGroup.buttonClicked.connect(self.aspectChange)
+ groupBox.setLayout(aspectLayout)
+ return groupBox
+
+
def screenOptions(self):
if QtGui.QDesktopWidget().numScreens() == 1:
self.screenFull = None
+ self.prefs['Screen Area'] = "All Monitors"
return None
groupBox = QtGui.QGroupBox("Screen Area")
groupBox.setAlignment(QtCore.Qt.AlignHCenter)
groupBox.setFixedHeight(120)
self.screenGroup = QtGui.QButtonGroup(groupBox)
self.displays = []
+ self.screenFull = QtGui.QRadioButton("All Monitors")
for x in range(0, QtGui.QDesktopWidget().numScreens()):
self.displays.append(QtGui.QRadioButton("Monitor %d" % x))
- self.screenFull = QtGui.QRadioButton("All Monitors")
for screen in self.displays:
self.screenGroup.addButton(screen)
self.screenGroup.addButton(self.screenFull)
screenLayout = QtGui.QVBoxLayout()
+ screenLayout.addWidget(self.screenFull)
for screen in self.displays:
screenLayout.addWidget(screen)
- screenLayout.addWidget(self.screenFull)
screenLayout.addStretch(1)
self.screenGroup.buttonClicked.connect(self.screenChange)
groupBox.setLayout(screenLayout)
@@ -73,16 +116,29 @@ class otherOptions(QtGui.QWidget):
flipLayout.addWidget(self.tabletRight)
flipLayout.addWidget(self.tabletLeft)
flipLayout.addStretch(1)
- getCommand = os.popen("xsetwacom --get \"%s stylus\" Rotate" % self.tabletStylus).readlines()
- # check correct button for orientation
- if getCommand[0] == "none\n":
- self.orient = "xsetwacom --set \"%s stylus\" Rotate none" % self.tabletStylus
- self.orient += "\nxsetwacom --set \"%s eraser\" Rotate none" % self.tabletEraser
- self.tabletRight.setChecked(1)
- elif getCommand[0] == "half\n":
- self.orient = "xsetwacom --set \"%s stylus\" Rotate half" % self.tabletStylus
- self.orient += "\nxsetwacom --set \"%s eraser\" Rotate half" % self.tabletEraser
- self.tabletLeft.setChecked(1)
+ # See if we have saved prefs - if we don't, probe for existing setting
+ if "Tablet Orientation" in self.prefs:
+ if self.prefs['Tablet Orientation'] == "Right-Handed":
+ self.tabletRight.setChecked(1)
+ elif self.prefs['Tablet Orientation'] == "Left-Handed":
+ self.tabletLeft.setChecked(1)
+ else:
+ getCommand = os.popen("xsetwacom --get \"%s stylus\" Rotate" % self.tabletStylus).readlines()
+ # check correct button for orientation
+ if getCommand[0] == "none\n":
+ self.orient = "xsetwacom --set \"%s stylus\" Rotate none" % self.tabletStylus
+ self.orient += "\nxsetwacom --set \"%s eraser\" Rotate none" % self.tabletEraser
+ self.tabletRight.setChecked(1)
+ self.prefs['Tablet Orientation'] = "Right-Handed"
+ elif getCommand[0] == "half\n":
+ self.orient = "xsetwacom --set \"%s stylus\" Rotate half" % self.tabletStylus
+ self.orient += "\nxsetwacom --set \"%s eraser\" Rotate half" % self.tabletEraser
+ self.tabletLeft.setChecked(1)
+ self.prefs['Tablet Orientation'] = "Left-Handed"
+ else:
+ # Default to Right-Handed
+ self.tabletRight.setChecked(1)
+ self.prefs['Tablet Orientation'] = "Right-Handed"
self.tabletFlipGroup.buttonClicked.connect(self.tabletFlipChange)
groupBox.setLayout(flipLayout)
return groupBox
@@ -95,11 +151,87 @@ class otherOptions(QtGui.QWidget):
elif buttonId.text() == "Left-Handed":
self.orient = "xsetwacom --set \"%s stylus\" Rotate half" % self.deviceNames['stylus']
self.orient += "\nxsetwacom --set \"%s eraser\" Rotate half" % self.deviceNames['eraser']
+ self.prefs['Tablet Orientation'] = str(buttonId.text())
flipTablet = os.popen(self.orient)
+ def aspectChange(self, buttonId):
+ self.aspectChangeMain(str(buttonId.text()), False)
+
+
+ def aspectChangeMain(self, buttonText, screenDone):
+ self.prefs['Aspect Mapping'] = buttonText
+ #print self.prefs['Aspect Mapping']
+
+ # reset the tablet area to the full tablet before getting the area
+ self.tabletAspectArea = "-1 -1 -1 -1"
+ cmd = "xsetwacom --set \"%s stylus\" area %s" % (self.deviceNames['stylus'], self.tabletAspectArea)
+ cmd += "\nxsetwacom --get \"%s stylus\" area" % self.deviceNames['stylus']
+ [tx, ty] = os.popen(cmd).read().split()[2:]
+
+ # if set to "None" - we don't need to do any more here
+ if buttonText == "None":
+ self.tabletAspectArea = "-1 -1 -1 -1"
+ else:
+ sa = self.prefs['Screen Area']
+ if sa == "All Monitors":
+ # get the total screen dimensions
+ cmd = "xdpyinfo | grep dimensions | awk '{print $2}' | awk -Fx '{print $1, $2}'"
+ [sx, sy] = os.popen(cmd).read().split()
+ else:
+ # get the dimensions on the current Monitor
+ id = sa.split(' ')[1]
+ screen = QtGui.QDesktopWidget().screenGeometry(int(id))
+ sx = screen.width()
+ sy = screen.height()
+
+ # default 'new' tablet area
+ ox = 0
+ oy = 0
+ nx = int(tx)
+ ny = int(ty)
+
+ if float(sy)/float(sx) <= 1.0:
+ # screen landscape mode
+ ny = int(float(tx) * float(sy) / float(sx))
+ # Move tablet Y origin (already 0 for "Top")
+ if buttonText == "Middle":
+ oy = (int(ty) - ny) / 2
+ elif buttonText == "Bottom":
+ oy = int(ty) - ny
+ else:
+ # screen portrait mode
+ # Top = Left, Middle = Middle and Bottom = Right
+ nx = int(float(ty) * float(sx) / float(sy))
+ # Move tablet X origin (already 0 for "Left")
+ if buttonText == "Middle":
+ ox = (int(tx) - nx) / 2
+ elif buttonText == "Bottom":
+ ox = int(tx) - nx
+ ny += oy
+ nx += ox
+ self.tabletAspectArea = "%d %d %d %d" % (ox, oy, nx, ny)
+
+ #print self.tabletAspectArea
+ for device in self.devices:
+ if device != "pad" and device != 'touch':
+ cmd = "xsetwacom --set \"%s %s\" Area %s" % (self.deviceNames[device], device, self.tabletAspectArea)
+ setCommand = os.popen(cmd)
+ # This probably isn't needed, but as we do something similar
+ # when Monitors change wrt Aspect, so update Monitors based
+ # on previous pref (if required)
+ if screenDone == False:
+ self.screenChangeMain(self.prefs['Screen Area'], True)
+
def screenChange(self, buttonId):
- if buttonId.text() == "All Monitors":
+ self.screenChangeMain(str(buttonId.text()), False)
+
+
+ def screenChangeMain(self, buttonText, aspectDone):
+ # is we only have one screen, then we don't make any changes here
+ if QtGui.QDesktopWidget().numScreens() == 1:
+ return
+ if buttonText == "All Monitors":
self.tabletActiveArea = "1 0 0 0 1 0 0 0 1"
for device in self.devices:
if device != "pad":
@@ -107,14 +239,30 @@ class otherOptions(QtGui.QWidget):
% (self.deviceNames[device], device, self.tabletActiveArea)
setCommand = os.popen(cmd)
else:
- self.tabletActiveArea = "HEAD-%s" % buttonId.text().split(' ')[1]
+ self.tabletActiveArea = "HEAD-%s" % buttonText.split(' ')[1]
for device in self.devices:
if device != "pad":
cmd = "xsetwacom set \"%s %s\" MapToOutput %s" % (self.deviceNames[device], device, self.tabletActiveArea)
setCommand = os.popen(cmd)
+ self.prefs['Screen Area'] = buttonText
+ # As Monitor mapping has changed, the Aspect mapping may not match
+ # so update Aspect based on previous pref (if required)
+ if aspectDone == False:
+ self.aspectChangeMain(self.prefs['Aspect Mapping'], True)
def getTabletArea(self):
+ sa = "Screen Area"
+ # See if we have saved prefs - if we don't, probe for existing setting
+ if sa in self.prefs:
+ if self.prefs[sa] == "All Monitors":
+ self.screenFull.setChecked(1)
+ return
+ # only check if we have more than one screen
+ for x in range(0, QtGui.QDesktopWidget().numScreens()):
+ if self.prefs[sa] == "Monitor %d" % x:
+ self.displays[x].setChecked(1)
+ return
# get current tablet area
tabletInfo = os.popen("xinput list-props \"%s stylus\" | grep Coordinate" % self.tabletStylus).readlines()
tabletInfo[0] = tabletInfo[0][41:].rstrip('\n')
@@ -139,6 +287,7 @@ class otherOptions(QtGui.QWidget):
break
if fullScreen:
self.screenFull.setChecked(1)
+ self.prefs[sa] = "All Monitors"
# have to build array then compare... boo
else:
for id in range(0, QtGui.QDesktopWidget().numScreens()):
@@ -165,7 +314,11 @@ class otherOptions(QtGui.QWidget):
break
if valid:
self.displays[id].setChecked(1)
+ self.prefs[sa] = "Monitor %d" % id
break
+ # Default to all screens
+ self.screenFull.setChecked(1)
+ self.prefs[sa] = "All Monitors"
def setDevices(self, devices):
@@ -180,6 +333,7 @@ class otherOptions(QtGui.QWidget):
self.tabletActiveArea = "HEAD-0"
for device in self.devices:
if device != "pad" and device != 'touch':
+ setCommands.append("xsetwacom --set \"%s %s\" Area %s" % (self.deviceNames[device], device, self.tabletAspectArea))
if 'HEAD' in self.tabletActiveArea:
setCommands.append("xsetwacom --set \"%s %s\" MapToOutput %s" %
(self.deviceNames[device], device, self.tabletActiveArea))
@@ -197,11 +351,16 @@ class otherOptions(QtGui.QWidget):
self.orient = "xsetwacom --set \"%s stylus\" Rotate none" % self.deviceNames['stylus']
self.orient += "\nxsetwacom --set \"%s eraser\" Rotate none" % self.deviceNames['eraser']
self.tabletRight.setChecked(1)
+ self.prefs['Tablet Orientation'] = "Right-Handed"
os.popen(self.orient)
for device in self.devices:
if device != "pad":
cmd = "xinput set-prop \"%s %s\" --type=float \"Coordinate Transformation Matrix\" 1 0 0 0 1 0 0 0 1" \
% (self.deviceNames[device], device)
+ cmd += "\nxsetwacom --set \"%s %s\" Area -1 -1 -1 -1" % (self.deviceNames[device], device)
setCommand = os.popen(cmd)
if self.screenFull is not None:
self.screenFull.setChecked(True)
+ self.prefs['Screen Area'] = "All Monitors"
+ self.prefs['Aspect Mapping'] = "None"
+ self.aspectNone.setChecked(1)
--- ./wacom-gui/pad.py.dist 2018-06-19 16:08:27.000000000 +0100
+++ ./wacom-gui/pad.py 2018-07-31 12:05:57.344886918 +0100
@@ -7,6 +7,7 @@ import distutils.dir_util
import shutil
import os
import re
+import json
from os.path import expanduser
from PyQt4 import QtCore, QtGui
from wacom_data import tabletidentities
@@ -115,6 +116,19 @@ class Pad(QtGui.QWidget):
QtGui.QMessageBox.warning(w, "Information", label)
w.show()
+ prefsFile = "%s/.wacom-gui/%s/prefs.json" % (expanduser("~"), self.Tablet.devID)
+ if os.path.exists(prefsFile) and os.access(prefsFile, os.R_OK):
+ try:
+ with open(prefsFile, 'r') as f:
+ self.Tablet.prefs = json.load(f)
+ except:
+ # ignore problems with the prefs file for now ...
+ self.Tablet.prefs = {}
+ else:
+ self.Tablet.prefs = {}
+
+ self.Tablet.prefsFile = prefsFile
+
opPath = os.path.dirname(os.path.realpath(__file__))
self.setWindowIcon(QtGui.QIcon(opPath + '/images/wacom-gui.svg'))
@@ -223,6 +237,8 @@ class Pad(QtGui.QWidget):
return item
return self.TabletIds.Tablets[len(self.TabletIds.Tablets)-1]
+ def getTabletPrefs(self):
+ return self.Tablet.prefs
def getTabletName(self):
return self.Tablet.Name
--- ./wacom-gui/wacom-gui.py.dist 2018-06-19 16:08:27.000000000 +0100
+++ ./wacom-gui/wacom-gui.py 2018-07-31 12:05:57.343886905 +0100
@@ -8,6 +8,7 @@ import os
import time
# import threading
import subprocess
+import json
from os.path import expanduser
from PyQt4 import QtCore, QtGui
@@ -31,7 +32,7 @@ class WacomGui(QtGui.QWidget):
self.eraserControl = pressure(self.pad.Tablet.deviceNames['eraser'], 'eraser')
self.cursorControl = pressure(self.pad.Tablet.deviceNames['cursor'], 'cursor')
self.touch = touch(self.pad.Tablet.deviceNames['touch'])
- self.options = otherOptions(self.pad.Tablet.deviceNames)
+ self.options = otherOptions(self.pad.Tablet.deviceNames, self.pad.Tablet.prefs)
self.help = Help()
self.setMaximumSize(1000, 500)
self.setMinimumSize(1000, 500)
@@ -211,6 +212,9 @@ class WacomGui(QtGui.QWidget):
config.close()
os.chmod(self.pad.Tablet.config, 0774)
+ with open(self.pad.Tablet.prefsFile, 'w') as f:
+ json.dump(self.pad.Tablet.prefs, f, indent=4)
+
def closeEvent(self, event):
reply = QtGui.QMessageBox.question(self, 'Message',
"Quit and write config file?",