Страница Ивана Рощина > Статьи >

© Иван Рощин, Москва

ZXNet: 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW: http://www.ivr.da.ru

Исправление набранного в неправильном режиме текста
с помощью резидентной программы

Радиомир. Ваш компьютер» 1/2003)
Дата последнего редактирования: 13.04.2003.

Введение
Постановка задачи
Начнём с простого…
Взаимодействие резидента с программой
Листаем справочник: прерывание BIOS 10h
Листаем справочник: прерывание BIOS 16h
Текст резидентной программы
Комментарии
«Бонусы» резидента
Литература

Введение

Когда приходится набирать текст, содержащий и русские, и латинские буквы, иногда забываешь переключить режим Рус/Lat, и набираемый текст становится бессмысленным. Бывает, что так же забываешь переключить режим Caps Lock, и вместо прописных букв вводятся строчные и наоборот. Приходится стирать неправильно набранный участок текста и вводить его заново.

Я предлагаю способ решения этой проблемы для MS-DOS с помощью резидентной программы. В этой статье подробно рассмотрен процесс её разработки.

Постановка задачи

Сначала определимся с терминами. Разрабатываемую программу я буду в дальнейшем называть «резидент», а термин «программа» буду использовать для обозначения программы, в которой вы набираете и исправляете текст. Клавиша (или комбинация с Shift, Alt, Ctrl), предназначенная для активизации резидента, будет в дальнейшем называться «горячей клавишей».

Итак, что нам надо? Пусть мы забыли переключить режим Рус/Lat, и в результате неправильно набрали определённый участок текста. Когда ошибка замечена, хотелось бы нажать определённую «горячую клавишу» (пусть это будет, скажем, F12), после чего неправильно набранный участок текста окажется исправленным. Точно так же, когда мы забыли включить (или наоборот, выключить) режим Caps Lock, хотелось бы нажать другую «горячую клавишу» (скажем, F11) для исправления неправильно набранного текста.

Возможно, в такой постановке решить задачу и не удастся, придётся идти на какие-либо компромиссы. Но это выяснится лишь в процессе разработки.

Начнём с простого…

Пусть у нас имеется строка символов, и известно, что при её наборе был неправильно установлен режим Caps Lock. Как исправить такую строку?

Если режим Caps Lock выключен, то при нажатии клавиш без Shift вводятся строчные буквы, а с Shift — прописные. Если же этот режим включён, то наоборот: без Shift вводятся прописные буквы, а с Shift — строчные. Таким образом, для исправления строки достаточно заменить в ней прописные буквы на строчные и наоборот.

Идём дальше. Теперь пусть у нас имеется строка символов, и известно, что при её наборе был ошибочно включён русский (или латинский) режим. Как исправить такую строку?

При нажатии одних и тех же клавиш в русском и латинском режиме вводятся различные символы. Их соответствие приведено в табл. 1 (для русификатора KeyRus с раскладкой клавиатуры «по умолчанию»).

Табл. 1
N строки таблицы Режим Caps Lock Режим работы клавиатуры Вводимые символы
1 выключен РУС абвгдежзийклмнопрстуфхцчшщъыьэюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"/':,.;
2 выключен LAT f,dult;pbqrkvyjghcnea[wxio]sm'.zF<DULT:PBQRKVYJGHCNEA{WXIO}SM">Z@#$%^&*
3 включён РУС АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя"/':,.;
4 включён LAT F,DULT;PBQRKVYJGHCNEA[WXIO]SM'.Zf<dult:pbqrkvyjghcnea{wxio}sm">z@#$%^&*

Отсюда непосредственно следует алгоритм исправления строки. Возможны четыре различных случая.

1. Если при наборе строки был ошибочно включён русский режим, и режим Caps Lock был выключен, то отыскиваем в строке все символы, имеющиеся в строке 1 табл. 1, и заменяем их на соответствующие символы из строки 2 табл. 1.

2. Если был ошибочно включён латинский режим, и режим Caps Lock был выключен, то отыскиваем в строке все символы, имеющиеся в строке 2 табл. 1, и заменяем их на соответствующие символы из строки 1 табл. 1.

3. Если был ошибочно включён русский режим, и режим Caps Lock был включён, то отыскиваем в строке все символы, имеющиеся в строке 3 табл. 1, и заменяем их на соответствующие символы из строки 4 табл. 1.

4. Если был ошибочно включён латинский режим, и режим Caps Lock был включён, то отыскиваем в строке все символы, имеющиеся в строке 4 табл. 1, и заменяем их на соответствующие символы из строки 3 табл. 1.

Как видим, для исправления строки, набранной в неправильном режиме Рус/Lat, надо ещё знать, включён или выключен был режим Caps Lock при её вводе. Если считать, что с момента ввода строки и до её исправления состояние Caps Lock не изменилось, то достаточно узнать текущее состояние Caps Lock. Как это сделать, мы рассмотрим позднее.

Можно заметить, что строка 1 в табл. 1 отличается от строки 3 только тем, что прописные буквы заменены на строчные и наоборот. То же самое относится к строкам 2 и 4. Тогда выгоднее для экономии места хранить не все четыре строки, а, например, только первую и вторую, а третью и четвёртую получать из них, меняя регистр букв на противоположный. Процедура для изменения регистра букв всё равно понадобится для исправления текста, набранного при неправильном состоянии Caps Lock.

Взаимодействие резидента с программой

Набираемый в программе текст хранится в некоторой области памяти. Где именно — «знает» только сама программа. И вот пользователь нажимает «горячую клавишу» для исправления текста. Как резидент «узнает», какой именно текст ему надо исправить?

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

Заметим, что набранный текст также отображается на экране! То есть он находится в видеопамяти. И непосредственно после неправильно набранной строки текста стоит курсор! Тогда можно, обращаясь к функциям прерывания BIOS 10h, определить координаты курсора, а затем узнать, какие символы находятся слева от курсора, — это и будет исправляемый текст.

Но надо ещё определить длину исправляемого участка текста. Не обязательно это будет вся расположенная слева от курсора часть строки. Может быть, пользователь набрал в неправильном режиме только последние несколько символов. Как быть? Не запрашивать же у пользователя длину неправильно введённой строки?

Определять длину автоматически? А как? Это вопрос непростой. Можно предложить разные способы, но ни один не может обеспечить 100% надёжности. Поэтому я решил, что одно нажатие «горячей клавиши» будет исправлять только один символ текста, находящийся слева от курсора, после чего курсор передвинется на символ влево. (Если какой-то символ, например, цифру, исправлять не нужно, то курсор просто передвинется влево.) Тогда, последовательно нажимая «горячую клавишу», пользователь сам остановится, когда весь неправильно набранный текст будет исправлен.

При нажатии «горячей клавиши» для исправления текста, набранного в неправильном режиме Рус/Lat, резидент должен ещё определить, какой режим был ошибочно включён при наборе текста: русский или латинский. Как это сделать? Если считать, что с момента ввода строки и до её исправления режим Рус/Lat не переключался, то достаточно знать его текущее состояние. Но сведения о том, какой режим в данный момент включён, хранятся где-то внутри драйвера-русификатора, и где именно — «знает» только он сам. Можно было бы запросить эту информацию у пользователя, но хотелось бы обойтись без этого…

Тогда поступим так: будем просматривать строку от позиции курсора до левого края экрана, пока не попадётся буква. Если эта буква — русская, значит, при наборе текста был ошибочно включён русский режим; если латинская — соответственно, латинский режим. Если в строке не оказалось буквы, то считаем, что ошибочно включённым был тот же режим, который был определён при нажатии «горячей клавиши» в прошлый раз.

Обратим внимание на следующее. Если пользователь исправляет строку текста, набранную в неправильном режиме Рус/Lat, нажимая для этого нужное число раз соответствующую «горячую клавишу», то определять, какой режим был ошибочно включён при наборе строки, надо только при первом нажатии «горячей клавиши». Дело в том, что если остались неисправленными только небуквенные символы, а слева остался участок строки, не нуждающийся в исправлении и с другими буквами, то определение режима произойдёт неверно.

Например, на экране такая строка:

Это первый участок "nj dnjhjq exfcnjr
                                     ^курсор

При первом нажатии «горячей клавиши» первый же символ слева от курсора оказывается латинской буквой. Значит, при наборе был ошибочно включён латинский режим. Последовательные нажатия «горячей клавиши» приводят к исправлению строки:

Это первый участок "то второй участок       
                    ^курсор

Осталось исправить только первый символ строки. Если сейчас попробовать снова определить, какой режим был неправильно включён при наборе текста, то первой встреченной буквой окажется русская, и будет сделан вывод, что был неправильно включён русский режим. Это неверно, и дальнейшее исправление будет неправильным:

Это первый участок @то второй участок       
                   ^курсор

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

Как это сделать? А как передаёт указания по исправлению текста пользователь? С помощью нажатия клавиш, конечно. Как вы стали бы исправлять неправильно набранный символ текста? Сначала нажали бы клавишу BackSpace для стирания этого символа, а затем ввели бы правильный.

Значит, резидент должен «подсунуть» программе информацию о нажимаемых клавишах, как будто их нажимает пользователь. А как это сделать?

В подавляющем большинстве случаев программы используют для опроса клавиатуры прерывание BIOS 16h (может быть, и не обращаясь к нему непосредственно). Следовательно, чтобы «подсовывать» программе нужные коды клавиш, нужно перехватить обработку этого прерывания. Помимо этого, перехват прерывания BIOS 16h нужен ещё и для того, чтобы отслеживать факт нажатия пользователем «горячих клавиш».

Листаем справочник: прерывание BIOS 10h

Рассмотрим функции прерывания BIOS 10h, которые понадобятся нам для определения положения курсора и определения кодов символов, отображённых на экране.

При вызове функции её номер указывается в регистре AH. В [3] упоминается, что после окончания работы вызванной функции содержимое регистров BX, CX, DX, SI и BP остаётся неизменённым (очевидно, если эти регистры не используются для возвращения результата).

Для определения координат курсора служит функция 3. Перед её вызовом в регистре BH надо указать номер текущей страницы видеопамяти. После вызова в DH будет номер строки, а в DL — номер столбца, где расположен курсор. В регистре CX возвращается размер курсора (он нам не понадобится, но знать, что содержимое CX изменится после вызова функции, необходимо).

Для использования этой функции надо знать номер текущей страницы видеопамяти. Чтобы его узнать, используется функция F. После её вызова нужный нам номер страницы будет в регистре BH.

Теперь о том, как определить код символа, если известны его координаты на экране. Для этого можно использовать функцию 8. Перед вызовом в регистре BH надо указать номер текущей страницы видеопамяти. После вызова код символа, находящегося в позиции курсора, будет в регистре AL.

Понятно, что перед вызовом этой функции надо установить курсор в нужную позицию, а после вызова — вернуть курсор на прежнее место. Для задания нового положения курсора служит функция 2. Перед её вызовом в регистре BH надо указать номер текущей страницы видеопамяти, а в DX — новые координаты курсора (DH — номер строки, DL — номер столбца).

Как видим, использование одной функции влечёт за собой необходимость использования другой функции, и так далее. Хотя было бы достаточно всего двух функций — «определить координаты курсора в текущей странице видеопамяти» и «определить код символа с заданными координатами в текущей странице видеопамяти».

Листаем справочник: прерывание BIOS 16h

Функции этого прерывания предназначены для работы с клавиатурой. Даже если программа пользуется более высокоуровневыми функциями DOS, они в итоге всё равно обращаются к этому прерыванию.

Вообще работа с клавиатурой организована так: при нажатии или отпускании какой-либо клавиши происходит аппаратное прерывание INT 9. Обработчик этого прерывания заносит скан-коды и ASCII-коды нажимаемых клавиш (далее пару [скан-код, ASCII-код] я буду называть просто кодом клавиши) в специальный буфер (далее — клавиатурный буфер). А когда программа вызывает функцию прерывания 16h для получения кодов нажатых клавиш, обработчик этой функции обращается к клавиатурному буферу.

Здесь мы рассмотрим те из функций прерывания BIOS 16h, которые нам придётся использовать и/или перехватывать в резиденте.

При вызове функции её номер указывается в регистре AH.

Функция 0 — чтение кода клавиши. Берёт из клавиатурного буфера значение скан-кода и ASCII-кода клавиши и возвращает их соответственно в AH и AL. Если в клавиатурном буфере пусто, функция будет ждать, пока там что-нибудь не появится, т.е. пока пользователь не нажмёт клавишу.

Функция 1 — чтение кода клавиши без удаления его из клавиатурного буфера. Если буфер не пуст, флаг Z сбрасывается, а прочитанные скан-код и ASCII-код возвращаются соответственно в AH и AL. Если в клавиатурном буфере пусто, функция не ждёт нажатия клавиши, а при выходе устанавливает флаг Z.

Функция 2 — опрос состояния служебных клавиш. Результат возвращается в регистре AL. Эта функция понадобится нам, чтобы узнать, включён или выключен режим Caps Lock. Если 6-й бит AL равен 0, то режим выключен; если 1 — включён.

Функции 10h и 11h — то же самое, что функции 0 и 1, но с их помощью можно также получить коды дополнительных клавиш, имеющихся на 101-клавишной клавиатуре, и коды некоторых комбинаций клавиш (Alt+Enter и др.). Эти функции, как сказано в [1], присутствуют в BIOS, выпущенном после 15 декабря 1985 года.

Функции 0, 1, 10h, 11h должны быть перехвачены резидентом, чтобы «подсовывать» программе нужные коды клавиш. За одно обращение к функции можно передать программе только один код клавиши, а для исправления неправильно введённого символа надо передать несколько кодов. Следовательно, надо организовать в резиденте специальный буфер (далее — внутренний буфер), помещать оставшиеся коды туда, а при следующих обращениях программы к функциям INT 16h для опроса клавиатуры — возвращать коды из этого буфера.

Как уже упоминалось выше, эти функции должны возвращать в AH скан-код клавиши, а в AL — ASCII-код. Но, как показала практика (и изучение скан-кодов, возвращаемых русификатором KeyRus), если функция возвращает ASCII-код русской или латинской буквы, то скан-код при этом может быть нулевым. То есть не надо хранить в резиденте таблицу соответствия букв и скан-кодов клавиш, с помощью которых эти буквы вводятся, и не нужна подпрограмма получения скан-кода по ASCII-коду буквы.

Текст резидентной программы

KEY_1      equ     8600h ;Скан-код и ASCII-код "горячей клавиши" RUS <-> LAT.
KEY_2      equ     8500h ;Скан-код и ASCII-код "горячей клавиши" txt <-> TXT.

codesg     segment
           assume  cs:codesg
           assume  ds:codesg

           org     100h

begin:     jmp     init       ;Переход к инициализации

;Определение переменных:

buf_inside dw      ?,?,?      ;Внутренний буфер.
buf_len    db      0          ;Текущее количество элементов в нём.
page_num   db      ?          ;Номер текущей страницы видеопамяти.
next_corr  db      0          ;Прошлой нажатой клавишей была KEY_1?
way_l2r    db      1          ;0 - RUS -> LAT, 1 - LAT -> RUS.
cursor_xy  dw      ?          ;Координаты курсора.

rus_chars  db      "абвгдежзийклмнопрстуфхцчшщъыьэюя"
           db      "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"
           db      '"',"/':,.;"

lat_chars  db      "f,dult;pbqrkvyjghcnea[wxio]sm'.z"
           db      'F<DULT:PBQRKVYJGHCNEA{WXIO}SM">Z'
           db      "@#$%^&*"

len_chars  equ     offset lat_chars - offset rus_chars

;Новый обработчик прерывания 16h.

new_16h:   test    ah,0eeh  ;Проверка номера вызванной функции.
           jz      main_1

;Если вызвана не одна из функций 0,1,10h,11h, передаём управление
;на старый обработчик:

           db      0eah  ;Код межсегментного JMP.
old_16h    dd      ?     ;Адрес старого обработчика.

;Иначе обрабатываем сами:

main_1:    push    ax
           push    bx
           push    cx
           push    dx
           push    es
           push    ds
           push    si
           push    di
           push    bp

           mov     bp,sp

;Теперь исходное (и возвращаемое) значение AX находится по адресу [BP+16],
;а исходное (и возвращаемое) значение регистра флагов - по адресу [BP+22].

           push    cs
           pop     ds

;Проверка номера вызванной функции:

           test    ah,0efh
           jnz     main_3

;Функция 0 или 10h - "чтение кода клавиши":

           cmp     buf_len,0             ;Если буфер пуст, заполняем.
           jne     main_2
           call    append_buf

main_2:    mov     ax,buf_inside         ;Берём пару "скан-код, символ"
           mov     [bp+16],ax            ;из буфера.

           mov     ax,buf_inside+2       ;Сдвигаем оставшиеся
           mov     buf_inside,ax         ;элементы буфера.
           mov     ax,buf_inside+4
           mov     buf_inside+2,ax

           dec     buf_len
           jmp     short return

;Функция 1 или 11h - "проверить наличие кода клавиши":

main_3:    cmp     buf_len,0
           jne     main_7

;Во внутреннем буфере пусто, проверяем клавиатурный буфер
;(с помощью функции 11h, чтобы отследить коды "горячих клавиш"):

           mov     ah,11h              ;!!!
           pushf
           call    old_16h
           mov     buf_inside,ax
           jnz     main_4

;Если там тоже пусто, выходим, установив флаг Z=1:

           or      word ptr [bp+22],40h
           jmp     short return

;В клавиатурном буфере что-то есть.
;Проверяем: это код "горячей клавиши"?

main_4:    cmp     ax,KEY_1
           je      main_5
           cmp     ax,KEY_2
           jne     main_6

;Обработка "горячей клавиши":

main_5:    call    act_hotkey
           jmp     main_3

;В клавиатурном буфере не код "горячей клавиши".
;Возвращаем ответ, вызывая старый обработчик INT 16h:

main_6:    or      word ptr [bp+22],40h ;Сначала устанавливаем флаг Z.
           mov     ax,[bp+16]
           pushf
           call    old_16h
           jnz     return_1             ;Переход, если флаг Z надо сбросить.
           jmp     short return_2

;Внутренний буфер не пуст. Берём оттуда значение и выходим, сбросив флаг Z:

main_7:    mov     ax,buf_inside

return_1:  and     word ptr [bp+22],0bfh
return_2:  mov     [bp+16],ax

return:    pop     bp
           pop     di
           pop     si
           pop     ds
           pop     es
           pop     dx
           pop     cx
           pop     bx
           pop     ax
           iret

;----------------------------------------------------------------------------
;Процедура append_buf - пополнение внутреннего буфера кодов клавиш.
;Вызывается, если буфер пуст (т.е. buf_len=0).

append_buf proc    near

;Чтобы иметь возможность определить нажатие "горячей клавиши", опрашиваем
;клавиатуру как 101-клавишную и ждём нажатия, не забирая символ из
;клавиатурного буфера:

append_1:  mov     ah,11h              ;!!!
           pushf
           call    old_16h
           mov     buf_inside,ax
           jz      append_1

;Проверяем: в клавиатурном буфере код "горячей клавиши"?

           cmp     ax,KEY_1
           jz      append_2
           cmp     ax,KEY_2
           jnz     append_3

;Обработка "горячей клавиши":

append_2:  call    act_hotkey
           cmp     buf_len,0
           jne     append_4

;В клавиатурном буфере не код "горячей клавиши" либо её код, но при обработке
;во внутренний буфер ничего не было помещено. Тогда просто запрашиваем код
;клавиши, обращаясь к старому обработчику:

append_3:  mov     ax,[bp+16]
           pushf
           call    old_16h
           mov     buf_inside,ax
           inc     buf_len        ;= mov buf_len,1
           mov     next_corr,0

append_4:  ret

append_buf endp

;----------------------------------------------------------------------------
;Процедура act_hotkey - обработка "горячей клавиши".
;
;Вход:  в клавиатурном буфере - код "горячей клавиши", во внутреннем буфере -
;       он же (но buf_len=0).
;Выход: если нажатие надо обрабатывать, в буфер помещаются три кода или один
;       код, иначе - ничего. Длина буфера соответственно будет 3, 1 или 0.

act_hotkey proc    near

;Извлекаем код "горячей клавиши" из клавиатурного буфера:

           mov     ah,10h              ;!!!
           pushf
           call    old_16h

;Определяем текущую страницу видеопамяти:

           mov     ah,15
           int     10h
           mov     page_num,bh

;Определяем координаты курсора:

           mov     ah,3
           int     10h
           mov     cursor_xy,dx

;Если курсор на левой границе экрана, выходим:

           and     dl,dl
           jz      act_exit

;Иначе переходим на символ влево:

           dec     dl

;Определяем, какая именно "горячая клавиша" нажата:

           cmp     buf_inside,KEY_1
           jnz     act_2

;Выполняем преобразование Рус/Lat.

           cmp     next_corr,0
           jnz     act_1

;Определяем направление преобразования:

           push    dx
           call    def_rl
           pop     dx
           inc     next_corr   ;=mov next_corr,1

act_1:     call    char_xy
           call    corr_rl
           cmp     al,0
           je      act_4
           jmp     short act_3

;Выполняем преобразование прописные/строчные:

act_2:     mov     next_corr,0
           call    char_xy
           call    corr_caps

;Сейчас, если AH=2, то символ не надо преобразовывать, иначе
;в AL преобразованный символ.

           cmp     ah,2
           je      act_4

;Символ надо преобразовывать.
;Заносим в буфер код Backspace:

act_3:     mov     buf_inside,0e08h

;Заносим в буфер скан-код (=0) и ASCII-код преобразованного символа:

           mov     ah,0
           mov     buf_inside+2,ax

;Заносим в буфер код "<-":

           mov     buf_inside+4,4b00h
           mov     buf_len,3
           ret

;Символ не надо преобразовывать - заносим в буфер только код "<-":

act_4:     mov     buf_inside,4b00h
           inc     buf_len           ;= mov buf_len,1

act_exit:  ret

act_hotkey endp

;----------------------------------------------------------------------------
;Процедура def_rl определяет направление преобразования строки: 0, если
;строка была неправильно набрана в русском режиме, и 1, если строка была
;неправильно набрана в латинском режиме.
;
;Вход:  DX - координаты последнего символа строки.
;Выход: way_l2r - направление.

def_rl     proc    near

           call    char_xy    ;Берем текущий символ строки.

           call    corr_caps  ;Проверяем: это буква, и если да, то какая?
           cmp     ah,2
           jne     def_rl_2

;Это не буква - тогда переходим к предыдущему символу. Если строка кончилась,
;выходим, при этом way_l2r сохраняет старое значение.

def_rl_1:  and     dl,dl
           jz      def_rl_3
           dec     dl
           jmp     def_rl

;Это буква - тогда в AH сейчас 0, если буква русская, и 1 - если латинская.
;Это значение и присваиваем way_l2r.

def_rl_2:  mov     way_l2r,ah
def_rl_3:  ret

def_rl     endp

;----------------------------------------------------------------------------
;Процедура char_xy возвращает код символа с заданными координатами.
;
;Вход:  DX - координаты символа.
;Выход: AL - код символа;
;       DX - без изменений.

char_xy    proc    near

           push    dx

           mov     ah,2               ;Устанавливаем курсор
           mov     bh,page_num        ;в заданную позицию.
           int     10h

           mov     ah,8               ;Определяем код символа
           int     10h                ;в позиции курсора.
           push    ax

           mov     ah,2               ;Восстанавливаем
           mov     dx,cursor_xy       ;координаты курсора.
           int     10h

           pop     ax
           pop     dx
           ret

char_xy    endp

;----------------------------------------------------------------------------
;Процедура corr_rl исправляет один символ строки.
;
;Вход:  AL - исходный символ,
;       way_l2r - направление преобразования.
;Выход: AL - исправленный символ или 0, если исправлять не надо.

corr_rl_a  dw      ?      ;Адрес вызываемой подпрограммы: addr_ret при
                          ;не нажатой Caps Lock и corr_caps - при нажатой.
corr_rl    proc    near

           push    ax

;Определяем, нажата ли клавиша Caps Lock:

           mov     ah,2
           pushf
           call    old_16h

;Если нажата, то при чтении из массивов rus_chars и lat_chars кодов букв
;надо изменять регистр этих букв на противоположный. Для этого в corr_rl_a
;записывается адрес процедуры corr_caps. Если клавиша Caps Lock не нажата,
;в corr_rl_a записывается адрес команды RET.

           mov     corr_rl_a,offset addr_ret
           and     al,40h
           jz      corr_rl_1
           mov     corr_rl_a,offset corr_caps

corr_rl_1: mov     di,offset rus_chars
           mov     si,offset lat_chars
           cmp     way_l2r,0
           je      corr_rl_2
           xchg    di,si

corr_rl_2: pop     cx           ;CL - исходный символ

;Сейчас DI указывает на строку с "неправильными" символами, а SI - на строку
;с "правильными".

           xor     bx,bx        ;Обнулили счётчик.

corr_rl_3: mov     al,[bx+di]
           call    corr_rl_a
           cmp     cl,al        ;Исходный символ совпал с "неправильным"?
           jnz     corr_rl_4

           mov     al,[bx+si]   ;Если совпал, заменяем его на "правильный".
           jmp     corr_rl_a    ;= CALL/RET

corr_rl_4: inc     bx           ;Если не совпал, надо проверить на совпадение
           cmp     bx,len_chars ;со следующим "неправильным" символом.
           jnz     corr_rl_3

           mov     al,0         ;Если не совпал ни с одним, возвращаем 0.
addr_ret:  ret

corr_rl    endp

;----------------------------------------------------------------------------
;Процедура corr_caps.
;
;Вход:  AL - код символа.
;Выход: если символ не является буквой, то AH=2, AL не изменяется;
;       если символ - русская буква, то AH=0, регистр буквы изменяется;
;       если символ - латинская буква, то AH=1, регистр буквы изменяется.

data_corr  db      1,97,122,-20h   ;a-z
           db      1,65,90,20h     ;A-Z
           db      0,160,175,-20h  ;а-п
           db      0,224,239,-50h  ;р-я
           db      0,128,143,20h   ;А-П
           db      0,144,159,50h   ;Р-Я
           db      0,240,240,1     ;Ё
           db      0,241,241,-1    ;ё
           db      2,0             ;Второе число, равное 0 - признак конца.

corr_caps  proc    near

           push    bx
           mov     bx,offset data_corr-4

corr_c_1:  add     bx,4

           mov     ah,[bx]           ;AH = 0, 1 или 2.
           cmp     byte ptr [bx+1],0 ;Конец таблицы?
           je      corr_c_2

           cmp     al,[bx+1]  ;Проверка левой границы диапазона.
           jb      corr_c_1
           cmp     al,[bx+2]  ;Проверка правой границы диапазона.
           ja      corr_c_1

           add     al,[bx+3]  ;Изменяем регистр буквы на противоположный.
corr_c_2:  pop     bx
           ret

corr_caps  endp

;----------------------------------------------------------------------------
;Инициализация:

init:      mov     ah,9                   ;Печать
           mov     dx,offset info         ;сведений
           int     21h                    ;о программе.

           mov     ax,3516h               ;Получаем и запоминаем
           int     21h                    ;адрес старого
           mov     word ptr old_16h,bx    ;обработчика INT 16h.
           mov     word ptr old_16h+2,es

           mov     dx,offset new_16h      ;Устанавливаем
           mov     ax,2516h               ;адрес нового
           int     21h                    ;обработчика INT 16h.

           mov     dx,offset init         ;Завершить и остаться
           int     27h                    ;резидентом.

info       db      13,10
           db      'Text corrector by Ivan Roshin, Moscow, 29 Apr 2002.'
           db      13,10
           db      'Use F11 to correct TEXT -> text, text -> TEXT.'
           db      13,10
           db      'Use F12 to correct RUS/LAT.'
           db      13,10
           db      'Welcome to http://www.ivr.da.ru!'
           db      13,10,13,10,'$'

codesg     ends
           end     begin

Скачать программу (1 КБ ZIP)
Скачать листинг программы в текстовом виде (4 КБ ZIP)

Комментарии

Резидентная часть программы занимает в памяти меньше килобайта (912 байтов, если быть точным).

Если в используемом вами русификаторе другая раскладка клавиатуры (обычно различие в том, какие клавиши используются для ввода знаков препинания в русском режиме), то следует соответствующим образом изменить строки rus_chars и lat_chars, определяющие взаимное соответствие символов, вводимых в русском и латинском режимах.

Если BIOS вашего компьютера не поддерживает 101-клавишную клавиатуру (т.е. выпущен раньше 15 декабря 1985 года), надо заменить вызовы функций 10h и 11h на вызовы функций 0 и 1 соответственно. Соответствующие строки программы отмечены комментарием «!!!».

Как уже упоминалось выше, для исправления текста, набранного при неправильном состоянии Caps Lock, я выбрал клавишу F11, а для исправления текста, набранного при неправильном режиме Рус/Lat, — клавишу F12. Вы можете выбрать другие «горячие клавиши», изменив значения констант KEY_1 и KEY_2. Старший байт константы содержит скан-код клавиши (или комбинации клавиш), а младший байт — ASCII-код.

Полный список пар [скан-код, ASCII-код] для всех клавиш и комбинаций Shift+клавиша, Alt+клавиша, Ctrl+клавиша я не привожу из-за его большого размера. Вместо этого предлагаю воспользоваться приведённой ниже программой, которая при нажатии клавиши (или комбинации клавиш) печатает шестнадцатеричное число, первые две цифры которого — скан-код, а вторые две — ASCII-код. Выход из программы — по нажатию Esc.

codesg     segment
           assume  cs:codesg
           assume  ds:codesg

           org     100h

main       proc    near

           mov     ah,10h     ;Или mov ah,0 для старого BIOS.
           int     16h        ;Ждём нажатия клавиши.

           cmp     ax,011bh   ;Если нажата Esc - выходим.
           je      exit

           mov     bx,offset string
           push    bx

           mov     cl,4       ;Подготавливаем
           mov     ch,ah      ;выводимую строку
           shr     ch,cl      ;в буфере.
           call    one_digit
           mov     ch,ah
           call    one_digit
           mov     ch,al
           shr     ch,cl
           call    one_digit
           mov     ch,al
           call    one_digit

           pop     dx         ;Печать строки.
           mov     ah,9
           int     21h

           jmp     main

exit:      ret

main       endp

one_digit  proc    near

           push    ax
           and     ch,0fh
           add     ch,'0'
           cmp     ch,'9'
           jna     m1
           add     ch,'a'-'0'-10
m1:        mov     [bx],ch
           inc     bx
           pop     ax
           ret

one_digit  endp

string     db      '0000',13,10,'$'

codesg     ends
           end     main

Скачать программу (1 КБ ZIP)
Скачать листинг программы в текстовом виде (1 КБ ZIP)

«Бонусы» резидента

Это возможности резидента, которые изначально при его разработке не задумывались, а получились «сами собой».

Если текущий режим — русский, а надо ввести одну латинскую букву (или наоборот, текущий режим — латинский, а надо ввести одну русскую букву), то может оказаться проще ввести букву, не переключая режим, а затем исправить её нажатием соответствующей «горячей клавиши».

Точно так же можно поступить, и когда текущий режим — русский, а надо ввести один из символов <, >, [, ], {, }, которые вводятся только в латинском режиме, или когда надо ввести один из символов, вместо которых в русском режиме вводятся знаки препинания (для русификатора KeyRus с раскладкой клавиатуры «по умолчанию» это символы @, #, $, %, ^, &, *). (В последнем случае для правильного исправления символа последняя буква в части строки слева от этого символа должна быть русской.)

Большое неудобство при наборе текста доставляет то, что знаки препинания вводятся по-разному в русском и латинском режимах. Допустим, вы набираете текст в латинском режиме, а знак препинания ввели так, как он вводится в русском режиме (или наоборот, набираете текст в русском режиме, а знак препинания ввели так, как он вводится в латинском режиме). В этом случае достаточно один раз нажать «горячую клавишу» для исправления текста, набранного в неправильном режиме Рус/Lat, и неверно введённый знак будет исправлен!

«Горячую клавишу» для исправления текста, набранного при неправильном состоянии Caps Lock, можно использовать не только для исправления ошибок, но и просто, чтобы заменить в какой-то части текста прописные буквы на строчные или наоборот (например, выделить заголовок прописными буквами). Просто подводим курсор в позицию за последним символом изменяемой строки и нажимаем «горячую клавишу», пока вся строка не будет обработана.

Можно, разумеется, изменять таким образом не строку, а лишь одну букву. При редактировании текста довольно часто приходится заменять прописную букву на строчную и наоборот — например, при добавлении слова в начало предложения и при удалении первого слова предложения; при разбиении одного предложения на два и при объединении двух предложений в одно.

Литература

  1. А.Фролов, Г.Фролов. «Аппаратное обеспечение IBM PC». Москва, Диалог-МИФИ, 1992.
  2. А.Фролов, Г.Фролов. «Программирование видеоадаптеров CGA, EGA и VGA». Москва, Диалог-МИФИ, 1992.
  3. П.Абель. «Ассемблер и программирование для IBM PC».
  4. Ф.Пьеро, Ж.-Л.Люкзак, Ф.Рейко и др. «Руководство по программированию под управлением MS-DOS». Москва, Радио и связь, 1995.

Другие мои статьи о резидентных программах для DOS:

1. 

«Отключение „опасных“ графических режимов в DOS». «Радиомир. Ваш компьютер» 12/2002.

Страница Ивана Рощина > Статьи >