Logo

Linux, OpenGL и FreePascal: Часть Вторая.
09.04.08

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

Обработка событий

В отличии от подхода в Windows, в Linux сообщения приходиться "выбивать силой" в главном цикле приложения :) И в этом помогут такие функции как XPending и XNextEvent, а также структура TXEvent, но обо всем по порядку.

Функция XPending возвращает количество сообщений в очереди для указанного display. Эта функция понадобиться для того чтобы обработать всю очередь сообщений прежде чем переходить непосредственно к обработке остальной части приложения. Функция XNextEvent как не сложно догадаться по названию - получает сообщение из очереди для указанного display, и удаляет его из нее. Что касается структуры TXEvent, то она сама по себе представляет лишь case-record других структур, каждая из которых несет в себе информацию о сообщении определенного типа - TXKeyEvent, TXButtonEvent, TXButtonEvent, TXMotionEvent и т.д. Прежде чем переходить к примеру, следует упомянуть про маску событий окна, которая формируется в атрибутах окна - TXSetWindowAttributes, а именно параметром event_mask. Этому параметру через операцию or присваиваются такие значения как:

  • ExposureMask - обработка события Expose, посылаемое X-сервером для перерисовки окна
  • FocusChangeMask - обработка событий получения/потери фокуса окном
  • ButtonPressMask, ButtonReleaseMask и PointerMotionMask - получения событий нажатия/отжатия кнопок мыши и перемещение курсора
  • KeyPressMask и KeyReleaseMask - получение событий нажатия/отжатия клавиш

Заменив старый код из предыдущего примера, на приведенный ниже, можно начать писать часть ответственную за обработку сообщений.

  wnd_Attr.event_mask := ExposureMask or
                         FocusChangeMask or
                         ButtonPressMask or
                         ButtonReleaseMask or
                         PointerMotionMask or
                         KeyPressMask or
                         KeyReleaseMask;

В первом примере уже присутствовал кусок кода ответственный за обработку сообщений(вызов функции XNextEvent с передачей ей параметров), поэтому начну с того как из полученного сообщения в переменной Event извлечь нужные данные. Прежде чем обрабатывать данные, нужно узнать тип сообщения. Делается это проверкой значения параметра _type:

  case Event._type of
    ClientMessage:
      begin
        // тут будет "ловля" сообщения по уничтожению окна
      end;
    MotionNotify:
      begin
        // тут обратываются сообщения перемещения курсора
      end;
    ButtonPress:
      begin
        // ... нажатых кнопок мыши
      end;
    KeyPress:
      begin
        // ... нажатых клавиш
      end;
  end;

Выше я привел лишь те типы сообщений, которые будут обрабатываться в примере, самих типов существует куда больше. Например для того чтобы получить сообщение отжатия клавиш, или кнопок мыши, следует использовать KeyRelease и ButtonRelease. Вообщем рекомендую детально изучить заголовочный файл X, или ознакомиться с man'ом.

Итак, собственно обработка сообщений. Получив тип сообщения, можно обращаться к нужным параметрам. Например координаты мышки извлекаются из Event.xmotion, а код нажатой кнопки мыши - из Event.xbutton.button. Сразу упомяну особенность Linux - кнопки мыши идут в порядке слева направо, т.е. левая - 1, средняя - 2, правая - 3, а не как сперва можно подумать - левая - 1, правая - 2 и т.д. Еще один момент - события скролла(wheel) тоже интерпретируются как события кнопок, и им соответствуют номера 4(вверх) и 5(вниз). Хотя это все относительно, т.к. пользователь может изменить ButtonMapping в своем xorg.conf как ему угодно :)

Что касается получения кодов нажатых клавиш, то тут ситуация чуть сложнее - для получения кода нужно вызвать функцию XLookupKeysym передав ей Event.xkey и индекс 0(сам правда не знаю зачем :)). Коды клавиш отличаются от таковых в Windows, и используют значения за пределами типа Byte. Открыв заголовочный файл keysym можно увидеть просто несметное количество констант... вообщем чтобы не утруждать себя разъяснениями что к чему, я написал функцию xkey_to_winkey и оформил её с кодами клавиш в модуле keyboard.pas. Так будет намного проще реализовать кроссплатформенное приложение.

Для наглядности в примере сделаем выход по нажатию Escape, смену цвета прямоугольника по нажатию левой кнопки мыши, и отрисовку красного квадрата в координатах мыши:

  case Event._type of
    MotionNotify:
      begin
        mouseX := Event.xmotion.X;
        mouseY := Event.xmotion.Y;
      end;
    ButtonPress:
      begin
        if Event.xbutton.button = 1 Then
          begin
            colorR := random( $FF );
            colorG := random( $FF );
            colorB := random( $FF );
          end
      end;
    KeyPress:
      begin
        if xkey_to_winkey( XLookupKeysym( @Event.xkey, 0 ) ) = 27 Then
          app_Work := FALSE;
      end;
  end;

Теперь осталось рассмотреть событие ClientMessage, в котором будет отслеживаться уничтожение окна. Для того чтобы отследить это, нужно предварительно зарегистрировать так званые "атомы" используя функцию XInternAtom. Ей передается идентификатор display и имя связанное с атомом(и как говорит man - последний параметр при False создаст атом в любом случаи, при True в случаи чего может возвратить None). Чтобы не ошибиться с правильностью определения, просто приведу код где показано как создать "атомы" и связать один из них(WM_DELETE_WINDOW) с окном посредством функции XSetWMProtocols.

  wnd_DestroyAtom := XInternAtom( scr_Display, 'WM_DELETE_WINDOW', TRUE );
  wnd_Protocols   := XInternAtom( scr_Display, 'WM_PROTOCOLS', TRUE );
  XSetWMProtocols( scr_Display, wnd_Handle, @wnd_DestroyAtom, 1 );

Сама же проверка внутри ClienMessage выглядит так:

  if ( Event.xclient.message_type = wnd_Protocols ) and
     ( Event.xclient.data.l[ 0 ] = wnd_DestroyAtom ) Then
    begin
      app_Work := FALSE;
      writeln( 'Убиффцо! 8D' );
    end;

MIRGames
Valid HTML 4.0.1 Strict Valid CSS 3.0 Strict