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
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@

## [Unreleased]

Пока пусто — планы в [ROADMAP.md](ROADMAP.md).
### Добавлено
- **`OwlIgnoreDisplay` в config.json** — «Режим совы» умеет держать бодрой только систему, отпуская
экран: с `true` он больше не выставляет `ES_DISPLAY_REQUIRED`, и экран гаснет по обычному таймауту
схемы питания. Для сценария «комп не должен уснуть ради удалённого доступа», где подсвеченная
матрица горит впустую. Крышку опция не затрагивает — закрытая крышка на сети всё так же не
усыпляет; медиаплееры запрашивают удержание экрана сами, так что фильмы не прерываются.
По умолчанию `false` — поведение существующих установок не меняется, пока поле не добавят руками.

## [0.8.0] — 2026-07-29

Expand Down
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ WMI-событий). `Program.cs`: single-instance mutex → DI-контейне
`Brightness` (WMI ACPI-подсветка), `TouchpadControl`/`TouchscreenControl` (вкл/выкл через
SetupAPI/CfgMgr32 — отключается родительский узел I2C HID, без PERSIST; общая механика в
базовом `HidNodeToggle`, разница лишь в HID-коллекции: тачпад U:0005, экран U:0004),
`AutoStart` (задача планировщика + самопочинка пути), `AwakeMode` («режим совы»),
`AutoStart` (задача планировщика + самопочинка пути), `AwakeMode` («режим совы»: всегда
`ES_SYSTEM_REQUIRED` + крышка на AC, экран держится `ES_DISPLAY_REQUIRED` — кроме
`OwlIgnoreDisplay: true` в config.json, тогда только «не спать»),
`MicControl`, `KeyActions`, `Sound` (WAV-джинглы), `BatteryInfo`, `PowerDraw`;
**HTTP API (XIC-13, opt-in)** — `HttpApi` (хост на `HttpListener`/http.sys, без ASP.NET Core;
создаётся только при включённой фиче → выключено = 0 CPU), `ApiRouter` (авторизация Bearer+SHA-256
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ current direction is shown by color (charging green / discharging orange).*
control; a disabled feature disappears from the menu and panel entirely.
- 🎨 The tray icon changes with the mode, monochrome to match a light/dark taskbar;
a dark menu matching the system theme (switches on the fly).
- 🦉 **"Owl mode"** — don't turn off the display or sleep; a closed lid on AC power only turns
off the display (on battery — regular sleep). Owl in the panel / a checkbox in the menu;
power timings aren't changed, the lid action is restored afterwards.
- 🦉 **"Owl mode"** — don't sleep, and don't turn off the display *(optional)*; a closed lid on
AC power only turns off the display (on battery — regular sleep). Owl in the panel / a checkbox
in the menu; power timings aren't changed, the lid action is restored afterwards.
If you only need the machine to stay awake (for a remote session, say), set
`"OwlIgnoreDisplay": true` in config — the screen then blanks as usual instead of burning idle.
The feature can be hidden entirely (`"OwlMode": false` in config).
- 🖥️ **Auto refresh rate** — plug in the charger → 120 Hz, unplug → 60 Hz
(rates configurable in config: `AcRefreshRate`/`BatteryRefreshRate`; if the panel lacks
Expand Down
8 changes: 5 additions & 3 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,11 @@
управление частотой; выключенная функция исчезает из меню и панели целиком.
- 🎨 Значок в трее меняется по режиму, монохром под светлую/тёмную панель задач;
тёмное меню в тон системной теме (переключается на лету).
- 🦉 **«Режим совы»** — не гасить экран и не засыпать; закрытая крышка на питании
от сети лишь выключает экран (на батарее — штатный сон). Сова в панели / галочка
в меню; тайминги электропитания не изменяются, действие крышки восстанавливается.
- 🦉 **«Режим совы»** — не засыпать и не гасить экран *(опционально)*; закрытая крышка
на питании от сети лишь выключает экран (на батарее — штатный сон). Сова в панели /
галочка в меню; тайминги электропитания не изменяются, действие крышки восстанавливается.
Если сова нужна только чтобы система не уснула (например, ради удалённого доступа) —
`"OwlIgnoreDisplay": true` в конфиге, и экран будет гаснуть как обычно, не выгорая впустую.
Можно скрыть фичу целиком (`"OwlMode": false` в конфиге).
- 🖥️ **Авто-герцовка** — подключил зарядку → экран 120 Гц, отключил → 60 Гц
(частоты настраиваются в конфиге: `AcRefreshRate`/`BatteryRefreshRate`; если такой
Expand Down
14 changes: 13 additions & 1 deletion src/Config/AppConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,21 @@ public sealed class AppConfig
/// <summary>Частота экрана (Гц) от батареи. Настраивается только правкой config.json.</summary>
public int BatteryRefreshRate { get; set; } = 60;

/// <summary>Режим «Не спать» активен: экран не гаснет, сна нет; крышка на AC — «ничего не делать».</summary>
/// <summary>Режим «Не спать» активен: сна нет; крышка на AC — «ничего не делать»;
/// экран не гаснет, если не задан <see cref="OwlIgnoreDisplay"/>.</summary>
public bool Awake { get; set; } = false;

/// <summary>
/// «Сова не трогает монитор»: с true режим держит бодрой только систему, а экран гаснет по
/// обычному таймауту схемы питания (не выставляем `ES_DISPLAY_REQUIRED`). Для сценария
/// «комп не должен уснуть ради удалённого доступа» — матрица там горит впустую.
/// Дефолт false = прежнее поведение, экран удерживается: поведение существующих установок
/// не меняется, пока поле не добавят в config.json руками. Крышку опция не затрагивает —
/// закрытая крышка на сети всё так же не усыпляет. Медиаплееры просят `ES_DISPLAY_REQUIRED`
/// сами, поэтому фильмы не прерываются и с true. Применяется при следующем включении совы.
/// </summary>
public bool OwlIgnoreDisplay { get; set; } = false;

/// <summary>Исходное действие крышки (AC) до включения «Не спать» — для восстановления, в т.ч. после сбоя.</summary>
public int? AwakeSavedLidAc { get; set; }

Expand Down
16 changes: 11 additions & 5 deletions src/SystemIntegration/AwakeMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
namespace XiControl.SystemIntegration;

/// <summary>
/// Режим «Не спать»: пока включён — экран не гаснет и система не засыпает
/// (SetThreadExecutionState), а закрытие крышки НА ПИТАНИИ ОТ СЕТИ не уводит
/// в сон (действие крышки AC → «ничего не делать» через Power Management API).
/// На батарее крышка не трогается — там штатный сон.
/// Режим «Не спать»: пока включён — система не засыпает (SetThreadExecutionState),
/// а закрытие крышки НА ПИТАНИИ ОТ СЕТИ не уводит в сон (действие крышки AC →
/// «ничего не делать» через Power Management API). На батарее крышка не трогается —
/// там штатный сон. Экран дополнительно удерживается негаснущим — кроме случая, когда в
/// config.json задан <see cref="AppConfig.OwlIgnoreDisplay"/> (дефолт false = прежнее
/// поведение); с ним сова держит бодрой только систему, для удалённого доступа.
/// Исходное действие крышки хранится в config.json (восстановление после сбоя).
/// </summary>
public static class AwakeMode
Expand Down Expand Up @@ -50,8 +52,12 @@ public static bool Enable(AppConfig cfg)
}
finally { LocalFree(pScheme); }

// ES_SYSTEM_REQUIRED — всегда, это и есть суть режима; экран отпускаем только по опции
uint flags = ES_CONTINUOUS | ES_SYSTEM_REQUIRED;
if (!cfg.OwlIgnoreDisplay) flags |= ES_DISPLAY_REQUIRED;

// возвращает прежние флаги; 0 — вызов не принят (сон/экран не удержим)
if (SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED) == 0)
if (SetThreadExecutionState(flags) == 0)
{
Log.Write("AwakeMode: SetThreadExecutionState не принял флаги");
return false;
Expand Down
5 changes: 5 additions & 0 deletions tests/XiControl.Tests/AppConfigMigrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public void FreshConfig_GetsDocumentedDefaults()
cfg.ProjKeyAction.Should().Be("projection");
}

[Fact]
public void OwlIgnoreDisplay_DefaultsToOff_SoOwlKeepsHoldingTheScreen()
=> new AppConfig().OwlIgnoreDisplay.Should().BeFalse(
"поле только для тех, кто добавит его руками; без него сова ведёт себя как раньше");

[Fact]
public void LegacyChargeFirst_MapsClickToChargeAndDoubleToModes()
{
Expand Down
Loading