Обновлен модуль сессии. Добавлено чтение нажатия клавиш. Безопасная инициализация сессии. Новые параметры: keypad и escDelay. Обновлены функции проверки вызовов ncurses-функций.

This commit is contained in:
Alexander Zhirov 2026-01-05 01:05:04 +03:00
parent a5778d0de5
commit 057b62b58e
Signed by: alexander
GPG key ID: C8D8BE544A27C511

View file

@ -1,12 +1,33 @@
module ncui.core.session; module ncui.core.session;
import core.stdc.locale : setlocale, LC_ALL; import core.stdc.locale : setlocale, LC_ALL;
import std.exception : enforce;
import deimos.ncurses; import deimos.ncurses;
import ncui.core.ncwin; 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` посимвольный режим символы доступны сразу, но часть спец-клавиш/ * - `cbreak` посимвольный режим символы доступны сразу, но часть спец-клавиш/
* управляющих символов всё ещё может обрабатываться терминалом (сигналы). * управляющих символов всё ещё может обрабатываться терминалом (сигналы).
* - `raw` "сырой" посимвольный режим минимальная обработка терминалом, * - `raw` "сырой" посимвольный режим минимальная обработка терминалом,
@ -41,7 +62,7 @@ enum InputMode
/// Сырой посимвольный ввод с минимальной обработкой терминалом. /// Сырой посимвольный ввод с минимальной обработкой терминалом.
raw, raw,
/// Канонический построчный ввод (поведение "как в обычной консоли"). /// Канонический построчный ввод (поведение "как в обычной консоли").
common cooked
} }
/** /**
@ -59,13 +80,34 @@ enum Echo
off off
} }
/**
* Режим обработки специальных клавиш в ncurses (`keypad`).
*
* Когда режим включён, ncurses преобразует специальные клавиши (стрелки, Home/End,
* PgUp/PgDn, F1..F12 и т.п.) в коды `KEY_*`, которые удобно обрабатывать в программе.
* Когда выключён многие такие клавиши приходят как последовательности символов
* (escape-последовательности), и их приходится разбирать вручную.
*/
enum Keypad : bool
{
/// Включить обработку специальных клавиш (`KEY_*`).
on = true,
/// Выключить обработку специальных клавиш.
off = false
}
/** /**
* Конфигурация терминальной сессии. * Конфигурация терминальной сессии.
* *
* Поля: * Поля:
* - `mode` режим ввода. * - `mode` режим ввода.
* - `cursor` видимость/тип курсора. * - `cursor` видимость/тип курсора.
* - `echo` отображать ли вводимые символы. * - `echo` отображать ли вводимые символы.
* - `keypad` включение обработки специальных клавиш (стрелки, F-клавиши `KEY_*`).
* - `escDelay` задержка (в миллисекундах) для различения одиночного `Esc` и
* escape-последовательностей (стрелки и т.п.). Обычно 0..50 для
* отзывчивого UI; слишком маленькое значение может повлиять на
* корректность распознавания некоторых клавиш в отдельных терминалах.
*/ */
struct SessionConfig struct SessionConfig
{ {
@ -75,6 +117,10 @@ struct SessionConfig
Cursor cursor = Cursor.normal; Cursor cursor = Cursor.normal;
/// Эхо ввода. По умолчанию отображает вводимые символы (`on`). /// Эхо ввода. По умолчанию отображает вводимые символы (`on`).
Echo echo = Echo.on; Echo echo = Echo.on;
/// Обработка специальных клавиш (стрелки, Home/End, PgUp/PgDn, F1..F12 → `KEY_*`).
Keypad keypad = Keypad.on;
/// Задержка распознавания ESC/escape-последовательностей в миллисекундах.
int escDelay = 50;
} }
final class Session final class Session
@ -89,48 +135,79 @@ private:
final switch (config.mode) final switch (config.mode)
{ {
case InputMode.raw: case InputMode.raw:
ncuiCall!nocbreak(OK); ncuiNotErr!nocbreak();
ncuiCall!raw(OK); ncuiNotErr!raw();
break; break;
case InputMode.cbreak: case InputMode.cbreak:
ncuiCall!noraw(OK); ncuiNotErr!noraw();
ncuiCall!cbreak(OK); ncuiNotErr!cbreak();
break; break;
case InputMode.common: case InputMode.cooked:
ncuiCall!noraw(OK); ncuiNotErr!noraw();
ncuiCall!nocbreak(OK); ncuiNotErr!nocbreak();
break; break;
} }
// Настройка отображения вводимых символов. // Настройка отображения вводимых символов.
final switch (config.echo) final switch (config.echo)
{ {
case Echo.on: case Echo.on:
ncuiCall!echo(OK); ncuiNotErr!echo();
break; break;
case Echo.off: case Echo.off:
ncuiCall!noecho(OK); ncuiNotErr!noecho();
break; break;
} }
// Настройка видимости курсора.
ncuiNotErr!curs_set(config.cursor);
// Настройка задержки при нажатии на ESC
ncuiNotErr!set_escdelay(config.escDelay);
// Настройка обработки специальных клавиш
ncuiNotErr!keypad(_root, config.keypad);
} }
public: public:
this(const SessionConfig config) this(const SessionConfig config)
{ {
// ncurses не должен быть инициализирован (false)
ncuiExpectMsg!cursesInitialized("ncurses is already initialized", false);
// Адекватное чтение юникода. // Адекватное чтение юникода.
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
_root = NCWin(ncuiNotNull!initscr()); _root = NCWin(ncuiNotNull!initscr());
// Если на этапе инициализации сработает проблема с конфигурированием сессии
scope (failure)
{
endwin();
gInitialized = false;
}
// Установить флаг инициализации ncurses
gInitialized = true;
setup(config); setup(config);
} }
NCWin root()
{
return _root;
}
KeyEvent readKey(NCWin inputWindow)
{
dchar ch;
int status = wget_wch(inputWindow, &ch);
return KeyEvent(status, ch);
}
void close() void close()
{ {
if (_ended) if (_ended)
return; return;
endwin(); endwin();
_ended = true; _ended = true;
gInitialized = false;
} }
~this() ~this()