最近做个东西,需要在X11环境下创建系统右下角的图标,在网上搜了一些资料,记录一下,也许会有用。
首先搜索system tray X11,找到了这个协议:
https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-0.3.html
这个协议应该是X11协议的一部分,X11的基础知识和协议就不多说了(因为我也不太明白),这个system tray的协议基本上的内容有这么几点:

  1. system tray是X11窗口管理器中的组件(在Xubuntu上是Panel里的Notification Area)需要满足的协议
  2. 要创建右下角system tray 图标,首先需要创建一个窗口(通过X11),然后获取桌面管理器的 system tray 组件,向system tray 发送 SYSTEM_TRAY_REQUEST_DOCK 的消息,将窗口嵌入到消息栏中。

获取桌面管理器system tray的代码:

Atom net_system_tray = XInternAtom(dis,"_NET_SYSTEM_TRAY_S0",False);
Window system_tray = XGetSelectionOwner(dis,net_system_tray);

向system tray发送消息的代码:

XEvent ev;

memset (&ev, 0, sizeof (ev));
ev.xclient.type = ClientMessage;
ev.xclient.window = system_tray;  //获取到的SystemTray窗口句柄
ev.xclient.message_type = XInternAtom (dpy, "_NET_SYSTEM_TRAY_OPCODE", False);
ev.xclient.format = 32;
ev.xclient.data.l[0] = CurrentTime;
ev.xclient.data.l[1] = SYSTEM_TRAY_REQUEST_DOCK;
ev.xclient.data.l[2] = win;       //需要隐藏到系统消息栏的窗口句柄
ev.xclient.data.l[3] = data2;
ev.xclient.data.l[4] = data3;

XSendEvent (dpy, w, False, NoEventMask, &ev);

从找到的资料上看,到这一步,应该就可以在system tray里创建了隐藏窗口对应的图标,可是在我的Xubuntu 16.04 上,窗口被隐藏了,但图标却没有出现。

======= update 2020.4.14 ======
X11上的问题是特别小众的问题,google能找到的资料也很少,大部分是gtk或QT如何创建system tray的资料。经过了两天的调试,最终发现了问题:

  1. 在XSendEvent之后,必须要sleep一段时间,才能调用XMapWindow把创建的图标窗口Map到系统中,否则图标就是死活看不到。
  2. 创建的窗口需要设置XSizeHints,并且把窗口设置为 _XEMBED_INFO 类型
  3. 右下角出现的图标实际就是创建出的窗口,并不是窗口本身的图标

这里就把完整代码贴上来,方便需要的人吧。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "unistd.h"

#define WIDTH 22
#define HEIGHT 22

Display *dis;
int s;
Window win;
GC gc;

void send_message( Display* dpy, Window w,    long message, long data1 , long
data2 , long data3 )
{
    XEvent ev;

    memset (&ev, 0, sizeof (ev));
    ev.xclient.type = ClientMessage;
    ev.xclient.window = w;
    ev.xclient.message_type =
        XInternAtom (dpy, "_NET_SYSTEM_TRAY_OPCODE", False);
    ev.xclient.format = 32;
    ev.xclient.data.l[0] = CurrentTime;
    ev.xclient.data.l[1] = message;
    ev.xclient.data.l[2] = data1;
    ev.xclient.data.l[3] = data2;
    ev.xclient.data.l[4] = data3;

    XSendEvent (dpy, w, False, NoEventMask, &ev);
    XSync (dpy, False);
}

void close_x() {
/* it is good programming practice to return system resources to the 
   system...
*/
    Display *dis = XOpenDisplay(0);
    XFreeGC(dis, gc);
    XDestroyWindow(dis,win);
    XCloseDisplay(dis);    
    exit(1);                
}

void redraw() {
    //这里实际绘制右下角图标!!!
       XSetForeground(dis,gc,0xffff00ff);
    XFillRectangle(dis,win,gc,0,0,WIDTH,HEIGHT);
    XSync(dis,s);
    XFlush(dis);
}


int main( int argc, char **argv )
{
    unsigned long buffer[(2+WIDTH * HEIGHT)];
    dis = XOpenDisplay(0);
    s = DefaultScreen(dis);
    Atom net_wm_icon = XInternAtom(dis, "_NET_WM_ICON", False);
    Atom cardinal = XInternAtom(dis, "CARDINAL", False);
    XEvent e;
    XSizeHints *  size_hints;

    if ( !( size_hints  = XAllocSizeHints() )) {
        fprintf(stderr, "Couldn't allocate memory.\n");
        return 0;
    }


    //创建一个窗口,大小就是图标大小,用于绘制右下角图标
    win = XCreateWindow(dis,RootWindow(dis,0), 0,0,16,16,1, CopyFromParent, CopyFromParent, CopyFromParent, 0, 0);   
    gc = XCreateGC(dis, win, 0,0);


    size_hints->flags       = PWinGravity | PMinSize;
    size_hints->min_width   = 22;
    size_hints->min_height  = 22;
    size_hints->win_gravity = NorthWestGravity;
    XSetWMProperties(dis, win, 0, 0, NULL, 0, size_hints, 0, 0);

    char * wm_name = "trayicon";
    XChangeProperty( dis, win,XInternAtom(dis, "_NET_WM_NAME", False),XInternAtom(dis, "UTF8_STRING", False),8, PropModeReplace, (unsigned char *) wm_name,strlen(wm_name));

    //窗口接受的事件
    XSelectInput(dis, win, ExposureMask | KeyPressMask | KeyReleaseMask |ButtonPressMask | ButtonReleaseMask  | EnterWindowMask | LeaveWindowMask | StructureNotifyMask);


    //把窗口设置为XEmbed模式
    long data[2];
    data[0] = 0;
    data[1] = 1;
    Atom embed_type = XInternAtom(dis,"_XEMBED_INFO",False);
    XChangeProperty(dis,win,embed_type,embed_type,32,PropModeReplace,(const unsigned char *)data,2);

    //获取system_tray窗口管理器,向system_tray窗口发送DOCK消息,将之前创建的窗口嵌入system_tray中
    Atom net_system_tray = XInternAtom(dis,"_NET_SYSTEM_TRAY_S0",False);
    Window tray_owner;
    tray_owner = XGetSelectionOwner(dis,net_system_tray);
    printf("tray_owner:0x%0x win:0x%0x  _NET_SYSTEM_TRAY_S0 %d \n",tray_owner,win,net_system_tray);
    send_message(dis,tray_owner,0,win,0,0);
    //一定要sleep,之后再XMapWindow,否则出不来
    usleep(10000);
    XMapWindow(dis, win);
    XSync(dis,False);
    XFlush(dis);

    //窗口事件处理,在Expose事件中绘制窗口,即右下角图标
    while(1) {
        XNextEvent(dis, &e);
        printf("event type:%d\n",e.type);

        if (e.type==Expose && e.xexpose.count==0) {
            /* the window was exposed redraw it! */
            redraw();
        }
    } 
}

标签: none

添加新评论