From 80446e20658202159fd252ba00713c6b2d844876 Mon Sep 17 00:00:00 2001 From: Oksion Date: Mon, 19 Oct 2026 20:19:35 +0500 Subject: [PATCH 1/5] =?UTF-8?q?feat(drivers):=20=D1=82=D0=B5=D0=BA=D1=83?= =?UTF-8?q?=D1=89=D0=B0=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=88=D0=B8=D0=B2=D0=BE=D0=BA=20=D1=82=D0=B0?= =?UTF-8?q?=D1=87=D0=BF=D0=B0=D0=B4=D0=B0=20=D0=B8=20=D1=8D=D0=BA=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B0=20(XIC-104)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Строки прошивок на вкладке «Драйверы» теперь показывают установленную версию. Тачпад читается командой 0x10 канала COL05 и сравнивается с хвостом каталожной версии как драйвер: на TM2424 44.25.11.0 при 44.25.13.0 в каталоге даёт «обновление». Экран — ROM Ver TCON Himax через IGCL ctlAUXAccess, последовательность OEM байт в байт, только по кнопке и только при пакете в каталоге. Показывается рядом с каталожной без вывода: номер зависит от варианта панели. По RDP панели нет — «версия неизвестна»; за самим ноутбуком экранная часть не проверена. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 9 + CLAUDE.md | 6 +- docs/16-oem-driver-check.md | 33 +++- src/Localization/lang/en.json | 10 +- src/Localization/lang/ru.json | 10 +- src/Localization/lang/zh.json | 10 +- src/SystemIntegration/OemDrivers.cs | 78 +++++++- src/SystemIntegration/PanelFirmware.cs | 178 ++++++++++++++++++ src/SystemIntegration/TouchpadHaptics.cs | 11 ++ .../TouchpadHapticsProtocol.cs | 7 + src/Ui/AppController.cs | 11 +- src/Ui/Settings/DriversTab.cs | 48 +++-- tests/XiControl.Tests/OemDriversTests.cs | 53 +++++- .../PanelFirmwareProtocolTests.cs | 77 ++++++++ .../TouchpadHapticsProtocolTests.cs | 9 + 15 files changed, 513 insertions(+), 37 deletions(-) create mode 100644 src/SystemIntegration/PanelFirmware.cs create mode 100644 tests/XiControl.Tests/PanelFirmwareProtocolTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0db1c67..3136407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ ## [Unreleased] +### Добавлено +- **Прошивки на вкладке «Драйверы» — с текущей версией.** Раньше строка говорила только «в каталоге + есть прошивка X». Теперь XiControl читает установленную версию сам, без драйверов. Тачпад + сравнивается как драйвер: «обновление», «актуальна» или «новее каталога» (на TM2424 стоит 44.25.11.0 + при 44.25.13.0 в каталоге). У экрана обе версии показываются рядом, но без вывода: номер прошивки + панели зависит от её варианта, и по нему не сказать, новее ли каталожная. Версия экрана читается + только по кнопке проверки, в локальном сеансе при включённом экране; по удалённому рабочему столу + она неизвестна, и это нормально (XIC-104). + ### Исправлено - **Проверка драйверов отбрасывает пакеты чужой модели.** Сервер Xiaomi называет в ответе модель, для которой пакет. Если она не совпала с моделью ноутбука, пакет не показывается. Для прошивок diff --git a/CLAUDE.md b/CLAUDE.md index b3826ce..32f74ac 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -252,7 +252,11 @@ WMI-событий). `Program.cs`: single-instance mutex → DI-контейне `OemDrivers` (XIC-95: `OemProtocol` — тело/подпись/разбор ответа сервера Xiaomi, `DriverMatch` — сверка строго попарно по hardware-id — кроме прошивок `FWTP`/`FWSC`: их id в каталоге синтетические, строка «в каталоге есть прошивка X» даётся по факту пакета для модели, одна на модуль (XIC-102); модули -ответа с чужой `product` отбрасываются ещё при разборе — для прошивок это единственная сверка (XIC-103); оба +ответа с чужой `product` отбрасываются ещё при разборе — для прошивок это единственная сверка (XIC-103); + текущая версия прошивки (XIC-104): тачпад — `TouchpadHaptics.ReadFirmwareVersion` (COL05, команда `0x10`), + сравнивается с хвостом каталожной; экран — `PanelFirmware` (ROM Ver TCON Himax через IGCL `ctlAUXAccess`, + записи в контроллер байт в байт как у OEM, только по кнопке; по RDP панели нет — «неизвестна»), только рядом, + без вывода; оба чистые и под тестами; `OemDriverCheck` — WMI + сеть, только по кнопке, без `sn`, ничего не скачивает и не ставит; протокол — `docs/16-oem-driver-check.md`), `OemService` (XIC-87: службы Xiaomi — MiDeviceService на выходе из Eco откатывает яркость и сама diff --git a/docs/16-oem-driver-check.md b/docs/16-oem-driver-check.md index 0e064e5..d62cfec 100644 --- a/docs/16-oem-driver-check.md +++ b/docs/16-oem-driver-check.md @@ -138,13 +138,31 @@ XMAPT4B0P0A0A → "0A0A" → 0x0A0A = 2570 → "1.0.0.2570" > `Win32_PnPSignedDriver` не встречаются — до XIC-102 строка была недостижима, и XiControl молчал там, > где PC Manager предлагал прошивку. Версия — из `hardwareIds[0].version` (на TM2424: `740B08` у экрана, > `3.5.13.01.44.25.13.0` у тачпада), имя устройства — локализованная константа по коду модуля, не из PnP. -> С версией драйвера не сравниваем и «у вас старее» не говорим — текущую версию прошивки читать нечем -> (см. «Ограничения»). Для обычных драйверов правило прежнее: нет совпавшего устройства — нет строки. +> С версией драйвера не сравниваем. Текущую версию прошивки с XIC-104 читаем сами (ниже). Для обычных +> драйверов правило прежнее: нет совпавшего устройства — нет строки. > > **Сверка модели в ответе (XIC-103).** Каждый модуль ответа несёт поле `product`. Модуль, где сервер > явно назвал не ту модель, о которой спрашивали, отбрасывается (`OemProtocol.ParseResponse`, без учёта > регистра; модуль без поля остаётся — доказать «чужую модель» нечем). Для драйверов это страховка поверх > сверки по hardware-id, для прошивок — **единственная** проверка, что пакет про этот ноутбук. +> +> **Текущая версия прошивки (XIC-104)** — по разбору ниже, только для модулей, на которые в каталоге +> есть пакет, по кнопке проверки, на пуле (`OemDriverCheck.RunAsync(currentFirmware)` → +> `DriverMatch.Compare(..., firmware)`): +> +> - **тачпад** — `TouchpadHaptics.ReadFirmwareVersion()`, команда `0x10` канала `COL05` +> (разбор — `TouchpadHapticsProtocol.FirmwareVersion`). Каталожная версия режется до последних +> четырёх чисел (`DriverMatch.TouchpadCatalogVersion`: `3.5.13.01.44.25.13.0` → `44.25.13.0`), и +> строка получает обычное «обновление / актуальна / новее каталога». На TM2424 живьём: установлена +> 44.25.11.0, в каталоге 44.25.13.0 → «обновление»; +> - **экран** — `PanelFirmware.ReadRomVersion()`, ROM Ver контроллера Himax через IGCL; чистая часть +> (последовательность операций, раскладка `ctl_aux_access_args_t`, разбор ответа) — +> `PanelFirmwareProtocol`, тесты сверяют записи с OEM байт в байт. Версия в формате каталога +> (`570B06`) показывается **рядом** с каталожной, состояние остаётся «сравнить нельзя» — см. «Чего это +> не решает». Своя инициализация IGCL на один вызов, записи — только на выходе, где DPCD `0x400` +> ответил OUI Himax; первая неудачная операция обрывает последовательность; +> - не прочиталось → «версия неизвестна, в каталоге X» (у экрана — с подсказкой про локальный сеанс). +> Результат каждого чтения пишется в журнал (`OemDriverCheck: текущая прошивка FWTP — …`). Сверка «что установлено против того, что есть у OEM» делается без сети и без Xiaomi-софта: @@ -172,8 +190,8 @@ XMAPT4B0P0A0A → "0A0A" → 0x0A0A = 2570 → "1.0.0.2570" - каталог живёт у них на сервере и устаревает в кэше: полагаться только на локальный `driver_cache.dat` нельзя — он даст «всё актуально» там, где это уже не так; - метаданные местами мусорные (у чипсета `releaseTime` = `1968-07-18`) — даты показывать с оглядкой; -- **текущие версии прошивок в приложении пока не читаем** — говорим «в каталоге есть прошивка X» - (XIC-102), но не «у вас старее». Driver-free прочитать их **можно**, разбор ниже (XIC-103); +- **текущая версия прошивки** читается driver-free (XIC-103, XIC-104), но «у вас старее» говорим только + про тачпад; у экрана — обе версии рядом, и только когда панель активна; - hardware-id прошивок в каталоге **синтетические** и ни с чем в системе не совпадают — сверять их по PnP бессмысленно, строка даётся по факту пакета для модели. @@ -212,9 +230,10 @@ RDP — HID-устройство от сеанса не зависит. В ка - чтение регистра включает **записи в контроллер** (`0D 00`, `20 01`, адрес). Это ровно то, что OEM делает при каждом скане, регистры прошивки не меняются, но это всё же запись в TCON — если делать в приложении, то только по кнопке проверки; -- живьём нашим кодом не проверено: на момент ресерча машина была доступна только по RDP. Проба — - `panel-fw-version.ps1` в рабочем каталоге проекта (вне git): без ключей только читает DPCD, - с `-ReadVersion` повторяет последовательность OEM. +- живьём за ноутбуком нашим кодом **ещё не проверено**: и ресерч XIC-103, и реализация XIC-104 шли по + RDP. По RDP проверено только «панели нет → версия неизвестна, ни одной AUX-операции» (в журнале + `PanelFirmware: контроллер Himax не найден`). Проба — `panel-fw-version.ps1` в рабочем каталоге + проекта (вне git): без ключей только читает DPCD, с `-ReadVersion` повторяет последовательность OEM. **Ревизии в синтетических id (`REV_1236/1237`, `REV_1836/1837`) расшифровать нельзя.** Служба сопоставляет прошивку по коду модуля (`fw_id`) и версии, а hardware-id из каталога лишь пишет в лог diff --git a/src/Localization/lang/en.json b/src/Localization/lang/en.json index 05301b7..f1fb421 100644 --- a/src/Localization/lang/en.json +++ b/src/Localization/lang/en.json @@ -282,9 +282,15 @@ "settings.drivers.update.desc": "{0} → {1} · {2}", "settings.drivers.package": "{0}, {1} MB", "settings.drivers.firmware.group": "Firmware", - "settings.drivers.firmware.warn": "Flashing the display or touchpad firmware is at your own risk. XiControl has not checked the package and does not install it: the link opens in your browser. The current firmware version cannot be read — you may already have this one. Install only with the tool inside this archive, on AC power, and do not interrupt it.", + "settings.drivers.firmware.warn": "Flashing the display or touchpad firmware is at your own risk. XiControl has not checked the package and does not install it: the link opens in your browser. Install only with the tool inside this archive, on AC power, and do not interrupt it.", "settings.drivers.firmware.title": "Firmware: {0}", - "settings.drivers.firmware.desc": "Catalog version {0} · {1}. The current firmware version cannot be read, so it cannot be compared.", + "settings.drivers.firmware.desc": "Catalog version {0} · {1}. The current firmware version could not be read, so it cannot be compared.", + "settings.drivers.firmware.unknown.panel": "Catalog version {0} · {1}. Current version unknown: the display controller only answers in a local session with the screen on, not over Remote Desktop.", + "settings.drivers.firmware.side": "Installed {0}, catalog {1} · {2}. Firmware numbers depend on the panel variant, so they do not tell whether the catalog one is newer.", + "settings.drivers.firmware.same": "Installed {0}, same as the catalog · {1}", + "settings.drivers.firmware.newer": "Installed {0}, catalog {1} — yours is newer · {2}", + "settings.drivers.firmware.v.same": "up to date", + "settings.drivers.firmware.v.newer": "newer than catalog", "settings.drivers.download": "Open link", "settings.drivers.download.confirm": "Open in browser?", "settings.drivers.nolink": "no link", diff --git a/src/Localization/lang/ru.json b/src/Localization/lang/ru.json index 8f79af7..0888f83 100644 --- a/src/Localization/lang/ru.json +++ b/src/Localization/lang/ru.json @@ -282,9 +282,15 @@ "settings.drivers.update.desc": "{0} → {1} · {2}", "settings.drivers.package": "{0}, {1} МБ", "settings.drivers.firmware.group": "Прошивки", - "settings.drivers.firmware.warn": "Прошивка экрана или тачпада — на ваш страх и риск. XiControl пакет не проверял и не устанавливает: ссылка откроется в браузере. Текущую версию прошивки прочитать нечем — возможно, у вас уже она. Ставьте только штатной утилитой из этого архива, на питании от сети и не прерывая процесс.", + "settings.drivers.firmware.warn": "Прошивка экрана или тачпада — на ваш страх и риск. XiControl пакет не проверял и не устанавливает: ссылка откроется в браузере. Ставьте только штатной утилитой из этого архива, на питании от сети и не прерывая процесс.", "settings.drivers.firmware.title": "Прошивка: {0}", - "settings.drivers.firmware.desc": "В каталоге версия {0} · {1}. Текущую версию прошивки прочитать нечем — сравнить не можем.", + "settings.drivers.firmware.desc": "В каталоге версия {0} · {1}. Текущую версию прошивки прочитать не удалось — сравнить не можем.", + "settings.drivers.firmware.unknown.panel": "В каталоге версия {0} · {1}. Текущая версия неизвестна: контроллер экрана отвечает только в локальном сеансе при включённом экране, не по удалённому рабочему столу.", + "settings.drivers.firmware.side": "Установлена {0}, в каталоге {1} · {2}. Номер прошивки зависит от варианта панели, поэтому новее ли каталожная — по номерам не сказать.", + "settings.drivers.firmware.same": "Установлена {0}, как в каталоге · {1}", + "settings.drivers.firmware.newer": "Установлена {0}, в каталоге {1} — у вас новее · {2}", + "settings.drivers.firmware.v.same": "актуальна", + "settings.drivers.firmware.v.newer": "новее каталога", "settings.drivers.download": "Открыть ссылку", "settings.drivers.download.confirm": "Открыть в браузере?", "settings.drivers.nolink": "нет ссылки", diff --git a/src/Localization/lang/zh.json b/src/Localization/lang/zh.json index a131b19..ad45ba0 100644 --- a/src/Localization/lang/zh.json +++ b/src/Localization/lang/zh.json @@ -282,9 +282,15 @@ "settings.drivers.update.desc": "{0} → {1} · {2}", "settings.drivers.package": "{0},{1} MB", "settings.drivers.firmware.group": "固件", - "settings.drivers.firmware.warn": "刷写屏幕或触控板固件风险自负。XiControl 未检查该固件包,也不会安装:链接将在浏览器中打开。无法读取当前固件版本——你可能已经是这个版本。请仅使用此压缩包内的官方工具、在接通电源的情况下刷写,且不要中断。", + "settings.drivers.firmware.warn": "刷写屏幕或触控板固件风险自负。XiControl 未检查该固件包,也不会安装:链接将在浏览器中打开。请仅使用此压缩包内的官方工具、在接通电源的情况下刷写,且不要中断。", "settings.drivers.firmware.title": "固件:{0}", - "settings.drivers.firmware.desc": "目录版本 {0} · {1}。无法读取当前固件版本,因此无法比较。", + "settings.drivers.firmware.desc": "目录版本 {0} · {1}。未能读取当前固件版本,因此无法比较。", + "settings.drivers.firmware.unknown.panel": "目录版本 {0} · {1}。当前版本未知:屏幕控制器仅在本地会话且屏幕开启时响应,远程桌面下无法读取。", + "settings.drivers.firmware.side": "已安装 {0},目录 {1} · {2}。固件编号因面板型号而异,无法据此判断目录版本是否更新。", + "settings.drivers.firmware.same": "已安装 {0},与目录一致 · {1}", + "settings.drivers.firmware.newer": "已安装 {0},目录 {1}——你的版本更新 · {2}", + "settings.drivers.firmware.v.same": "已是最新", + "settings.drivers.firmware.v.newer": "比目录新", "settings.drivers.download": "打开链接", "settings.drivers.download.confirm": "在浏览器中打开?", "settings.drivers.nolink": "无链接", diff --git a/src/SystemIntegration/OemDrivers.cs b/src/SystemIntegration/OemDrivers.cs index 0ee5f68..155724d 100644 --- a/src/SystemIntegration/OemDrivers.cs +++ b/src/SystemIntegration/OemDrivers.cs @@ -19,8 +19,10 @@ public sealed record OemHardwareId(string Id, string Version); /// Установленный драйвер: один из hardware-/compatible-id устройства и версия его драйвера. public sealed record InstalledDriver(string HardwareId, string Version, string Device); -/// Сверка одного устройства с каталогом. — прошивка: её текущую -/// версию читать нечем и устройства под её id в системе нет, поэтому только «в каталоге есть такая». +/// Сверка одного устройства с каталогом. — прошивка, которую сравнить +/// нельзя: текущая версия не прочиталась или (у экрана) номера не сопоставимы — «в каталоге есть такая», +/// и, если прочиталась, текущая рядом. Прошивка тачпада с прочитанной версией получает обычные +/// Same/Update/Newer (XIC-104). public enum DriverState { Same, Update, Newer, Firmware } public sealed record DriverRow(string Module, string Device, string? Installed, string Catalog, DriverState State, OemPackage? Package); @@ -62,6 +64,8 @@ public static class OemProtocol /// Прошивки: версии в каталоге — прошивочные, сравнивать их с версией драйвера нельзя. internal static bool IsFirmware(string module) => module is "FWTP" or "FWSC"; + internal const string TouchpadFirmware = "FWTP", PanelFirmware = "FWSC"; + /// /// Тело запроса. Ключи строго по алфавиту и компактно: служба собирает JSON на std::map, и /// подпись считается по ровно такой строке. Без sn: сервер отвечает так же, а хеш @@ -191,16 +195,34 @@ internal static int CompareVersions(string a, string b) => ? va.CompareTo(vb) : string.Compare(a, b, StringComparison.OrdinalIgnoreCase); + /// + /// Версия прошивки тачпада из каталожной: последние четыре числа + /// (3.5.13.01.44.25.13.0 → 44.25.13.0; префикс 3.5.13.01 — номер пакета, + /// он же в имени zip). Именно эти четыре числа отдаёт сам тачпад командой 0x10 (XIC-103). + /// null — строка другого вида, сравнивать не с чем. + /// + internal static string? TouchpadCatalogVersion(string catalog) + { + var parts = catalog.Split('.'); + if (parts.Length < 4) return null; + var tail = parts[^4..]; + return tail.All(p => p.Length > 0 && p.All(char.IsAsciiDigit)) ? string.Join('.', tail) : null; + } + /// /// Строки сверки. Версия — из записи ЭТОГО hardware-id, не «пакета»: один zip везёт драйверы /// разных вендоров (у аудио 10 разных версий на 125 id). Несколько id каталога могут указывать /// на одно устройство — строку даём одну. /// Прошивки (XIC-102) — без сверки по hardware-id: их id в каталоге синтетические - /// (USB\VID_6789&PID_1234&REV_1236 у экрана), такого устройства в системе нет, - /// а текущую версию прошивки читать нечем. Поэтому строка — одна на модуль с пакетом, - /// «в каталоге есть прошивка X»; в Device — код модуля, имя ему даёт UI по локализации. + /// (USB\VID_6789&PID_1234&REV_1236 у экрана), такого устройства в системе нет. + /// Строка — одна на модуль с пакетом; в Device — код модуля, имя ему даёт UI по локализации. + /// Текущая версия прошивки — из (код модуля → прочитанное, XIC-104): + /// у тачпада она сравнивается с хвостом каталожной, у экрана только встаёт рядом — ROM Ver панели + /// отличается от каталожного старшим байтом, и «старее» это или другой вариант панели, по номеру + /// не сказать (docs/16). /// - internal static List Compare(IEnumerable modules, IReadOnlyList installed) + internal static List Compare(IEnumerable modules, IReadOnlyList installed, + IReadOnlyDictionary? firmware = null) { var rows = new List(); var seen = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -209,7 +231,7 @@ internal static List Compare(IEnumerable modules, IReadOnl if (OemProtocol.IsFirmware(m.Name)) { if (m.Package is not null && seen.Add(m.Name)) - rows.Add(new DriverRow(m.Name, m.Name, null, m.HardwareIds.Count > 0 ? m.HardwareIds[0].Version : "", DriverState.Firmware, m.Package)); + rows.Add(FirmwareRow(m, firmware?.GetValueOrDefault(m.Name))); continue; } foreach (var h in m.HardwareIds) @@ -223,6 +245,22 @@ internal static List Compare(IEnumerable modules, IReadOnl } return rows; } + + private static DriverRow FirmwareRow(OemModule m, string? current) + { + string catalog = m.HardwareIds.Count > 0 ? m.HardwareIds[0].Version : ""; + var state = DriverState.Firmware; + if (m.Name == OemProtocol.TouchpadFirmware && TouchpadCatalogVersion(catalog) is { } tail) + { + catalog = tail; + if (current is not null) + { + int c = CompareVersions(tail, current); + state = c > 0 ? DriverState.Update : c < 0 ? DriverState.Newer : DriverState.Same; + } + } + return new DriverRow(m.Name, m.Name, current, catalog, state, m.Package); + } } /// @@ -235,8 +273,10 @@ public static class OemDriverCheck private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(15); private const int Parallel = 4; // вежливо к их серверу: полный скан — 25 запросов - /// Звать только из фонового потока. - public static async Task RunAsync(CancellationToken ct = default) + /// Звать только из фонового потока. — чтение текущей + /// версии прошивки по коду модуля (XIC-104); зовётся только для прошивок, на которые в каталоге + /// есть пакет: у экрана чтение включает записи в контроллер, без повода их не делаем. + public static async Task RunAsync(Func? currentFirmware = null, CancellationToken ct = default) { var sys = SystemInfo.Current; string? product = sys.Board; @@ -280,7 +320,7 @@ public static async Task RunAsync(CancellationToken ct = defa int failed = answers.Count(a => a is null); var modules = answers.Where(a => a is not null).SelectMany(a => a!).ToList(); - var rows = DriverMatch.Compare(modules, installed); + var rows = DriverMatch.Compare(modules, installed, ReadFirmware(modules, currentFirmware)); var status = failed == answers.Length ? DriverCheckStatus.Failed : modules.Count == 0 ? DriverCheckStatus.NoCatalog : rows.Count == 0 ? DriverCheckStatus.NoMatch @@ -291,6 +331,24 @@ public static async Task RunAsync(CancellationToken ct = defa bios is not null, failed, DateTime.Now); } + // По очереди, а не параллельно: тачпад и контроллер панели — разные устройства, но спешить + // некуда, а чтение версии у каждого — секунды пауз между кадрами + private static Dictionary? ReadFirmware(List modules, Func? read) + { + if (read is null) return null; + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (string name in modules.Where(m => OemProtocol.IsFirmware(m.Name) && m.Package is not null) + .Select(m => m.Name).Distinct(StringComparer.OrdinalIgnoreCase)) + { + string? v = null; + try { v = read(name); } + catch (Exception ex) { Log.Ex($"OemDriverCheck.ReadFirmware({name})", ex); } + Log.Write($"OemDriverCheck: текущая прошивка {name} — {v ?? "не прочиталась"}"); + if (v is not null) result[name] = v; + } + return result; + } + private static string Trim(string s) => s.Length <= 120 ? s : s[..120] + "…"; private static string WindowsBuild() diff --git a/src/SystemIntegration/PanelFirmware.cs b/src/SystemIntegration/PanelFirmware.cs new file mode 100644 index 0000000..d5b06df --- /dev/null +++ b/src/SystemIntegration/PanelFirmware.cs @@ -0,0 +1,178 @@ +using System.Buffers.Binary; +using System.Globalization; +using System.Runtime.InteropServices; +using static XiControl.SystemIntegration.PanelFirmwareProtocol; + +namespace XiControl.SystemIntegration; + +/// +/// Протокол чтения версии прошивки TCON-контроллера панели Himax HX8874 (XIC-103/104): чистая +/// логика, без IGCL. Восстановлен из IGCLCSharpDLL.dll (класс IGCLAUXFunc), которой +/// пользуется FirmwareScreenTool.exe службы Xiaomi, — последовательность повторена байт в +/// байт, ничего сверх. Разбор — docs/16-oem-driver-check.md, «Текущие версии прошивок». +/// +public static class PanelFirmwareProtocol +{ + /// Одна операция ctlAUXAccess: запись или чтение, канал, адрес, данные. + public sealed record AuxOp(int Op, int Flags, uint Address, byte[] Data, int Size); + + public const int OpRead = 1, OpWrite = 2; + public const int FlagNative = 1, FlagI2c = 2, FlagI2cMot = 4; // native AUX, I2C-over-AUX, I2C с MOT + public const uint DpcdSinkOui = 0x400; // DPCD: OUI приёмника, 3 байта + public const uint HimaxI2c = 0xF2; // I2C-адрес контроллера за AUX + public const uint RegRomVersion = 0xE7800210; // ROM Ver — его служба сверяет с каталогом + + // ctl_aux_access_args_t, 0xB0 байт: Size@0, Version@4, OpType@8, Flags@0xC, Address@0x10, + // RAD@0x18, PortID@0x20, DataSize@0x24, Data[132]@0x28 — сверено с IGCLCSharpDLL.dll + public const int ArgsSize = 0xB0, OffDataSize = 0x24, OffData = 0x28, DataMax = 132; + + /// Чтение OUI приёмника — только чтение, им контроллер и опознаётся. + public static AuxOp ReadOui() => new(OpRead, FlagNative, DpcdSinkOui, [], 3); + + /// Himax: OUI 94 0B D5. Только на нём делаем записи чтения регистра. + public static bool IsHimax(byte[] oui) => oui is [0x94, 0x0B, 0xD5]; + + /// + /// Чтение регистра (HX8874RegRead): запись 0D 00, запись 20 01, запись с MOT + /// 10 байт 00 <адрес LE> 00…, чтение 4 байт. Записи идут в контроллер — так делает OEM + /// при каждом скане; регистры прошивки от этого не меняются. + /// + public static AuxOp[] RegisterRead(uint reg) + { + var addr = new byte[10]; + BinaryPrimitives.WriteUInt32LittleEndian(addr.AsSpan(1), reg); + return + [ + new(OpWrite, FlagI2c, HimaxI2c, [0x0D, 0x00], 2), + new(OpWrite, FlagI2c, HimaxI2c, [0x20, 0x01], 2), + new(OpWrite, FlagI2cMot, HimaxI2c, addr, 10), + new(OpRead, FlagI2c, HimaxI2c, [], 4), + ]; + } + + /// Аргументы ctlAUXAccess под операцию — ровно 0xB0 байт. + public static byte[] Args(AuxOp op) + { + var a = new byte[ArgsSize]; + BinaryPrimitives.WriteInt32LittleEndian(a.AsSpan(0x00), ArgsSize); + BinaryPrimitives.WriteInt32LittleEndian(a.AsSpan(0x08), op.Op); + BinaryPrimitives.WriteInt32LittleEndian(a.AsSpan(0x0C), op.Flags); + BinaryPrimitives.WriteUInt32LittleEndian(a.AsSpan(0x10), op.Address); + BinaryPrimitives.WriteInt32LittleEndian(a.AsSpan(OffDataSize), op.Size); + op.Data.CopyTo(a, OffData); + return a; + } + + /// Данные ответа: DataSize после вызова, не больше буфера. Негодный размер — пусто. + public static byte[] Result(byte[] args) + { + int got = BinaryPrimitives.ReadInt32LittleEndian(args.AsSpan(OffDataSize)); + return got is > 0 and <= DataMax ? args[OffData..(OffData + got)] : []; + } + + /// ROM Ver в виде каталога: 4 байта LE → hex без префикса (06 0B 57 00 → + /// 570B06, в каталоге так же — 740B08). Ноль или короткий ответ — null: так + /// контроллер отвечает, когда ничего не прочиталось. + public static string? RomVersion(byte[] r) + { + if (r.Length < 4) return null; + uint v = BinaryPrimitives.ReadUInt32LittleEndian(r); + return v == 0 ? null : v.ToString("X6", CultureInfo.InvariantCulture); + } +} + +/// +/// Текущая версия прошивки панели (ROM Ver TCON Himax) через Intel IGCL ctlAUXAccess — eDP AUX, +/// user-mode API драйвера Intel, тот же ControlLib.dll, что у . +/// Зовётся только по кнопке проверки драйверов и только если в каталоге есть прошивка экрана: чтение +/// включает записи в контроллер. Видна панель лишь в локальном сеансе при включённом экране — по RDP +/// IGCL отдаёт ноль выходов, и null («версия неизвестна») здесь нормальный ответ, не ошибка. +/// Своя инициализация IGCL на один вызов: держать её открытой между проверками незачем. +/// +public static class PanelFirmware +{ + private const int InitArgsSize = 36, AppVersion = 1 << 16; // как в GpuTelemetry + + /// ROM Ver контроллера («570B06») или null. Звать из фонового потока. + public static string? ReadRomVersion() + { + IntPtr api = IntPtr.Zero; + try + { + if (!Init(out api)) return null; + foreach (IntPtr output in Outputs(api)) + { + if (Run(output, ReadOui()) is not { } oui || !IsHimax(oui)) continue; + byte[]? r = null; + foreach (var op in RegisterRead(RegRomVersion)) + if ((r = Run(output, op)) is null) break; // операция не прошла — дальше не пишем + string? v = r is null ? null : RomVersion(r); + if (v is null) Log.Write("PanelFirmware: контроллер Himax найден, но ROM Ver не прочитался"); + return v; + } + Log.Write("PanelFirmware: контроллер Himax не найден (RDP или экран выключен)"); + return null; + } + catch (Exception ex) when (ex is DllNotFoundException or EntryPointNotFoundException or BadImageFormatException) + { + Log.Write($"PanelFirmware: IGCL недоступен — {ex.GetType().Name}"); // не Intel + return null; + } + finally + { + if (api != IntPtr.Zero) + try + { + int r = ctlClose(api); + if (r != 0) Log.Write($"PanelFirmware: ctlClose -> 0x{r:X8}"); + } + catch (Exception ex) { Log.Ex("PanelFirmware.Close", ex); } + } + } + + private static bool Init(out IntPtr api) + { + var args = new byte[InitArgsSize]; + BinaryPrimitives.WriteInt32LittleEndian(args.AsSpan(0), InitArgsSize); + BinaryPrimitives.WriteInt32LittleEndian(args.AsSpan(8), AppVersion); + int r = ctlInit(args, out api); + if (r == 0) return true; + Log.Write($"PanelFirmware: ctlInit -> 0x{r:X8}"); + api = IntPtr.Zero; + return false; + } + + private static List Outputs(IntPtr api) + { + var list = new List(); + uint n = 0; + if (ctlEnumerateDevices(api, ref n, null) != 0 || n == 0) return list; + var devices = new IntPtr[n]; + if (ctlEnumerateDevices(api, ref n, devices) != 0) return list; + foreach (IntPtr d in devices.Take((int)n)) + { + uint m = 0; + if (ctlEnumerateDisplayOutputs(d, ref m, null) != 0 || m == 0) continue; + var outs = new IntPtr[m]; + if (ctlEnumerateDisplayOutputs(d, ref m, outs) == 0) list.AddRange(outs.Take((int)m)); + } + return list; + } + + /// Одна операция; null — IGCL вернул ошибку. + private static byte[]? Run(IntPtr output, AuxOp op) + { + byte[] args = Args(op); + int r = ctlAUXAccess(output, args); + if (r == 0) return Result(args); + Log.Write($"PanelFirmware: ctlAUXAccess op={op.Op} flags={op.Flags} addr=0x{op.Address:X} -> 0x{r:X8}"); + return null; + } + + private const string Lib = "ControlLib.dll"; + [DllImport(Lib)] private static extern int ctlInit(byte[] args, out IntPtr api); + [DllImport(Lib)] private static extern int ctlClose(IntPtr api); + [DllImport(Lib)] private static extern int ctlEnumerateDevices(IntPtr api, ref uint count, IntPtr[]? devices); + [DllImport(Lib)] private static extern int ctlEnumerateDisplayOutputs(IntPtr device, ref uint count, IntPtr[]? outputs); + [DllImport(Lib)] private static extern int ctlAUXAccess(IntPtr output, [In, Out] byte[] args); +} diff --git a/src/SystemIntegration/TouchpadHaptics.cs b/src/SystemIntegration/TouchpadHaptics.cs index 7327360..aacbdc5 100644 --- a/src/SystemIntegration/TouchpadHaptics.cs +++ b/src/SystemIntegration/TouchpadHaptics.cs @@ -42,6 +42,17 @@ public sealed class TouchpadHaptics } } + /// Версия прошивки тачпада (команда 0x10, только чтение — XIC-104). null — тачпада нет + /// или он не ответил. Зовётся по кнопке проверки драйверов, с пула. + public string? ReadFirmwareVersion() + { + lock (_lock) + { + using var h = Open(); + return h is not null && ReadValues(h, CmdFirmware, 2) is { } v ? FirmwareVersion(v) : null; + } + } + /// Записать силу вибрации; true — тачпад подтвердил значение чтением. public bool SetVibration(HapticsVibration level) => Write(CmdVibration, VibrationValues(level), v => MatchVibration(v[0], v[1]) == level); diff --git a/src/SystemIntegration/TouchpadHapticsProtocol.cs b/src/SystemIntegration/TouchpadHapticsProtocol.cs index 563a2a7..e30f155 100644 --- a/src/SystemIntegration/TouchpadHapticsProtocol.cs +++ b/src/SystemIntegration/TouchpadHapticsProtocol.cs @@ -29,6 +29,7 @@ public static class TouchpadHapticsProtocol public const byte CmdPressure = 0x5B; // 4 short: порог, порог×0.33, 500, 400 public const byte CmdSlide = 0x58; // 1 short: сила щелчка PulseFrame (XIC-73) public const byte CmdHeavyPress = 0x59; // 1 short: второй щелчок прошивки на сильном нажатии (XIC-78) + public const byte CmdFirmware = 0x10; // 2 short (4 байта): версия прошивки, только чтение (XIC-104) /// Значения 0x59 — ровно как шлёт PC Manager (WriteEnableHeavyPress): /// 1 — прошивка щёлкает второй раз, когда нажатие продавлено, 2 — не щёлкает (заводское). @@ -130,6 +131,12 @@ public static byte[] ReadRequest(byte cmd, int count) return any ? values : null; } + /// Версия прошивки из ответа на : четыре байта данных по + /// порядку, каждый — часть версии (2C 19 0B 00 → 44.25.11.0, XIC-103). Байты + /// приходят как два short little-endian — раскладываем обратно. + public static string FirmwareVersion(IReadOnlyList v) => + $"{v[0] & 0xFF}.{v[0] >> 8}.{v[1] & 0xFF}.{v[1] >> 8}"; + /// XOR байтов [3..31] плюс 1 — одна формула для кадров и ответов. public static byte Checksum(byte[] f) { diff --git a/src/Ui/AppController.cs b/src/Ui/AppController.cs index 63ab657..68e67e7 100644 --- a/src/Ui/AppController.cs +++ b/src/Ui/AppController.cs @@ -950,7 +950,7 @@ public async Task CheckDriversAsync() DriverCheck = previous with { Status = DriverCheckStatus.Running }; // Task.Run: синхронная часть проверки (WMI-перечень драйверов) длится секунды — вызывающий // должен сразу получить задачу и успеть показать «Проверяю…» - try { DriverCheck = await Task.Run(() => OemDriverCheck.RunAsync()).ConfigureAwait(false); } + try { DriverCheck = await Task.Run(() => OemDriverCheck.RunAsync(CurrentFirmware)).ConfigureAwait(false); } catch (Exception ex) { Log.Ex("CheckDrivers", ex); @@ -959,6 +959,15 @@ public async Task CheckDriversAsync() finally { Volatile.Write(ref _driverCheckRunning, 0); } } + // Текущие версии прошивок (XIC-104): тачпад — нашим каналом COL05 (в тестах _haptics нет), + // экран — ROM Ver контроллера панели через IGCL + private string? CurrentFirmware(string module) => module switch + { + OemProtocol.TouchpadFirmware => _haptics?.ReadFirmwareVersion(), + OemProtocol.PanelFirmware => PanelFirmware.ReadRomVersion(), + _ => null, + }; + // ---- Службы Xiaomi: MiDeviceService и MiService (XIC-87) ---- // Одна операция за раз на обе службы: остановка ждёт до ~15 с, параллельно им незачем diff --git a/src/Ui/Settings/DriversTab.cs b/src/Ui/Settings/DriversTab.cs index c2d00c9..bb5b63d 100644 --- a/src/Ui/Settings/DriversTab.cs +++ b/src/Ui/Settings/DriversTab.cs @@ -54,9 +54,12 @@ private void AddResult(SettingsToolkit ui, DriverCheckResult r) if (r.Rows.Count == 0) return; + // прошивки — своей группой ниже, в обновления и сводку драйверов не попадают + var drivers = r.Rows.Where(x => !OemProtocol.IsFirmware(x.Module)).ToList(); + // ---- Обновления: устройство, было → стало, пакет; один пакет — одна кнопка ---- ui.AddGroup(this, "settings.drivers.updates.group"); - var updates = r.Rows.Where(x => x.State == DriverState.Update).ToList(); + var updates = drivers.Where(x => x.State == DriverState.Update).ToList(); var offered = new HashSet(StringComparer.OrdinalIgnoreCase); if (updates.Count == 0) ui.AddNote(this, "settings.drivers.updates.none"); @@ -70,24 +73,23 @@ private void AddResult(SettingsToolkit ui, DriverCheckResult r) } } - // ---- Прошивки экрана и тачпада (XIC-102): только «в каталоге есть», без сравнения — текущую - // версию читать нечем, а устройства под их hardware-id в системе нет. Имя — по коду модуля, не - // из PnP. Предупреждение жёсткое, как у BIOS: сбой прошивки панели или тачпада — тоже ремонт ---- - var firmware = r.Rows.Where(x => x.State == DriverState.Firmware).ToList(); + // ---- Прошивки экрана и тачпада (XIC-102/104): по строке на модуль, имя — по коду модуля, не + // из PnP. Тачпад сравнивается как драйвер, у экрана обе версии рядом без вывода (номера + // вариантов панели несопоставимы), не прочиталась — «в каталоге есть X». Предупреждение + // жёсткое, как у BIOS: сбой прошивки панели или тачпада — тоже ремонт ---- + var firmware = r.Rows.Where(x => OemProtocol.IsFirmware(x.Module)).ToList(); if (firmware.Count > 0) { ui.AddGroup(this, "settings.drivers.firmware.group"); ui.AddNote(this, "settings.drivers.firmware.warn"); foreach (var f in firmware) - { - string desc = Loc.T("settings.drivers.firmware.desc", f.Catalog, PackageText(f.Package)); - Controls.Add(ui.Row(Loc.T("settings.drivers.firmware.title", ModuleName(f.Module)), desc, DownloadButton(ui, f.Package, offered))); - } + Controls.Add(ui.Row(Loc.T("settings.drivers.firmware.title", ModuleName(f.Module)), + FirmwareText(f), FirmwareControl(ui, f, offered))); } // ---- Сводка по модулям: под сотню строк «совпадает» читать незачем ---- ui.AddGroup(this, "settings.drivers.summary.group"); - foreach (var g in r.Rows.Where(x => x.State != DriverState.Firmware).GroupBy(x => x.Module).OrderBy(g => ModuleName(g.Key), StringComparer.CurrentCulture)) + foreach (var g in drivers.GroupBy(x => x.Module).OrderBy(g => ModuleName(g.Key), StringComparer.CurrentCulture)) { var parts = new List(); int up = g.Count(x => x.State == DriverState.Update), same = g.Count(x => x.State == DriverState.Same), newer = g.Count(x => x.State == DriverState.Newer); @@ -98,9 +100,33 @@ private void AddResult(SettingsToolkit ui, DriverCheckResult r) if (up > 0) value.ForeColor = ui.T.Accent; Controls.Add(ui.Row(ModuleName(g.Key), g.First().Device, value)); } - if (r.Rows.Any(x => x.State == DriverState.Newer)) ui.AddNote(this, "settings.drivers.summary.note"); + if (drivers.Any(x => x.State == DriverState.Newer)) ui.AddNote(this, "settings.drivers.summary.note"); } + private static string FirmwareText(DriverRow f) + { + string pkg = PackageText(f.Package); + return f.State switch + { + DriverState.Update => Loc.T("settings.drivers.update.desc", f.Installed ?? "—", f.Catalog, pkg), + DriverState.Same => Loc.T("settings.drivers.firmware.same", f.Catalog, pkg), + DriverState.Newer => Loc.T("settings.drivers.firmware.newer", f.Installed ?? "—", f.Catalog, pkg), + // экран: версия прочиталась, но сравнивать её с каталожной нельзя + _ when f.Installed is { } current => Loc.T("settings.drivers.firmware.side", current, f.Catalog, pkg), + _ when f.Module == OemProtocol.PanelFirmware => Loc.T("settings.drivers.firmware.unknown.panel", f.Catalog, pkg), + _ => Loc.T("settings.drivers.firmware.desc", f.Catalog, pkg), + }; + } + + // Ссылка — где пакет может пригодиться; где стоит та же или новее — только вердикт + private static Control FirmwareControl(SettingsToolkit ui, DriverRow f, HashSet offered) => + f.State switch + { + DriverState.Same => ui.ValueLabel(Loc.T("settings.drivers.firmware.v.same")), + DriverState.Newer => ui.ValueLabel(Loc.T("settings.drivers.firmware.v.newer")), + _ => DownloadButton(ui, f.Package, offered), + }; + /// /// Службы Xiaomi MiDeviceService и MiService (XIC-87, docs/17): состояние и «Отключить / Включить» /// в два нажатия. Здесь, а не в «О программе»: MiDeviceService — та самая служба, что проверяет diff --git a/tests/XiControl.Tests/OemDriversTests.cs b/tests/XiControl.Tests/OemDriversTests.cs index e421fc4..42c08ec 100644 --- a/tests/XiControl.Tests/OemDriversTests.cs +++ b/tests/XiControl.Tests/OemDriversTests.cs @@ -207,7 +207,58 @@ public void Прошивка_без_совпавшего_устройства_д r.Package.Should().NotBeNull(); }); rows.Single(r => r.Module == "FWSC").Catalog.Should().Be("740B08"); - rows.Single(r => r.Module == "FWTP").Catalog.Should().Be("3.5.13.01.44.25.13.0"); + rows.Single(r => r.Module == "FWTP").Catalog.Should().Be("44.25.13.0", "у тачпада показываем саму версию прошивки, без номера пакета"); + } + + // ---- текущие версии прошивок (XIC-104) ---- + + [Theory] + [InlineData("3.5.13.01.44.25.13.0", "44.25.13.0")] + [InlineData("44.25.13.0", "44.25.13.0")] + [InlineData("44.25.13", null)] + [InlineData("3.5.13.01.44.25.13.x", null)] + [InlineData("740B08", null)] + [InlineData("", null)] + public void Версия_тачпада_из_хвоста_каталожной(string catalog, string? expected) => + DriverMatch.TouchpadCatalogVersion(catalog).Should().Be(expected); + + private static readonly OemModule Touchpad = Module("FWTP", (@"USB\VID_6989&PID_1234&REV_1836", "3.5.13.01.44.25.13.0")); + private static readonly OemModule Panel = Module("FWSC", (@"USB\VID_6789&PID_1234&REV_1236", "740B08")); + + [Theory] + [InlineData("44.25.11.0", DriverState.Update)] // TM2424 на момент XIC-103 + [InlineData("44.25.13.0", DriverState.Same)] + [InlineData("44.25.14.0", DriverState.Newer)] + public void Тачпад_с_прочитанной_версией_сравнивается_как_драйвер(string current, DriverState state) + { + var row = DriverMatch.Compare([Touchpad], [], new Dictionary { ["FWTP"] = current }).Single(); + + row.State.Should().Be(state); + row.Installed.Should().Be(current); + row.Catalog.Should().Be("44.25.13.0"); + row.Package.Should().NotBeNull(); + } + + [Fact] + public void Экран_с_прочитанной_версией_только_рядом_без_вывода() + { + var row = DriverMatch.Compare([Panel], [], new Dictionary { ["FWSC"] = "570B06" }).Single(); + + row.State.Should().Be(DriverState.Firmware, "старший байт ROM Ver отличается — может быть другой вариант панели"); + row.Installed.Should().Be("570B06"); + row.Catalog.Should().Be("740B08"); + } + + [Fact] + public void Не_прочиталась_версия_остаётся_в_каталоге_есть() + { + var rows = DriverMatch.Compare([Touchpad, Panel], [], new Dictionary()); + + rows.Should().HaveCount(2).And.AllSatisfy(r => + { + r.State.Should().Be(DriverState.Firmware); + r.Installed.Should().BeNull(); + }); } [Fact] diff --git a/tests/XiControl.Tests/PanelFirmwareProtocolTests.cs b/tests/XiControl.Tests/PanelFirmwareProtocolTests.cs new file mode 100644 index 0000000..8736a40 --- /dev/null +++ b/tests/XiControl.Tests/PanelFirmwareProtocolTests.cs @@ -0,0 +1,77 @@ +using FluentAssertions; +using XiControl.SystemIntegration; +using Xunit; +using static XiControl.SystemIntegration.PanelFirmwareProtocol; + +namespace XiControl.Tests; + +/// +/// Версия прошивки контроллера панели (XIC-104). Эталон — последовательность из +/// IGCLCSharpDLL.dll OEM (docs/16): записи в контроллер должны совпадать с ней байт в байт, +/// ничего сверх. Сам IGCL — глазами, за ноутбуком (по RDP панели не видно). +/// +public sealed class PanelFirmwareProtocolTests +{ + private static byte[] Hex(string s) => Convert.FromHexString(s.Replace(" ", "")); + + [Fact] + public void Чтение_регистра_повторяет_OEM_байт_в_байт() + { + var ops = RegisterRead(RegRomVersion); + + ops.Select(o => (o.Op, o.Flags, o.Address, o.Size)).Should().Equal( + (OpWrite, FlagI2c, 0xF2u, 2), + (OpWrite, FlagI2c, 0xF2u, 2), + (OpWrite, FlagI2cMot, 0xF2u, 10), + (OpRead, FlagI2c, 0xF2u, 4)); + ops[0].Data.Should().Equal(Hex("0D 00")); + ops[1].Data.Should().Equal(Hex("20 01")); + ops[2].Data.Should().Equal(Hex("00 10 02 80 E7 00 00 00 00 00"), "адрес регистра — little-endian со второго байта"); + ops[3].Data.Should().BeEmpty(); + } + + [Fact] + public void Опознание_только_по_OUI_Himax() + { + ReadOui().Should().Match(o => o.Op == OpRead && o.Flags == FlagNative && o.Address == 0x400 && o.Size == 3); + IsHimax(Hex("94 0B D5")).Should().BeTrue(); + IsHimax(Hex("00 1C F8")).Should().BeFalse(); + IsHimax(Hex("94 0B")).Should().BeFalse(); + } + + [Fact] + public void Аргументы_по_раскладке_ctl_aux_access_args_t() + { + var a = Args(RegisterRead(RegRomVersion)[2]); + + a.Should().HaveCount(0xB0); + a[0x00..0x04].Should().Equal(Hex("B0 00 00 00")); // Size + a[0x08..0x0C].Should().Equal(Hex("02 00 00 00")); // OpType: запись + a[0x0C..0x10].Should().Equal(Hex("04 00 00 00")); // Flags: I2C MOT + a[0x10..0x14].Should().Equal(Hex("F2 00 00 00")); // Address + a[0x24..0x28].Should().Equal(Hex("0A 00 00 00")); // DataSize + a[0x28..0x32].Should().Equal(Hex("00 10 02 80 E7 00 00 00 00 00")); + a[0x32..].Should().OnlyContain(b => b == 0); + } + + [Fact] + public void Ответ_берётся_по_DataSize() + { + var a = Args(new AuxOp(OpRead, FlagI2c, HimaxI2c, [], 4)); + Hex("06 0B 57 00").CopyTo(a, 0x28); + + Result(a).Should().Equal(Hex("06 0B 57 00")); + a[0x24] = 0; + Result(a).Should().BeEmpty(); + a[0x24] = 200; // больше буфера — мусор, а не данные + Result(a).Should().BeEmpty(); + } + + [Theory] + [InlineData("06 0B 57 00", "570B06")] // TM2424 на момент XIC-103 + [InlineData("08 0B 74 00", "740B08")] // в формате каталога + [InlineData("00 00 00 00", null)] + [InlineData("06 0B", null)] + public void ROM_Ver_в_формате_каталога(string bytes, string? expected) => + RomVersion(Hex(bytes)).Should().Be(expected); +} diff --git a/tests/XiControl.Tests/TouchpadHapticsProtocolTests.cs b/tests/XiControl.Tests/TouchpadHapticsProtocolTests.cs index c1c882d..3223910 100644 --- a/tests/XiControl.Tests/TouchpadHapticsProtocolTests.cs +++ b/tests/XiControl.Tests/TouchpadHapticsProtocolTests.cs @@ -85,6 +85,15 @@ public void Parses_live_responses() ParseResponse(Padded("0D 06 3F 00 2C 19 0B 00"), 2).Should().Equal(0x192C, 0x000B); } + // версия прошивки (XIC-104): живой ответ на 0x10 — тот же 44.25.11.0, что в логе PC Manager + [Fact] + public void Firmware_version_from_live_response() + { + ReadRequest(CmdFirmware, 2).Should().Equal(Padded("0D 07 15 01 00 01 00 04 10")); + FirmwareVersion(ParseResponse(Padded("0D 06 3F 00 2C 19 0B 00"), 2)!).Should().Be("44.25.11.0"); + FirmwareVersion([0x192C, 0x000D]).Should().Be("44.25.13.0"); // каталожная — для сверки формата + } + [Fact] public void Empty_response_means_not_ready() { From dd0a25a98425985636939310c725569cbbf96860 Mon Sep 17 00:00:00 2001 From: Oksion Date: Tue, 20 Oct 2026 15:16:43 +0500 Subject: [PATCH 2/5] =?UTF-8?q?docs(drivers):=20=D1=80=D0=B0=D0=B7=D0=B1?= =?UTF-8?q?=D0=BE=D1=80=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82=D0=B0=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=88=D0=B8=D0=B2=D0=BA=D0=B8=20=D1=82=D0=B0=D1=87?= =?UTF-8?q?=D0=BF=D0=B0=D0=B4=D0=B0;=203.5.13.01=20=E2=80=94=20=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20BL7853=20(XIC-104)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В пакете тачпада две прошивки: контроллер касаний Betterlife BL7853 (3.5.13.01) и отдельный MCU силы и вибрации (44.25.13.0, папка Force). Команда 0x10 отдаёт версию второго, а не «номер пакета», как писали. HID-дескриптор прошивки не даёт ни клавиатуры, ни Consumer, ни usage жестов: аппаратных «умных жестов» нет, их считают Windows и PC Manager. Co-Authored-By: Claude Opus 5.5 --- docs/16-oem-driver-check.md | 41 +++++++++++++++++++++--- src/SystemIntegration/OemDrivers.cs | 7 ++-- tests/XiControl.Tests/OemDriversTests.cs | 2 +- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/docs/16-oem-driver-check.md b/docs/16-oem-driver-check.md index d62cfec..ef28ad3 100644 --- a/docs/16-oem-driver-check.md +++ b/docs/16-oem-driver-check.md @@ -199,10 +199,11 @@ XMAPT4B0P0A0A → "0A0A" → 0x0A0A = 2570 → "1.0.0.2570" **Тачпад (`FWTP`) — читается, стабильно.** Команда `0x10` нашего канала `COL05` (тот же, что у тактильного отклика XIC-77, без повышения прав): ответ `2C 19 0B 00` = **44.25.11.0**. Работает и по -RDP — HID-устройство от сеанса не зависит. В каталоге `3.5.13.01.44.25.13.0`: префикс `3.5.13.01` -совпадает с именем пакета (`P701_TouchPad_0728_01_351301.zip`), а хвост — версия прошивки, -**44.25.13.0**. То есть на TM2424 стоит прошивка старее каталожной. Служба Xiaomi эту версию -не видит вовсе: ищет её в реестре и пишет «Failed to get current firmware version for FWTP». +RDP — HID-устройство от сеанса не зависит. В каталоге `3.5.13.01.44.25.13.0` — **две** прошивки +(см. «Что внутри пакета тачпада» ниже): `3.5.13.01` — контроллер касаний BL7853, `44.25.13.0` — +MCU силы и вибрации. Команда `0x10` отдаёт версию второго, поэтому сравниваем хвост: на TM2424 +стоит 44.25.11.0, старее каталожной. Версию BL7853 читать нечем. Служба Xiaomi FWTP не видит вовсе: +ищет версию в реестре и пишет «Failed to get current firmware version for FWTP». **Экран (`FWSC`) — читается, но нестабильно и не без записи.** Это прошивка **TCON-контроллера панели Himax HX8874**, а не «монитора». `FirmwareScreenTool.exe --version` (так его зовёт служба, @@ -246,6 +247,38 @@ RDP — HID-устройство от сеанса не зависит. В ка последняя часть. Поэтому даже с чтением версии «у вас старее» честно говорить можно только про тачпад; для экрана — показывать обе версии рядом, без вывода. +### Что внутри пакета тачпада (разбор 2026-10-20) + +`P701_TouchPad_0728_01_351301.zip` (1 973 496 байт, md5 совпал с каталогом) скачан для разбора, ничего +не запускалось. Внутри: + +- `TP\BL7853_V3.5.13.01_20260615(67D2).bin`, 64 КБ — прошивка **контроллера касаний Betterlife + BL7853**. Код начинается с таблицы переходов `48 00 xx xx`, похоже на Andes NDS32; rizin эту + архитектуру не знает, код не дизассемблирован; +- `Force\44.25.13.0.bin` (она же в корне), 33 КБ — прошивка **отдельного MCU силы и вибрации**, + Cortex-M (векторы: SP `0x20003C60`, reset `0x0100108D`). HID-дескриптора в нём нет: это MCU за + контроллером касаний, наружу он виден только через `COL05`. Утилита так и пишет: «current force + version», «read force calibrate data»; +- `FirmwareTouchPadTool.exe` + `btl_upgrade.dll`/`btl_hid_transfer.dll` (BetterlifeForwardTool) — + прошивальщик. Ходит в `col04` и `col05`, вызывает MIFS `MiInterface`, ищет окно + `mi_touch_gesture`/`MiTouchGestureClass` (`FindWindowExW`) — по всей видимости, чтобы на время + прошивки унять жесты PC Manager; +- `Install.bat` вызывает `FirmwareScreenTool.exe --update HX_V0057_0B06.bin` — команду прошивки + **экрана**, которой в архиве нет: батник скопирован из пакета панели. Совпадение: `0057_0B06` — это + ровно текущий ROM Ver нашей панели `570B06`; +- `CCP\ibtusb.*` — посторонний драйвер, к тачпаду отношения не имеет. + +**Аппаратных «умных жестов» в прошивке нет.** В BL7853 два HID-дескриптора: обычный PTP и вариант с +силой — на TM2424 стоит второй. Он даёт только: мышь (ID 1, legacy-режим), PTP на 5 касаний с +16-битным Tip Pressure (ID 4), служебные feature-репорты (ID 2/3/5/6/7) и два вендорских канала по +32 байта — `0xFF00` (ID 14, `COL04`) и `0xFF01` (ID 13, `COL05`). Ни клавиатуры, ни Consumer Control, +ни usage жестов: распознанный жест прошивке передать в Windows просто нечем, кроме вендорских +каналов, а в `COL05` input-репорты — ответы на запросы. Значит, жесты Windows (2–4 пальца) считает +сама Windows по контактам, а краевые жесты и сильное нажатие Xiaomi — PC Manager (`PluginGesture.dll`) +по тем же контактам и давлению. На «железе» остаётся только тактильная часть: порог клика `0x5B` и +второй щелчок на сильном нажатии `0x59` прошивка отрабатывает сама, но событием наружу не сообщает. +Не проверено: шлёт ли прошивка что-то непрошенное в `COL04` — для этого нужен код BL7853. + ## 5. Где лежат данные Захват не в репозитории (сырые ответы содержат хеш серийника машины) — он в рабочем каталоге diff --git a/src/SystemIntegration/OemDrivers.cs b/src/SystemIntegration/OemDrivers.cs index 155724d..5bed2dc 100644 --- a/src/SystemIntegration/OemDrivers.cs +++ b/src/SystemIntegration/OemDrivers.cs @@ -197,9 +197,10 @@ internal static int CompareVersions(string a, string b) => /// /// Версия прошивки тачпада из каталожной: последние четыре числа - /// (3.5.13.01.44.25.13.0 → 44.25.13.0; префикс 3.5.13.01 — номер пакета, - /// он же в имени zip). Именно эти четыре числа отдаёт сам тачпад командой 0x10 (XIC-103). - /// null — строка другого вида, сравнивать не с чем. + /// (3.5.13.01.44.25.13.0 → 44.25.13.0). В пакете две прошивки: 3.5.13.01 — + /// контроллер касаний Betterlife BL7853, 44.25.13.0 — отдельный MCU силы и вибрации (папка + /// Force архива). Командой 0x10 тачпад отдаёт версию второго — с ней и сравниваем; + /// версию BL7853 читать нечем. null — строка другого вида, сравнивать не с чем. /// internal static string? TouchpadCatalogVersion(string catalog) { diff --git a/tests/XiControl.Tests/OemDriversTests.cs b/tests/XiControl.Tests/OemDriversTests.cs index 42c08ec..5f276e8 100644 --- a/tests/XiControl.Tests/OemDriversTests.cs +++ b/tests/XiControl.Tests/OemDriversTests.cs @@ -207,7 +207,7 @@ public void Прошивка_без_совпавшего_устройства_д r.Package.Should().NotBeNull(); }); rows.Single(r => r.Module == "FWSC").Catalog.Should().Be("740B08"); - rows.Single(r => r.Module == "FWTP").Catalog.Should().Be("44.25.13.0", "у тачпада показываем саму версию прошивки, без номера пакета"); + rows.Single(r => r.Module == "FWTP").Catalog.Should().Be("44.25.13.0", "у тачпада показываем версию прошивки Force-MCU — её тачпад и отдаёт"); } // ---- текущие версии прошивок (XIC-104) ---- From 89ecb8bc146c8d4ab92aac4abc24893169d7ad09 Mon Sep 17 00:00:00 2001 From: Oksion Date: Sat, 24 Oct 2026 20:52:44 +0500 Subject: [PATCH 3/5] =?UTF-8?q?docs(roadmap):=20=D1=82=D1=80=D0=B8=20?= =?UTF-8?q?=D0=BA=D0=B0=D0=BD=D0=B4=D0=B8=D0=B4=D0=B0=D1=82=D0=B0=20=D1=81?= =?UTF-8?q?=20TM2409=20=E2=80=94=20=D0=B4=D0=B0=D1=82=D1=87=D0=B8=D0=BA=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BC=D0=BF=D0=B5=D1=80=D0=B0=D1=82=D1=83=D1=80?= =?UTF-8?q?=D1=8B,=20=D1=8F=D1=80=D0=BB=D1=8B=D0=BA=20=D0=B2=20=C2=AB?= =?UTF-8?q?=D0=9F=D1=83=D1=81=D0=BA=C2=BB,=20165=20=D0=93=D1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Выбор датчика температуры CPU (XIC-105) и частота 165 Гц (XIC-107) — в «Другие модели и платформа»: оба с Redmi Book Pro 2025 (TM2409). Ярлык в меню «Пуск» с подъёмом существующего экземпляра (XIC-106) — отдельным пунктом. Co-Authored-By: Claude Opus 5 --- ROADMAP.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index 73e9023..f09a1a6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -361,10 +361,16 @@ Xiaomi-PC-Manager-Extended) и v0.16.1 (исправления авто-ярко - Другие модели и платформа: - **Профили моделей** (XIC-42) — один встраиваемый JSON вместо знаний о железе, размазанных по коду. - **Телеметрия GPU на AMD** (XIC-62) — разведать ADLX как аналог IGCL. + - **Выбор датчика температуры CPU** (XIC-105) — на TM2409 первая зона DPTF оказалась не ядром: + перечислять зоны с именами и дать выбрать, какую показывать в «Мониторе», компактном виде и трее. + - **Частота 165 Гц** (XIC-107) — на Redmi Book Pro 2025 (TM2409) Windows её предлагает, а у нас в + списке только 144: не опираться на зашитый набор частот, проверить фильтр режимов и округление. - **Переезд на WinUI 3** (XIC-50, M5 из PR #40) — сначала решение про второй рантайм и память (247 МБ против 79 МБ рабочего набора), потом код. - **Потребление от сети** (XIC-72) — прочерк вместо цифры на графике; пересмотреть решение не показывать RAPL при нулевом токе батареи. +- **Ярлык в меню «Пуск»** (XIC-106) — просят пользователи; по клику при уже запущенной копии + поднимать существующий экземпляр (мьютекс + вывод окна вперёд), а не второй процесс. ## ❓ Открытые вопросы - Fn отдельным кодом не приходит → комбинации с Fn недоступны (обходим жестами Mi). From d4b09a679a05699bb83a4b35049830a4ed08b7a1 Mon Sep 17 00:00:00 2001 From: Oksion Date: Sat, 24 Oct 2026 20:55:26 +0500 Subject: [PATCH 4/5] =?UTF-8?q?docs(roadmap):=20=D1=81=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=B2=20v0.19.0=20=E2=80=94=20=D1=82=D1=80=D0=B8=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B8=20=D1=81=20TM2409=20=D0=B2?= =?UTF-8?q?=20=C2=AB=D0=91=D0=BB=D0=B8=D0=B6=D0=B0=D0=B9=D1=88=D0=B5=D0=B5?= =?UTF-8?q?=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Выбор датчика температуры CPU (XIC-105), ярлык в меню «Пуск» с подъёмом существующего экземпляра (XIC-106) и частота 165 Гц (XIC-107) переехали из «Идей» в «Ближайшее». Веха v0.19.0 в Linear заведена, задачи к ней привязаны. Co-Authored-By: Claude Opus 5 --- ROADMAP.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index f09a1a6..d862521 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -339,7 +339,17 @@ Xiaomi-PC-Manager-Extended) и v0.16.1 (исправления авто-ярко ответу прошивки «команды нет» (XIC-100); мелочи с код-ревью (XIC-101). ## 🔜 Ближайшее -- Пока пусто — состав v0.19.0 не отобран; кандидаты — в «Идеях». +Состав **v0.19.0** — три просьбы с одного ноутбука, Redmi Book Pro 2025 (TM2409): +- **Выбор датчика температуры CPU** (XIC-105) — первая зона DPTF оказалась не ядром, цифра + расходится с реальной температурой: перечислять зоны с именами и дать выбрать, какую показывать + в «Мониторе», компактном виде и индикаторе трея. +- **Ярлык в меню «Пуск»** (XIC-106) — просят пользователи; по клику при уже запущенной копии + поднимать существующий экземпляр (мьютекс + вывод окна вперёд), а не второй процесс. +- **Частота 165 Гц** (XIC-107) — Windows её предлагает, а у нас в списке только 144: не опираться + на зашитый набор частот, проверить фильтр режимов и округление. + +Своего TM2409 нет: по температуре и частоте проверка упирается в живого тестера — это главный +риск для состава версии. ## 💡 Идеи - Камера/приватность (`0xA0` из карты MIControl) — на TM2424 событие не замечено, ждём тестеров. @@ -361,16 +371,10 @@ Xiaomi-PC-Manager-Extended) и v0.16.1 (исправления авто-ярко - Другие модели и платформа: - **Профили моделей** (XIC-42) — один встраиваемый JSON вместо знаний о железе, размазанных по коду. - **Телеметрия GPU на AMD** (XIC-62) — разведать ADLX как аналог IGCL. - - **Выбор датчика температуры CPU** (XIC-105) — на TM2409 первая зона DPTF оказалась не ядром: - перечислять зоны с именами и дать выбрать, какую показывать в «Мониторе», компактном виде и трее. - - **Частота 165 Гц** (XIC-107) — на Redmi Book Pro 2025 (TM2409) Windows её предлагает, а у нас в - списке только 144: не опираться на зашитый набор частот, проверить фильтр режимов и округление. - **Переезд на WinUI 3** (XIC-50, M5 из PR #40) — сначала решение про второй рантайм и память (247 МБ против 79 МБ рабочего набора), потом код. - **Потребление от сети** (XIC-72) — прочерк вместо цифры на графике; пересмотреть решение не показывать RAPL при нулевом токе батареи. -- **Ярлык в меню «Пуск»** (XIC-106) — просят пользователи; по клику при уже запущенной копии - поднимать существующий экземпляр (мьютекс + вывод окна вперёд), а не второй процесс. ## ❓ Открытые вопросы - Fn отдельным кодом не приходит → комбинации с Fn недоступны (обходим жестами Mi). From 6ce518dc21bfeebb9e446813478aea7a12c8635a Mon Sep 17 00:00:00 2001 From: Oksion Date: Sat, 24 Oct 2026 21:01:49 +0500 Subject: [PATCH 5/5] =?UTF-8?q?feat(drivers):=20=D0=B2=D0=B5=D1=80=D1=81?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D0=BB?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=20=D0=BA=D0=B0=D1=81=D0=B0=D0=BD=D0=B8=D0=B9?= =?UTF-8?q?=20BL7853=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20COL04=20(XIC-104)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit У тачпада две прошивки, и теперь читаются обе: контроллер касаний BL7853 через COL04 и MCU силы через COL05. В COL04 шлём ровно один константный кадр запроса версии из FirmwareTouchPadTool.exe OEM (0E 07 B4 00 00 01 00 04 B6), ответ разбираем как OEM: [7].[6].[4].[5]. Строка тачпада сверяется по обеим частям каталожной версии. На TM2424: 3.5.9.1 / 44.25.11.0 при 3.5.13.1 / 44.25.13.0 — «обновление». Запросы версий подсказал gxcreator (issue #99, #100). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 5 +- CLAUDE.md | 5 +- docs/05-open-questions.md | 5 +- docs/16-oem-driver-check.md | 38 +++++++++--- src/Localization/lang/en.json | 1 + src/Localization/lang/ru.json | 1 + src/Localization/lang/zh.json | 1 + src/SystemIntegration/OemDrivers.cs | 45 +++++++++----- src/SystemIntegration/TouchpadHaptics.cs | 61 ++++++++++++++----- .../TouchpadHapticsProtocol.cs | 29 +++++++++ src/Ui/AppController.cs | 6 +- src/Ui/Settings/DriversTab.cs | 8 ++- tests/XiControl.Tests/OemDriversTests.cs | 47 +++++++++++--- .../TouchpadHapticsProtocolTests.cs | 13 ++++ 14 files changed, 205 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3136407..516f6ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,9 @@ ### Добавлено - **Прошивки на вкладке «Драйверы» — с текущей версией.** Раньше строка говорила только «в каталоге есть прошивка X». Теперь XiControl читает установленную версию сам, без драйверов. Тачпад - сравнивается как драйвер: «обновление», «актуальна» или «новее каталога» (на TM2424 стоит 44.25.11.0 - при 44.25.13.0 в каталоге). У экрана обе версии показываются рядом, но без вывода: номер прошивки + сравнивается как драйвер: «обновление», «актуальна» или «новее каталога». Прошивок у него две — + контроллер касаний и сила нажатия с вибрацией, сверяются обе (на TM2424 стоят 3.5.9.1 и 44.25.11.0 + при 3.5.13.1 и 44.25.13.0 в каталоге). Запросы версий подсказал gxcreator в issue #99 и #100. У экрана обе версии показываются рядом, но без вывода: номер прошивки панели зависит от её варианта, и по нему не сказать, новее ли каталожная. Версия экрана читается только по кнопке проверки, в локальном сеансе при включённом экране; по удалённому рабочему столу она неизвестна, и это нормально (XIC-104). diff --git a/CLAUDE.md b/CLAUDE.md index 32f74ac..20b545b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -253,8 +253,9 @@ WMI-событий). `Program.cs`: single-instance mutex → DI-контейне сверка строго попарно по hardware-id — кроме прошивок `FWTP`/`FWSC`: их id в каталоге синтетические, строка «в каталоге есть прошивка X» даётся по факту пакета для модели, одна на модуль (XIC-102); модули ответа с чужой `product` отбрасываются ещё при разборе — для прошивок это единственная сверка (XIC-103); - текущая версия прошивки (XIC-104): тачпад — `TouchpadHaptics.ReadFirmwareVersion` (COL05, команда `0x10`), - сравнивается с хвостом каталожной; экран — `PanelFirmware` (ROM Ver TCON Himax через IGCL `ctlAUXAccess`, + текущая версия прошивки (XIC-104): тачпад — две прошивки, контроллер касаний BL7853 + `TouchpadHaptics.ReadControllerVersion` (COL04, единственный константный кадр OEM — больше в COL04 ничего не шлём) + и MCU силы `ReadFirmwareVersion` (COL05, команда `0x10`), сверка по обеим частям каталожной; экран — `PanelFirmware` (ROM Ver TCON Himax через IGCL `ctlAUXAccess`, записи в контроллер байт в байт как у OEM, только по кнопке; по RDP панели нет — «неизвестна»), только рядом, без вывода; оба чистые и под тестами; `OemDriverCheck` — WMI + сеть, только по diff --git a/docs/05-open-questions.md b/docs/05-open-questions.md index 93e6d85..acc3309 100644 --- a/docs/05-open-questions.md +++ b/docs/05-open-questions.md @@ -209,8 +209,9 @@ Report ID `0x0D`, 33 байта in/out) — канал Xiaomi PC Manager к пр команды, и все — глобальные настройки: версия прошивки `0x10`, сила вибрации `0x5D`, порог нажатия `0x5B` (второе значение прошивка пересчитывает сама), «сильное нажатие» `0x59`. Вибрация и порог сделаны (Настройки → Тачпад → «Тактильный отклик»). **Вывод этого вопроса не меняется:** -зонального параметра для кнопки нет и там. `COL04` (`0xFF00`, Report ID `0x0E`) — другой канал, -не трогаем. +зонального параметра для кнопки нет и там. `COL04` (`0xFF00`, Report ID `0x0E`) — канал самого +контроллера касаний BL7853 (`COL05` — MCU силы и вибрации за ним). В него шлём ровно один кадр OEM — +запрос версии прошивки (XIC-104, docs/16); больше ничего. **Импульс «вибрировать сейчас» нашёлся отдельно (XIC-73).** Он не в `SvrCModule.dll`, а в плагине краевых жестов `Plugins\PluginGesture.dll` (`XiaomiPcHost.exe`, отдельный архив в установщике): diff --git a/docs/16-oem-driver-check.md b/docs/16-oem-driver-check.md index ef28ad3..0c6bfe5 100644 --- a/docs/16-oem-driver-check.md +++ b/docs/16-oem-driver-check.md @@ -150,11 +150,14 @@ XMAPT4B0P0A0A → "0A0A" → 0x0A0A = 2570 → "1.0.0.2570" > есть пакет, по кнопке проверки, на пуле (`OemDriverCheck.RunAsync(currentFirmware)` → > `DriverMatch.Compare(..., firmware)`): > -> - **тачпад** — `TouchpadHaptics.ReadFirmwareVersion()`, команда `0x10` канала `COL05` -> (разбор — `TouchpadHapticsProtocol.FirmwareVersion`). Каталожная версия режется до последних -> четырёх чисел (`DriverMatch.TouchpadCatalogVersion`: `3.5.13.01.44.25.13.0` → `44.25.13.0`), и -> строка получает обычное «обновление / актуальна / новее каталога». На TM2424 живьём: установлена -> 44.25.11.0, в каталоге 44.25.13.0 → «обновление»; +> - **тачпад** — две прошивки, обе читаются: контроллер касаний BL7853 — +> `TouchpadHaptics.ReadControllerVersion()` через `COL04`, MCU силы и вибрации — +> `ReadFirmwareVersion()`, команда `0x10` канала `COL05` (разбор — `TouchpadHapticsProtocol`). +> Каталожная версия делится на части (`DriverMatch.TouchpadCatalogParts`: `3.5.13.01.44.25.13.0` → +> `3.5.13.1` + `44.25.13.0`), текущая собирается так же (`TouchpadCurrent`, «контроллер / сила»), и +> строка получает обычное «обновление / актуальна / новее каталога»: обновление — если старее хоть +> одна часть. Не ответил `COL04` — сравниваем одну силу, без силы — «версия неизвестна». На TM2424 +> живьём: установлено `3.5.9.1 / 44.25.11.0`, в каталоге `3.5.13.1 / 44.25.13.0` → «обновление»; > - **экран** — `PanelFirmware.ReadRomVersion()`, ROM Ver контроллера Himax через IGCL; чистая часть > (последовательность операций, раскладка `ctl_aux_access_args_t`, разбор ответа) — > `PanelFirmwareProtocol`, тесты сверяют записи с OEM байт в байт. Версия в формате каталога @@ -201,9 +204,21 @@ XMAPT4B0P0A0A → "0A0A" → 0x0A0A = 2570 → "1.0.0.2570" тактильного отклика XIC-77, без повышения прав): ответ `2C 19 0B 00` = **44.25.11.0**. Работает и по RDP — HID-устройство от сеанса не зависит. В каталоге `3.5.13.01.44.25.13.0` — **две** прошивки (см. «Что внутри пакета тачпада» ниже): `3.5.13.01` — контроллер касаний BL7853, `44.25.13.0` — -MCU силы и вибрации. Команда `0x10` отдаёт версию второго, поэтому сравниваем хвост: на TM2424 -стоит 44.25.11.0, старее каталожной. Версию BL7853 читать нечем. Служба Xiaomi FWTP не видит вовсе: -ищет версию в реестре и пишет «Failed to get current firmware version for FWTP». +MCU силы и вибрации. Команда `0x10` отдаёт версию второго: на TM2424 стоит 44.25.11.0, старее +каталожной. Служба Xiaomi FWTP не видит вовсе: ищет версию в реестре и пишет «Failed to get current +firmware version for FWTP». + +**Контроллер касаний BL7853 — через `COL04` (XIC-104).** Канал `0xFF00`, Report ID `0x0E`, 33 байта. +Первым запрос опубликовал gxcreator в [issue #99](https://github.com/Oksion/XiControl/issues/99); мы +сверили его с кодом `FirmwareTouchPadTool.exe` из пакета (запись кадра по смещению файла `0x1C7EA`): + +- запрос — константный кадр `0E 07 B4 00 00 01 00 04 B6` + нули до 33 байт, `HidD_SetOutputReport`; +- пауза **30 мс**, затем `HidD_GetInputReport` с `[0] = 0x0E`; +- годен ответ с `[1] = 0x06` и `[2] = 0xB6` (эхо команды, суммы в этом канале нет), версия — + байты `[7].[6].[4].[5]` десятичными. + +Это **единственный** кадр, который XiControl шлёт в `COL04`, и он не собирается, а взят как есть. +На TM2424: **3.5.9.1** при `3.5.13.01` в каталоге; у автора #99 после прошивки — 3.5.13.1 и 44.25.13.0. **Экран (`FWSC`) — читается, но нестабильно и не без записи.** Это прошивка **TCON-контроллера панели Himax HX8874**, а не «монитора». `FirmwareScreenTool.exe --version` (так его зовёт служба, @@ -233,7 +248,9 @@ MCU силы и вибрации. Команда `0x10` отдаёт верси в приложении, то только по кнопке проверки; - живьём за ноутбуком нашим кодом **ещё не проверено**: и ресерч XIC-103, и реализация XIC-104 шли по RDP. По RDP проверено только «панели нет → версия неизвестна, ни одной AUX-операции» (в журнале - `PanelFirmware: контроллер Himax не найден`). Проба — `panel-fw-version.ps1` в рабочем каталоге + `PanelFirmware: контроллер Himax не найден`). Сам алгоритм независимо подтвердил gxcreator в + [issue #100](https://github.com/Oksion/XiControl/issues/100): своим скриптом за ноутбуком получил + ROM Ver `0x570B06` и FW Ver `0x2701`, как в логе OEM-утилиты. Проба — `panel-fw-version.ps1` в рабочем каталоге проекта (вне git): без ключей только читает DPCD, с `-ReadVersion` повторяет последовательность OEM. **Ревизии в синтетических id (`REV_1236/1237`, `REV_1836/1837`) расшифровать нельзя.** Служба @@ -277,7 +294,8 @@ MCU силы и вибрации. Команда `0x10` отдаёт верси сама Windows по контактам, а краевые жесты и сильное нажатие Xiaomi — PC Manager (`PluginGesture.dll`) по тем же контактам и давлению. На «железе» остаётся только тактильная часть: порог клика `0x5B` и второй щелчок на сильном нажатии `0x59` прошивка отрабатывает сама, но событием наружу не сообщает. -Не проверено: шлёт ли прошивка что-то непрошенное в `COL04` — для этого нужен код BL7853. +Не проверено: шлёт ли прошивка что-то непрошенное в `COL04` (сам канал — команды контроллеру, им же +читается версия, см. выше) — для этого нужен код BL7853. ## 5. Где лежат данные diff --git a/src/Localization/lang/en.json b/src/Localization/lang/en.json index f1fb421..0b0e6c7 100644 --- a/src/Localization/lang/en.json +++ b/src/Localization/lang/en.json @@ -291,6 +291,7 @@ "settings.drivers.firmware.newer": "Installed {0}, catalog {1} — yours is newer · {2}", "settings.drivers.firmware.v.same": "up to date", "settings.drivers.firmware.v.newer": "newer than catalog", + "settings.drivers.firmware.tp.parts": "The touchpad has two firmwares: the first version is the touch controller, the second is force and haptics.", "settings.drivers.download": "Open link", "settings.drivers.download.confirm": "Open in browser?", "settings.drivers.nolink": "no link", diff --git a/src/Localization/lang/ru.json b/src/Localization/lang/ru.json index 0888f83..d56bab3 100644 --- a/src/Localization/lang/ru.json +++ b/src/Localization/lang/ru.json @@ -291,6 +291,7 @@ "settings.drivers.firmware.newer": "Установлена {0}, в каталоге {1} — у вас новее · {2}", "settings.drivers.firmware.v.same": "актуальна", "settings.drivers.firmware.v.newer": "новее каталога", + "settings.drivers.firmware.tp.parts": "В тачпаде две прошивки: первая версия — контроллер касаний, вторая — сила нажатия и вибрация.", "settings.drivers.download": "Открыть ссылку", "settings.drivers.download.confirm": "Открыть в браузере?", "settings.drivers.nolink": "нет ссылки", diff --git a/src/Localization/lang/zh.json b/src/Localization/lang/zh.json index ad45ba0..ff5eefc 100644 --- a/src/Localization/lang/zh.json +++ b/src/Localization/lang/zh.json @@ -291,6 +291,7 @@ "settings.drivers.firmware.newer": "已安装 {0},目录 {1}——你的版本更新 · {2}", "settings.drivers.firmware.v.same": "已是最新", "settings.drivers.firmware.v.newer": "比目录新", + "settings.drivers.firmware.tp.parts": "触控板有两套固件:第一个版本是触控控制器,第二个是压感与振动。", "settings.drivers.download": "打开链接", "settings.drivers.download.confirm": "在浏览器中打开?", "settings.drivers.nolink": "无链接", diff --git a/src/SystemIntegration/OemDrivers.cs b/src/SystemIntegration/OemDrivers.cs index 5bed2dc..b5953a6 100644 --- a/src/SystemIntegration/OemDrivers.cs +++ b/src/SystemIntegration/OemDrivers.cs @@ -195,21 +195,30 @@ internal static int CompareVersions(string a, string b) => ? va.CompareTo(vb) : string.Compare(a, b, StringComparison.OrdinalIgnoreCase); + /// Разделитель частей версии тачпада в строке: «контроллер / сила». + internal const string PartSeparator = " / "; + /// - /// Версия прошивки тачпада из каталожной: последние четыре числа - /// (3.5.13.01.44.25.13.0 → 44.25.13.0). В пакете две прошивки: 3.5.13.01 — - /// контроллер касаний Betterlife BL7853, 44.25.13.0 — отдельный MCU силы и вибрации (папка - /// Force архива). Командой 0x10 тачпад отдаёт версию второго — с ней и сравниваем; - /// версию BL7853 читать нечем. null — строка другого вида, сравнивать не с чем. + /// Каталожная версия тачпада по частям. В пакете две прошивки: 3.5.13.01.44.25.13.0 — + /// это контроллер касаний Betterlife BL7853 (3.5.13.01, он же 3.5.13.1) и отдельный + /// MCU силы и вибрации (44.25.13.0, папка Force архива). Сила — последние четыре + /// числа, контроллер — первые четыре, если они есть. null — строка другого вида. /// - internal static string? TouchpadCatalogVersion(string catalog) + internal static (string? Controller, string Force)? TouchpadCatalogParts(string catalog) { var parts = catalog.Split('.'); - if (parts.Length < 4) return null; - var tail = parts[^4..]; - return tail.All(p => p.Length > 0 && p.All(char.IsAsciiDigit)) ? string.Join('.', tail) : null; + if (parts.Length is not (4 or 8) || !parts.All(p => p.Length > 0 && p.All(char.IsAsciiDigit))) return null; + string force = string.Join('.', parts[^4..]); + // ведущие нули — как в каталоге (01), сам контроллер отдаёт числами (1) + string? controller = parts.Length == 8 ? Version.Parse(string.Join('.', parts[..4])).ToString() : null; + return (controller, force); } + /// Текущая версия тачпада одной строкой, в том же порядке, что каталог: «контроллер / сила» + /// или одна сила, если COL04 не ответил. Без версии силы — null: сравнивать не с чем. + internal static string? TouchpadCurrent(string? controller, string? force) => + force is null ? null : controller is null ? force : controller + PartSeparator + force; + /// /// Строки сверки. Версия — из записи ЭТОГО hardware-id, не «пакета»: один zip везёт драйверы /// разных вендоров (у аудио 10 разных версий на 125 id). Несколько id каталога могут указывать @@ -218,7 +227,8 @@ internal static int CompareVersions(string a, string b) => /// (USB\VID_6789&PID_1234&REV_1236 у экрана), такого устройства в системе нет. /// Строка — одна на модуль с пакетом; в Device — код модуля, имя ему даёт UI по локализации. /// Текущая версия прошивки — из (код модуля → прочитанное, XIC-104): - /// у тачпада она сравнивается с хвостом каталожной, у экрана только встаёт рядом — ROM Ver панели + /// у тачпада сравнивается по частям (контроллер и сила; «обновление», если старее хоть одна), + /// у экрана только встаёт рядом — ROM Ver панели /// отличается от каталожного старшим байтом, и «старее» это или другой вариант панели, по номеру /// не сказать (docs/16). /// @@ -251,13 +261,18 @@ private static DriverRow FirmwareRow(OemModule m, string? current) { string catalog = m.HardwareIds.Count > 0 ? m.HardwareIds[0].Version : ""; var state = DriverState.Firmware; - if (m.Name == OemProtocol.TouchpadFirmware && TouchpadCatalogVersion(catalog) is { } tail) + if (m.Name == OemProtocol.TouchpadFirmware && TouchpadCatalogParts(catalog) is { } cat) { - catalog = tail; - if (current is not null) + // часть, которую не прочитали, не сравниваем и не показываем — пары должны совпадать + string? ctl = null, force = current; + if (current?.Split(PartSeparator) is [var a, var b]) (ctl, force) = (a, b); + bool both = ctl is not null && cat.Controller is not null; + catalog = both ? cat.Controller + PartSeparator + cat.Force : cat.Force; + if (force is not null) { - int c = CompareVersions(tail, current); - state = c > 0 ? DriverState.Update : c < 0 ? DriverState.Newer : DriverState.Same; + int f = CompareVersions(cat.Force, force), c = both ? CompareVersions(cat.Controller!, ctl!) : 0; + state = f > 0 || c > 0 ? DriverState.Update : f < 0 || c < 0 ? DriverState.Newer : DriverState.Same; + current = both ? current : force; } } return new DriverRow(m.Name, m.Name, current, catalog, state, m.Package); diff --git a/src/SystemIntegration/TouchpadHaptics.cs b/src/SystemIntegration/TouchpadHaptics.cs index aacbdc5..1fdb913 100644 --- a/src/SystemIntegration/TouchpadHaptics.cs +++ b/src/SystemIntegration/TouchpadHaptics.cs @@ -23,9 +23,14 @@ public sealed class TouchpadHaptics private const int SettleMs = 120; private const int ReadAttempts = 3; + // COL05 — наш рабочий канал (MCU силы и вибрации); COL04 — канал контроллера касаний BL7853, + // в него шлём ровно один кадр OEM — запрос версии (XIC-104) + private const ushort ForcePage = 0xFF01, ControllerPage = 0xFF00; + private const int ControllerSettleMs = 30; // пауза OEM-прошивальщика перед ответом COL04 + private readonly object _lock = new(); - private string? _path; - private bool _searched; + private string? _path, _ctlPath; + private bool _searched, _ctlSearched; /// Прочитать обе настройки. null — тачпада нет или он не ответил. public TouchpadHapticsState? Read() @@ -42,8 +47,8 @@ public sealed class TouchpadHaptics } } - /// Версия прошивки тачпада (команда 0x10, только чтение — XIC-104). null — тачпада нет - /// или он не ответил. Зовётся по кнопке проверки драйверов, с пула. + /// Версия прошивки MCU силы и вибрации (команда 0x10 в COL05, только чтение — XIC-104). + /// null — тачпада нет или он не ответил. Зовётся по кнопке проверки драйверов, с пула. public string? ReadFirmwareVersion() { lock (_lock) @@ -53,6 +58,28 @@ public sealed class TouchpadHaptics } } + /// Версия прошивки контроллера касаний BL7853 через COL04 (XIC-104): кадр OEM как есть, + /// 30 мс, ответ. Пустой или чужой ответ — повтор того же кадра, как у чтения COL05. null — нет + /// канала или ответа. Только по кнопке проверки драйверов. + public string? ReadControllerVersion() + { + lock (_lock) + { + using var h = Open(controller: true); + if (h is null) return null; + for (int i = 0; i < ReadAttempts; i++) + { + if (!Send(h, ControllerVersionRequest())) return null; + Thread.Sleep(ControllerSettleMs); + var r = new byte[FrameLength]; + r[0] = ControllerReportId; + if (HidD_GetInputReport(h, r, FrameLength) && ParseControllerVersion(r) is { } v) return v; + } + Log.Write("TouchpadHaptics: COL04 не ответил на запрос версии"); + return null; + } + } + /// Записать силу вибрации; true — тачпад подтвердил значение чтением. public bool SetVibration(HapticsVibration level) => Write(CmdVibration, VibrationValues(level), v => MatchVibration(v[0], v[1]) == level); @@ -129,20 +156,22 @@ private static bool Send(SafeFileHandle h, byte[] frame) // Хэндл на операцию, как у PC Manager: держать открытым незачем, а после сна/переподключения // старый хэндл протухает молча - private SafeFileHandle? Open() + private SafeFileHandle? Open(bool controller = false) { - if (!_searched) { _path = FindCollection(); _searched = true; } - if (_path is null) return null; - var h = CreateFileW(_path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, + ref string? path = ref controller ? ref _ctlPath : ref _path; + ref bool searched = ref controller ? ref _ctlSearched : ref _searched; + if (!searched) { path = FindCollection(controller ? ControllerPage : ForcePage); searched = true; } + if (path is null) return null; + var h = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); if (!h.IsInvalid) return h; - Log.Write($"TouchpadHaptics: открыть коллекцию не удалось ({Marshal.GetLastWin32Error()})"); + Log.Write($"TouchpadHaptics: открыть коллекцию 0x{(controller ? ControllerPage : ForcePage):X4} не удалось ({Marshal.GetLastWin32Error()})"); h.Dispose(); - _searched = false; // устройство могло переподключиться под другим путём — поищем снова + searched = false; // устройство могло переподключиться под другим путём — поищем снова return null; } - private static string? FindCollection() + private static string? FindCollection(ushort usagePage) { HidD_GetHidGuid(out Guid guid); IntPtr set = SetupDiGetClassDevsW(ref guid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); @@ -154,7 +183,7 @@ private static bool Send(SafeFileHandle h, byte[] frame) { string? path = InterfacePath(set, ref di); if (path is null || !path.Contains("bltp7853", StringComparison.OrdinalIgnoreCase)) continue; - if (IsVendorChannel(path)) return path; + if (IsVendorChannel(path, usagePage)) return path; } return null; } @@ -175,16 +204,16 @@ private static bool Send(SafeFileHandle h, byte[] frame) finally { Marshal.FreeHGlobal(buf); } } - /// Вендорская коллекция с нашим протоколом: UsagePage 0xFF01, отчёты по 33 байта. - /// Открываем без прав доступа — только спросить дескриптор. - private static bool IsVendorChannel(string path) + /// Вендорская коллекция с нашим протоколом: нужная UsagePage (0xFF01 — COL05, 0xFF00 — + /// COL04), отчёты по 33 байта. Открываем без прав доступа — только спросить дескриптор. + private static bool IsVendorChannel(string path, ushort usagePage) { using var h = CreateFileW(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); if (h.IsInvalid || !HidD_GetPreparsedData(h, out IntPtr pp)) return false; try { return HidP_GetCaps(pp, out HIDP_CAPS c) == HIDP_STATUS_SUCCESS && - c.UsagePage == 0xFF01 && c.OutputReportByteLength == FrameLength && c.InputReportByteLength == FrameLength; + c.UsagePage == usagePage && c.OutputReportByteLength == FrameLength && c.InputReportByteLength == FrameLength; } finally { HidD_FreePreparsedData(pp); } } diff --git a/src/SystemIntegration/TouchpadHapticsProtocol.cs b/src/SystemIntegration/TouchpadHapticsProtocol.cs index e30f155..f4fac83 100644 --- a/src/SystemIntegration/TouchpadHapticsProtocol.cs +++ b/src/SystemIntegration/TouchpadHapticsProtocol.cs @@ -137,6 +137,35 @@ public static byte[] ReadRequest(byte cmd, int count) public static string FirmwareVersion(IReadOnlyList v) => $"{v[0] & 0xFF}.{v[0] >> 8}.{v[1] & 0xFF}.{v[1] >> 8}"; + // ---- COL04: канал контроллера касаний BL7853 (XIC-104) ---- + + /// Report ID коллекции COL04 (UsagePage 0xFF00) — канал самого контроллера касаний; + /// COL05 — канал MCU силы и вибрации за ним. + public const byte ControllerReportId = 0x0E; + private const byte CmdControllerVersion = 0xB6; + + /// + /// Запрос версии прошивки BL7853 — единственный кадр, который мы шлём в COL04, и он не + /// собирается, а взят как есть из FirmwareTouchPadTool.exe OEM (запись по смещению + /// 0x1C7EA: 0E 07 B4 00 00 01 00 04 B6, дальше нули). Разбор — docs/16; первым его + /// опубликовал gxcreator в issue #99. + /// + public static byte[] ControllerVersionRequest() + { + var f = new byte[FrameLength]; + byte[] oem = [0x0E, 0x07, 0xB4, 0x00, 0x00, 0x01, 0x00, 0x04, CmdControllerVersion]; + oem.CopyTo(f, 0); + return f; + } + + /// Ответ на : проверка как у OEM — ID 0x0E, + /// [1] = 6, [2] = эхо команды (суммы в этом канале нет); версия — байты + /// [7].[6].[4].[5] десятичными (3.5.13.1). null — ответ не тот. + public static string? ParseControllerVersion(byte[] r) => + r.Length >= 8 && r[0] == ControllerReportId && r[1] == 6 && r[2] == CmdControllerVersion + ? $"{r[7]}.{r[6]}.{r[4]}.{r[5]}" + : null; + /// XOR байтов [3..31] плюс 1 — одна формула для кадров и ответов. public static byte Checksum(byte[] f) { diff --git a/src/Ui/AppController.cs b/src/Ui/AppController.cs index 68e67e7..ffddbea 100644 --- a/src/Ui/AppController.cs +++ b/src/Ui/AppController.cs @@ -959,11 +959,11 @@ public async Task CheckDriversAsync() finally { Volatile.Write(ref _driverCheckRunning, 0); } } - // Текущие версии прошивок (XIC-104): тачпад — нашим каналом COL05 (в тестах _haptics нет), - // экран — ROM Ver контроллера панели через IGCL + // Текущие версии прошивок (XIC-104): тачпад — контроллер касаний через COL04 и MCU силы через + // COL05 (в тестах _haptics нет), экран — ROM Ver контроллера панели через IGCL private string? CurrentFirmware(string module) => module switch { - OemProtocol.TouchpadFirmware => _haptics?.ReadFirmwareVersion(), + OemProtocol.TouchpadFirmware when _haptics is { } h => DriverMatch.TouchpadCurrent(h.ReadControllerVersion(), h.ReadFirmwareVersion()), OemProtocol.PanelFirmware => PanelFirmware.ReadRomVersion(), _ => null, }; diff --git a/src/Ui/Settings/DriversTab.cs b/src/Ui/Settings/DriversTab.cs index bb5b63d..7fb44ce 100644 --- a/src/Ui/Settings/DriversTab.cs +++ b/src/Ui/Settings/DriversTab.cs @@ -103,7 +103,13 @@ private void AddResult(SettingsToolkit ui, DriverCheckResult r) if (drivers.Any(x => x.State == DriverState.Newer)) ui.AddNote(this, "settings.drivers.summary.note"); } - private static string FirmwareText(DriverRow f) + // у тачпада две прошивки в одной строке — объясняем, где какая + private static string FirmwareText(DriverRow f) => + f.Module == OemProtocol.TouchpadFirmware && f.Catalog.Contains(DriverMatch.PartSeparator, StringComparison.Ordinal) + ? FirmwareVerdict(f) + "\n" + Loc.T("settings.drivers.firmware.tp.parts") + : FirmwareVerdict(f); + + private static string FirmwareVerdict(DriverRow f) { string pkg = PackageText(f.Package); return f.State switch diff --git a/tests/XiControl.Tests/OemDriversTests.cs b/tests/XiControl.Tests/OemDriversTests.cs index 5f276e8..57398b4 100644 --- a/tests/XiControl.Tests/OemDriversTests.cs +++ b/tests/XiControl.Tests/OemDriversTests.cs @@ -202,7 +202,7 @@ public void Прошивка_без_совпавшего_устройства_д rows.Should().AllSatisfy(r => { r.State.Should().Be(DriverState.Firmware); - r.Installed.Should().BeNull("текущую версию прошивки читать нечем"); + r.Installed.Should().BeNull("текущие версии не передавали"); r.Device.Should().Be(r.Module, "имя устройства даёт UI по коду модуля, не PnP"); r.Package.Should().NotBeNull(); }); @@ -213,14 +213,43 @@ public void Прошивка_без_совпавшего_устройства_д // ---- текущие версии прошивок (XIC-104) ---- [Theory] - [InlineData("3.5.13.01.44.25.13.0", "44.25.13.0")] - [InlineData("44.25.13.0", "44.25.13.0")] - [InlineData("44.25.13", null)] - [InlineData("3.5.13.01.44.25.13.x", null)] - [InlineData("740B08", null)] - [InlineData("", null)] - public void Версия_тачпада_из_хвоста_каталожной(string catalog, string? expected) => - DriverMatch.TouchpadCatalogVersion(catalog).Should().Be(expected); + [InlineData("3.5.13.01.44.25.13.0", "3.5.13.1", "44.25.13.0")] // контроллер — без ведущего нуля, как отдаёт сам + [InlineData("44.25.13.0", null, "44.25.13.0")] + public void Каталожная_версия_тачпада_по_частям(string catalog, string? controller, string force) => + DriverMatch.TouchpadCatalogParts(catalog).Should().Be((controller, force)); + + [Theory] + [InlineData("44.25.13")] + [InlineData("3.5.13.01.44.25.13.x")] + [InlineData("1.3.5.13.01.44.25.13.0")] + [InlineData("740B08")] + [InlineData("")] + public void Чужая_каталожная_версия_тачпада_не_разбирается(string catalog) => + DriverMatch.TouchpadCatalogParts(catalog).Should().BeNull(); + + [Theory] + [InlineData("3.5.13.1", "44.25.11.0", "3.5.13.1 / 44.25.11.0")] + [InlineData(null, "44.25.11.0", "44.25.11.0")] + [InlineData("3.5.13.1", null, null)] + [InlineData(null, null, null)] + public void Текущая_версия_тачпада_одной_строкой(string? controller, string? force, string? expected) => + DriverMatch.TouchpadCurrent(controller, force).Should().Be(expected); + + [Theory] + [InlineData("3.5.13.1", "44.25.11.0", DriverState.Update)] // TM2424 на момент XIC-104: сила старее + [InlineData("3.5.12.1", "44.25.13.0", DriverState.Update)] // старее только контроллер + [InlineData("3.5.13.1", "44.25.13.0", DriverState.Same)] // машина автора issue #99 + [InlineData("3.5.14.1", "44.25.13.0", DriverState.Newer)] + [InlineData("3.5.14.1", "44.25.12.0", DriverState.Update)] // хоть одна старее — обновление + public void Тачпад_сверяется_по_обеим_прошивкам(string controller, string force, DriverState state) + { + var row = DriverMatch.Compare([Touchpad], [], + new Dictionary { ["FWTP"] = DriverMatch.TouchpadCurrent(controller, force)! }).Single(); + + row.State.Should().Be(state); + row.Installed.Should().Be($"{controller} / {force}"); + row.Catalog.Should().Be("3.5.13.1 / 44.25.13.0"); + } private static readonly OemModule Touchpad = Module("FWTP", (@"USB\VID_6989&PID_1234&REV_1836", "3.5.13.01.44.25.13.0")); private static readonly OemModule Panel = Module("FWSC", (@"USB\VID_6789&PID_1234&REV_1236", "740B08")); diff --git a/tests/XiControl.Tests/TouchpadHapticsProtocolTests.cs b/tests/XiControl.Tests/TouchpadHapticsProtocolTests.cs index 3223910..22c0d98 100644 --- a/tests/XiControl.Tests/TouchpadHapticsProtocolTests.cs +++ b/tests/XiControl.Tests/TouchpadHapticsProtocolTests.cs @@ -94,6 +94,19 @@ public void Firmware_version_from_live_response() FirmwareVersion([0x192C, 0x000D]).Should().Be("44.25.13.0"); // каталожная — для сверки формата } + // версия контроллера касаний через COL04 (XIC-104): кадр OEM как есть — из FirmwareTouchPadTool.exe + [Fact] + public void Controller_version_request_is_OEM_frame() => + ControllerVersionRequest().Should().Equal(Padded("0E 07 B4 00 00 01 00 04 B6")); + + [Theory] + [InlineData("0E 06 B6 00 0D 01 05 03", "3.5.13.1")] // [7].[6].[4].[5], как печатает OEM + [InlineData("0E 06 B5 00 0D 01 05 03", null)] // не эхо нашей команды + [InlineData("0E 05 B6 00 0D 01 05 03", null)] // не тот размер + [InlineData("0D 06 B6 00 0D 01 05 03", null)] // ответ COL05, не COL04 + public void Controller_version_parsed_as_OEM(string response, string? expected) => + ParseControllerVersion(Padded(response)).Should().Be(expected); + [Fact] public void Empty_response_means_not_ready() {