Browse files

Fully re-writted application in order to support multiple devices

  • Loading branch information...
1 parent 4dc70c1 commit 3a7cb080b1df2c7f4d1c1a96dde39c2adf9789f0 @zedtux zedtux committed Mar 3, 2012
Showing with 754 additions and 1,392 deletions.
  1. +2 −2 COPYING
  2. +19 −18 bin/naturalscrolling
  3. +1 −0 bin/naturalscrolling.py
  4. BIN data/media/background.png
  5. +0 −10 data/naturalscrolling.desktop
  6. +0 −55 data/ui/AboutNaturalscrollingDialog.ui
  7. +0 −9 data/ui/about_naturalscrolling_dialog.xml
  8. BIN help/C/figures/icon.png
  9. +0 −44 help/C/index.page
  10. +0 −18 help/C/preferences.page
  11. +0 −18 help/C/topic1.page
  12. +0 −36 naturalscrolling/AboutNaturalscrollingDialog.py
  13. +0 −177 naturalscrolling/NaturalscrollingIndicator.py
  14. +25 −32 naturalscrolling/__init__.py
  15. +66 −0 naturalscrolling/indicator.py
  16. +159 −0 naturalscrolling/indicatormenu.py
  17. +69 −0 naturalscrolling/xinputwarper.py
  18. +0 −49 naturalscrolling_lib/AboutDialog.py
  19. +0 −312 naturalscrolling_lib/Builder.py
  20. +0 −166 naturalscrolling_lib/GConfSettings.py
  21. +0 −38 naturalscrolling_lib/SwissKnife.py
  22. +0 −23 naturalscrolling_lib/__init__.py
  23. +228 −0 naturalscrolling_lib/gconfsettings.py
  24. +0 −113 naturalscrolling_lib/helpers.py
  25. +43 −31 naturalscrolling_lib/naturalscrollingconfig.py
  26. +89 −0 naturalscrolling_lib/udevobservator.py
  27. +0 −78 po/naturalscrolling.pot
  28. +53 −46 setup.py
  29. +0 −8 tests/data/xinput/xinput_one_slave_pointer.txt
  30. +0 −14 tests/data/xinput/xinput_seven_slave_pointer.txt
  31. +0 −15 tests/data/xinput/xinput_six_slave_pointer.txt
  32. +0 −10 tests/data/xinput/xinput_three_slave_pointer.txt
  33. +0 −9 tests/data/xinput/xinput_two_slave_pointer.txt
  34. +0 −61 tests/test_xinput_reader.py
View
4 COPYING
@@ -77,7 +77,7 @@ modification follow.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
-
+
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
@@ -510,7 +510,7 @@ actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
-
+
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
View
37 bin/naturalscrolling
@@ -1,45 +1,46 @@
#!/usr/bin/python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
-# Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis <ce@eumorphed.com>
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 3, as published
+# Copyright (C) 2011 Eumorphed UG,
+# Charalampos Emmanouilidis <ce@eumorphed.com>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranties of
+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
+#
+# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE
import sys
import os
-import gettext
-from gettext import gettext as _
-gettext.textdomain('naturalscrolling')
-
# Add project root directory (enable symlink and trunk execution)
PROJECT_ROOT_DIRECTORY = os.path.abspath(
os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))
python_path = []
-if os.path.abspath(__file__).startswith('/opt'):
+if os.path.abspath(__file__).startswith("/opt"):
syspath = sys.path[:] # copy to avoid infinite loop in pending objects
for path in syspath:
- opt_path = path.replace('/usr', '/opt/extras.ubuntu.com/naturalscrolling')
+ opt_path = path.replace("/usr",
+ "/opt/extras.ubuntu.com/naturalscrolling")
python_path.insert(0, opt_path)
sys.path.insert(0, opt_path)
-if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'naturalscrolling'))
+
+if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, "naturalscrolling"))
and PROJECT_ROOT_DIRECTORY not in sys.path):
python_path.insert(0, PROJECT_ROOT_DIRECTORY)
sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
+
if python_path:
- os.putenv('PYTHONPATH', "%s:%s" % (os.getenv('PYTHONPATH', ''), ':'.join(python_path))) # for subprocesses
+ os.putenv("PYTHONPATH", "%s:%s" %
+ (os.getenv("PYTHONPATH", ""), ":".join(python_path)))
import naturalscrolling
naturalscrolling.main()
View
1 bin/naturalscrolling.py
View
BIN data/media/background.png
Deleted file not rendered
View
10 data/naturalscrolling.desktop
@@ -1,10 +0,0 @@
-[Desktop Entry]
-Type=Application
-Encoding=UTF-8
-Name=NaturalScrolling
-Comment=Natural scrolling applet
-Exec=sh -c '/usr/bin/naturalscrolling'
-Hidden=false
-NoDisplay=false
-Terminal=false
-X-GNOME-Autostart-enabled=true
View
55 data/ui/AboutNaturalscrollingDialog.ui
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<interface>
- <!-- interface-requires about_naturalscrolling_dialog 1.0 -->
- <requires lib="gtk+" version="2.16"/>
- <!-- interface-naming-policy toplevel-contextual -->
- <object class="AboutNaturalscrollingDialog" id="about_naturalscrolling_dialog">
- <property name="can_focus">False</property>
- <property name="border_width">5</property>
- <property name="icon">../media/naturalscrolling.svg</property>
- <property name="type_hint">normal</property>
- <property name="program_name">Natural Scrolling</property>
- <property name="copyright" translatable="yes">Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis &lt;ce@eumorphed.com&gt;
-Guillaume Hain &lt;zedtux@zedroot.org&gt;</property>
- <property name="license" translatable="yes"># Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis &lt;ce@eumorphed.com&gt;
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 3, as published
-# by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.
- </property>
- <property name="authors">Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis &lt;ce@eumorphed.com&gt;
-Guillaume Hain &lt;zedtux@zedroot.org&gt;</property>
- <property name="logo">../media/naturalscrolling.svg</property>
- <child internal-child="vbox">
- <object class="GtkVBox" id="dialog-vbox1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">2</property>
- <child internal-child="action_area">
- <object class="GtkHButtonBox" id="dialog-action_area1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="layout_style">end</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="pack_type">end</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <placeholder/>
- </child>
- </object>
- </child>
- </object>
-</interface>
View
9 data/ui/about_naturalscrolling_dialog.xml
@@ -1,9 +0,0 @@
-<glade-catalog name="about_naturalscrolling_dialog" domain="glade-3"
- depends="gtk+" version="1.0">
- <glade-widget-classes>
- <glade-widget-class title="About Naturalscrolling Dialog" name="AboutNaturalscrollingDialog"
- generic-name="AboutNaturalscrollingDialog" parent="GtkAboutDialog"
- icon-name="widget-gtk-about-dialog"/>
- </glade-widget-classes>
-
-</glade-catalog>
View
BIN help/C/figures/icon.png
Deleted file not rendered
View
44 help/C/index.page
@@ -1,44 +0,0 @@
-<page xmlns="http://projectmallard.org/1.0/"
- type="guide"
- id="index">
-
-<info>
- <!-- This shows in the title bar so does not show [icon] -->
- <title type="text">Naturalscrolling</title>
- <desc>The <app>Naturalscrolling</app> help.</desc>
- <credit type="author">
- <name>Your Name</name>
- <email>Your E-mail</email>
- <years>2010</years>
- </credit>
- <license href="http://creativecommons.org/licenses/by-sa/3.0/">
- <p>Creative Commons Attribution-Share Alike 3.0 Unported License</p>
- </license>
-</info>
-
-<title>
-<!-- This shows on the page in title font -->
-<!-- the icon only shows when installed -->
-<media type="image" mime="image/png" src="figures/icon.png">[icon]</media>
-<app>Naturalscrolling</app> Help
-</title>
-
-<p>This is an example guide page. It's main function is to link together the help topics.</p>
-
-<!-- This is the visible index -->
-<section id="contents" style="2column">
-<!-- other pages needs to have a link to index#contents for this to work -->
-<title>Contents</title>
-</section>
-
-<note>
- <!-- delete this note -->
- <p>
- Your script or application looks better if it has help files similar to other applications in ubuntu.
- </p>
- <p>
- Some people think that help files are only for apps that are difficult to use.
- </p>
-</note>
-
-</page>
View
18 help/C/preferences.page
@@ -1,18 +0,0 @@
-<page xmlns="http://projectmallard.org/1.0/"
- type="topic"
- id="preferences">
-
-<info>
- <link type="guide" xref="index#contents"/>
- <credit type="author">
- <name>Your Name</name>
- <email>Your E-mail</email>
- <years>2010</years>
- </credit>
- <desc>Optional short description of Preferences for contents page</desc>
-</info>
-
-<title>Preferences</title>
-<p>This is the preferences page.</p>
-
-</page>
View
18 help/C/topic1.page
@@ -1,18 +0,0 @@
-<page xmlns="http://projectmallard.org/1.0/"
- type="topic"
- id="topic1">
-
-<info>
- <link type="guide" xref="index#contents"/>
- <credit type="author">
- <name>Your Name</name>
- <email>Your E-mail</email>
- <years>2010</years>
- </credit>
- <desc>Optional short description of Topic 1 for contents page</desc>
-</info>
-
-<title>Topic 1</title>
-<p>This is an example topic page. It's main function is to describe one topic.</p>
-
-</page>
View
36 naturalscrolling/AboutNaturalscrollingDialog.py
@@ -1,36 +0,0 @@
-# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
-### BEGIN LICENSE
-# Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis <ce@eumorphed.com>
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 3, as published
-# by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-### END LICENSE
-
-import gettext
-from gettext import gettext as _
-gettext.textdomain('naturalscrolling')
-
-import logging
-logger = logging.getLogger('naturalscrolling')
-
-from naturalscrolling_lib.AboutDialog import AboutDialog
-
-# See naturalscrolling_lib.AboutDialog.py for more details about how this class works.
-class AboutNaturalscrollingDialog(AboutDialog):
- __gtype_name__ = "AboutNaturalscrollingDialog"
-
- def finish_initializing(self, builder): # pylint: disable=E1002
- """Set up the about dialog"""
- super(AboutNaturalscrollingDialog, self).finish_initializing(builder)
-
- # Code for other initialization actions should be added here.
-
View
177 naturalscrolling/NaturalscrollingIndicator.py
@@ -1,177 +0,0 @@
-#!/usr/bin/env python
-import sys
-import os
-import gtk
-import appindicator
-
-from naturalscrolling_lib import naturalscrollingconfig
-from naturalscrolling_lib import SwissKnife
-from naturalscrolling_lib.GConfSettings import *
-from naturalscrolling.AboutNaturalscrollingDialog import AboutNaturalscrollingDialog
-
-import gettext
-from gettext import gettext as _
-gettext.textdomain('naturalscrolling')
-
-class NaturalscrollingIndicator:
-
- def __init__(self):
- self.AboutDialog = AboutNaturalscrollingDialog
- self.mouseids = self.get_slave_pointer()
- self.pingfrequency = 1 # in seconds
-
- self.ind = appindicator.Indicator(
- "natural-scrolling-indicator",
- "natural-scrolling-status-not-activated",
- appindicator.CATEGORY_APPLICATION_STATUS
- )
- self.settings = GConfSettings()
-
- media_path = "%s/media/" % naturalscrollingconfig.get_data_path()
- self.ind.set_icon_theme_path(media_path)
- self.ind.set_attention_icon("natural-scrolling-status-activated")
-
- self.menu_setup()
- self.ind.set_menu(self.menu)
-
- def get_slave_pointer(self):
- xinput_reader = SwissKnife.XinputReader()
- xinput = SwissKnife.Xinput()
-
- return xinput_reader.get_slave_pointer(xinput.list())[0]
-
- def menu_setup(self):
- self.menu = gtk.Menu()
-
- #natural scrolling
- self.menu_item_natural_scrolling = gtk.CheckMenuItem(_('Natural Scrolling'))
- self.enable_natural_scrolling(
- self.settings.key(GCONF_NATURAL_SCROLLING_KEY).is_enable()
- )
- self.menu_item_natural_scrolling.connect('activate', self.on_natural_scrolling_toggled)
- self.settings.server().fire_me_when_update_on_key(GCONF_NATURAL_SCROLLING_KEY, self.enable_natural_scrolling)
- self.menu_item_natural_scrolling.show()
-
- #seperator 1
- self.menu_item_seperator1 = gtk.SeparatorMenuItem()
- self.menu_item_seperator1.show()
-
- #preferences
- self.menu_sub = gtk.Menu()
-
- self.menu_item_preferences = gtk.MenuItem(_('Preferences'))
- self.menu_item_start_at_login = gtk.CheckMenuItem(_('Start at login'))
- if os.path.isfile(naturalscrollingconfig.get_auto_start_file_path()):
- self.menu_item_start_at_login.set_active(True)
- self.menu_item_start_at_login.connect("activate", self.on_start_at_login_clicked)
- self.menu_sub.append(self.menu_item_start_at_login)
- self.menu_item_preferences.set_submenu(self.menu_sub)
-
- self.menu_item_start_at_login.show()
- self.menu_item_preferences.show()
-
- #about
- self.menu_item_about = gtk.MenuItem(_('About...'))
- self.menu_item_about.connect('activate', self.on_about_clicked)
- self.menu_item_about.show()
-
- #seperator 2
- self.menu_item_seperator2 = gtk.SeparatorMenuItem()
- self.menu_item_seperator2.show()
-
- #quit
- self.menu_item_quit = gtk.MenuItem(_('Quit Natural Scrolling'))
- self.menu_item_quit.connect("activate", self.quit)
- self.menu_item_quit.show()
-
- #add items to menu
- self.menu.append(self.menu_item_natural_scrolling)
- self.menu.append(self.menu_item_seperator1)
- self.menu.append(self.menu_item_preferences)
- self.menu.append(self.menu_item_about)
- self.menu.append(self.menu_item_seperator2)
- self.menu.append(self.menu_item_quit)
-
- def enable_natural_scrolling(self, enabled):
- """
- Global method to apply or not Natural Scrolling
- """
- map = os.popen('xinput get-button-map %s' % self.mouseids).read().strip()
-
- if enabled == True:
- map = map.replace('4 5', '5 4')
- map = map.replace('6 7', '7 6')
- self.settings.key(GCONF_NATURAL_SCROLLING_KEY).enable()
- self.ind.set_status(appindicator.STATUS_ATTENTION)
- else:
- map = map.replace('5 4', '4 5')
- map = map.replace('7 6', '6 7')
- self.settings.key(GCONF_NATURAL_SCROLLING_KEY).disable()
- self.ind.set_status(appindicator.STATUS_ACTIVE)
-
- self.menu_item_natural_scrolling.set_active(enabled)
-
- os.system('xinput set-button-map %s %s' %(self.mouseids, map))
-
- def on_natural_scrolling_toggled(self, widget, data=None):
- """
- Fired method when user click on gtk.CheckMenuItem 'Natural Scrolling'
- """
- self.enable_natural_scrolling(widget.get_active())
-
- def on_start_at_login_clicked(self, widget, data=None):
- """
- Fired method when user click on gtk.CheckMenuItem 'Start at login'
- """
- if not os.path.exists(naturalscrollingconfig.get_auto_start_path()):
- os.makedirs(naturalscrollingconfig.get_auto_start_path())
-
- auto_start_file_exists = os.path.isfile(naturalscrollingconfig.get_auto_start_file_path())
- if widget.get_active():
- if not auto_start_file_exists:
- source = open(naturalscrollingconfig.get_data_path() + "/" + naturalscrollingconfig.get_auto_start_file_name(), "r")
- destination = open(naturalscrollingconfig.get_auto_start_file_path(), "w")
- destination.write(source.read())
- destination.close() and source.close()
- else:
- if auto_start_file_exists:
- os.remove(naturalscrollingconfig.get_auto_start_file_path())
-
- def on_about_clicked(self, widget, data=None):
- about = self.AboutDialog() # pylint: disable=E1102
- response = about.run()
- about.destroy()
-
-
- def isreversed(self):
- inreverseorder = False
-
- for id in self.mouseids:
- map = os.popen('xinput get-button-map %s' % id).read().strip()
-
- if '5 4' in map:
- inreverseorder = True
- break
-
- return inreverseorder
-
-
- def check_scrolling(self):
- if self.isreversed():
- self.ind.set_status(appindicator.STATUS_ATTENTION)
- else:
- self.ind.set_status(appindicator.STATUS_ACTIVE)
-
- return True
-
-
- def main(self):
- self.check_scrolling()
- gtk.timeout_add(self.pingfrequency * 1000, self.check_scrolling)
-
- gtk.main()
-
-
- def quit(self, widget):
- sys.exit(0)
-
View
57 naturalscrolling/__init__.py
@@ -1,45 +1,38 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
-# Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis <ce@eumorphed.com>
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 3, as published
+# Copyright (C) 2011 Eumorphed UG,
+# Charalampos Emmanouilidis <ce@eumorphed.com>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranties of
+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>
### END LICENSE
import optparse
-import gettext
-from gettext import gettext as _
-gettext.textdomain('naturalscrolling')
+from naturalscrolling_lib.naturalscrollingconfig import *
+from naturalscrolling.indicator import Indicator
+from naturalscrolling_lib.gconfsettings import GConfSettings
+from naturalscrolling.xinputwarper import XinputWarper
-import gtk
-from naturalscrolling import NaturalscrollingIndicator
-from naturalscrolling_lib import set_up_logging, get_version
-
-def parse_options():
+def main():
"""Support for command line options"""
- parser = optparse.OptionParser(version="%%prog %s" % get_version())
- parser.add_option(
- "-v", "--verbose", action="count", dest="verbose",
- help=_("Show debug messages (-vv debugs naturalscrolling_lib also)"))
+ parser = optparse.OptionParser(version="%%prog %s" % appliation_version())
+ parser.add_option("-v", "--verbose", action="count", dest="verbose",
+ help="Show debug messages (-vv debugs naturalscrolling_lib also)")
(options, args) = parser.parse_args()
- set_up_logging(options)
-
-def main():
- 'constructor for your class instances'
- parse_options()
+ # Initialize the GConf client
+ GConfSettings().server().on_update_fire(
+ XinputWarper().enable_natural_scrolling)
- # Run the application.
- indicator = NaturalscrollingIndicator.NaturalscrollingIndicator()
- indicator.main()
+ Indicator().start()
View
66 naturalscrolling/indicator.py
@@ -0,0 +1,66 @@
+import gtk
+import appindicator
+
+from naturalscrolling_lib import naturalscrollingconfig
+from naturalscrolling_lib.gconfsettings import GConfSettings
+from naturalscrolling_lib.udevobservator import UDevObservator
+from naturalscrolling.indicatormenu import IndicatorMenu
+
+
+class Indicator(object):
+ # Singleton
+ _instance = None
+ _init_done = False
+
+ def __new__(cls, *args, **kwargs):
+ if not cls._instance:
+ cls._instance = super(Indicator, cls).__new__(cls, *args,
+ **kwargs)
+ return cls._instance
+
+ def __init__(self):
+ # Initialize a new AppIndicator
+ self.indicator = appindicator.Indicator(
+ "natural-scrolling-indicator",
+ "natural-scrolling-status-not-activated",
+ appindicator.CATEGORY_APPLICATION_STATUS)
+ media_path = "%s/media/" % naturalscrollingconfig.get_data_path()
+ self.indicator.set_icon_theme_path(media_path)
+ self.indicator.set_attention_icon(
+ "natural-scrolling-status-activated")
+
+ menu = IndicatorMenu()
+ self.indicator.set_menu(menu)
+
+ # Initialize the UDev client
+ udev_observator = UDevObservator()
+ udev_observator.on_update_execute(menu.refresh)
+ udev_observator.start()
+
+ # Force the first refresh of the menu in order to populate it.
+ menu.refresh(udev_observator.gather_devices_names())
+
+ GConfSettings().initialize(udev_observator.gather_devices())
+
+ def status_attention(self):
+ self.set_status(appindicator.STATUS_ATTENTION)
+
+ def status_active(self):
+ self.set_status(appindicator.STATUS_ACTIVE)
+
+ def isreversed(self):
+ return True
+
+ def check_scrolling(self):
+ if self.isreversed():
+ self.indicator.set_status(appindicator.STATUS_ATTENTION)
+ else:
+ self.indicator.set_status(appindicator.STATUS_ACTIVE)
+ return True
+
+ def start(self):
+ self.check_scrolling()
+ try:
+ gtk.main()
+ except KeyboardInterrupt:
+ pass
View
159 naturalscrolling/indicatormenu.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+import os
+import gtk
+import webbrowser
+from naturalscrolling_lib.naturalscrollingconfig import *
+from naturalscrolling_lib.gconfsettings import GConfSettings
+from naturalscrolling.xinputwarper import XinputWarper
+
+
+class IndicatorMenu(gtk.Menu):
+
+ def __init__(self):
+ gtk.Menu.__init__(self)
+
+ # "Natural Scrolling" item is now dynamic.
+ # Look at refresh() method
+ self.__natural_scrolling = None
+
+ self.append(self.new_separator())
+
+ menu_sub = gtk.Menu()
+ start_at_login = gtk.CheckMenuItem("Start at login")
+ if os.path.isfile(get_auto_start_file_path()):
+ start_at_login.set_active(True)
+ start_at_login.connect("activate", self.on_start_at_login_clicked)
+ menu_sub.append(start_at_login)
+ start_at_login.show()
+
+ preferences = gtk.MenuItem("Preferences")
+ preferences.set_submenu(menu_sub)
+ self.append(preferences)
+ preferences.show()
+
+ about = gtk.MenuItem("About")
+ about.connect("activate", self.on_about_clicked)
+ self.append(about)
+ about.show()
+
+ self.append(self.new_separator())
+
+ #quit
+ quit = gtk.MenuItem("Quit Natural Scrolling")
+ quit.connect("activate", self.on_quit_clicked)
+ self.append(quit)
+ quit.show()
+
+ self.show()
+
+ def new_separator(self):
+ seperator = gtk.SeparatorMenuItem()
+ seperator.show()
+ return seperator
+
+ def refresh(self, devices):
+ """
+ Fire this method with the list of devices to display in order to
+ refresh the menu.
+ If there is only one device, the "Natural scrolling" menu item will be
+ a gtk.CheckMenuItem, but if there are more than one devcice, then
+ "Natural scrolling" menu item will be a gtk.Menu of gtk.CheckMenuItem
+ per device.
+ """
+ if self.__natural_scrolling:
+ self.remove(self.__natural_scrolling)
+ self.__natural_scrolling = None
+
+ if len(devices) == 1:
+ self.__natural_scrolling = gtk.CheckMenuItem("Natural Scrolling")
+ self.__natural_scrolling.connect("toggled",
+ self.on_natural_scrolling_toggled)
+ self.__natural_scrolling.show()
+ else:
+ self.__natural_scrolling = gtk.MenuItem("Natural Scrolling")
+ self.__natural_scrolling.show()
+ devices_menu = gtk.Menu()
+ for device in devices:
+ sub_item = gtk.CheckMenuItem(device)
+ devices_menu.append(sub_item)
+ sub_item.connect("toggled", self.on_natural_scrolling_toggled)
+ sub_item.show()
+
+ self.__natural_scrolling.set_submenu(devices_menu)
+
+ self.insert(self.__natural_scrolling, 0)
+
+ def on_quit_clicked(self, widget):
+ gtk.main_quit()
+
+ def on_natural_scrolling_toggled(self, widget, data=None):
+ """
+ Fired method when user click on gtk.CheckMenuItem 'Natural Scrolling'
+ """
+ enabled = widget.get_active()
+ print enabled
+ natural_scrolling_or_device_name = widget.get_label()
+
+ # When there is only one detected device
+ # the label of the gtk.CheckMenuItem is "Natural Scrolling"
+ if natural_scrolling_or_device_name == "Natural Scrolling":
+ # So the device XID is the id of the first device
+ device_xid = XinputWarper().first_xid()
+ else:
+ device_xid = XinputWarper().find_xid_by_name(widget.get_label())
+
+ GConfSettings().key(device_xid).set_value(enabled)
+
+ def on_start_at_login_clicked(self, widget, data=None):
+ """
+ Fired method when user click on gtk.CheckMenuItem 'Start at login'
+ """
+ if not os.path.exists(get_auto_start_path()):
+ os.makedirs(get_auto_start_path())
+
+ auto_start_file_exists = os.path.isfile(get_auto_start_file_path())
+ if widget.get_active():
+ if not auto_start_file_exists:
+ source = open(get_data_path() + "/" + \
+ get_auto_start_file_name(), "r")
+ destination = open(get_auto_start_file_path(), "w")
+ destination.write(source.read())
+ destination.close() and source.close()
+ else:
+ if auto_start_file_exists:
+ os.remove(get_auto_start_file_path())
+
+ def click_website(self, dialog, link):
+ webbrowser.open(link)
+
+ def on_about_clicked(self, widget, data=None):
+ gtk.about_dialog_set_url_hook(self.click_website)
+
+ app_name = appliation_name()
+ about = gtk.AboutDialog()
+ about.set_name(app_name)
+ about.set_version(appliation_version())
+ about.set_icon(
+ gtk.gdk.pixbuf_new_from_file("data/media/naturalscrolling.svg"))
+ about.set_logo(
+ gtk.gdk.pixbuf_new_from_file("data/media/naturalscrolling.svg"))
+ about.set_website(appliation_website())
+ about.set_website_label("%s Website" % app_name)
+ about.set_authors(["Charalampos Emmanouilidis <ce@eumorphed.com>",
+ "Guillaume Hain <zedtux@zedroot.org>"])
+ about.set_copyright("Copyright © 2011 Eumorphed UG")
+ about.set_wrap_license(True)
+ about.set_license(("%s is free software; you can redistribute it "
+ "and/or modify it under the terms of the GNU General "
+ "Public License as published by the Free Software Foundation; "
+ "either version 3 of the License, or (at your option) any later "
+ "version.\n\n%s is distributed in the hope that it will be "
+ "useful, but WITHOUT ANY WARRANTY; without even the implied "
+ "warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+ " See the GNU General Public License for more details.\n\n"
+ "You should have received a copy of the GNU General Public "
+ "License along with %s; if not, write to the Free Software "
+ "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, "
+ "MA 02110-1301, USA") % (app_name, app_name, app_name))
+ about.run()
+ about.destroy()
View
69 naturalscrolling/xinputwarper.py
@@ -0,0 +1,69 @@
+import os
+import re
+
+
+class XinputWarper(object):
+ # Singleton
+ _instance = None
+
+ def __new__(cls, *args, **kwargs):
+ if not cls._instance:
+ cls._instance = super(XinputWarper, cls).__new__(cls, *args,
+ **kwargs)
+ cls._instance.__xinput_list_pattern = re.compile(
+ r'\s+([A-z0-9\s\-\(\)]+)\s+id=(\d+)\s+\[slave\s+pointer.*\]')
+ cls._instance.__xinput_list = None
+ return cls._instance
+
+ def enable_natural_scrolling(self, devise_xid, enabled):
+ """
+ Global method to apply or not Natural Scrolling
+ """
+ map = os.popen("xinput get-button-map \"%s\"" %
+ devise_xid).read().strip()
+
+ if enabled == True:
+ map = map.replace("4 5", "5 4")
+ map = map.replace("6 7", "7 6")
+ else:
+ map = map.replace("5 4", "4 5")
+ map = map.replace("7 6", "6 7")
+
+ os.system("xinput set-button-map \"%s\" %s" %(devise_xid, map))
+
+ def find_xid_by_name(self, name):
+ """
+ Extract from the xinput list the id of the given device name
+ """
+ xinput_list = self._xinput_list(name)
+ for device_info in self.__xinput_list_pattern.findall(xinput_list):
+ if device_info[0].strip() == name:
+ return device_info[1]
+ return None
+
+ def first_xid(self):
+ """
+ Extract from the xinput list the id of the first detected device
+ """
+ xinput_list = self._xinput_list()
+ return self.__xinput_list_pattern.findall(xinput_list)[0][1]
+
+ def reset_cache(self):
+ """
+ Clear xinput cache in order to force refresh
+ """
+ self.__xinput_list = None
+
+ def _xinput_list(self, name=None):
+ """
+ Refresh cache and/or search in cached xinput list
+ """
+ if not self.__xinput_list:
+ self.__xinput_list = os.popen(("xinput list | grep -v 'XTEST' "
+ "| grep -v '\[master '")).read()
+
+ if name:
+ return re.search(r'(.*%s.*)' % re.escape(name),
+ self.__xinput_list).group(1)
+ else:
+ return self.__xinput_list
View
49 naturalscrolling_lib/AboutDialog.py
@@ -1,49 +0,0 @@
-# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
-### BEGIN LICENSE
-# Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis <ce@eumorphed.com>
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 3, as published
-# by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-### END LICENSE
-
-import gtk
-
-from . helpers import get_builder
-
-class AboutDialog(gtk.AboutDialog):
- __gtype_name__ = "AboutDialog"
-
- def __new__(cls):
- """Special static method that's automatically called by Python when
- constructing a new instance of this class.
-
- Returns a fully instantiated AboutDialog object.
- """
- builder = get_builder('AboutNaturalscrollingDialog')
- new_object = builder.get_object("about_naturalscrolling_dialog")
- new_object.finish_initializing(builder)
- return new_object
-
- def finish_initializing(self, builder):
- """Called while initializing this instance in __new__
-
- finish_initalizing should be called after parsing the ui definition
- and creating a AboutDialog object with it in order
- to finish initializing the start of the new AboutNaturalscrollingDialog
- instance.
-
- Put your initialization code in here and leave __init__ undefined.
- """
- # Get a reference to the builder and set up the signals.
- self.builder = builder
- self.ui = builder.get_ui(self)
-
View
312 naturalscrolling_lib/Builder.py
@@ -1,312 +0,0 @@
-# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
-### BEGIN LICENSE
-# Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis <ce@eumorphed.com>
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 3, as published
-# by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-### END LICENSE
-
-'''Enhances builder connections, provides object to access glade objects'''
-
-import sys
-if 'gobject' in sys.modules:
- import gobject as GObject
-else:
- from gi.repository import GObject
-
-import gtk
-import inspect
-import functools
-import logging
-logger = logging.getLogger('naturalscrolling_lib')
-
-from xml.etree.cElementTree import ElementTree
-
-# this module is big so uses some conventional prefixes and postfixes
-# *s list, except self.widgets is a dictionary
-# *_dict dictionary
-# *name string
-# ele_* element in a ElementTree
-
-
-# pylint: disable=R0904
-# the many public methods is a feature of gtk.Builder
-class Builder(gtk.Builder):
- ''' extra features
- connects glade defined handler to default_handler if necessary
- auto connects widget to handler with matching name or alias
- auto connects several widgets to a handler via multiple aliases
- allow handlers to lookup widget name
- logs every connection made, and any on_* not made
- '''
-
- def __init__(self):
- gtk.Builder.__init__(self)
- self.widgets = {}
- self.glade_handler_dict = {}
- self.connections = []
- self._reverse_widget_dict = {}
-
-# pylint: disable=R0201
-# this is a method so that a subclass of Builder can redefine it
- def default_handler(self,
- handler_name, filename, *args, **kwargs):
- '''helps the apprentice guru
-
- glade defined handlers that do not exist come here instead.
- An apprentice guru might wonder which signal does what he wants,
- now he can define any likely candidates in glade and notice which
- ones get triggered when he plays with the project.
- this method does not appear in gtk.Builder'''
- logger.debug('''tried to call non-existent function:%s()
- expected in %s
- args:%s
- kwargs:%s''', handler_name, filename, args, kwargs)
-# pylint: enable=R0201
-
- def get_name(self, widget):
- ''' allows a handler to get the name (id) of a widget
-
- this method does not appear in gtk.Builder'''
- return self._reverse_widget_dict.get(widget)
-
- def add_from_file(self, filename):
- '''parses xml file and stores wanted details'''
- gtk.Builder.add_from_file(self, filename)
-
- # extract data for the extra interfaces
- tree = ElementTree()
- tree.parse(filename)
-
- ele_widgets = tree.getiterator("object")
- for ele_widget in ele_widgets:
- name = ele_widget.attrib['id']
- widget = self.get_object(name)
-
- # populate indexes - a dictionary of widgets
- self.widgets[name] = widget
-
- # populate a reversed dictionary
- self._reverse_widget_dict[widget] = name
-
- # populate connections list
- ele_signals = ele_widget.findall("signal")
-
- connections = [
- (name,
- ele_signal.attrib['name'],
- ele_signal.attrib['handler']) for ele_signal in ele_signals]
-
- if connections:
- self.connections.extend(connections)
-
- ele_signals = tree.getiterator("signal")
- for ele_signal in ele_signals:
- self.glade_handler_dict.update(
- {ele_signal.attrib["handler"]: None})
-
- def connect_signals(self, callback_obj):
- '''connect the handlers defined in glade
-
- reports successful and failed connections
- and logs call to missing handlers'''
- filename = inspect.getfile(callback_obj.__class__)
- callback_handler_dict = dict_from_callback_obj(callback_obj)
- connection_dict = {}
- connection_dict.update(self.glade_handler_dict)
- connection_dict.update(callback_handler_dict)
- for item in connection_dict.items():
- if item[1] is None:
- # the handler is missing so reroute to default_handler
- handler = functools.partial(
- self.default_handler, item[0], filename)
-
- connection_dict[item[0]] = handler
-
- # replace the run time warning
- logger.warn("expected handler '%s' in %s",
- item[0], filename)
-
- # connect glade define handlers
- gtk.Builder.connect_signals(self, connection_dict)
-
- # let's tell the user how we applied the glade design
- for connection in self.connections:
- widget_name, signal_name, handler_name = connection
- logger.debug("connect builder by design '%s', '%s', '%s'",
- widget_name, signal_name, handler_name)
-
- def get_ui(self, callback_obj=None, by_name=True):
- '''Creates the ui object with widgets as attributes
-
- connects signals by 2 methods
- this method does not appear in gtk.Builder'''
-
- result = UiFactory(self.widgets)
-
- # Hook up any signals the user defined in glade
- if callback_obj is not None:
- # connect glade define handlers
- self.connect_signals(callback_obj)
-
- if by_name:
- auto_connect_by_name(callback_obj, self)
-
- return result
-
-
-# pylint: disable=R0903
-# this class deliberately does not provide any public interfaces
-# apart from the glade widgets
-class UiFactory():
- ''' provides an object with attributes as glade widgets'''
- def __init__(self, widget_dict):
- self._widget_dict = widget_dict
- for (widget_name, widget) in widget_dict.items():
- setattr(self, widget_name, widget)
-
- # Mangle any non-usable names (like with spaces or dashes)
- # into pythonic ones
- cannot_message = """cannot bind ui.%s, name already exists
- consider using a pythonic name instead of design name '%s'"""
- consider_message = """consider using a pythonic name instead of design name '%s'"""
-
- for (widget_name, widget) in widget_dict.items():
- pyname = make_pyname(widget_name)
- if pyname != widget_name:
- if hasattr(self, pyname):
- logger.debug(cannot_message, pyname, widget_name)
- else:
- logger.debug(consider_message, widget_name)
- setattr(self, pyname, widget)
-
- def iterator():
- '''Support 'for o in self' '''
- return iter(widget_dict.values())
- setattr(self, '__iter__', iterator)
-
- def __getitem__(self, name):
- 'access as dictionary where name might be non-pythonic'
- return self._widget_dict[name]
-# pylint: enable=R0903
-
-
-def make_pyname(name):
- ''' mangles non-pythonic names into pythonic ones'''
- pyname = ''
- for character in name:
- if (character.isalpha() or character == '_' or
- (pyname and character.isdigit())):
- pyname += character
- else:
- pyname += '_'
- return pyname
-
-
-def dict_from_callback_obj(callback_obj):
- '''a dictionary interface to callback_obj'''
- methods = inspect.getmembers(callback_obj, inspect.ismethod)
-
- aliased_methods = [x[1] for x in methods if hasattr(x[1], 'aliases')]
-
- # a method may have several aliases
- #~ @alias('on_btn_foo_clicked')
- #~ @alias('on_tool_foo_activate')
- #~ on_menu_foo_activate():
- #~ pass
- alias_groups = [(x.aliases, x) for x in aliased_methods]
-
- aliases = []
- for item in alias_groups:
- for alias in item[0]:
- aliases.append((alias, item[1]))
-
- dict_methods = dict(methods)
- dict_aliases = dict(aliases)
-
- results = {}
- results.update(dict_methods)
- results.update(dict_aliases)
-
- return results
-
-
-def auto_connect_by_name(callback_obj, builder):
- '''finds handlers like on_<widget_name>_<signal> and connects them
-
- i.e. find widget,signal pair in builder and call
- widget.connect(signal, on_<widget_name>_<signal>)'''
-
- callback_handler_dict = dict_from_callback_obj(callback_obj)
-
- for item in builder.widgets.items():
- (widget_name, widget) = item
- signal_ids = []
- try:
- widget_type = type(widget)
- while widget_type:
- signal_ids.extend(GObject.signal_list_ids(widget_type))
- widget_type = GObject.type_parent(widget_type)
- except RuntimeError: # pylint wants a specific error
- pass
- signal_names = [GObject.signal_name(sid) for sid in signal_ids]
-
- # Now, automatically find any the user didn't specify in glade
- for sig in signal_names:
- # using convention suggested by glade
- handler_names = ["on_%s_%s" % (widget_name, sig)]
-
- # Using the convention that the top level window is not
- # specified in the handler name. That is use
- # on_destroy() instead of on_windowname_destroy()
- if widget is callback_obj:
- handler_names.append("on_%s" % sig)
-
- do_connect(item, sig, handler_names,
- callback_handler_dict, builder.connections)
-
- log_unconnected_functions(callback_handler_dict, builder.connections)
-
-
-def do_connect(item, signal_name, handler_names,
- callback_handler_dict, connections):
- '''connect this signal to an unused handler'''
- widget_name, widget = item
-
- for handler_name in handler_names:
- target = handler_name in callback_handler_dict.keys()
- connection = (widget_name, signal_name, handler_name)
- duplicate = connection in connections
- if target and not duplicate:
- widget.connect(signal_name, callback_handler_dict[handler_name])
- connections.append(connection)
-
- logger.debug("connect builder by name '%s','%s', '%s'",
- widget_name, signal_name, handler_name)
-
-
-def log_unconnected_functions(callback_handler_dict, connections):
- '''log functions like on_* that we could not connect'''
-
- connected_functions = [x[2] for x in connections]
-
- handler_names = callback_handler_dict.keys()
- unconnected = [x for x in handler_names if x.startswith('on_')]
-
- for handler_name in connected_functions:
- try:
- unconnected.remove(handler_name)
- except ValueError:
- pass
-
- for handler_name in unconnected:
- logger.debug("Not connected to builder '%s'", handler_name)
View
166 naturalscrolling_lib/GConfSettings.py
@@ -1,166 +0,0 @@
-# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
-### BEGIN LICENSE
-# Copyright (C) 2011 Guillaume Hain <zedtux@zedroot.org>
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 3, as published
-# by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-### END LICENSE
-
-import gconf
-
-## GConf setup
-
-# GConf root path
-GCONF_ROOT_DIR = "/apps/naturalscrolling"
-
-# Natural Scrolling keys path definition
-GCONF_NATURAL_SCROLLING_KEY = GCONF_ROOT_DIR + "/natural_scrolling"
-
-# Natural Scrolling keys type definition
-gconf_keys = {GCONF_NATURAL_SCROLLING_KEY: bool}
-
-
-## Exception classes
-class InvalidKey(Exception):
- """ Raised class when key is unknown """
-
-class InvalidKeyType(Exception):
- """ Raised class when key type is unknown """
-
-class GConfServer(object):
- # Singleton
- _instance = None
- _init_done = False
- def __new__(cls, *args, **kwargs):
- if not cls._instance:
- cls._instance = super(GConfServer, cls).__new__(
- cls, *args, **kwargs
- )
- return cls._instance
-
- def __init__(self):
- """
- Open connection to GConf
- and connect to callback on naturalscrolling keys updates
- """
- if self._init_done:
- return
-
- if not hasattr(self, "__key_update_observators"):
- self.__key_update_observators = {}
-
- if not hasattr(self, "client"):
- # Get GConf client:
- self.client = gconf.client_get_default()
-
- # Add the root directory to the list of directories that our GConf
- # client will watch for changes:
- self.client.add_dir(GCONF_ROOT_DIR, gconf.CLIENT_PRELOAD_NONE)
-
- # Assign a callback function for when changes are made to keys in
- # the root directory namespace:
- self.client.notify_add(GCONF_ROOT_DIR, self.on_settings_changed)
-
- self._init_done = True
-
- def fire_me_when_update_on_key(self, key, method):
- """
- Register a Class instance method to fire
- swhen the given an update on the given key have been catched
- """
- self.__key_update_observators[key] = method
-
- def on_settings_changed(self, client, timestamp, entry, *extra):
- """
- This is the callback function that is called when the keys in our
- namespace change (such as editing them with gconf-editor).
- """
- key = entry.get_key()
- if not key in gconf_keys:
- raise InvalidKey("Unknown key %s" % key)
-
- if not key in self.__key_update_observators:
- return
-
- # Execute observer's method passing GConf key value as parameter
- self.__key_update_observators[key](entry.get_value().get_bool())
-
-class GConfKey(object):
-
- def __init__(self, key):
- if not key in gconf_keys:
- raise InvalidKey("Unknown key %s" % key)
-
- self.__gconf = GConfServer().client
- self.__value = None
- self.__key = key
- self.__type = gconf_keys[key]
- self.get_value()
-
- def get_value(self):
- """
- Magic method to read the value from GConf (auto cast)
- """
- if self.__type == bool:
- self.__value = self.__gconf.get_bool(self.__key)
- elif self.__type == str:
- self.__value = self.__gconf.get_string(self.__key)
- elif self.__type == int:
- self.__value = self.__gconf.get_int(self.__key)
- else:
- raise InvalidKeyType("Invalid key type %s" % self.__type)
-
- def set_value(self):
- """
- Magic method to write the value to GConf (auto cast)
- """
- if self.__type == bool:
- self.__gconf.set_bool(self.__key, self.__value)
- elif self.__type == str:
- self.__gconf.set_string(self.__key, self.__value)
- elif self.__type == int:
- self.__gconf.set_int(self.__key, self.__value)
- else:
- raise InvalidKeyType("Invalid key type %s" % self.__type)
-
- def is_enable(self):
- return self.__value == True
-
- def enable(self):
- """
- Set a boolean key value to True
- """
- self.__value = 1
- self.set_value()
-
- def disable(self):
- """
- Set a boolean key value to False
- """
- self.__value = 0
- self.set_value()
-
-class GConfSettings(object):
-
- def server(self):
- """
- Return the Singleton instance of the GConfServer
- """
- return GConfServer()
-
- def key(self, key):
- """
- Ruby styled method to define which is the key to check
- This method return an instance of the GConfKey class
- otherwise raise a InvalidKey or InvalidKeyType
- """
- return GConfKey(key)
View
38 naturalscrolling_lib/SwissKnife.py
@@ -1,38 +0,0 @@
-import os
-
-class XinputReader (object):
- def get_slave_pointer (self, xinput_list_output):
- slavepointer = []
- for line in xinput_list_output.split('\n'):
-
- if 'id=' in line and 'pointer' in line and 'slave' in line and 'XTEST' not in line:
- id = line.split ('id=')[1].split()[0]
- slavepointer.append(id)
-
- return slavepointer
-
-
-class XinputCommand (object):
- def list (self):
- return os.popen ('xinput list').read()
-
-
-class SwissKnife (object):
- xinputreader = None
- xinputcommand = None
-
- @staticmethod
- def XinputReader():
- if SwissKnife.xinputreader is None:
- SwissKnife.xinputreader = XinputReader()
-
- return SwissKnife.xinputreader
-
-
- @staticmethod
- def Xinput():
- if SwissKnife.xinputcommand is None:
- SwissKnife.xinputcommand = XinputCommand()
-
- return SwissKnife.xinputcommand
-
View
23 naturalscrolling_lib/__init__.py
@@ -1,23 +0,0 @@
-# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
-### BEGIN LICENSE
-# Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis <ce@eumorphed.com>
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 3, as published
-# by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-### END LICENSE
-
-'''facade - makes naturalscrolling_lib package easy to refactor
-
-while keeping its api constant'''
-from . helpers import set_up_logging
-from . naturalscrollingconfig import get_version
-from SwissKnife import SwissKnife
View
228 naturalscrolling_lib/gconfsettings.py
@@ -0,0 +1,228 @@
+### BEGIN LICENSE
+# Copyright (C) 2011 Guillaume Hain <zedtux@zedroot.org>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranties of
+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>
+### END LICENSE
+
+import re
+import gconf
+
+## GConf setup
+
+# GConf root path
+GCONF_ROOT_DIR = "/apps/naturalscrolling"
+
+
+class InvalidKey(Exception):
+ """ Raised class when key is unknown """
+
+
+class InvalidKeyType(Exception):
+ """ Raised class when key type is unknown """
+
+
+class GConfServer(object):
+ # Singleton
+ _instance = None
+ _init_done = False
+
+ def __new__(cls, *args, **kwargs):
+ if not cls._instance:
+ cls._instance = super(GConfServer, cls).__new__(cls, *args,
+ **kwargs)
+ return cls._instance
+
+ def __init__(self):
+ """
+ Open connection to GConf
+ and connect to callback on naturalscrolling keys updates
+ """
+ if self._init_done:
+ return
+
+ if not hasattr(self, "__key_update_observators"):
+ self.__key_update_observators = {}
+
+ if not hasattr(self, "__keys_update_observators"):
+ self.__keys_update_observators = []
+
+ if not hasattr(self, "client"):
+ # Get GConf client:
+ self.client = gconf.client_get_default()
+
+ # Add the root directory to the list of directories that our GConf
+ # client will watch for changes:
+ self.client.add_dir(GCONF_ROOT_DIR, gconf.CLIENT_PRELOAD_NONE)
+
+ # Assign a callback function for when changes are made to keys in
+ # the root directory namespace:
+ self.client.notify_add(GCONF_ROOT_DIR, self.on_settings_changed)
+
+ self._init_done = True
+
+ def fire_me_when_update_on_key(self, key, method):
+ """
+ Register a Class instance method to fire
+ swhen the given an update on the given key have been catched
+ """
+ self.__key_update_observators[key] = method
+
+ def on_update_fire(self, method):
+ """
+ Register method to fire when a key of the GConf root path
+ has been updated
+ """
+ self.__keys_update_observators.append(method)
+
+ def on_settings_changed(self, client, timestamp, entry, *extra):
+ """
+ This is the callback function that is called when the keys in our
+ namespace change (such as editing them with gconf-editor).
+ """
+ # Do nothing when the key has been removed
+ if not entry.value:
+ return
+
+ key = entry.key
+ gconf_key = GConfKey(key, entry.value.type)
+
+ if key in self.__key_update_observators:
+ # Execute observer's method passing GConf key value as parameter
+ self.__key_update_observators[key](gconf_key.get_value())
+
+ if self.__keys_update_observators:
+ for observator in self.__keys_update_observators:
+ observator(gconf_key.name, gconf_key.get_value())
+
+
+class GConfKey(object):
+
+ def __init__(self, key, type=None):
+ self.__gconf = GConfServer().client
+ self.__value = None
+
+ if key.startswith(GCONF_ROOT_DIR):
+ self.__key = key
+ self.__name = self._without_root_path(key)
+ else:
+ self.__key = self._with_root_path(key)
+ self.__name = key
+
+ if type:
+ self.__type = type
+ else:
+ self.__type = self.__gconf.get(self.__key).type
+
+ def get_name(self):
+ return self.__name
+ name = property(get_name)
+
+ def _without_root_path(self, key):
+ return re.sub("%s/" % GCONF_ROOT_DIR, "", key)
+
+ def _with_root_path(self, key):
+ return "%s/%s" % (GCONF_ROOT_DIR, key)
+
+ def get_value(self):
+ """
+ Magic method to read the value from GConf (auto cast)
+ """
+ if self.__type == gconf.VALUE_BOOL:
+ return self.__gconf.get_bool(self.__key)
+ elif self.__type == gconf.VALUE_STRING:
+ return self.__gconf.get_string(self.__key)
+ elif self.__type == gconf.VALUE_INT:
+ return self.__gconf.get_int(self.__key)
+ else:
+ raise InvalidKeyType("Can't read the value for type '%s'" %
+ self.__type)
+
+ def set_value(self, value):
+ """
+ Magic method to write the value to GConf (auto cast)
+ """
+ if self.__type == gconf.VALUE_BOOL:
+ self.__gconf.set_bool(self.__key, value)
+ elif self.__type == gconf.VALUE_STRING:
+ self.__gconf.set_string(self.__key, value)
+ elif self.__type == gconf.VALUE_INT:
+ self.__gconf.set_int(self.__key, value)
+ else:
+ raise InvalidKeyType("Can't write the value '%s' for type '%s'" %
+ (value, self.__type))
+
+ def is_enable(self):
+ return self.__value == True
+
+ def enable(self):
+ """
+ Set a boolean key value to True
+ """
+ self.__value = 1
+ self.set_value()
+
+ def disable(self):
+ """
+ Set a boolean key value to False
+ """
+ self.__value = 0
+ self.set_value()
+
+ def find_or_create(self):
+ """
+ Check if the current instance of GConfKey exists otherwise create it
+ """
+ if not self.__gconf.get(self.__key):
+ self.set_value(False)
+
+ def remove(self):
+ """ Remove the key from GConf """
+ self.__gconf.unset(self.__key)
+
+
+class GConfSettings(object):
+
+ def server(self):
+ """
+ Return the Singleton instance of the GConfServer
+ """
+ return GConfServer()
+
+ def initialize(self, devices):
+ """
+ Check if all keys exists
+ Create missing keys
+ """
+ GConfServer().client.recursive_unset(GCONF_ROOT_DIR,
+ gconf.UNSET_INCLUDING_SCHEMA_NAMES)
+ for device in devices:
+ GConfKey(device.keys()[0], gconf.VALUE_BOOL).find_or_create()
+
+ def key(self, key, type=None):
+ """
+ Ruby styled method to define which is the key to check
+ This method return an instance of the GConfKey class
+ otherwise raise a InvalidKey or InvalidKeyType
+ """
+ return GConfKey(key, self.python_type_to_gconf_type(type))
+
+ def python_type_to_gconf_type(self, type):
+ """
+ Convert a Python type (bool, int, str, ...) to GConf type
+ """
+ if type == bool:
+ return gconf.VALUE_BOOL
+ elif type == str:
+ return gconf.VALUE_STRING
+ elif type == int:
+ return gconf.VALUE_INT
View
113 naturalscrolling_lib/helpers.py
@@ -1,113 +0,0 @@
-# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
-### BEGIN LICENSE
-# Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis <ce@eumorphed.com>
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 3, as published
-# by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-### END LICENSE
-
-"""Helpers for an Ubuntu application."""
-import logging
-import os
-
-import gtk
-
-from . naturalscrollingconfig import get_data_file
-from . Builder import Builder
-
-import gettext
-from gettext import gettext as _
-gettext.textdomain('naturalscrolling')
-
-def get_builder(builder_file_name):
- """Return a fully-instantiated gtk.Builder instance from specified ui
- file
-
- :param builder_file_name: The name of the builder file, without extension.
- Assumed to be in the 'ui' directory under the data path.
- """
- # Look for the ui file that describes the user interface.
- ui_filename = get_data_file('ui', '%s.ui' % (builder_file_name,))
- if not os.path.exists(ui_filename):
- ui_filename = None
-
- builder = Builder()
- builder.set_translation_domain('naturalscrolling')
- builder.add_from_file(ui_filename)
- return builder
-
-
-# Owais Lone : To get quick access to icons and stuff.
-def get_media_file(media_file_name):
- media_filename = get_data_file('media', '%s' % (media_file_name,))
- if not os.path.exists(media_filename):
- media_filename = None
-
- return "file:///"+media_filename
-
-class NullHandler(logging.Handler):
- def emit(self, record):
- pass
-
-def set_up_logging(opts):
- # add a handler to prevent basicConfig
- root = logging.getLogger()
- null_handler = NullHandler()
- root.addHandler(null_handler)
-
- formatter = logging.Formatter("%(levelname)s:%(name)s: %(funcName)s() '%(message)s'")
-
- logger = logging.getLogger('naturalscrolling')
- logger_sh = logging.StreamHandler()
- logger_sh.setFormatter(formatter)
- logger.addHandler(logger_sh)
-
- lib_logger = logging.getLogger('naturalscrolling_lib')
- lib_logger_sh = logging.StreamHandler()
- lib_logger_sh.setFormatter(formatter)
- lib_logger.addHandler(lib_logger_sh)
-
- # Set the logging level to show debug messages.
- if opts.verbose:
- logger.setLevel(logging.DEBUG)
- logger.debug('logging enabled')
- if opts.verbose > 1:
- lib_logger.setLevel(logging.DEBUG)
-
-def get_help_uri(page=None):
- # help_uri from source tree - default language
- here = os.path.dirname(__file__)
- help_uri = os.path.abspath(os.path.join(here, '..', 'help', 'C'))
-
- if not os.path.exists(help_uri):
- # installed so use gnome help tree - user's language
- help_uri = 'naturalscrolling'
-
- # unspecified page is the index.page
- if page is not None:
- help_uri = '%s#%s' % (help_uri, page)
-
- return help_uri
-
-def show_uri(parent, link):
- screen = parent.get_screen()
- gtk.show_uri(screen, link, gtk.get_current_event_time())
-
-def alias(alternative_function_name):
- '''see http://www.drdobbs.com/web-development/184406073#l9'''
- def decorator(function):
- '''attach alternative_function_name(s) to function'''
- if not hasattr(function, 'aliases'):
- function.aliases = []
- function.aliases.append(alternative_function_name)
- return function
- return decorator
View
74 naturalscrolling_lib/naturalscrollingconfig.py
@@ -1,50 +1,62 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
-# Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis <ce@eumorphed.com>
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 3, as published
-# by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
+# Copyright (C) 2011 Eumorphed UG,
+# Charalampos Emmanouilidis <ce@eumorphed.com>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranties of
+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>
### END LICENSE
# THIS IS Naturalscrolling CONFIGURATION FILE
# YOU CAN PUT THERE SOME GLOBAL VALUE
# Do not touch unless you know what you're doing.
# you're warned :)
+import os
+
__all__ = [
- 'project_path_not_found',
- 'get_data_file',
- 'get_data_path',
- 'get_auto_start_path',
- 'get_auto_start_file_name',
- 'get_auto_start_file_path'
- ]
+ "appliation_name",
+ "appliation_version",
+ "appliation_website",
+ "get_data_file",
+ "get_data_path",
+ "get_auto_start_path",
+ "get_auto_start_file_name",
+ "get_auto_start_file_path"]
# Where your project will look for your data (for instance, images and ui
# files). By default, this is ../data, relative your trunk layout
-__naturalscrolling_data_directory__ = '../data/'
-__license__ = 'GPL-3'
-__version__ = 'VERSION'
-
-import os
+__naturalscrolling_data_directory__ = "../data/"
+__appname__ = "APPNAME"
+__version__ = "VERSION"
+__website__ = "http://webiste"
-import gettext
-from gettext import gettext as _
-gettext.textdomain('naturalscrolling')
class project_path_not_found(Exception):
"""Raised when we can't find the project directory."""
+def appliation_name():
+ return __appname__
+
+
+def appliation_version():
+ return __version__
+
+
+def appliation_website():
+ return __website__
+
+
def get_data_file(*path_segments):
"""Get the full path to a data file.
@@ -73,17 +85,17 @@ def get_data_path():
return abs_data_path
+
def get_auto_start_path():
""" Retrieve the autostart folder from user's HOME folder """
return os.getenv("HOME") + "/.config/autostart/"
+
def get_auto_start_file_name():
""" Return the autostart file for naturalscrolling """
return "naturalscrolling.desktop"
+
def get_auto_start_file_path():
""" Return the full path of the autostart file for naturalscrolling """
return get_auto_start_path() + "/" + get_auto_start_file_name()
-
-def get_version():
- return __version__
View
89 naturalscrolling_lib/udevobservator.py
@@ -0,0 +1,89 @@
+### BEGIN LICENSE
+# Copyright (C) 2011 Guillaume Hain <zedtux@zedroot.org>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranties of
+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>
+### END LICENSE
+
+import pyudev
+from pyudev.glib import GUDevMonitorObserver
+from naturalscrolling.xinputwarper import XinputWarper
+from naturalscrolling_lib.gconfsettings import GConfSettings
+
+
+class UDevObservator(object):
+
+ def __init__(self):
+ self.__observator = None
+
+ def start(self):
+ """ Observe added and removed events """
+ monitor = pyudev.Monitor.from_netlink(pyudev.Context())
+ monitor.filter_by(subsystem="input")
+ observer = GUDevMonitorObserver(monitor)
+ observer.connect("device-added", self.on_device_added)
+ observer.connect("device-removed", self.on_device_removed)
+ monitor.enable_receiving()
+
+ def on_update_execute(self, callback):
+ """ Define the observator of add and change signals """
+ self.__observator = callback
+
+ def gather_devices_names(self):
+ """ Gather and return all devices names """
+ devices_names = []
+ for device in pyudev.Context().list_devices(subsystem="input",
+ ID_INPUT_MOUSE=True):
+ if device.sys_name.startswith("event"):
+ # [1:-1] means remove double quotes
+ # at the begining and at the end
+ devices_names.append(device.parent["NAME"][1:-1])
+ return devices_names
+
+ def gather_devices(self):
+ """ Gather and return all devices (name and XID) """
+ devices = []
+ for device_name in self.gather_devices_names():
+ devices.append(
+ {XinputWarper().find_xid_by_name(device_name): device_name})
+ return devices
+
+ # ~~~~ Callback methods ~~~~
+ def on_device_added(self, observer, device):
+ """
+ Fired method when a new device is added to udev
+ - Create key in GConf for this new device
+ - Call back observators
+ """
+ if device.sys_name.startswith("event"):
+ XinputWarper().reset_cache()
+ print device.sys_name
+ GConfSettings().key(XinputWarper().find_xid_by_name(
+ device.parent["NAME"][1:-1]), bool).find_or_create()
+
+ self.__observator(self.gather_devices_names())
+
+ def on_device_removed(self, observer, device):
+ """
+ Fired method when a device is removed from udev
+ - Delete key from GConf of the device
+ - Call back observators
+ """
+ if device.sys_name.startswith("event"):
+ print device.sys_name
+ print device.parent["NAME"]
+ xid = XinputWarper().find_xid_by_name(device.parent["NAME"][1:-1])
+ print "xid: %s" % xid
+ GConfSettings().key(xid).remove()
+ XinputWarper().reset_cache()
+
+ self.__observator(self.gather_devices_names())
View
78 po/naturalscrolling.pot
@@ -1,78 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2011-08-19 14:04+0200\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=CHARSET\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#. natural scrolling
-#. Code for other initialization actions should be added here.
-#: ../naturalscrolling/NaturalscrollingIndicator.py:51
-#: ../naturalscrolling.desktop.in.h:1
-msgid "Natural Scrolling"
-msgstr ""
-
-#: ../naturalscrolling/NaturalscrollingIndicator.py:63
-msgid "Preferences"
-msgstr ""
-
-#: ../naturalscrolling/NaturalscrollingIndicator.py:64
-msgid "Start at login"
-msgstr ""
-
-#. about
-#: ../naturalscrolling/NaturalscrollingIndicator.py:74
-msgid "About..."
-msgstr ""
-
-#. quit
-#: ../naturalscrolling/NaturalscrollingIndicator.py:83
-msgid "Quit Natural Srcolling"
-msgstr ""
-
-#: ../naturalscrolling.desktop.in.h:2
-msgid "Natural Scrolling application"
-msgstr ""
-
-#: ../naturalscrolling/__init__.py:34
-msgid "Show debug messages (-vv debugs naturalscrolling_lib also)"
-msgstr ""
-
-#: ../data/ui/AboutNaturalscrollingDialog.ui.h:1
-msgid ""
-"# Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis <ce@eumorphed."
-"com>\n"
-"# \n"
-"# This program is free software: you can redistribute it and/or modify it \n"
-"# under the terms of the GNU General Public License version 3, as "
-"published \n"
-"# by the Free Software Foundation.\n"
-"# \n"
-"# This program is distributed in the hope that it will be useful, but \n"
-"# WITHOUT ANY WARRANTY; without even the implied warranties of \n"
-"# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR \n"
-"# PURPOSE. See the GNU General Public License for more details.\n"
-"# \n"
-"# You should have received a copy of the GNU General Public License along \n"
-"# with this program. If not, see <http://www.gnu.org/licenses/>.\n"
-" "
-msgstr ""
-
-#: ../data/ui/AboutNaturalscrollingDialog.ui.h:15
-msgid ""
-"Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis <ce@eumorphed."
-"com>\n"
-"Guillaume Hain <zedtux@zedroot.org>"
-msgstr ""
View
99 setup.py
@@ -1,100 +1,107 @@
#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
-# Copyright (C) 2011 Eumorphed UG, Charalampos Emmanouilidis <ce@eumorphed.com>
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 3, as published
+# Copyright (C) 2011 Eumorphed UG,
+# Charalampos Emmanouilidis <ce@eumorphed.com>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranties of
+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE
-###################### DO NOT TOUCH THIS (HEAD TO THE SECOND PART) ######################
+###################### DO NOT TOUCH THIS (HEAD TO THE SECOND PART) ###########
import os
import sys
try:
import DistUtilsExtra.auto
except ImportError:
- print >> sys.stderr, 'To build naturalscrolling you need https://launchpad.net/python-distutils-extra'
+ print >> sys.stderr, ("To build naturalscrolling you need "
+ "https://launchpad.net/python-distutils-extra")
sys.exit(1)
-assert DistUtilsExtra.auto.__version__ >= '2.18', 'needs DistUtilsExtra.auto >= 2.18'
+assert DistUtilsExtra.auto.__version__ >= "2.18", "needs DistUtilsExtra.auto >= 2.18"
-def update_config(values = {}):
+def update_config(values={}):
oldvalues = {}
try:
- fin = file('naturalscrolling_lib/naturalscrollingconfig.py', 'r')
- fout = file(fin.name + '.new', 'w')
+ fin = file("naturalscrolling_lib/naturalscrollingconfig.py", "r")
+ fout = file(fin.name + ".new", "w")
for line in fin:
- fields = line.split(' = ') # Separate variable from value
+ fields = line.split(" = ") # Separate variable from value
if fields[0] in values:
oldvalues[fields[0]] = fields[1].strip()
- line = "%s = %s\n" % (fields[0], values[fields[0]])
+ line = "%s = \"%s\"\n" % (fields[0], values[fields[0]])
fout.write(line)
fout.flush()
fout.close()
fin.close()
os.rename(fout.name, fin.name)
- except (OSError, IOError), e:
- print ("ERROR: Can't find naturalscrolling_lib/naturalscrollingconfig.py")
+ except (OSError, IOError):
+ print("ERROR: Can't find "
+ "naturalscrolling_lib/naturalscrollingconfig.py")
sys.exit(1)
return oldvalues
def update_desktop_file(datadir):
try:
- fin = file('naturalscrolling.desktop.in', 'r')
- fout = file(fin.name + '.new', 'w')
+ fin = file("naturalscrolling.desktop.in", "r")
+ fout = file(fin.name + ".new", "w")
- for line in fin:
- if 'Icon=' in line:
- line = "Icon=%s\n" % (datadir + 'media/naturalscrolling.svg')
+ for line in fin:
+ if "Icon=" in line:
+ line = "Icon=%s\n" % (datadir + "media/naturalscrolling.svg")
fout.write(line)
fout.flush()
fout.close()
fin.close()
os.rename(fout.name, fin.name)
- except (OSError, IOError), e:
- print ("ERROR: Can't find naturalscrolling.desktop.in")
+ except (OSError, IOError):
+ print "ERROR: Can't find naturalscrolling.desktop.in"
sys.exit(1)
class InstallAndUpdateDataDirectory(DistUtilsExtra.auto.install_auto):
+
def run(self):
- values = {'__naturalscrolling_data_directory__': "'%s'" % (self.prefix + '/share/naturalscrolling/'),
- '__version__': "'%s'" % self.distribution.get_version()}
+ path = self.prefix + "/share/naturalscrolling/"
+ values = {"__naturalscrolling_data_directory__": path,
+ "__appname__": self.distribution.get_name(),
+ "__version__": self.distribution.get_version(),
+ "__license__": self.distribution.get_license(),
+ "__website__": self.distribution.get_url()}
previous_values = update_config(values)
- update_desktop_file(self.prefix + '/share/naturalscrolling/')
+ update_desktop_file(path)
DistUtilsExtra.auto.install_auto.run(self)
update_config(previous_values)
-
-##################################################################################
-###################### YOU SHOULD MODIFY ONLY WHAT IS BELOW ######################
-##################################################################################
-DistUtilsExtra.auto.setup(
- name='naturalscrolling',
- version='0.4.1',
- license='GPL-3',
- author='Charalampos Emmanouilidis',
- author_email='charalampos.emmanouilidis@eumorphed.com',
- description='Natural Scrolling for Linux',
- long_description='Natural Scrolling adds a menu bar item allowing the direction of scrolling to be toggled',
- url='http://www.eumorphed.com/naturalscrolling',
- cmdclass={'install': InstallAndUpdateDataDirectory}
- )
+##############################################################################
+###################### YOU SHOULD MODIFY ONLY WHAT IS BELOW ##################
+##############################################################################
+DistUtilsExtra.auto.setup(
+ name="Natural Scrolling",
+ version="0.5.0",
+ license="GPL-3",
+ author="Charalampos Emmanouilidis",
+ author_email="charalampos.emmanouilidis@eumorphed.com",
+ description="Natural Scrolling for Linux",
+ long_description=("Natural Scrolling adds a menu bar item allowing the "
+ "direction of scrolling to be toggled"),
+ url="https://github.com/cemmanouilidis/naturalscrolling",
+ cmdclass={"install": InstallAndUpdateDataDirectory})
View
8 tests/data/xinput/xinput_one_slave_pointer.txt
@@ -1,8 +0,0 @@
-⎡ Virtual core pointer id=2 [master pointer (3)]
-⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)]
-⎜ ↳ USB Optical Mouse id=9 [slave pointer (2)]
-⎣ Virtual core keyboard id=3 [master keyboard (2)]
- ↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)]
- ↳ Power Button id=6 [slave keyboard (3)]
- ↳ Power Button id=7 [slave keyboard (3)]
- ↳ Dell Dell USB Keyboard id=8 [slave keyboard (3)]
View
14 tests/data/xinput/xinput_seven_slave_pointer.txt
@@ -1,14 +0,0 @@
-⎡ Virtual core pointer id=2 [master pointer (3)]
-⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)]
-⎜ ↳ USB Optical Mouse id=9 [slave pointer (2)]
-⎜ ↳ Dell Dell USB Optical Mouse id=10 [slave pointer (2)]
-⎜ ↳ Dell Dell USB Optical Mouse id=11 [slave pointer (2)]
-⎜ ↳ Dell Dell USB Optical Mouse id=12 [slave pointer (2)]
-⎜ ↳ Dell Dell USB Optical Mouse id=13 [slave pointer (2)]
-⎜ ↳ Dell Dell USB Optical Mouse id=14 [slave pointer (2)]
-⎜ ↳ Dell Dell USB Optical Mouse id=15 [slave pointer (2)]
-⎣ Virtual core keyboard id=3 [master keyboard (2)]
- ↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)]
- ↳ Power Button id=6 [slave keyboard (3)]
- ↳ Power Button id=7 [slave keyboard (3)]
- ↳ Dell Dell USB Keyboard id=8 [slave keyboard (3)]
View
15 tests/data/xinput/xinput_six_slave_pointer.txt
@@ -1,15 +0,0 @@
-⎡ Virtual core pointer id=2 [master pointer (3)]
-⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)]
-⎜ ↳ SynPS/2 Synaptics TouchPad id=10 [slave pointer (2)]
-⎜ ↳ Serial Wacom Tablet id=12 [slave pointer (2)]
-⎜ ↳ Serial Wacom Tablet eraser id=11 [slave pointer (2)]
-⎜ ↳ Macintosh mouse button emulation id=13 [slave pointer (2)]
-⎜ ↳ Logitech USB Trackball id=14 [slave pointer (2)]
-⎜ ↳ Microsoft Natural® Ergonomic Keyboard 4000 id=16 [slave pointer (2)]
-⎣ Virtual core keyboard id=3 [master keyboard (2)]
- ↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)]
- ↳ Power Button id=6 [slave keyboard (3)]
- ↳ Video Bus id=7 [slave keyboard (3)]
- ↳ Sleep Button id=8 [slave keyboard (3)]
- ↳ AT Translated Set 2 keyboard id=9 [slave keyboard (3)]
- ↳ Microsoft Natural® Ergonomic Keyboard 4000 id=15 [slave keyboard (3)]