added setup.py for python and debian packaging

This commit is contained in:
Daniel Pavel 2012-12-15 20:27:19 +02:00
parent 115d5c7db1
commit 064a7a113c
64 changed files with 820 additions and 563 deletions

4
.gitignore vendored
View File

@ -1,4 +1,6 @@
*.so
*.pyc
*.pyo
__pycache__/
*.log
/src/Solaar.egg-info/

View File

@ -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

View File

@ -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 "$@"

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -0,0 +1,39 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0
Upstream-Name: Solaar
Upstream-Contact: Daniel Pavel <daniel.pavel@gmail.com>
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.

View File

@ -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

54
setup.py Executable file
View File

@ -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'],
# },
)

View File

@ -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;

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 800 B

After

Width:  |  Height:  |  Size: 800 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -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 *

View File

@ -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()

384
src/hidapi/native.py Normal file
View File

@ -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)

View File

@ -4,6 +4,4 @@
from __future__ import absolute_import, division, print_function, unicode_literals
__author__ = "Daniel Pavel"
__license__ = "GPL"
__version__ = "0.8"

View File

@ -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):

7
src/solaar/__init__.py Normal file
View File

@ -0,0 +1,7 @@
#
#
#
from __future__ import absolute_import, division, print_function, unicode_literals
__version__ = "0.8.4"

View File

@ -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()

View File

@ -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 <daniel.pavel@gmail.com>"
__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()

18
src/solaar/ui/__init__.py Normal file
View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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]

62
src/solaar/ui/indicate.py Normal file
View File

@ -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

View File

@ -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('<b>%s</b>' % 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('<small>no status</small>')
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())

View File

@ -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)

View File

@ -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)

View File

@ -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 '<b>%s</b>: %s' % (ui.NAME, r.status)
yield '<b>Solaar</b>: %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)))

4
tools/build_deb.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
cd `dirname "$0"`/..
exec python setup.py sdist_dsc -x packaging/debian/stdeb.cfg bdist_deb "$@"

31
tools/hidconsole Executable file
View File

@ -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()