minigui源碼學習

前言:

個人習慣學習源碼先從編譯結構瞭解代碼模塊,所以先從編譯結構開始瞭解。
如果編譯角度搞不定,就代碼開始運行時,各個模塊初始化的角度開始瞭解。

學習源碼——編譯角度

下載源碼

    git clone https://github.com/VincentWei/minigui.gitminigui

編譯

minigui使用autotools工具來生成Makefile文件,然後再執行make編譯。

一般操作流程是:

autoconf
auto make
./configure
make

但是不起作用啊,autoconf執行失敗。

於是直接運行autogen.sh,然後autoconf,automake,./configure都可運行了,但是make運行失敗。

想了想,還是放棄從編譯角度切入了吧

  1. 自動生成的Makefile太過複雜,難以解析
  2. 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;
...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章