QNX RTP Logo QNX Realtime Platform: Русский Портал QNX
Thursday, 20 Nov 2008 19:50
Меню

Проект OpenNET - все о Unix
Глобальные "горячие клавиши" Print E-mail
Дмитрий Сошин, dmitry at mutex.ru

Иногда бывает удобно пользоваться "горячими клавишами", настройка которых не зависит от того, окно какого приложения в данный момент активно. О том, как добиться такого эффекта и пойдет здесь речь. Правда, описанные приемы могут оказаться полезными и в других случаях.
Для начала, немного об идеологии Фотона.

Пробел случая :).

Вообще-то, на эту тему есть хорошие статьи, например [1] и [2]. Но на всякий случай, а отчасти для придания солидности своему труду, добавлю немного теории.

Фирма QSSL придумала красивую аллегорию для описания своей графической оболочки.

Итак, дано:

прозрачный куб (ну, параллелепипед).

Передняя грань, обращенная к зрителю - экран вашего монитора, противоположенная теряется в его недрах.

Внутренний объем параллелепипеда называется пространством событий (event space).

В нем плавают прямоугольники - регионы (regions). Ближе всех к пользователю расположен регион графического драйвера (graphics driver region). За ним, регионы устройств ввода, далее все прочие регионы. За всеми регионами, в противоположенном конце пространства событий расположен корневой (root) регион.

Регионы могут излучать фотоны. Делают они это перпендикулярно своей плоскости. Кроме того, они обладают по отношению к фотонам двумя свойствами - чувствительность (sensitiv) и непрозрачность (opaque).

Если подвигать мышкой, регион драйвера мыши начнет излучать фотоны типа Ph_EV_PTR_MOTION_NOBUTTON (типа, мышь двигается :)). Перемещаем курсор в нужную позицию и нажимаем на левую кнопку. И тут начинается:

  • Регион драйвера мыши испускает фотон типа Ph_EV_BUT_PRESS.
  • Этот фотон летит в пространстве, пронзая прозрачные для него регионы, пока не наткнется на непрозрачный. Надо помнить, что независимо от свой прозрачности, регион может быть чувствительным для этого типа фотонов (этим мы позже и воспользуемся).
  • Допустим, он наткнулся на регион кнопки (виджет PtButton). Этот регион испускает фотон типа Ph_EV_DRAW .
  • Фотон врезается в регион графического драйвера. В ответ драйвер перерисовывает изображение кнопки, делая ее нажатой.

В действительности все происходит несколько сложнее, но об этом лучше написано в [2].
При использовании клавиатуры все происходит очень похоже. Излучается фотон типа Ph_EV_KEY. Если он встречает на своем пути регион, чувствительный к этому типу фотонов, например виджета PtText, то он вызывает ответную реакцию.

Этих представлений вполне достаточно, чтобы решить нашу задачу.

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

Физика элементарных частиц.

Строение фотона или события (event) описывается структурой PhEvent_t (см. файл PhT.h):

typedef struct Ph_event {
    unsigned long       type;
    unsigned short      subtype;
    unsigned short      processing_flags;
    PhEventRegion_t     emitter;
    PhEventRegion_t     collector;
    unsigned short      input_group;
    unsigned short      flags;
    unsigned long       timestamp;
    PhPoint_t           translation;
    unsigned short      num_rects;
    unsigned short      data_len;
} PhEvent_t;

Член структуры type определяет тип фотона и может принимать одно из следующих значений:

 Ph_EV_KEY0x00000001
 Ph_EV_BUT_PRESS 0x00000002
 Ph_EV_BUT_RELEASE 0x00000004
 Ph_EV_PTR_MOTION_NOBUTTON0x00000008
 Ph_EV_PTR_MOTION_BUTTON0x00000010
 Ph_EV_BOUNDARY0x00000020
 Ph_EV_EXPOSE0x00000040
 Ph_EV_DRAW0x00000080
 Ph_EV_INPUT_OTHER 0x00000100
 Ph_EV_DRAG0x00000200
 Ph_EV_COVERED0x00000400
 Ph_EV_BLIT0x00000800
 Ph_EV_SYSTEM0x00001000
 Ph_EV_WM0x00002000
 Ph_EV_BUT_REPEAT0x00004000
 Ph_EV_RAW 0x00008000
 Ph_EV_TIMER 0x00010000
 Ph_EV_LB_SYSTEM0x00020000
 Ph_EV_SERVICE0x00040000
 Ph_EV_INFO0x00080000
 Ph_EV_AUDIO 0x00100000
 Ph_EV_DNDROP0x00200000

Член структуры data_len содержит размер данных, связанных с событием.
А для того чтобы получить указатель на эти данные, существует функция PhGetData():

    void *PhGetData( PhEvent_t const *event );

С каждым типом события связаны свои данные. Например для типа Ph_EV_PTR_MOTION_NOBUTTON (перемещение мыши, не нажимая кнопки), это структура PhPointerEvent_t , описанная в PhT.h:

typedef struct Ph_ev_ptr_data {
	PhPoint_t          pos;
	unsigned short     buttons;
	unsigned short     button_state;
	unsigned char      click_count;
	unsigned char      flags;
	short              z;
	unsigned long      key_mods;
	unsigned long      zero;
} PhPointerEvent_t;

При нажатии клавиш излучаются фотоны типа Ph_EV_KEY, а данные выглядят как структура PhKeyEvent_t (опять PhT.h):

typedef struct Ph_ev_key_data {
	unsigned long     key_mods;
	unsigned long     key_flags;
	unsigned long     key_cap;
	unsigned long     key_sym;
	unsigned short    key_scan;
	unsigned short    key_zero;
	PhPoint_t         pos;	
	unsigned short    button_state;
} PhKeyEvent_t;

Член структуры key_mods - набор битовых флагов, описывающий состояние управляющих клавиш. Например, Pk_KM_Alt указывает на то, что была нажата клавиша <Alt>.

key_mods содержит информацию о том, какие действия производили с клавишей - нажимали, отпускали или просто держали нажатой.

Код нажатой клавиши находится в key_cap.

Все константы описаны в файле PkKeyDef.h.

А все подробности можно найти там, где ищут в последнюю очередь :) [3].

К снаряду...

Простой тест.

Напишем простой и бестолковый тест - запуск helpviewer при нажатии комбинации клавиш <Alt>+<F1>.

Создаем регион.

Регион должен перекрывать весь экран. Для этого выясним разрешение экрана. При этом не надо забывать, что на экране монитора видна только одна девятая часть всего пространства, а координата 0,0 соответствует левому верхнему углу первой части.

Текущие настройки экрана можно получить вызвав функцию PgGetVideoMode():

    int PgGetVideoMode( PgDisplaySettings_t *settings );

Структура PgDisplaySettings_t описана в Pg.h:

typedef struct {
	unsigned        mode;
	int             xres;
	int             yres;
	int             refresh;
	unsigned        flags;
	unsigned long   reserved[22];
} PgDisplaySettings_t;

Нас интересуют значения xres и yres.

Регион должен быть прозрачен для всех событий и чувствителен для событий типа Ph_EV_KEY.

Регион создается функцией PhRegionOpen():

    PhRid_t PhRegionOpen( unsigned fields,
                          PhRegion_t const *info,
                          PhRect_t const *rect,
                          void const *data );
    

Второй аргумент - структура PhRegion_tPhRegion_t const *info, описывающая свойства региона (все тот же PtT.h). Первый аргумент unsigned fields - набор битовых флагов, указывающий, какие члены структуры PhRegion_t надо принимать во внимание. Третий аргумент PhRect_t const *rect, прямоугольник, ассоциируемый с регионом, служит для описания его геометрии. Четвертый аргумент - указатель на данные связанные с регионом. Он нас не интересует.

Вот текст функции, создающий регион и возвращающий его идентификатор:

PhRid_t create_region()
{
    PhRegion_t  region;
    PhRect_t    rect;
    PhRid_t     rid;
    PgDisplaySettings_t settings;


    // выясняем размеры экрана
    PgGetVideoMode(&settings);

    // регион чувствителен к событиям типа Ph_EV_KEY
    region.events_sense = Ph_EV_KEY;

    // устанавливаем "родителя" нашего региона
    region.parent = Ph_ROOT_RID;

    // левый верхний угол региона по отношению к родительскому региону
    region.origin.x = region.origin.y = 0;

    // геометрия региона
    rect.ul.x = 0;                         // левый верхний угол региона - в левом верхнем углу экрана
    rect.ul.y = 0;   
    rect.lr.x = settings.xres * 3;         // регион перекрывает все пространство
    rect.lr.y = settings.yres * 3;

    // открытие региона
    rid = PhRegionOpen( Ph_REGION_PARENT |
                        Ph_REGION_EV_SENSE |
                        Ph_REGION_ORIGIN |
                        Ph_REGION_RECT,
                        &region, &rect, NULL );


    return( rid );
}

Прием событий.

Для приема событий предназначена функция PhEventNext():

    int PhEventNext( void *buffer, unsigned size );
    
    

Первый аргумент - указатель на буфер под принимаемые события, второй - размер этого буфера.

Указывать размер буфера надо так, чтобы туда поместились PhEvent_t и PhKeyEvent_t с учетом выравнивания.

В справке на эту функцию приведен пример, где размер указывается просто с запасом:

    PhEventNext( event, sizeof( PhEvent_t ) + 1000 ) );

Этим же рецептом воспользуемся и при выделении памяти под сообщение:

    event = (PhEvent_t *)malloc( sizeof( PhEvent_t ) + 1000 );

Ниже приведен вариант функции main():


#include <stdio.h>
#include <Ph.h>

int main()
{
    PhEvent_t     *event = 0;
    PhKeyEvent_t  *key_struct = 0; 
    int           ret = 0;


    // инициализация библиотеки Photon
    if(-1 == PtInit(NULL))
    {
        fprintf( stderr, "Couldn't initialize the widget library.\n" );
        exit( EXIT_FAILURE );
    }

    // создаем регион
    if(-1 == create_region())
    {
        fprintf( stderr, "Couldn't create region.\n" );
        exit( EXIT_FAILURE );
    }

    // резервируем память для сообщений
    event = (PhEvent_t *)malloc(sizeof(PhEvent_t) + 1000);
    if(!event)
    {
        fprintf( stderr, "Couldn't allocate event buffer.\n" );
        exit( EXIT_FAILURE );
    }

    // цикл приема сообщений
    for(;;)
    {
        if(Ph_EVENT_MSG == PhEventNext(event, sizeof(PhEvent_t) + 1000 ))
        {
            //если это событие от клавиатуры
            if(Ph_EV_KEY == event->type)
            {
                // добываем данные принесенные событием
                key_struct = PhGetData( event );
                
                // реагируем только на первое нажатие клавиши 
                // игнорируем повторы при удержании
                if((key_struct->key_flags & Pk_KF_Key_Down) && !(key_struct->key_flags & Pk_KF_Key_Repeat))
                    if((Pk_KM_Alt == key_struct->key_mods) && (Pk_F1 == key_struct->key_cap))
                        PtHelpTopic("/Photon microGUI for QNX 6/Library Reference/Ph---Photon/PhRegionOpen");
            }
        }
    }
}

Откомпилируем наш пример:

$ cc -l ph -o my_test my_test.c

И запустим:

$ ./my_test

Использование в приложениях.

Обычно в приложениях для приема событий используется не PhEventNext(), а функция PtMainLoop().

С этим связан ряд отличий: Открытие региона сводится в созданию виджета PtRegion.Функции приема событий и вызова обработчиков берет на себя PtMainLoop().Для того, чтобы присоединить обработчик события, используется функция PtAddEventHandler().

Исходя из это, функция создания региона теперь будет выглядеть так:

PtWidget_t *create_region()
{
    PtWidget_t  *region = 0;
    PtArg_t     args[10];
    PhArea_t	    area;
    unsigned    arg_count = 0;

    PgDisplaySettings_t settings;

    // выясняем размеры экрана
    PgGetVideoMode(&settings);

    // геометрия региона
    area.pos.x = 0;                         // левый верхний угол региона - в левом верхнем углу экрана
    area.pos.y = 0;   
    area.size.w = settings.xres * 3;         // регион перекрывает все пространство
    area.size.h = settings.yres * 3;

    // геометрия региона
    PtSetArg(&args[arg_count++], Pt_ARG_AREA, &area, 0 );

    // чувствительность в событиям типа Ph_EV_KEY
    PtSetArg(&args[arg_count++], Pt_ARG_REGION_SENSE, Ph_EV_KEY, Ph_EV_KEY);

    // прозрачный фон (по умолчанию он серый)
    PtSetArg( &args[arg_count++], Pt_ARG_FILL_COLOR, Pg_TRANSPARENT, 0 );
	
    // создание виджета
    region = PtCreateWidget( PtRegion, NULL, arg_count, args );

    // добавляем обработчик события
    PtAddEventHandler( region, Ph_EV_KEY, key_pressed_cb, NULL );

    PtRealizeWidget( region );
	
    return( region );
}

Ниже приведен пример функции обработки событий.
Обратите внимание на то, как передается указатель на событие PhEvent_t. Это член структуры PtCallbackInfo_t.


int key_pressed_cb( PtWidget_t *widget, void *data, PtCallbackInfo_t *cbinfo )
{
    PhKeyEvent_t	*key_struct = PhGetData(cbinfo->event); 

    // реагируем только на первое нажатие клавиши 
    // игнорируем повторы при удержании
    if((key_struct->key_flags & Pk_KF_Key_Down) && !(key_struct->key_flags & Pk_KF_Key_Repeat))
        if((Pk_KM_Alt == key_struct->key_mods) && (Pk_F1 == key_struct->key_cap))
            PtHelpTopic("/Photon microGUI for QNX 6/Library Reference/Ph---Photon/PhRegionOpen");

    return( Pt_CONTINUE );
}

И наконец, новый вариант функции main():

#include <stdio.h>
#include <Ph.h>
#include <Pt.h>

int main()
{

    // инициализация библиотеки Photon
    if(-1 == PtInit(NULL))
    {
        fprintf( stderr, "Couldn't initialize the widget library.\n" );
        exit( EXIT_FAILURE );
    }

    // создаем регион
    if(!create_region())
    {
        fprintf( stderr, "Couldn't create region.\n" );
        exit( EXIT_FAILURE );
    }

    // цикл приема событий
    PtMainLoop();
}

Что можно сделать еще?

Можно, например, создавать свой "ловчий" регион с индивидуальными настройками для каждого из девяти экранов.
Отслеживая координаты мыши, можно сделать "выпадающую" панель, на подобии Shelf, хотя для этого, наверное, есть и более простые средства.
И еще. Результаты ваших исследований поможет проанализировать утилита phview.

Литература.

1.И.Н. Коваленко Photon: По ту строну картинки.
2.С.В. Ющенко Графическая оболочка Photon - революция в мире интерфейсов.
3.Справка по PhEvent_tНа вашем компьютереВ интернете
4.Справка по PhGetData()На вашем компьютереВ интернете
5.Справка по PhPointerEvent_tНа вашем компьютереВ интернете
6.Справка по PhKeyEvent_tНа вашем компьютереВ интернете
7.Справка по PgGetVideoMode()На вашем компьютереВ интернете
8.Справка по PhRegionOpen()На вашем компьютереВ интернете
9.Справка по PhRegion_tНа вашем компьютереВ интернете
10.Справка по PhRect_tНа вашем компьютереВ интернете
11.Справка по PhEventNext()На вашем компьютереВ интернете
12.Справка по PtRegionНа вашем компьютереВ интернете
13.Справка по PtAddEventHandler()На вашем компьютереВ интернете
14.Справка по phviewНа вашем компьютереВ интернете
[Вернуться к списку]
©   2000-2003 Команда проекта QNX.ORG.RU // QNX.ORG.RU Team
Авторы проекта: Дмитрий Алексеев [dmi] и Дмитрий Васильев. Техническое сопровождение проекта: Игорь Сорокин [isorokin]. Информационное сопровождение: Дмитрий Алексеев [dmi]
QNX - зарегистрированная торговая марка QNX Software Systems, Ltd., Canada. Остальные упоминаемые на сайте торговые марки и логотипы являются исключительно собственностью их уважаемых владельцев. Ничьи права не затронуты. Материалы сайта не могут быть скопированы и где-либо использованы в той или иной форме без письменного разрешения разработчиков сайта.
Powered by Mambo Open Source