2009年1月27日 星期二

_NET_WM_ICON 資料在64bit OS上之輸出

日前由於手上有一個64bit的Linux,便幫忙PCMan前輩測試了一段程式碼,茲將心得貼於下方。

此bug由來是因為Lxpanel中的工作列上,工作圖示無法正確顯示。




先來看看來自xmisc.c的原始碼:

data=alloca((pib.width*pib.height+2)*sizeof(long));
data[0]=pib.width;
data[1]=pib.height;
for(j=0;j
a=&(pib.pixelPtr[j*pib.pitch]);
r=&(a[pib.offset[0]]);
g=&(a[pib.offset[1]]);
b=&(a[pib.offset[2]]);
a=&(a[pib.offset[3]]);
p=&(data[2+j*pib.width]);
for(i=0;i
*p=(*a<<24)|(*r<<16)|(*g<<8)|*b;
p++;
a+=pib.pixelSize;
r+=pib.pixelSize;
g+=pib.pixelSize;
b+=pib.pixelSize;
}
}


這邊將這些顏色資料宣告為long並儲存

根據 freedesktop.org 規範視窗管理程式的 EWMH spec,視窗的圖示 (顯示在視窗左上角那個),是用 property 的方式儲存,其他程式可以透過讀取視窗的 _NET_WM_ICON 這個 property,來取得視窗的圖示。

EWMH spec 原文:

_NET_WM_ICON CARDINAL[][2+n]/32

This is an array of possible icons for the client. This specification does not stipulate what size these icons should be, but individual desktop environments or toolkits may do so. The Window Manager MAY scale any of these icons to an appropriate size.

This is an array of 32bit packed CARDINAL ARGB with high byte being A, low byte being B. The first two cardinals are width, height. Data is in rows, left to right and top to bottom.

照這段說明所寫,我們呼叫 Xlib 的函數 XGetWindowProperty 來取得視窗的 _NET_WM_ICON 這個 property 時,他會以一個陣列的形式傳回視窗的圖示,而陣列中的每個元素儲存的,是圖示上每個畫素的 32 位元 ARGB 值。在陣列的開頭兩個元素儲存的,分別是圖示的寬度和高度。

所以我們預期,XGetWindowProperty(); 會傳回給我們的是一個 32 位元整數的陣列,開頭兩個元素是圖示的寬度和高度,而後面的其他元素則是點陣圖的 pixels 資料。

LXPanel的taskbar.c中的原始碼則為:

if(result == Success)
{
guint32* pdata = data;
guint32* pdata_end = data + nitems;
guint32* max_icon = NULL;
guint32 max_w = 0, max_h = 0;

/* get the largest icon available. */
/* FIXME: should we try to find an icon whose size is closest to
* iw and ih to reduce unnecessary resizing? */
while(pdata + 2 < w =" pdata[0];" h =" pdata[1];" size =" (w" w="%d," h="%d," size="%d">


這邊是使用guint32來撈出資料。在32bit OS上,這段程式碼很正常;但在64bit OS上,我們卻發現回傳的值是64bit的array

底下這段測試程式碼來自好友rick68
int main(void)
{
printf("sizeof(int) = [%d]\n", sizeof(int));
printf("sizeof(long) = [%d]\n", sizeof(long));
printf("sizeof(long long) = [%d]\n", sizeof(long long));

return 0;
}


將這個程式在32bit OS下編譯執行,產生結果為:
sizeof(int) = [4]
sizeof(long) = [4]
sizeof(long long) = [8]

在64bit OS下編譯執行,結果則為:
sizeof(int) = [4]
sizeof(long) = [8]
sizeof(long long) = [8]


這邊可以發現,long的長度在32bit OS下是32bit,而在64bit OS下則為64bit。

查詢 XgetWindowPropert 的文件,找到如下的敘述:

If the returned format is 8, the returned data is represented as a char array. If the returned format is 16, the returned data is represented as a array of short int type and should be cast to that type to obtain the elements. If the returned format is 32, the property data will be stored as an array of longs (which in a 64-bit application will be 64-bit values that are padded in the upper 4 bytes).


如果傳回的 format 是 32,代表資料是 long integer 陣列 (在 64 位元程式內就會是 64 位元)。也就是實際上資料只有 32 bit,但是他傳回的會是加上 4 bytes 作 padding 的 64 bit 整數,所以程式的處理必須用 long int 的 array,而不是 32bit int。

因此若使用guint32儲存,在64bit OS上就會發生錯誤。將guint32改用gulong來撈取資料後此問題就正常了,也可正常顯示icon



參考資料
http://code.google.com/p/gatos2/source/browse/trunk/avview/xmisc.
http://mlblog.osdir.com/video.gatos.devel/2005-10/msg00031.shtml
http://www.mail-archive.com/rxvt-unicode@lists.schmorp.de/msg00321.html
http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#id2552223
http://linux.die.net/man/3/xchangeproperty

特別感謝PCMan大哥檢視並修改此篇文章!

0 意見: