Index: sysutils/Makefile =================================================================== --- sysutils/Makefile +++ sysutils/Makefile @@ -1076,6 +1076,7 @@ SUBDIR += snowlog SUBDIR += socket SUBDIR += socklog + SUBDIR += solaar SUBDIR += sortu SUBDIR += spindown SUBDIR += spinner Index: sysutils/solaar/Makefile =================================================================== --- /dev/null +++ sysutils/solaar/Makefile @@ -0,0 +1,43 @@ +# $FreeBSD$ + +PORTNAME= solaar +PORTVERSION= g20170327 +CATEGORIES= sysutils + +MAINTAINER= tobik@FreeBSD.org +COMMENT= Device manager for the Logitech Unifying Receiver + +LICENSE= GPLv2+ +LICENSE_FILE= ${WRKSRC}/COPYING + +# libhidapi.so is only loaded at runtime via ctypes (dlopen) +RUN_DEPENDS= ${LOCALBASE}/lib/libhidapi.so:comms/hidapi + +USES= python +USE_PYTHON= distutils autoplist + +USE_GITHUB= yes +GH_ACCOUNT= pwr +GH_PROJECT= Solaar +# The latest release is from 2013 and there is not going to be a new +# release for now: +# https://github.com/pwr/Solaar/issues/288 +# https://github.com/pwr/Solaar/issues/296 +GH_TAGNAME= 53ec751 + +NO_ARCH= yes + +OPTIONS_DEFINE= GUI +GUI_RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}gobject3>=0:devel/py${PYTHON_MAJOR_VER:S/2//}-gobject3 + +post-patch: + @${CP} ${FILESDIR}/hidapi.py ${WRKSRC}/lib/hidapi/udev.py + @${REINPLACE_CMD} -e '/pyudev/d' \ + -e 's|python-gi|${PYTHON_PKGNAMEPREFIX}gobject3|' \ + ${WRKSRC}/lib/solaar/gtk.py + @${REINPLACE_CMD} 's|receiver\.path\.split.*|receiver.path)|' \ + ${WRKSRC}/lib/logitech_receiver/listener.py + @${REINPLACE_CMD} 's|[[:<:]]cmd[[:>:]]|action|' \ + ${WRKSRC}/lib/solaar/cli/__init__.py + +.include Index: sysutils/solaar/distinfo =================================================================== --- /dev/null +++ sysutils/solaar/distinfo @@ -0,0 +1,3 @@ +TIMESTAMP = 1490648037 +SHA256 (pwr-Solaar-g20170327-53ec751_GH0.tar.gz) = 0dca927e30c5436215d732a328d72f6d73b2a1878df6efa4ba054b1dbec9310f +SIZE (pwr-Solaar-g20170327-53ec751_GH0.tar.gz) = 1189828 Index: sysutils/solaar/files/hidapi.py =================================================================== --- /dev/null +++ sysutils/solaar/files/hidapi.py @@ -0,0 +1,186 @@ +# $FreeBSD$ +# This is based on previous support for libhidapi which was dropped in +# upstream commit f5d2eba. +import ctypes as _C +from struct import pack as _pack + +_native = _C.CDLL("libhidapi.so") + +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)) +] + +from collections import namedtuple +DeviceInfo = namedtuple('DeviceInfo', [ + 'path', + 'vendor_id', + 'product_id', + 'serial', + 'release', + 'manufacturer', + 'product', + 'interface', + 'driver', +]) +del namedtuple + +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) + +_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 + +def init(): + return _native.hid_init() == 0 + +def exit(): + return _native.hid_exit() == 0 + +def monitor_glib(callback, *device_filters): + pass + +def enumerate(vendor_id=None, product_id=None, interface_number=None, hid_driver=None): + 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): + return _native.hid_open(vendor_id, product_id, serial) or None + +def open_path(device_path): + if type(device_path) == str: + device_path = device_path.encode('ascii') + return _native.hid_open_path(device_path) or None + +def close(device_handle): + _native.hid_close(device_handle) + +def write(device_handle, data): + bytes_written = _native.hid_write(device_handle, _C.c_char_p(data), len(data)) + if bytes_written != len(data): + raise IOError(_errno.EIO, 'written %d bytes out of expected %d' % (bytes_written, len(data))) + +def read(device_handle, bytes_count, timeout_ms=-1): + 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): + 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): + 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): + return _read_wchar(_native.hid_get_manufacturer_string, device_handle) + +def get_product(device_handle): + return _read_wchar(_native.hid_get_product_string, device_handle) + +def get_serial(device_handle): + 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): + return _read_wchar(_native.hid_get_indexed_string, device_handle, index) Index: sysutils/solaar/files/patch-setup.py =================================================================== --- /dev/null +++ sysutils/solaar/files/patch-setup.py @@ -0,0 +1,32 @@ +--- setup.py.orig 2017-03-20 23:07:26 UTC ++++ setup.py +@@ -6,10 +6,8 @@ from distutils.core import setup + autostart_path = '/etc/xdg/autostart' + + import sys +-backup_path_0 = sys.path[0] +-sys.path[0] = backup_path_0 + '/lib' +-from solaar import NAME, __version__ +-sys.path[0] = backup_path_0 ++NAME = 'Solaar' ++__version__ = '0.9.2' + + if 'install' in sys.argv: + # naively guess where the autostart .desktop file should be installed +@@ -22,7 +20,7 @@ if 'install' in sys.argv: + autostart_path = path.join(xdg_config_home, 'autostart') + del environ, path, xdg_config_home + +-del sys, backup_path_0 ++del sys + + + def _data_files(): +@@ -36,7 +34,6 @@ def _data_files(): + yield _dirname(mo), [mo] + + yield 'share/applications', ['share/applications/solaar.desktop'] +- yield autostart_path, ['share/applications/solaar.desktop'] + + del _dirname + Index: sysutils/solaar/pkg-descr =================================================================== --- /dev/null +++ sysutils/solaar/pkg-descr @@ -0,0 +1,9 @@ +Solaar is a device manager for Logitech's Unifying Receiver. It is +able to pair/unpair devices to the receiver, and for most devices read +battery status. + +It comes in two flavors, command-line and GUI. Both are able to list +the devices paired to a Unifying Receiver, show detailed info for each +device, and also pair/unpair supported devices with the receiver. + +WWW: https://pwr.github.io/Solaar/