前言:
个人习惯学习源码先从编译结构了解代码模块,所以先从编译结构开始了解。
如果编译角度搞不定,就代码开始运行时,各个模块初始化的角度开始了解。
学习源码——编译角度
下载源码
git clone https://github.com/VincentWei/minigui.gitminigui
编译
minigui使用autotools工具来生成Makefile文件,然后再执行make编译。
一般操作流程是:
autoconf
auto make
./configure
make
但是不起作用啊,autoconf执行失败。
于是直接运行autogen.sh,然后autoconf,automake,./configure都可运行了,但是make运行失败。
想了想,还是放弃从编译角度切入了吧
- 自动生成的Makefile太过复杂,难以解析
- autotools工具也不熟
学习源码——运行角度
初始化入口:
MiniGUIAppMain
MiniGUIAppMain是用例的入口函数,所以从这个函数开始顺腾摸瓜,了解代码的模块和结构
MiniGUIAppMain的定义:
#define MiniGUIMain \
MiniGUIAppMain (int args, const char* argv[]); \
int main_entry (int args, const char* argv[]) \
{ \
int iRet = 0; \
if (InitGUI (args, argv) != 0) { \
return 1; \
} \
iRet = MiniGUIAppMain (args, argv); \
TerminateGUI (iRet); \
return iRet; \
} \
int MiniGUIAppMain
从代码可知,MiniGUIMain 会先运行InitGUI(),然后再运行MiniGUIAppMain,界面完了以后,还会调用TerminateGUI回收界面资源
模块初始化
InitGUI()总共调用了下面20多个初始化函数,前置工作准备
初始化各个模块初始化的函数,以及模块的流程如下:
1. _sysvipc_mutex_sem_init
2. mg_InitSliceAllocator
3. mg_InitFixStr
4. mg_InitMisc
5. mg_InitGAL
6. InstallSEGVHandle
7. mg_InitSystemRes
8. mg_InitGDI
9. mg_InitScreenDC
10. license_create
11. mg_InitCursor
12. mg_InitLWEvent
13. mg_InitLFManager
14. mg_InitMenu
15. mg_InitControlClass
16. mg_InitAccel
17. mg_InitDesktop
18. mg_InitFreeQMSGList
19. createThreadInfoKey
20. SystemThreads
21. SetKeyboardLayout
22. SetCursor
23. SetCursorPos
24. mg_TerminateMgEtc
模块作用了解
一般来说,每个模块会有个小的说明文档,但是没有找到,所以算了。
看源码应该大致能了解其作用,如果不能了解其作用,则跟着用例的创建窗口的代码往下跑,看模块是怎么用的来学习他吧。
模块作用总结如下:
1. _sysvipc_mutex_sem_init
信号量初始化,信号量是跨进程的,用于进程间共享内存锁。
这个函数内部清空了许多sem锁,然后重新创建。
功能:初始化锁
2. mg_InitSliceAllocator
作用尚未了结清楚,初步理解为窗口内存碎片整理
3. mg_InitFixStr
功能:字串补全,内存占用2^n~2^(n+1)时,空间大小补全为2^(n+1)
4. mg_InitMisc
功能:零零散散的杂项功能初始化,观其实现时是存取配置项功能。还有个clipboard剪辑版,暂时不了解
5. mg_InitGAL
功能:创建一个video设备,并创建一个surface对象与video设备互相指向。video设备初始化时,会从底层map一段内存出来,然后由surface对其进行管理。
读写surface时会直接改变界面。
6. InstallSEGVHandle
功能:linux 系统信号监听及处理
7. InitSystemRes
功能:建一张存数据的hash表,用于存储资源。添加bmp内部资源,添加icon资源,添加样式资源
8. InitGDI
全称:global graphic interface
功能:包括了所有图形操作的接口
9. InitScreenDC
功能:
1. 初始化InitBlockDataHeap(__mg_FreeClipRectList)
2. dc_InitClipRgnInfo,初始化dc池,后续取hdc就从这里取
3. 初始化全局dc,__mg_screen_dc
4. 初始化全局dc,__mg_screen_sys_dc
10. license_create
功能:加载license相关资源,具体作用未明
11. InitCursor
功能:初始化鼠标资源
12. mg_InitLWEvent
功能:初始化IAL,事件输入模块
13. mg_InitLFManager
功能:初始化系统默认样式
14. mg_InitMenu
功能:菜单相关
1. 初始化InitBlockDataHeap(MBHeap),作用未知
2. 初始化InitBlockDataHeap(MIHeap),作用未知
3. 初始化InitBlockDataHeap(TMIHeap),作用未知
15. mg_InitControlClass
功能:注册系统默认样式的控件
16. mg_InitAccel
功能:
1. 初始化InitBlockDataHeap(ACHeap),作用未知
2. 初始化InitBlockDataHeap(AIHeap),作用未知
17. mg_InitDesktop
功能:
1. 初始化z序的数据结构
2. 初始化InitBlockDataHeap(sg_FreeClipRectList)
3. 初始化InitBlockDataHeap(sg_FreeInvRectList)
4. 初始化__mg_hwnd_desktop,也就是DESKTOP_HWND
5. 初始化桌面的消息队列,__mg_hwnd_desktop->pMessages = __mg_dsk_msg_queue
6. 设置系统默认的控件样式
18. mg_InitFreeQMSGList
功能:初始化InitBlockDataHeap(QMSGHeap),作用未知
19. createThreadInfoKey
功能:初始化主线程的pthread_key,使主线程也有对应的线程标志。
后续创建主窗口时,也是根据该线程标志来获取消息队列的,也许还有很多和主线程pthread_key相关的模块
20. SystemThreads
功能:创建两个线程,一个是DesktopMain,另一个是EventLoop
DesktopMain:不停的接收并处理消息,凡是发给桌面的消息,都在该线程处理
EventLoop:运行IAL模块,不停地获取事件
21. SetKeyboardLayout
功能:初始化键盘布局相关,作用未知
22. SetCursor&SetCursorPos
功能:预测设置光标,作用未知
23. mg_TerminateMgEtc
功能:结束配置管理模块,作用未知
GAL模块解析
按照从上向下的顺序,第一我先详细说明GAL模块。
GAL模块的初始化,我就不按初始化的流程来说明,我先将模块拆解成一个个小模块,然后介绍完小模块以后,再说明整个初始化的流程。
video驱动管理模块——video-drivers
video会有各种不同厂商的设备,不同厂商的设备会有不同的驱动,所以video-drivers模块包含了很多video视频设备的驱动
所有的这些video驱动模块都保存在boot_strap(VideoBootStrap**)这个全局变量当中
src/newgal/video.c 中可以看到这个boot_strap这个全局变量包含了哪些驱动:
/* Available video drivers */
static VideoBootStrap *bootstrap[] = {
#ifdef _MGGAL_DUMMY
&DUMMY_bootstrap,
#endif
#ifdef _MGGAL_FBCON
&FBCON_bootstrap,
#endif
#ifdef _MGGAL_QVFB
&QVFB_bootstrap,
#endif
...
NULL
};
然后我们只挑FBCON_bootstrap,稍微深入了解下。
fbcon驱动模块
fbcon模块的源码目录在:src\newgal\fbcon\
我们先看fbvideo.c文件,FBCON_bootstrap的定义:
VideoBootStrap FBCON_bootstrap = {
"fbcon", "Linux Framebuffer Console",
FB_Available, FB_CreateDevice
};
GAL模块初始化,选中FBCON_bootstrap驱动时,会调用FB_CreateDevice获取一个video设备对象,我们称之为’video’
以后我们看看FB_CreateDevice,对返回的video设备对象做了哪些初始化:
this->VideoInit = FB_VideoInit;
this->ListModes = FB_ListModes;
this->SetVideoMode = FB_SetVideoMode;
this->SetColors = FB_SetColors;
this->VideoQuit = FB_VideoQuit;
this->AllocHWSurface = FB_AllocHWSurface;
this->CheckHWBlit = NULL;
this->FillHWRect = NULL;
this->SetHWColorKey = NULL;
this->SetHWAlpha = NULL;
this->UpdateRects = NULL;
this->FreeHWSurface = FB_FreeHWSurface;
this->GetFBInfo = FB_GetFBInfo;
this->free = FB_DeleteDevice;
这些函数就是video设备对象可以执行的操作了
操作很多也很复杂,但是我只了解了FB_VideoInit和FB_SetVideoMode两个函数的实现
因为初始化就用到了这两个函数
FB_VideoInit
FB_VideoInit函数中,映射了一段内存到video->hidden->mapped_mem:
#define mapped_mem (this->hidden->mapped_mem)
mapped_mem = mmap(NULL, mapped_memlen,
PROT_READ|PROT_WRITE, MAP_SHARED, console_fd, 0);
以及计算了一个偏移地址:
ioctl(console_fd, FBIOGET_FSCREENINFO, &finfo)
/* Memory map the device, compensating for buggy PPC mmap() */
mapped_offset = (((long)finfo.smem_start) -
(((long)finfo.smem_start)&~(getpagesize () - 1)));
mapped_memlen = finfo.smem_len+mapped_offset;
FB_SetVideoMode
FB_SetVideoMode函数中,对传入的参数current(也就是surface)进行了初始化
/* Set up the new mode framebuffer */
current->flags = (GAL_FULLSCREEN|GAL_HWSURFACE);
current->w = vinfo.xres;
current->h = vinfo.yres;
current->pitch = finfo.line_length;
current->pixels = mapped_mem+mapped_offset;
其实SetVideoMode就有点像告诉video驱动,我要要显示的图像的大小,格式,你给我一张合适的画布放surface里,我自己绘图
总结:映射的这段buffer,会被video控制器以一定频率不停的读取显示到video设备上
而这段内存又保存在了surface中,所以操作surface->pixels,就有点像直接在设备界面上绘图
surface模块
源码位置:src/newgal/surface.c
surface模块的初始化入口是GAL_CreateRGBSurface函数,这个函数里面会new一个surface对象,
然后由GAL_VideoSurface这个全局指针接管。据我目前观察,之后大多数情况都是使用的GAL_VideoSurface
GAL初始化流程
文件入口:src\newgal\newgal.c
mg_InitGAL:
GAL_VideoInit:
GAL_GetVideo:
FBCON_bootstrap->create:
FB_CreateDevice:
创建一个video设备对象,并返回
video->VideoInit:
映射一段buffer保存在video->hidden->mapped_mem中
初始化全局变量:
current_video = video
current_video->screen = NULL
GAL_VideoSurface = GAL_CreateRGBSurface(...):
创建一个surface
初始化全局变量:
GAL_VideoSurface->video = current_video;
__gal_screen = GAL_SetVideoMode:
video->SetVideoMode:
根据mode设置surface内容,并返回
初始化GDI模块的全局变量:SysPixelIndex
IAL模块解析
函数入口:mg_InitLWEvent->mg_InitIAL
文件入口:src/kernel/event.c->src/ial/ial.c
负责事件获取,并转成windows message上传到设备端
IAL引擎
同video有众多设备驱动一样,IAL也有众多引擎
根据配置里的IAL引擎名称,IAL模块会选择其中一个引擎赋值到全局变量__mg_cur_input中
然后使用__mg_cur_input来进行引擎操作,一下是IAL所有的引擎,我只取libinput说明一下
static INPUT inputs [] =
{
/* General IAL engines ... */
#ifdef _MGIAL_QVFB
{"qvfb", InitQVFBInput, TermQVFBInput},
#endif
...
#ifdef _MGIAL_LIBINPUT
{"libinput", InitLibInput, TermLibInput},
#endif
/* ... end of general IAL engines */
};
libinput
libinput的入口函数是InitLibInput,IAL获取到libinput之后,便会调用该接口
这里我只说明两个函数,一个是初始化函数InitLibInput,以及wait_event_ex
因为初始化会用到InitLibInput,以及之后读取事件会调用wait_event_ex
InitLibInput
InitLibInput对于input引擎的初始化:
...
input->update_mouse = mouse_update;
input->get_mouse_xy = mouse_getxy;
input->set_mouse_xy = mouse_setxy;
input->get_mouse_button = mouse_getbutton;
input->set_mouse_range = mouse_setrange;
input->suspend_mouse= input_suspend;
input->resume_mouse = input_resume;
input->update_keyboard = keyboard_update;
input->get_keyboard_state = keyboard_getstate;
input->suspend_keyboard = input_suspend;
input->resume_keyboard = input_resume;
input->set_leds = set_leds;
input->wait_event_ex = wait_event_ex;
...
wait_event_ex
wait_event_ex可以获取事件,并返回事件的类型
IAL初始化流程
mg_InitIAL:
InitLibInput:
初始化__mg_cur_input,并给其接口赋初值,之后便是用这个全局对象获取事件
SystemThreads模块解析
函数入口:SystemThreads
文件入口:src/kernel/init.c
该模块主要是运行起桌面模块和IAL模块,将这两者关联到一块
创建线程DesktopMain
初始化桌面
调用init_desktop_win函数,初始化桌面参数,如下:
...
static MAINWIN desktop_win;
pDesktopWin = &desktop_win;
pDesktopWin->pMessages = __mg_dsk_msg_queue;
pDesktopWin->MainWindowProc = DesktopWinProc;
...
pDesktopWin->pMainWin = pDesktopWin;
pDesktopWin->we_rdr = __mg_def_renderer;
__mg_hwnd_desktop = (HWND)pDesktopWin;
__mg_dsk_win = pDesktopWin;
...
注意:
#define HWND_DESKTOP __mg_hwnd_desktop
也就是说HWND_DESKTOP就是桌面本身,类型是MAINWIN
消息处理
获取并处理消息的步骤如下:
while (GetMessage(&Msg, HWND_DESKTOP)) {
...
DesktopWinProc (HWND_DESKTOP,
Msg.message, Msg.wParam, Msg.lParam);
...
}
创建线程EventLoop
循环读消息
IAL_WaitEvent
发消息
发送消息到HWND_DESKTOP桌面的毁掉函数中
control模块解析
函数入口:mg_InitControlClass
文件入口:src\gui\ctrlclass.c
button控件
文件入口:src\control\button.c
函数入口:RegisterButtonControl
控件有很多自定义模块,我这里就专门挑button说下
控件的绘制是在WinProc中的WM_PAINT中进行的,RegisterButtonControl函数中会
将WNDCLASS类型的对象填上一些参数,然后调用AddNewControlClass。具体代码如下:
BOOL RegisterButtonControl (void)
{
WNDCLASS WndClass;
WndClass.spClassName = CTRL_BUTTON;
WndClass.dwStyle = WS_NONE;
WndClass.dwExStyle = WS_EX_NONE;
WndClass.hCursor = GetSystemCursor (IDC_ARROW);
WndClass.iBkColor =
GetWindowElementPixel (HWND_NULL, WE_MAINC_THREED_BODY);
WndClass.WinProc = ButtonCtrlProc;
return AddNewControlClass (&WndClass) == ERR_OK;
}
AddNewControlClass函数会根据WndClass的内容创建一个CTRLCLASSINFO结构的对象,然后将其保存到ccitable链表中
初始化流程
BOOL mg_InitControlClass ()
{
int i;
for (i=0; i<LEN_CCITABLE; i++)
ccitable[i] = NULL;
// Register system controls here.
#ifdef _MGCTRL_STATIC
if (!RegisterStaticControl ())
return FALSE;
#endif
#ifdef _MGCTRL_BUTTON
if (!RegisterButtonControl())
return FALSE;
#endif
...
}
GDI模块初始化
入口函数有两个:mg_InitScreenDC,mg_InitGDI
入口文件:src/newgdi/gdi.c
前面模块了解部分已经初步学习,所以就不写了
mg_InitScreenDC
dc_InitClipRgnInfo
初始化DC池,将每一个DC和surface挂钩,具体初始化如下:
for (i=0; i<DCSLOTNUMBER; i++) {
/* Local clip region */
InitClipRgn (&DCSlot[i].lcrgn, &__mg_FreeClipRectList);
MAKE_REGION_INFINITE(&DCSlot[i].lcrgn);
/* Global clip region info */
DCSlot[i].pGCRInfo = NULL;
DCSlot[i].oldage = 0;
/* Effective clip region-- 有效剪辑区,暂时不了解 */
InitClipRgn (&DCSlot[i].ecrgn, &__mg_FreeClipRectList);
}
dc_InitScreenDC
调用两次,初始化__mg_screen_dc和__mg_screen_sys_dc
具体初始化的内容,我提炼了一下,如下:
1. pdc->DataType = TYPE_HDC;
2. 初始化bkcolor
3. 初始化画笔颜色,和样式
4. 初始化画刷颜色
5. 初始化DevRC,设备可显示屏幕大小
6. pdc->surface = surface;
7. pdc->cur_dst = pdc->surface->pixels;
...