Чтение скэн-кодов клавиш — это самый низкий уровень программного опроса клавиатуры. Каждая нажатая клавиша выдает свой {505} уникальный скэн-код, который зависит только от ее местоположения на клавиатуре и никак не зависит от того, что под ней понимается. Так, арифметические знаки на алфавитной и цифровой клавиатурах имеют различные скэн-коды. Скэн-коды полностью совместимой с IBM PC ПЭВМ приведены в табл. 21. 2.
Таблица 21. 2
Клавиша | Скэн-код | Клавиша | Скэн-код | ||
(16) | (10) | (16) | (10) | ||
Esc | $01 | Z | $2C | ||
!1 | $02 | X | $2D | ||
@2 | $03 | C | $2E | ||
#3 | $04 | V | $2F | ||
$4 | $05 | B | $30 | ||
%5 | $06 | N | $31 | ||
^6 | $07 | M | $32 | ||
&7 | $08 | <, | $33 | ||
*8 | $09 | >. | $34 | ||
(9 | $0A | ?/ | |||
)0 | $0B | Shift(правый) | $36 | ||
_- | $0C | PrintScreen | $37 | ||
+= | $0D | Alt | $38 | ||
BackSpace | $0E | Пробел | $39 | ||
TAB | $0F | CapsLock | $3A | ||
Q | $10 | F1 | $3B | ||
W | $11 | F2 | $3C | ||
E | $12 | F3 | $3D | ||
R | $13 | F4 | $3E | ||
T | $14 | F5 | $3F | ||
Y | $15 | F6 | $40 | ||
U | $16 | F7 | $41 | ||
I | $17 | F8 | $42 | ||
O | $18 | F9 | $43 | ||
P | $19 | F10 | $44 | ||
{[ | $1A | NumLock | $45 | ||
}] | $1B | ScrollLock | $46 | ||
Enter | $1C | 7 Home | $47 | ||
Ctrl | $1D | 8 Вверх | $48 | 72 {506} | |
A | $1E | 9 PgUp | $49 | ||
S | $1F | Серый - | $4A | ||
D | $20 | 4 Влево | $4B | ||
F | $21 | $4C | |||
G | $22 | 6 Вправо | $4D | ||
H | $23 | Серый + | $4E | ||
J | $24 | 1 End | $4F | ||
K | $25 | 2 Вниз | $50 | ||
L | $26 | 3 PgDn | $51 | ||
:; | $27 | 0 Ins | $52 | ||
“ ‘ | $28 | . Del | $53 | ||
~` | $29 | F11 | $D9 | ||
Shift (левый) | $2A | F12 | $DA | ||
|\ | $2B |
Проверить правильность этой таблицы на любой другой ПЭВМ (с MS-DOS и Турбо Паскалем, конечно) можно при помощи программы опроса скэн-кода нажатой клавиши (рис. 21. 10).
|
USES CRT, DOS; VAR Ch, ExtCh: Char; {символы с клавиатуры } Scan, LastScan: Byte; {скэн-коды клавиш } OldInt09H: Pointer; {адрес старого вектора } {$F+} PROCEDURE IntProc; INTERRUPT; {перехват прерывания } BEGIN Scan:=Port[$60]; {чтение скэн-кода } Inline($FF/$1E/>OldInt09H); {возврат прерывания } END; {$F-} BEGIN GetIntVec($09, OldInt09H); {взятие адреса прерывания } SetIntVec($09, @IntProc); {подстановка перехватчика } |
Рис. 21.10 {507}
Scan:= 128; { стартовое значение Scan } WriteLn('Нажимайте что угодно.', 'Esc - выход из программы.'); repeat { Основной цикл опроса: } Ch:= #0; ExtCh:= #0; { сброс значений до опроса } repeat until Scan<128; { ожидание любого нажатия } Write(' Скэн-код=', Scan:3); if KeyPressed { Клавиша - не регистровая? } then Ch:=ReadKey; { да, ее код запоминается } if KeyPressed and (Ch=#0) {Клавиша - функциональная? } then ExtCh:= ReadKey; { да, запоминается расш. код } { вывод итогов опроса: } Write ('Символ"', Ch + ExtCh); GotoXY(30, WhereY); { нейтрализация кода 13 } WriteLn((‘” Код=’, Ord(Ch):3, ‘ Расш. код=', Ord(ExtCh)); LastScan:= Scan; { нужен последний скэн-код } Scan:= 128; { снова стартовое значение } until LastScan=1; { условие конца — нажата Esc } SetIntVec($09, OldInt09H); { восстановление прерывания } ReadLn { пауза до нажатия ввода } END. |
Рис. 21.10 (окончание)
Программа перехватывает низкоуровневое прерывание номер 9 и запоминает содержимое порта, через который передаются коды нажатых клавиш, в глобальной переменной Scan. После этого анализируется, внесло ли нажатие что-либо в буфер ввода. Если да, то выводится информация о нажатой клавише. Используя перехват прерывания, кaк это сделано в примере, можно проводить и более сложный анализ (рис. 21.11). После каждого нажатия любой клавиши перехватчик записывает в Scan скэн-код. Но здесь есть особенность: при нажатии клавиши вырабатывается истинный скэн-код, а при отпускании — увеличенный на 128. Поэтому в примере ожидание нажатия возложено на цикл
|
repeat until Scan < 128;
который размыкается только при нажатии клавиши (Scan содержит число, меньшее 128) и не реагирует на отпускание их.
На рис. 21.11 рассматривается каркас Паскаль-программы, позволяющей «отлавливать» одновременное нажатие нескольких регистровых клавиш вместе с алфавитной клавишей или без нее.
Аналогичным методом можно определять факты нажатия практически всех распознаваемых ПЭВМ комбинаций клавиш. Надо {508}
{КАРКАС ПРОГРАММЫ, РЕАГИРУЮЩЕЙ НА СПЕЦИАЛЬНЫЕ КОМБИНАЦИИ} { НАЖАТИЙ НА КЛАВИАТУРЕ } USES CRT, DOS; {Необходим модуль DOS. CRT нужен для примера. } VAR { глобальные переменные программы: } OldInt09H: Pointer; { адрес прерывания 09 } CtrlRShiftD: Boolean; { флаг нажатия комбинации } CONST { Константы специальной комбинации клавиш: } HotKey = $20; { скэн-код клавиши [D]; } KlavReg = 1+4; { значение в байте $0:$0417 при нажатии } { левого регистра Shift вместе с Ctrl: } { 1 - нажата правая клавиша Shift (бит 0); } { 4 - Нажато Ctrl+Shift (бит номер 2). } {$F+} PROCEDURE IntProc; INTERRUPT; {перехват прерывания 09Н } VAR M: Byte absolute $000:$417; { байт состояния регистров } C,L,H: Byte; { значение скэн-кода и др. } BEGIN С:= Port[$60]; { чтение скэн-кода } {Устанавливаем флаг нажатия, анализируя скэн-код и состояние байта нажатия клавиш регистров: } CtrlRShiftD:=(C=HotKey) and ((M and KlavReg)=KlavReg); if CtrlRShiftD then begin { Специальная обработка } L:=Port[$61]; H:=L; { портов, если нажата } L:=L or $80; { требуемая комбинация } Port[$61]:=L; Port[$61]:=H; Port[$20]:=$20 end else { Иначе пусть выполняется } inline($FF/$1E/>OldInt09H); { настоящее прерывание } END; {$F-} VAR 1: Word; {=== ОСНОВНАЯ ЧАСТЬ ПРИМЕРА ==== } LABEL Stop; BEGIN CtrlRShiftD:=False; {обязательное стартовое значение! } ClrScr: Write(‘Нажатие Ctrl+Пр.Shift+D приостановит цикл. '); GetIntVec($09, OldInt09H); { сохраняем старый вектор } SetIntVec($09, @IntProc); { подставляем новый вектор } {...} |
Рис. 21.11 {509}
|
for i:=1 to 30000 do begin { рабочий цикл } GotoXY(5,5); Write(1:6, ' из 30000.'); { Программа должна периодически проверять, было ли } { одновременное нажатие Ctrl+Правая Shift + D? } if CtrlRShiftD then { Да. Обработка нажатия горячего ключа } begin Write('Комбинация нажата...Закончить(Y/N)? '); if UpCase(ReadKey)='Y' then Goto Stop; ClrScr; {... } end else { Нет. Дальнейшие действия программы } begin {...} end; {if} end; {for i} { конец рабочего цикла } {... } Stop: { метка выхода из цикла } SetIntVec{ $09, OldInt09H); { вернем исходный вектор } ClrScr END. |
Рис. 21.11 (окончание)
только устанавливать соответствующие значения скэн-кодов «горячих» клавиш в HotKey и числа для побитового сравнения с байтами $417/$418 в KlavReg.
Недостатком примера является задержка между нажатием комбинации клавиш и началом анализа. Избежать ее можно, если вставить вызовы процедур реакции на «горячие» комбинации прямо в Interrupt-процедуру. Однако не советуем спешить это сделать. При такой организации программы необходимо принимать специальные меры по предохранению содержимого регистров процессора, блокированию прочих прерываний, их согласованию и т.п., о чем можно всегда прочитать в любой толстой книге по системному программированию на языке ассемблера. В большинстве же программ может пригодиться и предложенный способ.