diff --git a/docs/features.md b/docs/features.md index 174791bd..73bedebe 100644 --- a/docs/features.md +++ b/docs/features.md @@ -11,9 +11,9 @@ Feature | ID | Status | Notes `ROOT` | `0x0000` | :heavy_check_mark: | System `FEATURE_SET` | `0x0001` | :heavy_check_mark: | System `FEATURE_INFO` | `0x0002` | :heavy_check_mark: | System -`DEVICE_FW_VERSION` | `0x0003` | :heavy_check_mark: | `get_firmware()` +`DEVICE_FW_VERSION` | `0x0003` | :heavy_check_mark: | `get_firmware()`, read only `DEVICE_UNIT_ID` | `0x0004` | :x: | -`DEVICE_NAME` | `0x0005` | :heavy_check_mark: | `get_kind()`, `get_name()` +`DEVICE_NAME` | `0x0005` | :heavy_check_mark: | `get_kind()`, `get_name()`, read only `DEVICE_GROUPS` | `0x0006` | :x: | `DEVICE_FRIENDLY_NAME` | `0x0007` | :x: | `KEEP_ALIVE` | `0x0008` | :x: | @@ -25,8 +25,8 @@ Feature | ID | Status | Notes `DFUCONTROL_UNSIGNED` | `0x00C1` | :x: | `DFUCONTROL_SIGNED` | `0x00C2` | :x: | `DFU` | `0x00D0` | :x: | -`BATTERY_STATUS` | `0x1000` | :heavy_check_mark: | `get_battery()` -`BATTERY_VOLTAGE` | `0x1001` | :x: | +`BATTERY_STATUS` | `0x1000` | :heavy_check_mark: | `get_battery()`, read only +`BATTERY_VOLTAGE` | `0x1001` | :heavy_check_mark: | `get_voltage()`, read only `CHARGING_CONTROL` | `0x1010` | :x: | `LED_CONTROL` | `0x1300` | :x: | `GENERIC_TEST` | `0x1800` | :x: | @@ -36,31 +36,31 @@ Feature | ID | Status | Notes `CHANGE_HOST` | `0x1814` | :x: | `HOSTS_INFO` | `0x1815` | :x: | `BACKLIGHT` | `0x1981` | :x: | -`BACKLIGHT2` | `0x1982` | :x: | +`BACKLIGHT2` | `0x1982` | :heavy_check_mark: | `_feature_backlight2()` `BACKLIGHT3` | `0x1983` | :x: | `PRESENTER_CONTROL` | `0x1A00` | :x: | `SENSOR_3D` | `0x1A01` | :x: | -`REPROG_CONTROLS` | `0x1B00` | :heavy_plus_sign: | Partially, only listing. `get_keys()` +`REPROG_CONTROLS` | `0x1B00` | :heavy_plus_sign: | `get_keys()`, only listing `REPROG_CONTROLS_V2` | `0x1B01` | :x: | `REPROG_CONTROLS_V2_2` | `0x1B02` | :x: | `REPROG_CONTROLS_V3` | `0x1B03` | :x: | -`REPROG_CONTROLS_V4` | `0x1B04` | :heavy_plus_sign: | Partially, only listing. `get_keys()` +`REPROG_CONTROLS_V4` | `0x1B04` | :heavy_plus_sign: | `get_keys()`, only listing `REPORT_HID_USAGE` | `0x1BC0` | :x: | `PERSISTENT_REMAPPABLE_ACTION` | `0x1C00` | :x: | -`WIRELESS_DEVICE_STATUS` | `0x1D4B` | :x: | +`WIRELESS_DEVICE_STATUS` | `0x1D4B` | :x: | status reporting from device `REMAINING_PAIRING` | `0x1DF0` | :x: | `FIRMWARE_PROPERTIES` | `0x1F1F` | :x: | `ADC_MEASUREMENT` | `0x1F20` | :x: | `LEFT_RIGHT_SWAP` | `0x2001` | :x: | `SWAP_BUTTON_CANCEL` | `0x2005` | :x: | `POINTER_AXIS_ORIENTATION` | `0x2006` | :x: | -`VERTICAL_SCROLLING` | `0x2100` | :heavy_check_mark: | `get_vertical_scrolling_info()` +`VERTICAL_SCROLLING` | `0x2100` | :heavy_check_mark: | `get_vertical_scrolling_info()`, read only `SMART_SHIFT` | `0x2110` | :heavy_check_mark: | `_feature_smart_shift()` `HI_RES_SCROLLING` | `0x2120` | :heavy_check_mark: | `get_hi_res_scrolling_info()`, `_feature_hi_res_scroll()` `HIRES_WHEEL` | `0x2121` | :heavy_check_mark: | `get_hires_wheel()`, `_feature_hires_smooth_invert()`, `_feature_hires_smooth_resolution()` `LOWRES_WHEEL` | `0x2130` | :heavy_check_mark: | `get_lowres_wheel_status()`, `_feature_lowres_smooth_scroll()` `THUMB_WHEEL` | `0x2150` | :x: | -`MOUSE_POINTER` | `0x2200` | :heavy_check_mark: | `get_mouse_pointer_info()` +`MOUSE_POINTER` | `0x2200` | :heavy_check_mark: | `get_mouse_pointer_info()`, read only `ADJUSTABLE_DPI` | `0x2201` | :heavy_check_mark: | `_feature_adjustable_dpi()` `POINTER_SPEED` | `0x2205` | :heavy_check_mark: | `get_pointer_speed_info()`, `_feature_pointer_speed()` `ANGLE_SNAPPING` | `0x2230` | :x: | @@ -110,3 +110,74 @@ Feature | ID | Status | Notes `EQUALIZER` | `0x8310` | :x: | `HEADSET_OUT` | `0x8320` | :x: | +"read only" in the notes column means that the state of the feature can be read, but not set. + +# Implementing a feature + +Features are implemented as settable features in +lib/logitech_receiver/settings_templates.py +Some features also have direct implementation in +lib/logitech_receiver/hidpp20.py + +In most cases it should suffice to only implement the settable feature +interface for each setting in the feature. That will add one or more +widgets in the Solaar main window to show and change the feature, +store and restore changed settings, and +output the feature settings in `solaar show`. + +Adding a setting involves several steps, described here and illustrated by the pointer speed setting. + +Add a name, a label, and a description for the setting in the common strings section. +The name is used in the persistent settings structure to store and restore changed settings. +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. + +``` +_POINTER_SPEED = ('pointer_speed', _("Sensitivity (Pointer Speed)"), _("How fast the pointer moves")) +``` + +Create the feature interface for the setting. There are several possible +feature interfaces, ranging from simple toggles, to ranges, to fixed lists, to +dynamic lists, each created by a macro function. Pointer speed is a setting +whose values are integers in a range so `feature_range` is used. +The arguments to this macro are +an identifier of the setting (in simple cases using the setting name is easiest), +the HID++ 2.0 feature ID for the setting (from the FEATURE structure in hidpp20.py), +the minimum and maximum values for the setting, +the HID++ 2.0 function IDs to read and write the setting, +the byte size of the setting value, +a label and description for the setting, +and which kinds of devices can have this setting. +(This last is no longer used because keyboards with integrated pointers only +report that they are keyboards.) +The values to be used need to be determined from documentation of the +feature or from reverse-engineering behaviour of Logitech software under +Windows or MacOS. + +``` +def _feature_pointer_speed(): + """Pointer Speed feature""" + return feature_range(_POINTER_SPEED[0], _F.POINTER_SPEED, 0x002e, 0x01ff, + read_function_id=0x0, + write_function_id=0x10, + bytes_count=2, + label=_POINTER_SPEED[1], description=_POINTER_SPEED[2], + device_kind=(_DK.mouse, _DK.trackball)) +``` + +Settings that are toggles or choices work very similarly. +Settings where the choices are determined from the device +need an auxiliary function to receive and decipher the permissable choices. +See `_feature_adjustable_dpi_choices` for an example. + +Add the setting identifier to _SETTINGS_LIST, +to the RegisterSettings list as a setting with no register implementation, +and to the FeatureSettings list with its implementation. + +Add the setting's name and feature to check_feature_settings (and feature +interface identifier if different from the name) so that devices will be +probed to see if they support the setting. + +``` +check_feature(_POINTER_SPEED[0], _F.POINTER_SPEED) +```