settings: add setting for HAPTIC feature

This commit is contained in:
Peter F. Patel-Schneider
2025-11-03 15:58:56 +09:00
parent 97b6b958c8
commit 0fd262424e
5 changed files with 93 additions and 2 deletions

View File

@@ -66,6 +66,7 @@ class SupportedFeature(IntEnum):
BACKLIGHT3 = 0x1983
ILLUMINATION = 0x1990
FORCE_SENSING_BUTTON = 0x19C0
HAPTIC = 0x19B0
PRESENTER_CONTROL = 0x1A00
SENSOR_3D = 0x1A01
REPROG_CONTROLS = 0x1B00
@@ -277,3 +278,23 @@ class ParamId(IntEnum):
PIXEL_ZONE = 2 # 4 2-byte integers, left, bottom, width, height; pixels
RATIO_ZONE = 3 # 4 bytes, left, bottom, width, height; unit 1/240 pad size
SCALE_FACTOR = 4 # 2-byte integer, with 256 as normal scale
HapticWaveForms = NamedInts(
SHARP_STATE_CHANGE=0x00,
DAMP_STATE_CHANGE=0x01,
SHARP_COLLISION=0x02,
DAMP_COLLISION=0x03,
SUBTLE_COLLISION=0x04,
HAPPY_ALERT=0x05,
ANGRY_ALERT=0x06,
COMPLETED=0x07,
SQUARE=0x08,
WAVE=0x09,
FIREWORK=0x0A,
MAD=0x0B,
KNOCK=0x0C,
JINGLE=0x0D,
RINGING=0xE,
WHISPER_COLLISION=0x1B,
)

View File

@@ -57,6 +57,7 @@ class Setting:
rw_options = {}
validator_class = None
validator_options = {}
display = True # display setting in UI
def __init__(self, device, rw, validator):
self._device = device

View File

@@ -40,6 +40,7 @@ class Setting:
choices_universe = None # All possible acceptable keys, for settings with keys
kind = Kind.NONE # What GUI interface to use
persist = True # Whether to remember the setting
display = True # display setting in UI
_device = None # The device that this setting is for
_device_object = None # The object that interacts with the feature for the device
_value = None # Stored value as maintained by Solaar, used for persistence

View File

@@ -1809,6 +1809,72 @@ class ForceSensing(settings_new.Settings):
return setting
class HapticLevel(settings.Setting):
name = "haptic-level"
label = _("Haptic Feeback Level")
description = _("Change power of haptic feedback. (Zero to turn off.)")
feature = _F.HAPTIC
choices_universe = common.NamedInts(Off=0, Low=25, Medium=50, High=75, Maximum=100)
min_value = 0
max_value = 100
class rw_class(settings.FeatureRW):
def __init__(self, feature):
super().__init__(feature, read_fnid=0x10, write_fnid=0x20)
def read(self, device, data_bytes=b""):
result = device.feature_request(self.feature, 0x10)
if result[0] & 0x01 == 0: # disabled, return 0
return b"\x00"
else: # enabled, return second byte
return result[1:2]
def write(self, device, data_bytes):
if data_bytes == b"\x00":
write_bytes = b"\x00\x32" # disable, at 50 percent
else:
write_bytes = b"\x01" + data_bytes
reply = device.feature_request(self.feature, 0x20, write_bytes)
return reply
@classmethod
def build(cls, device):
response = device.feature_request(cls.feature, 0x10)
if response:
rw = cls.rw_class(cls.feature)
levels = response[2] & 0x01
if levels: # device only has four levels
validator = settings_validator.ChoicesValidator(choices=cls.choices_universe)
else: # device has all levels
validator = settings_validator.RangeValidator(min_value=cls.min_value, max_value=cls.max_value)
return cls(device, rw, validator)
# This setting is not displayed in the UI
# Use `solaar config <device> haptic-play <form>` to play a haptic form
class PlayHapticWaveForm(settings.Setting):
name = "haptic-play"
label = _("Play Haptic Waveform")
description = _("Tell device to play a haptic waveform.")
feature = _F.HAPTIC
choices_universe = hidpp20_constants.HapticWaveForms
rw_options = {"read_fnid": None, "write_fnid": 0x40} # nothing to read
persist = False # persisting this setting is useless
display = False # don't display in UI, interact using `solaar config ...`
class validator_class(settings_validator.ChoicesValidator):
@classmethod
def build(cls, setting_class, device):
response = device.feature_request(_F.HAPTIC, 0x00)
if response:
waves = common.NamedInts()
waveforms = int.from_bytes(response[4:8])
for waveform in hidpp20_constants.HapticWaveForms:
if (1 << int(waveform)) & waveforms:
waves[int(waveform)] = str(waveform)
return cls(choices=waves, byte_count=1)
SETTINGS: list[settings.Setting] = [
RegisterHandDetection, # simple
RegisterSmoothScroll, # simple
@@ -1866,6 +1932,8 @@ SETTINGS: list[settings.Setting] = [
Gesture2Gestures, # working
Gesture2Divert,
Gesture2Params, # working
HapticLevel,
PlayHapticWaveForm,
Sidetone,
Equalizer,
ADCPower,

View File

@@ -16,7 +16,6 @@
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import logging
import traceback
from enum import Enum
from threading import Timer
@@ -70,7 +69,6 @@ def _write_async(setting, value, sbox, sensitive=True, key=None):
v = setting.write_key_value(key, v)
v = {key: v}
except Exception:
traceback.print_exc()
v = None
if sb:
GLib.idle_add(_update_setting_item, sb, v, True, sensitive, priority=99)
@@ -660,6 +658,8 @@ def _change_icon(allowed, icon):
def _create_sbox(s, _device):
if not s.display:
return
sbox = Gtk.HBox(homogeneous=False, spacing=6)
sbox.setting = s
sbox.kind = s.kind