added setup.py for python and debian packaging
|
@ -1,4 +1,6 @@
|
|||
*.so
|
||||
*.pyc
|
||||
*.pyo
|
||||
__pycache__/
|
||||
*.log
|
||||
|
||||
/src/Solaar.egg-info/
|
||||
|
|
10
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
|
||||
|
|
|
@ -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 "$@"
|
43
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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
|
@ -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.
|
|
@ -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
|
|
@ -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'],
|
||||
# },
|
||||
)
|
|
@ -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;
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 800 B After Width: | Height: | Size: 800 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -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 *
|
|
@ -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
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
interactive = os.isatty(0)
|
||||
start_time = 0
|
||||
try: # python3 support
|
||||
read_packet = raw_input
|
||||
except:
|
||||
# no Python 3 support :(
|
||||
read_packet = input
|
||||
interactive = os.isatty(0)
|
||||
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()
|
|
@ -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)
|
|
@ -4,6 +4,4 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
__author__ = "Daniel Pavel"
|
||||
__license__ = "GPL"
|
||||
__version__ = "0.8"
|
|
@ -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):
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
#
|
||||
#
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
__version__ = "0.8.4"
|
|
@ -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()
|
|
@ -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',
|
||||
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()
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
@ -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]
|
|
@ -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
|
|
@ -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())
|
|
@ -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)
|
||||
|
|
@ -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)
|
|
@ -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)))
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd `dirname "$0"`/..
|
||||
exec python setup.py sdist_dsc -x packaging/debian/stdeb.cfg bdist_deb "$@"
|
|
@ -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()
|