diff --git a/ChangeLog b/ChangeLog index b90d1c44..5f0efe0f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +0.9.3: + * Merged solaar-cli functionality into main solaar. + * Scrolling over the systray icon switches between multiple peripherals. + * Swedish translation courtesy of Daniel Zippert and Emelie Snecker + * French translation courtesy of Papoteur, David Geiger and Damien Lallement. + * Fixed some untranslated strings. + 0.9.2: * Added support for hand detection on the K800. * Added support for V550 and V450 Nano. diff --git a/README.md b/README.md index 87d1b9e5..a36f77ba 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,16 @@ Pre-built packages are available for a few Linux distros. The `solaar` package uses a standard system tray implementation; to ensure integration with *gnome-shell* or *Unity*, install `solaar-gnome3`. -* a [Gentoo overlay][gentoo], courtesy of Carlos Silva -* an [OpenSUSE rpm][opensuse], courtesy of Mathias Homann * an [Arch package][arch], courtesy of Arnaud Taffanel +* a [Gentoo overlay][gentoo], courtesy of Carlos Silva +* a [Mageia package][mageia], courtesy of Damien Lallement +* an [OpenSUSE rpm][opensuse], courtesy of Mathias Homann [ppa]: http://launchpad.net/~daniel.pavel/+archive/solaar -[gentoo]: http://code.r3pek.org/gentoo-overlay/src -[opensuse]: http://software.opensuse.org/package/Solaar [arch]: http://aur.archlinux.org/packages/solaar +[gentoo]: http://code.r3pek.org/gentoo-overlay/src +[mageia]: http://mageia.madb.org/package/show/release/cauldron/application/0/name/solaar +[opensuse]: http://software.opensuse.org/package/Solaar ## Manual installation diff --git a/bin/solaar-cli b/bin/solaar-cli index b3231d43..35192c3b 100755 --- a/bin/solaar-cli +++ b/bin/solaar-cli @@ -37,6 +37,7 @@ def init_paths(): if __name__ == '__main__': + print ('WARNING: solaar-cli is deprecated; use solaar with the usual arguments') init_paths() import solaar.cli - solaar.cli.main() + solaar.cli.run() diff --git a/docs/devices.md b/docs/devices.md index 3e96b6af..c8b6fe96 100644 --- a/docs/devices.md +++ b/docs/devices.md @@ -40,9 +40,6 @@ Solaar, but its supported features are not specified here, I would love to hear about it. -Devices marked with an asterisk (*) use a Nano receiver that knows the Unifying -protocol, and should be fully supported by Solaar. - The HID++ column specifies the device's HID++ version. The Battery column specifies if Solaar is able to read the device's battery @@ -56,14 +53,14 @@ You are able to read this feature using solaar-cli, but it is not possible to assign different keys. -Keyboards: +Keyboards (Unifying): | Device | HID++ | Battery | Other supported features | |------------------|-------|---------|-----------------------------------------| | K230 | 2.0 | yes | | -| K270 | | | | -| K340 | | | | -| K350 | | | | +| K270 | 1.0 | yes | | +| K340 | 1.0 | yes | | +| K350 | 1.0 | yes | | | K360 | 2.0 | yes | FN swap, reprog keys | | K400 Touch | 2.0 | yes | | | K750 Solar | 2.0 | yes | FN swap, Lux reading, light button | @@ -71,50 +68,70 @@ Keyboards: | MK700 | 1.0 | yes | FN swap, reprog keys | -Mice: +Mice (Unifying): | Device | HID++ | Battery | DPI | Other supported features | |------------------|-------|---------|-------|---------------------------------| -| V450 Nano | 1.0 | yes | - | smooth scrolling | -| V550 Nano | 1.0 | yes | - | smooth scrolling | -| VX Nano | 1.0 | yes | - | smooth scrolling | -| M175 * | | yes | | | -| M185 * | | yes | | | -| M187 * | 2.0 | yes | | | -| M215 * | 1.0 | yes | | | -| M235 * | | yes | | | -| M305 * | 1.0 | yes | | | -| M310 * | | yes | | | -| M315 * | | yes | | | | M317 | | | | | | M325 | | | | | | M345 | 2.0 | yes | - | | -| M505 | 1.0 | yes | | | +| M350 | 1.0 | yes | | | +| M505 | 1.0 | yes | | smooth scrolling | | M510 | 1.0 | yes | | smooth scrolling | | M515 Couch | 2.0 | yes | - | | | M525 | 2.0 | yes | - | | | M600 Touch | 2.0 | yes | | | | M705 Marathon | 1.0 | yes | - | smooth scrolling | | T400 Zone Touch | | | | | -| T620 Touch | 2.0 | | | | +| T620 Touch | 2.0 | yes | | | | Performance MX | 1.0 | yes | R/W | | | Anywhere MX | 1.0 | yes | - | | | Cube | 2.0 | yes | | | -Trackballs: +Mice (Nano): + +| Device | HID++ | Battery | DPI | Other supported features | +|------------------|-------|---------|-------|---------------------------------| +| V450 Nano | 1.0 | yes | - | smooth scrolling | +| V550 Nano | 1.0 | yes | - | smooth scrolling | +| VX Nano | 1.0 | yes | - | smooth scrolling | +| M175 | | yes | | | +| M185 | | yes | | | +| M187 | 2.0 | yes | | | +| M215 | 1.0 | yes | | | +| M235 | | yes | | | +| M305 | 1.0 | yes | | | +| M310 | 1.0 | yes | | | +| M315 | | yes | | | + + +Mice (Mini): + +| Device | HID++ | Battery | DPI | Other supported features | +|------------------|-------|---------|-------|---------------------------------| +| MX610 | 1.0 | yes | | | +| MX610 lefthanded | 1.0 | yes | | | +| V400 | 1.0 | yes | | | +| V450 | 1.0 | yes | | | +| VX Revolution | 1.0 | yes | | | +| MX Air | 1.0 | yes | | | +| MX Revolution | 1.0 | yes | | | + + +Trackballs (Unifying): | Device | HID++ | Battery | DPI | Other supported features | |------------------|-------|---------|-------|---------------------------------| | M570 Trackball | | | | | -Touchpads: +Touchpads (Unifying): | Device | HID++ | Battery | DPI | Other supported features | |------------------|-------|---------|-------|---------------------------------| -| Wireless Touch | 2.0 | | | | -| T650 Touchpad | 2.0 | | | | +| Wireless Touch | 2.0 | yes | | | +| T650 Touchpad | 2.0 | yes | | | Mouse-Keyboard combos: diff --git a/docs/i18n.md b/docs/i18n.md index 05369664..97ffb974 100644 --- a/docs/i18n.md +++ b/docs/i18n.md @@ -28,3 +28,24 @@ the source root): You can edit the translation iteratively, just repeat from step 3. If the upstream changes, do a `git pull` and then repeat from step 2. + +Before opening a pull request, please run `./tools/po-update.sh` again. It will +format and sort the translation file, and ensure a minimal diff when updating +a translation. + + +# Supported languages + +Currently Solaar has been translated in the following languages: + +- Français: [Papoteur][papoteur], [David Geiger][david-geiger], + [Damien Lallement][damsweb] +- Polski: [Adrian Piotrowicz][nexces] +- Română: Daniel Pavel +- Svensk: [Daniel Zippert][zipperten], Emelie Snecker + +[papoteur]: http://github.com/papoteur +[david-geiger]: http://github.com/david-geiger +[damsweb]: http://github.com/damsweb +[nexces]: http://github.com/nexces +[zipperten]: http://github.com/zipperten diff --git a/jekyll/_config.yml b/jekyll/_config.yml index 95f2bd6c..7c05c378 100644 --- a/jekyll/_config.yml +++ b/jekyll/_config.yml @@ -3,8 +3,8 @@ tagline: Linux devices manager for the Logitech Unifying Receiver. owner: pwr owner_url: https://github.com/pwr repository: https://github.com/pwr/Solaar -version: 0.9.1 -tar_download: https://github.com/pwr/Solaar/archive/0.9.1.tar.gz +version: 0.9.2 +tar_download: https://github.com/pwr/Solaar/archive/0.9.2.tar.gz ga_id: UA-36908718-1 pygments: true diff --git a/lib/logitech_receiver/base_usb.py b/lib/logitech_receiver/base_usb.py index 173faa3e..690dffaa 100644 --- a/lib/logitech_receiver/base_usb.py +++ b/lib/logitech_receiver/base_usb.py @@ -28,30 +28,33 @@ _GENERIC_DRIVER = ('hid-generic', 'generic-usb') # each tuple contains (vendor_id, product_id, usb interface number, hid driver) +_unifying_receiver = lambda product_id: (0x046d, product_id, 2, _UNIFYING_DRIVER) +_nano_receiver = lambda product_id: (0x046d, product_id, 1, _GENERIC_DRIVER) + # standard Unifying receivers (marked with the orange Unifying logo) -UNIFYING_RECEIVER = (0x046d, 0xc52b, 2, _UNIFYING_DRIVER) -UNIFYING_RECEIVER_2 = (0x046d, 0xc532, 2, _UNIFYING_DRIVER) - - +UNIFYING_RECEIVER_C52B = _unifying_receiver(0xc52b) +UNIFYING_RECEIVER_C532 = _unifying_receiver(0xc532) # Nano receviers that support the Unifying protocol -NANO_RECEIVER_ADVANCED = (0x046d, 0xc52f, 1, _GENERIC_DRIVER) +NANO_RECEIVER_ADVANCED = _nano_receiver(0xc52f) # Nano receivers that don't support the Unifying protocol -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) +NANO_RECEIVER_C517 = _nano_receiver(0xc517) +NANO_RECEIVER_C518 = _nano_receiver(0xc518) +NANO_RECEIVER_C51A = _nano_receiver(0xc51a) +NANO_RECEIVER_C51B = _nano_receiver(0xc51b) +NANO_RECEIVER_C521 = _nano_receiver(0xc521) +NANO_RECEIVER_C525 = _nano_receiver(0xc525) +NANO_RECEIVER_C526 = _nano_receiver(0xc526) +del _unifying_receiver, _nano_receiver + ALL = ( - UNIFYING_RECEIVER, - UNIFYING_RECEIVER_2, + UNIFYING_RECEIVER_C52B, + UNIFYING_RECEIVER_C532, NANO_RECEIVER_ADVANCED, NANO_RECEIVER_C517, NANO_RECEIVER_C518, diff --git a/lib/logitech_receiver/descriptors.py b/lib/logitech_receiver/descriptors.py index 88392f1f..e147c86a 100644 --- a/lib/logitech_receiver/descriptors.py +++ b/lib/logitech_receiver/descriptors.py @@ -20,12 +20,10 @@ from __future__ import absolute_import, division, print_function, unicode_literals -from . import hidpp10 as _hidpp10 from .common import NamedInts as _NamedInts +from .hidpp10 import REGISTERS as _R, DEVICE_KIND as _DK from .settings_templates import RegisterSettings as _RS, FeatureSettings as _FS -_R = _hidpp10.REGISTERS - # # # @@ -41,10 +39,11 @@ def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None, assert name if kind is None: - kind = (_hidpp10.DEVICE_KIND.mouse if 'Mouse' in name - else _hidpp10.DEVICE_KIND.keyboard if 'Keyboard' in name - else _hidpp10.DEVICE_KIND.touchpad if 'Touchpad' in name - else _hidpp10.DEVICE_KIND.trackball if 'Trackball' in name + kind = (_DK.mouse if 'Mouse' in name + else _DK.keyboard if 'Keyboard' in name + else _DK.numpad if 'Number Pad' in name + else _DK.touchpad if 'Touchpad' in name + else _DK.trackball if 'Trackball' in name else None) assert kind is not None, 'descriptor for %s does not have kind set' % name @@ -64,12 +63,12 @@ def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None, 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 + assert w[0:1] == '4', '%s has protocol %0.1f, wpid %s' % (name, protocol, w) else: if w[0:1] == '1': - assert kind == _hidpp10.DEVICE_KIND.mouse, name + ' has protocol ' + protocol + ', wpid ' + w + assert kind == _DK.mouse, '%s has protocol %0.1f, wpid %s' % (name, protocol, w) elif w[0:1] == '2': - assert kind == _hidpp10.DEVICE_KIND.keyboard, name + ' has protocol ' + protocol + ', wpid ' + w + assert kind in (_DK.keyboard, _DK.numpad), '%s has protocol %0.1f, wpid %s' % (name, protocol, w) device_descriptor = _DeviceDescriptor(name=name, kind=kind, wpid=wpid, codename=codename, protocol=protocol, @@ -144,10 +143,16 @@ _PERFORMANCE_MX_DPIS = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 1 # Keyboards _D('Wireless Keyboard K230', protocol=2.0, wpid='400D') -_D('Wireless Keyboard K270') +_D('Wireless Keyboard K270', protocol=1.0, + registers=(_R.battery_status, ), + ) _D('Wireless Keyboard MK330') -_D('Wireless Keyboard K340') -_D('Wireless Keyboard K350', wpid='200A') +_D('Wireless Compact Keyboard K340', protocol=1.0, wpid='2007', + registers=(_R.battery_status, ), + ) +_D('Wireless Wave Keyboard K350', protocol=1.0, wpid='200A', + registers=(_R.battery_status, ), + ) _D('Wireless Keyboard K360', protocol=2.0, wpid='4004', settings=[ _FS.fn_swap() @@ -158,7 +163,15 @@ _D('Wireless Touch Keyboard K400', protocol=2.0, wpid=('400E', '4024'), _FS.fn_swap() ], ) -_D('Wireless Keyboard MK520') +_D('Wireless Keyboard K520', protocol=1.0, wpid='2011', + registers=(_R.battery_status, ), + settings=[ + _RS.fn_swap(), + ], + ) +_D('Number Pad N545', protocol=1.0, wpid='2006', + registers=(_R.battery_status, ), + ) _D('Wireless Keyboard MK550') _D('Wireless Keyboard MK700', protocol=1.0, wpid='2008', registers=(_R.battery_status, ), @@ -192,11 +205,16 @@ _D('Wireless Mouse M305', protocol=1.0, wpid='101F', _RS.side_scroll(), ], ) -_D('Wireless Mouse M310') +_D('Wireless Mouse M310', protocol=1.0, wpid='1024', + registers=(_R.battery_status, ), + ) _D('Wireless Mouse M315') _D('Wireless Mouse M317') _D('Wireless Mouse M325') _D('Wireless Mouse M345', protocol=2.0, wpid='4017') +_D('Wireless Mouse M350', protocol=1.0, wpid='101C', + registers=(_R.battery_charge, ), + ) _D('Wireless Mouse M505', codename='M505/B605', protocol=1.0, wpid='101D', registers=(_R.battery_charge, ), settings=[ @@ -223,7 +241,7 @@ _D('Marathon Mouse M705', protocol=1.0, wpid='101B', ) _D('Zone Touch Mouse T400') _D('Touch Mouse T620', protocol=2.0) -_D('Logitech Cube', kind=_hidpp10.DEVICE_KIND.mouse, protocol=2.0) +_D('Logitech Cube', kind=_DK.mouse, protocol=2.0) _D('Anywhere Mouse MX', codename='Anywhere MX', protocol=1.0, wpid='1017', registers=(_R.battery_charge, ), settings=[ @@ -240,6 +258,13 @@ _D('Performance Mouse MX', codename='Performance MX', protocol=1.0, wpid='101A', ], ) +_D('G7 Cordless Laser Mouse', codename='G7', protocol=1.0, wpid='1002', + registers=(_R.battery_status, ), + ) +_D('G700 Gaming Mouse', codename='G700', protocol=1.0, wpid='1023', + registers=(_R.battery_status, ), + ) + # Trackballs _D('Wireless Trackball M570') @@ -254,7 +279,7 @@ _D('Wireless Touchpad', codename='Wireless Touch', protocol=2.0, wpid='4011') # A wpid is necessary to properly identify them. # -_D('VX Nano Cordless Laser Mouse', codename='VX Nano', protocol=1.0, wpid='100F', +_D('VX Nano Cordless Laser Mouse', codename='VX Nano', protocol=1.0, wpid=('100B', '100F'), registers=(_R.battery_charge, ), settings=[ _RS.smooth_scroll(), @@ -271,3 +296,34 @@ _D('V550 Nano Cordless Laser Mouse', codename='V550 Nano', protocol=1.0, wpid='1 _RS.side_scroll(), ], ) + +# Mini receiver mice + +_D('MX610 Laser Cordless Mouse', codename='MX610', protocol=1.0, wpid='1001', + registers=(_R.battery_status, ), + ) +_D('MX620 Laser Cordless Mouse', codename='MX620', protocol=1.0, wpid=('100A', '1016'), + registers=(_R.battery_charge, ), + ) +_D('MX610 Left-Handled Mouse', codename='MX610L', protocol=1.0, wpid='1004', + registers=(_R.battery_status, ), + ) +_D('V400 Laser Cordless Mouse', codename='V400', protocol=1.0, wpid='1003', + registers=(_R.battery_status, ), + ) +_D('V450 Laser Cordless Mouse', codename='V450', protocol=1.0, wpid='1005', + registers=(_R.battery_status, ), + ) +_D('VX Revolution', codename='VX Revolution', kind=_DK.mouse, protocol=1.0, wpid=('1006', '100D'), + registers=(_R.battery_charge, ), + ) +_D('MX Air', codename='MX Air', protocol=1.0, kind=_DK.mouse, wpid=('1007', '100E'), + registers=(_R.battery_charge, ), + ) +_D('MX Revolution', codename='MX Revolution', protocol=1.0, kind=_DK.mouse, wpid=('1008', '100C'), + registers=(_R.battery_charge, ), + ) + +# Some exotics... + +_D('Fujitsu Sonic Mouse', codename='Sonic', protocol=1.0, wpid='1029') diff --git a/lib/logitech_receiver/notifications.py b/lib/logitech_receiver/notifications.py index 2001304d..93bb3482 100644 --- a/lib/logitech_receiver/notifications.py +++ b/lib/logitech_receiver/notifications.py @@ -147,7 +147,7 @@ def _process_hidpp10_notification(device, status, n): device.status = None if device.number in device.receiver: del device.receiver[device.number] - status.changed(active=False, alert=_ALERT.ALL, reason='unpaired') + status.changed(active=False, alert=_ALERT.ALL, reason=_("unpaired")) else: _log.warn("%s: disconnection with unknown type %02X: %s", device, n.address, n) return True diff --git a/lib/logitech_receiver/status.py b/lib/logitech_receiver/status.py index 72b0ceae..fc68c71d 100644 --- a/lib/logitech_receiver/status.py +++ b/lib/logitech_receiver/status.py @@ -140,13 +140,9 @@ class DeviceStatus(dict): self.updated = 0 def __str__(self): - def _item(name, format): - value = self.get(name) - if value is not None: - return format % value - def _items(): - # TODO properly string approximative battery levels + comma = False + battery_level = self.get(KEYS.BATTERY_LEVEL) if battery_level is not None: if isinstance(battery_level, _NamedInt): @@ -154,20 +150,24 @@ class DeviceStatus(dict): else: yield _("Battery") + ': ' + ('%d%%' % battery_level) - battery_status = _item(KEYS.BATTERY_STATUS, ' (%s)') - if battery_status: - yield battery_status + battery_status = self.get(KEYS.BATTERY_STATUS) + if battery_status is not None: + yield ' (%s)' % _(str(battery_status)) - light_level = _item(KEYS.LIGHT_LEVEL, _("Lighting") + ': %d ' + _("lux")) - if light_level: - if battery_level: - yield ', ' - yield light_level + comma = True + + light_level = self.get(KEYS.LIGHT_LEVEL) + if light_level is not None: + if comma: yield ', ' + yield _("Lighting") + (': %d ' % light_level) + _("lux") return ''.join(i for i in _items()) __unicode__ = __str__ + def __repr__(self): + return '{' + ', '.join('\'%s\': %r' % (k, v) for k, v in self.items()) + '}' + def __bool__(self): return bool(self._active) __nonzero__ = __bool__ diff --git a/lib/solaar/async.py b/lib/solaar/async.py new file mode 100644 index 00000000..2fbf3e6d --- /dev/null +++ b/lib/solaar/async.py @@ -0,0 +1,70 @@ +#!/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 logging import getLogger, DEBUG as _DEBUG +_log = getLogger(__name__) +del getLogger + +from threading import Thread as _Thread + +try: + from Queue import Queue as _Queue +except ImportError: + from queue import Queue as _Queue + +# +# +# + +class TaskRunner(_Thread): + def __init__(self, name): + super(TaskRunner, self).__init__(name=name) + self.daemon = True + self.queue = _Queue(16) + self.alive = False + + def __call__(self, function, *args, **kwargs): + task = (function, args, kwargs) + self.queue.put(task) + + def stop(self): + self.alive = False + self.queue.put(None) + + def run(self): + self.alive = True + + if _log.isEnabledFor(_DEBUG): + _log.debug("started") + + while self.alive: + task = self.queue.get() + if task: + function, args, kwargs = task + assert function + try: + function(*args, **kwargs) + except: + _log.exception("calling %s", function) + + if _log.isEnabledFor(_DEBUG): + _log.debug("stopped") diff --git a/lib/solaar/cli.py b/lib/solaar/cli.py deleted file mode 100644 index b9e7eb77..00000000 --- a/lib/solaar/cli.py +++ /dev/null @@ -1,434 +0,0 @@ -#!/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 - -import sys -import logging - - -NAME = 'solaar-cli' -from solaar import __version__ - -# -# -# - -def _fail(text): - if sys.exc_info()[0]: - logging.exception(text) - sys.exit("%s: error: %s" % (NAME, text)) - - -def _require(module, os_package): - try: - __import__(module) - except ImportError: - _fail("missing required package '%s'" % os_package) - -# -# -# - -def _receiver(dev_path=None): - from logitech_receiver import Receiver - from logitech_receiver.base import receivers - for dev_info in receivers(): - if dev_path is not None and dev_path != dev_info.path: - continue - try: - r = Receiver.open(dev_info) - if r: - return r - except Exception as e: - _fail(str(e)) - return r - _fail("Logitech receiver not found") - - -def _find_device(receiver, name, may_be_receiver=False): - if len(name) == 1: - try: - number = int(name) - except: - pass - else: - if number < 1 or number > receiver.max_devices: - _fail("%s (%s) supports device numbers 1 to %d" % (receiver.name, receiver.path, receiver.max_devices)) - dev = receiver[number] - if dev is None: - _fail("no paired device with number %s" % number) - return dev - - if len(name) < 3: - _fail("need at least 3 characters to match a device") - - name = name.lower() - if may_be_receiver and ('receiver'.startswith(name) or name == receiver.serial.lower()): - return receiver - - for dev in receiver: - if (name == dev.serial.lower() or - name == dev.codename.lower() or - name == str(dev.kind).lower() or - name in dev.name.lower()): - return dev - - _fail("no device found matching '%s'" % name) - - -def _print_receiver(receiver, verbose=False): - paired_count = receiver.count() - if not verbose: - print ("Unifying Receiver [%s:%s] with %d devices" % (receiver.path, receiver.serial, paired_count)) - return - - print ("Unifying Receiver") - print (" Device path :", receiver.path) - print (" USB id : 046d:%s" % receiver.product_id) - print (" Serial :", receiver.serial) - for f in receiver.firmware: - print (" %-11s: %s" % (f.kind, f.version)) - - print (" Has", paired_count, "paired device(s) out of a maximum of", receiver.max_devices, ".") - - from logitech_receiver import hidpp10 - notification_flags = hidpp10.get_notification_flags(receiver) - if notification_flags is not None: - if notification_flags: - notification_names = hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags) - print (" Notifications: 0x%06X = %s" % (notification_flags, ', '.join(notification_names))) - else: - print (" Notifications: (none)") - - activity = receiver.read_register(hidpp10.REGISTERS.devices_activity) - if activity: - activity = [(d, ord(activity[d - 1:d])) for d in range(1, receiver.max_devices)] - activity_text = ', '.join(('%d=%d' % (d, a)) for d, a in activity if a > 0) - print (" Device activity counters:", activity_text or '(empty)') - - -def _print_device(dev, verbose=False): - assert dev - state = '' if dev.ping() else 'offline' - - if not verbose: - print ("%d: %s [%s:%s]" % (dev.number, dev.name, dev.codename, dev.serial), state) - return - - print ("%d: %s" % (dev.number, dev.name)) - print (" Codename :", dev.codename) - print (" Kind :", dev.kind) - print (" Wireless PID :", dev.wpid) - if dev.protocol: - print (" Protocol : HID++ %1.1f" % dev.protocol) - else: - print (" Protocol : unknown (device is offline)") - print (" Polling rate :", dev.polling_rate, "ms") - print (" Serial number:", dev.serial) - for fw in dev.firmware: - print (" %11s:" % fw.kind, (fw.name + ' ' + fw.version).strip()) - - if dev.power_switch_location: - print (" The power switch is located on the %s." % dev.power_switch_location) - - from logitech_receiver import hidpp10, hidpp20, special_keys - - if dev.online: - notification_flags = hidpp10.get_notification_flags(dev) - if notification_flags is not None: - if notification_flags: - notification_names = hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags) - print (" Notifications: 0x%06X = %s." % (notification_flags, ', '.join(notification_names))) - else: - print (" Notifications: (none).") - - if dev.online: - if dev.features: - print (" Supports %d HID++ 2.0 features:" % len(dev.features)) - for index, feature in enumerate(dev.features): - feature = dev.features[index] - flags = dev.request(0x0000, feature.bytes(2)) - flags = 0 if flags is None else ord(flags[1:2]) - flags = hidpp20.FEATURE_FLAG.flag_names(flags) - print (" %2d: %-22s {%04X} %s" % (index, feature, feature, ', '.join(flags))) - - if dev.online: - if dev.keys: - print (" Has %d reprogrammable keys:" % len(dev.keys)) - for k in dev.keys: - flags = special_keys.KEY_FLAG.flag_names(k.flags) - print (" %2d: %-26s => %-27s %s" % (k.index, k.key, k.task, ', '.join(flags))) - - if dev.online: - battery = hidpp20.get_battery(dev) - if battery is None: - battery = hidpp10.get_battery(dev) - if battery is not None: - from logitech_receiver.common import NamedInt as _NamedInt - level, status = battery - if isinstance(level, _NamedInt): - text = str(level) - else: - text = '%d%%' % level - print (" Battery: %s, %s," % (text, status)) - else: - print (" Battery status unavailable.") - else: - print (" Battery status is unknown (device is offline).") - -# -# -# - -def show_devices(receiver, args): - if args.device == 'all': - _print_receiver(receiver, args.verbose) - for dev in receiver: - if args.verbose: - print ("") - _print_device(dev, args.verbose) - else: - dev = _find_device(receiver, args.device, True) - if dev is receiver: - _print_receiver(receiver, args.verbose) - else: - _print_device(dev, args.verbose) - - -def pair_device(receiver, args): - # get all current devices - known_devices = [dev.number for dev in receiver] - - from logitech_receiver import base, hidpp10, status, notifications - receiver.status = status.ReceiverStatus(receiver, lambda *args, **kwargs: None) - - # check if it's necessary to set the notification flags - old_notification_flags = hidpp10.get_notification_flags(receiver) or 0 - if not (old_notification_flags & hidpp10.NOTIFICATION_FLAG.wireless): - hidpp10.set_notification_flags(receiver, old_notification_flags | hidpp10.NOTIFICATION_FLAG.wireless) - - class HandleWithNotificationHook(int): - def notifications_hook(self, n): - assert n - if n.devnumber == 0xFF: - notifications.process(receiver, n) - elif n.sub_id == 0x41 and n.address == 0x04: - if n.devnumber not in known_devices: - receiver.status.new_device = receiver[n.devnumber] - - timeout = 20 # seconds - receiver.handle = HandleWithNotificationHook(receiver.handle) - receiver.set_lock(False, timeout=timeout) - print ("Pairing: turn your new device on (timing out in", timeout, "seconds).") - - # the lock-open notification may come slightly later, wait for it a bit - from time import time as timestamp - pairing_start = timestamp() - patience = 5 # seconds - - while receiver.status.lock_open or timestamp() - pairing_start < patience: - n = base.read(receiver.handle) - if n: - n = base.make_notification(*n) - if n: - receiver.handle.notifications_hook(n) - - if not (old_notification_flags & hidpp10.NOTIFICATION_FLAG.wireless): - # only clear the flags if they weren't set before, otherwise a - # concurrently running Solaar app might stop working properly - hidpp10.set_notification_flags(receiver, old_notification_flags) - - if receiver.status.new_device: - dev = receiver.status.new_device - print ("Paired device %d: %s [%s:%s:%s]" % (dev.number, dev.name, dev.wpid, dev.codename, dev.serial)) - else: - error = receiver.status[status.KEYS.ERROR] or 'no device detected?' - _fail(error) - - -def unpair_device(receiver, args): - dev = _find_device(receiver, args.device) - - # query these now, it's last chance to get them - number, name, codename, serial = dev.number, dev.name, dev.codename, dev.serial - try: - del receiver[number] - print ("Unpaired %d: %s [%s:%s]" % (number, name, codename, serial)) - except Exception as e: - _fail("failed to unpair device %s: %s" % (dev.name, e)) - - -def config_device(receiver, args): - dev = _find_device(receiver, args.device) - # if dev is receiver: - # _fail("no settings for the receiver") - - if not dev.settings: - _fail("no settings for %s" % dev.name) - - if not args.setting: - print ("[%s:%s]" % (dev.serial, dev.kind)) - print ("#", dev.name) - for s in dev.settings: - print ("") - print ("# %s" % s.label) - if s.choices: - print ("# possible values: one of [", ', '.join(str(v) for v in s.choices), "], or higher/lower/highest/max/lowest/min") - else: - print ("# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0") - value = s.read() - if value is None: - print ("# %s = ? (failed to read from device)" % s.name) - else: - print (s.name, "=", value) - return - - setting = None - for s in dev.settings: - if args.setting.lower() == s.name.lower(): - setting = s - break - if setting is None: - _fail("no setting '%s' for %s" % (args.setting, dev.name)) - - if args.value is None: - result = setting.read() - if result is None: - _fail("failed to read '%s'" % setting.name) - print ("%s = %s" % (setting.name, setting.read())) - return - - from logitech_receiver import settings as _settings - - if setting.kind == _settings.KIND.toggle: - value = args.value - try: - value = bool(int(value)) - except: - if value.lower() in ('1', 'true', 'yes', 'on', 't', 'y'): - value = True - elif value.lower() in ('0', 'false', 'no', 'off', 'f', 'n'): - value = False - else: - _fail("don't know how to interpret '%s' as boolean" % value) - - elif setting.choices: - value = args.value.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) - - if value == 'lower': - lower_values = setting.choices[:old_value] - value = lower_values[-1] if lower_values else setting.choices[:][0] - elif value == 'higher': - higher_values = setting.choices[old_value + 1:] - value = higher_values[0] if higher_values else setting.choices[:][-1] - elif value in ('highest', 'max'): - value = setting.choices[:][-1] - elif value in ('lowest', 'min'): - value = setting.choices[:][0] - elif value not in setting.choices: - _fail("possible values for '%s' are: [%s]" % (setting.name, ', '.join(str(v) for v in setting.choices))) - value = setting.choices[value] - - else: - raise NotImplemented - - result = setting.write(value) - if result is None: - _fail("failed to set '%s' = '%s' [%r]" % (setting.name, value, value)) - print ("%s = %s" % (setting.name, result)) - -# -# -# - -def _parse_arguments(): - from argparse import ArgumentParser - arg_parser = 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)') - arg_parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__) - arg_parser.add_argument('-D', '--hidraw', action='store', dest='hidraw_path', metavar='PATH', - help='unifying receiver to use; the first detected receiver if unspecified. Example: /dev/hidraw2') - - subparsers = arg_parser.add_subparsers(title='commands') - - sp = subparsers.add_parser('show', help='show information about paired devices') - sp.add_argument('device', nargs='?', default='all', - help='device to show information about; may be a device number (1..6), a device serial, ' - 'at least 3 characters of a device\'s name, "receiver", or "all" (the default)') - sp.add_argument('-v', '--verbose', action='store_true', - help='print all available information about the inspected device(s)') - sp.set_defaults(cmd=show_devices) - - sp = subparsers.add_parser('config', help='read/write device-specific settings', - epilog='Please note that configuration only works on active devices.') - sp.add_argument('device', - help='device to configure; may be a device number (1..6), a device serial, ' - 'or at least 3 characters of a device\'s name') - sp.add_argument('setting', nargs='?', - help='device-specific setting; leave empty to list available settings') - sp.add_argument('value', nargs='?', - help='new value for the setting') - sp.set_defaults(cmd=config_device) - - sp = subparsers.add_parser('pair', help='pair a new device', - epilog='The Logitech Unifying Receiver supports up to 6 paired devices at the same time.') - sp.set_defaults(cmd=pair_device) - - sp = subparsers.add_parser('unpair', help='unpair a device') - sp.add_argument('device', - help='device to unpair; may be a device number (1..6), a device serial, ' - 'or at least 3 characters of a device\'s name.') - sp.set_defaults(cmd=unpair_device) - - args = arg_parser.parse_args() - - # Python 3 has an undocumented 'feature' that breaks parsing empty args - # http://bugs.python.org/issue16308 - if not 'cmd' in args: - arg_parser.print_usage(sys.stderr) - sys.stderr.write('%s: error: too few arguments\n' % NAME.lower()) - sys.exit(2) - - if args.debug > 0: - log_level = logging.WARNING - 10 * args.debug - log_format='%(asctime)s,%(msecs)03d %(levelname)8s %(name)s: %(message)s' - logging.basicConfig(level=max(log_level, logging.DEBUG), format=log_format, datefmt='%H:%M:%S') - else: - logging.root.addHandler(logging.NullHandler()) - logging.root.setLevel(logging.ERROR) - - return args - - -def main(): - _require('pyudev', 'python-pyudev') - args = _parse_arguments() - receiver = _receiver(args.hidraw_path) - args.cmd(receiver, args) - -if __name__ == '__main__': - main() diff --git a/lib/solaar/cli/__init__.py b/lib/solaar/cli/__init__.py new file mode 100644 index 00000000..a339acf3 --- /dev/null +++ b/lib/solaar/cli/__init__.py @@ -0,0 +1,154 @@ +# -*- 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 + + +import argparse as _argparse +import sys as _sys + +from logging import getLogger, DEBUG as _DEBUG +_log = getLogger(__name__) +del getLogger + + +from solaar import NAME + +# +# +# + +def _create_parser(): + parser = _argparse.ArgumentParser(prog=NAME.lower(), add_help=False, + epilog='For details on individual actions, run `%s --help`.' % NAME.lower()) + subparsers = parser.add_subparsers(title='actions', + help='optional action to perform') + + sp = subparsers.add_parser('show', help='show information about devices') + sp.add_argument('device', nargs='?', default='all', + help='device to show information about; may be a device number (1..6), a serial, ' + 'a substring of a device\'s name, or "all" (the default)') + sp.set_defaults(action='show') + + sp = subparsers.add_parser('config', help='read/write device-specific settings', + epilog='Please note that configuration only works on active devices.') + sp.add_argument('device', + help='device to configure; may be a device number (1..6), a device serial, ' + 'or at least 3 characters of a device\'s name') + sp.add_argument('setting', nargs='?', + help='device-specific setting; leave empty to list available settings') + sp.add_argument('value', nargs='?', + help='new value for the setting') + sp.set_defaults(action='config') + + sp = subparsers.add_parser('pair', help='pair a new device', + epilog='The Logitech Unifying Receiver supports up to 6 paired devices at the same time.') + sp.add_argument('receiver', nargs='?', + help='select a certain receiver when more than one is present') + sp.set_defaults(action='pair') + + sp = subparsers.add_parser('unpair', help='unpair a device') + sp.add_argument('device', + help='device to unpair; may be a device number (1..6), a serial, ' + 'or a substring of a device\'s name.') + sp.set_defaults(action='unpair') + + return parser, subparsers.choices + + +_cli_parser, actions = _create_parser() + + +def _receivers(): + from logitech_receiver import Receiver + from logitech_receiver.base import receivers + for dev_info in receivers(): + try: + r = Receiver.open(dev_info) + if _log.isEnabledFor(_DEBUG): + _log.debug("[%s] => %s", dev_info.path, r) + if r: + yield r + except Exception as e: + _log.exception('opening ' + str(dev_info)) + _sys.exit("%s: error: %s" % (NAME, str(e))) + + +def _find_receiver(receivers, name): + assert receivers + assert name + + for r in receivers: + if name in r.name.lower() or name == r.serial.lower(): + return r + + +def _find_device(receivers, name): + assert receivers + assert name + + number = None + if len(name) == 1: + try: + number = int(name) + except: + pass + else: + assert not (number < 0) + if number > 6: number = None + + for r in receivers: + if number and number <= r.max_devices: + dev = r[number] + if dev: + return dev + + for dev in r: + if (name == dev.serial.lower() or + name == dev.codename.lower() or + name == str(dev.kind).lower() or + name in dev.name.lower()): + return dev + + raise Exception("no device found matching '%s'" % name) + + +def run(cli_args=None): + if cli_args == 'help': + _cli_parser.print_help() + return + + if cli_args: + action = cli_args[0] + args = _cli_parser.parse_args(cli_args) + else: + args = _cli_parser.parse_args() + action = args.action + assert action in actions + + try: + c = list(_receivers()) + if not c: + raise Exception('Logitech receiver not found') + + from importlib import import_module + m = import_module('.' + action, package=__name__) + m.run(c, args, _find_receiver, _find_device) + except Exception as e: + _sys.exit('%s: error: %s' % (NAME.lower(), e)) diff --git a/lib/solaar/cli/config.py b/lib/solaar/cli/config.py new file mode 100644 index 00000000..d26522ec --- /dev/null +++ b/lib/solaar/cli/config.py @@ -0,0 +1,120 @@ +# -*- 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 configuration as _configuration +from logitech_receiver import settings as _settings + + +def _print_setting(s, verbose=True): + print ('#', s.label) + if verbose: + print ('#', s.description.replace('\n', ' ')) + if s.kind == _settings.KIND.toggle: + print ('# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0') + elif s.choices: + print ('# possible values: one of [', ', '.join(str(v) for v in s.choices), '], or higher/lower/highest/max/lowest/min') + else: + # wtf? + pass + value = s.read(cached=False) + if value is None: + print (s.name, '= ? (failed to read from device)') + else: + print (s.name, '= %r' % value) + + +def run(receivers, args, find_receiver, find_device): + assert receivers + assert args.device + + device_name = args.device.lower() + dev = find_device(receivers, device_name) + + if not dev.ping(): + raise Exception('%s is offline' % dev.name) + + if not dev.settings: + raise Exception('no settings for %s' % dev.name) + + _configuration.attach_to(dev) + + if not args.setting: + print (dev.name, '(%s) [%s:%s]' % (dev.codename, dev.wpid, dev.serial)) + for s in dev.settings: + print ('') + _print_setting(s) + return + + setting_name = args.setting.lower() + setting = None + for s in dev.settings: + if setting_name == s.name.lower(): + setting = s + break + if setting is None: + raise Exception("no setting '%s' for %s" % (args.setting, dev.name)) + + if args.value is None: + _print_setting(setting) + return + + if setting.kind == _settings.KIND.toggle: + value = args.value + try: + value = bool(int(value)) + except: + if value.lower() in ('true', 'yes', 'on', 't', 'y'): + value = True + elif value.lower() in ('false', 'no', 'off', 'f', 'n'): + value = False + else: + raise Exception("don't know how to interpret '%s' as boolean" % value) + + elif setting.choices: + value = args.value.lower() + + if value in ('higher', 'lower'): + old_value = setting.read() + if old_value is None: + raise Exception("could not read current value of '%s'" % setting.name) + + if value == 'lower': + lower_values = setting.choices[:old_value] + value = lower_values[-1] if lower_values else setting.choices[:][0] + elif value == 'higher': + higher_values = setting.choices[old_value + 1:] + value = higher_values[0] if higher_values else setting.choices[:][-1] + elif value in ('highest', 'max'): + value = setting.choices[:][-1] + elif value in ('lowest', 'min'): + value = setting.choices[:][0] + elif value not in setting.choices: + raise Exception("possible values for '%s' are: [%s]" % (setting.name, ', '.join(str(v) for v in setting.choices))) + value = setting.choices[value] + + else: + raise NotImplemented + + result = setting.write(value) + if result is None: + raise Exception("failed to set '%s' = '%s' [%r]" % (setting.name, value, value)) + _print_setting(setting, False) diff --git a/lib/solaar/cli/pair.py b/lib/solaar/cli/pair.py new file mode 100644 index 00000000..f9e582f2 --- /dev/null +++ b/lib/solaar/cli/pair.py @@ -0,0 +1,91 @@ +# -*- 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 time import time as _timestamp + +from logitech_receiver import ( + base as _base, + hidpp10 as _hidpp10, + status as _status, + notifications as _notifications, + ) + + +def run(receivers, args, find_receiver, _ignore): + assert receivers + + if args.receiver: + receiver_name = args.receiver.lower() + receiver = find_receiver(receiver_name) + if not receiver: + raise Exception("no receiver found matching '%s'" % receiver_name) + else: + receiver = receivers[0] + + assert receiver + receiver.status = _status.ReceiverStatus(receiver, lambda *args, **kwargs: None) + + # check if it's necessary to set the notification flags + old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0 + if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless): + _hidpp10.set_notification_flags(receiver, old_notification_flags | _hidpp10.NOTIFICATION_FLAG.wireless) + + # get all current devices + known_devices = [dev.number for dev in receiver] + + class _HandleWithNotificationHook(int): + def notifications_hook(self, n): + assert n + if n.devnumber == 0xFF: + _notifications.process(receiver, n) + elif n.sub_id == 0x41 and n.address == 0x04: + if n.devnumber not in known_devices: + receiver.status.new_device = receiver[n.devnumber] + + timeout = 20 # seconds + receiver.handle = _HandleWithNotificationHook(receiver.handle) + + receiver.set_lock(False, timeout=timeout) + print ('Pairing: turn your new device on (timing out in', timeout, 'seconds).') + + # the lock-open notification may come slightly later, wait for it a bit + pairing_start = _timestamp() + patience = 5 # seconds + + while receiver.status.lock_open or _timestamp() - pairing_start < patience: + n = _base.read(receiver.handle) + if n: + n = _base.make_notification(*n) + if n: + receiver.handle.notifications_hook(n) + + if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless): + # only clear the flags if they weren't set before, otherwise a + # concurrently running Solaar app might stop working properly + _hidpp10.set_notification_flags(receiver, old_notification_flags) + + if receiver.status.new_device: + dev = receiver.status.new_device + print ('Paired device %d: %s (%s) [%s:%s]' % (dev.number, dev.name, dev.codename, dev.wpid, dev.serial)) + else: + error = receiver.status.get(_status.KEYS.ERROR) or 'no device detected?' + raise Exception("pairing failed: %s" % error) diff --git a/lib/solaar/cli/show.py b/lib/solaar/cli/show.py new file mode 100644 index 00000000..906cb40c --- /dev/null +++ b/lib/solaar/cli/show.py @@ -0,0 +1,146 @@ +# -*- 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 logitech_receiver import ( + hidpp10 as _hidpp10, + hidpp20 as _hidpp20, + special_keys as _special_keys, + ) + + +def _print_receiver(receiver): + paired_count = receiver.count() + + print ('Unifying Receiver') + print (' Device path :', receiver.path) + print (' USB id : 046d:%s' % receiver.product_id) + print (' Serial :', receiver.serial) + for f in receiver.firmware: + print (' %-11s: %s' % (f.kind, f.version)) + + print (' Has', paired_count, 'paired device(s) out of a maximum of %d.' % receiver.max_devices) + + notification_flags = _hidpp10.get_notification_flags(receiver) + if notification_flags is not None: + if notification_flags: + notification_names = _hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags) + print (' Notifications: %s (0x%06X)' % (', '.join(notification_names), notification_flags)) + else: + print (' Notifications: (none)') + + activity = receiver.read_register(_hidpp10.REGISTERS.devices_activity) + if activity: + activity = [(d, ord(activity[d - 1:d])) for d in range(1, receiver.max_devices)] + activity_text = ', '.join(('%d=%d' % (d, a)) for d, a in activity if a > 0) + print (' Device activity counters:', activity_text or '(empty)') + + +def _print_device(dev): + assert dev + # check if the device is online + dev.ping() + + print (' %d: %s' % (dev.number, dev.name)) + print (' Codename :', dev.codename) + print (' Kind :', dev.kind) + print (' Wireless PID :', dev.wpid) + if dev.protocol: + print (' Protocol : HID++ %1.1f' % dev.protocol) + else: + print (' Protocol : unknown (device is offline)') + if dev.polling_rate: + print (' Polling rate :', dev.polling_rate, 'ms (%dHz)' % (1000 // dev.polling_rate)) + print (' Serial number:', dev.serial) + for fw in dev.firmware: + print (' %11s:' % fw.kind, (fw.name + ' ' + fw.version).strip()) + + if dev.power_switch_location: + print (' The power switch is located on the %s.' % dev.power_switch_location) + + if dev.online: + notification_flags = _hidpp10.get_notification_flags(dev) + if notification_flags is not None: + if notification_flags: + notification_names = _hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags) + print (' Notifications: %s (0x%06X).' % (', '.join(notification_names), notification_flags)) + else: + print (' Notifications: (none).') + + if dev.online and dev.features: + print (' Supports %d HID++ 2.0 features:' % len(dev.features)) + for index, feature in enumerate(dev.features): + feature = dev.features[index] + flags = dev.request(0x0000, feature.bytes(2)) + flags = 0 if flags is None else ord(flags[1:2]) + flags = _hidpp20.FEATURE_FLAG.flag_names(flags) + print (' %2d: %-22s {%04X} %s' % (index, feature, feature, ', '.join(flags))) + + if dev.online and dev.keys: + print (' Has %d reprogrammable keys:' % len(dev.keys)) + for k in dev.keys: + flags = _special_keys.KEY_FLAG.flag_names(k.flags) + print (' %2d: %-26s => %-27s %s' % (k.index, k.key, k.task, ', '.join(flags))) + + if dev.online: + battery = _hidpp20.get_battery(dev) + if battery is None: + battery = _hidpp10.get_battery(dev) + if battery is not None: + from logitech_receiver.common import NamedInt as _NamedInt + level, status = battery + if isinstance(level, _NamedInt): + text = str(level) + else: + text = '%d%%' % level + print (' Battery: %s, %s.' % (text, status)) + else: + print (' Battery status unavailable.') + else: + print (' Battery: unknown (device is offline).') + + +def run(receivers, args, find_receiver, find_device): + assert receivers + assert args.device + + device_name = args.device.lower() + + if device_name == 'all': + for r in receivers: + _print_receiver(r) + count = r.count() + for dev in r: + print ('') + _print_device(dev) + count -= 1 + if count == 0: + break + return + + dev = find_receiver(receivers, device_name) + if dev: + _print_receiver(dev) + return + + dev = find_device(receivers, device_name) + assert dev + _print_device(dev) diff --git a/lib/solaar/cli/unpair.py b/lib/solaar/cli/unpair.py new file mode 100644 index 00000000..b035b413 --- /dev/null +++ b/lib/solaar/cli/unpair.py @@ -0,0 +1,36 @@ +# -*- 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 + + +def run(receivers, args, find_receiver, find_device): + assert receivers + assert args.device + + device_name = args.device.lower() + dev = find_device(receivers, device_name) + + # query these now, it's last chance to get them + try: + number, codename, wpid, serial = dev.number, dev.codename, dev.wpid, dev.serial + del dev.receiver[number] + print ('Unpaired %d: %s (%s) [%s:%s]' % (number, dev.name, codename, wpid, serial)) + except Exception as e: + raise Exception('failed to unpair device %s: %s' % (dev.name, e)) diff --git a/lib/solaar/gtk.py b/lib/solaar/gtk.py index 66a077f4..aabd35f5 100644 --- a/lib/solaar/gtk.py +++ b/lib/solaar/gtk.py @@ -23,6 +23,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera from solaar import __version__, NAME import solaar.i18n as _i18n +import solaar.cli as _cli # # @@ -40,10 +41,18 @@ def _parse_arguments(): 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__) + arg_parser.add_argument('--help-actions', action='store_true', + help='print help for the optional actions') + arg_parser.add_argument('action', nargs=argparse.REMAINDER, choices=_cli.actions, + help='optional actions to perform') + args = arg_parser.parse_args() + if args.help_actions: + return 'help' + import logging if args.debug > 0: log_level = logging.WARNING - 10 * args.debug @@ -53,34 +62,33 @@ def _parse_arguments(): logging.root.addHandler(logging.NullHandler()) logging.root.setLevel(logging.ERROR) + if args.action: + return args.action + if logging.root.isEnabledFor(logging.INFO): logging.info("language %s (%s), translations path %s", _i18n.language, _i18n.encoding, _i18n.path) - return args - def main(): _require('pyudev', 'python-pyudev') - _require('gi.repository', 'python-gi') - _require('gi.repository.Gtk', 'gir1.2-gtk-3.0') - _parse_arguments() # handle ^C in console import signal signal.signal(signal.SIGINT, signal.SIG_DFL) + cli_action = _parse_arguments() + if cli_action: + return _cli.run(cli_action) + + _require('gi.repository', 'python-gi') + _require('gi.repository.Gtk', 'gir1.2-gtk-3.0') + try: import solaar.ui as ui - ui.init() - import solaar.listener as listener listener.setup_scanner(ui.status_changed, ui.error_dialog) - listener.start_all() - # main UI event loop - ui.run_loop() - - listener.stop_all() + ui.run_loop(listener.start_all, listener.stop_all) except Exception as e: import sys sys.exit('%s: error: %s' % (NAME.lower(), e)) diff --git a/lib/solaar/ui/__init__.py b/lib/solaar/ui/__init__.py index 5a0340e3..68a7e08b 100644 --- a/lib/solaar/ui/__init__.py +++ b/lib/solaar/ui/__init__.py @@ -20,7 +20,7 @@ from __future__ import absolute_import, division, print_function, unicode_literals -from logging import getLogger, DEBUG as _DEBUG, INFO as _INFO +from logging import getLogger, DEBUG as _DEBUG _log = getLogger(__name__) del getLogger @@ -37,27 +37,6 @@ assert Gtk.get_major_version() > 2, 'Solaar requires Gtk 3 python bindings' GLib.threads_init() -def _init_application(): - 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", dbus_path, registered) - # assert registered, "failed to register unique application %s" % app - - # if there is already a running instance, bail out - if app.get_is_remote(): - # pop up the window in the other instance - app.activate() - raise Exception("already running") - - return app - -application = _init_application() - # # # @@ -72,7 +51,7 @@ def _error_dialog(reason, object): _("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.") % (object.name, object.receiver.name) + \ + text = _("Failed to unpair %{device} from %{receiver}.").format(device=object.name, receiver=object.receiver.name) + \ '\n\n' + \ _("The receiver returned an error, with no further details.") else: @@ -92,40 +71,13 @@ def error_dialog(reason, object): GLib.idle_add(_error_dialog, reason, object) # -# A separate thread is used to read/write from the device -# so as not to block the main (GUI) thread. +# # -try: - from Queue import Queue -except ImportError: - from queue import Queue -_task_queue = Queue(16) -del Queue - - -from threading import Thread, current_thread as _current_thread - -def _process_async_queue(): - t = _current_thread() - t.alive = True - while t.alive: - function, args, kwargs = _task_queue.get() - if function: - function(*args, **kwargs) - if _log.isEnabledFor(_DEBUG): - _log.debug("stopped") - -_queue_processor = Thread(name='AsyncUI', target=_process_async_queue) -_queue_processor.daemon = True -_queue_processor.alive = False -_queue_processor.start() - -del Thread - +_task_runner = None def async(function, *args, **kwargs): - task = (function, args, kwargs) - _task_queue.put(task) + if _task_runner: + _task_runner(function, *args, **kwargs) # # @@ -133,36 +85,73 @@ def async(function, *args, **kwargs): from . import notify, tray, window -def init(): + +def _startup(app, startup_hook): + if _log.isEnabledFor(_DEBUG): + _log.debug("startup registered=%s, remote=%s", app.get_is_registered(), app.get_is_remote()) + + from solaar.async import TaskRunner as _TaskRunner + global _task_runner + _task_runner = _TaskRunner('AsyncUI') + _task_runner.start() + notify.init() tray.init(lambda _ignore: window.destroy()) window.init() -def run_loop(): - def _activate(app): - assert app == application - if app.get_windows(): - window.popup() - else: - app.add_window(window._window) + startup_hook() - def _shutdown(app): - # stop the async UI processor - _queue_processor.alive = False - async(None) - tray.destroy() - notify.uninit() +def _activate(app): + if _log.isEnabledFor(_DEBUG): + _log.debug("activate") + if app.get_windows(): + window.popup() + else: + app.add_window(window._window) + +def _command_line(app, command_line): + if _log.isEnabledFor(_DEBUG): + _log.debug("command_line %s", command_line.get_arguments()) + + return 0 + + +def _shutdown(app, shutdown_hook): + if _log.isEnabledFor(_DEBUG): + _log.debug("shutdown") + + shutdown_hook() + + # stop the async UI processor + global _task_runner + _task_runner.stop() + _task_runner = None + + tray.destroy() + notify.uninit() + + +def run_loop(startup_hook, shutdown_hook, args=None): + # from gi.repository.Gio import ApplicationFlags as _ApplicationFlags + APP_ID = 'io.github.pwr.solaar' + application = Gtk.Application.new(APP_ID, 0) # _ApplicationFlags.HANDLES_COMMAND_LINE) + + application.connect('startup', _startup, startup_hook) + application.connect('command-line', _command_line) application.connect('activate', _activate) - application.connect('shutdown', _shutdown) - application.run(None) + application.connect('shutdown', _shutdown, shutdown_hook) + + application.run(args) # # # from logitech_receiver.status import ALERT + + def _status_changed(device, alert, reason): assert device is not None if _log.isEnabledFor(_DEBUG): diff --git a/lib/solaar/ui/about.py b/lib/solaar/ui/about.py index efa73d6e..fc56a1c2 100644 --- a/lib/solaar/ui/about.py +++ b/lib/solaar/ui/about.py @@ -65,8 +65,10 @@ def _create(): logging.exception("failed to fully create the about dialog") about.set_translator_credits('\n'.join(( + 'Papoteur, David Geiger, Damien Lallement (français)', 'Adrian Piotrowicz (polski)', 'Daniel Pavel (română)', + 'Daniel Zippert, Emelie Snecker (svensk)', ))) about.set_website('http://pwr.github.io/Solaar/') diff --git a/lib/solaar/ui/icons.py b/lib/solaar/ui/icons.py index 275bd30e..fd836fec 100644 --- a/lib/solaar/ui/icons.py +++ b/lib/solaar/ui/icons.py @@ -69,29 +69,41 @@ def _look_for_application_icons(): del _environ # del _path -_default_theme = Gtk.IconTheme.get_default() -for p in _look_for_application_icons(): - _default_theme.prepend_search_path(p) -if _log.isEnabledFor(_DEBUG): - _log.debug("icon theme paths: %s", _default_theme.get_search_path()) -# -# -# +_default_theme = None +_has_gpm_icons = None +_has_oxygen_icons = None +_has_gnome_icons = None +_has_elementary_icons = None -_has_gpm_icons = _default_theme.has_icon('gpm-battery-020-charging') -_has_oxygen_icons = _default_theme.has_icon('battery-charging-caution') and \ - _default_theme.has_icon('battery-charging-040') -_has_gnome_icons = _default_theme.has_icon('battery-caution-charging') and \ - _default_theme.has_icon('battery-full-charged') -_has_elementary_icons = _default_theme.has_icon('battery-020-charging') -if _log.isEnabledFor(_DEBUG): - _log.debug("detected icon sets: gpm %s, oxygen %s, gnome %s, elementary %s", - _has_gpm_icons, _has_oxygen_icons, _has_gnome_icons, _has_elementary_icons) -if (not _has_gpm_icons and not _has_oxygen_icons and - not _has_gnome_icons and not _has_elementary_icons): - _log.warning("failed to detect a known icon set") +def _init_icon_paths(): + global _default_theme + if _default_theme: + return + + _default_theme = Gtk.IconTheme.get_default() + for p in _look_for_application_icons(): + _default_theme.prepend_search_path(p) + if _log.isEnabledFor(_DEBUG): + _log.debug("icon theme paths: %s", _default_theme.get_search_path()) + + global _has_gpm_icons, _has_oxygen_icons, _has_gnome_icons, _has_elementary_icons + + _has_gpm_icons = _default_theme.has_icon('gpm-battery-020-charging') + _has_oxygen_icons = _default_theme.has_icon('battery-charging-caution') and \ + _default_theme.has_icon('battery-charging-040') + _has_gnome_icons = _default_theme.has_icon('battery-caution-charging') and \ + _default_theme.has_icon('battery-full-charged') + _has_elementary_icons = _default_theme.has_icon('battery-020-charging') + + if _log.isEnabledFor(_DEBUG): + _log.debug("detected icon sets: gpm %s, oxygen %s, gnome %s, elementary %s", + _has_gpm_icons, _has_oxygen_icons, _has_gnome_icons, _has_elementary_icons) + + if (not _has_gpm_icons and not _has_oxygen_icons and + not _has_gnome_icons and not _has_elementary_icons): + _log.warning("failed to detect a known icon set") # # @@ -106,6 +118,8 @@ def battery(level=None, charging=False): return icon_name def _battery_icon_name(level, charging): + _init_icon_paths() + if level is None or level < 0: return 'gpm-battery-missing' \ if _has_gpm_icons and _default_theme.has_icon('gpm-battery-missing') \ @@ -186,6 +200,8 @@ def device_icon_set(name='_', kind=None): def device_icon_file(name, kind=None, size=_LARGE_SIZE): + _init_icon_paths() + icon_set = device_icon_set(name, kind) assert icon_set for n in reversed(icon_set.names): @@ -194,6 +210,8 @@ def device_icon_file(name, kind=None, size=_LARGE_SIZE): def device_icon_name(name, kind=None): + _init_icon_paths() + icon_set = device_icon_set(name, kind) assert icon_set for n in reversed(icon_set.names): @@ -202,6 +220,8 @@ def device_icon_name(name, kind=None): def icon_file(name, size=_LARGE_SIZE): + _init_icon_paths() + if _default_theme.has_icon(name): theme_icon = _default_theme.lookup_icon(name, size, 0) file_name = theme_icon.get_filename() diff --git a/lib/solaar/ui/notify.py b/lib/solaar/ui/notify.py index 81b0f9a1..48391a01 100644 --- a/lib/solaar/ui/notify.py +++ b/lib/solaar/ui/notify.py @@ -112,8 +112,14 @@ try: 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")))) + if reason: + message = reason + elif dev.status is None: + message = _("unpaired") + elif bool(dev.status): + message = dev.status.__str__() or _("connected") + else: + message = _("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 --git a/lib/solaar/ui/tray.py b/lib/solaar/ui/tray.py index 05149da6..3dd76f4c 100644 --- a/lib/solaar/ui/tray.py +++ b/lib/solaar/ui/tray.py @@ -19,7 +19,7 @@ from __future__ import absolute_import, division, print_function, unicode_literals -from logging import getLogger, DEBUG as _DEBUG, INFO as _INFO +from logging import getLogger, DEBUG as _DEBUG _log = getLogger(__name__) del getLogger @@ -67,82 +67,88 @@ def _create_menu(quit_handler): return menu +_last_scroll = 0 +def _scroll(tray_icon, event, direction=None): + if direction is None: + direction = event.direction + now = event.time / 1000.0 + else: + now = None + + if direction != ScrollDirection.UP and direction != ScrollDirection.DOWN: + # ignore all other directions + return + + if len(_devices_info) < 4: + # don't bother with scrolling when there's only one receiver + # with only one device (3 = [receiver, device, separator]) + return + + # scroll events come way too fast (at least 5-6 at once) + # so take a little break between them + global _last_scroll + now = now or _timestamp() + if now - _last_scroll < 0.33: # seconds + return + _last_scroll = now + + # if _log.isEnabledFor(_DEBUG): + # _log.debug("scroll direction %s", direction) + + global _picked_device + candidate = None + + if _picked_device is None: + for info in _devices_info: + # pick first peripheral found + if info[1] is not None: + candidate = info + break + else: + found = False + for info in _devices_info: + if not info[1]: + # only conside peripherals + continue + # compare peripherals + if info[0:2] == _picked_device[0:2]: + if direction == ScrollDirection.UP and candidate: + # select previous device + break + found = True + else: + if found: + candidate = info + if direction == ScrollDirection.DOWN: + break + # if direction is up, but no candidate found before _picked, + # let it run through all candidates, will get stuck with the last one + else: + if direction == ScrollDirection.DOWN: + # only use the first one, in case no candidates are after _picked + if candidate is None: + candidate = info + else: + candidate = info + + # if the last _picked_device is gone, clear it + # the candidate will be either the first or last one remaining, + # depending on the scroll direction + if not found: + _picked_device = None + + _picked_device = candidate or _picked_device + if _log.isEnabledFor(_DEBUG): + _log.debug("scroll: picked %s", _picked_device) + _update_tray_icon() + + try: # raise ImportError from gi.repository import AppIndicator3 - if _log.isEnabledFor(_INFO): - _log.info("using AppIndicator3") - - _last_scroll = 0 - def _scroll(ind, _ignore, direction): - if direction != ScrollDirection.UP and direction != ScrollDirection.DOWN: - # ignore all other directions - return - - if len(_devices_info) < 4: - # don't bother with scrolling when there's only one receiver - # with only one device (3 = [receiver, device, separator]) - return - - # scroll events come way too fast (at least 5-6 at once) - # so take a little break between them - global _last_scroll - now = _timestamp() - if now - _last_scroll < 0.33: # seconds - return - _last_scroll = now - - # if _log.isEnabledFor(_DEBUG): - # _log.debug("scroll direction %s", direction) - - global _picked_device - candidate = None - - if _picked_device is None: - for info in _devices_info: - # pick first peripheral found - if info[1] is not None: - candidate = info - break - else: - found = False - for info in _devices_info: - if not info[1]: - # only conside peripherals - continue - # compare peripherals - if info[0:2] == _picked_device[0:2]: - if direction == ScrollDirection.UP and candidate: - # select previous device - break - found = True - else: - if found: - candidate = info - if direction == ScrollDirection.DOWN: - break - # if direction is up, but no candidate found before _picked, - # let it run through all candidates, will get stuck with the last one - else: - if direction == ScrollDirection.DOWN: - # only use the first one, in case no candidates are after _picked - if candidate is None: - candidate = info - else: - candidate = info - - # if the last _picked_device is gone, clear it - # the candidate will be either the first or last one remaining, - # depending on the scroll direction - if not found: - _picked_device = None - - _picked_device = candidate or _picked_device - if _log.isEnabledFor(_DEBUG): - _log.debug("scroll: picked %s", _picked_device) - _update_tray_icon() - + if _log.isEnabledFor(_DEBUG): + _log.debug("using AppIndicator3") def _create(menu): theme_paths = Gtk.IconTheme.get_default().get_search_path() @@ -201,8 +207,8 @@ try: except ImportError: - if _log.isEnabledFor(_INFO): - _log.info("using StatusIcon") + if _log.isEnabledFor(_DEBUG): + _log.debug("using StatusIcon") def _create(menu): icon = Gtk.StatusIcon.new_from_icon_name(_icons.TRAY_INIT) @@ -210,10 +216,9 @@ except ImportError: icon.set_title(NAME) icon.set_tooltip_text(NAME) icon.connect('activate', _window_toggle) - - icon.connect('popup_menu', - lambda icon, button, time: - menu.popup(None, None, icon.position_menu, icon, button, time)) + icon.connect('scroll-event', _scroll) + icon.connect('popup-menu', + lambda icon, button, time: menu.popup(None, None, icon.position_menu, icon, button, time)) return icon @@ -506,7 +511,7 @@ def update(device=None): menu_items[no_receivers_index + 1].set_visible(not _devices_info) global _picked_device - if not _picked_device and device is not None and device.kind is not None: + if (not _picked_device or _last_scroll == 0) and device is not None and device.kind is not None: # if it's just a receiver update, it's unlikely the picked device would change _picked_device = _pick_device_with_lowest_battery() diff --git a/lib/solaar/ui/window.py b/lib/solaar/ui/window.py index 496082e6..e0401754 100644 --- a/lib/solaar/ui/window.py +++ b/lib/solaar/ui/window.py @@ -76,9 +76,6 @@ _NANO_RECEIVER_TEXT = ( # create UI layout # -Gtk.Window.set_default_icon_name(NAME.lower()) -Gtk.Window.set_default_icon_from_file(_icons.icon_file(NAME.lower())) - def _new_button(label, icon_name=None, icon_size=_NORMAL_BUTTON_ICON_SIZE, tooltip=None, toggle=False, clicked=None): if toggle: b = Gtk.ToggleButton() @@ -630,7 +627,7 @@ def _update_device_panel(device, panel, buttons, full=False): panel._battery._icon.set_sensitive(True) if isinstance(battery_level, _NamedInt): - text = str(battery_level) + text = _(str(battery_level)) else: text = '%d%%' % battery_level if is_online: @@ -734,6 +731,9 @@ _window = None def init(): + Gtk.Window.set_default_icon_name(NAME.lower()) + Gtk.Window.set_default_icon_from_file(_icons.icon_file(NAME.lower())) + global _model, _tree, _details, _info, _empty, _window _model = Gtk.TreeStore(*_COLUMN_TYPES) _tree = _create_tree(_model) diff --git a/packaging/debian/changelog b/packaging/debian/changelog index e69f57ba..793a5bad 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -1,3 +1,16 @@ +solaar (0.9.2-2) unstable; urgency=low + + * Fixes for lintian warnings. + + -- Daniel Pavel Wed, 24 Jul 2013 21:47:17 +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 * Release 0.9.1. diff --git a/packaging/debian/control b/packaging/debian/control index 1ae9d7c8..9a8a0d00 100644 --- a/packaging/debian/control +++ b/packaging/debian/control @@ -2,7 +2,7 @@ Source: solaar Section: misc Priority: optional Maintainer: Daniel Pavel -Build-Depends: debhelper (>= 8) +Build-Depends: debhelper (>= 9) Build-Depends-Indep: python, po-debconf X-Python-Version: >= 2.7 X-Python3-Version: >= 3.2 diff --git a/packaging/debian/copyright b/packaging/debian/copyright index 5d82ebbf..66eefda5 100644 --- a/packaging/debian/copyright +++ b/packaging/debian/copyright @@ -42,7 +42,6 @@ License: LGPL Files: share/icons/light_*.png Copyright: Oxygen Icons License: LGPL -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 @@ -55,3 +54,4 @@ Comment: These files were copied from the Oxygen icon theme (weather-*). . You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . +Comment: These files were copied from the Oxygen icon theme (weather-*). diff --git a/po/fr.po b/po/fr.po new file mode 100644 index 00000000..be7ec53d --- /dev/null +++ b/po/fr.po @@ -0,0 +1,453 @@ +# French translations for solaar package. +# Copyright (C) 2013 THE solaar'S COPYRIGHT HOLDER +# This file is distributed under the same license as the solaar package. +# Automatically generated, 2013. +# +msgid "" +msgstr "Project-Id-Version: solaar 0.9.2\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2013-08-08 21:51+0200\n" + "PO-Revision-Date: 2013-08-07 14:43+0200\n" + "Last-Translator: Damien Lallement \n" + "Language-Team: Language: fr\n" + "Language: fr\n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: lib/logitech_receiver/i18n.py:38 +msgid "critical" +msgstr "critique" + +#: lib/logitech_receiver/i18n.py:38 +msgid "empty" +msgstr "vide" + +#: lib/logitech_receiver/i18n.py:38 lib/logitech_receiver/i18n.py:41 +msgid "full" +msgstr "pleine" + +#: lib/logitech_receiver/i18n.py:38 +msgid "good" +msgstr "bonne" + +#: lib/logitech_receiver/i18n.py:38 +msgid "low" +msgstr "faible" + +#: lib/logitech_receiver/i18n.py:41 +msgid "almost full" +msgstr "presque pleine" + +#: lib/logitech_receiver/i18n.py:41 +msgid "discharging" +msgstr "En décharge" + +#: lib/logitech_receiver/i18n.py:41 +msgid "recharging" +msgstr "En charge" + +#: lib/logitech_receiver/i18n.py:42 +msgid "invalid battery" +msgstr "batterie invalide" + +#: lib/logitech_receiver/i18n.py:42 +msgid "slow recharge" +msgstr "recharge lente" + +#: lib/logitech_receiver/i18n.py:42 +msgid "thermal error" +msgstr "erreur thermique" + +#: lib/logitech_receiver/i18n.py:45 +msgid "device not supported" +msgstr "périphérique non pris en charge" + +#: lib/logitech_receiver/i18n.py:45 +msgid "device timeout" +msgstr "le périphérique ne répond pas" + +#: lib/logitech_receiver/i18n.py:45 +msgid "sequence timeout" +msgstr "dépassement de délai" + +#: lib/logitech_receiver/i18n.py:45 +msgid "too many devices" +msgstr "trop de périphériques" + +#: lib/logitech_receiver/i18n.py:48 +msgid "Bootloader" +msgstr "Chargeur d'amorçage" + +#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:535 +msgid "Firmware" +msgstr "Micrologiciel" + +#: lib/logitech_receiver/i18n.py:48 +msgid "Hardware" +msgstr "Matériel" + +#: lib/logitech_receiver/i18n.py:48 +msgid "Other" +msgstr "Autre" + +#: lib/logitech_receiver/notifications.py:67 +msgid "closed" +msgstr "fermé" + +#: lib/logitech_receiver/notifications.py:67 +msgid "open" +msgstr "ouvert" + +#: lib/logitech_receiver/notifications.py:67 +msgid "pairing lock is " +msgstr "le verrou de jumelage est " + +#: lib/logitech_receiver/notifications.py:150 lib/solaar/ui/notify.py:118 +msgid "unpaired" +msgstr "non jumelé" + +#: lib/logitech_receiver/notifications.py:192 +msgid "powered on" +msgstr "sous tension" + +#: lib/logitech_receiver/receiver.py:107 lib/solaar/ui/window.py:622 +msgid "unknown" +msgstr "inconnu" + +#: lib/logitech_receiver/settings_templates.py:77 +msgid "Smooth Scrolling" +msgstr "Défilement fluide" + +#: lib/logitech_receiver/settings_templates.py:78 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Mode haute sensibilité pour défilement vertical avec la molette." + +#: lib/logitech_receiver/settings_templates.py:79 +msgid "Side Scrolling" +msgstr "Défilement latéral" + +#: lib/logitech_receiver/settings_templates.py:80 +msgid "When disabled, pushing the wheel sideways sends custom button " + "events\n" + "instead of the standard side-scrolling events." +msgstr "Lorsque désactivé, l'appui sur les côtés de la molette envoie des " + "évènements\n" + " de bouton personnalisés à la place des évènements standards de " + "défilement latéral." + +#: lib/logitech_receiver/settings_templates.py:82 +msgid "Sensitivity (DPI)" +msgstr "Sensibilité (DPI)" + +#: lib/logitech_receiver/settings_templates.py:83 +msgid "Swap Fx function" +msgstr "Fonction Swap Fx" + +#: lib/logitech_receiver/settings_templates.py:84 +msgid "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." +msgstr "Lorsque défini, les touches F1..F12 activeront leurs fonctions " + "spéciales,\n" + "et vous devez maintenir la touche FN pour activer leurs fonctions " + "standards." + +#: lib/logitech_receiver/settings_templates.py:87 +msgid "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." +msgstr "Lorsque non défini, les touches F1..F12 activeront leurs fonctions " + "standards,\n" + "et vous devez maintenir la touche FN pour activer leurs fonctions " + "spéciales." + +#: lib/logitech_receiver/settings_templates.py:89 +msgid "Hand Detection" +msgstr "Détection manuelle" + +#: lib/logitech_receiver/settings_templates.py:90 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Allume l'éclairage lorsque les mains passent au-dessus du clavier." + +#: lib/logitech_receiver/status.py:98 +msgid "No paired devices." +msgstr "Aucun périphériques jumelés" + +#: lib/logitech_receiver/status.py:99 +msgid "1 paired device." +msgstr "1 périphérique jumelé." + +#: lib/logitech_receiver/status.py:100 +msgid " paired devices." +msgstr " périphériques jumelés." + +#: lib/logitech_receiver/status.py:149 lib/logitech_receiver/status.py:151 +#: lib/solaar/ui/window.py:143 +msgid "Battery" +msgstr "Batterie" + +#: lib/logitech_receiver/status.py:162 lib/solaar/ui/window.py:150 +msgid "Lighting" +msgstr "Éclairage" + +#: lib/logitech_receiver/status.py:162 lib/solaar/ui/window.py:663 +msgid "lux" +msgstr "lux" + +#: lib/solaar/listener.py:95 +msgid "The receiver was unplugged." +msgstr "Le récepteur a été débranché." + +#: lib/solaar/ui/__init__.py:48 +msgid "Permissions error" +msgstr "Erreur de permissions" + +#: lib/solaar/ui/__init__.py:49 +#, python-format +msgid "Found a Logitech Receiver (%s), but did not have permission to open " + "it." +msgstr "Trouvé un récepteur Logitech (%s), mais n'a pas eu l'autorisation de " + "l'ouvrir." + +#: lib/solaar/ui/__init__.py:51 +msgid "If you've just installed Solaar, try removing the receiver and " + "plugging it back in." +msgstr "Si vous venez juste d'installer Solaar, essayez de retirer le " + "récepteur et de le rebrancher." + +#: lib/solaar/ui/__init__.py:53 +msgid "Unpairing failed" +msgstr "La déconnexion a échoué" + +#: lib/solaar/ui/__init__.py:54 +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Impossible de dissocier %{device} de %{receiver}." + +#: lib/solaar/ui/__init__.py:56 +msgid "The receiver returned an error, with no further details." +msgstr "Le récepteur a retourné une erreur, sans plus de détails." + +#: lib/solaar/ui/about.py:39 +msgid "Shows status of devices connected\n" + "through wireless Logitech receivers." +msgstr "Affiche l'état des périphériques connectés\n" + "à travers les récepteurs sans fil Logitech." + +#: lib/solaar/ui/about.py:48 +msgid "GUI design" +msgstr "Interface graphique" + +#: lib/solaar/ui/about.py:49 +msgid "Testing" +msgstr "Testeur" + +#: lib/solaar/ui/about.py:54 +msgid "Logitech documentation" +msgstr "Documentațion Logitech" + +#: lib/solaar/ui/action.py:68 lib/solaar/ui/window.py:316 +msgid "About" +msgstr "À propos de" + +#: lib/solaar/ui/action.py:95 lib/solaar/ui/action.py:98 +#: lib/solaar/ui/window.py:203 +msgid "Unpair" +msgstr "Déconnecter" + +#: lib/solaar/ui/config_panel.py:97 +msgid "Working" +msgstr "En fonctionnement" + +#: lib/solaar/ui/config_panel.py:100 +msgid "Read/write operation failed." +msgstr "Les opérations de lecture/écriture ont échoué." + +#: lib/solaar/ui/notify.py:120 +msgid "connected" +msgstr "connecté" + +#: lib/solaar/ui/notify.py:122 lib/solaar/ui/tray.py:290 +#: lib/solaar/ui/tray.py:295 lib/solaar/ui/window.py:653 +msgid "offline" +msgstr "non connecté" + +#: lib/solaar/ui/pair_window.py:133 +msgid "Pairing failed" +msgstr "Le jumelage a échoué" + +#: lib/solaar/ui/pair_window.py:135 +msgid "Make sure your device is within range, and has a decent battery " + "charge." +msgstr "Assurez-vous que votre périphérique soit à portée, et que sa " + "batterie soit suffisamment chargée." + +#: lib/solaar/ui/pair_window.py:137 +msgid "A new device was detected, but it is not compatible with this " + "receiver." +msgstr "Un nouveau périphérique a été détecté, mais il n'est pas compatible " + "avec ce récepteur." + +#: lib/solaar/ui/pair_window.py:139 +#, python-format +msgid "The receiver only supports %d paired device(s)." +msgstr "Le récepteur supporte seulement %d périphérique(s). " + +#: lib/solaar/ui/pair_window.py:141 +msgid "No further details are available about the error." +msgstr "Aucun autre détail n'est disponible à propos de l'erreur." + +#: lib/solaar/ui/pair_window.py:155 +msgid "Found a new device" +msgstr "Nouveau périphérique disponible" + +#: lib/solaar/ui/pair_window.py:180 +msgid "The wireless link is not encrypted" +msgstr "La connexion sans fil n'est pas chiffrée" + +#: lib/solaar/ui/pair_window.py:197 +msgid "pair new device" +msgstr "Jumele le nouveau périphérique" + +#: lib/solaar/ui/pair_window.py:205 +msgid "Turn on the device you want to pair." +msgstr "Allumez le périphérique que vous souhaitez jumeler." + +#: lib/solaar/ui/pair_window.py:206 +msgid "If the device is already turned on,\n" + "turn if off and on again." +msgstr "Si le périphérique est déjà allumé,\n" + "éteignez le et rallumez-le à nouveau." + +#: lib/solaar/ui/tray.py:55 +msgid "No Logitech receiver found" +msgstr "Aucun récepteur Logitech trouvé" + +#: lib/solaar/ui/tray.py:62 +msgid "Quit" +msgstr "Quitter" + +#: lib/solaar/ui/tray.py:274 +msgid "no receiver" +msgstr "aucun récepteur" + +#: lib/solaar/ui/tray.py:293 +msgid "no status" +msgstr "aucun statut" + +#: lib/solaar/ui/window.py:58 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "La connexion sans fil entre cet appareil et son récepteur est " + "chiffrée." + +#: lib/solaar/ui/window.py:59 +msgid "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." +msgstr "La connexion sans fil entre cet appareil et son récepteur n'est pas " + "chiffrée.\n" + "\n" + "Pour des dispositifs de pointage (souris, trackball, trackpad), " + "c'est une faille mineure de sécurité\n" + "\n" + "Par contre, c'est une faille majeure pour les dispositifs d'entrée " + "de texte (clavier, pavé numérique)\n" + "car les données saisies peuvent être volées à votre insu par des " + "tiers à proximité." + +#: lib/solaar/ui/window.py:67 lib/solaar/ui/window.py:71 +msgid "No device paired" +msgstr "Aucun périphérique jumelé" + +#: lib/solaar/ui/window.py:67 lib/solaar/ui/window.py:68 +#, python-format +msgid "Up to %d devices can be paired to this receiver" +msgstr "Jusqu'à %d périphériques peuvent être jumelés à ce récepteur" + +#: lib/solaar/ui/window.py:68 +msgid "paired devices" +msgstr "périphérique(s) jumelé(s)" + +#: lib/solaar/ui/window.py:72 +msgid "Only one device can be paired to this receiver" +msgstr "Un seul périphérique peut être jumelé à ce récepteur" + +#: lib/solaar/ui/window.py:110 +msgid "Scanning" +msgstr "Balayage" + +#: lib/solaar/ui/window.py:146 +msgid "Wireless Link" +msgstr "Connexion sans fil" + +#: lib/solaar/ui/window.py:179 +msgid "Show Technical Details" +msgstr "Voir les détails techniques" + +#: lib/solaar/ui/window.py:192 +msgid "Pair new device" +msgstr "Jumeler un nouveau périphérique" + +#: lib/solaar/ui/window.py:211 +msgid "Select a device" +msgstr "Sélectionner un périphérique" + +#: lib/solaar/ui/window.py:508 +msgid "Path" +msgstr "Chemin" + +#: lib/solaar/ui/window.py:510 +msgid "USB id" +msgstr "Id USB" + +#: lib/solaar/ui/window.py:513 lib/solaar/ui/window.py:515 +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 +msgid "Serial" +msgstr "Numéro de série" + +#: lib/solaar/ui/window.py:519 +msgid "Index" +msgstr "Index" + +#: lib/solaar/ui/window.py:520 +msgid "Wireless PID" +msgstr "PID sans fil" + +#: lib/solaar/ui/window.py:522 +msgid "Protocol" +msgstr "Protocole" + +#: lib/solaar/ui/window.py:524 +msgid "Polling rate" +msgstr "Taux de scrutation" + +#: lib/solaar/ui/window.py:539 +msgid "none" +msgstr "aucun" + +#: lib/solaar/ui/window.py:540 +msgid "Notifications" +msgstr "Notifications" + +#: lib/solaar/ui/window.py:635 +msgid "charging" +msgstr "en charge" + +#: lib/solaar/ui/window.py:637 +msgid "last known" +msgstr "dernière valeur connue" + +#: lib/solaar/ui/window.py:644 +msgid "not encrypted" +msgstr "non chiffrée" + +#: lib/solaar/ui/window.py:648 +msgid "encrypted" +msgstr "chiffrée" diff --git a/po/pl.po b/po/pl.po index 3ec566f0..febf64d2 100644 --- a/po/pl.po +++ b/po/pl.po @@ -6,7 +6,7 @@ msgid "" msgstr "Project-Id-Version: solaar 0.9.1\n" "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2013-07-23 22:38+0200\n" + "POT-Creation-Date: 2013-08-08 21:53+0200\n" "PO-Revision-Date: 2013-07-23 10:41+0100\n" "Last-Translator: Adrian Piotrowicz \n" "Language-Team: none\n" @@ -82,7 +82,7 @@ msgstr "za dużo urządzeń" msgid "Bootloader" msgstr "Bootloader" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:538 +#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:535 msgid "Firmware" msgstr "Firmware" @@ -106,11 +106,15 @@ msgstr "otwarte" msgid "pairing lock is " msgstr "blokada parowania jest" +#: lib/logitech_receiver/notifications.py:150 lib/solaar/ui/notify.py:118 +msgid "unpaired" +msgstr "nie sparowany" + #: lib/logitech_receiver/notifications.py:192 msgid "powered on" msgstr "włączone" -#: lib/logitech_receiver/receiver.py:107 lib/solaar/ui/window.py:625 +#: lib/logitech_receiver/receiver.py:107 lib/solaar/ui/window.py:622 msgid "unknown" msgstr "nieznane" @@ -177,16 +181,16 @@ msgstr "1 sparowane urządzenie." msgid " paired devices." msgstr "sparowane(-ych) urządzeń." -#: lib/logitech_receiver/status.py:153 lib/logitech_receiver/status.py:155 -#: lib/solaar/ui/window.py:146 +#: lib/logitech_receiver/status.py:149 lib/logitech_receiver/status.py:151 +#: lib/solaar/ui/window.py:143 msgid "Battery" msgstr "Bateria" -#: lib/logitech_receiver/status.py:161 lib/solaar/ui/window.py:153 +#: lib/logitech_receiver/status.py:162 lib/solaar/ui/window.py:150 msgid "Lighting" msgstr "Podświetlenie" -#: lib/logitech_receiver/status.py:161 lib/solaar/ui/window.py:666 +#: lib/logitech_receiver/status.py:162 lib/solaar/ui/window.py:663 msgid "lux" msgstr "lux" @@ -194,33 +198,33 @@ msgstr "lux" msgid "The receiver was unplugged." msgstr "Odbiornik został odłączony." -#: lib/solaar/ui/__init__.py:69 +#: lib/solaar/ui/__init__.py:48 msgid "Permissions error" msgstr "Błąd uprawnień" -#: lib/solaar/ui/__init__.py:70 +#: lib/solaar/ui/__init__.py:49 #, fuzzy, python-format msgid "Found a Logitech Receiver (%s), but did not have permission to open " "it." msgstr "Znaleziono odbiornik Logitech (%s), ale nie ma uprawnień do " "otworzenia go." -#: lib/solaar/ui/__init__.py:72 +#: lib/solaar/ui/__init__.py:51 msgid "If you've just installed Solaar, try removing the receiver and " "plugging it back in." msgstr "Jeżeli właśnie zainstalowałeś Solaar spróbuj odłączyć nadajnik i " "podłączyć go ponownie." -#: lib/solaar/ui/__init__.py:74 +#: lib/solaar/ui/__init__.py:53 msgid "Unpairing failed" msgstr "Usunięcie parowania nie powiodło się" -#: lib/solaar/ui/__init__.py:75 -#, python-format -msgid "Failed to unpair %s from %s." -msgstr "Nie powidło się usunięcie parowania %s z %s." +#: lib/solaar/ui/__init__.py:54 +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Nie powidło się usunięcie parowania %{device} z %{receiver}." -#: lib/solaar/ui/__init__.py:77 +#: lib/solaar/ui/__init__.py:56 msgid "The receiver returned an error, with no further details." msgstr "Odbiornik zwrócił błąd bez dodatkowych informacji." @@ -242,12 +246,12 @@ msgstr "Testy" msgid "Logitech documentation" msgstr "Dokumentacja Logitech" -#: lib/solaar/ui/action.py:68 lib/solaar/ui/window.py:319 +#: lib/solaar/ui/action.py:68 lib/solaar/ui/window.py:316 msgid "About" msgstr "O" #: lib/solaar/ui/action.py:95 lib/solaar/ui/action.py:98 -#: lib/solaar/ui/window.py:206 +#: lib/solaar/ui/window.py:203 msgid "Unpair" msgstr "Usuń parowanie" @@ -259,16 +263,12 @@ msgstr "Pracuję" msgid "Read/write operation failed." msgstr "Operacja odczytu/zapisu nie powiodła się." -#: lib/solaar/ui/notify.py:115 -msgid "unpaired" -msgstr "nie sparowany" - -#: lib/solaar/ui/notify.py:116 +#: lib/solaar/ui/notify.py:120 msgid "connected" msgstr "podłączony" -#: lib/solaar/ui/notify.py:116 lib/solaar/ui/tray.py:285 -#: lib/solaar/ui/tray.py:290 lib/solaar/ui/window.py:656 +#: lib/solaar/ui/notify.py:122 lib/solaar/ui/tray.py:290 +#: lib/solaar/ui/tray.py:295 lib/solaar/ui/window.py:653 msgid "offline" msgstr "wyłączony" @@ -326,11 +326,11 @@ msgstr "Nie znaleziono odbiornika Logitech" msgid "Quit" msgstr "Wyjdź" -#: lib/solaar/ui/tray.py:269 +#: lib/solaar/ui/tray.py:274 msgid "no receiver" msgstr "brak odbiornika" -#: lib/solaar/ui/tray.py:288 +#: lib/solaar/ui/tray.py:293 msgid "no status" msgstr "brak statusu" @@ -380,75 +380,75 @@ msgstr "sparowane urządzenia" msgid "Only one device can be paired to this receiver" msgstr "Tylko jedno urządzenie może być sparowane z tym odbiornikiem" -#: lib/solaar/ui/window.py:113 +#: lib/solaar/ui/window.py:110 msgid "Scanning" msgstr "Wyszukiwanie" -#: lib/solaar/ui/window.py:149 +#: lib/solaar/ui/window.py:146 msgid "Wireless Link" msgstr "Połączenie bezprzewodowe" -#: lib/solaar/ui/window.py:182 +#: lib/solaar/ui/window.py:179 msgid "Show Technical Details" msgstr "Wyświetl szczegóły techniczne" -#: lib/solaar/ui/window.py:195 +#: lib/solaar/ui/window.py:192 msgid "Pair new device" msgstr "Sparuj nowe urządzenie" -#: lib/solaar/ui/window.py:214 +#: lib/solaar/ui/window.py:211 msgid "Select a device" msgstr "Wybierz urządzenie" -#: lib/solaar/ui/window.py:511 +#: lib/solaar/ui/window.py:508 msgid "Path" msgstr "Ścieżka" -#: lib/solaar/ui/window.py:513 +#: lib/solaar/ui/window.py:510 msgid "USB id" msgstr "USB id" -#: lib/solaar/ui/window.py:516 lib/solaar/ui/window.py:518 -#: lib/solaar/ui/window.py:530 lib/solaar/ui/window.py:532 +#: lib/solaar/ui/window.py:513 lib/solaar/ui/window.py:515 +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 msgid "Serial" msgstr "Serial" -#: lib/solaar/ui/window.py:522 +#: lib/solaar/ui/window.py:519 msgid "Index" msgstr "Index" -#: lib/solaar/ui/window.py:523 +#: lib/solaar/ui/window.py:520 msgid "Wireless PID" msgstr "Wireless PID" -#: lib/solaar/ui/window.py:525 +#: lib/solaar/ui/window.py:522 msgid "Protocol" msgstr "Protokół" -#: lib/solaar/ui/window.py:527 +#: lib/solaar/ui/window.py:524 msgid "Polling rate" msgstr "Częstotliwość próbkowania" -#: lib/solaar/ui/window.py:542 +#: lib/solaar/ui/window.py:539 msgid "none" msgstr "brak" -#: lib/solaar/ui/window.py:543 +#: lib/solaar/ui/window.py:540 msgid "Notifications" msgstr "Powiadomienia" -#: lib/solaar/ui/window.py:638 +#: lib/solaar/ui/window.py:635 msgid "charging" msgstr "ładowanie" -#: lib/solaar/ui/window.py:640 +#: lib/solaar/ui/window.py:637 msgid "last known" msgstr "ostatni znany" -#: lib/solaar/ui/window.py:647 +#: lib/solaar/ui/window.py:644 msgid "not encrypted" msgstr "nie szyfrowane" -#: lib/solaar/ui/window.py:651 +#: lib/solaar/ui/window.py:648 msgid "encrypted" msgstr "szyfrowane" diff --git a/po/ro.po b/po/ro.po index e1e16d25..e6a77808 100644 --- a/po/ro.po +++ b/po/ro.po @@ -6,7 +6,7 @@ msgid "" msgstr "Project-Id-Version: solaar 0.9.1\n" "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2013-07-23 22:35+0200\n" + "POT-Creation-Date: 2013-08-08 21:53+0200\n" "PO-Revision-Date: 2013-07-17 20:27+0100\n" "Last-Translator: Daniel Pavel \n" "Language-Team: none\n" @@ -82,7 +82,7 @@ msgstr "prea multe periferice" msgid "Bootloader" msgstr "" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:538 +#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:535 msgid "Firmware" msgstr "" @@ -106,11 +106,15 @@ msgstr "deschis" msgid "pairing lock is " msgstr "lacătul de contectare este " +#: lib/logitech_receiver/notifications.py:150 lib/solaar/ui/notify.py:118 +msgid "unpaired" +msgstr "deconectat(ă)" + #: lib/logitech_receiver/notifications.py:192 msgid "powered on" msgstr "a pornit" -#: lib/logitech_receiver/receiver.py:107 lib/solaar/ui/window.py:625 +#: lib/logitech_receiver/receiver.py:107 lib/solaar/ui/window.py:622 msgid "unknown" msgstr "necunoscută" @@ -174,16 +178,16 @@ msgstr "Un periferic contectat." msgid " paired devices." msgstr " periferice contectate." -#: lib/logitech_receiver/status.py:153 lib/logitech_receiver/status.py:155 -#: lib/solaar/ui/window.py:146 +#: lib/logitech_receiver/status.py:149 lib/logitech_receiver/status.py:151 +#: lib/solaar/ui/window.py:143 msgid "Battery" msgstr "Baterie" -#: lib/logitech_receiver/status.py:161 lib/solaar/ui/window.py:153 +#: lib/logitech_receiver/status.py:162 lib/solaar/ui/window.py:150 msgid "Lighting" msgstr "Lumină" -#: lib/logitech_receiver/status.py:161 lib/solaar/ui/window.py:666 +#: lib/logitech_receiver/status.py:162 lib/solaar/ui/window.py:663 msgid "lux" msgstr "lucși" @@ -191,32 +195,32 @@ msgstr "lucși" msgid "The receiver was unplugged." msgstr "Receptor deconectat." -#: lib/solaar/ui/__init__.py:69 +#: lib/solaar/ui/__init__.py:48 msgid "Permissions error" msgstr "Eroare de permisiuni" -#: lib/solaar/ui/__init__.py:70 +#: lib/solaar/ui/__init__.py:49 #, python-format msgid "Found a Logitech Receiver (%s), but did not have permission to open " "it." msgstr "Receptor Logitech detectat (%s), dar nu am permisiunea să-l deschid." -#: lib/solaar/ui/__init__.py:72 +#: lib/solaar/ui/__init__.py:51 msgid "If you've just installed Solaar, try removing the receiver and " "plugging it back in." msgstr "Dacă tocmai ați instalat Solaar, scoateți receptorul și re-" "introduceți-l." -#: lib/solaar/ui/__init__.py:74 +#: lib/solaar/ui/__init__.py:53 msgid "Unpairing failed" msgstr "Deconectare eșuată" -#: lib/solaar/ui/__init__.py:75 -#, python-format -msgid "Failed to unpair %s from %s." -msgstr "Deconectarea %s de la %s a eșuat." +#: lib/solaar/ui/__init__.py:54 +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Deconectarea %{device} de la %{receiver} a eșuat." -#: lib/solaar/ui/__init__.py:77 +#: lib/solaar/ui/__init__.py:56 msgid "The receiver returned an error, with no further details." msgstr "Receptorul a semnalat o eroare, fără alte detalii." @@ -238,12 +242,12 @@ msgstr "Testare" msgid "Logitech documentation" msgstr "Documentație Logitech" -#: lib/solaar/ui/action.py:68 lib/solaar/ui/window.py:319 +#: lib/solaar/ui/action.py:68 lib/solaar/ui/window.py:316 msgid "About" msgstr "Despre" #: lib/solaar/ui/action.py:95 lib/solaar/ui/action.py:98 -#: lib/solaar/ui/window.py:206 +#: lib/solaar/ui/window.py:203 msgid "Unpair" msgstr "Deconectează" @@ -255,16 +259,12 @@ msgstr "Prelucrez" msgid "Read/write operation failed." msgstr "Operațiunea a eșuat." -#: lib/solaar/ui/notify.py:115 -msgid "unpaired" -msgstr "deconectat(ă)" - -#: lib/solaar/ui/notify.py:116 +#: lib/solaar/ui/notify.py:120 msgid "connected" msgstr "conectat(ă)" -#: lib/solaar/ui/notify.py:116 lib/solaar/ui/tray.py:285 -#: lib/solaar/ui/tray.py:290 lib/solaar/ui/window.py:656 +#: lib/solaar/ui/notify.py:122 lib/solaar/ui/tray.py:290 +#: lib/solaar/ui/tray.py:295 lib/solaar/ui/window.py:653 msgid "offline" msgstr "inactivă" @@ -323,11 +323,11 @@ msgstr "Nu am găsit nici un receptor Logitech" msgid "Quit" msgstr "Ieșire" -#: lib/solaar/ui/tray.py:269 +#: lib/solaar/ui/tray.py:274 msgid "no receiver" msgstr "nici un receptor" -#: lib/solaar/ui/tray.py:288 +#: lib/solaar/ui/tray.py:293 msgid "no status" msgstr "stare necunoscută" @@ -365,75 +365,75 @@ msgstr "periferice conectate" msgid "Only one device can be paired to this receiver" msgstr "Acest receptor suportă un singur periferic conectat" -#: lib/solaar/ui/window.py:113 +#: lib/solaar/ui/window.py:110 msgid "Scanning" msgstr "Caut" -#: lib/solaar/ui/window.py:149 +#: lib/solaar/ui/window.py:146 msgid "Wireless Link" msgstr "Legatură fără fir" -#: lib/solaar/ui/window.py:182 +#: lib/solaar/ui/window.py:179 msgid "Show Technical Details" msgstr "Detalii tehnice" -#: lib/solaar/ui/window.py:195 +#: lib/solaar/ui/window.py:192 msgid "Pair new device" msgstr "Conectează periferic" -#: lib/solaar/ui/window.py:214 +#: lib/solaar/ui/window.py:211 msgid "Select a device" msgstr "Selectați un dispozitiv" -#: lib/solaar/ui/window.py:511 +#: lib/solaar/ui/window.py:508 msgid "Path" msgstr "Cale" -#: lib/solaar/ui/window.py:513 +#: lib/solaar/ui/window.py:510 msgid "USB id" msgstr "USB" -#: lib/solaar/ui/window.py:516 lib/solaar/ui/window.py:518 -#: lib/solaar/ui/window.py:530 lib/solaar/ui/window.py:532 +#: lib/solaar/ui/window.py:513 lib/solaar/ui/window.py:515 +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 msgid "Serial" msgstr "Serial" -#: lib/solaar/ui/window.py:522 +#: lib/solaar/ui/window.py:519 msgid "Index" msgstr "Index" -#: lib/solaar/ui/window.py:523 +#: lib/solaar/ui/window.py:520 msgid "Wireless PID" msgstr "Cod WPID" -#: lib/solaar/ui/window.py:525 +#: lib/solaar/ui/window.py:522 msgid "Protocol" msgstr "Protocol" -#: lib/solaar/ui/window.py:527 +#: lib/solaar/ui/window.py:524 msgid "Polling rate" msgstr "Rată acces" -#: lib/solaar/ui/window.py:542 +#: lib/solaar/ui/window.py:539 msgid "none" msgstr "nici una" -#: lib/solaar/ui/window.py:543 +#: lib/solaar/ui/window.py:540 msgid "Notifications" msgstr "Notificări" -#: lib/solaar/ui/window.py:638 +#: lib/solaar/ui/window.py:635 msgid "charging" msgstr "se încarcă" -#: lib/solaar/ui/window.py:640 +#: lib/solaar/ui/window.py:637 msgid "last known" msgstr "ultima valoare" -#: lib/solaar/ui/window.py:647 +#: lib/solaar/ui/window.py:644 msgid "not encrypted" msgstr "ne-criptată" -#: lib/solaar/ui/window.py:651 +#: lib/solaar/ui/window.py:648 msgid "encrypted" msgstr "criptată" diff --git a/po/solaar.pot b/po/solaar.pot index 9e72ecf5..29919edb 100644 --- a/po/solaar.pot +++ b/po/solaar.pot @@ -7,7 +7,11 @@ msgid "" msgstr "Project-Id-Version: solaar 0.9.2\n" "Report-Msgid-Bugs-To: \n" +<<<<<<< HEAD "POT-Creation-Date: 2013-08-05 18:51+0300\n" +======= + "POT-Creation-Date: 2013-08-08 22:21+0200\n" +>>>>>>> master "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -80,7 +84,7 @@ msgstr "" msgid "Bootloader" msgstr "" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:538 +#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:535 msgid "Firmware" msgstr "" @@ -104,11 +108,15 @@ msgstr "" msgid "pairing lock is " msgstr "" +#: lib/logitech_receiver/notifications.py:150 lib/solaar/ui/notify.py:118 +msgid "unpaired" +msgstr "" + #: lib/logitech_receiver/notifications.py:192 msgid "powered on" msgstr "" -#: lib/logitech_receiver/receiver.py:107 lib/solaar/ui/window.py:625 +#: lib/logitech_receiver/receiver.py:107 lib/solaar/ui/window.py:622 msgid "unknown" msgstr "" @@ -168,16 +176,16 @@ msgstr "" msgid " paired devices." msgstr "" -#: lib/logitech_receiver/status.py:153 lib/logitech_receiver/status.py:155 -#: lib/solaar/ui/window.py:146 +#: lib/logitech_receiver/status.py:149 lib/logitech_receiver/status.py:151 +#: lib/solaar/ui/window.py:143 msgid "Battery" msgstr "" -#: lib/logitech_receiver/status.py:161 lib/solaar/ui/window.py:153 +#: lib/logitech_receiver/status.py:162 lib/solaar/ui/window.py:150 msgid "Lighting" msgstr "" -#: lib/logitech_receiver/status.py:161 lib/solaar/ui/window.py:666 +#: lib/logitech_receiver/status.py:162 lib/solaar/ui/window.py:663 msgid "lux" msgstr "" @@ -185,31 +193,31 @@ msgstr "" msgid "The receiver was unplugged." msgstr "" -#: lib/solaar/ui/__init__.py:69 +#: lib/solaar/ui/__init__.py:48 msgid "Permissions error" msgstr "" -#: lib/solaar/ui/__init__.py:70 +#: lib/solaar/ui/__init__.py:49 #, python-format msgid "Found a Logitech Receiver (%s), but did not have permission to open " "it." msgstr "" -#: lib/solaar/ui/__init__.py:72 +#: lib/solaar/ui/__init__.py:51 msgid "If you've just installed Solaar, try removing the receiver and " "plugging it back in." msgstr "" -#: lib/solaar/ui/__init__.py:74 +#: lib/solaar/ui/__init__.py:53 msgid "Unpairing failed" msgstr "" -#: lib/solaar/ui/__init__.py:75 -#, python-format -msgid "Failed to unpair %s from %s." +#: lib/solaar/ui/__init__.py:54 +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." msgstr "" -#: lib/solaar/ui/__init__.py:77 +#: lib/solaar/ui/__init__.py:56 msgid "The receiver returned an error, with no further details." msgstr "" @@ -230,12 +238,12 @@ msgstr "" msgid "Logitech documentation" msgstr "" -#: lib/solaar/ui/action.py:68 lib/solaar/ui/window.py:319 +#: lib/solaar/ui/action.py:68 lib/solaar/ui/window.py:316 msgid "About" msgstr "" #: lib/solaar/ui/action.py:95 lib/solaar/ui/action.py:98 -#: lib/solaar/ui/window.py:206 +#: lib/solaar/ui/window.py:203 msgid "Unpair" msgstr "" @@ -247,16 +255,12 @@ msgstr "" msgid "Read/write operation failed." msgstr "" -#: lib/solaar/ui/notify.py:115 -msgid "unpaired" -msgstr "" - -#: lib/solaar/ui/notify.py:116 +#: lib/solaar/ui/notify.py:120 msgid "connected" msgstr "" -#: lib/solaar/ui/notify.py:116 lib/solaar/ui/tray.py:285 -#: lib/solaar/ui/tray.py:290 lib/solaar/ui/window.py:656 +#: lib/solaar/ui/notify.py:122 lib/solaar/ui/tray.py:290 +#: lib/solaar/ui/tray.py:295 lib/solaar/ui/window.py:653 msgid "offline" msgstr "" @@ -312,11 +316,11 @@ msgstr "" msgid "Quit" msgstr "" -#: lib/solaar/ui/tray.py:269 +#: lib/solaar/ui/tray.py:274 msgid "no receiver" msgstr "" -#: lib/solaar/ui/tray.py:288 +#: lib/solaar/ui/tray.py:293 msgid "no status" msgstr "" @@ -354,75 +358,75 @@ msgstr "" msgid "Only one device can be paired to this receiver" msgstr "" -#: lib/solaar/ui/window.py:113 +#: lib/solaar/ui/window.py:110 msgid "Scanning" msgstr "" -#: lib/solaar/ui/window.py:149 +#: lib/solaar/ui/window.py:146 msgid "Wireless Link" msgstr "" -#: lib/solaar/ui/window.py:182 +#: lib/solaar/ui/window.py:179 msgid "Show Technical Details" msgstr "" -#: lib/solaar/ui/window.py:195 +#: lib/solaar/ui/window.py:192 msgid "Pair new device" msgstr "" -#: lib/solaar/ui/window.py:214 +#: lib/solaar/ui/window.py:211 msgid "Select a device" msgstr "" -#: lib/solaar/ui/window.py:511 +#: lib/solaar/ui/window.py:508 msgid "Path" msgstr "" -#: lib/solaar/ui/window.py:513 +#: lib/solaar/ui/window.py:510 msgid "USB id" msgstr "" -#: lib/solaar/ui/window.py:516 lib/solaar/ui/window.py:518 -#: lib/solaar/ui/window.py:530 lib/solaar/ui/window.py:532 +#: lib/solaar/ui/window.py:513 lib/solaar/ui/window.py:515 +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 msgid "Serial" msgstr "" -#: lib/solaar/ui/window.py:522 +#: lib/solaar/ui/window.py:519 msgid "Index" msgstr "" -#: lib/solaar/ui/window.py:523 +#: lib/solaar/ui/window.py:520 msgid "Wireless PID" msgstr "" -#: lib/solaar/ui/window.py:525 +#: lib/solaar/ui/window.py:522 msgid "Protocol" msgstr "" -#: lib/solaar/ui/window.py:527 +#: lib/solaar/ui/window.py:524 msgid "Polling rate" msgstr "" -#: lib/solaar/ui/window.py:542 +#: lib/solaar/ui/window.py:539 msgid "none" msgstr "" -#: lib/solaar/ui/window.py:543 +#: lib/solaar/ui/window.py:540 msgid "Notifications" msgstr "" -#: lib/solaar/ui/window.py:638 +#: lib/solaar/ui/window.py:635 msgid "charging" msgstr "" -#: lib/solaar/ui/window.py:640 +#: lib/solaar/ui/window.py:637 msgid "last known" msgstr "" -#: lib/solaar/ui/window.py:647 +#: lib/solaar/ui/window.py:644 msgid "not encrypted" msgstr "" -#: lib/solaar/ui/window.py:651 +#: lib/solaar/ui/window.py:648 msgid "encrypted" msgstr "" diff --git a/po/sv.po b/po/sv.po new file mode 100644 index 00000000..2c056ae5 --- /dev/null +++ b/po/sv.po @@ -0,0 +1,451 @@ +# Swedish translations for solaar package. +# Copyright (C) 2013 THE solaar'S COPYRIGHT HOLDER +# This file is distributed under the same license as the solaar package. +# Automatically generated, 2013. +# +msgid "" +msgstr "Project-Id-Version: solaar 0.9.1\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2013-08-08 19:46+0200\n" + "PO-Revision-Date: 2013-07-27 16:28+0100\n" + "Last-Translator: Daniel Zippert & Emelie Snecker \n" + "Language-Team: none\n" + "Language: sv\n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=2; plural=(n != 1);\n" + "X-Generator: Poedit 1.5.4\n" + "X-Poedit-Basepath: /pwr/Solaar/po\n" + "X-Poedit-SearchPath-0: /pwr/Solaar/po\n" + +#: lib/logitech_receiver/i18n.py:38 +msgid "critical" +msgstr "kritisk" + +#: lib/logitech_receiver/i18n.py:38 +msgid "empty" +msgstr "tom" + +#: lib/logitech_receiver/i18n.py:38 lib/logitech_receiver/i18n.py:41 +msgid "full" +msgstr "full" + +#: lib/logitech_receiver/i18n.py:38 +msgid "good" +msgstr "normal" + +#: lib/logitech_receiver/i18n.py:38 +msgid "low" +msgstr "låg" + +#: lib/logitech_receiver/i18n.py:41 +msgid "almost full" +msgstr "nästan full" + +#: lib/logitech_receiver/i18n.py:41 +msgid "discharging" +msgstr "laddar ur" + +#: lib/logitech_receiver/i18n.py:41 +msgid "recharging" +msgstr "laddar" + +#: lib/logitech_receiver/i18n.py:42 +msgid "invalid battery" +msgstr "batterifel" + +#: lib/logitech_receiver/i18n.py:42 +msgid "slow recharge" +msgstr "långsam laddning" + +#: lib/logitech_receiver/i18n.py:42 +msgid "thermal error" +msgstr "termiskt fel" + +#: lib/logitech_receiver/i18n.py:45 +msgid "device not supported" +msgstr "enheten stöds inte" + +#: lib/logitech_receiver/i18n.py:45 +msgid "device timeout" +msgstr "enheten svarde inte i tid" + +#: lib/logitech_receiver/i18n.py:45 +msgid "sequence timeout" +msgstr "sekvens timout" + +#: lib/logitech_receiver/i18n.py:45 +msgid "too many devices" +msgstr "för många enheter" + +#: lib/logitech_receiver/i18n.py:48 +msgid "Bootloader" +msgstr "Bootloader" + +#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:535 +msgid "Firmware" +msgstr "Firmware" + +#: lib/logitech_receiver/i18n.py:48 +msgid "Hardware" +msgstr "Hårdvara" + +#: lib/logitech_receiver/i18n.py:48 +msgid "Other" +msgstr "Annan" + +#: lib/logitech_receiver/notifications.py:67 +msgid "closed" +msgstr "låst" + +#: lib/logitech_receiver/notifications.py:67 +msgid "open" +msgstr "öppen" + +#: lib/logitech_receiver/notifications.py:67 +msgid "pairing lock is " +msgstr "parkopplingsläge låst" + +#: lib/logitech_receiver/notifications.py:150 lib/solaar/ui/notify.py:118 +msgid "unpaired" +msgstr "inte parkopplad" + +#: lib/logitech_receiver/notifications.py:192 +msgid "powered on" +msgstr "påslagen" + +#: lib/logitech_receiver/receiver.py:107 lib/solaar/ui/window.py:622 +msgid "unknown" +msgstr "okänd" + +#: lib/logitech_receiver/settings_templates.py:77 +msgid "Smooth Scrolling" +msgstr "Mjuk Scrollning" + +#: lib/logitech_receiver/settings_templates.py:78 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Hög känslighet för scrollhjulet. " + +#: lib/logitech_receiver/settings_templates.py:79 +msgid "Side Scrolling" +msgstr "Vertikal Scrollning" + +#: lib/logitech_receiver/settings_templates.py:80 +msgid "When disabled, pushing the wheel sideways sends custom button " + "events\n" + "instead of the standard side-scrolling events." +msgstr "Vid avaktivering, kommer tryckningar sidledes på scrollhjulet\n" + "fungera som specialknappar istället för standard sidscrolling." + +#: lib/logitech_receiver/settings_templates.py:82 +msgid "Sensitivity (DPI)" +msgstr "Känslighet (DPI)" + +#: lib/logitech_receiver/settings_templates.py:83 +msgid "Swap Fx function" +msgstr "Skifta funktionen för F1-12 tangenterna" + +#: lib/logitech_receiver/settings_templates.py:84 +msgid "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." +msgstr "Vid aktivering, kommer F1-12 tangenterna få sina specialfunktioner,\n" + "och du behöver trycka ned FN tangenten för att komma åt dess normala " + "funktioner." + +#: lib/logitech_receiver/settings_templates.py:87 +msgid "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." +msgstr "Vid avaktivering, kommer F1-12 tangenterna få sina " + "standardfunktioner,\n" + "och du behöver trycka ned FN tangenten för att komma åt dess " + "spesicalfunktioner." + +#: lib/logitech_receiver/settings_templates.py:89 +msgid "Hand Detection" +msgstr "Handavkänning" + +#: lib/logitech_receiver/settings_templates.py:90 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Tänd belysning, om en hand hålls över tangentbordet. " + +#: lib/logitech_receiver/status.py:98 +msgid "No paired devices." +msgstr "Inga enheter är parkopplade" + +#: lib/logitech_receiver/status.py:99 +msgid "1 paired device." +msgstr "1 parkopplad enhet." + +#: lib/logitech_receiver/status.py:100 +msgid " paired devices." +msgstr "parkopplade enheter." + +#: lib/logitech_receiver/status.py:149 lib/logitech_receiver/status.py:151 +#: lib/solaar/ui/window.py:143 +msgid "Battery" +msgstr "Batteri" + +#: lib/logitech_receiver/status.py:162 lib/solaar/ui/window.py:150 +msgid "Lighting" +msgstr "Belysning" + +#: lib/logitech_receiver/status.py:162 lib/solaar/ui/window.py:663 +msgid "lux" +msgstr "lux" + +#: lib/solaar/listener.py:95 +msgid "The receiver was unplugged." +msgstr "Mottagaren kopplades ur" + +#: lib/solaar/ui/__init__.py:48 +msgid "Permissions error" +msgstr "Behörighetsfel" + +#: lib/solaar/ui/__init__.py:49 +#, python-format +msgid "Found a Logitech Receiver (%s), but did not have permission to open " + "it." +msgstr "En Logitech mottagare hittades (%s), men behörighet att använda " + "den saknas." + +#: lib/solaar/ui/__init__.py:51 +msgid "If you've just installed Solaar, try removing the receiver and " + "plugging it back in." +msgstr "Om du just installerade solaar, prova koppla ur mottagaren och " + "anslut den sedan igen. " + +#: lib/solaar/ui/__init__.py:53 +msgid "Unpairing failed" +msgstr "Kunde inte bryta parkoppling." + +#: lib/solaar/ui/__init__.py:54 +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Misslyckades att bryta parkoppling mellan %{device} och %{receiver}." + +#: lib/solaar/ui/__init__.py:56 +msgid "The receiver returned an error, with no further details." +msgstr "Mottagaren rapporterade ett fel, utan specifika detaljer." + +#: lib/solaar/ui/about.py:39 +msgid "Shows status of devices connected\n" + "through wireless Logitech receivers." +msgstr "Visa status för enheter kopplade till\n" + "din trådlösa Logitech mottagare" + +#: lib/solaar/ui/about.py:48 +msgid "GUI design" +msgstr "GUI utseende" + +#: lib/solaar/ui/about.py:49 +msgid "Testing" +msgstr "Prövar" + +#: lib/solaar/ui/about.py:54 +msgid "Logitech documentation" +msgstr "Logitech dokumentation" + +#: lib/solaar/ui/action.py:68 lib/solaar/ui/window.py:316 +msgid "About" +msgstr "Om" + +#: lib/solaar/ui/action.py:95 lib/solaar/ui/action.py:98 +#: lib/solaar/ui/window.py:203 +msgid "Unpair" +msgstr "Ta bort parkoppling" + +#: lib/solaar/ui/config_panel.py:97 +msgid "Working" +msgstr "Lyckades" + +#: lib/solaar/ui/config_panel.py:100 +msgid "Read/write operation failed." +msgstr "Läsning/Skrivning misslyckades." + +#: lib/solaar/ui/notify.py:120 +msgid "connected" +msgstr "ansluten" + +#: lib/solaar/ui/notify.py:122 lib/solaar/ui/tray.py:290 +#: lib/solaar/ui/tray.py:295 lib/solaar/ui/window.py:653 +msgid "offline" +msgstr "avstängd" + +#: lib/solaar/ui/pair_window.py:133 +msgid "Pairing failed" +msgstr "Parkoppling misslyckades" + +#: lib/solaar/ui/pair_window.py:135 +msgid "Make sure your device is within range, and has a decent battery " + "charge." +msgstr "Se till så att enheten inom räckhåll, och har tillräckligt laddat " + "batteri. " + +#: lib/solaar/ui/pair_window.py:137 +msgid "A new device was detected, but it is not compatible with this " + "receiver." +msgstr "En ny enhet upptäcktes, men är inte kompatibel med mottagaren. " + +#: lib/solaar/ui/pair_window.py:139 +#, python-format +msgid "The receiver only supports %d paired device(s)." +msgstr "Mottagaren klarar bara %d parkopplad(e) enhet(er)." + +#: lib/solaar/ui/pair_window.py:141 +msgid "No further details are available about the error." +msgstr "Ingen mer information är tillgänglig om felet. " + +#: lib/solaar/ui/pair_window.py:155 +msgid "Found a new device" +msgstr "Ny enhet har hittats" + +#: lib/solaar/ui/pair_window.py:180 +msgid "The wireless link is not encrypted" +msgstr "Den trådlösa anslutningen är okrypterad" + +#: lib/solaar/ui/pair_window.py:197 +msgid "pair new device" +msgstr "parkoppla ny enhet" + +#: lib/solaar/ui/pair_window.py:205 +msgid "Turn on the device you want to pair." +msgstr "Sätt på enheten du vill parkoppla." + +#: lib/solaar/ui/pair_window.py:206 +msgid "If the device is already turned on,\n" + "turn if off and on again." +msgstr "Om enheten redan är igång,\n" + "stäng av den och sätt på den igen." + +#: lib/solaar/ui/tray.py:55 +msgid "No Logitech receiver found" +msgstr "Ingen Logitech motagare hittades" + +#: lib/solaar/ui/tray.py:62 +msgid "Quit" +msgstr "Stäng" + +#: lib/solaar/ui/tray.py:274 +msgid "no receiver" +msgstr "ingen mottagare" + +#: lib/solaar/ui/tray.py:293 +msgid "no status" +msgstr "ingen status" + +#: lib/solaar/ui/window.py:58 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "Den trådlösa anslutningen är krypterad." + +#: lib/solaar/ui/window.py:59 +msgid "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." +msgstr "Den trådlösa anslutningen mellan den här enheten och mottagaren är " + "okrypterad\n" + "\n" + "For pekdon (möss, styrkulor, pekplattor), är detta inget stort " + "säkerhetsproblem.\n" + "\n" + "Men för textinmatande enheter (tangentbord, numpads) är denna " + "säkerhetsbrist allvarlig, eftersom skriven text obemärkt kan fångas " + "upp av tredje part som befinner sig inom enhetens räckhåll. " + +#: lib/solaar/ui/window.py:67 lib/solaar/ui/window.py:71 +msgid "No device paired" +msgstr "Inga enheter parkopplade" + +#: lib/solaar/ui/window.py:67 lib/solaar/ui/window.py:68 +#, python-format +msgid "Up to %d devices can be paired to this receiver" +msgstr "Upp till %d enheter kan parkopplas till mottagaren" + +#: lib/solaar/ui/window.py:68 +msgid "paired devices" +msgstr "parkopplade enheter" + +#: lib/solaar/ui/window.py:72 +msgid "Only one device can be paired to this receiver" +msgstr "Bara en enhet kan parkopplas till den här mottagaren" + +#: lib/solaar/ui/window.py:110 +msgid "Scanning" +msgstr "Söker" + +#: lib/solaar/ui/window.py:146 +msgid "Wireless Link" +msgstr "Trådlös anslutning" + +#: lib/solaar/ui/window.py:179 +msgid "Show Technical Details" +msgstr "Visa Tekniska Detaljer" + +#: lib/solaar/ui/window.py:192 +msgid "Pair new device" +msgstr "Parkoppla en ny enhet" + +#: lib/solaar/ui/window.py:211 +msgid "Select a device" +msgstr "Välj en enhet" + +#: lib/solaar/ui/window.py:508 +msgid "Path" +msgstr "Sökväg" + +#: lib/solaar/ui/window.py:510 +msgid "USB id" +msgstr "USB id" + +#: lib/solaar/ui/window.py:513 lib/solaar/ui/window.py:515 +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 +msgid "Serial" +msgstr "Seriell" + +#: lib/solaar/ui/window.py:519 +msgid "Index" +msgstr "Index" + +#: lib/solaar/ui/window.py:520 +msgid "Wireless PID" +msgstr "Trådlös PID" + +#: lib/solaar/ui/window.py:522 +msgid "Protocol" +msgstr "Protokoll" + +#: lib/solaar/ui/window.py:524 +msgid "Polling rate" +msgstr "Uppdateringshastighet" + +#: lib/solaar/ui/window.py:539 +msgid "none" +msgstr "ingen" + +#: lib/solaar/ui/window.py:540 +msgid "Notifications" +msgstr "Notifikation " + +#: lib/solaar/ui/window.py:635 +msgid "charging" +msgstr "laddar" + +#: lib/solaar/ui/window.py:637 +msgid "last known" +msgstr "senast kända" + +#: lib/solaar/ui/window.py:644 +msgid "not encrypted" +msgstr "okrypterad" + +#: lib/solaar/ui/window.py:648 +msgid "encrypted" +msgstr "krypterad" diff --git a/tools/build_gh_pages.sh b/tools/build_gh_pages.sh index bbbb9761..2e9714c8 100755 --- a/tools/build_gh_pages.sh +++ b/tools/build_gh_pages.sh @@ -80,8 +80,8 @@ done # create packages/ sub-directory /bin/mkdir --parents "$SITE/../packages" "$SITE/packages/" -/bin/cp --archive --update --target-directory="$SITE/../packages/" "$SELF/dist/debian"/solaar_* || true -/bin/cp --archive --update --target-directory="$SITE/../packages/" "$SELF/dist/debian"/solaar-gnome3_* || true +#/bin/cp --archive --update --target-directory="$SITE/../packages/" "$SELF/dist/debian"/solaar_* || true +#/bin/cp --archive --update --target-directory="$SITE/../packages/" "$SELF/dist/debian"/solaar-gnome3_* || true if test -x /usr/bin/dpkg-scanpackages; then cd "$SITE/../packages/" /bin/rm --force *.build diff --git a/tools/po-update.sh b/tools/po-update.sh index 8c57c3ae..ac7c1acd 100755 --- a/tools/po-update.sh +++ b/tools/po-update.sh @@ -1,14 +1,13 @@ #!/bin/sh -if test -z "$1"; then - echo "Use: $0 " - exit 2 -fi -LL_CC="$1" -shift - set -e +if test "$1" = "-h" -o "$1" = "--help"; then + echo "Use: $0 []" + echo "Run without arguments to update all translation files." + exit 0 +fi + cd "$(readlink -f "$(dirname "$0")/..")" VERSION=$(python setup.py --version) @@ -33,18 +32,11 @@ POT_FILE="$POT_DIR/$DOMAIN.pot" /bin/sed --in-place --expression="s/charset=CHARSET/charset=UTF-8/" "$POT_FILE" -PO_FILE="$POT_DIR/$LL_CC.po" - -test -r "$PO_FILE" || /usr/bin/msginit \ - --no-translator --locale="$LL_CC" \ - --input="$POT_FILE" \ - --output-file="$PO_FILE" unfmt() { local SOURCE="/usr/share/locale/$LL_CC/LC_MESSAGES/$1.mo" - if [ ! -f $SOURCE ] - then - local SOURCE="/usr/share/locale-langpack/$LL_CC/LC_MESSAGES/$1.mo" + if test ! -f "$SOURCE"; then + SOURCE="/usr/share/locale-langpack/$LL_CC/LC_MESSAGES/$1.mo" fi local TARGET="$(mktemp --tmpdir $1-$LL_CC-XXXXXX.po)" /usr/bin/msgunfmt \ @@ -54,14 +46,32 @@ unfmt() { echo "$TARGET" } -/usr/bin/msgmerge \ - --update --no-fuzzy-matching \ - --no-escape --indent --add-location --sort-by-file \ - --lang="$LL_CC" \ - --compendium="$(unfmt gtk30)" \ - --compendium="$(unfmt gtk30-properties)" \ - "$PO_FILE" "$POT_FILE" +update_po() { + local LL_CC="$1" + local PO_FILE="$POT_DIR/$LL_CC.po" -# /bin/sed --in-place --expression="s/Language: \\\\n/Language: $L_NAME\\\\n/" "$PO_FILE" + test -r "$PO_FILE" || /usr/bin/msginit \ + --no-translator --locale="$LL_CC" \ + --input="$POT_FILE" \ + --output-file="$PO_FILE" -echo "Language file is $PO_FILE" + /usr/bin/msgmerge \ + --update --no-fuzzy-matching \ + --no-escape --indent --add-location --sort-by-file \ + --lang="$LL_CC" \ + --compendium="$(unfmt gtk30)" \ + --compendium="$(unfmt gtk30-properties)" \ + "$PO_FILE" "$POT_FILE" + + # /bin/sed --in-place --expression="s/Language: \\\\n/Language: $L_NAME\\\\n/" "$PO_FILE" + echo "Updated $PO_FILE" +} + +if test "$1"; then + update_po "$1" +else + for l in $(ls -1 "$POT_DIR"/??.po); do + l="$(basename "$l")" + update_po "${l%.po}" + done +fi