docs: update for PERSISTENT_REMAPPABLE_ACTION and class-based settings

This commit is contained in:
Peter F. Patel-Schneider 2021-12-31 23:23:43 -05:00
parent d646e18543
commit 3e6c92de70
4 changed files with 100 additions and 102 deletions

View File

@ -50,8 +50,13 @@ its wireless PID as reported by Solaar. The software identity of devices that
connect via a USB cable or via bluetooth can be determined by their USB or connect via a USB cable or via bluetooth can be determined by their USB or
Bluetooth product ID. Bluetooth product ID.
Even something as fundamental as pairing works differently for different
receivers. For Unifying receivers, pairing adds a new paired device, but # Pairing and Unpairing
Solaar is able to pair and unpair devices with
receivers as supported by the device and receiver.
For Unifying receivers, pairing adds a new paired device, but
only if there is an open slot on the receiver. So these receivers need to only if there is an open slot on the receiver. So these receivers need to
be able to unpair devices that they have been paired with or else they will be able to unpair devices that they have been paired with or else they will
not have any open slots for pairing. Some other receivers, like the not have any open slots for pairing. Some other receivers, like the
@ -72,13 +77,10 @@ without the Unifying logo can probably only connect to the kind of receiver
that they were bought with. that they were bought with.
## Supported Features ## Device Settings
Solaar uses the HID++ protocol to pair devices to receivers and unpair Solaar can display quite a few changeable settings of receivers and devices.
devices from receivers, and also uses the HID++ protocol to display For a list of HID++ features and their support see [the features page](features).
features of receivers and devices. Currently it only displays some
features, and can modify even fewer. For a list of HID++ features
and their support see [the features page](features).
Solaar does not do much beyond using the HID++ protocol to change the Solaar does not do much beyond using the HID++ protocol to change the
behavior of receivers and devices via changing their settings. behavior of receivers and devices via changing their settings.
@ -91,14 +93,17 @@ Settings can only be changed in the Solaar GUI when they are unlocked.
To unlock a setting click on the icon at the right-hand edge of the setting To unlock a setting click on the icon at the right-hand edge of the setting
until an unlocked lock appears (with tooltop "Changes allowed"). until an unlocked lock appears (with tooltop "Changes allowed").
Solaar keep tracks of the changeable settings of a device. Solaar keeps track of most of the changeable settings of a device.
Most devices forget changed settings when the are turned off Devices forget most changed settings when the device is turned off
or go into a power-saving mode. When Solaar starts, it restores on-line or goes into a power-saving mode.
devices to their previously-known state, and while running it restores The exceptions include the setting to change the host the device is connected to
and the setting to persistently change what a key or button does.
When Solaar starts, it restores on-line devices to their previously-known state
for the unexceptionable settings and while running it restores
devices to their previously-known state when the device itself comes on line. devices to their previously-known state when the device itself comes on line.
This information is stored in the file `~/.config/solaar/config.json`. Setting information is stored in the file `~/.config/solaar/config.json`.
Updating of settings can be turned off in the Solaar GUI by clicking on the icon Updating of a setting can be turned off in the Solaar GUI by clicking on the icon
at the right-hand edge of the setting until a red icon appears (with tooltip at the right-hand edge of the setting until a red icon appears (with tooltip
"Ignore this setting" ). "Ignore this setting" ).
@ -140,6 +145,7 @@ Users can edit rules using a GUI by clicking on the `Edit Rule` button in the So
Solaar rules is an experimental feature. Significant changes might be made in response to problems. Solaar rules is an experimental feature. Significant changes might be made in response to problems.
### Sliding DPI ### Sliding DPI
A few mice (such as the MX Vertical) have a button that is supposed to be used to change A few mice (such as the MX Vertical) have a button that is supposed to be used to change
@ -155,7 +161,6 @@ the previous value that was set is applied to the mouse.
Notifications from Solaar are displayed while the mouse button is done Notifications from Solaar are displayed while the mouse button is done
showing the setting that will be applied. showing the setting that will be applied.
### Mouse Gestures ### Mouse Gestures
Some mice (such as the MX Master 3) have a button that is supposed to be used to Some mice (such as the MX Master 3) have a button that is supposed to be used to

View File

@ -14,11 +14,11 @@ Feature | ID | Status | Notes
`ROOT` | `0x0000` | Supported | System `ROOT` | `0x0000` | Supported | System
`FEATURE_SET` | `0x0001` | Supported | System `FEATURE_SET` | `0x0001` | Supported | System
`FEATURE_INFO` | `0x0002` | Supported | System `FEATURE_INFO` | `0x0002` | Supported | System
`DEVICE_FW_VERSION` | `0x0003` | Supported | `get_firmware`, read only `DEVICE_FW_VERSION` | `0x0003` | Supported | `get_firmware`, `get_ids`, read only
`DEVICE_UNIT_ID` | `0x0004` | Unsupported | `DEVICE_UNIT_ID` | `0x0004` | Unsupported |
`DEVICE_NAME` | `0x0005` | Supported | `get_kind`, `get_name`, read only `DEVICE_NAME` | `0x0005` | Supported | `get_kind`, `get_name`, read only
`DEVICE_GROUPS` | `0x0006` | Unsupported | `DEVICE_GROUPS` | `0x0006` | Unsupported |
`DEVICE_FRIENDLY_NAME` | `0x0007` | Supported | read only `DEVICE_FRIENDLY_NAME` | `0x0007` | Supported | `get_friendly_name`, read only
`KEEP_ALIVE` | `0x0008` | Unsupported | `KEEP_ALIVE` | `0x0008` | Unsupported |
`RESET` | `0x0020` | Unsupported | aka “Config Change” `RESET` | `0x0020` | Unsupported | aka “Config Change”
`CRYPTO_ID` | `0x0021` | Unsupported | `CRYPTO_ID` | `0x0021` | Unsupported |
@ -30,26 +30,27 @@ Feature | ID | Status | Notes
`DFU` | `0x00D0` | Unsupported | `DFU` | `0x00D0` | Unsupported |
`BATTERY_STATUS` | `0x1000` | Supported | `get_battery`, read only `BATTERY_STATUS` | `0x1000` | Supported | `get_battery`, read only
`BATTERY_VOLTAGE` | `0x1001` | Supported | `get_voltage`, read only `BATTERY_VOLTAGE` | `0x1001` | Supported | `get_voltage`, read only
`UNIFIED_BATTERY` | `0x1004` | Supported | `get_battery`, read only
`CHARGING_CONTROL` | `0x1010` | Unsupported | `CHARGING_CONTROL` | `0x1010` | Unsupported |
`LED_CONTROL` | `0x1300` | Unsupported | `LED_CONTROL` | `0x1300` | Unsupported |
`GENERIC_TEST` | `0x1800` | Unsupported | `GENERIC_TEST` | `0x1800` | Unsupported |
`DEVICE_RESET` | `0x1802` | Unsupported | `DEVICE_RESET` | `0x1802` | Unsupported |
`OOBSTATE` | `0x1805` | Unsupported | `OOBSTATE` | `0x1805` | Unsupported |
`CONFIG_DEVICE_PROPS` | `0x1806` | Unsupported | `CONFIG_DEVICE_PROPS` | `0x1806` | Unsupported |
`CHANGE_HOST` | `0x1814` | Supported | `CHANGE_HOST` | `0x1814` | Supported | `ChangeHost`
`HOSTS_INFO` | `0x1815` | Partial Support | `get_host_names`, partial listing only `HOSTS_INFO` | `0x1815` | Partial Support | `get_host_names`, partial listing only
`BACKLIGHT` | `0x1981` | Unsupported | `BACKLIGHT` | `0x1981` | Unsupported |
`BACKLIGHT2` | `0x1982` | Supported | `_feature_backlight2` `BACKLIGHT2` | `0x1982` | Supported | `Backlight2`
`BACKLIGHT3` | `0x1983` | Unsupported | `BACKLIGHT3` | `0x1983` | Unsupported |
`PRESENTER_CONTROL` | `0x1A00` | Unsupported | `PRESENTER_CONTROL` | `0x1A00` | Unsupported |
`SENSOR_3D` | `0x1A01` | Unsupported | `SENSOR_3D` | `0x1A01` | Unsupported |
`REPROG_CONTROLS` | `0x1B00` | Listing Only | `get_keys`, only listing `REPROG_CONTROLS` | `0x1B00` | Unsupported |
`REPROG_CONTROLS_V2` | `0x1B01` | Unsupported | `REPROG_CONTROLS_V2` | `0x1B01` | Listing Only | `get_keys`
`REPROG_CONTROLS_V2_2` | `0x1B02` | Unsupported | `REPROG_CONTROLS_V2_2` | `0x1B02` | Unsupported |
`REPROG_CONTROLS_V3` | `0x1B03` | Unsupported | `REPROG_CONTROLS_V3` | `0x1B03` | Unsupported |
`REPROG_CONTROLS_V4` | `0x1B04` | Partial Support | `get_keys`, _feature_reprogrammable_keys `REPROG_CONTROLS_V4` | `0x1B04` | Partial Support | `ReprogrammableKeys`, `DivertKeys`, `MouseGesture`, `get_keys`
`REPORT_HID_USAGE` | `0x1BC0` | Unsupported | `REPORT_HID_USAGE` | `0x1BC0` | Unsupported |
`PERSISTENT_REMAPPABLE_ACTION` | `0x1C00` | Unsupported | `PERSISTENT_REMAPPABLE_ACTION` | `0x1C00` | Supported | `PersistentRemappableAction`
`WIRELESS_DEVICE_STATUS` | `0x1D4B` | Read only | status reporting from device `WIRELESS_DEVICE_STATUS` | `0x1D4B` | Read only | status reporting from device
`REMAINING_PAIRING` | `0x1DF0` | Unsupported | `REMAINING_PAIRING` | `0x1DF0` | Unsupported |
`FIRMWARE_PROPERTIES` | `0x1F1F` | Unsupported | `FIRMWARE_PROPERTIES` | `0x1F1F` | Unsupported |
@ -58,30 +59,31 @@ Feature | ID | Status | Notes
`SWAP_BUTTON_CANCEL` | `0x2005` | Unsupported | `SWAP_BUTTON_CANCEL` | `0x2005` | Unsupported |
`POINTER_AXIS_ORIENTATION` | `0x2006` | Unsupported | `POINTER_AXIS_ORIENTATION` | `0x2006` | Unsupported |
`VERTICAL_SCROLLING` | `0x2100` | Supported | `get_vertical_scrolling_info`, read only `VERTICAL_SCROLLING` | `0x2100` | Supported | `get_vertical_scrolling_info`, read only
`SMART_SHIFT` | `0x2110` | Supported | `_feature_smart_shift` `SMART_SHIFT` | `0x2110` | Supported | `SmartShift`
`HI_RES_SCROLLING` | `0x2120` | Supported | `get_hi_res_scrolling_info`, `_feature_hi_res_scroll` `SMART_SHIFT_ENHANCED` | `0x2111` | Supported | `SmartShiftEnhanced`
`HIRES_WHEEL` | `0x2121` | Supported | `get_hires_wheel`, `_feature_hires_smooth_invert`, `_feature_hires_smooth_resolution` `HI_RES_SCROLLING` | `0x2120` | Supported | `HiResScroll`, `get_hi_res_scrolling_info`
`LOWRES_WHEEL` | `0x2130` | Supported | `get_lowres_wheel_status`, `_feature_lowres_smooth_scroll` `HIRES_WHEEL` | `0x2121` | Supported | `HiresSmoothInvert`, `HiresSmoothResolution`, `get_hires_wheel`
`THUMB_WHEEL` | `0x2150` | Supported | `_feature_thumb_mode`, `_feature_thumb_invert` `LOWRES_WHEEL` | `0x2130` | Supported | `LowresSmoothScroll`, `get_lowres_wheel_status`
`THUMB_WHEEL` | `0x2150` | Supported | `ThumbMode`, `ThumbInvert`
`MOUSE_POINTER` | `0x2200` | Supported | `get_mouse_pointer_info`, read only `MOUSE_POINTER` | `0x2200` | Supported | `get_mouse_pointer_info`, read only
`ADJUSTABLE_DPI` | `0x2201` | Supported | `_feature_adjustable_dpi` `ADJUSTABLE_DPI` | `0x2201` | Supported | `AdjustableDpi`, `DpiSliding`
`POINTER_SPEED` | `0x2205` | Supported | `get_pointer_speed_info`, `_feature_pointer_speed` `POINTER_SPEED` | `0x2205` | Supported | `PointerSpeed`, `SpeedChange`, `get_pointer_speed_info`
`ANGLE_SNAPPING` | `0x2230` | Unsupported | `ANGLE_SNAPPING` | `0x2230` | Unsupported |
`SURFACE_TUNING` | `0x2240` | Unsupported | `SURFACE_TUNING` | `0x2240` | Unsupported |
`HYBRID_TRACKING` | `0x2400` | Unsupported | `HYBRID_TRACKING` | `0x2400` | Unsupported |
`FN_INVERSION` | `0x40A0` | Supported | `_feature_fn_swap` `FN_INVERSION` | `0x40A0` | Supported | `FnSwap`
`NEW_FN_INVERSION` | `0x40A2` | Supported | `get_new_fn_inversion`, `_feature_new_fn_swap` `NEW_FN_INVERSION` | `0x40A2` | Supported | `NewFnSwap`, `get_new_fn_inversion
`K375S_FN_INVERSION` | `0x40A3` | Supported | `_feature_k375s_fn_swap` `K375S_FN_INVERSION` | `0x40A3` | Supported | `K375sFnSwap`
`ENCRYPTION` | `0x4100` | Unsupported | `ENCRYPTION` | `0x4100` | Unsupported |
`LOCK_KEY_STATE` | `0x4220` | Unsupported | `LOCK_KEY_STATE` | `0x4220` | Unsupported |
`SOLAR_DASHBOARD` | `0x4301` | Unsupported | `SOLAR_DASHBOARD` | `0x4301` | Unsupported |
`KEYBOARD_LAYOUT` | `0x4520` | Unsupported | read only `KEYBOARD_LAYOUT` | `0x4520` | Unsupported | read only
`KEYBOARD_DISABLE_KEYS` | `0x4521` | Supported | `_feature_disable_keyboard_keys` `KEYBOARD_DISABLE_KEYS` | `0x4521` | Supported | `DisableKeyboardKeys`
`KEYBOARD_DISABLE_BY_USAGE` | `0x4522` | Unsupported | `KEYBOARD_DISABLE_BY_USAGE` | `0x4522` | Unsupported |
`DUALPLATFORM` | `0x4530` | Supported | `_feature_dualplatform`, untested `DUALPLATFORM` | `0x4530` | Supported | `Dualplatform`, untested
`MULTIPLATFORM` | `0x4531` | Supported | `_feature_multiplatform` `MULTIPLATFORM` | `0x4531` | Supported | `Multiplatform`
`KEYBOARD_LAYOUT_2` | `0x4540` | Unsupported | read only `KEYBOARD_LAYOUT_2` | `0x4540` | Unsupported | read only
`CROWN` | `0x4600` | Supported | `CROWN` | `0x4600` | Supported | `DivertCrown`, `CrownSmooth`
`TOUCHPAD_FW_ITEMS` | `0x6010` | Unsupported | `TOUCHPAD_FW_ITEMS` | `0x6010` | Unsupported |
`TOUCHPAD_SW_ITEMS` | `0x6011` | Unsupported | `TOUCHPAD_SW_ITEMS` | `0x6011` | Unsupported |
`TOUCHPAD_WIN8_FW_ITEMS` | `0x6012` | Unsupported | `TOUCHPAD_WIN8_FW_ITEMS` | `0x6012` | Unsupported |
@ -93,18 +95,18 @@ Feature | ID | Status | Notes
`TOUCHMOUSE_RAW_POINTS` | `0x6110` | Unsupported | `TOUCHMOUSE_RAW_POINTS` | `0x6110` | Unsupported |
`TOUCHMOUSE_6120` | `0x6120` | Unsupported | `TOUCHMOUSE_6120` | `0x6120` | Unsupported |
`GESTURE` | `0x6500` | Unsupported | `GESTURE` | `0x6500` | Unsupported |
`GESTURE_2` | `0x6501` | Partial Support | `_feature_gesture2_gestures`, `_feature_gesture2_params` `GESTURE_2` | `0x6501` | Partial Support | `Gesture2Gestures`, `Gesture2Params`
`GKEY` | `0x8010` | Partial Support | `GKEY` | `0x8010` | Partial Support | `DivertGkeys`
`MKEYS` | `0x8020` | Unsupported | `MKEYS` | `0x8020` | Unsupported |
`MR` | `0x8030` | Unsupported | `MR` | `0x8030` | Unsupported |
`BRIGHTNESS_CONTROL` | `0x8040` | Unsupported | `BRIGHTNESS_CONTROL` | `0x8040` | Unsupported |
`REPORT_RATE` | `0x8060` | Supported | `REPORT_RATE` | `0x8060` | Supported | `ReportRate`
`COLOR_LED_EFFECTS` | `0x8070` | Unsupported | `COLOR_LED_EFFECTS` | `0x8070` | Unsupported |
`RGB_EFFECTS` | `0X8071` | Unsupported | `RGB_EFFECTS` | `0X8071` | Unsupported |
`PER_KEY_LIGHTING` | `0x8080` | Unsupported | `PER_KEY_LIGHTING` | `0x8080` | Unsupported |
`PER_KEY_LIGHTING_V2` | `0x8081` | Unsupported | `PER_KEY_LIGHTING_V2` | `0x8081` | Unsupported |
`MODE_STATUS` | `0x8090` | Unsupported | `MODE_STATUS` | `0x8090` | Unsupported |
`ONBOARD_PROFILES` | `0x8100` | Unsupported | in progress `ONBOARD_PROFILES` | `0x8100` | Unsupported |
`MOUSE_BUTTON_SPY` | `0x8110` | Unsupported | `MOUSE_BUTTON_SPY` | `0x8110` | Unsupported |
`LATENCY_MONITORING` | `0x8111` | Unsupported | `LATENCY_MONITORING` | `0x8111` | Unsupported |
`GAMING_ATTACHMENTS` | `0x8120` | Unsupported | `GAMING_ATTACHMENTS` | `0x8120` | Unsupported |
@ -128,87 +130,76 @@ widgets in the Solaar main window to show and change the setting,
will permit storing and restoring changed settings, and will permit storing and restoring changed settings, and
will output the feature settings in `solaar show`. will output the feature settings in `solaar show`.
Adding a setting implementation involves several steps, described here and A setting implementation is a subclass of one of the built-in setting classes
illustrated by the pointer speed setting implementation. illustrated by the pointer speed setting implementation.
First add a name, a label, and a description for the setting in the common strings section. ```python
class PointerSpeed(_Setting):
name = 'pointer_speed'
label = _('Sensitivity (Pointer Speed)')
description = _('Speed multiplier for mouse (256 is normal multiplier).')
feature = _F.POINTER_SPEED
validator_class = _RangeV
min_value = 0x002e
max_value = 0x01ff
validator_options = {'byte_count': 2}
```
A setting implementation needs a name, a label, and a description.
The name is used in the persistent settings structure to store and restore changed settings and The name is used in the persistent settings structure to store and restore changed settings and
should be a valid Python identifier. (Some older settings have dashes.) should be a valid Python identifier. (Some older settings have dashes.)
The label is displayed in the Solaar main window and the description is used as a tooltip there. The label is displayed in the Solaar main window and the description is used as a tooltip there.
The label and description should be specified as translatable strings. The label and description should be specified as translatable strings.
A setting implementation for a feature (for modern devices that use the HID++ 2.0 protocol)
needs a feature identifier.
A setting implementation needs a reader/writer and a validator.
```python The reader/writer is resposible for actually writing settings to the device
_POINTER_SPEED = ('pointer_speed', and reading them from the device, writing and reading the byte strings that
_("Sensitivity (Pointer Speed)"), represent the setting values on the device.
_("How fast the pointer moves")) For most feature settings the setting implementation can just inherit
the standard feature reader/writer, `FeatureRW`.
Options for `FeatureRW` are supplied by the `rw_options` class variable,
which is used to provide command numbers for reading and writing as well
as other information needed to identify the parts of the command and reponse
that hold the setting value and modify the reading and writing procedure.
`PointerSpeed` uses the defaults; here is an example of specifying non-default commands
for reading and writing:
```
rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20}
``` ```
Next implement an interface for the setting by creating Some old devices use registers instead and the setting needs to use the register reader/writer.
a reader/writer, a validator, and a setting instance for it.
Most settings use device features and thus need feature interfaces.
Some settings use device register and thus need register interfaces.
Only implement a register interface for the setting if you are very brave and Only implement a register interface for the setting if you are very brave and
you have access to a device that has a register interface for the setting. you have access to a device that has a register interface for the setting.
Register interfaces cannot be auto-discovered and need to be stated in descriptors.py Register interfaces cannot be auto-discovered and need to be stated in descriptors.py
for each device with the register interface. for each device with the register interface.
The reader/writer instance is responsible for reading raw values The validator instance is responsible for turning raw values read from the device into Python data
from the device and writing values to it. and Python data into raw values to be written to the device and validating that the Python data is
There are different classes for feature interfaces and register interfaces.
Pointer speed is a feature so the _FeatureRW reader/writer is used.
Reader/writers take the register or feature ID and the command numbers for reading and writing,
plus other arguments for complex interfaces.
The validator instance is responsible for turning read raw values into Python data
and Python data into raw values to be written and validating that the Python data is
acceptable for the setting. acceptable for the setting.
There are several possible kinds of Python data for setting interfaces, There are several possible kinds of Python data for setting interfaces,
ranging from simple toggles, to ranges, to fixed lists, to ranging from simple toggles, to ranges, to fixed lists, to
dynamic choices, to maps of dynamic choices. dynamic choices, to maps of dynamic choices.
Pointer speed is a setting whose values are integers in a range so a _RangeV validator is used. Pointer speed is a setting whose values are integers in a range so _RangeV validator is used.
The arguments to this class are the Arguments to validators are specified as class variables.
the minimum and maximum values for the value The _RangeV validator requires the minimum and maximum for the value as separate class variables
and the byte size of the value on the device. and the byte size of the value on the device as part of `validator_options`.
Splitting the minimum and maximum makes it easier for code that works with
settings to determine this information.
Settings that are toggles or choices work similarly, Settings that are toggles or choices work similarly,
but their validators have different arguments. but their validators have different arguments.
Map settings have more complicated validators. Map settings have more complicated validators and more arguments.
The setting instance keeps everything together and provides control.
It takes the strings for the setting, the reader/writer, the validator, and
which kinds of devices can have this setting.
(This last is no longer used because keyboards with integrated trackpads only
report that they are keyboards.)
```python
def _feature_pointer_speed():
"""Pointer Speed feature"""
# min and max values taken from usb traces of Win software
validator = _RangeV(0x002e, 0x01ff, 2)
rw = _FeatureRW(_F.POINTER_SPEED)
return _Setting(_POINTER_SPEED, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
```
Settings where the acceptable values are determined from the device Settings where the acceptable values are determined from the device
need an auxiliary function to receive and decipher the permissible choices. subclass the validator and provide a build class method that queries the device
See `_feature_adjustable_dpi_choices` for an example. and creates an instance of the validator.
This method can also return `None`, indicating that even though the
device implements the feature it does not usefully support the setting.
Finally, add an element to _SETTINGS_TABLE with Settings need to be added to the `SETTINGS` list so that setting discovery can be done.
the common strings for the setting,
the feature ID (if any),
the feature implementation (if any),
the register implementation (if any).
and
the identifier for the setting implementation if different from the setting name.
The identifier is used in descriptors.py to say that a device has the register or feature implementation.
This table is used to generate the data structures for describing devices in descriptors.py
and is also used to auto-discover feature implementations.
```python
_S( _POINTER_SPEED, _F.POINTER_SPEED, _feature_pointer_speed ),
```
The values to be used need to be determined from documentation of the
feature or from reverse-engineering behavior of Logitech software under
Windows or MacOS.
For more information on implementing feature settings For more information on implementing feature settings
see the comments in lib/logitech_receiver/settings_templates.py. see the comments in lib/logitech_receiver/settings_templates.py.

View File

@ -18,10 +18,11 @@ Both interfaces are able to list the connected devices and
show information about each device, often including battery status. show information about each device, often including battery status.
Solaar is able to pair and unpair devices with Solaar is able to pair and unpair devices with
receivers as supported by the device and receiver. receivers as supported by the device and receiver.
Solaar can also control some changeable features of devices, Solaar can also control some changeable settings of devices,
such as scroll wheel direction and function key behavior. such as scroll wheel direction and function key behavior.
Solaar keeps track of these changed settings on a per-computer basis and the GUI application restores them whenever a device connects. Solaar keeps track of most of these settings on a per-computer basis,
(Devices forget most settings when powered down.) because devices forget most settings when powered down,
and the GUI application restores them whenever a device connects.
For more information on how to use Solaar see For more information on how to use Solaar see
[the usage page](https://pwr-solaar.github.io/Solaar/usage), [the usage page](https://pwr-solaar.github.io/Solaar/usage),
and for more information on its capabilities see and for more information on its capabilities see

View File

@ -39,6 +39,7 @@ _IR = _hidpp10.INFO_SUBREGISTERS
# #
# #
class Receiver: class Receiver:
"""A Unifying Receiver instance. """A Unifying Receiver instance.