Blog. Just Blog

Обработка событий в консоли

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Обработка событий в консоли
Обработка событий в консоли

В одной из предыдущих статей я уже рассказывал об обработке нештатных ситуаций консолью. А как у консоли обстоят дела со штатными? Оказывается, не намного хуже, чем у оконных приложений. Окно консоли может обрабатывать не только клавиатурный ввод, но и операции с мышью, установку и потерю фокуса окна, а также работу с системным меню.

Все эти операции объединены в условные объекты типа "консольный ввод" и обрабатываются примерно так же, как и сообщения в оконном приложении. При наступлении того или иного события оно становится в очередь - буфер ввода. Затем консольное приложение поочередно извлекает события из этого буфера. Делается это все при помощи функции ReadConsoleInput или PeekConsoleInput. В первом случае очередь обрабатывается синхронно и событие будет удалено из очереди сразу после его появления, а во втором случае данные обрабатываются асинхронно и событие останется на месте до последующего его извлечения из очереди.

Для начала нам понадобится пачка констант и структур, которые не описаны в FASM. Ничего, мы люди привычные, нас такими мелочами не напугать.
  1. KEY_EVENT                = 0x0001
  2. MOUSE_EVENT              = 0x0002
  3. WINDOW_BUFFER_SIZE_EVENT = 0x0004
  4. MENU_EVENT               = 0x0008
  5. FOCUS_EVENT              = 0x0010
  6.  
  7. struct COORD
  8.         X dw ?
  9.         Y dw ?
  10. ends
  11.  
  12. struct KEY_EVENT_RECORD
  13.         bKeyDown          dd ?
  14.         wRepeatCount      dw ?
  15.         wVirtualKeyCode   dw ?
  16.         wVirtualScanCode  dw ?
  17.         union
  18.           UnicodeChar     dw ?
  19.           AsciiChar       db ?
  20.         ends
  21.         dwControlKeyState dd ?
  22. ends
  23.  
  24. struct MOUSE_EVENT_RECORD
  25.         dwMousePosition   COORD
  26.         dwButtonState     dd ?
  27.         dwControlKeyState dd ?
  28.         dwEventFlags      dd ?
  29. ends
  30.  
  31. struct WINDOW_BUFFER_SIZE_RECORD
  32.         dwSize COORD
  33. ends
  34.  
  35. struct MENU_EVENT_RECORD
  36.         dwCommandId dd ?
  37. ends
  38.  
  39. struct FOCUS_EVENT_RECORD
  40.         bSetFocus dd ?
  41. ends
  42.  
  43. struct INPUT_RECORD
  44.         EventType rw 2
  45.         union
  46.           KeyEvent              KEY_EVENT_RECORD
  47.           MouseEvent            MOUSE_EVENT_RECORD
  48.           WindowBufferSizeEvent WINDOW_BUFFER_SIZE_RECORD
  49.           MenuEvent             MENU_EVENT_RECORD
  50.           FocusEvent            FOCUS_EVENT_RECORD
  51.         ends
  52. ends
Данные о событии извлекаются в структуру INPUT_RECORD. Обратите внимание, что поля структуры выровнены до DWORD. Первое ее поле EventType определяет тип события. В зависимости от его значения остальные поля структуры будут принимать то или иное значение. Цикл обработки событий в синхронном режиме будет выглядеть примерно так:
  1.         ; Получить хэндл стандартного ввода stdin
  2.         invoke  GetStdHandle,STD_INPUT_HANDLE
  3.         mov     ebx,eax
  4. @@:
  5.         ; Прочитать одно событие из очереди
  6.         invoke  ReadConsoleInput,ebx,input,1,tmp
  7.  
  8.         ; Обработка в зависимости от типа события
  9.         cmp     word [input.EventType],KEY_EVENT
  10.         je      loc_key_event
  11.         cmp     word [input.EventType],MOUSE_EVENT
  12.         je      loc_mouse_event
  13.         cmp     word [input.EventType],WINDOW_BUFFER_SIZE_EVENT
  14.         je      loc_buffer_event
  15.         cmp     word [input.EventType],MENU_EVENT
  16.         je      loc_menu_event
  17.         cmp     word [input.EventType],FOCUS_EVENT
  18.         je      loc_focus_event
  19.  
  20.         jmp     @b
Асинхронная обработка практически такая же, только в ней первым действием через PeekConsoleInput определяется наличие события в буфере, затем нужные события обрабатываются, а все прочие просто извлекаются из очереди. Между этим в цикле обработки программа выполняет заложенные в нее действия. Теперь давайте разберем события более подробно.

Первое событие, самое простое, это 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.

В приложении пример консольной программы с исходным текстом, которая обрабатывает все поступающие события.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Console.Events.Demo.zip (2,515 bytes)


Поделиться ссылкой ВКонтакте Поделиться ссылкой на Facebook Поделиться ссылкой на LiveJournal Поделиться ссылкой в Мой Круг Добавить в Мой мир Добавить на ЛиРу (Liveinternet) Добавить в закладки Memori Добавить в закладки Google
Просмотров: 550 | Комментариев: 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, но происходили всякие странности.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2022
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.06 сек. / MySQL: 2 (0.0039 сек.) / Память: 4.75 Mb
Наверх