Skip to content

feat(touchpad): edges as sliders — brightness left, volume right (XIC-61) - #49

Merged
Oksion merged 2 commits into
mainfrom
oksion/xic-61-touchpad-edge-sliders
Aug 31, 2026
Merged

feat(touchpad): edges as sliders — brightness left, volume right (XIC-61)#49
Oksion merged 2 commits into
mainfrom
oksion/xic-61-touchpad-edge-sliders

Conversation

@Oksion

@Oksion Oksion commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Closes XIC-61. Requested by the owner: the vendor utility turns the touchpad edges into sliders — brightness on the left, volume on the right. Huawei calls it Free Touch; Xiaomi PC Manager comes from the same lineage.

No reverse engineering was needed. The mechanism is documented Windows behaviour, and edgeslide (MIT) is an existing precedent.

Two halves, both driver-free

Pointer suppression uses the stock Windows curtain zones (SuperCurtainLeft / SuperCurtainRight) — the same knobs already used for the bottom dead zone.

The gesture itself is read through Raw Input on the PTP digitizer collection and parsed with HidP_*. No driver, and no administrator rights for the reading part.

That these two are compatible was measured, not assumed. With a 30 mm left curtain active, 12 447 contacts arrived from the leftmost 10% of the pad — so the filtering lives in the Windows PTP mapper, above the HID class driver. The probe and method are kept in reference/touchpad-edges.

This is what edgeslide cannot do: it suppresses nothing and relies on heuristics, so the pointer travels while you slide.

Design decisions worth knowing

Only contacts that began inside a strip drive a slider. This is the join with the curtain zone, which suppresses contact initiation specifically. A finger that started mid-pad keeps moving the pointer, and having it also drive a slider would mean one movement doing two things.

Brightness is written as a human edit, without the Own mark. The slider under your finger is the user, so the auto-brightness curve must learn from it and the brightness cap must treat it as a deliberate choice. With the mark, brightness would quietly drift back a minute later and it would be our fault.

Brightness and volume share one scale. The first version moved brightness 5% per step and volume one VK_VOLUME_UP tap — which is 2% — so the two ranges drifted apart by 2.5×. Both are now measured in percent of their own scale, with the volume remainder carried between steps. EdgeSlideScaleTests asserts that both cross exactly 100% for every sensitivity preset.

Strip width is capped. Not caution: a deliberately large value (150 mm) is ignored outright by the PTP mapper and the zone silently stops working. A narrow zone beats a dead setting.

Self-review before opening this

Reviewing my own code found four real problems, all fixed in e1b3af2 and each described in that commit:

  1. Settings were read once in the constructor, so the sensitivity control did nothing and the gesture's strip width drifted from the actual zone.
  2. WMI ran under the lock that the reader thread also takes — the previous fix moved the work off that thread but left the blocking behind.
  3. WorkerTimer is periodic and re-enters when a tick overruns; a WMI write occasionally exceeds 50 ms.
  4. Changing sensitivity ran the full Apply — registry write plus node restart — so the touchpad blinked for a setting that does not touch the zones.

Also: on a stop timeout the reader no longer clears its thread reference, because the next Start would raise a second reader and every gesture would count twice.

Known limitation

The strip width is converted to a fraction of pad width using 139 mm, the measured width of a Book Pro 14. On other models that is an approximation — the suppressed zone is still exactly right (the registry value is in millimetres), only the gesture's pickup edge may sit slightly differently. Reading the true width from the HID descriptor means parsing Units/UnitExponent, which is exactly where an earlier attempt produced 54.7 mm instead of 139, so it is deliberately left for later rather than guessed at now.

Verification

  • dotnet build XiControl.sln -c Release — 0 warnings, 0 errors
  • dotnet test XiControl.sln -c Release — 608 passed (30 new: 19 on gesture recognition, 11 on the shared scale)
  • Live on a Book Pro 14 through several rounds of owner testing: pointer dead in the strips, gestures from the centre unaffected, two-finger scrolling intact, both scales in step, sensitivity switching without a touchpad restart
  • Off by default; without a Precision Touchpad the feature does not start and says so in the log

🤖 Generated with Claude Code

Oksion and others added 2 commits August 31, 2026 18:24
…права (XIC-61)

Как Free Touch у Huawei: палец, опустившийся в краевую полосу, вертикальным
движением крутит яркость (слева) и громкость (справа).

Две половины, обе driver-free. Курсорное движение в полосе гасится штатной
curtain-зоной Windows (SuperCurtainLeft/Right — те же ручки, что у мёртвой зоны
снизу), а сам жест читается через Raw Input. То, что это совместимо, не
предполагалось, а измерено: при активной 30-миллиметровой зоне из левых 10%
ширины пришло 12447 касаний, то есть фильтрация живёт в PTP-маппере Windows,
выше HID-драйвера. Пробник и методика — reference/touchpad-edges.

Мёртвой зоне снизу не мешаем и она нам: значения в реестре разные, общий только
перезапуск узла, и он идемпотентен.

Жест принимает лишь контакты, НАЧАВШИЕСЯ в полосе. Это стыковка с той же
curtain-зоной, которая гасит именно инициацию касания (XIC-24): палец, начатый
в середине, курсор двигать продолжает, и крутить им ещё и ползунок значило бы
делать два действия одним движением. Второй палец отменяет жест — двухпальцевое
движение принадлежит прокрутке Windows.

Яркость меняется как правка ЧЕЛОВЕКА, без метки Own: ползунок под пальцем — это
и есть пользователь, поэтому кривая авто-яркости обязана на нём учиться, а лимит
— считать его осознанным выбором. Иначе яркость уезжала бы обратно через минуту.
Для этого добавлен Brightness.ApplyAsUser.

Ширина полосы ограничена сверху не из осторожности: измерено, что заведомо
большое значение (150 мм) PTP-маппер игнорирует вовсе и зона молча перестаёт
работать. Координаты считаются долями логического диапазона, а не миллиметрами —
физические единицы PTP объявлены в дюймах с показателем, и трактовка «на глазок»
однажды уже дала 54,7 мм вместо 139.

Чистая логика жеста — TouchpadEdgeGesture, 19 тестов. Win32-часть
(RawTouchpadReader) живёт на своём потоке с окном-только-для-сообщений: поток
касаний идёт сотнями отчётов в секунду и UI-потоку там делать нечего.

Выключено по умолчанию. Нет Precision Touchpad — фича тихо не стартует и пишет
строку в журнал.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ки на лету (XIC-61)

Разбор собственного кода перед PR нашёл четыре вещи, три из них ломали фичу
по-настоящему.

1. Настройки читались один раз в конструкторе. _gesture и _scale строились при
   создании объекта и больше не пересобирались: Apply перезаписывал зоны и
   перезапускал чтение, но считали его прежние объекты. Чувствительность молча
   не работала, а ширина полосы расходилась с реальной зоной — курсор гас в
   двадцати миллиметрах, ползунок ловил палец в двенадцати. Пересборка вынесена
   в Reconfigure и зовётся всегда, даже при выключенной фиче.

2. WMI стояло под замком, который берёт поток чтения касаний. Вчерашняя правка
   унесла применение на воркер, но Brightness.Get остался внутри общего lock —
   и поток чтения вставал на нём в очередь ровно там, где его и разгружали.
   Кэш яркости теперь принадлежит воркеру, а сброс идёт volatile-флагом.

3. WorkerTimer периодический и умеет войти повторно, если тик затянулся, — а
   WMI-запись изредка занимает больше 50 мс. Добавлена защита Interlocked, как
   в TrayMetricIcon.

4. Смена чувствительности гоняла Apply: запись в реестр плюс перезапуск узла,
   то есть тачпад пропадал на секунду. Зоны от чувствительности не зависят —
   теперь для неё лёгкий Reconfigure.

Плюс RawTouchpadReader на таймауте остановки больше не обнуляет ссылку на поток:
иначе следующий Start поднял бы второй читатель, оба окна получали бы WM_INPUT,
и каждый жест считался бы дважды.

Клэмп чувствительности сведён с набором пресетов: раньше вписанное руками 5
работало, но комбо показывало «Обычно» — UI врал бы о том, что реально включено.

Подписи пресетов больше не обещают «за один/два/три прохода»: арифметика
сходится, но на живом железе результат зависит от того, откуда начал и где
оторвал палец. Осталось Резко / Обычно / Плавно.

README на двух языках, CHANGELOG.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Oksion
Oksion merged commit 607afca into main Aug 31, 2026
5 checks passed
@Oksion
Oksion deleted the oksion/xic-61-touchpad-edge-sliders branch August 31, 2026 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant