Skip to content
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@
усыпляет; медиаплееры запрашивают удержание экрана сами, так что фильмы не прерываются.
По умолчанию `false` — поведение существующих установок не меняется, пока поле не добавят руками.

### Исправлено
- **Порог заряда в панели и меню больше не расходится с железом.** Если порог менял кто-то снаружи
(Xiaomi PC Manager, чужая утилита), быстрая панель продолжала показывать старое значение — она
рисовала из конфига и прошивку не перечитывала вовсе. Теперь порог подтягивается из прошивки в момент
показа панели и пересборки меню трея (`AppController.SyncCareFromFirmware()`): фонового опроса не
добавилось, читаем только когда UI открывают. Принимается только валидный порог < 100 %: показание
«100» неотличимо от транзиента после сна/смены питания (EC теряет лимит до ре-арма `ChargeGuard`) и
не принимается — иначе панель, открытая сразу после resume, молча выключала бы защиту. Прошивка не
ответила → остаётся значение конфига; при активном «В дорогу» примирение не выполняется. Заодно
исправлена та же ошибка в меню трея: галочка считалась от живого значения, а процент в подписи брался
из конфига, из-за чего меню могло показать «беречь 60 %» при 50 % в железе.
Разбор — [docs/12-charge-levels.md](docs/12-charge-levels.md) (XIC-17).

## [0.8.0] — 2026-07-29

### Добавлено
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ double-click; current direction is shown by color (charging green / discharging
- 🔌 **Charger wattage** — when the charger is plugged in, show the connected PD adapter's
wattage (watts) in the OSD and in the Monitor. Over the charge icon — a **PSU quality badge**:
🔴 "!" if the adapter is weaker than the configured threshold (slow charging), ⚪ "?" if the
PSU is non-PD (e.g. plain 5 V — wattage can't be negotiated). The icon still shows the 80/100
limit. The threshold is configurable (Settings → Battery). Driver-free (read-only).
PSU is non-PD (e.g. plain 5 V — wattage can't be negotiated). The icon still shows the current
charge limit. The threshold is configurable (Settings → Battery). Driver-free (read-only).
- 🩺 **Battery health** — Settings → Battery: actual wear (current vs. design capacity),
charge cycle count, capacity in Wh. Stock ACPI/Windows data, read-only.
- ⚡ **Performance modes**: Eco (hidden firmware mode) / Quiet / Auto /
Expand All @@ -70,8 +70,8 @@ double-click; current direction is shown by color (charging green / discharging
- microphone on/off, keyboard backlight (off / 50% / 100% / auto).
- 🅼 **Mi button**:
- short press — cycle through modes with an OSD (configurable);
- double click — charge 80/100 (configurable);
- hold — quick settings panel (modes + charge 80/100, closes on Esc/X/click-outside).
- double click — toggle the charge limit (configurable);
- hold — quick settings panel (modes + charge limit, closes on Esc/X/click-outside).
- ⌨️ **Reviving "dead" keys** with remapping: the Mi clicks and the "settings" / AI /
"projection" keys can be bound to any function — from cycling modes to launching your
own program (see "Key remapping"); the microphone key mutes the system mic, the backlight
Expand Down Expand Up @@ -222,7 +222,7 @@ menu item — the utility lifts the limit once and tops up to 100%.
- Manually picking the threshold/100 pill also cancels the mode. With a permanent "100%" the button is
inactive (nothing to top up).

The 80/100 pills show the **base** setting — "travel" is a temporary override on top of it
The "threshold/100" pills show the **base** setting — "travel" is a temporary override on top of it
(driver-free, the same charge WMI channel). In `config.json`: `"TravelMode"`, `"TravelSound"`.

A custom ready sound — the "Custom sound file" field in the same settings (or `config.json`;
Expand Down Expand Up @@ -333,7 +333,7 @@ When editing `AcRefreshRate`/`BatteryRefreshRate` directly in the config, restar

Each key gets its own action: **Settings → Keys**. The slots are the single and double Mi-button
click, the "Settings" (gear), AI and "Projection" keys. Any slot can be bound to: cycle modes,
charge 80/100, quick panel, owl mode, Monitor, "travel", touchpad and touchscreen on/off, the
charge limit on/off, quick panel, owl mode, Monitor, "travel", touchpad and touchscreen on/off, the
system "Projection (Win+P)" / "Windows Settings" / "Copilot (Win+C)", launching your own program,
or "Nothing".

Expand Down
10 changes: 5 additions & 5 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ RAM, **температура** «горячей точки» (горячая з
- 🔌 **Мощность зарядника** — при подключении зарядки показываем мощность подключённого
PD-адаптера (ватты) в OSD и в «Мониторе». Поверх иконки заряда — **бейдж качества блока**:
🔴 «!» если адаптер слабее заданного порога (медленный заряд), ⚪ «?» если блок не-PD (например
обычный 5 В — мощность не согласуется). При этом иконка по-прежнему показывает лимит 80/100.
обычный 5 В — мощность не согласуется). При этом иконка по-прежнему показывает текущий лимит заряда.
Порог настраивается (Настройки → Батарея). Driver-free (только чтение).
- 🩺 **Здоровье батареи** — Настройки → Батарея: реальный износ (текущая ёмкость к проектной),
число циклов заряда, ёмкость в Вт·ч. Штатные данные ACPI/Windows, только чтение.
Expand All @@ -71,8 +71,8 @@ RAM, **температура** «горячей точки» (горячая з
- микрофон вкл/выкл, подсветка клавиатуры (выкл / 50% / 100% / авто).
- 🅼 **Mi-кнопка**:
- короткое нажатие — циклическое переключение режимов с OSD (настраивается);
- двойной клик — заряд 80/100 (настраивается);
- удержание — панель быстрых настроек (режимы + заряд 80/100, закрытие по Esc/X/клику вне).
- двойной клик — переключение лимита заряда (настраивается);
- удержание — панель быстрых настроек (режимы + лимит заряда, закрытие по Esc/X/клику вне).
- ⌨️ **Оживление «мёртвых» клавиш** с переназначением: на клики Mi и клавиши
«настройки» / AI / «проекция» вешается любая функция — от цикла режимов до запуска
своей программы (см. «Переназначение клавиш»); клавиша микрофона мьютит системный
Expand Down Expand Up @@ -225,7 +225,7 @@ dotnet publish src/XiControl.csproj -c Release -r win-x64 --self-contained -p:Pu
- Ручной выбор пилюли «порог/100» тоже отменяет режим. При постоянном «100%» кнопка неактивна
(дозаряжать некуда).

Пилюли 80/100 показывают **базовую** настройку — «В дорогу» это временный оверрайд поверх неё
Пилюли «порог/100» показывают **базовую** настройку — «В дорогу» это временный оверрайд поверх неё
(driver-free, тот же WMI-канал заряда). В `config.json`: `"TravelMode"`, `"TravelSound"`.

Свой звук готовности — поле «Свой звуковой файл» там же в настройках (или `config.json`;
Expand Down Expand Up @@ -335,7 +335,7 @@ dotnet publish src/XiControl.csproj -c Release -r win-x64 --self-contained -p:Pu

Каждой клавише — своё действие: **Настройки → Клавиши**. Слоты — одиночный и двойной
клик Mi-кнопки, клавиши «Настройки» (шестерёнка), AI и «Проекция». На любой слот можно
навесить: цикл режимов, заряд 80/100, быструю панель, режим совы, «Монитор»,
навесить: цикл режимов, лимит заряда вкл/выкл, быструю панель, режим совы, «Монитор»,
«В дорогу», тачпад и сенсорный экран вкл/выкл, системные «Проекция (Win+P)» /
«Параметры Windows» / «Copilot (Win+C)», запуск своей программы или «Ничего».

Expand Down
31 changes: 31 additions & 0 deletions docs/12-charge-levels.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,37 @@ OEM (`Xiaomi PC Manager`) даёт селектор лимита заряда (4
«В дорогу»; иначе просто запоминает. `ChargeGuard` армит **выбранный** порог.
- UI: панель/меню/OSD показывают реальный %, пикер — Настройки → Батарея.

## Примирение с внешней сменой (XIC-17)

Канала уведомления в MIFS нет, поэтому «на лету» две программы не синхронизируются (см. выше). Но
подхватить чужое изменение **в момент показа UI** можно — и нужно, иначе внешняя смена до нас не
доходит вовсе. `AppController.SyncCareFromFirmware()` читает `GET 0x10/02` и приводит конфиг к
прошивке; зовётся из `QuickPanelForm.RefreshState()` (до `Show()`, рядом с чтением режима и тачпада) и
из `TrayMenuBuilder` при пересборке меню. Фонового опроса не появилось — только когда UI открывают.

Тонкости, без которых это ломается:

- **Прошивка молчит** (`null`) → конфиг не трогаем: лучше показать своё значение, чем затереть нулём.
- **Принимаем только валидный порог < 100** (`ChargeCodeForPercent != null`): другой уровень EC сам
не породит — это всегда чей-то осознанный SET, его принимать безопасно.
- **«100 %» НЕ принимаем — это защита от гонки с ChargeGuard.** EC читается как 100 и когда защиту
выключили снаружи, и — транзиентно — после сна/смены питания: лимит на этих событиях теряется, а
гард переармирует его с дебаунсом 1.5 с. Отличить эти случаи нельзя. Приняв 100, мы сбросили бы
`ChargeCare` в конфиге — и гард, который читает желаемый порог из конфига, разоружился бы навсегда:
панель, открытая сразу после resume или выдёргивания зарядника, молча убивала бы защиту. Внешнее
«выключить» гард всё равно перебивает на следующем событии питания (документированное поведение),
поэтому UI показывает намерение пользователя, а не мгновенное состояние EC.
- **При активном «В дорогу» примирение не выполняется**: там прошивка намеренно держит 100 % при
`ChargeCare = true`, и примирение сбросило бы флаг, сломав режим.
- **`CareChanged` не дёргаем.** Это не действие пользователя, а подхват чужого; при скрытой панели
`TrayApp` показывает на это событие OSD — всплывашка вылетала бы на каждое открытие панели.
- Конфиг пишем только при фактическом отличии, факт принятия — в лог.

Заодно исправлена та же ошибка в меню трея: галочка считалась от живого значения, а процент в подписи
брался из конфига, поэтому меню могло показать «беречь 60 %» с галочкой при 50 % в железе. Теперь и
панель, и меню рисуют из конфига — он уже приведён к прошивке. Тултип трея сознательно оставлен на
конфиге: он живёт на 30-секундном опросе, и правдивость потребовала бы фонового WMI-вызова.

**Деградация на моделях без granular** сделана рантаймом, а не гейтом по SKU (в духе «набор функций
определяем в рантайме»): прошивка сама валидирует уровень, отказ (`OUT[1] != 0x80`) → `SetChargeLimit`
возвращает `false` → выбор откатывается, пользователь получает честный error-OSD, а 80/100 (legacy-коды
Expand Down
4 changes: 2 additions & 2 deletions src/Localization/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"settings.key.proj.desc": "The display-switch key.",
"settings.key.command": "Program / file / URL",
"settings.act.modes": "Cycle modes",
"settings.act.charge": "Charge 80 / 100 %",
"settings.act.charge": "Charge limit on/off",
"settings.act.panel": "Quick panel",
"settings.act.owl": "Owl mode",
"settings.act.monitor": "Monitor",
Expand Down Expand Up @@ -143,7 +143,7 @@
"settings.api.cmd.mode": "Performance modes",
"settings.api.cmd.mode.desc": "POST /mode — switch the mode.",
"settings.api.cmd.care": "Battery protection",
"settings.api.cmd.care.desc": "POST /care — toggle “protect ~80%”.",
"settings.api.cmd.care.desc": "POST /care — toggle the charge limit.",
"settings.api.cmd.travel": "Travel mode",
"settings.api.cmd.travel.desc": "POST /travel — one-off charge to 100%.",
"settings.api.cmd.owl": "Owl mode",
Expand Down
6 changes: 3 additions & 3 deletions src/Localization/lang/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,14 @@
"settings.key.mi.double.desc": "«Ничего» — жест отключён, одиночный клик срабатывает мгновенно.",
"settings.keys.other": "Прочие клавиши",
"settings.key.settings": "Клавиша «Настройки»",
"settings.key.settings.desc": "Клавиша с шестерёнкой. При открытой панели — всегда заряд 80/100.",
"settings.key.settings.desc": "Клавиша с шестерёнкой. При открытой панели — всегда переключает лимит заряда.",
"settings.key.ai": "AI-клавиша",
"settings.key.ai.desc": "Клавиша нейропомощника.",
"settings.key.proj": "Клавиша «Проекция»",
"settings.key.proj.desc": "Клавиша переключения экранов.",
"settings.key.command": "Программа / файл / URL",
"settings.act.modes": "Цикл режимов",
"settings.act.charge": "Заряд 80 / 100 %",
"settings.act.charge": "Лимит заряда вкл/выкл",
"settings.act.panel": "Быстрая панель",
"settings.act.owl": "Режим совы",
"settings.act.monitor": "Монитор",
Expand Down Expand Up @@ -143,7 +143,7 @@
"settings.api.cmd.mode": "Режимы производительности",
"settings.api.cmd.mode.desc": "POST /mode — переключение режима.",
"settings.api.cmd.care": "Защита заряда",
"settings.api.cmd.care.desc": "POST /care — «беречь ~80%» вкл/выкл.",
"settings.api.cmd.care.desc": "POST /care — лимит заряда вкл/выкл.",
"settings.api.cmd.travel": "Режим «В дорогу»",
"settings.api.cmd.travel.desc": "POST /travel — разовый заряд до 100%.",
"settings.api.cmd.owl": "Режим совы",
Expand Down
4 changes: 2 additions & 2 deletions src/Localization/lang/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"settings.key.proj.desc": "屏幕切换键。",
"settings.key.command": "程序 / 文件 / URL",
"settings.act.modes": "循环性能模式",
"settings.act.charge": "充电 80 / 100 %",
"settings.act.charge": "充电限制开关",
"settings.act.panel": "快速面板",
"settings.act.owl": "猫头鹰模式",
"settings.act.monitor": "监视器",
Expand Down Expand Up @@ -143,7 +143,7 @@
"settings.api.cmd.mode": "性能模式",
"settings.api.cmd.mode.desc": "POST /mode — 切换性能模式。",
"settings.api.cmd.care": "电池保护",
"settings.api.cmd.care.desc": "POST /care — 开关“保持约 80%”。",
"settings.api.cmd.care.desc": "POST /care — 开关充电限制。",
"settings.api.cmd.travel": "出行模式",
"settings.api.cmd.travel.desc": "POST /travel — 一次性充电至 100%。",
"settings.api.cmd.owl": "猫头鹰模式",
Expand Down
Loading
Loading