diff --git a/CHANGELOG.md b/CHANGELOG.md
index f45e176..c417d82 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,13 @@
## [Unreleased]
-Пока пусто — планы в [ROADMAP.md](ROADMAP.md).
+### Добавлено
+- **`OwlIgnoreDisplay` в config.json** — «Режим совы» умеет держать бодрой только систему, отпуская
+ экран: с `true` он больше не выставляет `ES_DISPLAY_REQUIRED`, и экран гаснет по обычному таймауту
+ схемы питания. Для сценария «комп не должен уснуть ради удалённого доступа», где подсвеченная
+ матрица горит впустую. Крышку опция не затрагивает — закрытая крышка на сети всё так же не
+ усыпляет; медиаплееры запрашивают удержание экрана сами, так что фильмы не прерываются.
+ По умолчанию `false` — поведение существующих установок не меняется, пока поле не добавят руками.
## [0.8.0] — 2026-07-29
diff --git a/CLAUDE.md b/CLAUDE.md
index ff619bc..2e00cb3 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -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
diff --git a/README.md b/README.md
index 7ee2850..0ca5045 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/README.ru.md b/README.ru.md
index 2d46036..6d35b2c 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -89,9 +89,11 @@
управление частотой; выключенная функция исчезает из меню и панели целиком.
- 🎨 Значок в трее меняется по режиму, монохром под светлую/тёмную панель задач;
тёмное меню в тон системной теме (переключается на лету).
-- 🦉 **«Режим совы»** — не гасить экран и не засыпать; закрытая крышка на питании
- от сети лишь выключает экран (на батарее — штатный сон). Сова в панели / галочка
- в меню; тайминги электропитания не изменяются, действие крышки восстанавливается.
+- 🦉 **«Режим совы»** — не засыпать и не гасить экран *(опционально)*; закрытая крышка
+ на питании от сети лишь выключает экран (на батарее — штатный сон). Сова в панели /
+ галочка в меню; тайминги электропитания не изменяются, действие крышки восстанавливается.
+ Если сова нужна только чтобы система не уснула (например, ради удалённого доступа) —
+ `"OwlIgnoreDisplay": true` в конфиге, и экран будет гаснуть как обычно, не выгорая впустую.
Можно скрыть фичу целиком (`"OwlMode": false` в конфиге).
- 🖥️ **Авто-герцовка** — подключил зарядку → экран 120 Гц, отключил → 60 Гц
(частоты настраиваются в конфиге: `AcRefreshRate`/`BatteryRefreshRate`; если такой
diff --git a/src/Config/AppConfig.cs b/src/Config/AppConfig.cs
index 33a4bf4..09784d3 100644
--- a/src/Config/AppConfig.cs
+++ b/src/Config/AppConfig.cs
@@ -154,9 +154,21 @@ public sealed class AppConfig
/// Частота экрана (Гц) от батареи. Настраивается только правкой config.json.
public int BatteryRefreshRate { get; set; } = 60;
- /// Режим «Не спать» активен: экран не гаснет, сна нет; крышка на AC — «ничего не делать».
+ /// Режим «Не спать» активен: сна нет; крышка на AC — «ничего не делать»;
+ /// экран не гаснет, если не задан .
public bool Awake { get; set; } = false;
+ ///
+ /// «Сова не трогает монитор»: с true режим держит бодрой только систему, а экран гаснет по
+ /// обычному таймауту схемы питания (не выставляем `ES_DISPLAY_REQUIRED`). Для сценария
+ /// «комп не должен уснуть ради удалённого доступа» — матрица там горит впустую.
+ /// Дефолт false = прежнее поведение, экран удерживается: поведение существующих установок
+ /// не меняется, пока поле не добавят в config.json руками. Крышку опция не затрагивает —
+ /// закрытая крышка на сети всё так же не усыпляет. Медиаплееры просят `ES_DISPLAY_REQUIRED`
+ /// сами, поэтому фильмы не прерываются и с true. Применяется при следующем включении совы.
+ ///
+ public bool OwlIgnoreDisplay { get; set; } = false;
+
/// Исходное действие крышки (AC) до включения «Не спать» — для восстановления, в т.ч. после сбоя.
public int? AwakeSavedLidAc { get; set; }
diff --git a/src/SystemIntegration/AwakeMode.cs b/src/SystemIntegration/AwakeMode.cs
index 5633b12..c7f4af8 100644
--- a/src/SystemIntegration/AwakeMode.cs
+++ b/src/SystemIntegration/AwakeMode.cs
@@ -4,10 +4,12 @@
namespace XiControl.SystemIntegration;
///
-/// Режим «Не спать»: пока включён — экран не гаснет и система не засыпает
-/// (SetThreadExecutionState), а закрытие крышки НА ПИТАНИИ ОТ СЕТИ не уводит
-/// в сон (действие крышки AC → «ничего не делать» через Power Management API).
-/// На батарее крышка не трогается — там штатный сон.
+/// Режим «Не спать»: пока включён — система не засыпает (SetThreadExecutionState),
+/// а закрытие крышки НА ПИТАНИИ ОТ СЕТИ не уводит в сон (действие крышки AC →
+/// «ничего не делать» через Power Management API). На батарее крышка не трогается —
+/// там штатный сон. Экран дополнительно удерживается негаснущим — кроме случая, когда в
+/// config.json задан (дефолт false = прежнее
+/// поведение); с ним сова держит бодрой только систему, для удалённого доступа.
/// Исходное действие крышки хранится в config.json (восстановление после сбоя).
///
public static class AwakeMode
@@ -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;
diff --git a/tests/XiControl.Tests/AppConfigMigrationTests.cs b/tests/XiControl.Tests/AppConfigMigrationTests.cs
index 36f4b74..03116ec 100644
--- a/tests/XiControl.Tests/AppConfigMigrationTests.cs
+++ b/tests/XiControl.Tests/AppConfigMigrationTests.cs
@@ -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()
{