MiniGUI事件传递机制分析

前言

这段时间学习了一下MiniGUI中的事件传递机制,包括从内核驱动上报事件到MiniGUI分发事件这一整条通路,在嵌入式平台上,获取触摸事件可以使用tslib框架,通过tslib获取到事件之后,会把消息保存在桌面消息队列中,然后进一步分发到应用消息队列,我会在这篇文章中详细说明

1. IAL初始化流程

IAL全拼是Input Abstract Layer,也就是输入抽象层,在MiniGUI中抽象了很多输入设备,比如tslibial、ipaqh3600ial、consoleial和nexusial等等,本文以tslibial为例进行说明

首先看一下初始化流程图,有一个初步的印象
MiniGUI输入引擎UML图-初始化流程图
整个MiniGUI的初始化会调用到InitGUI()函数中,本文主要关注IAL初始化相关的代码

/* libminigui-gpl-3.2/src/kernel/init.c */
int GUIAPI InitGUI (int args, const char *agr[])
{
	/*...*/
    /*初始化应用窗口默认事件处理进程*/
    __mg_def_proc[0] = PreDefMainWinProc;
    /*初始化应用对话框默认事件处理进程*/
    __mg_def_proc[1] = PreDefDialogProc;
    /*初始化应用控件窗口默认事件处理进程*/
    __mg_def_proc[2] = PreDefControlProc;
    
	/*...*/
	/*初始化输入引擎*/
    if(!mg_InitLWEvent()) {
        fprintf(stderr, "KERNEL>InitGUI: Low level event initialization failure!\n");
        goto failure1;
    }
    
    /*...*/
	/*创建桌面接受消息线程*/
    if (!SystemThreads()) {
        fprintf (stderr, "KERNEL>InitGUI: Init system threads failure!\n");
        goto failure;
    }
    
    /*...*/
}

static BOOL SystemThreads(void)
{
	/*...*/
	/*这里以MiniGUI-Threads模式为例*/
	/*创建桌面接受消息线程,会初始化桌面消息队列和一个while循环去取消息*/
	pthread_create (&__mg_desktop, NULL, DesktopMain, &wait);
	
	/*...*/
	/*创建接受内核上报事件线程,会等待内核事件,如果有事件则解析并上报*/
	pthread_create (&__mg_parsor, NULL, EventLoop, &wait);
	
	/*...*/
}
/* libminigui-gpl-3.2/src/kernel/event.c */
BOOL mg_InitLWEvent (void)
{
	/*获取双击最小事件间隔,可以配置MiniGUI.cfg中的dblclicktime字段,默认300*/
    GetDblclickTime ();
    /*获取超时时间,可以配置MiniGUI.cfg中的timeoutusec字段,默认300000,该值是每一次循环获取事件的最大等待时间*/
    GetTimeout ();

	/*初始化具体的输入引擎*/
    if (mg_InitIAL ())
        return FALSE;

	/*重置鼠标按键状态*/
    ResetMouseEvent();
    ResetKeyEvent();

    return TRUE;
}
/* libminigui-gpl-3.2/src/ial/ial.c */
int mg_InitIAL (void)
{
	/*...*/
	/*会解析MiniGUI.cfg中的ial_engine、mdev和mtype字段,判断使用哪种输入引擎*/
	/*IAL_InitInput会最终调用到具体的输入引擎中,tslibial会调用到InitTSLibInput函数*/
	if (!IAL_InitInput (__mg_cur_input, mdev, mtype)) {
        fprintf (stderr, "IAL: Init IAL engine failure.\n");
        return ERR_INPUT_ENGINE;
    }
    
	/*...*/
}

上面写到会创建桌面接受消息线程,因为用的是MiniGUI-Threads模式,所以在desktop-ths.c中

/* libminigui-gpl-3.2/src/kernel/desktop-ths.c */
void* DesktopMain (void* data)
{
	/*...*/
	/*__mg_dsk_msg_queue就是桌面消息队列了,默认DEF_MSGQUEUE_LEN是16,也就是最多保存16个消息*/
	if (!(__mg_dsk_msg_queue = mg_InitMsgQueueThisThread ()) ) {
        _MG_PRINTF ("KERNEL>Desktop: mg_InitMsgQueueThisThread failure!\n");
        return NULL;
    }
	
	/*...*/
	/*开启一个while循环,调用GetMessage,指定HWND_DESKTOP,每次从__mg_dsk_msg_queue队列中取一个消息*/
	while (GetMessage(&Msg, HWND_DESKTOP)) {
			/*...*/
			/*如果取到消息的话,就把消息丢给桌面事件处理函数进行分发,具体怎么分发,下节详解*/
	        lRet = DesktopWinProc (HWND_DESKTOP, 
                        Msg.message, Msg.wParam, Msg.lParam);
            /*...*/
	}
	
	/*...*/
}

2. 内核事件上报流程

在初始化的时候,开启了一个接受并解析内核事件的线程EventLoop,这一小节重点分析改函数,先看图
MiniGUI输入引擎UML图-内核事件上报流程图
EventLoop函数中,主要是一个while循环,IAL_WaitEvent会调用到输入引擎的wait_event函数中去等待内核事件

/* libminigui-gpl-3.2/src/kernel/init.c */
static void* EventLoop (void* data)
{
    LWEVENT lwe;
    int event;

    lwe.data.me.x = 0; lwe.data.me.y = 0;

    sem_post ((sem_t*)data);

    while (__mg_quiting_stage > _MG_QUITING_STAGE_EVENT) {
    	/*等待内核上报事件,__mg_event_timeout是等待超时时间,在超时时间内没有事件的话会再次进入while循环*/
        event = IAL_WaitEvent (IAL_MOUSEEVENT | IAL_KEYEVENT, 0,
                        NULL, NULL, NULL, (void*)&__mg_event_timeout);
        if (event < 0) {
            continue;
        }

        lwe.status = 0L;
        lwe.data.me.status = 0;
        /*鼠标事件,kernel_GetLWEvent主要是获取上报的坐标值,和判断是否双击*/
        if (event & IAL_MOUSEEVENT && kernel_GetLWEvent (IAL_MOUSEEVENT, &lwe))
            ParseEvent (&lwe);

        lwe.status = 0L;
        lwe.data.ke.status = 0;
        /*键盘事件*/
        if (event & IAL_KEYEVENT && kernel_GetLWEvent (IAL_KEYEVENT, &lwe))
            ParseEvent (&lwe);

        if (event == 0 && kernel_GetLWEvent (0, &lwe))
            ParseEvent (&lwe);
    }

    /* printf("Quit from EventLoop()\n"); */
    return NULL;
}
/* libminigui-gpl-3.2/src/kernel/event.c */
BOOL kernel_GetLWEvent (int event, PLWEVENT lwe)
{
	/*...*/
    if (event & IAL_MOUSEEVENT) {
    	/*更新点击的坐标,会调到输入引擎的mouse_update函数中*/
        if (!IAL_UpdateMouse ())
            event &= ~IAL_MOUSEEVENT;
    }
    if (event & IAL_KEYEVENT) {
        if ((nr_keys = IAL_UpdateKeyboard ()) == 0)
            event &= ~IAL_KEYEVENT;
    }
    
	/*...*/
	/*判断是单击还是双击*/
	if ( !(oldbutton & IAL_MOUSE_LEFTBUTTON) && 
              (button & IAL_MOUSE_LEFTBUTTON) )
        {
            license_on_input();

            interval = __mg_timer_counter - time1;
            if (interval <= dblclicktime)
                me->event = ME_LEFTDBLCLICK;
            else
                me->event = ME_LEFTDOWN;
            time1 = __mg_timer_counter;

            goto mouseret;
       }

	/*...*/
}

重要的函数ParseEvent,里面会根据事件类型,上报MSG_KEYDOWN、MSG_KEYUP、MSG_MOUSEMOVE、MSG_LBUTTONDOWN、MSG_LBUTTONUP和MSG_LBUTTONDBLCLK等等事件

/* libminigui-gpl-3.2/src/kernel/init.c */
static void ParseEvent (PLWEVENT lwe)
{
	/*...*/
	/*根据事件类型进行赋值*/
	else if(lwe->type == LWETYPE_MOUSE) {
        Msg.wParam = me->status;
        Msg.lParam = MAKELONG (me->x, me->y);

        switch (me->event) {
        case ME_MOVED:
            Msg.message = MSG_MOUSEMOVE;
            break;
        case ME_LEFTDOWN:
            Msg.message = MSG_LBUTTONDOWN;
            scrolltime = __mg_timer_counter;
            scrollX = me->x;
            scrollY = me->y;
            flingX  = me->x;
            flingY  = me->y;
            break;
        case ME_LEFTUP:
            Msg.message = MSG_LBUTTONUP;
            break;
        case ME_LEFTDBLCLICK:
            Msg.message = MSG_LBUTTONDBLCLK;
            break;
        case ME_RIGHTDOWN:
            Msg.message = MSG_RBUTTONDOWN;
            break;
        case ME_RIGHTUP:
            Msg.message = MSG_RBUTTONUP;
            break;
        case ME_RIGHTDBLCLICK:
            Msg.message = MSG_RBUTTONDBLCLK;
            break;
        }
        
	/*...*/
	/*把消息保存到桌面消息队列中,QueueDeskMessage展开是kernel_QueueMessage (__mg_dsk_msg_queue, msg);*/
	/*__mg_dsk_msg_queue就是在初始化的时候创建的桌面消息队列*/
	QueueDeskMessage (&Msg);
}

下面看看是怎么把消息存到消息队列中的,在MiniGUI中通过记录readpos和writepos,判断是否有新消息,当写入一个消息writepos就加1,读了一个消息readpos就加1,如果readpos和writepos相等就表示没有消息,默认消息队列一次最多存16条消息,由DEF_MSGQUEUE_LEN宏控制,消息满了的话就会丢弃

/* libminigui-gpl-3.2/src/kernel/message.c */
BOOL kernel_QueueMessage (PMSGQUEUE msg_que, PMSG msg)
{
	/*...*/
	if (msg->message == MSG_MOUSEMOVE 
            || msg->message == MSG_NCMOUSEMOVE
            || msg->message == MSG_DT_MOUSEMOVE) {
        int readpos = msg_que->readpos;
        PMSG a_msg, last_msg = NULL;
		/*如果有新消息的话*/
        while (readpos != msg_que->writepos) {
            a_msg = msg_que->msg + readpos;
			/*检查是否是重复的消息,如果是的话就把这次的消息覆盖上一次的消息*/
            if (a_msg->message == msg->message
                        && a_msg->wParam == msg->wParam
                        && a_msg->hwnd == msg->hwnd) {
                last_msg = a_msg;
            }

            readpos ++;
            readpos %= msg_que->len;
        }

        if (last_msg) {
        	/*覆盖上一次消息*/
            last_msg->lParam = msg->lParam;
            last_msg->time = msg->time;
            goto ret;
        }
    }

	/*...*/
	/*把消息写入队列中,writepos加1,超过最大数之后就归零*/
	msg_que->msg [msg_que->writepos] = *msg;
    msg_que->writepos++;
    msg_que->writepos %= msg_que->len;
}

3. 桌面获取事件流程

在初始化的时候,开启了一个获取消息的线程DesktopMain,话不多说,上图
MiniGUI输入引擎UML图-桌面获取事件流程图
主要是一个while循环去取消息,GetMessage最终会调用到PeekMessageEx函数,取完消息就移除消息

/* libminigui-gpl-3.2/src/kernel/desktop-ths.c */
void* DesktopMain (void* data)
{
	/*...*/
	/*开启一个while循环,调用GetMessage,指定HWND_DESKTOP,每次从__mg_dsk_msg_queue队列中取一个消息*/
	while (GetMessage(&Msg, HWND_DESKTOP)) {
			/*...*/
			/*如果取到消息的话,就把消息丢给桌面事件处理函数进行分发*/
	        lRet = DesktopWinProc (HWND_DESKTOP, 
                        Msg.message, Msg.wParam, Msg.lParam);
            /*...*/
	}
}
/* libminigui-gpl-3.2/src/kernel/message.c */
BOOL PeekMessageEx (PMSG pMsg, HWND hWnd, UINT nMsgFilterMin, UINT nMsgFilterMax, 
                          BOOL bWait, UINT uRemoveMsg)
{
	/*...*/
	/*如果是QS_POSTMSG类型的消息*/
	if (pMsgQueue->dwState & QS_POSTMSG) {
		/*如果有新消息*/
        if (pMsgQueue->readpos != pMsgQueue->writepos) {
        	/*取到消息*/
            *pMsg = pMsgQueue->msg[pMsgQueue->readpos];
            SET_PADD (NULL);
            if (IS_MSG_WANTED(pMsg->message)) {
                CheckCapturedMouseMessage (pMsg);
                /*如果需要移除消息,把readpos加1就可以了*/
                if (uRemoveMsg == PM_REMOVE) {
                    pMsgQueue->readpos++;
                    pMsgQueue->readpos %= pMsgQueue->len;
                }

                UNLOCK_MSGQ (pMsgQueue);
                return TRUE;
            }
        }
        else
            pMsgQueue->dwState &= ~QS_POSTMSG;
    }
    
    /*...*/
}

下面重点说一下获取消息后怎么发送消息给应用,主要说明点击事件的传递

/* libminigui-gpl-3.2/src/kernel/desktop-comm.c */
LRESULT DesktopWinProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	/*...*/
	/*如果是鼠标事件,最后调用MouseMessageHandler函数进行处理*/
	if (message >= MSG_FIRSTMOUSEMSG && message <= MSG_LASTMOUSEMSG) {

	/*...*/
#ifdef _MGHAVE_MENU
        if (sg_ptmi) {
            if (PopupMenuTrackProc (sg_ptmi, message, x, y))
                return MouseMessageHandler (message, flags, x, y);
        }
        else
#endif
            return MouseMessageHandler (message, flags, x, y);
    }
}

static LRESULT MouseMessageHandler (UINT message, WPARAM flags, int x, int y)
{
	/*...*/
	/*获取需要接受事件的应用窗口句柄*/
	pCtrlPtrIn = gui_GetMainWindowPtrUnderPoint (x, y);
	/**/
	switch (message) {
        case MSG_MOUSEMOVE:
        	/*在MSG_LBUTTONDOWN的时候,_mgs_button_down_main_window被赋值好了
        	 *所以把MSG_MOUSEMOVE加上MSG_DT_MOUSEOFF偏移发送给应用窗口*/
            if (_mgs_button_down_main_window) {
                PostMessage ((HWND)_mgs_button_down_main_window, 
                                message + MSG_DT_MOUSEOFF, 
                                flags, MAKELONG (x, y));
            }
            break;

        case MSG_LBUTTONDOWN:
        case MSG_RBUTTONDOWN:
            if (_mgs_button_down_main_window) {
                PostMessage ((HWND)_mgs_button_down_main_window, 
                                message + MSG_DT_MOUSEOFF, 
                                flags, MAKELONG (x, y));
            }
            else if (pUnderPointer) {
				/*...*/
				/*把MSG_LBUTTONDOWN加上MSG_DT_MOUSEOFF偏移,然后发送给应用窗口*/
                PostMessage ((HWND)pUnderPointer, message + MSG_DT_MOUSEOFF,
                                flags, MAKELONG (x, y));
				/*把应用窗口句柄赋值给_mgs_button_down_main_window*/
                _mgs_button_down_main_window = pUnderPointer;
            }
        break;

        case MSG_LBUTTONUP:
        case MSG_RBUTTONUP:
			/*...*/
			/*把MSG_LBUTTONUP加上MSG_DT_MOUSEOFF偏移,发送给应用窗口*/
            if (_mgs_button_down_main_window) {
                PostMessage ((HWND)_mgs_button_down_main_window, 
                                message + MSG_DT_MOUSEOFF, 
                                flags, MAKELONG (x, y));
                                
                if (!(_mgs_down_buttons & DOWN_BUTTON_ANY)) {
                	/*_mgs_button_down_main_window赋值为NULL,等待下一次down和up事件来临*/
                    _mgs_button_down_main_window = NULL;
                    _mgs_down_buttons = DOWN_BUTTON_NONE;
                }
            }
        break;
        
        /*...*/
}

4. 应用获取事件流程

多窗口的应用,有一个while (GetMessage(&Msg, hMainWnd))就可以了,不需要每个窗口都去while,上一小节已经说过,会获取需要接受事件的应用窗口句柄然后进行事件分发,典型创建窗口代码如下

int MiniGUIMain(int argc, const char* argv[])
{
    int i = 0;
    MSG Msg;
    HWND hMainWnd;
    MAINWINCREATE CreateInfo;

	/*...*/
	/*CreateMainWindow里面会创建应用消息队列*/
    hMainWnd = CreateMainWindow(&CreateInfo);
    if (hMainWnd == HWND_INVALID) {
        return -1;
    }

    ShowWindow(hMainWnd, SW_SHOWNORMAL);

	/*开启一个while循环,循环获取应用消息队列里的消息*/
    while (GetMessage(&Msg, hMainWnd)) {
    	/*把击键消息转换为MSG_CHAR消息*/
        TranslateMessage(&Msg);
        /*把消息发往目标窗口的窗口过程*/
        DispatchMessage(&Msg);
    }

    MainWindowThreadCleanup(hMainWnd);

    return 0;
}

GetMessage上一小节已经说过,这一小节主要讲解DispatchMessage的作用,先看一下图
MiniGUI输入引擎UML图-应用获取事件流程图

/* libminigui-gpl-3.2/src/kernel/message.c */
LRESULT GUIAPI DispatchMessage (PMSG pMsg)
{
	/*...*/
	/*获取应用窗口消息处理函数*/
	if (!(WndProc = GetWndProc (pMsg->hwnd)))
        return -1;
	/*调用应用窗口消息处理函数,这也就是我们写应用的时候,为什么消息处理函数被调用的原因了*/
    lRet = WndProc (pMsg->hwnd, pMsg->message, pMsg->wParam, pMsg->lParam);

	/*...*/
}

典型的应用窗口消息处理函数

static int AcitvityMainWinProc(HWND hWnd, int message, WPARAM wParam, LPARAM lParam)
{
    static int point_count = 0;
    switch (message) {
        case MSG_CREATE:
        break;
        case MSG_PAINT: {
            HDC hdc;
            hdc = BeginPaint(hWnd);
            SetBrushColor(hdc, PIXEL_red);
            FillBox(hdc, m_point.x, m_point.y, 20, 20);
            EndPaint(hWnd, hdc);
            return 0;
        }
	case MSG_LBUTTONDOWN:
            point_count = 1;
        break;
    case MSG_MOUSEMOVE:
            point_count++;
        break;
    case MSG_LBUTTONUP:
            point_count++;
            InvalidateRect(hWnd, NULL, TRUE);
        break;
    case MSG_CLOSE: {
            DestroyMainWindow(hWnd);
            PostQuitMessage(hWnd);
            return 0;
        }
    }
	/*如果应用没有处理消息,则调用默认的消息处理函数*/
    return DefaultMainWinProc(hWnd, message, wParam, lParam);
}

上一小节说过,MSG_LBUTTONDOWN等消息是加上了MSG_DT_MOUSEOFF偏移的,并不是我们想要的MSG_LBUTTONDOWN消息,那来看看DefaultMainWinProc是怎么处理的吧,其实是一个宏,指向一个函数

/*__mg_def_proc[0]在MiniGUI初始化的时候赋值为PreDefMainWinProc函数*/
#define DefaultMainWinProc (__mg_def_proc[0])
/* libminigui-gpl-3.2/src/gui/window.c */
LRESULT PreDefMainWinProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PMAINWIN pWin = (PMAINWIN)hWnd;
	/*不同的消息,会进入不同的函数中处理,加上MSG_DT_MOUSEOFF偏移的会进入DefaultDTMouseMsgHandler函数中*/
    if (message > MSG_DT_MOUSEOFF && message <= MSG_DT_RBUTTONDBLCLK)
        return DefaultDTMouseMsgHandler(pWin, message, 
                wParam, LOSWORD (lParam), HISWORD (lParam));
    else if (message >= MSG_FIRSTMOUSEMSG && message <= MSG_NCMOUSEOFF)
        return DefaultMouseMsgHandler(pWin, message, 
                wParam, LOSWORD (lParam), HISWORD (lParam));
    else if (message > MSG_NCMOUSEOFF && message <= MSG_LASTMOUSEMSG)
        return DefaultNCMouseMsgHandler(pWin, message, 
                (int)wParam, LOSWORD (lParam), HISWORD (lParam));
	
	/*...*/
    return 0;
}

static LRESULT DefaultDTMouseMsgHandler (PMAINWIN pWin, UINT message, 
        WPARAM flags, int x, int y)
{
	/*...*/
	/*可以看到,会把消息减去MSG_DT_MOUSEOFF偏移,然后在PostMessage把消息发送到应用消息队列中
	 *下一次GetMessage就可以取到真正的消息了*/
	switch (message) {
        case MSG_DT_MOUSEMOVE:
			/*...*/
        case MSG_DT_LBUTTONDBLCLK:
        case MSG_DT_RBUTTONDBLCLK:
            if (hc_mainwin == HT_CLIENT) {
                PostMessage((HWND)pWin, 
                        message + (MSG_NCMOUSEOFF - MSG_DT_MOUSEOFF),
                        hc_mainwin, MAKELONG (x, y));
                PostMessage((HWND)pWin, 
                        message - MSG_DT_MOUSEOFF,
                        flags, MAKELONG (cx, cy));
            }
            break;
        case MSG_DT_LBUTTONDOWN:
        case MSG_DT_RBUTTONDOWN:
            if (hc_mainwin != HT_CLIENT) {
            	/*...*/
            }
            else 
            {
                PostMessage((HWND)pWin,
                        message - MSG_DT_MOUSEOFF,
                        flags, MAKELONG(cx, cy));
            }
            break;
        case MSG_DT_LBUTTONUP:
        case MSG_DT_RBUTTONUP:
            if (hc_mainwin == HT_CLIENT) {
                PostMessage((HWND)pWin, 
                        message - MSG_DT_MOUSEOFF,
                        flags, MAKELONG(cx, cy));
            }
            break;
    }
}

如果应用的消息处理线程MSG_LBUTTONDOWN是break,不是return的话,则还会调用DefaultMainWinProc,最终还会调用DefaultMouseMsgHandler,把消息传递到窗口的控件里,这里不再详细描述

5. 总结

在MiniGUI中,事件大概就是这样一个传递流程,经过一段时间的学习,对MiniGUI的框架更加了解了一些,做一些定制化的功能也更加容易,接下来还有MiniGUI绘图机制的分析和双缓冲解决切线的优化

发布了30 篇原创文章 · 获赞 28 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/anyuliuxing/article/details/90239014