2008年5月25日 星期日

讓我們輕鬆自在設計自己的 LXPanel Plugin

Standard
今年度『LXDE』發展蓬勃,陸續有新血加入,無論是國內外各個 Distribution 的 Package Maintainer 亦或是 Developer、翻譯者,都讓整個 Project Team 充滿熱血的氣氛。 除了已加入的成員外,其他持有興趣者也越來越多,開始有人提出『如何讀 LXDE 的 Source Code』、『該從哪對 LXDE 著手』等議題。充份顯示出目前 LXDE 缺少 Development Primer 和相關開發文件。

其實,只要你願意,你可以從最困難又複雜的『PCManFM』和『LXPanel』逐行讀起,這似乎是一種方法,但我並不建議用這種痛苦的方法參與 LXDE 的計劃。如果真的想讀一個完整的 Source Code,極為簡單的『LXSession-lite』是一個不錯的選擇。不過,就算完全讀通『LXSession-lite』 這類子專案,一般人還是無法在上面有所著力,因為這類子專案之所以簡單,是因為功能需求不複雜且沒有太多需要擴充的空間。不過,要是有人純粹想練功夫, Just do it!

那麼,如何開始在 LXDE 上開發程式呢?建議從 LXPanel Plugin 上起步最佳。就如同研究『Linux Kernel』 一般,入門的第一步通常都是『Writing Module』。你不用完全了解 LXPanel 的架構,只要遵守規範,就可以在上面開發你自己的 Plugin/Module,其撰寫就如同寫一般的 GTK+ 程式,非常容易。

撰寫 Plugin 的準備,是先下載 LXPanel 的 Source Code,然後在『src/plugins』之下建立一個新 Plugin 的目錄,在這次範例中我們建立一個『src/plugins/explugin』目錄,並將程式碼放在裡面。

這邊講一個簡單的 LXPanel Plugin 範例,其功能是載入並顯示一張 PNG 圖檔在 LXPanel 上。

這裡是原始程式碼〔src/plugins/explugin/explugin.c〕:
#include <stdlib.h>
#include <glib/gi18n.h>

#include "panel.h"
#include "misc.h"
#include "plugin.h"
#include "dbg.h"

typedef struct {
GtkWidget *main;
GtkTooltips *tip;
} ex_plugin;

static int
explugin_constructor(Plugin *p, char** fp)
{
ex_plugin *exp;

ENTER;

/* 建立一塊記憶體 */
exp = g_slice_new0(ex_plugin);

g_return_val_if_fail(exp != NULL, 0);

/* 設定 Plugin 的私有記憶體區塊 */
p->priv = exp;

/* 讀取一張圖並建立一個 GtkWidget */
exp->main = gtk_image_new_from_file("/usr/share/lxpanel/images/example.png");

/* 顯示 GtkWidget 的所有內容 */
gtk_widget_show_all(exp->main);

/* 建立一個 tooltips */
exp->tip = gtk_tooltips_new();
#if GLIB_CHECK_VERSION( 2, 10, 0 )
g_object_ref_sink( exp->tip );
#else
g_object_ref( exp->tip );
gtk_object_sink( exp->tip );
#endif

/* 將已建立的 Plugin 內容加入並放在 LXPanel 上 */
p->pwid = exp->main;

RET(1);
}

static void
explugin_destructor(Plugin *p)
{
ex_plugin *exp = (ex_plugin *)p->priv;

ENTER;

/* 釋放 tooltips */
g_object_unref(exp->tip);
/* 釋放 GtkWidget */
gtk_widget_destroy(exp->main);
/* 釋放私有記憶體區塊 */
g_slice_free(ex_plugin, exp);

RET();
}

/* LXPanel Plugin 的相關設定 */
PluginClass explugin_plugin_class = {
fname: NULL,
count: 0,

type : "explugin",
name : N_("Example Plugin"), /* Plugin 的名稱 */
version: "1.0",
description : N_("This is a Example Plugin"), /* Plugin 的簡介和描述 */

constructor : explugin_constructor, /* Plugin 建立時的函式 */
destructor : explugin_destructor, /* Plugin 被釋放時的函式 */
config : NULL,
save : NULL,
orientation : NULL
};


除程式碼外,還得建立一個 Makefile 的設定〔src/plugins/explugin/Makefile.am 〕:
INCLUDES = \
-I. \
-I$(top_srcdir)/src \
$(PACKAGE_CFLAGS) \
$(G_CAST_CHECKS)

module_LTLIBRARIES = explugin.la

moduledir = $(libdir)/lxpanel/plugins

explugin_la_SOURCES = \
explugin.c

explugin_la_LIBADD = \
$(PACKAGE_LIBS)

explugin_la_LDFLAGS = \
-module \
@LXPANEL_MODULE@


加上設定〔粗體字部份〕,讓 Autotools 去產生 explugin 的 Makefile〔./configure.ac〕:
AC_CONFIG_FILES([
Makefile
src/Makefile
src/plugins/Makefile
src/plugins/netstatus/Makefile
src/plugins/netstat/Makefile
src/plugins/volume/Makefile
src/plugins/volumealsa/Makefile
src/plugins/cpu/Makefile
src/plugins/deskno/Makefile
src/plugins/batt/Makefile
src/plugins/kbled/Makefile
src/plugins/xkb/Makefile
src/plugins/explugin/Makefile
po/Makefile.in
data/Makefile
data/default/panels/panel
man/Makefile
])


編譯 Plugin 的方法〔在 LXPanel 的原始碼根目錄下〕:
# ./autogen.sh
# ./configure
# cd src/plugins/explugin
# make


測試 Plugin〔在 src/plugins/explugin 目錄下〕:
# make install
# lxpanelctl restart

* 之後可在 LXPanel 的工作列元件清單看到『Example Plugin』

現在來談到程式碼的說明,LXPanel Plugin 有幾種 handler 設定:
constructor - 指向 Plugin 建立時的函式
destructor - 指向 Plugin 被釋放時的函式
config - 指向 Plugin 設定視窗的處理函式
save - 指向 Plugin 儲存設定的處理函式
orientation - 指向直向或橫向的處理函式

若某些 handler 對此 Plugin 非必要,可設為 NULL。一般來說,一個正常的 Plugin 最少要有 constructor 和 destructor 的 handler,可參考本文的範例程式。至於 config 、save 以及 orientation 的部份,由於牽涉到設定檔等等問題,要花不少篇幅說明,所以在此先不詳述,日後有機會再撰文補上。

LXPanel 對 Plugin 有一個設計 -『私有記憶體區塊』,顧名思義就是 Plugin 私有的記憶體存取空間,可讓 Plugin 裡每一部份的程式存取和共用。其原理是 LXPanel 提供了一個『priv 指標』,讓我們可以指向自己為 Plugin 建立的記憶體區塊。所以,在使用上我們不必再煩腦函式之間的傳值問題,直接透過標準的『priv 指標』去存取共用區的資料即可。

後記
截至這篇文章為止,LXPanel 還未提供一個完整的 plugin-dev package,所以我們在開發 Plugin 時需要 LXPanel 的 Source Code,而且必須要在『src/plugins/*』底下做編譯。