--- /dev/null
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkWindow" id="window">
+ <property name="title" translatable="yes">Nub configuration</property>
+ <property name="window_position">center</property>
+ <child>
+ <object class="GtkTable" id="table">
+ <property name="visible">True</property>
+ <property name="n_rows">11</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">4</property>
+ <child>
+ <object class="GtkFrame" id="leftmode">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkRadioButton" id="LeftRadio_mouse">
+ <property name="label" translatable="yes">mouse movement</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="LeftRadio_mbuttons">
+ <property name="label" translatable="yes">mouse buttons</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">LeftRadio_mouse</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="LeftRadio_scroll">
+ <property name="label" translatable="yes">scrolling</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">LeftRadio_mouse</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="LeftRadio_absolute">
+ <property name="label" translatable="yes">joystick</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">LeftRadio_mouse</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="LeftModeLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Left nub mode</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFrame" id="rmode">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkRadioButton" id="RightRadio_mouse">
+ <property name="label" translatable="yes">mouse movement</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="RightRadio_mbuttons">
+ <property name="label" translatable="yes">mouse buttons</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">RightRadio_mouse</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="RightRadio_scroll">
+ <property name="label" translatable="yes">scrolling</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">RightRadio_mouse</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="RightRadio_absolute">
+ <property name="label" translatable="yes">joystick</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">RightRadio_mouse</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="RightModeLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Right nub mode</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="LeftButtonThreshold">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">lbutton</property>
+ <property name="digits">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="LeftMouseSpeed">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">lmouse</property>
+ <property name="digits">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator1">
+ <property name="height_request">8</property>
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ScrollYLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Scroll sensitivity (y-axis)</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ScrollXLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Scroll sensitivity (x-axis)</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ScrollLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Scroll speed</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="MouseButtonLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Mouse button sensitivity</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="MouseSpeedLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Mouse speed</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="LeftScrollRate">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">lscroll</property>
+ <property name="digits">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="LeftScrollX">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">lscrollx</property>
+ <property name="digits">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="LeftScrollY">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">lscrolly</property>
+ <property name="digits">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="RightMouseSpeed">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">rmouse</property>
+ <property name="digits">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="RightButtonThreshold">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">rbutton</property>
+ <property name="digits">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="RightScrollRate">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">rscroll</property>
+ <property name="digits">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="RightScrollX">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">rscrollx</property>
+ <property name="digits">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="RightScrollY">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="update_policy">delayed</property>
+ <property name="adjustment">rscrolly</property>
+ <property name="digits">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator2">
+ <property name="height_request">6</property>
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">7</property>
+ <property name="bottom_attach">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="ResetLeft">
+ <property name="label" translatable="yes">Reset left nub</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_ResetLeft_clicked"/>
+ </object>
+ <packing>
+ <property name="top_attach">8</property>
+ <property name="bottom_attach">9</property>
+ <property name="y_padding">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="ResetRight">
+ <property name="label" translatable="yes">Reset right nub</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_ResetRight_clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">8</property>
+ <property name="bottom_attach">9</property>
+ <property name="y_padding">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="UndoChanges">
+ <property name="label" translatable="yes">Scan nub state</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="image_position">top</property>
+ <signal name="clicked" handler="on_UndoChanges_clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">8</property>
+ <property name="bottom_attach">9</property>
+ <property name="y_padding">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImage" id="LogoImage">
+ <property name="height_request">84</property>
+ <property name="visible">True</property>
+ <property name="pixbuf">pndlogo.png</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="ApplyChanges">
+ <property name="label" translatable="yes">Apply configuration</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_ApplyChanges_clicked"/>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="SaveProfile">
+ <property name="label" translatable="yes">Save profile</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_SaveProfile_clicked"/>
+ </object>
+ <packing>
+ <property name="top_attach">9</property>
+ <property name="bottom_attach">10</property>
+ <property name="y_padding">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxEntry" id="ProfileComboEntry">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">9</property>
+ <property name="bottom_attach">10</property>
+ <property name="y_padding">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="LoadProfile">
+ <property name="label" translatable="yes">Load profile</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_LoadProfile_clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">9</property>
+ <property name="bottom_attach">10</property>
+ <property name="y_padding">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStatusbar" id="statusbar">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ </object>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">10</property>
+ <property name="bottom_attach">11</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkAdjustment" id="lmouse">
+ <property name="value">150</property>
+ <property name="lower">50</property>
+ <property name="upper">300</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="rmouse">
+ <property name="value">150</property>
+ <property name="lower">50</property>
+ <property name="upper">300</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="lbutton">
+ <property name="value">20</property>
+ <property name="lower">1</property>
+ <property name="upper">40</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="rbutton">
+ <property name="value">20</property>
+ <property name="lower">1</property>
+ <property name="upper">40</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="lscroll">
+ <property name="value">20</property>
+ <property name="lower">1</property>
+ <property name="upper">40</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="rscroll">
+ <property name="value">20</property>
+ <property name="lower">1</property>
+ <property name="upper">40</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="lscrollx">
+ <property name="value">7</property>
+ <property name="lower">-32</property>
+ <property name="upper">32</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="rscrollx">
+ <property name="value">7</property>
+ <property name="lower">-32</property>
+ <property name="upper">32</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="lscrolly">
+ <property name="value">7</property>
+ <property name="lower">-32</property>
+ <property name="upper">32</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="rscrolly">
+ <property name="value">7</property>
+ <property name="lower">-32</property>
+ <property name="upper">32</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+</interface>
--- /dev/null
+#!/usr/bin/python
+
+# TODO:
+# - fix nub reset (permission issue) and add test
+# - wrap into PND
+# - upload into beta software, gather & process feedback
+# - upload into repo
+
+import os
+import re
+import sys
+import gtk
+import time
+import optparse
+
+# EDs reset in op_nubmode.sh
+#
+# @left-nub: 3-0066/reset
+
+# echo 1 > /sys/bus/i2c/drivers/vsense/3-0067/reset
+# sleep 1
+# echo 0 > /sys/bus/i2c/drivers/vsense/3-0067/reset
+# curmode=$(cat /proc/pandora/nub1/mode)
+# echo mouse > /proc/pandora/nub1/mode
+# while ! zenity --question --title="Resetted right nub" --text="The right nub has been resetted.\nPlease try to move the mouse cursor\nto test if it is working properly." --ok-label="Working properly" --cancel-label="Reset again"; do
+# echo 1 > /sys/bus/i2c/drivers/vsense/3-0067/reset
+# sleep 1
+# echo 0 > /sys/bus/i2c/drivers/vsense/3-0067/reset
+# done
+# echo $curmode > /proc/pandora/nub1/mode
+
+# ================================================================
+
+GUI_DESCRIPTION = 'nubmode.glade'
+PROFILES = '/etc/pandora/conf/nub_profiles.conf'
+
+MODES = ("mouse", "mbuttons", "scroll", "absolute")
+SLIDERS = ("mouse", "button", "scroll", "scrollx", "scrolly")
+
+DEFAULT_PROFILENAME = "Default"
+DEFAULT_PROFILE = [DEFAULT_PROFILENAME,
+ "mouse 150 20 20 7 7", "mbuttons 150 20 20 7 7"]
+
+RESET_CMD_LEFT = 'echo %i > /sys/bus/i2c/drivers/vsense/3-0066/reset'
+RESET_CMD_RIGHT = 'echo %i > /sys/bus/i2c/drivers/vsense/3-0067/reset'
+
+# Settings read/written to /proc/pandora/nub<x> (<x>: 0 or 1)
+FILES = "mode mouse_sensitivity mbutton_threshold scroll_rate scrollx_sensitivity scrolly_sensitivity".split(' ')
+LEFT_NUB_CONFIG = [os.path.join('/proc/pandora/nub0', x) for x in FILES]
+RIGHT_NUB_CONFIG = [os.path.join('/proc/pandora/nub1', x) for x in FILES]
+
+RE_FORMAT = "(%s),(\d+),(\d+),(\d+),(\d+),(\d+)" % '|'.join(MODES)
+BOUNDS = ((50, 300), (1, 40), (1, 40), (-32, 32), (-32, 32))
+
+# ================================================================
+
+def Validate(value):
+ mo = re.match(RE_FORMAT, value)
+ if mo and all(lower <= int(value) <= upper for (value, (lower, upper)) in
+ zip(mo.groups()[1:], BOUNDS)):
+ return True, list(mo.groups())
+ else:
+ return False, None
+
+def ReadConfigFromProc(paths):
+ config = []
+ for filepath in paths:
+ with open(filepath) as f:
+ config.append(f.readline().strip())
+ return config
+
+def StoreConfigToProc(paths, values):
+ # fix for scrollx, scrolly being stored one closer to zero than specified
+ values[-2:] = [int(v) + (-1 if v < 0 else 1) for v in values[-2:]]
+
+ for filepath, value in zip(paths, values):
+ with open(filepath, 'w') as f:
+ f.write('%s\n' % value)
+
+
+class Nub(object):
+ def __init__(self, builder, modeprefix, sliderprefix):
+ self.radios = [builder.get_object(modeprefix % m) for m in MODES]
+ self.sliders = [builder.get_object(sliderprefix % s) for s in SLIDERS]
+
+ def SetConfig(self, config):
+ i = MODES.index(config[0])
+ self.radios[i].set_active(True)
+ for s, v in zip(self.sliders, config[1:]):
+ s.value = int(v)
+
+ def GetConfig(self):
+ i = (j for j, r in enumerate(self.radios) if r.get_active()).next()
+ config = [MODES[i]]
+ for s in self.sliders:
+ config.append(str(int(s.value)))
+ return config
+
+
+class NubConfig(object):
+ """GUI application for modifying the pandora nub configuration"""
+ def __init__(self):
+ builder = gtk.Builder()
+ builder.add_from_file(
+ os.path.join(os.path.dirname(__file__), GUI_DESCRIPTION))
+ builder.connect_signals(self)
+
+ self.leftnub = Nub(builder, "LeftRadio_%s", "l%s")
+ self.rightnub = Nub(builder, "RightRadio_%s", "r%s")
+
+ self.profiles = gtk.ListStore(str, str, str)
+ self.profiles.append(DEFAULT_PROFILE)
+ with open(PROFILES) as f:
+ c = map(str.rstrip, f.readlines())
+ for p in zip(*(c[i::3] for i in range(3))):
+ self.profiles.append(p)
+
+ self.statusbar = builder.get_object("statusbar")
+ self.contextid = self.statusbar.get_context_id('')
+
+ self.comboentry = builder.get_object("ProfileComboEntry")
+ self.comboentry.set_model(self.profiles)
+ self.comboentry.set_text_column(0)
+
+ self.entry = self.comboentry.get_child()
+ self.entry.connect('changed', self.on_combo_changed)
+
+ self.load = builder.get_object('LoadProfile')
+ self.save = builder.get_object('SaveProfile')
+
+ self.comboentry.set_active(0)
+
+ # read current config:
+ self.on_UndoChanges_clicked(None)
+
+ self.window = builder.get_object("window")
+ self.window.connect("destroy", self.on_window_destroy)
+ self.window.show_all()
+
+ accelgrp = gtk.AccelGroup()
+ key, mod = gtk.accelerator_parse('<Control>Q')
+ accelgrp.connect_group(key, mod, 0, self.on_window_destroy)
+ self.window.add_accel_group(accelgrp)
+
+ def write_profiles_to_file(self):
+ with open(PROFILES, 'w') as f:
+ for name,left,right in self.profiles:
+ if name != DEFAULT_PROFILENAME:
+ f.write("%s\n%s\n%s\n" % (name, left, right))
+
+ def Notify(self, message):
+ self.statusbar.pop(self.contextid)
+ self.statusbar.push(self.contextid, message)
+
+ def on_combo_changed(self, widget, *data):
+ profileid = self.comboentry.get_active()
+ self.load.set_sensitive(profileid != -1)
+ self.save.set_sensitive(profileid != 0 and self.entry.get_text() != '')
+
+ def on_ResetLeft_clicked(self, widget, *data):
+ self.Notify("Resetting left nub...")
+ os.system(RESET_CMD_LEFT % 1)
+ time.sleep(1)
+ os.system(RESET_CMD_LEFT % 0)
+ self.Notify("Left nub reset")
+
+ def on_ResetRight_clicked(self, widget, *data):
+ self.Notify("Resetting right nub...")
+ os.system(RESET_CMD_RIGHT % 1)
+ time.sleep(1)
+ os.system(RESET_CMD_RIGHT % 0)
+ self.Notify("Right nub reset")
+
+ def on_UndoChanges_clicked(self, widget, *data):
+ self.leftnub.SetConfig(ReadConfigFromProc(LEFT_NUB_CONFIG))
+ self.rightnub.SetConfig(ReadConfigFromProc(RIGHT_NUB_CONFIG))
+ self.Notify("Active nub configuration loaded.")
+
+ def on_ApplyChanges_clicked(self, widget, *data):
+ StoreConfigToProc(LEFT_NUB_CONFIG, self.leftnub.GetConfig())
+ StoreConfigToProc(RIGHT_NUB_CONFIG, self.rightnub.GetConfig())
+ self.Notify("Nub configuration updated.")
+
+ def on_SaveProfile_clicked(self, widget, *data):
+ name = self.entry.get_text().replace(' ', '_')
+ if name == '' or name == DEFAULT_PROFILENAME:
+ self.Notify("Invalid profile name")
+ else:
+ left = ' '.join(self.leftnub.GetConfig())
+ right = ' '.join(self.rightnub.GetConfig())
+ for profileid, row in enumerate(self.profiles):
+ if row[0] == name:
+ row[1] = left
+ row[2] = right
+ self.comboentry.set_active(profileid)
+ break
+ else:
+ self.profiles.append([name, left, right])
+ self.entry.set_text(name)
+ self.Notify("Profile saved as: %s" % name)
+
+ def on_LoadProfile_clicked(self, widget, *data):
+ profileid = self.comboentry.get_active()
+ if profileid == -1:
+ self.Notify("Cannot load profile, please select an existing profile.")
+ else:
+ name, left, right = self.profiles[profileid]
+ self.leftnub.SetConfig(left.split(' '))
+ self.rightnub.SetConfig(right.split(' '))
+ self.Notify("Profile loaded, hit 'Apply configuration' to make it active")
+
+ def on_window_destroy(self, widget, *data):
+ self.Notify("Storing profiles...")
+ self.write_profiles_to_file()
+ gtk.main_quit()
+
+if __name__ == '__main__':
+ parser = optparse.OptionParser()
+ parser.add_option('--reset', default='',
+ help="Reset specified nub(s). Format: left,right")
+ parser.add_option('-l', '--left_nub', default='',
+ help="Configure left nub. Format: %s. E.g. mouse,150,20,20,7,7" % ' '.join(FILES).replace(' ', ','))
+ parser.add_option('-r', '--right_nub', default='',
+ help="Configure right nub. Format: %s. E.g. mbuttons,150,20,20,7,7" % ' '.join(FILES).replace(' ', ','))
+ parser.add_option('-s', '--save_profile', default='',
+ help="Store current configuration as specified profile (no spaces allowed)")
+ parser.add_option('-p', '--load_profile', default='',
+ help="Load specified nub configuration profile")
+ options, args = parser.parse_args()
+
+ app = NubConfig()
+ if len(sys.argv) == 1: # run gui app
+ gtk.main()
+ else: # run command line app
+ if 'left' in options.reset:
+ app.on_ResetLeft_clicked(None)
+ if 'right' in options.reset:
+ app.on_ResetRight_clicked(None)
+ ok, values = Validate(options.left_nub)
+ if ok:
+ StoreConfigToProc(LEFT_NUB_CONFIG, values)
+ ok, values = Validate(options.right_nub)
+ if ok:
+ StoreConfigToProc(RIGHT_NUB_CONFIG, values)
+ if options.save_profile:
+ app.on_UndoChanges_clicked(None)
+ app.entry.set_text(options.save_profile)
+ app.on_SaveProfile_clicked(None)
+ app.write_profiles_to_file()
+ if options.load_profile:
+ for profileid, row in enumerate(app.profiles):
+ if row[0] == options.load_profile:
+ app.comboentry.set_active(profileid)
+ app.on_LoadProfile_clicked(None)
+ app.on_ApplyChanges_clicked(None)