diff --git a/CHANGELOG.md b/CHANGELOG.md index b0c9f99..c82f1d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,11 @@ ничего не открывает (XIC-106). ### Исправлено +- **Частота экрана: в списке есть 165 Гц и всё, что умеет панель.** Выбор частоты «от сети» и «от + батареи» во вкладке «Экран» строился из зашитого набора 144/120/90/60/48: на Redmi Book Pro 2025 + (TM2409) с панелью 165 Гц максимум упирался в 144, а на других моделях список предлагал частоты, + которых у панели нет. Теперь в списке ровно частоты встроенной панели. Значение из конфига, которого + у панели нет, остаётся в списке. Режимы панели пишутся в журнал при старте (XIC-107). - **Проверка драйверов отбрасывает пакеты чужой модели.** Сервер Xiaomi называет в ответе модель, для которой пакет. Если она не совпала с моделью ноутбука, пакет не показывается. Для прошивок экрана и тачпада это единственная проверка, что пакет вообще про ваш ноутбук (XIC-103). diff --git a/CLAUDE.md b/CLAUDE.md index 090dabf..20a5011 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -200,6 +200,8 @@ WMI-событий). `Program.cs`: single-instance mutex (вторая копи питания И перед уходом в сон/shutdown — EC теряет его на переходах), `RefreshRateGuard`/ `RefreshRate` (авто-герцовка, чистый `ChangeDisplaySettingsEx` по ВСТРОЕННОЙ панели — она ищется через CCD `QueryDisplayConfig`, а не `null`, иначе правился бы основной экран (XIC-21); + зашитых наборов частот нет нигде: и цикл, и комбо «от сети / от батареи» берут режимы панели + (`Choices`; пресеты — только если панель режимов не отдала, XIC-107), режимы пишутся в журнал на старте; с `HoldRefreshRate` гард слушает ещё и смену режима экрана — «удерживать частоту» после чужих изменений, только событие, без опроса), `PowerProfileGuard` (режим по питанию + независимое запоминание яркости; единственный diff --git a/README.md b/README.md index 3157e6a..7711287 100644 --- a/README.md +++ b/README.md @@ -549,6 +549,11 @@ rate is set with `ChangeDisplaySettingsEx` — no driver needed): - **The nearest supported rate** at the current resolution is taken: you asked for 120, the panel can only do 90/60 → it picks 90 (ties go to the higher). So entering "144" on a 60 Hz matrix is safe — it just stays 60. A value ≤ 0 in config is ignored. +- **The AC / battery lists show the panel's own rates** — 165 on a 165 Hz panel, only 120 and 60 on + a 60/120 one. A value from config that the panel lacks stays in the list rather than being swapped + silently. If the panel reports no modes (remote desktop, panel inactive), a fixed preset list is + shown. The panel's modes are written to `log.txt` at startup — handy when reporting a model we + don't have. - **Triggers** at app startup, on AC↔battery changes and on wake from sleep (the latter two are debounced ~1.5 s, events arrive in bursts), and immediately when you enable the option. - If the desired rate **is already set — the screen doesn't blink** (no redundant call is made). diff --git a/README.ru.md b/README.ru.md index cda5d53..e97771f 100644 --- a/README.ru.md +++ b/README.ru.md @@ -549,6 +549,11 @@ dotnet publish src/XiControl.csproj -c Release -r win-x64 --self-contained -p:Pu - **Берётся ближайшая поддерживаемая частота** при текущем разрешении: попросили 120, а панель умеет только 90/60 → выберет 90 (при равном расстоянии — большую). Поэтому вписать «144» на 60-герцовой матрице безопасно — просто останется 60. Значение ≤ 0 в конфиге игнорируется. +- **Списки «от сети» / «от батареи» показывают частоты самой панели** — 165 на 165-герцовой, только + 120 и 60 на панели 60/120. Значение из конфига, которого у панели нет, остаётся в списке, а не + подменяется молча. Если панель режимов не отдала (удалённый рабочий стол, панель неактивна), + показывается фиксированный набор пресетов. Режимы панели пишутся в `log.txt` при старте — + пригодится, если сообщаете о модели, которой у нас нет. - **Срабатывает** на старте приложения, при смене питания сеть↔батарея и при выходе из сна (эти два — через дебаунс ~1.5 с, события приходят пачкой), а также сразу при включении опции. - Если нужная частота **уже стоит — экран не мигает** (лишний вызов не делается). diff --git a/src/SystemIntegration/RefreshRate.cs b/src/SystemIntegration/RefreshRate.cs index 8b1323e..2f37d21 100644 --- a/src/SystemIntegration/RefreshRate.cs +++ b/src/SystemIntegration/RefreshRate.cs @@ -137,6 +137,24 @@ internal static bool HasMultipleRates(IEnumerable rates) => } } + /// Пресеты на случай, когда панель режимов не отдала (RDP, панель неактивна): + /// настройка не должна пропадать, а применение всё равно возьмёт ближайшую реальную. + internal static readonly int[] FallbackRates = [144, 120, 90, 60, 48]; + + /// + /// Варианты для комбо «от сети / от батареи» (XIC-107): частоты панели по убыванию. Раньше + /// здесь стояли зашитые 144/120/90/60/48 — на TM2409 с 165 Гц максимум выбора упирался + /// в 144, а на TM2424 (60/120) список предлагал частоты, которых у панели нет. + /// Значение из конфига, которого у панели нет, остаётся первым пунктом: вписанное руками + /// (или приехавшее с другой машины) не подменяем молча. + /// + internal static int[] Choices(IEnumerable supported, int current) + { + int[] rates = supported.Where(x => x > 1).Distinct().OrderDescending().ToArray(); + if (rates.Length == 0) rates = FallbackRates; + return rates.Contains(current) ? rates : [current, .. rates]; + } + /// /// Какие частоты реально участвуют в переборе: пересечение поддерживаемых панелью с /// выбором пользователя. Выбор пуст или вырожден (меньше двух реальных частот) — берём все. diff --git a/src/Ui/AppController.cs b/src/Ui/AppController.cs index d0be828..ca04302 100644 --- a/src/Ui/AppController.cs +++ b/src/Ui/AppController.cs @@ -114,6 +114,11 @@ public void Startup() if (_autoStart) Safe(() => { AutoStart.RepairIfBroken(); return true; }, true); // то же для ярлыка в «Пуске» (XIC-106): нет ярлыка — ничего не создаём Safe(() => { StartMenuShortcut.RepairIfBroken(); return true; }, true); + // режимы панели — в журнал: по log.txt тестера с чужой моделью сразу видно, что отдал + // драйвер (например, 164 вместо 165 при дробных 164,9 Гц) — XIC-107 + int[] rates = RefreshRate.Supported(); + Log.Write(rates.Length > 0 ? $"RefreshRate: режимы панели {string.Join('/', rates)} Гц" + : "RefreshRate: панель не отдала режимы"); }); // Тактильные настройки тачпада живут в нём самом — только прочитать для вкладки diff --git a/src/Ui/Settings/DisplayTab.cs b/src/Ui/Settings/DisplayTab.cs index 088c3fa..94db531 100644 --- a/src/Ui/Settings/DisplayTab.cs +++ b/src/Ui/Settings/DisplayTab.cs @@ -142,10 +142,12 @@ public DisplayTab(SettingsToolkit ui, AppConfig cfg, SettingsActions act, Action hold.Enabled = cfg.AutoRefreshRate; ui.AddRow(this, "settings.hz.hold", "settings.hz.hold.desc", hold); ui.AddGroup(this, "settings.hz.rates"); + // режимы панели — один раз на постройку: ими же живут и комбо, и список для цикла ниже + int[] supported = SystemIntegration.RefreshRate.Supported(); ui.AddRow(this, "settings.hz.ac", "settings.hz.ac.desc", - HzCombo(cfg.AcRefreshRate, hz => act.SetRefreshRates(hz, cfg.BatteryRefreshRate))); + HzCombo(cfg.AcRefreshRate, supported, hz => act.SetRefreshRates(hz, cfg.BatteryRefreshRate))); ui.AddRow(this, "settings.hz.battery", "settings.hz.battery.desc", - HzCombo(cfg.BatteryRefreshRate, hz => act.SetRefreshRates(cfg.AcRefreshRate, hz))); + HzCombo(cfg.BatteryRefreshRate, supported, hz => act.SetRefreshRates(cfg.AcRefreshRate, hz))); ui.AddNote(this, "settings.hz.note"); // Какие частоты перебирать клавишей (XIC-69). Показываем ровно то, что отдала панель: @@ -155,7 +157,6 @@ public DisplayTab(SettingsToolkit ui, AppConfig cfg, SettingsActions act, Action // выключение одного тумблера оставляет одну частоту, а это вырожденный случай, и // перебор возвращается ко всем. Тумблер выглядел бы выключенным, не меняя ничего — // настройка, которая делает вид, что работает, хуже отсутствующей. - int[] supported = SystemIntegration.RefreshRate.Supported(); if (supported.Length >= 3) { ui.AddGroup(this, "settings.hz.cycle"); @@ -192,12 +193,11 @@ private void ReleaseLux() private static string LuxText(float lux) => float.IsNaN(lux) ? "—" : Loc.T("settings.bright.lux.val", Math.Round(lux)); - // Комбо частоты: пресеты + текущее значение из config.json, если оно нестандартное - // (вручную вписанные 165 Гц не должны отображаться как «144») - private ComboBox HzCombo(int current, Action apply) + // Комбо частоты: режимы самой панели + значение из config.json, если у панели его нет + // (зашитые пресеты упирались в 144 на панели с 165 Гц — XIC-107; правила — RefreshRate.Choices) + private ComboBox HzCombo(int current, int[] supported, Action apply) { - int[] presets = [144, 120, 90, 60, 48]; - int[] rates = presets.Contains(current) ? presets : [current, .. presets]; + int[] rates = SystemIntegration.RefreshRate.Choices(supported, current); return Ui.Combo([.. rates.Select(r => $"{r} " + Loc.T("settings.hz.unit"))], Array.IndexOf(rates, current), i => apply(rates[i]), Ui.Sc(110)); } diff --git a/tests/XiControl.Tests/RefreshRateTests.cs b/tests/XiControl.Tests/RefreshRateTests.cs index 8b87502..1ce52c6 100644 --- a/tests/XiControl.Tests/RefreshRateTests.cs +++ b/tests/XiControl.Tests/RefreshRateTests.cs @@ -119,6 +119,34 @@ public void RememberCycleForHold_SameOrInvalidRateDoesNotSave(int selected) RefreshRate.CycleRates(Panel, Pick60120).Should().Equal(60, 120); } + // ---- Варианты комбо «от сети / от батареи» (XIC-107) ---- + + private static readonly int[] Panel165 = [60, 165, 120, 48, 165, 0]; + + [Fact] + public void Choices_ЧастотыПанелиПоУбыванию() + { + // TM2409: Windows предлагает 165, а зашитые пресеты упирались в 144; 0 — не частота + RefreshRate.Choices(Panel165, 120).Should().Equal(165, 120, 60, 48); + // TM2424: только то, что умеет панель — без чужих 144/90/48 + RefreshRate.Choices(Panel60120, 120).Should().Equal(120, 60); + } + + [Fact] + public void Choices_ЗначениеКонфигаВнеПанели_ОстаётсяПервым() + { + // вписано руками или приехало с другой машины — молча не подменяем + RefreshRate.Choices(Panel60120, 144).Should().Equal(144, 120, 60); + } + + [Fact] + public void Choices_ПанельНеОтдалаРежимы_ПрежниеПресеты() + { + // RDP / панель неактивна: настройка не должна пропасть + RefreshRate.Choices([], 120).Should().Equal(RefreshRate.FallbackRates); + RefreshRate.Choices([0, 1], 165).Should().Equal([165, .. RefreshRate.FallbackRates]); + } + [Fact] public void CycleRates_ВыбранноеЧегоПанельНеУмеет_Игнорируется() {