Assembler - язык неограниченных возможностей

         

Работа с мышью


Все общение с мышью в DOS выполняется через прерывание 33h, обработчик которого устанавливает драйвер мыши, загружаемый обычно при запуске системы. Современные драйверы поддерживают около 60 функций, позволяющих настраивать разрешение мыши, профили ускорений, виртуальные координаты, настраивать дополнительные обработчики событий и т.п. Большинство этих функций требуются редко, сейчас рассмотрим основные:

INT 33h, AX = 0 — Инициализация мыши

Ввод: AX = 0000h
Вывод: АХ = 0000h, если мышь или драйвер мыши не установлены
АХ = FFFFh, если драйвер и мышь установлены
ВХ = число кнопок:
    0002 или FFFF — две
    0003 — три
    0000 — другое количество

Выполняется аппаратный и программный сброс мыши и драйвера.

INT 33h, AX = 1 — Показать курсор

Ввод: AX = 0001h

INT 33h, AX = 2 — Спрятать курсор

Ввод: AX = 0002h

Драйвер мыши поддерживает внутренний счетчик, управляющий видимостью курсора мыши. Функция 2 уменьшает значение счетчика на единицу, а функция 1 увеличивает его, но только до значения 0. Если значение счетчика — отрицательное число, он спрятан, если ноль — показан. Это позволяет процедурам, использующим прямой вывод в видеопамять, вызывать функцию 2 в самом начале и 1 — в самом конце, не заботясь о том, в каком состоянии был курсор мыши у вызвавшей эту процедуру программы.

INT 33h, AX = 3 — Определить состояние мыши

Ввод: AX = 0003h
Вывод: ВХ = состояние кнопок:

    бит 0 — нажата левая кнопка

    бит 1 — нажата правая кнопка

    бит 2 — нажата средняя кнопка

СХ = Х-координата
DX = Y-координата

Возвращаемые координаты совпадают с пиксельными координатами соответствующей точки на экране в большинстве графических режимов, кроме 04, 05, 0Dh, 13h, в которых Х-координату мыши нужно разделить на 2, чтобы получить номер столбца соответствующей точки на экране. В текстовых режимах обе координаты надо разделить на 8, чтобы получить номер строки и столбца соответственно.


В большинстве случаев эта функция не используется в программах, так как для того, чтобы реагировать на нажатие кнопки или перемещение мыши в заданную область, требуется вызывать это прерывание постоянно, что приводит к трате процессорного времени. Функции 5 (определить положение курсора при последнем нажатии кнопки), 6 (определить положение курсора при последнем отпускании кнопки) и 0Bh (определить расстояние, пройденное мышью) могут помочь оптимизировать работу программы, самостоятельно следящей за всеми передвижениями мыши, но гораздо эффективнее указать драйверу самому следить за ее передвижениями (чем он, собственно, и занимается постоянно) и передавать управление в программу, как только выполнится заранее определенное условие, например пользователь нажмет на левую кнопку мыши. Такой сервис обеспечивает функция 0Ch — установить обработчик событий.

INT 33h, AX = 0Ch — Установить обработчик событий

Ввод: AX = 000Ch
ES:DX = адрес обработчика
СХ = условие вызова


    бит 0 — любое перемещение мыши

    бит 1 — нажатие левой кнопки

    бит 2 — отпускание левой кнопки

    бит 3 — нажатие правой кнопки

    бит 4 — отпускание правой кнопки

    бит 5 — нажатие средней кнопки

    бит 6 — отпускание средней кнопки


СХ = 0000h — отменить обработчик
Обработчик событий должен быть оформлен, как дальняя процедура (то есть завершаться командой RETF). На входе в процедуру обработчика АХ содержит условие вызова, ВХ — состояние кнопок, СХ, DX — X- и Y-координаты курсора, SI, DI — счетчики последнего перемещения по горизонтали и вертикали (единицы измерения для этих счетчиков — мики, 1/200 дюйма), DS — сегмент данных драйвера мыши. Перед завершением программы установленный обработчик событий должен быть обязательно удален (вызов функции 0Ch с СХ = 0), так как иначе при первом же выполнении условия управление будет передано по адресу в памяти, с которого начинался обработчик.



Функция 0Ch используется так часто, что у нее появилось несколько модификаций — функция 14h, позволяющая установить одновременно три обработчика с разными условиями, и функция 18h, также позволяющая установить три обработчика и включающая в условие вызова состояние клавиш Shift, Ctrl и Alt. Воспользуемся обычной функцией 0Ch, чтобы написать простую программу для рисования.

; mousedr.asm ; Рисует на экране прямые линии с концами в позициях, указываемых мышью ; .model tiny .code org 100h ; СОМ-файл .186 ; для команды shr cx,3 start: mov ax,12h int 10h ; видеорежим 640x480 mov ax,0 ; инициализировать мышь int 33h mov ax,1 ; показать курсор мыши int 33h mov ax,000Ch ; установить обработчик событий мыши mov cx,0002h ; событие - нажатие левой кнопки mov dx,offset handler ; ES:DX - адрес обработчика int 33h mov ah,0 ; ожидание нажатия любой клавиши int 16h mov ax,000Ch mov cx,0000h ; удалить обработчик событий мыши int 33h mov ax,3 ; текстовый режим int 10h ret ; конец программы

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

handler: push 0A000h pop es ; ES - начало видеопамяти push cs pop ds ; DS - сегмент кода и данных этой программы push сх ; СХ (Х-координата) и push dx ; DX (Y-координата) потребуются в конце

mov ax, 2 ; спрятать курсор мыши перед выводом на экран int 33h cmp word ptr previous_X,-1 ; если это первый вызов, je first_point ; только вывести точку,

call line_bresenham ; иначе - провести прямую exit_handler: pop dx ; восстановить СХ и DX pop сх mov previous_X,cx ; и запомнить их как предыдущие mov previous_Y,dx ; координаты

mov ax,1 ; показать курсор мыши int 33h retf ; выход из обработчика - команда RETF

first_point: call putpixel1b ; вывод одной точки (при первом вызове) jmp short exit_handler

; Процедура рисования прямой линии с использованием алгоритма Брезенхама ; Ввод: СХ, DX - X, Y конечной точки ; previous_X,previous_Y - X, Y начальной точки



line_bresenham: mov ax, сх sub ax,previous_X ; AX = длина проекции прямой на ось X jns dx_pos ; если АХ отрицательный - neg ax ; сменить его знак, причем mov word ptr X_increment,1 ; координата X при выводе jmp short dx_neg ; прямой будет расти, dx_pos: mov word ptr X_increment,-1 ; а иначе - уменьшаться

dx_neg: mov bx,dx sub bx,previous_Y ; BX = длина проекции прямой на ось Y jns dy_pos ; если ВХ отрицательный - neg bx ; сменить его знак, причем mov word ptr Y_increment,1 ; координата Y при выводе jmp short dy_neg ; прямой будет расти, dy_pos: mov word ptr Y_increment,-1 ; а иначе - уменьшаться

dy_neg: shl ax,1 ; удвоить значения проекций, shl bx,1 ; чтобы избежать работы с полуцелыми числами

call putpixel1b ; вывести первую точку (прямая рисуется от ; CX,DX к previous_X,previous_Y) cmp ax,bx ; если проекция на ось X больше, чем на Y: jna dx_le_dy mov di,ax ; DI будет указывать, в какую сторону мы shr di,1 ; отклонились от идеальной прямой neg di ; оптимальное начальное значение DI: add di,bx ; DI = 2 * dy - dx cycle: cmp ex,word ptr previous_X ; основной цикл выполняется, je exit_bres ; пока Х не станет равно previous_X cmp di,0 ; если DI > 0, jl fractlt0 add dx,word ptr Y_increment ; перейти к следующему Y sub di,ax ; и уменьшить DI на 2 * dx fractlt0: add cx,word ptr X_increment ; следующий Х (на каждом шаге) add di,bx ; увеличить DI на 2 * dy call putpixel1b ; вывести точку jmp short cycle ; продолжить цикл dx_le_dy: ; если проекция на ось Y больше, чем на X mov di,bx shr di,1 neg di ; оптимальное начальное значение DI: add di,ax ; DI = 2 * dx - dy cycle2: cmp dx,word ptr previous_Y ; основной цикл выполняется, je exit_bres ; пока Y не станет равным previous_Y, cmp di,0 ; если DI > 0, jl fractlt02 add cx,word ptr X_increment ; перейти к следующему X sub di,bx ; и уменьшить DI на 2 * dy fractlt02: add dx,word ptr Y_increment ; следующий Y (на каждом шаге) add di,ax ; увеличить DI на 2 * dy call putpixel1b ; вывести точку jmp short cycle2 ; продолжить цикл exit_bres: ret ; конец процедуры



; Процедура вывода точки на экран в режиме, использующем один бит для ; хранения одного пикселя. ; DХ = строка, СХ = столбец ; Все регистры сохраняются

putpixel1b: pusha ; сохранить регистры xor bx,bx mov ax,dx ; AX = номер строки imul ах,ах,80 ; АХ = номер строки * число байт в строке push cx shr сх,3 ; СХ = номер байта в строке add ах,сх ; АХ = номер байта в видеопамяти mov di,ax ; поместить его в SI и DI для команд mov si,di ; строковой обработки

pop cx ; СХ снова содержит номер столбца mov bx,0080h and cx,07h ; последние три бита СХ = ; остаток от деления на 8 = номер бита в байте, считая справа налево shr bx,cl ; теперь в BL установлен в 1 нужный бит lods es:byte ptr some_label ; AL = байт из видеопамяти or ax,bx ; установить выводимый бит в 1, ; чтобы стереть пиксель с экрана, эту команду OR можно заменить на ; not bx ; and ax,bx ; или лучше инициализировать ВХ не числом 0080h, а числом FF7Fh и использовать ; только and stosb ; и вернуть байт на место рора ; восстановить регистры ret ; конец

previous_X dw -1 ; предыдущая Х-координата previous_Y dw -1 ; предыдущая Y-координата Y_increment dw -1 ; направление изменения Y X_increment dw -1 ; направление изменения X some_label: ; метка, используемая для переопределения ; сегмента-источника для lods с DS на ES end start

Алгоритм Брезенхама, использованный в этой программе, является самым распространенным алгоритмом рисования прямой. Существуют, конечно, и более эффективные алгоритмы, например алгоритм Цаолинь By, работающий на принципе конечного автомата, но алгоритм Брезенхама стал стандартом де-факто.

Приведенную реализацию этого алгоритма можно значительно ускорить, использовав самомодифицирующийся код, то есть после проверки на направление прямой в начале алгоритма вписать прямо в дальнейший текст программы команды INС СХ, DEC CX, INС DX и DEC DX вместо команд сложения этих регистров с переменными X_increment и Y_increment. Самомодифицирующийся код часто применяется при программировании для DOS, но во многих многозадачных системах текст программы загружается в область памяти, защищенную от записи, так что в последнее время область применения этого приема становится ограниченной.

Содержание раздела