Чтение скэн-кодов клавиш — это самый низкий уровень программного опроса клавиатуры. Каждая нажатая клавиша выдает свой {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).
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-процедуру. Однако не советуем спешить это сделать. При такой организации программы необходимо принимать специальные меры по предохранению содержимого регистров процессора, блокированию прочих прерываний, их согласованию и т.п., о чем можно всегда прочитать в любой толстой книге по системному программированию на языке ассемблера. В большинстве же программ может пригодиться и предложенный способ.