feat(touchpad): edges as sliders — brightness left, volume right (XIC-61) - #49
Merged
Conversation
…права (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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Ownmark. 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_UPtap — 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.EdgeSlideScaleTestsasserts 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
e1b3af2and each described in that commit:WorkerTimeris periodic and re-enters when a tick overruns; a WMI write occasionally exceeds 50 ms.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
Startwould 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 errorsdotnet test XiControl.sln -c Release— 608 passed (30 new: 19 on gesture recognition, 11 on the shared scale)🤖 Generated with Claude Code