这段时间学习了一下MiniGUI中的事件传递机制,包括从内核驱动上报事件到MiniGUI分发事件这一整条通路,在嵌入式平台上,获取触摸事件可以使用tslib框架,通过tslib获取到事件之后,会把消息保存在桌面消息队列中,然后进一步分发到应用消息队列,我会在这篇文章中详细说明
IAL全拼是Input Abstract Layer,也就是输入抽象层,在MiniGUI中抽象了很多输入设备,比如tslibial、ipaqh3600ial、consoleial和nexusial等等,本文以tslibial为例进行说明
首先看一下初始化流程图,有一个初步的印象
整个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);
/*...*/
}
/*...*/
}
在初始化的时候,开启了一个接受并解析内核事件的线程EventLoop,这一小节重点分析改函数,先看图
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;
}
在初始化的时候,开启了一个获取消息的线程DesktopMain,话不多说,上图
主要是一个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;
/*...*/
}
多窗口的应用,有一个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的作用,先看一下图
/* 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,把消息传递到窗口的控件里,这里不再详细描述
在MiniGUI中,事件大概就是这样一个传递流程,经过一段时间的学习,对MiniGUI的框架更加了解了一些,做一些定制化的功能也更加容易,接下来还有MiniGUI绘图机制的分析和双缓冲解决切线的优化