Главная » Delphi » Некоторые особенности работы с клавиатурой. Как все это устроено

0

Чтобы эффективно использовать клавиатуру, хорошо бы понимать, как ведется обработка нажатия клавиш в системе. С клавиатурой связано как минимум три разновидности кодов: символьные коды, виртуальные коды и скан-коды, и не всем понятна разница между ними. Попробуем внести ясность в этот запутанный вопрос.

Самая большая путаница со скан-кодами — для начала, их также существует две разновидности. Есть "настоящие" скан-коды — это то, что получает система прямо из клавиатуры. Прочитать их можно, если напрямую обратиться в порт 60h, но мы здесь не будем копать так глубоко, а посмотрим, что с ними происходит дальше. А дальше они попадают в цепкие лапы BIOS, которая через прерывание INT9h их интерпретирует: например, вызывает прерывания при нажатии клавиш <Ctrl>+<Alt>+<Del>, <Ctrl>+<Break> и т. п., а также справляется с текущей таблицей кодов символов (когда надо, с учетом нажатых клавиш-модификаторов <Alt>, <Ctrl>, <Shift> или <Caps Lock>) и помещает все это в буфер клавиатуры. Буфер находится в начале памяти по адресу 0040h:00lEh и может занимать максимум 32 байта, что соответствует I6 нажатым клавишам. Если программа-обработчик не успевает очищать буфер по мере того, как клавиши нажимаются, то все сверх 16 нажатий пропадает (а компьютер начинает жалобно пищать динамиком). Читать буфер можно напрямую (указатели на "голову" и "хвост" находятся по адресам 0040h:001 Ah и 0040h:001Ch, если они равны, то буфер пуст), но смысла в этом особенного нет — позже мы увидим, как это можно делать "законным" путем.

В буфере клавиатуры отводится два байта на каждый символ — один из них представляет собой скан-код (но уже, как вы поняли, не совсем тот, что поступал в порт 60h), второй — символьный код (для управляющих клавиш он равен нулю). Символьный код зависит от нажатых одновременно с данной клавишей клавиш-модификаторов, а его экранная интерпретация — также от текущей кодовой страницы. Именно страницы (т. е. таблицы однобайтных кодов), которая загружается на уровне BIOS, а не раскладки клавиатуры. Это концептуально разные вещи — в DOS (н основанных на ней Win9x) кодовая страница загружается обычно при загрузке системы, в зависимости от локализации. Может даже не загружаться вообще, локализация в DOS часто вообще не требовалась — переключение кодовой страницы производила резидентная программа-переключатель раскладки. Подчеркнем еще раз: переключение раскладки и переключение кодовой страницы — в DOS два разных действия. Есть DOS-программы, которые переключают только страницу (т. е. фактически экранные шрифты — известная программа Evafont Петра Квите- ка позволяет автоматически создавать подобные утилиты), не переключая раскладку, теоретически можно сделать и наоборот, хотя вряд ли кому это нужно. Вы спокойно можете разработать свою собственную кодовую таблицу (что равносильно разработке нового экранного шрифта) и "подсунуть" ее системе — автор этих строк так заставлял свои DOS-программы отображать надстрочные и подстрочные индексы.

В Windows же все это сделано настолько запутанно, что дальше некуда, причем еще и организовано несколько различным образом в 9х и в NT (ХР). Там было введено понятие текущего "языка" (кроме отдельно "раскладки" и "кодовой страницы") — изначально это просто означало совокупность кодовой страницы и собственно раскладки, что логично. Но с переходом к Unicode понятие "языка" фактически стало жить самостоятельной жизнью, а к чему это приводит — мы подробнее обсудим в главе 8. Там же мы более подробно поговорим и о различных кириллических кодировках.

Операционная система (DOS или Windows) может использовать поверх всего этого и другие таблицы интерпретации, параллельно с кодовой страницей. Под "другими таблицами" имеются в виду не только национальные таблицы символов, но и реализация, например, такой функции, как ввод символа по его номеру с нажатой клавишей <Alt> (напомню, работает только левый <Alt>, только с цифровой клавиатурой, и только при включенном <Num Lock>), или популярного в DOS-программах представления управляющих кодов (символьные коды 01—31), которые невозможно набрать на клавиатуре прямо, через нажатие <Ctrl>+<буква_алфавита>. Скажем, действие, аналогичное нажатию <Enter>, т. е. ввод кодов 10 + 13, при этом выглядит, как последовательность нажатий <Ctr1>+<J>, <Ctrl>+<M>[1].

Рис. 6.1. Оригинальная В4-кнопочная клавиатура IBM PC

По всем этим причинам до оригинальных скан-кодов добраться непросто, но, по счастью, скан-коды в чистом виде на практике могут потребоваться нечасто, а для точной идентификации клавиш хватит и того, что предоставляет система. Скан-коды присваивались разработанной одновременно с IBM PC’ 84-кнопочной клавиатуре подряд (слеаа-направо, сверху-вниз), поэтому, например, клавише <Esc> присвоен скан-код 01, а следующий код (02) присвоен не клавише <Fl>, как бы следовало ожидать, глядя на современную стандартную клавиатуру, а клавише <1>, которая на той клавиатуре следовала сразу за <Esc> (рис. 6.1). Еще большая путаница с системными клавишами на дополнительной клавиатуре (слева от цифровой), которые на 101/104-кно- почной клавиатуре дублируют функции цифровой клавиатуры — это <Ноше>, <End>, клавиши управления курсором и т. п. Дублирующими являются и клавиши около цифровой клавиатуры: <точка>, <умножение>, <riлюс> и т. л. Общий принцип назначения скан-кодов состоит в следующем: основные скан-коды (они 7-битные, наличие 8-го бита означает код отпускания этой же клавиши) назначались тем клавишам, которые использовались в 84-кно- почном варианте, а вновь введенным дублирующим присваивалось то же самое значение с добавлением второго (старшего) байта, равного $Е0 (это не относится к таким дублирующим клавишам, как, к примеру, правый <Shift> — она изначально была на 84-кнопочной клавиатуре). Однако, как вы увидите из таблицы, помещенной в приложении2, фактически доступные скан-коды дополнительной клавиатуры повторяют аналогичные скан-коды клавиатуры цифровой, причем при выключенном <Num Lock> идентичны также и виртуальные коды, а вот при включенном виртуальный код клавиш на цифровой клавиатуре особый и отличается от цифр на основной клавиатуре. Особый код (и виртуальный, и скан-код) и у клавиш <+>, <-> и т. п., расположенных рядом с цифровой клавиатурой, потому для них отдельно и определены константы — идентификаторы виртуальных кодов.

Заметки на полях

Эта картина наблюдается и в DOS, и в Windows, т. е. обработка дополнительных клавиш ведется еще на уровне BIOS. В общем, сделано все, чтобы запутать честного программиста, который наивно предполагает, что все должно происходить согпасно здравому смыслу. Если такой программист хочет найти погику в этой картине, то ему придется вернуться к истокам. На самом депв тут все прозрачно: упорная неразпичимость левых-прааых клавиш-модификаторов была, очевидно, заложена еще при создании первых модепей 101-кнопочной клавиатуры (а зачем, простите, их разпичать, если они задумывались именно как дублирующие?), и тогда же опрометчиво была заложена в BIOS, а дальше это требоввлось соблюдать просто для совместимости. Аналогично произошло и с системными клавишами на допопнитепьной клавиатуре. В то же время от- личвющийся виртуальный и сквн-код цифровых клавиш на цифровой и основной клавиатурах (а также левого и правого <Shift>) был заложен а систему с самого начала (еще во времена IBM PC, когда клавиатуры были только 84-кнопочныв), а тогда системные программисты были другими, и не стали решать за пользователей, как им интерпретировать ту или иную клааишу. Вот все наслоения этих эпох мы и вынуждены теперь расхлебывать. С другой стороны, нельзя не отметить, что неразличимость кодов нв уровне ОС во многом оправдана, например, можно спокойно проектировать любую удобную конфигурацию клавиатуры (квк, к примеру, в случае ноутбуков), и все будет совместимо без лишних сложностей,

Остановимся теперь подробнее на виртуальных кодах. Виртуальные коды — это то, что использует система для идентификации клавиш. С соответствующими предопределенными константами, действующими в Delphi, можно ознакомиться в файле Windows.pas (..\Source\Rtl\Win\Windows.pas). Сама Windows определяет в том числе константы для буквенно-цифровых клавиш, и в приложении 2 они приведены, но в файле Windows.pas их нет, т. к. виртуальные коды буквенно-цифровых клавиш совпадают с кодами соответствующих символов (см. приложение 3). В программах Delphi их надо определять через функцию ord или просто использовать цифровое значение кода символа. В таблице приложения 2 суммированы сведения о предопределенных константах, виртуальных и скан-кодах для всех клавиш обычной 104-кнопочной клавиатуры, полученные из различных источников и проверенные автором по методике, которая излагается далее. По этой таблице можно определить разницу, например, между vk Add и vk_OEM_pius (угадайте с первого раза, какая из констант относится к "плюсу" на цифровой клавиатуре, а какая — на основной?) — нигде больше этих сведений в полном объеме вы не найдете. Отметим, что сейчас в моде расширенные клавиатуры с дополнительными функциональными клавишами (<F13>…<F24>), с клавишами управления браузером, медиаплеером и т. п., и хотя многие виртуальные коды таких клавиш также стандартизированы в Windows, пользоваться ими в прикладных программах общего назначения по понятным причинам не рекомендуется — кто знает, на каком компьютере ваша программа будет исполняться?

Заметки на полях

В связи с этим рвсскажу одну историю, которвя врезалвсь мне в пвмять, как типичный пример бездумного и безответственного отношения программиста к саоим обязанностям. История, прввда, больше относится к мышам, а не клавишам, но это не принципиально. В одном крупном провинциальном научно- производственном центре была рвзработана уникапьнвя аппарвтурв для гидрографических исследований, а именно— для картографирования океанского дна. Это было еще во аременв системы DOS, в которой, как известно, мыши разных производителей требовали непременно "родных" дрвйверов, в противном случве отказывались рвботвть. Программист, который делал основную профвмму картографирования, решил, что будет очень здорово, если он возьмет и нвпишет собственный драйвер мыши конкретно для данного случвя. Причины, которые его подвигли на твкой шаг, остались неизвестными — то ли он просто не подозревал о существовании прерывания int33h, которое предоставляет "мышиный" интерфейс, то ли очень хотел поквзать, что лично без него в рейсе не обойдутся. В общем, на берегу программа отлично работала, потому что все мыши в упомянутом НПЦ были закуплены единовременно и централизованно и тем самым, естественно, оказались одного производителя. А в море работать ничего не стало — на корабле мыши были другой системы. В результате все же нашлась однв мышь, которая работала, ее главный гидрограф каждый раз во время работы втыкал в компьютер, а после уносил к себе в каюту и запирал в личном сейфе. И все же в какой-то момент в ней сломалась левая кнопка, и только усилиями вашего покорного спуги, который ее починил "на коленке", программа исследований не была сорвана. Хочу предостеречь читателей от подобных "приколов" — вместо того, чтобы удивляться вашему искусству, пользователи скорее всего будут вас проклинать на чем свет стоит.

Теперь о том, как самому определить виртуальный и скан-код клавиши. Прочитать то, что находится в буфере BIOS, можно из DOS простой программкой на обычном Pascal, вызывающей прерывание intl6h:

uses crt,dos; var

xah,xal:byte;

begin

asm

mov ah,lOh (функция чтения из клавиатуры)

int 16h

mov xah,ah

mov xal,al

end;

writeln(‘Symbol- ‘,xal,’ Scan= ‘,xah); end.

To есть после нажатия какой-нибудь клавиши в регистре ai окажется кол символа, а в ah — скан-код (при этом для управляющих клавиш в ai будет 0). Недостатком этого способа является то, что не распознается нажатие некоторых системных клавиш (типа <PrintScrn>), и, главное, клавиш-модификаторов (<AIt>, <Ctrl> или <Shift>). Но мы же живем в эпоху Windows! А там есть замечательная функция MapvirtualKey, которою мы и используем — через нее мы можем узнать скан-код клавиши по известному виртуальному коду (или наоборот). Создадим новый проект (на диске в папке Glava6\l ). поместим на форму компонент Memo, установим для формы параметр KeyPreview в True, и напишем следующий обработчик события onKeyDown:

prooedure TForml.FormKeyDown(Sender: TObject; var Key: Word;

Shift: TShiftState);

var

st:string;

xscan:integer; begin

xscan:=MapVirtualKey(Key, 0);

st: = " Scan = ‘+IntToStr (xscan) + ‘ Ч-hexb(byte(xscan) )+ ‘

Key=’+IntToStr(Key)+’ ‘+hexb(byte(Key)); Memol.Lines.Clear; Memol.Lines.Add(st); end;

Для удобства вывод производится сразу в двух представлениях: в десятичном и шестнадцатеричном, для этого используется описанный в приложении 1 модуль Ariphm, который следует объявить в интерфейсной части модуля Unitl, кроме того, в папке с проектом необходимо, разумеется, разместить файл Ariphm.pas.

В компоненте Memo при нажатии клавиш нам будут возвращаться виртуальные коды и скан-коды тех клавиш, которые мы нажимаем. Такая программа нам позволит отследить скан-код и виртуальный код любой клавиши (кроме опять же <PrintScrn>), и даже узнать скан-код, если мы вручную подставим в функцию MapvirtuaiKey вместо Key соответствующую константу из приложения 2. Впрочем, узнать разницу в скан-кодах между правым и левым <Ctrl> и <Alt> она не позволит— в среде Windows 9х этого и не обещают, но и в ХР все равно не получается. Подчеркнем, что не дает результатов и подстановка констант (типа vk RControi), а процедура Delphi onKeyDown об этих различиях уж тем более "не знает". В приложении 2 для таких констант приведены только "официальные" виртуальные коды (которые на практике все равно нигде не работают), а скан-коды указаны одинаковые, причем для этих клавиш работает и общий код с константами vk Controi (<Ctrl>) и vk Menu (<Alt>). Еще одна пара клавиш, которые трудно разделить— основная и "цифровая" клавиши <Enter>. Не получается также узнать разницу в скан- кодах между упомянутыми ранее системными клавишами (<Home>, <End> и т. п) на цифровой клавиатуре и на дополнительной — как уже говорилось, все дополнительные клавиши имеют "настоящий" (на уровне "железа") скан- код с дополнительным байтом $Е0 и "в лоб" их различить действительно нельзя, если только не писать соответствующий кусок кода на ассемблере с перехватом нажатия непосредственно в порту клавиатуры. Отметим, что клавиши левый-правый <Shifit>, в отличие от указанных, функция различает — по крайней мере в Windows ХР.

Но простой способ различить в Windows (любой версии) правый-левый <Ctrl> или <Alt>, дополнительный <Enter> от основного и вообще любую дополнительную клавишу с "настоящим" скан-кодом, содержащим второй байт $Е0, все же имеется, и он состоит в том, чтобы проанализировать LParam в системном сообщении wm KeyDown. 16—23 биты (третий байт) этого параметра содержит скан-код нажатой клавиши (указанный в таблице), а младший бит следующего байта (бит номер 24) определяет, какая из клавиш нажата: если этот бит равен I. то нажата расширенная (правая) клавиша, а если О — стандартная (левая):.

Если хотите, можно считать, что скан-код расширенной клавиши на величину 256 больше, чем основной, так. если основной скан-код <C4rl> равен $ ID, то код правой клавиши <Clrl> будет равен $11D. Отсюда понятно, почему функция MapVirtuaiKey выдает одни и те же коды для левой и правой клавиши: она анализирует только один третий байт, опуская следующий по старшинству бит. Подчеркнем, что величина S1ID ни в коем случае не может рассматриваться как именно значение скан-кода и служит лишь для наглядности представления.

Эти сведения мы используем в следующей главе для того, чтобы создать резидентный переключатель раскладки клавиатуры — специальную программу для замены стандартных клавиатурных комбинаций Windows с возможностью выбора клавиши. А сейчас попробуем создать механизм, позволяющий отслеживать указанный ранее параметр LParam в системном сообщении wm KeyDovm. И тут уж нам не обойтись без использования механизма ловушек — Hook, который позволяет перехватывать любые сообщения (мы о нем упоминали в предыдущей главе).

Источник: Ревнч Ю. В.  Нестандартные приемы программирования на Delphi. — СПб.: БХВ-Петербург, 2005. — 560 е.: ил.

По теме:

  • Комментарии