Обработка событий в консоли
Обработка событий в консоли
В одной из предыдущих статей я уже рассказывал об обработке нештатных ситуаций консолью. А как у консоли обстоят дела со штатными? Оказывается, не намного хуже, чем у оконных приложений. Окно консоли может обрабатывать не только клавиатурный ввод, но и операции с мышью, установку и потерю фокуса окна, а также работу с системным меню.
Все эти операции объединены в условные объекты типа "консольный ввод" и обрабатываются примерно так же, как и сообщения в оконном приложении. При наступлении того или иного события оно становится в очередь - буфер ввода. Затем консольное приложение поочередно извлекает события из этого буфера. Делается это все при помощи функции ReadConsoleInput или PeekConsoleInput. В первом случае очередь обрабатывается синхронно и событие будет удалено из очереди сразу после его появления, а во втором случае данные обрабатываются асинхронно и событие останется на месте до последующего его извлечения из очереди.
Для начала нам понадобится пачка констант и структур, которые не описаны в FASM. Ничего, мы люди привычные, нас такими мелочами не напугать.
Code (Assembler) : Убрать нумерацию
- KEY_EVENT = 0x0001
- MOUSE_EVENT = 0x0002
- WINDOW_BUFFER_SIZE_EVENT = 0x0004
- MENU_EVENT = 0x0008
- FOCUS_EVENT = 0x0010
- struct COORD
- X dw ?
- Y dw ?
- ends
- struct KEY_EVENT_RECORD
- bKeyDown dd ?
- wRepeatCount dw ?
- wVirtualKeyCode dw ?
- wVirtualScanCode dw ?
- union
- UnicodeChar dw ?
- AsciiChar db ?
- ends
- dwControlKeyState dd ?
- ends
- struct MOUSE_EVENT_RECORD
- dwMousePosition COORD
- dwButtonState dd ?
- dwControlKeyState dd ?
- dwEventFlags dd ?
- ends
- struct WINDOW_BUFFER_SIZE_RECORD
- dwSize COORD
- ends
- struct MENU_EVENT_RECORD
- dwCommandId dd ?
- ends
- struct FOCUS_EVENT_RECORD
- bSetFocus dd ?
- ends
- struct INPUT_RECORD
- EventType rw 2
- union
- KeyEvent KEY_EVENT_RECORD
- MouseEvent MOUSE_EVENT_RECORD
- WindowBufferSizeEvent WINDOW_BUFFER_SIZE_RECORD
- MenuEvent MENU_EVENT_RECORD
- FocusEvent FOCUS_EVENT_RECORD
- ends
- ends
Code (Assembler) : Убрать нумерацию
- ; Получить хэндл стандартного ввода stdin
- invoke GetStdHandle,STD_INPUT_HANDLE
- mov ebx,eax
- @@:
- ; Прочитать одно событие из очереди
- invoke ReadConsoleInput,ebx,input,1,tmp
- ; Обработка в зависимости от типа события
- cmp word [input.EventType],KEY_EVENT
- je loc_key_event
- cmp word [input.EventType],MOUSE_EVENT
- je loc_mouse_event
- cmp word [input.EventType],WINDOW_BUFFER_SIZE_EVENT
- je loc_buffer_event
- cmp word [input.EventType],MENU_EVENT
- je loc_menu_event
- cmp word [input.EventType],FOCUS_EVENT
- je loc_focus_event
- jmp @b
Первое событие, самое простое, это FocusEvent. При получении фокуса консольным окном в поле bSetFocus структуры FOCUS_EVENT_RECORD будет 1, а когда окно станет неактивным, то там будет значение 0. Это событие удобно использовать, например, для приостановки каких-нибудь ресурсоемких операций, когда приложение работает в фоновом режиме.
Событие MenuEvent относится к системным и в подавляющем большинстве случаев должно быть просто проигнорировано. Но если вам вдруг станет интересно, что пользователь вдруг решил поковыряться в системном меню вашей консольки, то никто не запретит вам его тоже обрабатывать. В единственном поле dwCommandId структуры MENU_EVENT_RECORD будет передан идентификатор соответствующего пункта меню.
Следующее системное событие WindowBufferSizeEvent срабатывает, когда пользователь меняет размеры консоли через меню настройки консоли. В поле dwSize структуры WINDOW_BUFFER_SIZE_RECORD передаются новые значения консольного буфера. Это действие выполняется примерно никогда в жизни, но если вы хотите динамически перегруппировывать выводимые на экран данные в вашей программе, то отслеживать это событие придется.
Теперь самые интересные события - клавиатура и мышь. Событие KeyEvent наступает, когда происходит нажатие любой клавиши клавиатуре. Подробная информация о нажатии передается в полях структуры KEY_EVENT_RECORD: bKeyDown показывает, была клавиша нажата или отпущена, wRepeatCount - счетчик повторов при удержании клавиши нажатой, wVirtualKeyCode - виртуальный устройство-независимый код нажатой клавиши, wVirtualScanCode - виртуальный код нажатой клавиши, зависящий от клавиатуры, uChar - ASCII или юникодный символ в зависимости от режима работы консоли, dwControlKeyState - состояние клавиш-модификаторов типа Alt, Ctrl, Shift и переключателей *Lock. Событие происходит даже если буквенно-цифровые клавиши не нажимались, а были нажаты только клавиши-модификаторы.
Чуть менее полезное, но не менее интересное событие MouseEvent. Оно возникает в случае любой мышиной активности над консольным окном. В структуре MOUSE_EVENT_RECORD в поле dwMousePosition передаются координаты буфера (не окна!) консоли, по которым произошло событие, dwButtonState - состояние нажатия кнопок мыши, dwControlKeyState - состояние клавиш-модификаторов и переключателей, значение полностью аналогично полю из клавиатурного события, dwEventFlags - более подробная расшифровка действия мышки, то есть направление вращения колесика, нажатие или отпускание кнопки мыши, двойной клик или изменение координат мыши. Обработку мыши можно активировать или отключать при помощи функции SetConsoleMode с флагом ENABLE_MOUSE_INPUT.
В приложении пример консольной программы с исходным текстом, которая обрабатывает все поступающие события.
Просмотров: 1512 | Комментариев: 2
Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
ManHunter
(28.01.2022 в 14:07):
Сам не пробовал, но как бы начал делать я. Аттач к чужой консоли https://www.manhunter.ru/assem...zheniya.html , получение хэндла ввода и затем отправка https://docs.microsoft.com/en-...consoleinput заполненной структуры KEY_EVENT_RECORD с "нажатием" Ctrl+C
pawel97
(28.01.2022 в 00:17):
Спасибо, полезно!
А как в запущенный нами процесс передать программно ctrl+c, чтобы нас при этом не прибили? Например если прога запускает консольный сервер или конвертер и мониторит его лог, и нужна возможность грамотно отменить операцию - дать дочернему процессу нормально завершиться.
Я что-то пытался играться с GenerateConsoleCtrlEvent, но происходили всякие странности.
А как в запущенный нами процесс передать программно ctrl+c, чтобы нас при этом не прибили? Например если прога запускает консольный сервер или конвертер и мониторит его лог, и нужна возможность грамотно отменить операцию - дать дочернему процессу нормально завершиться.
Я что-то пытался играться с GenerateConsoleCtrlEvent, но происходили всякие странности.
Добавить комментарий
Заполните форму для добавления комментария