Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
ничего не открывает (XIC-106).

### Исправлено
- **Частота экрана: в списке есть 165 Гц и всё, что умеет панель.** Выбор частоты «от сети» и «от
батареи» во вкладке «Экран» строился из зашитого набора 144/120/90/60/48: на Redmi Book Pro 2025
(TM2409) с панелью 165 Гц максимум упирался в 144, а на других моделях список предлагал частоты,
которых у панели нет. Теперь в списке ровно частоты встроенной панели. Значение из конфига, которого
у панели нет, остаётся в списке. Режимы панели пишутся в журнал при старте (XIC-107).
- **Проверка драйверов отбрасывает пакеты чужой модели.** Сервер Xiaomi называет в ответе модель,
для которой пакет. Если она не совпала с моделью ноутбука, пакет не показывается. Для прошивок
экрана и тачпада это единственная проверка, что пакет вообще про ваш ноутбук (XIC-103).
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` (режим по питанию + независимое запоминание яркости; единственный
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
5 changes: 5 additions & 0 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 с, события приходят пачкой), а также сразу при включении опции.
- Если нужная частота **уже стоит — экран не мигает** (лишний вызов не делается).
Expand Down
18 changes: 18 additions & 0 deletions src/SystemIntegration/RefreshRate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,24 @@ internal static bool HasMultipleRates(IEnumerable<int> rates) =>
}
}

/// <summary>Пресеты на случай, когда панель режимов не отдала (RDP, панель неактивна):
/// настройка не должна пропадать, а применение всё равно возьмёт ближайшую реальную.</summary>
internal static readonly int[] FallbackRates = [144, 120, 90, 60, 48];

/// <summary>
/// Варианты для комбо «от сети / от батареи» (XIC-107): частоты панели по убыванию. Раньше
/// здесь стояли зашитые 144/120/90/60/48 — на TM2409 с 165 Гц максимум выбора упирался
/// в 144, а на TM2424 (60/120) список предлагал частоты, которых у панели нет.
/// Значение из конфига, которого у панели нет, остаётся первым пунктом: вписанное руками
/// (или приехавшее с другой машины) не подменяем молча.
/// </summary>
internal static int[] Choices(IEnumerable<int> 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];
}

/// <summary>
/// Какие частоты реально участвуют в переборе: пересечение поддерживаемых панелью с
/// выбором пользователя. Выбор пуст или вырожден (меньше двух реальных частот) — берём все.
Expand Down
5 changes: 5 additions & 0 deletions src/Ui/AppController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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: панель не отдала режимы");
});

// Тактильные настройки тачпада живут в нём самом — только прочитать для вкладки
Expand Down
16 changes: 8 additions & 8 deletions src/Ui/Settings/DisplayTab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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). Показываем ровно то, что отдала панель:
Expand All @@ -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");
Expand Down Expand Up @@ -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<int> apply)
// Комбо частоты: режимы самой панели + значение из config.json, если у панели его нет
// (зашитые пресеты упирались в 144 на панели с 165 Гц — XIC-107; правила — RefreshRate.Choices)
private ComboBox HzCombo(int current, int[] supported, Action<int> 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));
}
Expand Down
28 changes: 28 additions & 0 deletions tests/XiControl.Tests/RefreshRateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,34 @@ public void CycleRates_ОставляемТолькоВыбранные()
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_ВыбранноеЧегоПанельНеУмеет_Игнорируется()
{
Expand Down
Loading