diff -Nru solaar-0.9.1/COPYING solaar-0.9.2/COPYING --- solaar-0.9.1/COPYING 2013-06-20 12:57:34.000000000 +0000 +++ solaar-0.9.2/COPYING 2013-07-24 09:42:46.000000000 +0000 @@ -1,12 +1,12 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 - Copyright (C) 1989, 1991 Free Software Foundation, Inc. + Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - Preamble + Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public @@ -15,7 +15,7 @@ General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to +the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not @@ -55,8 +55,8 @@ The precise terms and conditions for copying, distribution and modification follow. - - GNU GENERAL PUBLIC LICENSE + + GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains @@ -110,7 +110,7 @@ License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) - + These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in @@ -168,7 +168,7 @@ access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. - + 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is @@ -225,7 +225,7 @@ This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - + 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License @@ -255,7 +255,7 @@ of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - NO WARRANTY + NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN @@ -277,9 +277,9 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it @@ -303,17 +303,16 @@ MERCHANTABILITY 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, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: - Gnomovision version 69, Copyright (C) year name of author + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. @@ -336,5 +335,5 @@ This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General +library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. diff -Nru solaar-0.9.1/ChangeLog solaar-0.9.2/ChangeLog --- solaar-0.9.1/ChangeLog 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/ChangeLog 2013-07-24 09:42:46.000000000 +0000 @@ -1,3 +1,11 @@ +0.9.2: + * Added support for hand detection on the K800. + * Added support for V550 and V450 Nano. + * Fixed side-scrolling wit the M705 Marathon. + * Fixed identification of the T650 Touchpad. + * Added internationalization support and romanian translation. + * Polish translation courtesy of Adrian Piotrowicz. + 0.9.1: * When devices report a battery alert, only show the alert once. * Make sure devices in the window tree are sorted by registration index. diff -Nru solaar-0.9.1/PKG-INFO solaar-0.9.2/PKG-INFO --- solaar-0.9.1/PKG-INFO 2013-07-13 10:06:01.000000000 +0000 +++ solaar-0.9.2/PKG-INFO 2013-07-24 12:43:05.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: solaar -Version: 0.9.1 +Version: 0.9.2 Summary: Linux devices manager for the Logitech Unifying Receiver. Home-page: http://pwr.github.io/Solaar/ Author: Daniel Pavel diff -Nru solaar-0.9.1/bin/solaar solaar-0.9.2/bin/solaar --- solaar-0.9.1/bin/solaar 2013-07-07 20:55:22.000000000 +0000 +++ solaar-0.9.2/bin/solaar 2013-07-24 09:42:46.000000000 +0000 @@ -1,7 +1,22 @@ #!/usr/bin/env python # -*- python-mode -*- # -*- coding: UTF-8 -*- -"""Takes care of starting the main function.""" + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, unicode_literals diff -Nru solaar-0.9.1/bin/solaar-cli solaar-0.9.2/bin/solaar-cli --- solaar-0.9.1/bin/solaar-cli 2013-07-07 20:55:22.000000000 +0000 +++ solaar-0.9.2/bin/solaar-cli 2013-07-24 09:42:46.000000000 +0000 @@ -1,7 +1,22 @@ #!/usr/bin/env python # -*- python-mode -*- # -*- coding: UTF-8 -*- -"""Takes care of starting the main function.""" + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, unicode_literals diff -Nru solaar-0.9.1/debian/changelog solaar-0.9.2/debian/changelog --- solaar-0.9.1/debian/changelog 2013-07-13 10:13:07.000000000 +0000 +++ solaar-0.9.2/debian/changelog 2013-07-24 19:08:25.000000000 +0000 @@ -1,8 +1,15 @@ -solaar (0.9.1-1pwr1) precise; urgency=low +solaar (0.9.2-1ppa1) precise; urgency=low - * Launchpad upload of 0.9.1. + * Launchpad upload. - -- Daniel Pavel Sat, 13 Jul 2013 12:12:10 +0200 + -- Daniel Pavel Wed, 24 Jul 2013 21:07:49 +0200 + +solaar (0.9.2-1) unstable; urgency=low + + * Release 0.9.2. + * Closes: #717766. + + -- Daniel Pavel Wed, 24 Jul 2013 20:59:52 +0200 solaar (0.9.1-1) unstable; urgency=low diff -Nru solaar-0.9.1/debian/compat solaar-0.9.2/debian/compat --- solaar-0.9.1/debian/compat 2013-01-09 14:31:36.000000000 +0000 +++ solaar-0.9.2/debian/compat 2013-07-24 09:42:46.000000000 +0000 @@ -1 +1 @@ -8 +9 diff -Nru solaar-0.9.1/debian/control solaar-0.9.2/debian/control --- solaar-0.9.1/debian/control 2013-07-13 10:12:09.000000000 +0000 +++ solaar-0.9.2/debian/control 2013-07-24 19:08:25.000000000 +0000 @@ -16,7 +16,7 @@ Architecture: all Depends: ${misc:Depends}, ${debconf:Depends}, udev (>= 175), passwd | adduser, ${python:Depends}, python-pyudev (>= 0.13), python-gi (>= 3.2), gir1.2-gtk-3.0 (>= 3.4), - ${Desktop-Icon-Theme} + gnome-icon-theme-full | oxygen-icon-theme-complete Recommends: gir1.2-notify-0.7, consolekit (>= 0.4.3) | systemd (>= 44), python-dbus (>= 1.1.0), upower Suggests: gir1.2-appindicator3-0.1, solaar-gnome3 (= ${source:Version}) @@ -30,7 +30,7 @@ Section: gnome Depends: ${misc:Depends}, solaar (= ${source:Version}), gir1.2-appindicator3-0.1, gnome-shell (>= 3.4) | unity (>= 5.10), - ${Gnome-Icon-Theme} + gnome-icon-theme-full Enhances: solaar Description: gnome-shell/Unity integration for Solaar Solaar is a Linux device manager for Logitech's Unifying Receiver peripherals. diff -Nru solaar-0.9.1/debian/copyright solaar-0.9.2/debian/copyright --- solaar-0.9.1/debian/copyright 2013-07-01 17:36:44.000000000 +0000 +++ solaar-0.9.2/debian/copyright 2013-07-24 09:42:46.000000000 +0000 @@ -4,7 +4,7 @@ Upstream-Source: http://github.com/pwr/Solaar Files: * -Copyright: Copyright (C) 2012-2013 Daniel Pavel +Copyright: Copyright 2012-2013 Daniel Pavel License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as @@ -23,11 +23,35 @@ version 2, can be found in /usr/share/common-licenses/GPL-2. Files: share/icons/solaar*.svg -Copyright: Copyright (C) 2012-2013 Daniel Pavel +Copyright: Copyright 2012-2013 Daniel Pavel License: LGPL + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. . + This library 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 + Lesser General Public License for more details. + . + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Files: share/icons/light_*.png Copyright: Oxygen Icons License: LGPL - These files were copied from the Oxygen icon theme (weather-*). +Comment: These files were copied from the Oxygen icon theme (weather-*). + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + . + This library 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 + Lesser General Public License for more details. + . + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . diff -Nru solaar-0.9.1/debian/rules solaar-0.9.2/debian/rules --- solaar-0.9.1/debian/rules 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/debian/rules 2013-07-24 09:42:46.000000000 +0000 @@ -6,7 +6,6 @@ #export DH_OPTIONS=-v PREFIX = /usr --include debian/rules.extra %: # Adding the required helpers @@ -16,7 +15,4 @@ dh_auto_install -- --prefix=$(PREFIX) --install-lib=$(PREFIX)/share/solaar/lib override_dh_python2: - dh_python2 $(PREFIX)/share/solaar - -override_dh_gencontrol: - dh_gencontrol -- -Tdebian/substvars.theme + dh_python2 $(PREFIX)/share/solaar/lib diff -Nru solaar-0.9.1/debian/rules.extra solaar-0.9.2/debian/rules.extra --- solaar-0.9.1/debian/rules.extra 2013-01-09 14:31:36.000000000 +0000 +++ solaar-0.9.2/debian/rules.extra 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -# this file is included by debian/rules diff -Nru solaar-0.9.1/debian/solaar.install solaar-0.9.2/debian/solaar.install --- solaar-0.9.1/debian/solaar.install 2013-07-12 12:35:52.000000000 +0000 +++ solaar-0.9.2/debian/solaar.install 2013-07-24 09:42:46.000000000 +0000 @@ -1,5 +1,6 @@ usr/bin/ usr/share/solaar/ +usr/share/locale/ usr/share/icons/hicolor/scalable/apps/ usr/share/applications/ usr/share/applications/solaar.desktop etc/xdg/autostart/ diff -Nru solaar-0.9.1/debian/substvars.theme solaar-0.9.2/debian/substvars.theme --- solaar-0.9.1/debian/substvars.theme 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/debian/substvars.theme 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -Desktop-Icon-Theme=gnome-icon-theme-full | oxygen-icon-theme-complete -Gnome-Icon-Theme=gnome-icon-theme-full diff -Nru solaar-0.9.1/lib/hidapi/__init__.py solaar-0.9.2/lib/hidapi/__init__.py --- solaar-0.9.1/lib/hidapi/__init__.py 2013-07-07 12:06:23.000000000 +0000 +++ solaar-0.9.2/lib/hidapi/__init__.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,8 +1,27 @@ +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + """Generic Human Interface Device API.""" from __future__ import absolute_import, division, print_function, unicode_literals -__version__ = "0.6" +__version__ = '0.9' from hidapi.udev import ( enumerate, diff -Nru solaar-0.9.1/lib/hidapi/hidconsole.py solaar-0.9.2/lib/hidapi/hidconsole.py --- solaar-0.9.1/lib/hidapi/hidconsole.py 2013-07-07 12:06:23.000000000 +0000 +++ solaar-0.9.2/lib/hidapi/hidconsole.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -81,7 +96,7 @@ def _error(text, scroll=False): - _print("!!", text, scroll) + _print('!!', text, scroll) def _continuous_read(handle, timeout=2000): @@ -93,7 +108,7 @@ break assert reply is not None if reply: - _print(">>", reply, True) + _print('>>', reply, True) def _validate_input(line, hidpp=False): @@ -170,10 +185,10 @@ def _parse_arguments(): import argparse arg_parser = argparse.ArgumentParser() - arg_parser.add_argument('--history', help='history file (default ~/.hidconsole-history)') - arg_parser.add_argument('--hidpp', action='store_true', help='ensure input data is a valid HID++ request') - arg_parser.add_argument('device', nargs='?', help='linux device to connect to (/dev/hidrawX); ' - 'may be omitted if --hidpp is given, in which case it looks for the first Logitech receiver') + arg_parser.add_argument('--history', help="history file (default ~/.hidconsole-history)") + arg_parser.add_argument('--hidpp', action='store_true', help="ensure input data is a valid HID++ request") + arg_parser.add_argument('device', nargs='?', help="linux device to connect to (/dev/hidrawX); " + "may be omitted if --hidpp is given, in which case it looks for the first Logitech receiver") return arg_parser.parse_args() @@ -187,7 +202,7 @@ import readline if args.history is None: import os.path - args.history = os.path.join(os.path.expanduser("~"), ".hidconsole-history") + args.history = os.path.join(os.path.expanduser('~'), '.hidconsole-history') try: readline.read_history_file(args.history) except: @@ -215,7 +230,7 @@ if data is None: continue - _print("<<", data) + _print('<<', data) _hid.write(handle, data) # wait for some kind of reply if args.hidpp and not interactive: diff -Nru solaar-0.9.1/lib/hidapi/udev.py solaar-0.9.2/lib/hidapi/udev.py --- solaar-0.9.1/lib/hidapi/udev.py 2013-07-09 12:43:48.000000000 +0000 +++ solaar-0.9.2/lib/hidapi/udev.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,3 +1,22 @@ +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + """Generic Human Interface Device API. It is currently a partial pure-Python implementation of the native HID API @@ -63,20 +82,24 @@ if not usb_device: return - vid = usb_device['ID_VENDOR_ID'] - pid = usb_device['ID_MODEL_ID'] + vid = usb_device.get('ID_VENDOR_ID') + pid = usb_device.get('ID_MODEL_ID') if not ((vendor_id is None or vendor_id == int(vid, 16)) and (product_id is None or product_id == int(pid, 16))): return if action == 'add': hid_device = device.find_parent('hid') - # print ("** found hid", action, device, "hid:", hid_device, hid_device['DRIVER']) if not hid_device: return - hid_driver_name = hid_device['DRIVER'] - if hid_driver is not None and hid_driver != hid_driver_name: - return + hid_driver_name = hid_device.get('DRIVER') + # print ("** found hid", action, device, "hid:", hid_device, hid_driver_name) + if hid_driver: + if isinstance(hid_driver, tuple): + if hid_driver_name not in hid_driver: + return + elif hid_driver_name != hid_driver: + return intf_device = device.find_parent('usb', 'usb_interface') # print ("*** usb interface", action, device, "usb_interface:", intf_device) @@ -92,9 +115,9 @@ vendor_id=vid[-4:], product_id=pid[-4:], serial=hid_device.get('HID_UNIQ'), - release=attrs['bcdDevice'], - manufacturer=attrs['manufacturer'], - product=attrs['product'], + release=attrs.get('bcdDevice'), + manufacturer=attrs.get('manufacturer'), + product=attrs.get('product'), interface=usb_interface, driver=hid_driver_name) return d_info diff -Nru solaar-0.9.1/lib/logitech_receiver/__init__.py solaar-0.9.2/lib/logitech_receiver/__init__.py --- solaar-0.9.1/lib/logitech_receiver/__init__.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/__init__.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,3 +1,22 @@ +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + """Low-level interface for devices connected through a Logitech Universal Receiver (UR). diff -Nru solaar-0.9.1/lib/logitech_receiver/base.py solaar-0.9.2/lib/logitech_receiver/base.py --- solaar-0.9.1/lib/logitech_receiver/base.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/base.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,7 +1,24 @@ -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + # Base low-level functions used by the API proper. # Unlikely to be used directly unless you're expanding the API. -# from __future__ import absolute_import, division, print_function, unicode_literals @@ -271,7 +288,7 @@ return _HIDPP_Notification(devnumber, sub_id, address, data[2:]) from collections import namedtuple -_HIDPP_Notification = namedtuple('_HIDPP_Notification', ['devnumber', 'sub_id', 'address', 'data']) +_HIDPP_Notification = namedtuple('_HIDPP_Notification', ('devnumber', 'sub_id', 'address', 'data')) _HIDPP_Notification.__str__ = lambda self: 'Notification(%d,%02X,%02X,%s)' % (self.devnumber, self.sub_id, self.address, _strhex(self.data)) _HIDPP_Notification.__unicode__ = _HIDPP_Notification.__str__ del namedtuple diff -Nru solaar-0.9.1/lib/logitech_receiver/base_usb.py solaar-0.9.2/lib/logitech_receiver/base_usb.py --- solaar-0.9.1/lib/logitech_receiver/base_usb.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/base_usb.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,28 +1,51 @@ -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + # USB ids of Logitech wireless receivers. # Only receivers supporting the HID++ protocol can go in here. -# from __future__ import absolute_import, division, print_function, unicode_literals +_UNIFYING_DRIVER = 'logitech-djreceiver' +_GENERIC_DRIVER = ('hid-generic', 'generic-usb') + + # each tuple contains (vendor_id, product_id, usb interface number, hid driver) # standard Unifying receivers (marked with the orange Unifying logo) -UNIFYING_RECEIVER = (0x046d, 0xc52b, 2, 'logitech-djreceiver') -UNIFYING_RECEIVER_2 = (0x046d, 0xc532, 2, 'logitech-djreceiver') +UNIFYING_RECEIVER = (0x046d, 0xc52b, 2, _UNIFYING_DRIVER) +UNIFYING_RECEIVER_2 = (0x046d, 0xc532, 2, _UNIFYING_DRIVER) + + # Nano receviers that support the Unifying protocol -NANO_RECEIVER_ADVANCED = (0x046d, 0xc52f, 1, 'hid-generic') +NANO_RECEIVER_ADVANCED = (0x046d, 0xc52f, 1, _GENERIC_DRIVER) # Nano receivers that don't support the Unifying protocol -NANO_RECEIVER_C517 = (0x046d, 0xc517, 1, 'hid-generic') -NANO_RECEIVER_C518 = (0x046d, 0xc518, 1, 'hid-generic') -NANO_RECEIVER_C51A = (0x046d, 0xc51a, 1, 'hid-generic') -NANO_RECEIVER_C51B = (0x046d, 0xc51b, 1, 'hid-generic') -NANO_RECEIVER_C521 = (0x046d, 0xc521, 1, 'hid-generic') -NANO_RECEIVER_C525 = (0x046d, 0xc525, 1, 'hid-generic') -NANO_RECEIVER_C526 = (0x046d, 0xc526, 1, 'hid-generic') +NANO_RECEIVER_C517 = (0x046d, 0xc517, 1, _GENERIC_DRIVER) +NANO_RECEIVER_C518 = (0x046d, 0xc518, 1, _GENERIC_DRIVER) +NANO_RECEIVER_C51A = (0x046d, 0xc51a, 1, _GENERIC_DRIVER) +NANO_RECEIVER_C51B = (0x046d, 0xc51b, 1, _GENERIC_DRIVER) +NANO_RECEIVER_C521 = (0x046d, 0xc521, 1, _GENERIC_DRIVER) +NANO_RECEIVER_C525 = (0x046d, 0xc525, 1, _GENERIC_DRIVER) +NANO_RECEIVER_C526 = (0x046d, 0xc526, 1, _GENERIC_DRIVER) diff -Nru solaar-0.9.1/lib/logitech_receiver/common.py solaar-0.9.2/lib/logitech_receiver/common.py --- solaar-0.9.1/lib/logitech_receiver/common.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/common.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,23 @@ -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + # Some common functions and types. -# from __future__ import absolute_import, division, print_function, unicode_literals @@ -53,7 +70,7 @@ return self.name.lower() == other.lower() # this should catch comparisons with bytes in Py3 if other is not None: - raise TypeError("Unsupported type " + str(type(other))) + raise TypeError('Unsupported type ' + str(type(other))) def __ne__(self, other): return not self.__eq__(other) @@ -82,7 +99,7 @@ if the value already exists in the set (int or string), ValueError will be raised. """ - __slots__ = ['__dict__', '_values', '_indexed', '_fallback'] + __slots__ = ('__dict__', '_values', '_indexed', '_fallback') def __init__(self, **kwargs): def _readable_name(n): @@ -191,6 +208,7 @@ def strhex(x): + assert x is not None """Produce a hex-string representation of a sequence of bytes.""" return _hexlify(x).decode('ascii').upper() diff -Nru solaar-0.9.1/lib/logitech_receiver/descriptors.py solaar-0.9.2/lib/logitech_receiver/descriptors.py --- solaar-0.9.1/lib/logitech_receiver/descriptors.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/descriptors.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -17,7 +32,7 @@ from collections import namedtuple _DeviceDescriptor = namedtuple('_DeviceDescriptor', - ['name', 'kind', 'wpid', 'codename', 'protocol', 'registers', 'settings']) + ('name', 'kind', 'wpid', 'codename', 'protocol', 'registers', 'settings')) del namedtuple DEVICES = {} @@ -31,12 +46,12 @@ else _hidpp10.DEVICE_KIND.touchpad if 'Touchpad' in name else _hidpp10.DEVICE_KIND.trackball if 'Trackball' in name else None) - assert kind is not None, "descriptor for %s does not have 'kind' set" % name + assert kind is not None, 'descriptor for %s does not have kind set' % name # heuristic: the codename is the last word in the device name if codename is None and ' ' in name: codename = name.split(' ')[-1] - assert codename is not None, "descriptor for %s does not have codename set" % name + assert codename is not None, 'descriptor for %s does not have codename set' % name if protocol is not None: # ? 2.0 devices should not have any registers @@ -46,11 +61,21 @@ assert registers is None assert settings is None or all(s._rw.kind == 2 for s in settings) + if wpid: + for w in wpid if isinstance(wpid, tuple) else (wpid, ): + if protocol > 1.0: + assert w[0:1] == '4', name + ' has protocol ' + protocol + ', wpid ' + w + else: + if w[0:1] == '1': + assert kind == _hidpp10.DEVICE_KIND.mouse, name + ' has protocol ' + protocol + ', wpid ' + w + elif w[0:1] == '2': + assert kind == _hidpp10.DEVICE_KIND.keyboard, name + ' has protocol ' + protocol + ', wpid ' + w + device_descriptor = _DeviceDescriptor(name=name, kind=kind, wpid=wpid, codename=codename, protocol=protocol, registers=registers, settings=settings) - assert codename not in DEVICES, "duplicate codename in device descriptors: %s" % (DEVICES[codename], ) + assert codename not in DEVICES, 'duplicate codename in device descriptors: %s' % (DEVICES[codename], ) DEVICES[codename] = device_descriptor if wpid: @@ -58,7 +83,7 @@ wpid = (wpid, ) for w in wpid: - assert w not in DEVICES, "duplicate wpid in device descriptors: %s" % (DEVICES[w], ) + assert w not in DEVICES, 'duplicate wpid in device descriptors: %s' % (DEVICES[w], ) DEVICES[w] = device_descriptor # @@ -150,6 +175,7 @@ registers=(_R.battery_status, _R.three_leds, ), settings=[ _RS.fn_swap(), + _RS.hand_detection(), ], ) @@ -162,28 +188,37 @@ _D('Wireless Mouse M235') _D('Wireless Mouse M305', protocol=1.0, wpid='101F', registers=(_R.battery_status, ), + settings=[ + _RS.side_scroll(), + ], ) _D('Wireless Mouse M310') _D('Wireless Mouse M315') _D('Wireless Mouse M317') _D('Wireless Mouse M325') -_D('Wireless Mouse M345') +_D('Wireless Mouse M345', protocol=2.0, wpid='4017') _D('Wireless Mouse M505', codename='M505/B605', protocol=1.0, wpid='101D', registers=(_R.battery_charge, ), + settings=[ + _RS.smooth_scroll(), + _RS.side_scroll(), + ], ) _D('Wireless Mouse M510', protocol=1.0, wpid='1025', registers=(_R.battery_status, ), settings=[ _RS.smooth_scroll(), + _RS.side_scroll(), ], ) -_D('Couch Mouse M515', protocol=2.0) -_D('Wireless Mouse M525', protocol=2.0) +_D('Couch Mouse M515', protocol=2.0, wpid='4007') +_D('Wireless Mouse M525', protocol=2.0, wpid='4013') _D('Touch Mouse M600', protocol=2.0, wpid='401A') _D('Marathon Mouse M705', protocol=1.0, wpid='101B', registers=(_R.battery_charge, ), settings=[ _RS.smooth_scroll(), + _RS.side_scroll(), ], ) _D('Zone Touch Mouse T400') @@ -191,11 +226,17 @@ _D('Logitech Cube', kind=_hidpp10.DEVICE_KIND.mouse, protocol=2.0) _D('Anywhere Mouse MX', codename='Anywhere MX', protocol=1.0, wpid='1017', registers=(_R.battery_charge, ), + settings=[ + _RS.smooth_scroll(), + _RS.side_scroll(), + ], ) _D('Performance Mouse MX', codename='Performance MX', protocol=1.0, wpid='101A', registers=(_R.battery_status, _R.three_leds, ), settings=[ _RS.dpi(choices=_PERFORMANCE_MX_DPIS), + _RS.smooth_scroll(), + _RS.side_scroll(), ], ) @@ -205,7 +246,7 @@ # Touchpads -_D('Wireless Rechargeable Touchpad T650', protocol=2.0) +_D('Wireless Rechargeable Touchpad T650', protocol=2.0, wpid='4101') _D('Wireless Touchpad', codename='Wireless Touch', protocol=2.0, wpid='4011') # @@ -217,5 +258,16 @@ registers=(_R.battery_charge, ), settings=[ _RS.smooth_scroll(), + _RS.side_scroll(), + ], + ) +_D('V450 Nano Cordless Laser Mouse', codename='V450 Nano', protocol=1.0, wpid='1011', + registers=(_R.battery_charge, ), + ) +_D('V550 Nano Cordless Laser Mouse', codename='V550 Nano', protocol=1.0, wpid='1013', + registers=(_R.battery_charge, ), + settings=[ + _RS.smooth_scroll(), + _RS.side_scroll(), ], ) diff -Nru solaar-0.9.1/lib/logitech_receiver/hidpp10.py solaar-0.9.2/lib/logitech_receiver/hidpp10.py --- solaar-0.9.1/lib/logitech_receiver/hidpp10.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/hidpp10.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -14,7 +29,7 @@ int2bytes as _int2bytes, NamedInts as _NamedInts, FirmwareInfo as _FirmwareInfo) -from .hidpp20 import FIRMWARE_KIND +from .hidpp20 import FIRMWARE_KIND, BATTERY_STATUS # # Constants - most of them as defined by the official Logitech HID++ 1.0 @@ -102,7 +117,7 @@ receiver_info=0x2B5, # only apply to devices - mouse_smooth_scroll=0x01, + mouse_button_flags=0x01, keyboard_hand_detection=0x01, battery_status=0x07, keyboard_fn_swap=0x09, @@ -121,14 +136,14 @@ # def read_register(device, register_number, *params): - assert device + assert device, 'tried to read register %02X from invalid device %s' % (register_number, device) # support long registers by adding a 2 in front of the register number request_id = 0x8100 | (int(register_number) & 0x2FF) return device.request(request_id, *params) def write_register(device, register_number, *value): - assert device + assert device, 'tried to write register %02X to invalid device %s' % (register_number, device) # support long registers by adding a 2 in front of the register number request_id = 0x8000 | (int(register_number) & 0x2FF) return device.request(request_id, *value) @@ -170,9 +185,9 @@ if register == REGISTERS.battery_charge: charge = ord(reply[:1]) status_byte = ord(reply[2:3]) & 0xF0 - status_text = ('discharging' if status_byte == 0x30 - else 'charging' if status_byte == 0x50 - else 'fully charged' if status_byte == 0x90 + status_text = (BATTERY_STATUS.discharging if status_byte == 0x30 + else BATTERY_STATUS.recharging if status_byte == 0x50 + else BATTERY_STATUS.full if status_byte == 0x90 else None) return charge, status_text @@ -187,11 +202,11 @@ charging_byte = ord(reply[1:2]) if charging_byte == 0x00: - status_text = 'discharging' + status_text = BATTERY_STATUS.discharging elif charging_byte & 0x21 == 0x21: - status_text = 'charging' + status_text = BATTERY_STATUS.recharging elif charging_byte & 0x22 == 0x22: - status_text = 'fully charged' + status_text = BATTERY_STATUS.full else: _log.warn("could not parse 0x07 battery status: %02X (level %02X)", charging_byte, status_byte) status_text = None diff -Nru solaar-0.9.1/lib/logitech_receiver/hidpp20.py solaar-0.9.2/lib/logitech_receiver/hidpp20.py --- solaar-0.9.1/lib/logitech_receiver/hidpp20.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/hidpp20.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,23 @@ -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + # Logitech Unifying Receiver API. -# from __future__ import absolute_import, division, print_function, unicode_literals @@ -78,7 +95,7 @@ Hardware=0x02, Other=0x03) -BATTERY_OK = lambda status: status not in ("invalid_battery", "thermal_error") +BATTERY_OK = lambda status: status not in (BATTERY_STATUS.invalid_battery, BATTERY_STATUS.thermal_error) BATTERY_STATUS = _NamedInts( discharging=0x00, @@ -401,7 +418,7 @@ pointer_info = feature_request(device, FEATURE.MOUSE_POINTER) if pointer_info: dpi, flags = _unpack('!HB', pointer_info[:3]) - acceleration = ['none', 'low', 'med', 'high' ][flags & 0x3] + acceleration = ('none', 'low', 'med', 'high')[flags & 0x3] suggest_os_ballistics = (flags & 0x04) != 0 suggest_vertical_orientation = (flags & 0x08) != 0 return { diff -Nru solaar-0.9.1/lib/logitech_receiver/i18n.py solaar-0.9.2/lib/logitech_receiver/i18n.py --- solaar-0.9.1/lib/logitech_receiver/i18n.py 1970-01-01 00:00:00.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/i18n.py 2013-07-24 09:42:46.000000000 +0000 @@ -0,0 +1,50 @@ +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Translation support for the Logitech receivers library + +from __future__ import absolute_import, division, print_function, unicode_literals + +import gettext as _gettext + + +try: + unicode + _ = lambda x: _gettext.gettext(x).decode('UTF-8') +except: + _ = _gettext.gettext + + +# A few common strings, not always accessible as such in the code. + +_DUMMY = ( + # approximative battery levels + _("empty"), _("critical"), _("low"), _("good"), _("full"), + + # battery charging statuses + _("discharging"), _("recharging"), _("almost full"), _("full"), + _("slow recharge"), _("invalid battery"), _("thermal error"), + + # pairing errors + _("device timeout"), _("device not supported"), _("too many devices"), _("sequence timeout"), + + # firmware kinds + _("Firmware"), _("Bootloader"), _("Hardware"), _("Other"), + + ) diff -Nru solaar-0.9.1/lib/logitech_receiver/listener.py solaar-0.9.2/lib/logitech_receiver/listener.py --- solaar-0.9.1/lib/logitech_receiver/listener.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/listener.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -30,7 +45,7 @@ Closing a ThreadedHandle will close all handles. """ - __slots__ = ['path', '_local', '_handles', '_listener'] + __slots__ = ('path', '_local', '_handles', '_listener') def __init__(self, listener, path, handle): assert listener is not None diff -Nru solaar-0.9.1/lib/logitech_receiver/notifications.py solaar-0.9.2/lib/logitech_receiver/notifications.py --- solaar-0.9.1/lib/logitech_receiver/notifications.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/notifications.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,7 +1,24 @@ -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + # Handles incoming events from the receiver/devices, updating the related # status object as appropiate. -# from __future__ import absolute_import, division, print_function, unicode_literals @@ -10,6 +27,7 @@ del getLogger +from .i18n import _ from .common import strhex as _strhex, unpack as _unpack from . import hidpp10 as _hidpp10 from . import hidpp20 as _hidpp20 @@ -46,7 +64,7 @@ # pairing lock notification if n.sub_id == 0x4A: status.lock_open = bool(n.address & 0x01) - reason = 'pairing lock is ' + ('open' if status.lock_open else 'closed') + reason = _("pairing lock is ") + (_("open") if status.lock_open else _("closed")) if _log.isEnabledFor(_INFO): _log.info("%s: %s", receiver, reason) @@ -171,7 +189,7 @@ if n.address == 0x01: if _log.isEnabledFor(_DEBUG): _log.debug("%s: device powered on", device) - reason = str(status) or 'powered on' + reason = str(status) or _("powered on") status.changed(active=True, alert=_ALERT.NOTIFICATION, reason=reason) else: _log.warn("%s: unknown %s", device, n) diff -Nru solaar-0.9.1/lib/logitech_receiver/receiver.py solaar-0.9.2/lib/logitech_receiver/receiver.py --- solaar-0.9.1/lib/logitech_receiver/receiver.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/receiver.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -11,6 +26,7 @@ del getLogger +from .i18n import _ from . import base as _base from . import hidpp10 as _hidpp10 from . import hidpp20 as _hidpp20 @@ -88,7 +104,7 @@ self.wpid = _strhex(device_info[3:5]) self._polling_rate = 0 - self._power_switch = '(unknown)' + self._power_switch = '(' + _("unknown") + ')' # the wpid is necessary to properly identify wireless link on/off notifications # also it gets set to None on this object when the device is unpaired @@ -100,7 +116,9 @@ # do not support this call. codename = self.receiver.read_register(_R.receiver_info, 0x40 + self.number - 1) if codename: - self._codename = codename[2:].rstrip(b'\x00').decode('utf-8') + codename_length = ord(codename[1:2]) + codename = codename[2:2 + codename_length] + self._codename = codename.decode('ascii') self.descriptor = _DESCRIPTORS.get(self._codename) if self.descriptor: @@ -133,7 +151,9 @@ if self._codename is None: codename = self.receiver.read_register(_R.receiver_info, 0x40 + self.number - 1) if codename: - self._codename = codename[2:].rstrip(b'\x00').decode('utf-8') + codename_length = ord(codename[1:2]) + codename = codename[2:2 + codename_length] + self._codename = codename.decode('ascii') # if _log.isEnabledFor(_DEBUG): # _log.debug("device %d codename %s", self.number, self._codename) else: diff -Nru solaar-0.9.1/lib/logitech_receiver/settings.py solaar-0.9.2/lib/logitech_receiver/settings.py --- solaar-0.9.1/lib/logitech_receiver/settings.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/settings.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,25 +1,48 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals +from logging import getLogger, DEBUG as _DEBUG +_log = getLogger(__name__) +del getLogger + from copy import copy as _copy -from .common import NamedInt as _NamedInt, NamedInts as _NamedInts +from .common import ( + NamedInt as _NamedInt, + NamedInts as _NamedInts, + bytes2int as _bytes2int, + ) # # # -KIND = _NamedInts(toggle=0x1, choice=0x02, range=0x12) +KIND = _NamedInts(toggle=0x01, choice=0x02, range=0x12) class Setting(object): """A setting descriptor. Needs to be instantiated for each specific device.""" - __slots__ = ['name', 'label', 'description', 'kind', 'persister', 'device_kind', - '_rw', '_validator', '_device', '_value'] + __slots__ = ('name', 'label', 'description', 'kind', 'persister', 'device_kind', + '_rw', '_validator', '_device', '_value') def __init__(self, name, rw, validator, kind=None, label=None, description=None, device_kind=None): assert name @@ -87,22 +110,46 @@ def write(self, value): assert hasattr(self, '_value') assert hasattr(self, '_device') + assert value is not None - if self._device: - data_bytes = self._validator.prepare_write(value) - reply = self._rw.write(self._device, data_bytes) - if reply: - self._value = self._validator.validate_write(value, reply) - if self.persister and self._value is not None: - self.persister[self.name] = self._value - return self._value + if _log.isEnabledFor(_DEBUG): + _log.debug("%s: write %r to %s", self.name, value, self._device) + + if self._device.online: + # Remember the value we're trying to set, even if the write fails. + # This way even if the device is offline or some other error occurs, + # the last value we've tried to write is remembered in the configuration. + self._value = value + if self.persister: + self.persister[self.name] = value + + current_value = None + if self._validator.needs_current_value: + # the validator needs the current value, possibly to merge flag values + current_value = self._rw.read(self._device) + + data_bytes = self._validator.prepare_write(value, current_value) + if data_bytes is not None: + if _log.isEnabledFor(_DEBUG): + _log.debug("%s: prepare write(%s) => %r", self.name, value, data_bytes) + + reply = self._rw.write(self._device, data_bytes) + if not reply: + # tell whomever is calling that the write failed + return None + + return value def apply(self): assert hasattr(self, '_value') assert hasattr(self, '_device') - if self._value is not None: - self.write(self._value) + if _log.isEnabledFor(_DEBUG): + _log.debug("%s: apply %s (%s)", self.name, self._value, self._device) + + value = self.read() + if value is not None: + self.write(value) def __str__(self): if hasattr(self, '_value'): @@ -116,7 +163,7 @@ # class RegisterRW(object): - __slots__ = ['register'] + __slots__ = ('register', ) kind = _NamedInt(0x01, 'register') @@ -132,7 +179,7 @@ class FeatureRW(object): - __slots__ = ['feature', 'read_fnid', 'write_fnid'] + __slots__ = ('feature', 'read_fnid', 'write_fnid') kind = _NamedInt(0x02, 'feature') default_read_fnid = 0x00 @@ -158,7 +205,7 @@ # class BooleanValidator(object): - __slots__ = ['true_value', 'false_value', 'mask', 'write_returns_value'] + __slots__ = ('true_value', 'false_value', 'mask', 'needs_current_value') kind = KIND.toggle default_true = 0x01 @@ -166,72 +213,139 @@ # mask specifies all the affected bits in the value default_mask = 0xFF - def __init__(self, true_value=default_true, false_value=default_false, mask=default_mask, write_returns_value=False): + def __init__(self, true_value=default_true, false_value=default_false, mask=default_mask): + if isinstance(true_value, int): + assert isinstance(false_value, int) + if mask is None: + mask = self.default_mask + else: + assert isinstance(mask, int) + assert true_value & false_value == 0 + assert true_value & mask == true_value + assert false_value & mask == false_value + self.needs_current_value = (mask != self.default_mask) + elif isinstance(true_value, bytes): + if false_value is None or false_value == self.default_false: + false_value = b'\x00' * len(true_value) + else: + assert isinstance(false_value, bytes) + if mask is None or mask == self.default_mask: + mask = b'\xFF' * len(true_value) + else: + assert isinstance(mask, bytes) + assert len(mask) == len(true_value) == len(false_value) + tv = _bytes2int(true_value) + fv = _bytes2int(false_value) + mv = _bytes2int(mask) + assert tv & fv == 0 + assert tv & mv == tv + assert fv & mv == fv + self.needs_current_value = any(m != b'\xFF' for m in mask) + else: + raise Exception("invalid mask '%r', type %s" % (mask, type(mask))) + self.true_value = true_value self.false_value = false_value self.mask = mask - self.write_returns_value = write_returns_value - def _validate_value(self, reply_bytes, expected_value): - if isinstance(expected_value, int): - return ord(reply_bytes[:1]) & self.mask == expected_value - else: - for i in range(0, len(self.mask)): - masked_value = ord(reply_bytes[i:i+1]) & ord(self.mask[i:i+1]) - if masked_value != ord(expected_value[i:i+1]): - return False + def validate_read(self, reply_bytes): + if isinstance(self.mask, int): + reply_value = ord(reply_bytes[:1]) & self.mask + if _log.isEnabledFor(_DEBUG): + _log.debug("BooleanValidator: validate read %r => %02X", reply_bytes, reply_value) + if reply_value == self.true_value: + return True + if reply_value == self.false_value: + return False + _log.warn("BooleanValidator: reply %02X mismatched %02X/%02X/%02X", + reply_value, self.true_value, self.false_value, self.mask) + return False + + count = len(self.mask) + mask = _bytes2int(self.mask) + reply_value = _bytes2int(reply_bytes[:count]) & mask + + true_value = _bytes2int(self.true_value) + if reply_value == true_value: return True - def validate_read(self, reply_bytes): - return self._validate_value(reply_bytes, self.true_value) + false_value = _bytes2int(self.false_value) + if reply_value == false_value: + return False + + _log.warn("BooleanValidator: reply %r mismatched %r/%r/%r", + reply_bytes, self.true_value, self.false_value, self.mask) + return False + + def prepare_write(self, new_value, current_value=None): + if new_value is None: + new_value = False + else: + assert isinstance(new_value, bool) + + to_write = self.true_value if new_value else self.false_value + + if isinstance(self.mask, int): + if current_value is not None and self.needs_current_value: + to_write |= ord(current_value[:1]) & (0xFF ^ self.mask) + if current_value is not None and to_write == ord(current_value[:1]): + return None + else: + to_write = list(to_write) + count = len(self.mask) + for i in range(0, count): + b = ord(to_write[i]) + m = ord(self.mask[i : i + 1]) + assert b & m == b + # b &= m + if current_value is not None and self.needs_current_value: + b |= ord(current_value[i : i + 1]) & (0xFF ^ m) + to_write[i] = chr(b) + to_write = b''.join(to_write) + + if current_value is not None and to_write == current_value[:len(to_write)]: + return None - def prepare_write(self, value): - # FIXME: this does not work right when there is more than one flag in - # the same register! - return self.true_value if value else self.false_value - - def validate_write(self, value, reply_bytes): - if self.write_returns_value: - return self._validate_value(reply_bytes, self.true_value) - - # just assume the value was written correctly, otherwise there would not - # be any reply_bytes to check - return bool(value) + if _log.isEnabledFor(_DEBUG): + _log.debug("BooleanValidator: prepare_write(%s, %s) => %r", new_value, current_value, to_write) + + return to_write class ChoicesValidator(object): - __slots__ = ['choices', 'write_returns_value'] + __slots__ = ('choices', 'flag', '_bytes_count', 'needs_current_value') kind = KIND.choice - def __init__(self, choices, write_returns_value=False): + def __init__(self, choices): + assert choices is not None assert isinstance(choices, _NamedInts) + assert len(choices) > 2 self.choices = choices - self.write_returns_value = write_returns_value + self.needs_current_value = False + + max_bits = max(x.bit_length() for x in choices) + self._bytes_count = (max_bits // 8) + (1 if max_bits % 8 else 0) + assert self._bytes_count < 8 def validate_read(self, reply_bytes): - assert self.choices is not None - reply_value = ord(reply_bytes[:1]) + reply_value = _bytes2int(reply_bytes[:self._bytes_count]) valid_value = self.choices[reply_value] - assert valid_value is not None, "%: failed to validate read value %02X" % (self.__class__.__name__, reply_value) + assert valid_value is not None, "%s: failed to validate read value %02X" % (self.__class__.__name__, reply_value) return valid_value - def prepare_write(self, value): - assert self.choices is not None - choice = self.choices[value] + def prepare_write(self, new_value, current_value=None): + if new_value is None: + choice = self.choices[:][0] + else: + if isinstance(new_value, int): + choice = self.choices[new_value] + elif new_value in self.choices: + choice = self.choices[new_value] + else: + raise ValueError(new_value) + if choice is None: - raise ValueError("invalid choice " + repr(value)) + raise ValueError("invalid choice %r" % new_value) assert isinstance(choice, _NamedInt) - return choice.bytes(1) - - def validate_write(self, value, reply_bytes): - assert self.choices is not None - if self.write_returns_value: - reply_value = ord(reply_bytes[:1]) - choice = self.choices[reply_value] - assert choice is not None, "failed to validate write reply %02X" % reply_value - return choice - - # just assume the value was written correctly, otherwise there would not - # be any reply_bytes to check - return self.choices[value] + return choice.bytes(self._bytes_count) diff -Nru solaar-0.9.1/lib/logitech_receiver/settings_templates.py solaar-0.9.2/lib/logitech_receiver/settings_templates.py --- solaar-0.9.1/lib/logitech_receiver/settings_templates.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/settings_templates.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,10 +1,26 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals +from .i18n import _ from . import hidpp10 as _hidpp10 from . import hidpp20 as _hidpp20 from .settings import ( @@ -25,60 +41,79 @@ # def register_toggle(name, register, - true_value=_BooleanV.default_true, false_value=_BooleanV.default_false, - mask=_BooleanV.default_mask, write_returns_value=False, + true_value=_BooleanV.default_true, + false_value=_BooleanV.default_false, + mask=_BooleanV.default_mask, label=None, description=None, device_kind=None): + validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask) rw = _RegisterRW(register) - validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask, write_returns_value=write_returns_value) return _Setting(name, rw, validator, label=label, description=description, device_kind=device_kind) def register_choices(name, register, choices, - kind=_KIND.choice, write_returns_value=False, + kind=_KIND.choice, label=None, description=None, device_kind=None): assert choices + validator = _ChoicesV(choices) rw = _RegisterRW(register) - validator = _ChoicesV(choices, write_returns_value=write_returns_value) return _Setting(name, rw, validator, kind=kind, label=label, description=description, device_kind=device_kind) def feature_toggle(name, feature, - read_function_id=_FeatureRW.default_read_fnid, write_function_id=_FeatureRW.default_write_fnid, - true_value=_BooleanV.default_true, false_value=_BooleanV.default_false, - mask=_BooleanV.default_mask, write_returns_value=False, + read_function_id=_FeatureRW.default_read_fnid, + write_function_id=_FeatureRW.default_write_fnid, + true_value=_BooleanV.default_true, + false_value=_BooleanV.default_false, + mask=_BooleanV.default_mask, label=None, description=None, device_kind=None): + validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask) rw = _FeatureRW(feature, read_function_id, write_function_id) - validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask, write_returns_value=write_returns_value) return _Setting(name, rw, validator, label=label, description=description, device_kind=device_kind) # # common strings for settings # -_SMOOTH_SCROLL = ('smooth-scroll', 'Smooth Scrolling', - 'High-sensitivity mode for vertical scroll with the wheel.') -_DPI = ('dpi', 'Sensitivity (DPI)', None) -_FN_SWAP = ('fn-swap', 'Swap Fx function', - ('When set, the F1..F12 keys will activate their special function,\n' - 'and you must hold the FN key to activate their standard function.\n' - '\n' - 'When unset, the F1..F12 keys will activate their standard function,\n' - 'and you must hold the FN key to activate their special function.')) +_SMOOTH_SCROLL = ('smooth-scroll', _("Smooth Scrolling"), + _("High-sensitivity mode for vertical scroll with the wheel.")) +_SIDE_SCROLL = ('side-scroll', _("Side Scrolling"), + _("When disabled, pushing the wheel sideways sends custom button events\n" + "instead of the standard side-scrolling events.")) +_DPI = ('dpi', _("Sensitivity (DPI)"), None) +_FN_SWAP = ('fn-swap', _("Swap Fx function"), + _("When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function.") + + '\n\n' + + _("When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function.")) +_HAND_DETECTION = ('hand-detection', _("Hand Detection"), + _("Turn on illumination when the hands hover over the keyboard.")) # # # +def _register_hand_detection(register=_R.keyboard_hand_detection, + true_value=b'\x00\x00\x00', false_value=b'\x00\x00\x30', mask=b'\x00\x00\xFF'): + return register_toggle(_HAND_DETECTION[0], register, true_value=true_value, false_value=false_value, + label=_HAND_DETECTION[1], description=_HAND_DETECTION[2], + device_kind=_DK.keyboard) + def _register_fn_swap(register=_R.keyboard_fn_swap, true_value=b'\x00\x01', mask=b'\x00\x01'): return register_toggle(_FN_SWAP[0], register, true_value=true_value, mask=mask, label=_FN_SWAP[1], description=_FN_SWAP[2], device_kind=_DK.keyboard) -def _register_smooth_scroll(register=_R.mouse_smooth_scroll, true_value=0x40, mask=0x40): +def _register_smooth_scroll(register=_R.mouse_button_flags, true_value=0x40, mask=0x40): return register_toggle(_SMOOTH_SCROLL[0], register, true_value=true_value, mask=mask, label=_SMOOTH_SCROLL[1], description=_SMOOTH_SCROLL[2], device_kind=_DK.mouse) +def _register_side_scroll(register=_R.mouse_button_flags, true_value=0x02, mask=0x02): + return register_toggle(_SIDE_SCROLL[0], register, true_value=true_value, mask=mask, + label=_SIDE_SCROLL[1], description=_SIDE_SCROLL[2], + device_kind=_DK.mouse) + def _register_dpi(register=_R.mouse_dpi, choices=None): return register_choices(_DPI[0], register, choices, label=_DPI[1], description=_DPI[2], @@ -87,7 +122,6 @@ def _feature_fn_swap(): return feature_toggle(_FN_SWAP[0], _F.FN_INVERSION, - write_returns_value=True, label=_FN_SWAP[1], description=_FN_SWAP[2], device_kind=_DK.keyboard) @@ -100,6 +134,7 @@ _SETTINGS_LIST = namedtuple('_SETTINGS_LIST', [ 'fn_swap', 'smooth_scroll', + 'side_scroll', 'dpi', 'hand_detection', 'typing_illumination', @@ -109,13 +144,15 @@ RegisterSettings = _SETTINGS_LIST( fn_swap=_register_fn_swap, smooth_scroll=_register_smooth_scroll, + side_scroll=_register_side_scroll, dpi=_register_dpi, - hand_detection=None, + hand_detection=_register_hand_detection, typing_illumination=None, ) FeatureSettings = _SETTINGS_LIST( fn_swap=_feature_fn_swap, smooth_scroll=None, + side_scroll=None, dpi=None, hand_detection=None, typing_illumination=None, diff -Nru solaar-0.9.1/lib/logitech_receiver/special_keys.py solaar-0.9.2/lib/logitech_receiver/special_keys.py --- solaar-0.9.1/lib/logitech_receiver/special_keys.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/special_keys.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,23 @@ -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + # Reprogrammable keys information -# from __future__ import absolute_import, division, print_function, unicode_literals diff -Nru solaar-0.9.1/lib/logitech_receiver/status.py solaar-0.9.2/lib/logitech_receiver/status.py --- solaar-0.9.1/lib/logitech_receiver/status.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/logitech_receiver/status.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -11,6 +26,7 @@ del getLogger +from .i18n import _ from .common import NamedInts as _NamedInts, NamedInt as _NamedInt from . import hidpp10 as _hidpp10 from . import hidpp20 as _hidpp20 @@ -79,9 +95,9 @@ def __str__(self): count = len(self._receiver) - return ('No paired devices.' if count == 0 else - '1 paired device.' if count == 1 else - '%d paired devices.' % count) + return (_("No paired devices.") if count == 0 else + _("1 paired device.") if count == 1 else + (str(count) + _(" paired devices."))) __unicode__ = __str__ def changed(self, alert=ALERT.NOTIFICATION, reason=None): @@ -134,15 +150,15 @@ battery_level = self.get(KEYS.BATTERY_LEVEL) if battery_level is not None: if isinstance(battery_level, _NamedInt): - yield 'Battery: %s' % str(battery_level) + yield _("Battery") + ': ' + _(str(battery_level)) else: - yield 'Battery: %d%%' % battery_level + yield _("Battery") + ': ' + ('%d%%' % battery_level) battery_status = _item(KEYS.BATTERY_STATUS, ' (%s)') if battery_status: yield battery_status - light_level = _item(KEYS.LIGHT_LEVEL, 'Light: %d lux') + light_level = _item(KEYS.LIGHT_LEVEL, _("Lighting") + ': %d ' + _("lux")) if light_level: if battery_level: yield ', ' @@ -171,7 +187,7 @@ old_level, self[KEYS.BATTERY_LEVEL] = self.get(KEYS.BATTERY_LEVEL), level old_status, self[KEYS.BATTERY_STATUS] = self.get(KEYS.BATTERY_STATUS), status - charging = status in ('charging', 'fully charged', 'recharging', 'slow recharge') + charging = status in (_hidpp20.BATTERY_STATUS.recharging, _hidpp20.BATTERY_STATUS.slow_recharge) old_charging, self[KEYS.BATTERY_CHARGING] = self.get(KEYS.BATTERY_CHARGING), charging changed = old_level != level or old_status != status or old_charging != charging @@ -247,7 +263,8 @@ # Devices lose configuration when they are turned off, # make sure they're up-to-date. - for s in self._device.settings: + # _log.debug("%s settings %s", d, d.settings) + for s in d.settings: s.apply() if self.get(KEYS.BATTERY_LEVEL) is None: @@ -269,7 +286,7 @@ self.updated = timestamp # if _log.isEnabledFor(_DEBUG): - # _log.debug("device %d changed: active=%s %s", self._device.number, self._active, dict(self)) + # _log.debug("device %d changed: active=%s %s", d.number, self._active, dict(self)) self._changed_callback(d, alert, reason) # def poll(self, timestamp): diff -Nru solaar-0.9.1/lib/solaar/__init__.py solaar-0.9.2/lib/solaar/__init__.py --- solaar-0.9.1/lib/solaar/__init__.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/solaar/__init__.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,8 +1,23 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals -__version__ = '0.9.1' +__version__ = '0.9.2' NAME = 'Solaar' diff -Nru solaar-0.9.1/lib/solaar/cli.py solaar-0.9.2/lib/solaar/cli.py --- solaar-0.9.1/lib/solaar/cli.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/solaar/cli.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,22 @@ -# -# -# +#!/usr/bin/env python +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -307,9 +323,9 @@ try: value = bool(int(value)) except: - if value.lower() in ['1', 'true', 'yes', 'on', 't', 'y']: + if value.lower() in ('1', 'true', 'yes', 'on', 't', 'y'): value = True - elif value.lower() in ['0', 'false', 'no', 'off', 'f', 'n']: + elif value.lower() in ('0', 'false', 'no', 'off', 'f', 'n'): value = False else: _fail("don't know how to interpret '%s' as boolean" % value) @@ -317,7 +333,7 @@ elif setting.choices: value = args.value.lower() - if value in ['higher', 'lower']: + if value in ('higher', 'lower'): old_value = setting.read() if old_value is None: _fail("could not read current value of '%s'" % setting.name) diff -Nru solaar-0.9.1/lib/solaar/configuration.py solaar-0.9.2/lib/solaar/configuration.py --- solaar-0.9.1/lib/solaar/configuration.py 2013-07-12 12:35:52.000000000 +0000 +++ solaar-0.9.2/lib/solaar/configuration.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import os as _os import os.path as _path diff -Nru solaar-0.9.1/lib/solaar/gtk.py solaar-0.9.2/lib/solaar/gtk.py --- solaar-0.9.1/lib/solaar/gtk.py 2013-07-12 12:35:52.000000000 +0000 +++ solaar-0.9.2/lib/solaar/gtk.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,11 +1,28 @@ -# -# -# +#!/usr/bin/env python +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals from solaar import __version__, NAME +import solaar.i18n as _i18n # # @@ -23,7 +40,7 @@ import argparse arg_parser = argparse.ArgumentParser(prog=NAME.lower()) arg_parser.add_argument('-d', '--debug', action='count', default=0, - help='print logging messages, for debugging purposes (may be repeated for extra verbosity)') + help="print logging messages, for debugging purposes (may be repeated for extra verbosity)") arg_parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__) args = arg_parser.parse_args() @@ -36,6 +53,9 @@ logging.root.addHandler(logging.NullHandler()) logging.root.setLevel(logging.ERROR) + if logging.root.isEnabledFor(logging.INFO): + logging.info("language %s (%s), translations path %s", _i18n.language, _i18n.encoding, _i18n.path) + return args @@ -63,7 +83,7 @@ listener.stop_all() except Exception as e: import sys - sys.exit("%s: error: %s" % (NAME.lower(), e)) + sys.exit('%s: error: %s' % (NAME.lower(), e)) if __name__ == '__main__': diff -Nru solaar-0.9.1/lib/solaar/i18n.py solaar-0.9.2/lib/solaar/i18n.py --- solaar-0.9.1/lib/solaar/i18n.py 1970-01-01 00:00:00.000000000 +0000 +++ solaar-0.9.2/lib/solaar/i18n.py 2013-07-24 09:42:46.000000000 +0000 @@ -0,0 +1,64 @@ +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from __future__ import absolute_import, division, print_function, unicode_literals + +from solaar import NAME as _NAME + +# +# +# + +def _find_locale_path(lc_domain): + import os.path as _path + + import sys as _sys + prefix_share = _path.normpath(_path.join(_path.realpath(_sys.path[0]), '..')) + src_share = _path.normpath(_path.join(_path.realpath(_sys.path[0]), '..', 'share')) + del _sys + + from glob import glob as _glob + + for location in prefix_share, src_share: + mo_files = _glob(_path.join(location, 'locale', '*', 'LC_MESSAGES', lc_domain + '.mo')) + if mo_files: + return _path.join(location, 'locale') + + # del _path + + +import locale +locale.setlocale(locale.LC_ALL, '') +language, encoding = locale.getlocale() +del locale + +_LOCALE_DOMAIN = _NAME.lower() +path = _find_locale_path(_LOCALE_DOMAIN) + +import gettext as _gettext + +_gettext.bindtextdomain(_LOCALE_DOMAIN, path) +_gettext.textdomain(_LOCALE_DOMAIN) +_gettext.install(_LOCALE_DOMAIN) + +try: + unicode + _ = lambda x: _gettext.gettext(x).decode('UTF-8') +except: + _ = _gettext.gettext diff -Nru solaar-0.9.1/lib/solaar/listener.py solaar-0.9.2/lib/solaar/listener.py --- solaar-0.9.1/lib/solaar/listener.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/solaar/listener.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -8,6 +23,8 @@ _log = getLogger(__name__) del getLogger + +from solaar.i18n import _ from . import configuration from logitech_receiver import ( Receiver, @@ -21,7 +38,7 @@ # from collections import namedtuple -_GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ['receiver', 'number', 'name', 'kind', 'status', 'online']) +_GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ('receiver', 'number', 'name', 'kind', 'status', 'online')) _GHOST_DEVICE.__bool__ = lambda self: False _GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__ del namedtuple @@ -75,7 +92,7 @@ # make sure to clean up in _all_listeners _all_listeners.pop(r.path, None) - r.status = 'The receiver was unplugged.' + r.status = _("The receiver was unplugged.") if r: try: r.close() @@ -262,8 +279,7 @@ def setup_scanner(status_changed_callback, error_callback): global _status_callback, _error_callback - if _status_callback: - raise Exception("scanner was already set-up") + assert _status_callback is None, 'scanner was already set-up' _status_callback = status_changed_callback _error_callback = error_callback diff -Nru solaar-0.9.1/lib/solaar/ui/__init__.py solaar-0.9.2/lib/solaar/ui/__init__.py --- solaar-0.9.1/lib/solaar/ui/__init__.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/solaar/ui/__init__.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -10,19 +25,27 @@ del getLogger from gi.repository import GLib, Gtk -GLib.threads_init() + + +from solaar.i18n import _ # # # +assert Gtk.get_major_version() > 2, 'Solaar requires Gtk 3 python bindings' + +GLib.threads_init() + def _init_application(): - app = Gtk.Application.new('io.github.pwr.solaar', 0) + APP_ID = 'io.github.pwr.solaar' + app = Gtk.Application.new(APP_ID, 0) # not sure this is necessary... # app.set_property('register-session', True) registered = app.register(None) + dbus_path = app.get_dbus_object_path() if hasattr(app, 'get_dbus_object_path') else APP_ID if _log.isEnabledFor(_INFO): - _log.info("application %s, registered %s", app.get_dbus_object_path(), registered) + _log.info("application %s, registered %s", dbus_path, registered) # assert registered, "failed to register unique application %s" % app # if there is already a running instance, bail out @@ -43,17 +66,15 @@ _log.error("error: %s %s", reason, object) if reason == 'permissions': - title = 'Permissions error' - text = ('Found a Logitech Receiver (%s), but did not have permission to open it.\n' - '\n' - 'If you\'ve just installed Solaar, try removing the receiver\n' - 'and plugging it back in.' % object) + title = _("Permissions error") + text = _("Found a Logitech Receiver (%s), but did not have permission to open it.") % object + \ + '\n\n' + \ + _("If you've just installed Solaar, try removing the receiver and plugging it back in.") elif reason == 'unpair': - title = 'Unpairing failed' - text = ('Failed to unpair %s from %s.\n' - '\n' - 'The receiver returned an error, with no further details.' - % (object.name, object.receiver.name)) + title = _("Unpairing failed") + text = _("Failed to unpair %s from %s.") % (object.name, object.receiver.name) + \ + '\n\n' + \ + _("The receiver returned an error, with no further details.") else: raise Exception("ui.error_dialog: don't know how to handle (%s, %s)", reason, object) diff -Nru solaar-0.9.1/lib/solaar/ui/about.py solaar-0.9.2/lib/solaar/ui/about.py --- solaar-0.9.1/lib/solaar/ui/about.py 2013-07-08 18:51:20.000000000 +0000 +++ solaar-0.9.2/lib/solaar/ui/about.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,12 +1,32 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + from __future__ import absolute_import, division, print_function, unicode_literals from gi.repository import Gtk from solaar import __version__, NAME +from solaar.i18n import _ +# +# +# _dialog = None @@ -16,22 +36,22 @@ about.set_program_name(NAME) about.set_version(__version__) - about.set_comments('Shows status of devices connected\nto a Logitech Unifying Receiver.') + about.set_comments(_("Shows status of devices connected\nthrough wireless Logitech receivers.")) about.set_logo_icon_name(NAME.lower()) - about.set_copyright(b'\xC2\xA9'.decode('utf-8') + ' 2012-2013 Daniel Pavel') + about.set_copyright('© 2012-2013 Daniel Pavel') about.set_license_type(Gtk.License.GPL_2_0) about.set_authors(('Daniel Pavel http://github.com/pwr',)) try: - about.add_credit_section('GUI design', ('Julien Gascard',)) - about.add_credit_section('Testing', ( + about.add_credit_section(_("GUI design"), ('Julien Gascard', 'Daniel Pavel')) + about.add_credit_section(_("Testing"), ( 'Douglas Wagner', 'Julien Gascard', 'Peter Wu http://www.lekensteyn.nl/logitech-unifying.html', )) - about.add_credit_section('Logitech documentation', ( + about.add_credit_section(_("Logitech documentation"), ( 'Julien Danjou http://julien.danjou.info/blog/2012/logitech-unifying-upower', 'Nestor Lopez Casado http://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28', )) @@ -44,6 +64,11 @@ import logging logging.exception("failed to fully create the about dialog") + about.set_translator_credits('\n'.join(( + 'Adrian Piotrowicz (polski)', + 'Daniel Pavel (română)', + ))) + about.set_website('http://pwr.github.io/Solaar/') about.set_website_label(NAME) diff -Nru solaar-0.9.1/lib/solaar/ui/action.py solaar-0.9.2/lib/solaar/ui/action.py --- solaar-0.9.1/lib/solaar/ui/action.py 2013-07-08 18:51:20.000000000 +0000 +++ solaar-0.9.2/lib/solaar/ui/action.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -10,21 +25,28 @@ # _log = getLogger(__name__) # del getLogger + +from solaar.i18n import _ + # # # -def make(name, label, function, *args): +def make(name, label, function, stock_id=None, *args): action = Gtk.Action(name, label, label, None) action.set_icon_name(name) + if stock_id is not None: + action.set_stock_id(stock_id) if function: action.connect('activate', function, *args) return action -def make_toggle(name, label, function, *args): +def make_toggle(name, label, function, stock_id=None, *args): action = Gtk.ToggleAction(name, label, label, None) action.set_icon_name(name) + if stock_id is not None: + action.set_stock_id(stock_id) action.connect('activate', function, *args) return action @@ -43,7 +65,7 @@ from .about import show_window as _show_about_window from solaar import NAME -about = make('help-about', 'About ' + NAME, _show_about_window) +about = make('help-about', _("About") + ' ' + NAME, _show_about_window, stock_id=Gtk.STOCK_ABOUT) # # @@ -70,10 +92,10 @@ qdialog = Gtk.MessageDialog(window, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, - "Unpair device\n%s ?" % device.name) + _("Unpair") + ' ' + device.name + ' ?') qdialog.set_icon_name('remove') qdialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) - qdialog.add_button('Unpair', Gtk.ResponseType.ACCEPT) + qdialog.add_button(_("Unpair"), Gtk.ResponseType.ACCEPT) choice = qdialog.run() qdialog.destroy() if choice == Gtk.ResponseType.ACCEPT: diff -Nru solaar-0.9.1/lib/solaar/ui/config_panel.py solaar-0.9.2/lib/solaar/ui/config_panel.py --- solaar-0.9.1/lib/solaar/ui/config_panel.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/solaar/ui/config_panel.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,12 +1,28 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals from gi.repository import Gtk, GLib +from solaar.i18n import _ from solaar.ui import async as _ui_async from logitech_receiver.settings import KIND as _SETTING_KIND @@ -78,10 +94,10 @@ sbox.pack_start(Gtk.Label(s.label), False, False, 0) spinner = Gtk.Spinner() - spinner.set_tooltip_text('Working...') + spinner.set_tooltip_text(_("Working") + '...') failed = Gtk.Image.new_from_icon_name('dialog-warning', Gtk.IconSize.SMALL_TOOLBAR) - failed.set_tooltip_text('Failed to read value from the device.') + failed.set_tooltip_text(_("Read/write operation failed.")) if s.kind == _SETTING_KIND.toggle: control = _create_toggle_control(s) diff -Nru solaar-0.9.1/lib/solaar/ui/icons.py solaar-0.9.2/lib/solaar/ui/icons.py --- solaar-0.9.1/lib/solaar/ui/icons.py 2013-07-12 12:35:52.000000000 +0000 +++ solaar-0.9.2/lib/solaar/ui/icons.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals diff -Nru solaar-0.9.1/lib/solaar/ui/notify.py solaar-0.9.2/lib/solaar/ui/notify.py --- solaar-0.9.1/lib/solaar/ui/notify.py 2013-07-07 12:06:23.000000000 +0000 +++ solaar-0.9.2/lib/solaar/ui/notify.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,10 +1,33 @@ -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + # Optional desktop notifications. -# from __future__ import absolute_import, division, print_function, unicode_literals +from solaar.i18n import _ + +# +# +# + try: # this import is allowed to fail, in which case the entire feature is unavailable from gi.repository import Notify @@ -89,8 +112,8 @@ if n is None: n = _notifications[summary] = Notify.Notification() - message = reason or ('unpaired' if dev.status is None else - (str(dev.status) or ('connected' if dev.status else 'offline'))) + message = reason or (_("unpaired") if dev.status is None else + (str(dev.status) or (_("connected") if dev.status else _("offline")))) # we need to use the filename here because the notifications daemon # is an external application that does not know about our icon sets diff -Nru solaar-0.9.1/lib/solaar/ui/pair_window.py solaar-0.9.2/lib/solaar/ui/pair_window.py --- solaar-0.9.1/lib/solaar/ui/pair_window.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/solaar/ui/pair_window.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -10,6 +25,8 @@ _log = getLogger(__name__) del getLogger + +from solaar.i18n import _ from . import icons as _icons from logitech_receiver.status import KEYS as _K @@ -113,19 +130,15 @@ assistant.commit() - header = 'Pairing failed: %s.' % error + header = _("Pairing failed") + ': ' + _(str(error)) + '.' if 'timeout' in str(error): - text = ('Make sure your device is within range,\n' - 'and it has a decent battery charge.') + text = _("Make sure your device is within range, and has a decent battery charge.") elif str(error) == 'device not supported': - text = ('A new device was detected, but\n' - 'it is not compatible with this receiver.') + text = _("A new device was detected, but it is not compatible with this receiver.") elif 'many' in str(error): - text = ('The receiver only supports\n' - '%d paired device(s).') + text = _("The receiver only supports %d paired device(s).") else: - text = ('No further details are available\n' - 'about the error.') + text = _("No further details are available about the error.") _create_page(assistant, Gtk.AssistantPageType.SUMMARY, header, 'dialog-error', text) assistant.next_page() @@ -139,7 +152,7 @@ page = _create_page(assistant, Gtk.AssistantPageType.SUMMARY) - header = Gtk.Label('Found a new device:') + header = Gtk.Label(_("Found a new device") + ':') header.set_alignment(0.5, 0) page.pack_start(header, False, False, 0) @@ -164,7 +177,7 @@ if assistant.is_drawable(): if device.status.get(_K.LINK_ENCRYPTED) == False: hbox.pack_start(Gtk.Image.new_from_icon_name('security-low', Gtk.IconSize.MENU), False, False, 0) - hbox.pack_start(Gtk.Label('The wireless link is not encrypted!'), False, False, 0) + hbox.pack_start(Gtk.Label(_("The wireless link is not encrypted") + '!'), False, False, 0) hbox.show_all() else: return True @@ -181,7 +194,7 @@ assert receiver.kind is None assistant = Gtk.Assistant() - assistant.set_title(receiver.name + ': pair new device') + assistant.set_title(receiver.name + ': ' + _("pair new device")) assistant.set_icon_name('list-add') assistant.set_size_request(400, 240) @@ -189,8 +202,8 @@ assistant.set_role('pair-device') page_intro = _create_page(assistant, Gtk.AssistantPageType.PROGRESS, - 'Turn on the device you want to pair.', 'preferences-desktop-peripherals', - 'If the device is already turned on,\nturn if off and on again.') + _("Turn on the device you want to pair."), 'preferences-desktop-peripherals', + _("If the device is already turned on,\nturn if off and on again.")) spinner = Gtk.Spinner() spinner.set_visible(True) page_intro.pack_end(spinner, True, True, 24) diff -Nru solaar-0.9.1/lib/solaar/ui/tray.py solaar-0.9.2/lib/solaar/ui/tray.py --- solaar-0.9.1/lib/solaar/ui/tray.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/solaar/ui/tray.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -13,7 +28,9 @@ from gi.repository import Gtk, GLib from gi.repository.Gdk import ScrollDirection + from solaar import NAME +from solaar.i18n import _ from logitech_receiver.status import KEYS as _K from . import icons as _icons from .window import popup as _window_popup, toggle as _window_toggle @@ -35,14 +52,14 @@ # per-device menu entries will be generated as-needed - no_receiver = Gtk.MenuItem.new_with_label('No receiver found') + no_receiver = Gtk.MenuItem.new_with_label(_("No Logitech receiver found")) no_receiver.set_sensitive(False) menu.append(no_receiver) menu.append(Gtk.SeparatorMenuItem.new()) from .action import about, make menu.append(about.create_menu_item()) - menu.append(make('application-exit', 'Quit', quit_handler).create_menu_item()) + menu.append(make('application-exit', _("Quit"), quit_handler, stock_id=Gtk.STOCK_QUIT).create_menu_item()) del about, make menu.show_all() @@ -249,7 +266,7 @@ def _generate_tooltip_lines(): if not _devices_info: - yield '%s: no receivers' % NAME + yield '%s: ' % NAME + _("no receiver") return yield '%s' % NAME @@ -265,12 +282,12 @@ if status: yield '\t%s' % p else: - yield '\t%s (offline)' % p + yield '\t%s (' % p + _("offline") + ')' else: if status: - yield '%s no status' % name + yield '%s (' % name + _("no status") + ')' else: - yield '%s (offline)' % name + yield '%s (' % name + _("offline") + ')' yield '' diff -Nru solaar-0.9.1/lib/solaar/ui/window.py solaar-0.9.2/lib/solaar/ui/window.py --- solaar-0.9.1/lib/solaar/ui/window.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/solaar/ui/window.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals @@ -11,7 +26,9 @@ from gi.repository import Gtk, Gdk, GLib from gi.repository.GObject import TYPE_PYOBJECT + from solaar import NAME +from solaar.i18n import _ # from solaar import __version__ as VERSION from solaar.ui import async as _ui_async from logitech_receiver import hidpp10 as _hidpp10 @@ -38,21 +55,21 @@ assert len(_TREE_SEPATATOR) == len(_COLUMN_TYPES) assert len(_COLUMN_TYPES) == len(_COLUMN) -_TOOLTIP_LINK_SECURE = 'The wireless link between this device and its receiver is encrypted.' -_TOOLTIP_LINK_INSECURE = ('The wireless link between this device and its receiver is not encrypted.\n' - '\n' - 'For pointing devices (mice, trackballs, trackpads), this is a minor security issue.\n' - '\n' - 'It is, however, a major security issue for text-input devices (keyboards, numpads),\n' - 'because typed text can be sniffed inconspicuously by 3rd parties within range.') +_TOOLTIP_LINK_SECURE = _("The wireless link between this device and its receiver is encrypted.") +_TOOLTIP_LINK_INSECURE = _("The wireless link between this device and its receiver is not encrypted.\n" + "\n" + "For pointing devices (mice, trackballs, trackpads), this is a minor security issue.\n" + "\n" + "It is, however, a major security issue for text-input devices (keyboards, numpads),\n" + "because typed text can be sniffed inconspicuously by 3rd parties within range.") _UNIFYING_RECEIVER_TEXT = ( - 'No paired devices.\n\nUp to %d devices can be paired to this receiver.', - '%d paired device(s).\n\nUp to %d devices can be paired to this receiver.', + _("No device paired") + '.\n\n' + _("Up to %d devices can be paired to this receiver") + '.', + '%d ' + _("paired devices") + '\n\n' + _("Up to %d devices can be paired to this receiver") + '.', ) _NANO_RECEIVER_TEXT = ( - 'No paired device.\n\n ', - ' \n\nOnly one device can be paired to this receiver.', + _("No device paired") + '.\n\n ', + ' \n\n' + _("Only one device can be paired to this receiver") + '.', ) # @@ -89,11 +106,11 @@ p = Gtk.Box.new(Gtk.Orientation.VERTICAL, 4) p._count = Gtk.Label() - p._count.set_padding(32, 0) + p._count.set_padding(24, 0) p._count.set_alignment(0, 0.5) p.pack_start(p._count, True, True, 0) - p._scanning = Gtk.Label('Scanning...') + p._scanning = Gtk.Label(_("Scanning") + '...') p._spinner = Gtk.Spinner() bp = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 8) @@ -126,14 +143,14 @@ return b - p._battery = _status_line('Battery') + p._battery = _status_line(_("Battery")) p.pack_start(p._battery, False, False, 0) - p._secure = _status_line('Wireless Link') + p._secure = _status_line(_("Wireless Link")) p._secure._icon.set_from_icon_name('dialog-warning', _INFO_ICON_SIZE) p.pack_start(p._secure, False, False, 0) - p._lux = _status_line('Lighting') + p._lux = _status_line(_("Lighting")) p.pack_start(p._lux, False, False, 0) p._config = _config_panel.create() @@ -162,7 +179,7 @@ bb.set_layout(Gtk.ButtonBoxStyle.END) bb._details = _new_button(None, 'dialog-information', _SMALL_BUTTON_ICON_SIZE, - tooltip='Show Technical Details', toggle=True, clicked=_update_details) + tooltip=_("Show Technical Details"), toggle=True, clicked=_update_details) bb.add(bb._details) bb.set_child_secondary(bb._details, True) bb.set_child_non_homogeneous(bb._details, True) @@ -175,7 +192,7 @@ assert receiver.kind is None _action.pair(_window, receiver) - bb._pair = _new_button('Pair new device', 'list-add', clicked=_pair_new_device) + bb._pair = _new_button(_("Pair new device"), 'list-add', clicked=_pair_new_device) bb.add(bb._pair) def _unpair_current_device(trigger): @@ -186,7 +203,7 @@ assert device.kind is not None _action.unpair(_window, device) - bb._unpair = _new_button('Unpair', 'edit-delete', clicked=_unpair_current_device) + bb._unpair = _new_button(_("Unpair"), 'edit-delete', clicked=_unpair_current_device) bb.add(bb._unpair) return bb @@ -194,7 +211,7 @@ def _create_empty_panel(): p = Gtk.Label() - p.set_markup('Select a device') + p.set_markup('' + _("Select a device") + '') p.set_sensitive(False) return p @@ -299,7 +316,7 @@ panel.pack_start(_info, True, True, 0) panel.pack_start(_empty, True, True, 0) - about_button = _new_button('About ' + NAME, 'help-about', + about_button = _new_button(_("About") + ' ' + NAME, 'help-about', icon_size=_SMALL_BUTTON_ICON_SIZE, clicked=_show_about_window) bottom_buttons_box = Gtk.ButtonBox(Gtk.Orientation.HORIZONTAL) @@ -491,39 +508,39 @@ # cached, and involves no HID++ calls. if device.kind is None: - yield ('Path', device.path) + yield (_("Path"), device.path) # 046d is the Logitech vendor id - yield ('USB id', '046d:' + device.product_id) + yield (_("USB id"), '046d:' + device.product_id) if read_all: - yield ('Serial', device.serial) + yield (_("Serial"), device.serial) else: - yield ('Serial', '...') + yield (_("Serial"), '...') else: # yield ('Codename', device.codename) - yield ('Index', device.number) - yield ('Wireless PID', device.wpid) + yield (_("Index"), device.number) + yield (_("Wireless PID"), device.wpid) hid_version = device.protocol - yield ('Protocol', 'HID++ %1.1f' % hid_version if hid_version else 'unknown') + yield (_("Protocol"), 'HID++ %1.1f' % hid_version if hid_version else 'unknown') if read_all and device.polling_rate: - yield ('Polling rate', '%d ms (%dHz)' % (device.polling_rate, 1000 // device.polling_rate)) + yield (_("Polling rate"), '%d ms (%dHz)' % (device.polling_rate, 1000 // device.polling_rate)) if read_all or not device.online: - yield ('Serial', device.serial) + yield (_("Serial"), device.serial) else: - yield ('Serial', '...') + yield (_("Serial"), '...') if read_all: for fw in list(device.firmware): yield (' ' + str(fw.kind), (fw.name + ' ' + fw.version).strip()) elif device.kind is None or device.online: - yield (' Firmware', '...') + yield (' %s' % _("Firmware"), '...') flag_bits = device.status.get(_K.NOTIFICATION_FLAGS) if flag_bits is not None: - flag_names = ('(none)',) if flag_bits == 0 else _hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits) - yield ('Notifications', ('\n%15s' % ' ').join(flag_names)) + flag_names = ('(%s)' % _("none"),) if flag_bits == 0 else _hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits) + yield (_("Notifications"), ('\n%15s' % ' ').join(flag_names)) def _set_details(text): _details._text.set_markup(text) @@ -605,7 +622,7 @@ panel._battery._icon.set_sensitive(False) panel._battery._icon.set_from_icon_name(icon_name, _INFO_ICON_SIZE) panel._battery._text.set_sensitive(True) - panel._battery._text.set_markup('unknown') + panel._battery._text.set_markup('%s' % _("unknown")) else: charging = device.status.get(_K.BATTERY_CHARGING) icon_name = _icons.battery(battery_level, charging) @@ -618,25 +635,25 @@ text = '%d%%' % battery_level if is_online: if charging: - text += ' (charging)' + text += ' (%s)' % _("charging") else: - text += ' (last known)' + text += ' (%s)' % _("last known") panel._battery._text.set_sensitive(is_online) panel._battery._text.set_markup(text) if is_online: not_secure = device.status.get(_K.LINK_ENCRYPTED) == False if not_secure: - panel._secure._text.set_text('not encrypted') + panel._secure._text.set_text(_("not encrypted")) panel._secure._icon.set_from_icon_name('security-low', _INFO_ICON_SIZE) panel._secure.set_tooltip_text(_TOOLTIP_LINK_INSECURE) else: - panel._secure._text.set_text('encrypted') + panel._secure._text.set_text(_("encrypted")) panel._secure._icon.set_from_icon_name('security-high', _INFO_ICON_SIZE) panel._secure.set_tooltip_text(_TOOLTIP_LINK_SECURE) panel._secure._icon.set_visible(True) else: - panel._secure._text.set_markup('offline') + panel._secure._text.set_markup('%s' % _("offline")) panel._secure._icon.set_visible(False) panel._secure.set_tooltip_text('') @@ -646,7 +663,7 @@ panel._lux.set_visible(False) else: panel._lux._icon.set_from_icon_name(_icons.lux(light_level), _INFO_ICON_SIZE) - panel._lux._text.set_text('%d lux' % light_level) + panel._lux._text.set_text('%d %s' % (light_level, _("lux"))) panel._lux.set_visible(True) else: panel._lux.set_visible(False) diff -Nru solaar-0.9.1/lib/solaar/upower.py solaar-0.9.2/lib/solaar/upower.py --- solaar-0.9.1/lib/solaar/upower.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/lib/solaar/upower.py 2013-07-24 09:42:46.000000000 +0000 @@ -1,6 +1,21 @@ -# -# -# +# -*- python-mode -*- +# -*- coding: UTF-8 -*- + +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import, division, print_function, unicode_literals diff -Nru solaar-0.9.1/setup.py solaar-0.9.2/setup.py --- solaar-0.9.1/setup.py 2013-07-13 09:25:20.000000000 +0000 +++ solaar-0.9.2/setup.py 2013-07-24 09:42:46.000000000 +0000 @@ -24,6 +24,23 @@ del sys, backup_path_0 + +def _data_files(): + from os.path import dirname as _dirname + + yield 'share/solaar/icons', _glob('share/solaar/icons/solaar*.svg') + yield 'share/solaar/icons', _glob('share/solaar/icons/light_*.png') + yield 'share/icons/hicolor/scalable/apps', ['share/solaar/icons/solaar.svg'] + + for mo in _glob('share/locale/*/LC_MESSAGES/solaar.mo'): + yield _dirname(mo), [mo] + + yield 'share/applications', ['share/applications/solaar.desktop'] + yield autostart_path, ['share/applications/solaar.desktop'] + + del _dirname + + setup(name=NAME.lower(), version=__version__, description='Linux devices manager for the Logitech Unifying Receiver.', @@ -55,13 +72,6 @@ package_dir={'': 'lib'}, packages=['hidapi', 'logitech_receiver', 'solaar', 'solaar.ui'], - - data_files=[('share/solaar/icons', _glob('share/solaar/icons/solaar*.svg')), - ('share/solaar/icons', _glob('share/solaar/icons/light_*.png')), - ('share/icons/hicolor/scalable/apps', ['share/solaar/icons/solaar.svg']), - ('share/applications', ['share/applications/solaar.desktop']), - (autostart_path, ['share/applications/solaar.desktop']), - ], - + data_files=list(_data_files()), scripts=_glob('bin/*'), ) Binary files /tmp/7Pn3MlMZHM/solaar-0.9.1/share/locale/pl/LC_MESSAGES/solaar.mo and /tmp/kaYJgNC10t/solaar-0.9.2/share/locale/pl/LC_MESSAGES/solaar.mo differ Binary files /tmp/7Pn3MlMZHM/solaar-0.9.1/share/locale/ro/LC_MESSAGES/solaar.mo and /tmp/kaYJgNC10t/solaar-0.9.2/share/locale/ro/LC_MESSAGES/solaar.mo differ