diff --git a/CHANGELOG.md b/CHANGELOG.md
index acba923..b6e7e9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,13 @@
ручной выбор в параметрах Windows не трогаем. По умолчанию выключено (XIC-96).
### Исправлено
+- **Прошивки экрана и тачпада снова видны на вкладке «Драйверы».** Xiaomi PC Manager предлагал
+ прошивку панели и тачпада, а XiControl молчал: строка «прошивка» появлялась только при совпадении
+ hardware-id каталога с устройством в системе, а у прошивок эти id условные — такого устройства нет
+ ни на одном ноутбуке. Теперь, если Xiaomi публикует пакет прошивки для модели, он показан в
+ отдельной группе «Прошивки» — версия из каталога и ссылка, под тем же предупреждением, что и BIOS.
+ Текущую версию прошивки прочитать по-прежнему нечем, поэтому «у вас старее» не говорим: возможно,
+ она уже стоит. Для обычных драйверов ничего не изменилось (XIC-102).
- **Вебхук «заряд дошёл до порога» больше не обрывает «В дорогу».** Во время разового заряда до
100 % событие уходило всё равно, едва заряд проходил обычный порог, — и розетка из автоматизации
Home Assistant выключалась на 80 %, так что «В дорогу» до конца не доходил. Теперь на время
diff --git a/CLAUDE.md b/CLAUDE.md
index c89b01f..a29d4ed 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -250,7 +250,9 @@ WMI-событий). `Program.cs`: single-instance mutex → DI-контейне
отсекает скользящий pre-release; не чаще раза в сутки, тумблер = выключатель трафика,
самообновления нет намеренно — запущенный exe не перезаписать, XIC-20),
`OemDrivers` (XIC-95: `OemProtocol` — тело/подпись/разбор ответа сервера Xiaomi, `DriverMatch` —
- сверка строго попарно по hardware-id, оба чистые и под тестами; `OemDriverCheck` — WMI + сеть, только по
+ сверка строго попарно по hardware-id — кроме прошивок `FWTP`/`FWSC`: их id в каталоге синтетические,
+ строка «в каталоге есть прошивка X» даётся по факту пакета для модели, одна на модуль (XIC-102); оба
+ чистые и под тестами; `OemDriverCheck` — WMI + сеть, только по
кнопке, без `sn`, ничего не скачивает и не ставит; протокол — `docs/16-oem-driver-check.md`),
`OemService` (XIC-87: службы Xiaomi — MiDeviceService на выходе из Eco откатывает яркость и сама
меняет режим питания Windows, MiService (аккаунт/телеметрия) не мешает, отключение по желанию; чтение состояния и отключение/включение через SCM напрямую, без sc.exe —
diff --git a/docs/16-oem-driver-check.md b/docs/16-oem-driver-check.md
index 544175a..96ddaca 100644
--- a/docs/16-oem-driver-check.md
+++ b/docs/16-oem-driver-check.md
@@ -128,8 +128,18 @@ XMAPT4B0P0A0A → "0A0A" → 0x0A0A = 2570 → "1.0.0.2570"
> захватом службы до символа), `DriverMatch` (сверка попарно по hardware-id), `OemDriverCheck`.
> Показываем сводку по модулям и подробно — только обновления, со ссылкой на пакет (второе нажатие
> открывает её в браузере). У BIOS — тоже ссылка, но под отдельным предупреждением «на свой страх
-> и риск» (решение владельца: иначе родная утилита оставалась бы нужна ради одного BIOS). Прошивки `FWTP`/`FWSC` с версией драйвера
-> не сравниваются: строка «в каталоге есть прошивка X». Ничего не скачиваем и не ставим.
+> и риск» (решение владельца: иначе родная утилита оставалась бы нужна ради одного BIOS). Ничего не
+> скачиваем и не ставим.
+>
+> **Прошивки `FWTP`/`FWSC` (XIC-102)** — отдельная группа «Прошивки», по строке на модуль, если сервер
+> вернул пакет: «Прошивка: Тачпад / Экран — в каталоге версия X», ссылка на пакет под предупреждением
+> как у BIOS. Сверки по hardware-id для них **нет**: их id в каталоге синтетические
+> (`USB\VID_6789&PID_1234&REV_1236/1237` у экрана, `USB\VID_6989&PID_1234&REV_1836/1837` у тачпада) и в
+> `Win32_PnPSignedDriver` не встречаются — до XIC-102 строка была недостижима, и XiControl молчал там,
+> где PC Manager предлагал прошивку. Версия — из `hardwareIds[0].version` (на TM2424: `740B08` у экрана,
+> `3.5.13.01.44.25.13.0` у тачпада), имя устройства — локализованная константа по коду модуля, не из PnP.
+> С версией драйвера не сравниваем и «у вас старее» не говорим — текущую версию прошивки читать нечем
+> (см. «Ограничения»). Для обычных драйверов правило прежнее: нет совпавшего устройства — нет строки.
Сверка «что установлено против того, что есть у OEM» делается без сети и без Xiaomi-софта:
@@ -159,9 +169,12 @@ XMAPT4B0P0A0A → "0A0A" → 0x0A0A = 2570 → "1.0.0.2570"
- метаданные местами мусорные (у чипсета `releaseTime` = `1968-07-18`) — даты показывать с оглядкой;
- **текущие версии прошивок тачпада (`FWTP`) и экрана (`FWSC`) читать пока нечем.** Служба берёт
их через `firmware::GetFirmwareVersionById` — реестр плюс `FirmwareScreenTool.exe`, то есть части
- MiService. Без неё скажем «есть пакет версии X», но не «у вас старее». Возможный путь для
- тачпада — наш канал `COL05` (в `SvrCModule.dll` есть `sc::TouchSettingManager::ReadFirmware`),
- не проверено.
+ MiService. Без неё говорим «в каталоге есть прошивка X» (так и сделано, XIC-102), но не «у вас
+ старее» — пакет может оказаться уже установленным, об этом предупреждаем прямо в UI. Возможный
+ путь для тачпада — наш канал `COL05` (в `SvrCModule.dll` есть `sc::TouchSettingManager::ReadFirmware`),
+ не проверено;
+- hardware-id прошивок в каталоге **синтетические** и ни с чем в системе не совпадают — сверять их
+ по PnP бессмысленно, строка даётся по факту пакета для модели.
## 5. Где лежат данные
diff --git a/src/Localization/lang/en.json b/src/Localization/lang/en.json
index 1e34b84..f628a33 100644
--- a/src/Localization/lang/en.json
+++ b/src/Localization/lang/en.json
@@ -281,6 +281,8 @@
"settings.drivers.updates.warn": "Packages come from Xiaomi's server: XiControl has not checked them and does not install them. The link opens in your browser. Create a restore point before installing.",
"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.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.download": "Open link",
@@ -309,6 +311,8 @@
"settings.drivers.mod.PMT": "Platform monitoring",
"settings.drivers.mod.HDR": "Display HDR",
"settings.drivers.mod.OTS": "IoT module",
+ "settings.drivers.mod.FWSC": "Display",
+ "settings.drivers.mod.FWTP": "Touchpad",
"settings.drivers.oem.group": "Xiaomi services",
"settings.drivers.oem.desc": "Preinstalled by Xiaomi and stays after PC Manager is uninstalled. When you leave Eco mode it restores the previous brightness, and on every mode change it switches the Windows power mode by itself. XiControl does not need it.",
"settings.drivers.oem.status": "Now: {0}, startup — {1}.",
diff --git a/src/Localization/lang/ru.json b/src/Localization/lang/ru.json
index 662b95b..8845a72 100644
--- a/src/Localization/lang/ru.json
+++ b/src/Localization/lang/ru.json
@@ -281,6 +281,8 @@
"settings.drivers.updates.warn": "Пакеты лежат на сервере Xiaomi: XiControl их не проверял и не устанавливает. Ссылка откроется в браузере. Перед установкой создайте точку восстановления.",
"settings.drivers.update.desc": "{0} → {1} · {2}",
"settings.drivers.package": "{0}, {1} МБ",
+ "settings.drivers.firmware.group": "Прошивки",
+ "settings.drivers.firmware.warn": "Прошивка экрана или тачпада — на ваш страх и риск. XiControl пакет не проверял и не устанавливает: ссылка откроется в браузере. Текущую версию прошивки прочитать нечем — возможно, у вас уже она. Ставьте только штатной утилитой из этого архива, на питании от сети и не прерывая процесс.",
"settings.drivers.firmware.title": "Прошивка: {0}",
"settings.drivers.firmware.desc": "В каталоге версия {0} · {1}. Текущую версию прошивки прочитать нечем — сравнить не можем.",
"settings.drivers.download": "Открыть ссылку",
@@ -309,6 +311,8 @@
"settings.drivers.mod.PMT": "Мониторинг платформы",
"settings.drivers.mod.HDR": "HDR экрана",
"settings.drivers.mod.OTS": "IoT-модуль",
+ "settings.drivers.mod.FWSC": "Экран",
+ "settings.drivers.mod.FWTP": "Тачпад",
"settings.drivers.oem.group": "Службы Xiaomi",
"settings.drivers.oem.desc": "Предустановлена Xiaomi и остаётся после удаления PC Manager. При выходе из режима Eco возвращает прежнюю яркость, а при смене режима сама переключает режим питания Windows. XiControl она не нужна.",
"settings.drivers.oem.status": "Сейчас: {0}, запуск — {1}.",
diff --git a/src/Localization/lang/zh.json b/src/Localization/lang/zh.json
index a41028d..32cf0f6 100644
--- a/src/Localization/lang/zh.json
+++ b/src/Localization/lang/zh.json
@@ -281,6 +281,8 @@
"settings.drivers.updates.warn": "驱动包来自小米服务器:XiControl 未对其进行检查,也不会安装。链接将在浏览器中打开。安装前请创建还原点。",
"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.title": "固件:{0}",
"settings.drivers.firmware.desc": "目录版本 {0} · {1}。无法读取当前固件版本,因此无法比较。",
"settings.drivers.download": "打开链接",
@@ -309,6 +311,8 @@
"settings.drivers.mod.PMT": "平台监控",
"settings.drivers.mod.HDR": "屏幕 HDR",
"settings.drivers.mod.OTS": "IoT 模块",
+ "settings.drivers.mod.FWSC": "屏幕",
+ "settings.drivers.mod.FWTP": "触控板",
"settings.drivers.oem.group": "小米服务",
"settings.drivers.oem.desc": "由小米预装,卸载 PC Manager 后仍会保留。退出 Eco 模式时它会恢复之前的亮度,每次切换模式时还会自行切换 Windows 电源模式。XiControl 不需要它。",
"settings.drivers.oem.status": "当前:{0},启动类型:{1}。",
diff --git a/src/SystemIntegration/OemDrivers.cs b/src/SystemIntegration/OemDrivers.cs
index bfb7f44..9ecd6a8 100644
--- a/src/SystemIntegration/OemDrivers.cs
+++ b/src/SystemIntegration/OemDrivers.cs
@@ -20,7 +20,7 @@ public sealed record OemHardwareId(string Id, string Version);
public sealed record InstalledDriver(string HardwareId, string Version, string Device);
/// Сверка одного устройства с каталогом. — прошивка: её текущую
-/// версию читать нечем, поэтому только «в каталоге есть такая».
+/// версию читать нечем и устройства под её id в системе нет, поэтому только «в каталоге есть такая».
public enum DriverState { Same, Update, Newer, Firmware }
public sealed record DriverRow(string Module, string Device, string? Installed, string Catalog, DriverState State, OemPackage? Package);
@@ -156,7 +156,8 @@ private static OemPackage ParsePackage(JsonElement p)
private static string Str(string s) => JsonSerializer.Serialize(s);
}
-/// Сверка каталога с установленным — строго попарно по hardware-id (чистая логика).
+/// Сверка каталога с установленным — строго попарно по hardware-id (чистая логика);
+/// прошивки — исключение, см. .
public static class DriverMatch
{
///
@@ -187,6 +188,10 @@ internal static int CompareVersions(string a, string b) =>
/// Строки сверки. Версия — из записи ЭТОГО hardware-id, не «пакета»: один zip везёт драйверы
/// разных вендоров (у аудио 10 разных версий на 125 id). Несколько id каталога могут указывать
/// на одно устройство — строку даём одну.
+ /// Прошивки (XIC-102) — без сверки по hardware-id: их id в каталоге синтетические
+ /// (USB\VID_6789&PID_1234&REV_1236 у экрана), такого устройства в системе нет,
+ /// а текущую версию прошивки читать нечем. Поэтому строка — одна на модуль с пакетом,
+ /// «в каталоге есть прошивка X»; в Device — код модуля, имя ему даёт UI по локализации.
///
internal static List Compare(IEnumerable modules, IReadOnlyList installed)
{
@@ -194,16 +199,16 @@ internal static List Compare(IEnumerable modules, IReadOnl
var seen = new HashSet(StringComparer.OrdinalIgnoreCase);
foreach (var m in modules)
{
- bool firmware = OemProtocol.IsFirmware(m.Name);
+ 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));
+ continue;
+ }
foreach (var h in m.HardwareIds)
{
if (Find(installed, h.Id) is not { } inst) continue;
if (!seen.Add($"{m.Name}|{inst.Device}|{h.Version}")) continue;
- if (firmware)
- {
- rows.Add(new DriverRow(m.Name, inst.Device, null, h.Version, DriverState.Firmware, m.Package));
- continue;
- }
int c = CompareVersions(h.Version, inst.Version);
var state = c > 0 ? DriverState.Update : c < 0 ? DriverState.Newer : DriverState.Same;
rows.Add(new DriverRow(m.Name, inst.Device, inst.Version, h.Version, state, m.Package));
@@ -274,7 +279,7 @@ public static async Task RunAsync(CancellationToken ct = defa
: rows.Count == 0 ? DriverCheckStatus.NoMatch
: DriverCheckStatus.Ok;
Log.Write($"OemDriverCheck: {product}, модулей с пакетом {modules.Count}, устройств сверено {rows.Count}, " +
- $"обновлений {rows.Count(r => r.State == DriverState.Update)}, без ответа {failed}");
+ $"обновлений {rows.Count(r => r.State == DriverState.Update)}, прошивок {rows.Count(r => r.State == DriverState.Firmware)}, без ответа {failed}");
return new DriverCheckResult(status, product, rows, sys.Bios, bios?.FirstOrDefault()?.Package,
bios is not null, failed, DateTime.Now);
}
diff --git a/src/Ui/Settings/DriversTab.cs b/src/Ui/Settings/DriversTab.cs
index 0a70ebc..c2d00c9 100644
--- a/src/Ui/Settings/DriversTab.cs
+++ b/src/Ui/Settings/DriversTab.cs
@@ -7,8 +7,8 @@ namespace XiControl.Ui.Settings;
///
/// Вкладка «Драйверы» (XIC-95): что Xiaomi предлагает для этой модели и как это соотносится с
/// установленным. В сеть — только по кнопке; ничего не скачиваем и не ставим. Обновления — со
-/// ссылкой на пакет (открывается в браузере после второго нажатия); у BIOS — тоже, но под жёстким
-/// предупреждением «на свой страх и риск».
+/// ссылкой на пакет (открывается в браузере после второго нажатия); у BIOS и прошивок экрана/тачпада
+/// (XIC-102) — тоже, но под жёстким предупреждением «на свой страх и риск».
/// Результат живёт в контроллере: окно пересобирается на каждый показ, запроса отсюда нет.
/// Здесь же — служба Xiaomi MiDeviceService с кнопкой «Отключить / Включить» (XIC-87).
///
@@ -57,22 +57,31 @@ private void AddResult(SettingsToolkit ui, DriverCheckResult r)
// ---- Обновления: устройство, было → стало, пакет; один пакет — одна кнопка ----
ui.AddGroup(this, "settings.drivers.updates.group");
var updates = r.Rows.Where(x => x.State == DriverState.Update).ToList();
- var firmware = r.Rows.Where(x => x.State == DriverState.Firmware).ToList();
- if (updates.Count == 0 && firmware.Count == 0)
+ var offered = new HashSet(StringComparer.OrdinalIgnoreCase);
+ if (updates.Count == 0)
ui.AddNote(this, "settings.drivers.updates.none");
else
{
ui.AddNote(this, "settings.drivers.updates.warn");
- var offered = new HashSet(StringComparer.OrdinalIgnoreCase);
foreach (var u in updates)
{
string desc = Loc.T("settings.drivers.update.desc", u.Installed ?? "—", u.Catalog, PackageText(u.Package));
Controls.Add(ui.Row(u.Device, desc, DownloadButton(ui, u.Package, offered)));
}
+ }
+
+ // ---- Прошивки экрана и тачпада (XIC-102): только «в каталоге есть», без сравнения — текущую
+ // версию читать нечем, а устройства под их hardware-id в системе нет. Имя — по коду модуля, не
+ // из PnP. Предупреждение жёсткое, как у BIOS: сбой прошивки панели или тачпада — тоже ремонт ----
+ var firmware = r.Rows.Where(x => x.State == DriverState.Firmware).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", f.Device), desc, DownloadButton(ui, f.Package, offered)));
+ Controls.Add(ui.Row(Loc.T("settings.drivers.firmware.title", ModuleName(f.Module)), desc, DownloadButton(ui, f.Package, offered)));
}
}
diff --git a/tests/XiControl.Tests/OemDriversTests.cs b/tests/XiControl.Tests/OemDriversTests.cs
index 0fb4989..6ee2d1d 100644
--- a/tests/XiControl.Tests/OemDriversTests.cs
+++ b/tests/XiControl.Tests/OemDriversTests.cs
@@ -160,4 +160,45 @@ private static OemModule Module(string name, params (string Id, string Version)[
rows.Should().ContainSingle().Which.Should().Match(r => r.State == DriverState.Firmware && r.Installed == null);
}
+
+ // ---- прошивки без устройства (XIC-102) ----
+
+ [Fact]
+ public void Прошивка_без_совпавшего_устройства_даёт_одну_строку_на_модуль()
+ {
+ // id прошивок в каталоге синтетические — в системе их нет; устройства подставлены настоящие с TM2424
+ var rows = DriverMatch.Compare(
+ [
+ Module("FWSC", (@"USB\VID_6789&PID_1234&REV_1236", "740B08"), (@"USB\VID_6789&PID_1234&REV_1237", "740B08")),
+ Module("FWTP", (@"USB\VID_6989&PID_1234&REV_1836", "3.5.13.01.44.25.13.0"), (@"USB\VID_6989&PID_1234&REV_1837", "3.5.13.01.44.25.13.0")),
+ ],
+ [Npu, Wifi, Audio]);
+
+ rows.Should().HaveCount(2, "по строке на модуль, а не на каждый hardware-id");
+ rows.Should().AllSatisfy(r =>
+ {
+ r.State.Should().Be(DriverState.Firmware);
+ r.Installed.Should().BeNull("текущую версию прошивки читать нечем");
+ r.Device.Should().Be(r.Module, "имя устройства даёт UI по коду модуля, не PnP");
+ 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");
+ }
+
+ [Fact]
+ public void Прошивка_без_пакета_строки_не_даёт()
+ {
+ var noPackage = new OemModule("FWSC", null, [new OemHardwareId(@"USB\VID_6789&PID_1234&REV_1236", "740B08")]);
+
+ DriverMatch.Compare([noPackage], []).Should().BeEmpty("нечего предлагать — ни ссылки, ни пакета");
+ }
+
+ [Fact]
+ public void Драйвер_без_совпавшего_устройства_строки_не_даёт()
+ {
+ var rows = DriverMatch.Compare([Module("NFC", (@"PCI\VEN_1FC9&DEV_0001", "1.0.0.1"))], [Npu, Wifi, Audio]);
+
+ rows.Should().BeEmpty("для обычных драйверов правило прежнее: нет устройства — нет строки");
+ }
}