From 064a7a113c75b53e61690bc45391442532ecd2f4 Mon Sep 17 00:00:00 2001 From: Daniel Pavel Date: Sat, 15 Dec 2012 20:27:19 +0200 Subject: [PATCH] added setup.py for python and debian packaging --- .gitignore | 4 +- README.md | 10 +- bin/hidconsole | 11 - bin/solaar | 43 +- bin/solaar-cli | 31 +- lib/hidapi/native.py | 384 ------------------ packaging/debian/copyright | 39 ++ packaging/debian/stdeb.cfg | 10 + setup.py | 54 +++ share/applications/solaar.desktop | 9 + .../128x128/status => }/battery_000.png | Bin .../128x128/status => }/battery_010.png | Bin .../128x128/status => }/battery_020.png | Bin .../128x128/status => }/battery_030.png | Bin .../128x128/status => }/battery_040.png | Bin .../128x128/status => }/battery_050.png | Bin .../128x128/status => }/battery_060.png | Bin .../128x128/status => }/battery_070.png | Bin .../128x128/status => }/battery_080.png | Bin .../128x128/status => }/battery_090.png | Bin .../128x128/status => }/battery_100.png | Bin .../128x128/status => }/battery_unknown.png | Bin .../128x128/status => }/light_000.png | Bin .../128x128/status => }/light_020.png | Bin .../128x128/status => }/light_040.png | Bin .../128x128/status => }/light_060.png | Bin .../128x128/status => }/light_080.png | Bin .../128x128/status => }/light_100.png | Bin .../128x128/status => }/light_unknown.png | Bin .../apps/Solaar-fail.png => solaar-fail.png} | Bin .../apps/Solaar-init.png => solaar-init.png} | Bin .../apps/Solaar-mask.png => solaar-mask.png} | Bin .../128x128/apps/Solaar.png => solaar.png} | Bin {lib => src}/hidapi/__init__.py | 2 - {lib => src}/hidapi/hidconsole.py | 36 +- src/hidapi/native.py | 384 ++++++++++++++++++ {lib => src}/hidapi/udev.py | 0 {lib => src}/logitech/__init__.py | 2 - .../logitech/unifying_receiver/__init__.py | 0 .../logitech/unifying_receiver/base.py | 0 .../logitech/unifying_receiver/common.py | 3 + .../logitech/unifying_receiver/descriptors.py | 0 .../logitech/unifying_receiver/hidpp10.py | 0 .../logitech/unifying_receiver/hidpp20.py | 0 .../logitech/unifying_receiver/listener.py | 0 .../logitech/unifying_receiver/receiver.py | 0 .../logitech/unifying_receiver/settings.py | 0 .../logitech/unifying_receiver/status.py | 0 src/solaar/__init__.py | 7 + app/solaar_cli.py => src/solaar/cli.py | 14 +- app/solaar.py => src/solaar/gtk.py | 43 +- {app => src/solaar}/listener.py | 0 src/solaar/ui/__init__.py | 18 + {app => src/solaar}/ui/action.py | 40 +- {app => src/solaar}/ui/config_panel.py | 10 +- app/ui/__init__.py => src/solaar/ui/icons.py | 68 +--- src/solaar/ui/indicate.py | 62 +++ {app => src/solaar}/ui/main_window.py | 29 +- {app => src/solaar}/ui/notify.py | 9 +- {app => src/solaar}/ui/pair_window.py | 4 +- {app => src/solaar}/ui/status_icon.py | 22 +- tools/build_deb.sh | 4 + tools/hidconsole | 31 ++ bin/scan-registers => tools/scan-registers.sh | 0 64 files changed, 820 insertions(+), 563 deletions(-) delete mode 100755 bin/hidconsole delete mode 100644 lib/hidapi/native.py create mode 100644 packaging/debian/copyright create mode 100644 packaging/debian/stdeb.cfg create mode 100755 setup.py create mode 100644 share/applications/solaar.desktop rename share/icons/{hicolor/128x128/status => }/battery_000.png (100%) rename share/icons/{hicolor/128x128/status => }/battery_010.png (100%) rename share/icons/{hicolor/128x128/status => }/battery_020.png (100%) rename share/icons/{hicolor/128x128/status => }/battery_030.png (100%) rename share/icons/{hicolor/128x128/status => }/battery_040.png (100%) rename share/icons/{hicolor/128x128/status => }/battery_050.png (100%) rename share/icons/{hicolor/128x128/status => }/battery_060.png (100%) rename share/icons/{hicolor/128x128/status => }/battery_070.png (100%) rename share/icons/{hicolor/128x128/status => }/battery_080.png (100%) rename share/icons/{hicolor/128x128/status => }/battery_090.png (100%) rename share/icons/{hicolor/128x128/status => }/battery_100.png (100%) rename share/icons/{hicolor/128x128/status => }/battery_unknown.png (100%) rename share/icons/{hicolor/128x128/status => }/light_000.png (100%) rename share/icons/{hicolor/128x128/status => }/light_020.png (100%) rename share/icons/{hicolor/128x128/status => }/light_040.png (100%) rename share/icons/{hicolor/128x128/status => }/light_060.png (100%) rename share/icons/{hicolor/128x128/status => }/light_080.png (100%) rename share/icons/{hicolor/128x128/status => }/light_100.png (100%) rename share/icons/{hicolor/128x128/status => }/light_unknown.png (100%) rename share/icons/{hicolor/128x128/apps/Solaar-fail.png => solaar-fail.png} (100%) rename share/icons/{hicolor/128x128/apps/Solaar-init.png => solaar-init.png} (100%) rename share/icons/{hicolor/128x128/apps/Solaar-mask.png => solaar-mask.png} (100%) rename share/icons/{hicolor/128x128/apps/Solaar.png => solaar.png} (100%) rename {lib => src}/hidapi/__init__.py (78%) rename {lib => src}/hidapi/hidconsole.py (92%) create mode 100644 src/hidapi/native.py rename {lib => src}/hidapi/udev.py (100%) rename {lib => src}/logitech/__init__.py (69%) rename {lib => src}/logitech/unifying_receiver/__init__.py (100%) rename {lib => src}/logitech/unifying_receiver/base.py (100%) rename {lib => src}/logitech/unifying_receiver/common.py (99%) rename {lib => src}/logitech/unifying_receiver/descriptors.py (100%) rename {lib => src}/logitech/unifying_receiver/hidpp10.py (100%) rename {lib => src}/logitech/unifying_receiver/hidpp20.py (100%) rename {lib => src}/logitech/unifying_receiver/listener.py (100%) rename {lib => src}/logitech/unifying_receiver/receiver.py (100%) rename {lib => src}/logitech/unifying_receiver/settings.py (100%) rename {lib => src}/logitech/unifying_receiver/status.py (100%) create mode 100644 src/solaar/__init__.py rename app/solaar_cli.py => src/solaar/cli.py (98%) rename app/solaar.py => src/solaar/gtk.py (85%) rename {app => src/solaar}/listener.py (100%) create mode 100644 src/solaar/ui/__init__.py rename {app => src/solaar}/ui/action.py (71%) rename {app => src/solaar}/ui/config_panel.py (97%) rename app/ui/__init__.py => src/solaar/ui/icons.py (51%) create mode 100644 src/solaar/ui/indicate.py rename {app => src/solaar}/ui/main_window.py (93%) rename {app => src/solaar}/ui/notify.py (87%) rename {app => src/solaar}/ui/pair_window.py (98%) rename {app => src/solaar}/ui/status_icon.py (86%) create mode 100755 tools/build_deb.sh create mode 100755 tools/hidconsole rename bin/scan-registers => tools/scan-registers.sh (100%) diff --git a/.gitignore b/.gitignore index 045b5abd..a9606130 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -*.so *.pyc *.pyo +__pycache__/ *.log + +/src/Solaar.egg-info/ diff --git a/README.md b/README.md index 1907b2b7..bcf0b9e5 100644 --- a/README.md +++ b/README.md @@ -76,12 +76,10 @@ required steps by hand, as the root user: ## Known Issues -- When running under Ubuntu's Unity, the tray icon will probably not appear, nor - will the application window. Either run the application with the '-S' option, - or whitelist "Solaar" into the systray. For details, see - [How do I access and enable more icons to be in the system tray?](http://askubuntu.com/questions/30742/how-do-i-access-and-enable-more-icons-to-be-in-the-system-tray). - - Support for Unity's indicators is a planned feature. +- Ubuntu's Unity indicators are not supported at this time. However, if you + whitelist 'Solaar' in the systray, you will get an icon (see +[How do I access and enable more icons to be in the system tray?](http://askubuntu.com/questions/30742/how-do-i-access-and-enable-more-icons-to-be-in-the-system-tray) +) - Running the command-line application (`bin/solaar-cli`) while the GUI application is also running *may* occasionally cause either of them to become diff --git a/bin/hidconsole b/bin/hidconsole deleted file mode 100755 index 93b08d29..00000000 --- a/bin/hidconsole +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -Z=`dirname "$0"` -Z=`readlink -f "$Z/.."` - -export PYTHONPATH="$Z/lib" -export PYTHONUNBUFFERED=yes - -NICE=`which nice 2>/dev/null` -PYTHON=${PYTHON:-`which python python2 python3 | head -n 1`} -exec $NICE $PYTHON -m hidapi.hidconsole "$@" diff --git a/bin/solaar b/bin/solaar index 75e3b1f7..591a1c10 100755 --- a/bin/solaar +++ b/bin/solaar @@ -1,11 +1,38 @@ -#!/bin/sh +#!/usr/bin/env python +# -*- python-mode -*- +"""Takes care of starting the main function.""" -Z=`dirname "$0"` -Z=`readlink -f "$Z/.."` +from __future__ import absolute_import -export PYTHONPATH="$Z/app:$Z/lib" -export XDG_DATA_DIRS="$Z/share_override:$Z/share:$XDG_DATA_DIRS" -NICE=`which nice 2>/dev/null` -PYTHON=${PYTHON:-`which python python2 python3 | head -n 1`} -exec $NICE $PYTHON -m solaar "$@" +def init_paths(): + import os.path as _path + + prefix = _path.dirname(_path.dirname(_path.realpath(_path.abspath(__file__)))) + + dist_lib = _path.join(prefix, 'share', 'solaar', 'lib') + src_lib = _path.join(prefix, 'src') + + for location in (dist_lib, src_lib): + init_py = _path.join(location, 'solaar', '__init__.py') + if _path.exists(init_py): + import sys + sys.path.insert(1, location) + break + + # src_override = _path.join(prefix, 'share_override') + dist_share = _path.join(prefix, 'share', 'solaar') + src_share = _path.join(prefix, 'share') + + for location in (dist_share, src_share): + solaar_png = _path.join(location, 'icons', 'solaar.png') + if _path.exists(solaar_png): + import os + os.environ['XDG_DATA_DIRS'] = location + ':' + os.environ.get('XDG_DATA_DIRS', '') + break + + +if __name__ == '__main__': + init_paths() + import solaar.gtk + solaar.gtk.main() diff --git a/bin/solaar-cli b/bin/solaar-cli index 3d572292..f9f18a61 100755 --- a/bin/solaar-cli +++ b/bin/solaar-cli @@ -1,10 +1,27 @@ -#!/bin/sh +#!/usr/bin/env python +# -*- python-mode -*- +"""Takes care of starting the main function.""" -Z=`dirname "$0"` -Z=`readlink -f "$Z/.."` +from __future__ import absolute_import -export PYTHONPATH="$Z/app:$Z/lib" -NICE=`which nice 2>/dev/null` -PYTHON=${PYTHON:-`which python python2 python3 | head -n 1`} -exec $NICE $PYTHON -m solaar_cli "$@" +def init_paths(): + import os.path as _path + + prefix = _path.dirname(_path.dirname(_path.realpath(_path.abspath(__file__)))) + + dist_lib = _path.join(prefix, 'share', 'solaar', 'lib') + src_lib = _path.join(prefix, 'src') + + for location in (dist_lib, src_lib): + init_py = _path.join(location, 'solaar', '__init__.py') + if _path.exists(init_py): + import sys + sys.path.insert(1, location) + break + + +if __name__ == '__main__': + init_paths() + import solaar.cli + solaar.cli.main() diff --git a/lib/hidapi/native.py b/lib/hidapi/native.py deleted file mode 100644 index 8c807f74..00000000 --- a/lib/hidapi/native.py +++ /dev/null @@ -1,384 +0,0 @@ -"""Generic Human Interface Device API. - -It is little more than a thin ctypes layer over a native hidapi implementation. -The docstrings are mostly copied from the hidapi API header, with changes where -necessary. - -The native HID API implemenation is available at -https://github.com/signal11/hidapi. - -The native implementation comes in two flavors, hidraw (``libhidapi-hidraw.so``) -and libusb (``libhidapi-libusb.so``). For this API to work, at least one of them -must be in ``LD_LIBRARY_PATH``; otherwise an ImportError will be raised. - -Using the native hidraw implementation is recommended. -Currently the native libusb implementation (temporarily) detaches the device's -USB driver from the kernel, and it may cause the device to become unresponsive. -""" - -# -# LEGACY, no longer supported -# - -__version__ = '0.3-hidapi-0.7.0' - - -import ctypes as _C -from struct import pack as _pack - - -# -# look for a native implementation in the same directory as this file -# - -# The CDLL native library object. -_native = None - -for native_implementation in ('hidraw', 'libusb'): - try: - _native = _C.cdll.LoadLibrary('libhidapi-' + native_implementation + '.so') - break - except OSError: - pass - -if _native is None: - raise ImportError('hidapi: failed to load any HID API native implementation') - - -# -# Structures used by this API. -# - - -# used by the native implementation when enumerating, no need to expose it -class _NativeDeviceInfo(_C.Structure): - pass -_NativeDeviceInfo._fields_ = [ - ('path', _C.c_char_p), - ('vendor_id', _C.c_ushort), - ('product_id', _C.c_ushort), - ('serial', _C.c_wchar_p), - ('release', _C.c_ushort), - ('manufacturer', _C.c_wchar_p), - ('product', _C.c_wchar_p), - ('usage_page', _C.c_ushort), - ('usage', _C.c_ushort), - ('interface', _C.c_int), - ('next_device', _C.POINTER(_NativeDeviceInfo)) -] - - -# the tuple object we'll expose when enumerating devices -from collections import namedtuple -DeviceInfo = namedtuple('DeviceInfo', [ - 'path', - 'vendor_id', - 'product_id', - 'serial', - 'release', - 'manufacturer', - 'product', - 'interface', - 'driver', - ]) -del namedtuple - - -# create a DeviceInfo tuple from a hid_device object -def _makeDeviceInfo(native_device_info): - return DeviceInfo( - path=native_device_info.path.decode('ascii'), - vendor_id=hex(native_device_info.vendor_id)[2:].zfill(4), - product_id=hex(native_device_info.product_id)[2:].zfill(4), - serial=native_device_info.serial if native_device_info.serial else None, - release=hex(native_device_info.release)[2:], - manufacturer=native_device_info.manufacturer, - product=native_device_info.product, - interface=native_device_info.interface, - driver=None) - - -# -# set-up arguments and return types for each hidapi function -# - -_native.hid_init.argtypes = None -_native.hid_init.restype = _C.c_int - -_native.hid_exit.argtypes = None -_native.hid_exit.restype = _C.c_int - -_native.hid_enumerate.argtypes = [_C.c_ushort, _C.c_ushort] -_native.hid_enumerate.restype = _C.POINTER(_NativeDeviceInfo) - -_native.hid_free_enumeration.argtypes = [_C.POINTER(_NativeDeviceInfo)] -_native.hid_free_enumeration.restype = None - -_native.hid_open.argtypes = [_C.c_ushort, _C.c_ushort, _C.c_wchar_p] -_native.hid_open.restype = _C.c_void_p - -_native.hid_open_path.argtypes = [_C.c_char_p] -_native.hid_open_path.restype = _C.c_void_p - -_native.hid_close.argtypes = [_C.c_void_p] -_native.hid_close.restype = None - -_native.hid_write.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t] -_native.hid_write.restype = _C.c_int - -_native.hid_read.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t] -_native.hid_read.restype = _C.c_int - -_native.hid_read_timeout.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t, _C.c_int] -_native.hid_read_timeout.restype = _C.c_int - -_native.hid_set_nonblocking.argtypes = [_C.c_void_p, _C.c_int] -_native.hid_set_nonblocking.restype = _C.c_int - -_native.hid_send_feature_report.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t] -_native.hid_send_feature_report.restype = _C.c_int - -_native.hid_get_feature_report.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t] -_native.hid_get_feature_report.restype = _C.c_int - -_native.hid_get_manufacturer_string.argtypes = [_C.c_void_p, _C.c_wchar_p, _C.c_size_t] -_native.hid_get_manufacturer_string.restype = _C.c_int - -_native.hid_get_product_string.argtypes = [_C.c_void_p, _C.c_wchar_p, _C.c_size_t] -_native.hid_get_product_string.restype = _C.c_int - -_native.hid_get_serial_number_string.argtypes = [_C.c_void_p, _C.c_wchar_p, _C.c_size_t] -_native.hid_get_serial_number_string.restype = _C.c_int - -_native.hid_get_indexed_string.argtypes = [_C.c_void_p, _C.c_int, _C.c_wchar_p, _C.c_size_t] -_native.hid_get_indexed_string.restype = _C.c_int - -_native.hid_error.argtypes = [_C.c_void_p] -_native.hid_error.restype = _C.c_wchar_p - - -# -# exposed API -# docstrings mostly copied from hidapi.h -# - - -def init(): - """Initialize the HIDAPI library. - - This function initializes the HIDAPI library. Calling it is not strictly - necessary, as it will be called automatically by enumerate() and any of the - open_*() functions if it is needed. This function should be called at the - beginning of execution however, if there is a chance of HIDAPI handles - being opened by different threads simultaneously. - - :returns: ``True`` if successful. - """ - return _native.hid_init() == 0 - - -def exit(): - """Finalize the HIDAPI library. - - This function frees all of the static data associated with HIDAPI. It should - be called at the end of execution to avoid memory leaks. - - :returns: ``True`` if successful. - """ - return _native.hid_exit() == 0 - - -def enumerate(vendor_id=None, product_id=None, interface_number=None): - """Enumerate the HID Devices. - - List all the HID devices attached to the system, optionally filtering by - vendor_id, product_id, and/or interface_number. - - :returns: an iterable of matching ``DeviceInfo`` tuples. - """ - - devices = _native.hid_enumerate(vendor_id, product_id) - d = devices - while d: - if interface_number is None or interface_number == d.contents.interface: - yield _makeDeviceInfo(d.contents) - d = d.contents.next_device - - if devices: - _native.hid_free_enumeration(devices) - - -def open(vendor_id, product_id, serial=None): - """Open a HID device by its Vendor ID, Product ID and optional serial number. - - If no serial is provided, the first device with the specified IDs is opened. - - :returns: an opaque device handle, or ``None``. - """ - return _native.hid_open(vendor_id, product_id, serial) or None - - -def open_path(device_path): - """Open a HID device by its path name. - - :param device_path: the path of a ``DeviceInfo`` tuple returned by - enumerate(). - - :returns: an opaque device handle, or ``None``. - """ - if type(device_path) == str: - device_path = device_path.encode('ascii') - return _native.hid_open_path(device_path) or None - - -def close(device_handle): - """Close a HID device. - - :param device_handle: a device handle returned by open() or open_path(). - """ - _native.hid_close(device_handle) - - -def write(device_handle, data): - """Write an Output report to a HID device. - - :param device_handle: a device handle returned by open() or open_path(). - :param data: the data bytes to send including the report number as the - first byte. - - The first byte of data[] must contain the Report ID. For - devices which only support a single report, this must be set - to 0x0. The remaining bytes contain the report data. Since - the Report ID is mandatory, calls to hid_write() will always - contain one more byte than the report contains. For example, - if a hid report is 16 bytes long, 17 bytes must be passed to - hid_write(), the Report ID (or 0x0, for devices with a - single report), followed by the report data (16 bytes). In - this example, the length passed in would be 17. - - write() will send the data on the first OUT endpoint, if - one exists. If it does not, it will send the data through - the Control Endpoint (Endpoint 0). - - :returns: ``True`` if the write was successful. - """ - bytes_written = _native.hid_write(device_handle, _C.c_char_p(data), len(data)) - return bytes_written > -1 - - -def read(device_handle, bytes_count, timeout_ms=-1): - """Read an Input report from a HID device. - - :param device_handle: a device handle returned by open() or open_path(). - :param bytes_count: maximum number of bytes to read. - :param timeout_ms: can be -1 (default) to wait for data indefinitely, 0 to - read whatever is in the device's input buffer, or a positive integer to - wait that many milliseconds. - - Input reports are returned to the host through the INTERRUPT IN endpoint. - The first byte will contain the Report number if the device uses numbered - reports. - - :returns: the data packet read, an empty bytes string if a timeout was - reached, or None if there was an error while reading. - """ - out_buffer = _C.create_string_buffer(b'\x00' * (bytes_count + 1)) - bytes_read = _native.hid_read_timeout(device_handle, out_buffer, bytes_count, timeout_ms) - if bytes_read == -1: - return None - if bytes_read == 0: - return b'' - return out_buffer[:bytes_read] - - -def send_feature_report(device_handle, data, report_number=None): - """Send a Feature report to the device. - - :param device_handle: a device handle returned by open() or open_path(). - :param data: the data bytes to send including the report number as the - first byte. - :param report_number: if set, it is sent as the first byte with the data. - - Feature reports are sent over the Control endpoint as a - Set_Report transfer. The first byte of data[] must - contain the Report ID. For devices which only support a - single report, this must be set to 0x0. The remaining bytes - contain the report data. Since the Report ID is mandatory, - calls to send_feature_report() will always contain one - more byte than the report contains. For example, if a hid - report is 16 bytes long, 17 bytes must be passed to - send_feature_report(): the Report ID (or 0x0, for - devices which do not use numbered reports), followed by the - report data (16 bytes). - - :returns: ``True`` if the report was successfully written to the device. - """ - if report_number is not None: - data = _pack(b'!B', report_number) + data - bytes_written = _native.hid_send_feature_report(device_handle, _C.c_char_p(data), len(data)) - return bytes_written > -1 - - -def get_feature_report(device_handle, bytes_count, report_number=None): - """Get a feature report from a HID device. - - :param device_handle: a device handle returned by open() or open_path(). - :param bytes_count: how many bytes to read. - :param report_number: if set, it is sent as the report number. - - :returns: the feature report data. - """ - out_buffer = _C.create_string_buffer('\x00' * (bytes_count + 2)) - if report_number is not None: - out_buffer[0] = _pack(b'!B', report_number) - bytes_read = _native.hid_get_feature_report(device_handle, out_buffer, bytes_count) - if bytes_read > -1: - return out_buffer[:bytes_read] - - -def _read_wchar(func, device_handle, index=None): - _BUFFER_SIZE = 64 - buf = _C.create_unicode_buffer('\x00' * _BUFFER_SIZE) - if index is None: - ok = func(device_handle, buf, _BUFFER_SIZE) - else: - ok = func(device_handle, index, buf, _BUFFER_SIZE) - if ok == 0: - return buf.value - - -def get_manufacturer(device_handle): - """Get the Manufacturer String from a HID device. - - :param device_handle: a device handle returned by open() or open_path(). - """ - return _read_wchar(_native.hid_get_manufacturer_string, device_handle) - - -def get_product(device_handle): - """Get the Product String from a HID device. - - :param device_handle: a device handle returned by open() or open_path(). - """ - return _read_wchar(_native.hid_get_product_string, device_handle) - - -def get_serial(device_handle): - """Get the serial number from a HID device. - - :param device_handle: a device handle returned by open() or open_path(). - """ - serial = _read_wchar(_native.hid_get_serial_number_string, device_handle) - if serial is not None: - return ''.join(hex(ord(c)) for c in serial) - - -def get_indexed_string(device_handle, index): - """Get a string from a HID device, based on its string index. - - Note: currently not working in the ``hidraw`` native implementation. - - :param device_handle: a device handle returned by open() or open_path(). - :param index: the index of the string to get. - """ - return _read_wchar(_native.hid_get_indexed_string, device_handle, index) diff --git a/packaging/debian/copyright b/packaging/debian/copyright new file mode 100644 index 00000000..efe6ac63 --- /dev/null +++ b/packaging/debian/copyright @@ -0,0 +1,39 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0 +Upstream-Name: Solaar +Upstream-Contact: Daniel Pavel +Upstream-Source: http://github.com/pwr/Solaar + +Files: * +Copyright: Copyright (C) 2012 Daniel Pavel +License: GPL-2 + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied 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 St, Fifth Floor, Boston, MA 02110-1301, USA. + . + On Debian systems, the complete text of the GNU General Public License, + version 2, can be found in /usr/share/common-licenses/GPL-2. + +Files: share/icons/solaar*.png +Copyright: Copyright (C) 2012 Daniel Pavel +License: LGPL + . + +Files: share/icons/light_*.png +Copyright: GNOME Project +License: LGPL + These files were copied from the Gnome icon theme (weather-*). + +Files: share/icons/battery_*.png +Copyright: Oxygen Icons + Copyright (C) 2012 Daniel Pavel +License: LGPL + Based on icons from the Oxygen icon theme, with some modifications. diff --git a/packaging/debian/stdeb.cfg b/packaging/debian/stdeb.cfg new file mode 100644 index 00000000..fdb56adf --- /dev/null +++ b/packaging/debian/stdeb.cfg @@ -0,0 +1,10 @@ +[Solaar] +Source = solaar +Package = solaar +Section = misc +Copyright-File = packaging/debian/copyright +XS-Python-Version = >= 2.7 +Depends = pyudev (>= 0.13), python-gi (>= 3.2), gir1.2-gtk-3.0 +Suggests = gir1.2-notify-0.7 +Udev-Rules = rules.d/99-logitech-unifying-receiver.rules +no-backwards-compatibility = True diff --git a/setup.py b/setup.py new file mode 100755 index 00000000..ad69aac6 --- /dev/null +++ b/setup.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +from glob import glob +# from distutils.core import setup +from setuptools import setup, find_packages + + +setup(name='Solaar', + version='0.8.4', + description='Linux devices manager for the Logitech Unifying Receiver.', + long_description=''' +Solaar is a Linux device manager for Logitech's Unifying Receiver peripherals. +It is able to pair/unpair devices to the receiver, and for some devices read +battery status. +'''.strip(), + author='Daniel Pavel', + author_email='daniel.pavel@gmail.com', + license='GPLv2', + url='http://pwr.github.com/Solaar/', + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: X11 Applications :: GTK', + 'Environment :: Console', + 'Intended Audience :: End Users/Desktop', + 'License :: DFSG approved', + 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', + 'Natural Language :: English', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.2', + 'Operating System :: POSIX :: Linux', + 'Topic :: Utilities', + ], + + platforms=['linux'], + requires=['pyudev (>= 0.13)', 'gi.repository.GObject (>= 2.0)', 'gi.repository.Gtk (>= 3.0)'], + extras_require={'notifications': 'gi.repository.Notify'}, + + package_dir={'': 'src'}, + # packages=['hidapi', 'logitech', 'logitech.unifying_receiver', 'solaar', 'solaar.ui'], + packages=find_packages('src'), + + data_files=[ + # ('share/applications', ['share/applications/solaar.desktop']), + ('share/icons/hicolor/128x128/apps', ['share/icons/solaar.png']), + ('share/solaar/icons', glob('share/icons/*.png')), + # ('/etc/udev/rules.d', ['rules.d/99-logitech-unifying-receiver.rules']), + ], + + scripts=['bin/solaar', 'bin/solaar-cli'], + # entry_points={ + # 'console_scripts': ['solaar-cli = solaar.cli:main'], + # 'gui_scripts': ['solaar = solaar.gtk:main'], + # }, + ) diff --git a/share/applications/solaar.desktop b/share/applications/solaar.desktop new file mode 100644 index 00000000..7ece0187 --- /dev/null +++ b/share/applications/solaar.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Solaar +Comment=Logitech Unifying Receiver peripherals manager +Exec=solaar +Icon=solaar.png +StartupNotify=false +Terminal=false +Type=Application +Categories=Utility; diff --git a/share/icons/hicolor/128x128/status/battery_000.png b/share/icons/battery_000.png similarity index 100% rename from share/icons/hicolor/128x128/status/battery_000.png rename to share/icons/battery_000.png diff --git a/share/icons/hicolor/128x128/status/battery_010.png b/share/icons/battery_010.png similarity index 100% rename from share/icons/hicolor/128x128/status/battery_010.png rename to share/icons/battery_010.png diff --git a/share/icons/hicolor/128x128/status/battery_020.png b/share/icons/battery_020.png similarity index 100% rename from share/icons/hicolor/128x128/status/battery_020.png rename to share/icons/battery_020.png diff --git a/share/icons/hicolor/128x128/status/battery_030.png b/share/icons/battery_030.png similarity index 100% rename from share/icons/hicolor/128x128/status/battery_030.png rename to share/icons/battery_030.png diff --git a/share/icons/hicolor/128x128/status/battery_040.png b/share/icons/battery_040.png similarity index 100% rename from share/icons/hicolor/128x128/status/battery_040.png rename to share/icons/battery_040.png diff --git a/share/icons/hicolor/128x128/status/battery_050.png b/share/icons/battery_050.png similarity index 100% rename from share/icons/hicolor/128x128/status/battery_050.png rename to share/icons/battery_050.png diff --git a/share/icons/hicolor/128x128/status/battery_060.png b/share/icons/battery_060.png similarity index 100% rename from share/icons/hicolor/128x128/status/battery_060.png rename to share/icons/battery_060.png diff --git a/share/icons/hicolor/128x128/status/battery_070.png b/share/icons/battery_070.png similarity index 100% rename from share/icons/hicolor/128x128/status/battery_070.png rename to share/icons/battery_070.png diff --git a/share/icons/hicolor/128x128/status/battery_080.png b/share/icons/battery_080.png similarity index 100% rename from share/icons/hicolor/128x128/status/battery_080.png rename to share/icons/battery_080.png diff --git a/share/icons/hicolor/128x128/status/battery_090.png b/share/icons/battery_090.png similarity index 100% rename from share/icons/hicolor/128x128/status/battery_090.png rename to share/icons/battery_090.png diff --git a/share/icons/hicolor/128x128/status/battery_100.png b/share/icons/battery_100.png similarity index 100% rename from share/icons/hicolor/128x128/status/battery_100.png rename to share/icons/battery_100.png diff --git a/share/icons/hicolor/128x128/status/battery_unknown.png b/share/icons/battery_unknown.png similarity index 100% rename from share/icons/hicolor/128x128/status/battery_unknown.png rename to share/icons/battery_unknown.png diff --git a/share/icons/hicolor/128x128/status/light_000.png b/share/icons/light_000.png similarity index 100% rename from share/icons/hicolor/128x128/status/light_000.png rename to share/icons/light_000.png diff --git a/share/icons/hicolor/128x128/status/light_020.png b/share/icons/light_020.png similarity index 100% rename from share/icons/hicolor/128x128/status/light_020.png rename to share/icons/light_020.png diff --git a/share/icons/hicolor/128x128/status/light_040.png b/share/icons/light_040.png similarity index 100% rename from share/icons/hicolor/128x128/status/light_040.png rename to share/icons/light_040.png diff --git a/share/icons/hicolor/128x128/status/light_060.png b/share/icons/light_060.png similarity index 100% rename from share/icons/hicolor/128x128/status/light_060.png rename to share/icons/light_060.png diff --git a/share/icons/hicolor/128x128/status/light_080.png b/share/icons/light_080.png similarity index 100% rename from share/icons/hicolor/128x128/status/light_080.png rename to share/icons/light_080.png diff --git a/share/icons/hicolor/128x128/status/light_100.png b/share/icons/light_100.png similarity index 100% rename from share/icons/hicolor/128x128/status/light_100.png rename to share/icons/light_100.png diff --git a/share/icons/hicolor/128x128/status/light_unknown.png b/share/icons/light_unknown.png similarity index 100% rename from share/icons/hicolor/128x128/status/light_unknown.png rename to share/icons/light_unknown.png diff --git a/share/icons/hicolor/128x128/apps/Solaar-fail.png b/share/icons/solaar-fail.png similarity index 100% rename from share/icons/hicolor/128x128/apps/Solaar-fail.png rename to share/icons/solaar-fail.png diff --git a/share/icons/hicolor/128x128/apps/Solaar-init.png b/share/icons/solaar-init.png similarity index 100% rename from share/icons/hicolor/128x128/apps/Solaar-init.png rename to share/icons/solaar-init.png diff --git a/share/icons/hicolor/128x128/apps/Solaar-mask.png b/share/icons/solaar-mask.png similarity index 100% rename from share/icons/hicolor/128x128/apps/Solaar-mask.png rename to share/icons/solaar-mask.png diff --git a/share/icons/hicolor/128x128/apps/Solaar.png b/share/icons/solaar.png similarity index 100% rename from share/icons/hicolor/128x128/apps/Solaar.png rename to share/icons/solaar.png diff --git a/lib/hidapi/__init__.py b/src/hidapi/__init__.py similarity index 78% rename from lib/hidapi/__init__.py rename to src/hidapi/__init__.py index e57e28ed..72a2628c 100644 --- a/lib/hidapi/__init__.py +++ b/src/hidapi/__init__.py @@ -2,8 +2,6 @@ from __future__ import absolute_import, division, print_function, unicode_literals -__author__ = "Daniel Pavel" -__license__ = "GPL" __version__ = "0.5" from hidapi.udev import * diff --git a/lib/hidapi/hidconsole.py b/src/hidapi/hidconsole.py similarity index 92% rename from lib/hidapi/hidconsole.py rename to src/hidapi/hidconsole.py index 962df601..d3228f9d 100644 --- a/lib/hidapi/hidconsole.py +++ b/src/hidapi/hidconsole.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python -u +#!/usr/bin/env python from __future__ import absolute_import, division, print_function, unicode_literals @@ -7,20 +7,19 @@ import sys from select import select as _select import time from binascii import hexlify, unhexlify +import hidapi # # # +# no Python 3 support :( +read_packet = input interactive = os.isatty(0) -start_time = 0 -try: # python3 support - read_packet = raw_input -except: - read_packet = input prompt = '?? Input: ' if interactive else '' strhex = lambda d: hexlify(d).decode('ascii').upper() +start_time = time.time() # # @@ -28,6 +27,7 @@ strhex = lambda d: hexlify(d).decode('ascii').upper() from threading import Lock print_lock = Lock() +del Lock def _print(marker, data, scroll=False): t = time.time() - start_time @@ -104,6 +104,7 @@ def _validate_input(line, hidpp=False): return data + def _open(device, hidpp): if hidpp and not device: for d in hidapi.enumerate(vendor_id=0x046d): @@ -125,8 +126,8 @@ def _open(device, hidpp): repr(hidapi.get_manufacturer(handle)), repr(hidapi.get_product(handle)), repr(hidapi.get_serial(handle)))) - if args.hidpp: - if hidapi.get_manufacturer(handle) != 'Logitech': + if hidpp: + if hidapi.get_manufacturer(handle) != b'Logitech': sys.exit("!! Only Logitech devices support the HID++ protocol.") print (".. HID++ validation enabled.") @@ -136,16 +137,18 @@ def _open(device, hidpp): # # -if __name__ == '__main__': +def _parse_arguments(): import argparse arg_parser = argparse.ArgumentParser() arg_parser.add_argument('--history', help='history file (default ~/.hidconsole-history)') arg_parser.add_argument('--hidpp', action='store_true', help='ensure input data is a valid HID++ request') arg_parser.add_argument('device', nargs='?', help='linux device to connect to (/dev/hidrawX); ' 'may be omitted if --hidpp is given, in which case it looks for the first Logitech receiver') - args = arg_parser.parse_args() + return arg_parser.parse_args() - import hidapi + +def main(): + args = _parse_arguments() handle = _open(args.device, args.hidpp) if interactive: @@ -161,7 +164,12 @@ if __name__ == '__main__': # file may not exist yet pass - start_time = time.time() + # re-open stdout unbuffered + try: + sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) + except: + # will fail in python3 + pass try: from threading import Thread @@ -204,3 +212,7 @@ if __name__ == '__main__': hidapi.close(handle) if interactive: readline.write_history_file(args.history) + + +if __name__ == '__main__': + main() diff --git a/src/hidapi/native.py b/src/hidapi/native.py new file mode 100644 index 00000000..c2e04be8 --- /dev/null +++ b/src/hidapi/native.py @@ -0,0 +1,384 @@ +# """Generic Human Interface Device API. + +# It is little more than a thin ctypes layer over a native hidapi implementation. +# The docstrings are mostly copied from the hidapi API header, with changes where +# necessary. + +# The native HID API implemenation is available at +# https://github.com/signal11/hidapi. + +# The native implementation comes in two flavors, hidraw (``libhidapi-hidraw.so``) +# and libusb (``libhidapi-libusb.so``). For this API to work, at least one of them +# must be in ``LD_LIBRARY_PATH``; otherwise an ImportError will be raised. + +# Using the native hidraw implementation is recommended. +# Currently the native libusb implementation (temporarily) detaches the device's +# USB driver from the kernel, and it may cause the device to become unresponsive. +# """ + +# # +# # LEGACY, no longer supported +# # + +# __version__ = '0.3-hidapi-0.7.0' + + +# import ctypes as _C +# from struct import pack as _pack + + +# # +# # look for a native implementation in the same directory as this file +# # + +# # The CDLL native library object. +# _native = None + +# for native_implementation in ('hidraw', 'libusb'): +# try: +# _native = _C.cdll.LoadLibrary('libhidapi-' + native_implementation + '.so') +# break +# except OSError: +# pass + +# if _native is None: +# raise ImportError('hidapi: failed to load any HID API native implementation') + + +# # +# # Structures used by this API. +# # + + +# # used by the native implementation when enumerating, no need to expose it +# class _NativeDeviceInfo(_C.Structure): +# pass +# _NativeDeviceInfo._fields_ = [ +# ('path', _C.c_char_p), +# ('vendor_id', _C.c_ushort), +# ('product_id', _C.c_ushort), +# ('serial', _C.c_wchar_p), +# ('release', _C.c_ushort), +# ('manufacturer', _C.c_wchar_p), +# ('product', _C.c_wchar_p), +# ('usage_page', _C.c_ushort), +# ('usage', _C.c_ushort), +# ('interface', _C.c_int), +# ('next_device', _C.POINTER(_NativeDeviceInfo)) +# ] + + +# # the tuple object we'll expose when enumerating devices +# from collections import namedtuple +# DeviceInfo = namedtuple('DeviceInfo', [ +# 'path', +# 'vendor_id', +# 'product_id', +# 'serial', +# 'release', +# 'manufacturer', +# 'product', +# 'interface', +# 'driver', +# ]) +# del namedtuple + + +# # create a DeviceInfo tuple from a hid_device object +# def _makeDeviceInfo(native_device_info): +# return DeviceInfo( +# path=native_device_info.path.decode('ascii'), +# vendor_id=hex(native_device_info.vendor_id)[2:].zfill(4), +# product_id=hex(native_device_info.product_id)[2:].zfill(4), +# serial=native_device_info.serial if native_device_info.serial else None, +# release=hex(native_device_info.release)[2:], +# manufacturer=native_device_info.manufacturer, +# product=native_device_info.product, +# interface=native_device_info.interface, +# driver=None) + + +# # +# # set-up arguments and return types for each hidapi function +# # + +# _native.hid_init.argtypes = None +# _native.hid_init.restype = _C.c_int + +# _native.hid_exit.argtypes = None +# _native.hid_exit.restype = _C.c_int + +# _native.hid_enumerate.argtypes = [_C.c_ushort, _C.c_ushort] +# _native.hid_enumerate.restype = _C.POINTER(_NativeDeviceInfo) + +# _native.hid_free_enumeration.argtypes = [_C.POINTER(_NativeDeviceInfo)] +# _native.hid_free_enumeration.restype = None + +# _native.hid_open.argtypes = [_C.c_ushort, _C.c_ushort, _C.c_wchar_p] +# _native.hid_open.restype = _C.c_void_p + +# _native.hid_open_path.argtypes = [_C.c_char_p] +# _native.hid_open_path.restype = _C.c_void_p + +# _native.hid_close.argtypes = [_C.c_void_p] +# _native.hid_close.restype = None + +# _native.hid_write.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t] +# _native.hid_write.restype = _C.c_int + +# _native.hid_read.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t] +# _native.hid_read.restype = _C.c_int + +# _native.hid_read_timeout.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t, _C.c_int] +# _native.hid_read_timeout.restype = _C.c_int + +# _native.hid_set_nonblocking.argtypes = [_C.c_void_p, _C.c_int] +# _native.hid_set_nonblocking.restype = _C.c_int + +# _native.hid_send_feature_report.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t] +# _native.hid_send_feature_report.restype = _C.c_int + +# _native.hid_get_feature_report.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t] +# _native.hid_get_feature_report.restype = _C.c_int + +# _native.hid_get_manufacturer_string.argtypes = [_C.c_void_p, _C.c_wchar_p, _C.c_size_t] +# _native.hid_get_manufacturer_string.restype = _C.c_int + +# _native.hid_get_product_string.argtypes = [_C.c_void_p, _C.c_wchar_p, _C.c_size_t] +# _native.hid_get_product_string.restype = _C.c_int + +# _native.hid_get_serial_number_string.argtypes = [_C.c_void_p, _C.c_wchar_p, _C.c_size_t] +# _native.hid_get_serial_number_string.restype = _C.c_int + +# _native.hid_get_indexed_string.argtypes = [_C.c_void_p, _C.c_int, _C.c_wchar_p, _C.c_size_t] +# _native.hid_get_indexed_string.restype = _C.c_int + +# _native.hid_error.argtypes = [_C.c_void_p] +# _native.hid_error.restype = _C.c_wchar_p + + +# # +# # exposed API +# # docstrings mostly copied from hidapi.h +# # + + +# def init(): +# """Initialize the HIDAPI library. + +# This function initializes the HIDAPI library. Calling it is not strictly +# necessary, as it will be called automatically by enumerate() and any of the +# open_*() functions if it is needed. This function should be called at the +# beginning of execution however, if there is a chance of HIDAPI handles +# being opened by different threads simultaneously. + +# :returns: ``True`` if successful. +# """ +# return _native.hid_init() == 0 + + +# def exit(): +# """Finalize the HIDAPI library. + +# This function frees all of the static data associated with HIDAPI. It should +# be called at the end of execution to avoid memory leaks. + +# :returns: ``True`` if successful. +# """ +# return _native.hid_exit() == 0 + + +# def enumerate(vendor_id=None, product_id=None, interface_number=None): +# """Enumerate the HID Devices. + +# List all the HID devices attached to the system, optionally filtering by +# vendor_id, product_id, and/or interface_number. + +# :returns: an iterable of matching ``DeviceInfo`` tuples. +# """ + +# devices = _native.hid_enumerate(vendor_id, product_id) +# d = devices +# while d: +# if interface_number is None or interface_number == d.contents.interface: +# yield _makeDeviceInfo(d.contents) +# d = d.contents.next_device + +# if devices: +# _native.hid_free_enumeration(devices) + + +# def open(vendor_id, product_id, serial=None): +# """Open a HID device by its Vendor ID, Product ID and optional serial number. + +# If no serial is provided, the first device with the specified IDs is opened. + +# :returns: an opaque device handle, or ``None``. +# """ +# return _native.hid_open(vendor_id, product_id, serial) or None + + +# def open_path(device_path): +# """Open a HID device by its path name. + +# :param device_path: the path of a ``DeviceInfo`` tuple returned by +# enumerate(). + +# :returns: an opaque device handle, or ``None``. +# """ +# if type(device_path) == str: +# device_path = device_path.encode('ascii') +# return _native.hid_open_path(device_path) or None + + +# def close(device_handle): +# """Close a HID device. + +# :param device_handle: a device handle returned by open() or open_path(). +# """ +# _native.hid_close(device_handle) + + +# def write(device_handle, data): +# """Write an Output report to a HID device. + +# :param device_handle: a device handle returned by open() or open_path(). +# :param data: the data bytes to send including the report number as the +# first byte. + +# The first byte of data[] must contain the Report ID. For +# devices which only support a single report, this must be set +# to 0x0. The remaining bytes contain the report data. Since +# the Report ID is mandatory, calls to hid_write() will always +# contain one more byte than the report contains. For example, +# if a hid report is 16 bytes long, 17 bytes must be passed to +# hid_write(), the Report ID (or 0x0, for devices with a +# single report), followed by the report data (16 bytes). In +# this example, the length passed in would be 17. + +# write() will send the data on the first OUT endpoint, if +# one exists. If it does not, it will send the data through +# the Control Endpoint (Endpoint 0). + +# :returns: ``True`` if the write was successful. +# """ +# bytes_written = _native.hid_write(device_handle, _C.c_char_p(data), len(data)) +# return bytes_written > -1 + + +# def read(device_handle, bytes_count, timeout_ms=-1): +# """Read an Input report from a HID device. + +# :param device_handle: a device handle returned by open() or open_path(). +# :param bytes_count: maximum number of bytes to read. +# :param timeout_ms: can be -1 (default) to wait for data indefinitely, 0 to +# read whatever is in the device's input buffer, or a positive integer to +# wait that many milliseconds. + +# Input reports are returned to the host through the INTERRUPT IN endpoint. +# The first byte will contain the Report number if the device uses numbered +# reports. + +# :returns: the data packet read, an empty bytes string if a timeout was +# reached, or None if there was an error while reading. +# """ +# out_buffer = _C.create_string_buffer(b'\x00' * (bytes_count + 1)) +# bytes_read = _native.hid_read_timeout(device_handle, out_buffer, bytes_count, timeout_ms) +# if bytes_read == -1: +# return None +# if bytes_read == 0: +# return b'' +# return out_buffer[:bytes_read] + + +# def send_feature_report(device_handle, data, report_number=None): +# """Send a Feature report to the device. + +# :param device_handle: a device handle returned by open() or open_path(). +# :param data: the data bytes to send including the report number as the +# first byte. +# :param report_number: if set, it is sent as the first byte with the data. + +# Feature reports are sent over the Control endpoint as a +# Set_Report transfer. The first byte of data[] must +# contain the Report ID. For devices which only support a +# single report, this must be set to 0x0. The remaining bytes +# contain the report data. Since the Report ID is mandatory, +# calls to send_feature_report() will always contain one +# more byte than the report contains. For example, if a hid +# report is 16 bytes long, 17 bytes must be passed to +# send_feature_report(): the Report ID (or 0x0, for +# devices which do not use numbered reports), followed by the +# report data (16 bytes). + +# :returns: ``True`` if the report was successfully written to the device. +# """ +# if report_number is not None: +# data = _pack(b'!B', report_number) + data +# bytes_written = _native.hid_send_feature_report(device_handle, _C.c_char_p(data), len(data)) +# return bytes_written > -1 + + +# def get_feature_report(device_handle, bytes_count, report_number=None): +# """Get a feature report from a HID device. + +# :param device_handle: a device handle returned by open() or open_path(). +# :param bytes_count: how many bytes to read. +# :param report_number: if set, it is sent as the report number. + +# :returns: the feature report data. +# """ +# out_buffer = _C.create_string_buffer('\x00' * (bytes_count + 2)) +# if report_number is not None: +# out_buffer[0] = _pack(b'!B', report_number) +# bytes_read = _native.hid_get_feature_report(device_handle, out_buffer, bytes_count) +# if bytes_read > -1: +# return out_buffer[:bytes_read] + + +# def _read_wchar(func, device_handle, index=None): +# _BUFFER_SIZE = 64 +# buf = _C.create_unicode_buffer('\x00' * _BUFFER_SIZE) +# if index is None: +# ok = func(device_handle, buf, _BUFFER_SIZE) +# else: +# ok = func(device_handle, index, buf, _BUFFER_SIZE) +# if ok == 0: +# return buf.value + + +# def get_manufacturer(device_handle): +# """Get the Manufacturer String from a HID device. + +# :param device_handle: a device handle returned by open() or open_path(). +# """ +# return _read_wchar(_native.hid_get_manufacturer_string, device_handle) + + +# def get_product(device_handle): +# """Get the Product String from a HID device. + +# :param device_handle: a device handle returned by open() or open_path(). +# """ +# return _read_wchar(_native.hid_get_product_string, device_handle) + + +# def get_serial(device_handle): +# """Get the serial number from a HID device. + +# :param device_handle: a device handle returned by open() or open_path(). +# """ +# serial = _read_wchar(_native.hid_get_serial_number_string, device_handle) +# if serial is not None: +# return ''.join(hex(ord(c)) for c in serial) + + +# def get_indexed_string(device_handle, index): +# """Get a string from a HID device, based on its string index. + +# Note: currently not working in the ``hidraw`` native implementation. + +# :param device_handle: a device handle returned by open() or open_path(). +# :param index: the index of the string to get. +# """ +# return _read_wchar(_native.hid_get_indexed_string, device_handle, index) diff --git a/lib/hidapi/udev.py b/src/hidapi/udev.py similarity index 100% rename from lib/hidapi/udev.py rename to src/hidapi/udev.py diff --git a/lib/logitech/__init__.py b/src/logitech/__init__.py similarity index 69% rename from lib/logitech/__init__.py rename to src/logitech/__init__.py index af308ea8..e414df44 100644 --- a/lib/logitech/__init__.py +++ b/src/logitech/__init__.py @@ -4,6 +4,4 @@ from __future__ import absolute_import, division, print_function, unicode_literals -__author__ = "Daniel Pavel" -__license__ = "GPL" __version__ = "0.8" diff --git a/lib/logitech/unifying_receiver/__init__.py b/src/logitech/unifying_receiver/__init__.py similarity index 100% rename from lib/logitech/unifying_receiver/__init__.py rename to src/logitech/unifying_receiver/__init__.py diff --git a/lib/logitech/unifying_receiver/base.py b/src/logitech/unifying_receiver/base.py similarity index 100% rename from lib/logitech/unifying_receiver/base.py rename to src/logitech/unifying_receiver/base.py diff --git a/lib/logitech/unifying_receiver/common.py b/src/logitech/unifying_receiver/common.py similarity index 99% rename from lib/logitech/unifying_receiver/common.py rename to src/logitech/unifying_receiver/common.py index 2c24f43c..622a5853 100644 --- a/lib/logitech/unifying_receiver/common.py +++ b/src/logitech/unifying_receiver/common.py @@ -36,6 +36,9 @@ class NamedInt(int): def __ne__(self, other): return not self.__eq__(other) + def __hash__(self): + return int(self) + def __str__(self): return str(self.name) def __unicode__(self): diff --git a/lib/logitech/unifying_receiver/descriptors.py b/src/logitech/unifying_receiver/descriptors.py similarity index 100% rename from lib/logitech/unifying_receiver/descriptors.py rename to src/logitech/unifying_receiver/descriptors.py diff --git a/lib/logitech/unifying_receiver/hidpp10.py b/src/logitech/unifying_receiver/hidpp10.py similarity index 100% rename from lib/logitech/unifying_receiver/hidpp10.py rename to src/logitech/unifying_receiver/hidpp10.py diff --git a/lib/logitech/unifying_receiver/hidpp20.py b/src/logitech/unifying_receiver/hidpp20.py similarity index 100% rename from lib/logitech/unifying_receiver/hidpp20.py rename to src/logitech/unifying_receiver/hidpp20.py diff --git a/lib/logitech/unifying_receiver/listener.py b/src/logitech/unifying_receiver/listener.py similarity index 100% rename from lib/logitech/unifying_receiver/listener.py rename to src/logitech/unifying_receiver/listener.py diff --git a/lib/logitech/unifying_receiver/receiver.py b/src/logitech/unifying_receiver/receiver.py similarity index 100% rename from lib/logitech/unifying_receiver/receiver.py rename to src/logitech/unifying_receiver/receiver.py diff --git a/lib/logitech/unifying_receiver/settings.py b/src/logitech/unifying_receiver/settings.py similarity index 100% rename from lib/logitech/unifying_receiver/settings.py rename to src/logitech/unifying_receiver/settings.py diff --git a/lib/logitech/unifying_receiver/status.py b/src/logitech/unifying_receiver/status.py similarity index 100% rename from lib/logitech/unifying_receiver/status.py rename to src/logitech/unifying_receiver/status.py diff --git a/src/solaar/__init__.py b/src/solaar/__init__.py new file mode 100644 index 00000000..e09402f6 --- /dev/null +++ b/src/solaar/__init__.py @@ -0,0 +1,7 @@ +# +# +# + +from __future__ import absolute_import, division, print_function, unicode_literals + +__version__ = "0.8.4" diff --git a/app/solaar_cli.py b/src/solaar/cli.py similarity index 98% rename from app/solaar_cli.py rename to src/solaar/cli.py index a63591cf..cedbfef1 100644 --- a/app/solaar_cli.py +++ b/src/solaar/cli.py @@ -1,15 +1,12 @@ -#!/usr/bin/env python -u +#!/usr/bin/env python from __future__ import absolute_import, division, print_function, unicode_literals import sys -import solaar + NAME = 'solaar-cli' -__author__ = solaar.__author__ -__version__ = solaar.__version__ -__license__ = solaar.__license__ -del solaar +from solaar import __version__ # # @@ -377,8 +374,11 @@ def _parse_arguments(): return args -if __name__ == '__main__': +def main(): _require('pyudev', 'python-pyudev') args = _parse_arguments() receiver = _receiver() args.cmd(receiver, args) + +if __name__ == '__main__': + main() diff --git a/app/solaar.py b/src/solaar/gtk.py similarity index 85% rename from app/solaar.py rename to src/solaar/gtk.py index 94a171f1..0d38d084 100644 --- a/app/solaar.py +++ b/src/solaar/gtk.py @@ -1,12 +1,10 @@ -#!/usr/bin/env python -u +#!/usr/bin/env python from __future__ import absolute_import, division, print_function, unicode_literals + NAME = 'Solaar' -VERSION = '0.8.4' -__author__ = "Daniel Pavel " -__version__ = VERSION -__license__ = "GPL" +from solaar import __version__ # # @@ -45,7 +43,7 @@ def _parse_arguments(): def _run(args): - import ui + import solaar.ui as ui # even if --no-notifications is given on the command line, still have to # check they are available, and decide whether to put the option in the @@ -58,7 +56,7 @@ def _run(args): else: ui.action.toggle_notifications = None - from listener import DUMMY + from solaar.listener import DUMMY, ReceiverListener window = ui.main_window.create(NAME, DUMMY.name, DUMMY.max_devices, args.systray) if args.systray: menu_actions = (ui.action.toggle_notifications, @@ -67,28 +65,27 @@ def _run(args): else: icon = None - from gi.repository import Gtk, GObject + listener = [None] # initializes the receiver listener def check_for_listener(notify=False): # print ("check_for_listener", notify) - global listener - listener = None + listener[0] = None - from listener import ReceiverListener try: - listener = ReceiverListener.open(status_changed) + listener[0] = ReceiverListener.open(status_changed) except OSError: - ui.error(window, 'Permissions error', - 'Found a possible Unifying Receiver device,\n' - 'but did not have permission to open it.') + ui.error_dialog(window, 'Permissions error', + 'Found a possible Unifying Receiver device,\n' + 'but did not have permission to open it.') - if listener is None: + if listener[0] is None: if notify: status_changed(DUMMY) else: return True + from gi.repository import Gtk, GObject from logitech.unifying_receiver import status # callback delivering status notifications from the receiver/devices to the UI @@ -111,18 +108,20 @@ def _run(args): GObject.timeout_add(10, check_for_listener, True) Gtk.main() - if listener: - listener.stop() - listener.join() + if listener[0]: + listener[0].stop() + listener[0].join() ui.notify.uninit() -if __name__ == '__main__': +def main(): _require('pyudev', 'python-pyudev') _require('gi.repository', 'python-gi') _require('gi.repository.Gtk', 'gir1.2-gtk-3.0') - args = _parse_arguments() - listener = None _run(args) + + +if __name__ == '__main__': + main() diff --git a/app/listener.py b/src/solaar/listener.py similarity index 100% rename from app/listener.py rename to src/solaar/listener.py diff --git a/src/solaar/ui/__init__.py b/src/solaar/ui/__init__.py new file mode 100644 index 00000000..978fbdf9 --- /dev/null +++ b/src/solaar/ui/__init__.py @@ -0,0 +1,18 @@ +# +# +# + +from __future__ import absolute_import, division, print_function, unicode_literals + +from gi.repository import GObject, Gtk +GObject.threads_init() + + +def error_dialog(window, title, text): + m = Gtk.MessageDialog(window, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, text) + m.set_title(title) + m.run() + m.destroy() + + +from . import notify, status_icon, main_window diff --git a/app/ui/action.py b/src/solaar/ui/action.py similarity index 71% rename from app/ui/action.py rename to src/solaar/ui/action.py index d6ba15b2..fed26123 100644 --- a/app/ui/action.py +++ b/src/solaar/ui/action.py @@ -4,15 +4,17 @@ from __future__ import absolute_import, division, print_function, unicode_literals -# from sys import version as PYTHON_VERSION from gi.repository import Gtk, Gdk -import ui -from solaar import NAME as _NAME -from solaar import VERSION as _VERSION +from . import notify, pair_window +from ..ui import error_dialog -def _action(name, label, function, *args): +_NAME = 'Solaar' +from solaar import __version__ + + +def make(name, label, function, *args): action = Gtk.Action(name, label, label, None) action.set_icon_name(name) if function: @@ -20,7 +22,7 @@ def _action(name, label, function, *args): return action -def _toggle_action(name, label, function, *args): +def make_toggle(name, label, function, *args): action = Gtk.ToggleAction(name, label, label, None) action.set_icon_name(name) action.connect('activate', function, *args) @@ -32,20 +34,20 @@ def _toggle_action(name, label, function, *args): def _toggle_notifications(action): if action.get_active(): - ui.notify.init(_NAME) + notify.init('Solaar') else: - ui.notify.uninit() - action.set_sensitive(ui.notify.available) -toggle_notifications = _toggle_action('notifications', 'Notifications', _toggle_notifications) + notify.uninit() + action.set_sensitive(notify.available) +toggle_notifications = make_toggle('notifications', 'Notifications', _toggle_notifications) def _show_about_window(action): about = Gtk.AboutDialog() - about.set_icon_name(_NAME) + about.set_icon_name(_NAME.lower()) about.set_program_name(_NAME) - about.set_logo_icon_name(_NAME) - about.set_version(_VERSION) + about.set_logo_icon_name(_NAME.lower()) + about.set_version(__version__) about.set_comments('Shows status of devices connected\nto a Logitech Unifying Receiver.') about.set_copyright(b'\xC2\xA9'.decode('utf-8') + ' 2012 Daniel Pavel') @@ -68,9 +70,9 @@ def _show_about_window(action): about.run() about.destroy() -about = _action('help-about', 'About ' + _NAME, _show_about_window) +about = make('help-about', 'About ' + _NAME, _show_about_window) -quit = _action('exit', 'Quit', Gtk.main_quit) +quit = make('exit', 'Quit', Gtk.main_quit) # # @@ -79,7 +81,7 @@ quit = _action('exit', 'Quit', Gtk.main_quit) def _pair_device(action, frame): window = frame.get_toplevel() - pair_dialog = ui.pair_window.create(action, frame._device) + pair_dialog = pair_window.create(action, frame._device) pair_dialog.set_transient_for(window) pair_dialog.set_destroy_with_parent(True) pair_dialog.set_modal(True) @@ -88,7 +90,7 @@ def _pair_device(action, frame): pair_dialog.present() def pair(frame): - return _action('add', 'Pair new device', _pair_device, frame) + return make('add', 'Pair new device', _pair_device, frame) def _unpair_device(action, frame): @@ -107,7 +109,7 @@ def _unpair_device(action, frame): try: del device.receiver[device.number] except: - ui.error(window, 'Unpairing failed', 'Failed to unpair device\n%s .' % device.name) + error_dialog(window, 'Unpairing failed', 'Failed to unpair device\n%s .' % device.name) def unpair(frame): - return _action('edit-delete', 'Unpair', _unpair_device, frame) + return make('edit-delete', 'Unpair', _unpair_device, frame) diff --git a/app/ui/config_panel.py b/src/solaar/ui/config_panel.py similarity index 97% rename from app/ui/config_panel.py rename to src/solaar/ui/config_panel.py index e5f76563..762efb5a 100644 --- a/app/ui/config_panel.py +++ b/src/solaar/ui/config_panel.py @@ -6,7 +6,6 @@ from __future__ import absolute_import, division, print_function, unicode_litera from gi.repository import Gtk, GObject -import ui from logitech.unifying_receiver import settings as _settings # @@ -169,7 +168,7 @@ def update(frame): if device is None: # remove all settings widgets # if another device gets paired here, it will add its own widgets - ui.remove_children(box) + _remove_children(box) return if not box.get_visible(): @@ -183,7 +182,7 @@ def update(frame): force_read = False items = box.get_children() if len(device.settings) != len(items): - ui.remove_children(box) + _remove_children(box) if device.status: items = list(_add_settings(box, device)) assert len(device.settings) == len(items) @@ -196,3 +195,8 @@ def update(frame): if device_active: for sbox, s in zip(items, device.settings): _apply_queue.put(('read', s, force_read, sbox)) + + +def _remove_children(container): + container.foreach(lambda x, _: container.remove(x), None) + diff --git a/app/ui/__init__.py b/src/solaar/ui/icons.py similarity index 51% rename from app/ui/__init__.py rename to src/solaar/ui/icons.py index a7cd2491..3d01f96d 100644 --- a/app/ui/__init__.py +++ b/src/solaar/ui/icons.py @@ -4,20 +4,30 @@ from __future__ import absolute_import, division, print_function, unicode_literals -from gi.repository import GObject, Gtk -GObject.threads_init() +from gi.repository import Gtk + +# +# +# _LARGE_SIZE = 64 Gtk.IconSize.LARGE = Gtk.icon_size_register('large', _LARGE_SIZE, _LARGE_SIZE) # Gtk.IconSize.XLARGE = Gtk.icon_size_register('x-large', _LARGE_SIZE * 2, _LARGE_SIZE * 2) - -from . import notify, status_icon, main_window, pair_window, action - -from solaar import NAME -APP_ICON = { 1: NAME, 2: NAME + '-mask', 0: NAME + '-init', -1: NAME + '-fail' } +# print ("menu", int(Gtk.IconSize.MENU), Gtk.icon_size_lookup(Gtk.IconSize.MENU)) +# print ("small toolbar", int(Gtk.IconSize.SMALL_TOOLBAR), Gtk.icon_size_lookup(Gtk.IconSize.SMALL_TOOLBAR)) +# print ("large toolbar", int(Gtk.IconSize.LARGE_TOOLBAR), Gtk.icon_size_lookup(Gtk.IconSize.LARGE_TOOLBAR)) +# print ("button", int(Gtk.IconSize.BUTTON), Gtk.icon_size_lookup(Gtk.IconSize.BUTTON)) +# print ("dnd", int(Gtk.IconSize.DND), Gtk.icon_size_lookup(Gtk.IconSize.DND)) +# print ("dialog", int(Gtk.IconSize.DIALOG), Gtk.icon_size_lookup(Gtk.IconSize.DIALOG)) -def get_battery_icon(level): +APP_ICON = { 1: 'solaar', 2: 'solaar-mask', 0: 'solaar-init', -1: 'solaar-fail' } + +# +# +# + +def battery(level): if level < 0: return 'battery_unknown' return 'battery_%03d' % (10 * ((level + 5) // 10)) @@ -67,45 +77,3 @@ def icon_file(name, size=_LARGE_SIZE): theme = Gtk.IconTheme.get_default() if theme.has_icon(name): return theme.lookup_icon(name, size, 0).get_filename() - - -def error(window, title, text): - m = Gtk.MessageDialog(window, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, text) - m.set_title(title) - m.run() - m.destroy() - - -def remove_children(container): - container.foreach(lambda x, _: container.remove(x), None) - - -# def find_children(container, *child_names): -# assert container is not None -# assert isinstance(container, Gtk.Container) -# -# def _iterate_children(widget, names, result, count): -# assert isinstance(widget, Gtk.Widget) -# wname = widget.get_name() -# if wname in names: -# index = names.index(wname) -# names[index] = None -# result[index] = widget -# count -= 1 -# -# if count > 0 and isinstance(widget, Gtk.Container): -# for w in widget: -# # assert isinstance(w, Gtk.Widget): -# count = _iterate_children(w, names, result, count) -# if count == 0: -# break -# -# return count -# -# names = list(child_names) -# count = len(names) -# result = [None] * count -# if _iterate_children(container, names, result, count) > 0: -# # some children could not be found -# pass -# return tuple(result) if count > 1 else result[0] diff --git a/src/solaar/ui/indicate.py b/src/solaar/ui/indicate.py new file mode 100644 index 00000000..2a42a8ae --- /dev/null +++ b/src/solaar/ui/indicate.py @@ -0,0 +1,62 @@ +# +# +# + +# import logging + +# try: +# from gi.repository import Indicate +# from time import time as _timestamp + +# # import ui + +# # necessary because the notifications daemon does not know about our XDG_DATA_DIRS +# _icons = {} + +# # def _icon(title): +# # if title not in _icons: +# # _icons[title] = ui.icon_file(title) + +# # return _icons.get(title) + +# def init(app_title): +# global available + +# try: +# s = Indicate.Server() +# s.set_type('message.im') +# s.set_default() +# print s +# s.show() +# s.connect('server-display', server_display) + +# i = Indicate.Indicator() +# i.set_property('sender', 'test message sender') +# i.set_property('body', 'test message body') +# i.set_property_time('time', _timestamp()) +# i.set_subtype('im') +# print i, i.list_properties() +# i.show() +# i.connect('user-display', display) + +# pass +# except: +# available = False + +# init('foo') + +# # assumed to be working since the import succeeded +# available = True + +# def server_display(s): +# print 'server display', s + +# def display(i): +# print "indicator display", i +# i.hide() + +# except ImportError: +# available = False +# init = lambda app_title: False +# uninit = lambda: None +# show = lambda dev: None diff --git a/app/ui/main_window.py b/src/solaar/ui/main_window.py similarity index 93% rename from app/ui/main_window.py rename to src/solaar/ui/main_window.py index 3022ae2c..7f10ca59 100644 --- a/app/ui/main_window.py +++ b/src/solaar/ui/main_window.py @@ -6,10 +6,13 @@ from __future__ import absolute_import, division, print_function, unicode_litera from gi.repository import Gtk, Gdk, GObject -import ui from logitech.unifying_receiver import status as _status from . import config_panel as _config_panel +from . import action as _action, icons as _icons +# +# +# _RECEIVER_ICON_SIZE = Gtk.IconSize.LARGE_TOOLBAR _DEVICE_ICON_SIZE = Gtk.IconSize.DIALOG @@ -27,7 +30,7 @@ def _make_receiver_box(name): frame._device = None frame.set_name(name) - icon_set = ui.device_icon_set(name) + icon_set = _icons.device_icon_set(name) icon = Gtk.Image.new_from_icon_set(icon_set, _RECEIVER_ICON_SIZE) icon.set_padding(2, 2) frame._icon = icon @@ -76,9 +79,9 @@ def _make_receiver_box(name): if active: GObject.timeout_add(50, _update_info_label, f) - toggle_info_action = ui.action._toggle_action('info', 'Details', _toggle_info_label, frame) + toggle_info_action = _action.make_toggle('info', 'Details', _toggle_info_label, frame) toolbar.insert(toggle_info_action.create_tool_item(), 0) - toolbar.insert(ui.action.pair(frame).create_tool_item(), -1) + toolbar.insert(_action.pair(frame).create_tool_item(), -1) # toolbar.insert(ui.action.about.create_tool_item(), -1) vbox = Gtk.VBox(homogeneous=False, spacing=2) @@ -109,7 +112,7 @@ def _make_device_box(index): label.set_padding(4, 0) frame._label = label - battery_icon = Gtk.Image.new_from_icon_name(ui.get_battery_icon(-1), _STATUS_ICON_SIZE) + battery_icon = Gtk.Image.new_from_icon_name(_icons.battery(-1), _STATUS_ICON_SIZE) battery_label = Gtk.Label() battery_label.set_width_chars(6) @@ -208,9 +211,9 @@ def _make_device_box(index): if active: GObject.timeout_add(30, _config_panel.update, f) - toggle_info_action = ui.action._toggle_action('info', 'Details', _toggle_info_label, frame) + toggle_info_action = _action.make_toggle('info', 'Details', _toggle_info_label, frame) toolbar.insert(toggle_info_action.create_tool_item(), 0) - toggle_config_action = ui.action._toggle_action('preferences-system', 'Configuration', _toggle_config, frame) + toggle_config_action = _action.make_toggle('preferences-system', 'Configuration', _toggle_config, frame) toolbar.insert(toggle_config_action.create_tool_item(), -1) vbox = Gtk.VBox(homogeneous=False, spacing=2) @@ -224,7 +227,7 @@ def _make_device_box(index): unpair = Gtk.Button('Unpair') unpair.set_image(Gtk.Image.new_from_icon_name('edit-delete', Gtk.IconSize.BUTTON)) - unpair.connect('clicked', ui.action._unpair_device, frame) + unpair.connect('clicked', _action._unpair_device, frame) unpair.set_relief(Gtk.ReliefStyle.NONE) unpair.set_property('margin-left', 106) unpair.set_property('margin-right', 106) @@ -242,7 +245,7 @@ def _make_device_box(index): def create(title, name, max_devices, systray=False): window = Gtk.Window() window.set_title(title) - window.set_icon_name(ui.APP_ICON[0]) + window.set_icon_name(_icons.APP_ICON[0]) window.set_role('status-window') vbox = Gtk.VBox(homogeneous=False, spacing=12) @@ -357,7 +360,7 @@ def _update_device_box(frame, dev): if first_run: frame._device = dev frame.set_name(dev.name) - icon_set = ui.device_icon_set(dev.name, dev.kind) + icon_set = _icons.device_icon_set(dev.name, dev.kind) frame._icon.set_from_icon_set(icon_set, _DEVICE_ICON_SIZE) frame._label.set_markup('%s' % dev.name) for i in frame._toolbar.get_children(): @@ -371,11 +374,11 @@ def _update_device_box(frame, dev): if battery_level is None: battery_icon.set_sensitive(False) - battery_icon.set_from_icon_name(ui.get_battery_icon(-1), _STATUS_ICON_SIZE) + battery_icon.set_from_icon_name(_icons.battery(-1), _STATUS_ICON_SIZE) battery_label.set_markup('no status') battery_label.set_sensitive(True) else: - battery_icon.set_from_icon_name(ui.get_battery_icon(battery_level), _STATUS_ICON_SIZE) + battery_icon.set_from_icon_name(_icons.battery(battery_level), _STATUS_ICON_SIZE) battery_icon.set_sensitive(True) battery_label.set_text('%d%%' % battery_level) battery_label.set_sensitive(True) @@ -419,7 +422,7 @@ def _update_device_box(frame, dev): def update(window, receiver, device=None): assert receiver is not None # print ("update", receiver, receiver.status, len(receiver), device) - window.set_icon_name(ui.APP_ICON[1 if receiver else -1]) + window.set_icon_name(_icons.APP_ICON[1 if receiver else -1]) vbox = window.get_child() frames = list(vbox.get_children()) diff --git a/app/ui/notify.py b/src/solaar/ui/notify.py similarity index 87% rename from app/ui/notify.py rename to src/solaar/ui/notify.py index 17fe1112..6e583402 100644 --- a/app/ui/notify.py +++ b/src/solaar/ui/notify.py @@ -10,12 +10,15 @@ try: from gi.repository import Notify import logging - import ui + from . import icons as _icons + # assumed to be working since the import succeeded available = True - _notifications = {} + # cache references to shown notifications here, so if another status comes + # while its notification is still visible we don't create another one + _notifications = {} def init(app_title): """Init the notifications system.""" @@ -53,7 +56,7 @@ try: # we need to use the filename here because the notifications daemon # is an external application that does not know about our icon sets - n.update(summary, message, ui.device_icon_file(dev.name, dev.kind)) + n.update(summary, message, _icons.device_icon_file(dev.name, dev.kind)) urgency = Notify.Urgency.LOW if dev.status else Notify.Urgency.NORMAL n.set_urgency(urgency) diff --git a/app/ui/pair_window.py b/src/solaar/ui/pair_window.py similarity index 98% rename from app/ui/pair_window.py rename to src/solaar/ui/pair_window.py index a6944ff6..b52d0550 100644 --- a/app/ui/pair_window.py +++ b/src/solaar/ui/pair_window.py @@ -10,7 +10,7 @@ from logging import getLogger, DEBUG as _DEBUG _log = getLogger('pair-window') del getLogger -import ui +from . import icons as _icons from logitech.unifying_receiver import status as _status # @@ -143,7 +143,7 @@ def _pairing_succeeded(assistant, receiver, device): page.pack_start(header, False, False, 0) device_icon = Gtk.Image() - icon_set = ui.device_icon_set(device.name, device.kind) + icon_set = _icons.device_icon_set(device.name, device.kind) device_icon.set_from_icon_set(icon_set, Gtk.IconSize.LARGE) device_icon.set_alignment(0.5, 1) page.pack_start(device_icon, True, True, 0) diff --git a/app/ui/status_icon.py b/src/solaar/ui/status_icon.py similarity index 86% rename from app/ui/status_icon.py rename to src/solaar/ui/status_icon.py index 8344ca49..6d730909 100644 --- a/app/ui/status_icon.py +++ b/src/solaar/ui/status_icon.py @@ -6,7 +6,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera from gi.repository import Gtk, GObject, GdkPixbuf -import ui +from . import action as _action, icons as _icons from logitech.unifying_receiver import status as _status # @@ -20,18 +20,18 @@ def create(window, menu_actions=None): icon = Gtk.StatusIcon() icon.set_title(name) icon.set_name(name) - icon.set_from_icon_name(ui.APP_ICON[0]) + icon.set_from_icon_name(_icons.APP_ICON[0]) icon._devices = list(_NO_DEVICES) icon.set_tooltip_text(name) icon.connect('activate', window.toggle_visible) menu = Gtk.Menu() - for action in menu_actions or (): - if action: - menu.append(action.create_menu_item()) + for a in menu_actions or (): + if a: + menu.append(a.create_menu_item()) - menu.append(ui.action.quit.create_menu_item()) + menu.append(_action.quit.create_menu_item()) menu.show_all() icon.connect('popup_menu', @@ -54,16 +54,16 @@ def create(window, menu_actions=None): _PIXMAPS = {} def _icon_with_battery(level, active): - battery_icon = ui.get_battery_icon(level) + battery_icon = _icons.battery(level) name = '%s-%s' % (battery_icon, active) if name not in _PIXMAPS: - mask = ui.icon_file(ui.APP_ICON[2], 128) + mask = _icons.icon_file(_icons.APP_ICON[2], 128) assert mask mask = GdkPixbuf.Pixbuf.new_from_file(mask) assert mask.get_width() == 128 and mask.get_height() == 128 mask.saturate_and_pixelate(mask, 0.7, False) - battery = ui.icon_file(battery_icon, 128) + battery = _icons.icon_file(battery_icon, 128) assert battery battery = GdkPixbuf.Pixbuf.new_from_file(battery) assert battery.get_width() == 128 and battery.get_height() == 128 @@ -86,7 +86,7 @@ def update(icon, receiver, device=None): return def _lines(r, devices): - yield '%s: %s' % (ui.NAME, r.status) + yield 'Solaar: %s' % r.status yield '' for dev in devices: @@ -121,6 +121,6 @@ def update(icon, receiver, device=None): battery_level = level if battery_status is None: - icon.set_from_icon_name(ui.APP_ICON[1 if receiver else -1]) + icon.set_from_icon_name(_icons.APP_ICON[1 if receiver else -1]) else: icon.set_from_pixbuf(_icon_with_battery(battery_level, bool(battery_status))) diff --git a/tools/build_deb.sh b/tools/build_deb.sh new file mode 100755 index 00000000..5d751278 --- /dev/null +++ b/tools/build_deb.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cd `dirname "$0"`/.. +exec python setup.py sdist_dsc -x packaging/debian/stdeb.cfg bdist_deb "$@" diff --git a/tools/hidconsole b/tools/hidconsole new file mode 100755 index 00000000..aed2ce5b --- /dev/null +++ b/tools/hidconsole @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# -*- python-mode -*- +"""Takes care of starting the main function.""" + +from __future__ import absolute_import + +import sys + + +def solaar_init(): + import os.path as _path + + prefix = _path.dirname(_path.dirname(_path.realpath(_path.abspath(__file__)))) + + dist_lib = _path.join(prefix, 'share', 'solaar', 'lib') + src_lib = _path.join(prefix, 'src') + + for location in (dist_lib, src_lib): + init_py = _path.join(location, 'hidapi', '__init__.py') + if _path.exists(init_py): + sys.path.insert(1, location) + break + + +if __name__ == '__main__': + if sys.version_info >= (2, 8): + sys.exit("Python 3 is not supported by hidconsole.") + + solaar_init() + from hidapi import hidconsole + hidconsole.main() diff --git a/bin/scan-registers b/tools/scan-registers.sh similarity index 100% rename from bin/scan-registers rename to tools/scan-registers.sh