From 057b62b58e8b8707fa458342e50f328a2a4536f9 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Mon, 5 Jan 2026 01:05:04 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20=D1=81=D0=B5?= =?UTF-8?q?=D1=81=D1=81=D0=B8=D0=B8.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D1=87=D1=82=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D0=BD=D0=B0=D0=B6=D0=B0=D1=82=D0=B8=D1=8F=20=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D1=88.=20=D0=91=D0=B5=D0=B7=D0=BE=D0=BF?= =?UTF-8?q?=D0=B0=D1=81=D0=BD=D0=B0=D1=8F=20=D0=B8=D0=BD=D0=B8=D1=86=D0=B8?= =?UTF-8?q?=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=D0=B5?= =?UTF-8?q?=D1=81=D1=81=D0=B8=D0=B8.=20=D0=9D=D0=BE=D0=B2=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D1=8B:=20keypa?= =?UTF-8?q?d=20=D0=B8=20escDelay.=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B7=D0=BE=D0=B2=D0=BE=D0=B2=20ncurses-=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=B9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/ncui/core/session.d | 109 +++++++++++++++++++++++++++++++------ 1 file changed, 93 insertions(+), 16 deletions(-) diff --git a/source/ncui/core/session.d b/source/ncui/core/session.d index 51444ff..a39e7f9 100644 --- a/source/ncui/core/session.d +++ b/source/ncui/core/session.d @@ -1,12 +1,33 @@ module ncui.core.session; import core.stdc.locale : setlocale, LC_ALL; -import std.exception : enforce; import deimos.ncurses; import ncui.core.ncwin; -import ncui.lib.common; +import ncui.lib.checks; +import ncui.core.event; + +/** + * Глобальный флаг инициализации ncurses для всего процесса. + * + * Хранит "истину" о том, выполнялась ли успешная инициализация (`initscr()`). + * Используется для защиты от повторной инициализации и для быстрых проверок + * перед вызовами функций, требующих активной ncurses-сессии. + */ +private __gshared bool gInitialized; + +/** + * Проверяет, была ли ncurses-сессия уже инициализирована в текущем процессе. + * + * Возвращает: + * `true`, если ранее была выполнена успешная инициализация (и был поднят `gInitialized`), + * иначе `false`. + */ +bool cursesInitialized() @nogc nothrow +{ + return gInitialized; +} /** * Уровень видимости/типа курсора. @@ -28,7 +49,7 @@ enum Cursor : int /** * Режим обработки ввода терминалом. * - * - `common` — канонический (построчный) режим — ввод приходит после Enter. + * - `cooked` — канонический (построчный) режим — ввод приходит после Enter. * - `cbreak` — посимвольный режим — символы доступны сразу, но часть спец-клавиш/ * управляющих символов всё ещё может обрабатываться терминалом (сигналы). * - `raw` — "сырой" посимвольный режим — минимальная обработка терминалом, @@ -41,7 +62,7 @@ enum InputMode /// Сырой посимвольный ввод с минимальной обработкой терминалом. raw, /// Канонический построчный ввод (поведение "как в обычной консоли"). - common + cooked } /** @@ -59,13 +80,34 @@ enum Echo off } +/** + * Режим обработки специальных клавиш в ncurses (`keypad`). + * + * Когда режим включён, ncurses преобразует специальные клавиши (стрелки, Home/End, + * PgUp/PgDn, F1..F12 и т.п.) в коды `KEY_*`, которые удобно обрабатывать в программе. + * Когда выключён — многие такие клавиши приходят как последовательности символов + * (escape-последовательности), и их приходится разбирать вручную. + */ +enum Keypad : bool +{ + /// Включить обработку специальных клавиш (`KEY_*`). + on = true, + /// Выключить обработку специальных клавиш. + off = false +} + /** * Конфигурация терминальной сессии. * * Поля: - * - `mode` — режим ввода. - * - `cursor` — видимость/тип курсора. - * - `echo` — отображать ли вводимые символы. + * - `mode` — режим ввода. + * - `cursor` — видимость/тип курсора. + * - `echo` — отображать ли вводимые символы. + * - `keypad` — включение обработки специальных клавиш (стрелки, F-клавиши → `KEY_*`). + * - `escDelay` — задержка (в миллисекундах) для различения одиночного `Esc` и + * escape-последовательностей (стрелки и т.п.). Обычно 0..50 для + * отзывчивого UI; слишком маленькое значение может повлиять на + * корректность распознавания некоторых клавиш в отдельных терминалах. */ struct SessionConfig { @@ -75,6 +117,10 @@ struct SessionConfig Cursor cursor = Cursor.normal; /// Эхо ввода. По умолчанию отображает вводимые символы (`on`). Echo echo = Echo.on; + /// Обработка специальных клавиш (стрелки, Home/End, PgUp/PgDn, F1..F12 → `KEY_*`). + Keypad keypad = Keypad.on; + /// Задержка распознавания ESC/escape-последовательностей в миллисекундах. + int escDelay = 50; } final class Session @@ -89,48 +135,79 @@ private: final switch (config.mode) { case InputMode.raw: - ncuiCall!nocbreak(OK); - ncuiCall!raw(OK); + ncuiNotErr!nocbreak(); + ncuiNotErr!raw(); break; case InputMode.cbreak: - ncuiCall!noraw(OK); - ncuiCall!cbreak(OK); + ncuiNotErr!noraw(); + ncuiNotErr!cbreak(); break; - case InputMode.common: - ncuiCall!noraw(OK); - ncuiCall!nocbreak(OK); + case InputMode.cooked: + ncuiNotErr!noraw(); + ncuiNotErr!nocbreak(); break; } // Настройка отображения вводимых символов. final switch (config.echo) { case Echo.on: - ncuiCall!echo(OK); + ncuiNotErr!echo(); break; case Echo.off: - ncuiCall!noecho(OK); + ncuiNotErr!noecho(); break; } + // Настройка видимости курсора. + ncuiNotErr!curs_set(config.cursor); + // Настройка задержки при нажатии на ESC + ncuiNotErr!set_escdelay(config.escDelay); + // Настройка обработки специальных клавиш + ncuiNotErr!keypad(_root, config.keypad); } public: this(const SessionConfig config) { + // ncurses не должен быть инициализирован (false) + ncuiExpectMsg!cursesInitialized("ncurses is already initialized", false); // Адекватное чтение юникода. setlocale(LC_ALL, ""); _root = NCWin(ncuiNotNull!initscr()); + + // Если на этапе инициализации сработает проблема с конфигурированием сессии + scope (failure) + { + endwin(); + gInitialized = false; + } + + // Установить флаг инициализации ncurses + gInitialized = true; setup(config); } + NCWin root() + { + return _root; + } + + KeyEvent readKey(NCWin inputWindow) + { + dchar ch; + int status = wget_wch(inputWindow, &ch); + return KeyEvent(status, ch); + } + void close() { if (_ended) return; endwin(); _ended = true; + gInitialized = false; } ~this()