在xlib的窗口系统中,当一个窗口创建后,可以对窗口进行鼠标点击,键盘输入,改变窗口位置,改变窗口大小等操作。所有这些操作X Server都会事件的方式进行处理。客户端程序可以通过XPending,XNextEvent等c语言来查询当前窗口是否有待处理的消息事件。其中XPending是非阻塞的方式,调用立即返回;如果程序中没有待处理事件XPending返回0,如果有待处理事件,则返回值为待处理事件条目数。XNextEvent会阻塞程序的调用,当程序中没有待处理事件时,程序会阻塞在XNextEvent调用处;当有待处理事件时,XNextEvent从事件队列中一次取出一个事件。下面是一个xlib事件示例程序。
1.事件处理
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
Display *display;
Window window;
int screen;
XEvent event;
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "无法打开X显示器\n");
exit(1);
}
screen = DefaultScreen(display);
window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 400, 300, 1,
BlackPixel(display, screen), WhitePixel(display, screen));
/* 选择要接收的事件类型 */
XSelectInput(display, window, ExposureMask | KeyPressMask);
/* 显示(映射)窗口 */
XMapWindow(display, window);
/* 事件循环 */
while (1) {
XNextEvent(display, &event);
/* 绘制消息 */
if (event.type == Expose) {
printf("Expose...\n");
}
/* 处理退出条件 */
if (event.type == KeyPress){
printf("Key Pressed...\n");
break;
}
}
XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
以上程序中,相较于第一个简单的显示窗口程序。在这个程序我们处理xlib窗口系统的Expose和KeyPress事件。Expose是窗口被显示后绘制事件,KeyPress是当我们按下键盘的任意一个按键时,X Server产生的事件。上面的程序中用到了一个新的函数XSelectInput,这个函数的作用就是设置窗口处理的事件类型。以上ExposureMask和KeyPressMask,是设置我们创建的window窗口处理键盘和Expose事件。若我们把上面程序XSelectInput中的KeyPressMask去掉。当我们按下键盘上的按键时,X Server不会产生KeyPress事件。
编译以上程序,运行编译的程序。若我们在控制台中启动程序,窗口显示后,在控制台我们能够看到打印的Expose…字符串。按下键盘任意一个键,控制台打印Key Pressed…,然后程序运行结束。
在上面的程序中,XSelectInput可以设置的事件掩码有很多。常见的有如下
#define KeyPressMask (1L<<0)
#define KeyReleaseMask (1L<<1)
#define ButtonPressMask (1L<<2)
#define ButtonReleaseMask (1L<<3)
#define EnterWindowMask (1L<<4)
#define LeaveWindowMask (1L<<5)
#define PointerMotionMask (1L<<6)
#define Button1MotionMask (1L<<8)
#define Button2MotionMask (1L<<9)
#define Button3MotionMask (1L<<10)
#define Button4MotionMask (1L<<11)
#define Button5MotionMask (1L<<12)
#define ButtonMotionMask (1L<<13)
#define ExposureMask (1L<<15)
#define StructureNotifyMask (1L<<17)
#define FocusChangeMask (1L<<21)
KeyPressMask即窗口处理键盘按下事件。KeyReleaseMask处理键盘按键按下松开事件。ButtonPressMask事件,鼠标事件按下事件,这里一旦设置了该事件,窗口将处理鼠标左键,中键,右键以中键滚动事件。EnternWindowMask处理当前移动鼠标,鼠标光标进入到窗口范围内事件。LeaveWindowMask鼠标从窗口内移动到窗口之外事件。PointerMotionMask鼠标在窗口内移动事件。Button1MotionMask当鼠标左键被按下且在窗口内移动时才会产生鼠标移动事件。其它几个也类似。ButtonMotionMask是不管哪个鼠标键被按下并且在窗口内移动时产生的鼠标移动事件。ExposureMask处理窗口显示后的绘制事件,在这个事件中我们可以在窗口中绘制一些文本、图片,甚至和Region结合绘制一个异形窗口。StructureNotifyMask,当窗口的位置大小改变时产生的事件。FocusChangeMask,窗口焦点发生时的事件,当键盘从其它窗口移动到当前窗口或者当前窗口失去键盘输入时产生的事件。
XEvent是一个联合结构体。数据结构定义如下:
typedef union _XEvent {
int type; /* must not be changed; first element */
XAnyEvent xany;
XKeyEvent xkey;
XButtonEvent xbutton;
XMotionEvent xmotion;
XCrossingEvent xcrossing;
XFocusChangeEvent xfocus;
XExposeEvent xexpose;
XGraphicsExposeEvent xgraphicsexpose;
XNoExposeEvent xnoexpose;
XVisibilityEvent xvisibility;
XCreateWindowEvent xcreatewindow;
XDestroyWindowEvent xdestroywindow;
XUnmapEvent xunmap;
XMapEvent xmap;
XMapRequestEvent xmaprequest;
XReparentEvent xreparent;
XConfigureEvent xconfigure;
XGravityEvent xgravity;
XResizeRequestEvent xresizerequest;
XConfigureRequestEvent xconfigurerequest;
XCirculateEvent xcirculate;
XCirculateRequestEvent xcirculaterequest;
XPropertyEvent xproperty;
XSelectionClearEvent xselectionclear;
XSelectionRequestEvent xselectionrequest;
XSelectionEvent xselection;
XColormapEvent xcolormap;
XClientMessageEvent xclient;
XMappingEvent xmapping;
XErrorEvent xerror;
XKeymapEvent xkeymap;
XGenericEvent xgeneric;
XGenericEventCookie xcookie;
long pad[24];
} XEvent;
XEvent数据结构中,type用于表示事件的类型,常见的事件类型定义如下:
#define KeyPress 2
#define KeyRelease 3
#define ButtonPress 4
#define ButtonRelease 5
#define MotionNotify 6
#define EnterNotify 7
#define LeaveNotify 8
#define FocusIn 9
#define FocusOut 10
#define Expose 12
#define CreateNotify 16
#define DestroyNotify 17
#define MapNotify 19
#define ConfigureNotify 22
#define ClientMessage 33
在XEvent联合体数据结构中, 有XAnyEvent、XButtonEvent、XMotionEvent这种结构体数据结构,由于XEvent是一个联合体,所以每次事件只有一个结构体是有效的,有效结构体与XEvent中的type相关。常用type与结构体对应关系如下
事件类型 | 事件结构体 | 描述 |
---|---|---|
KeyPress | XKeyEvent | 键盘按键被按下事件,XKeyEvent结构体记录被按下的键盘。 |
KeyRelease | XKeyEvent | 键盘按键被按下松开事件 |
ButtonPress | XButtonEvent | 鼠标键被按下事件 |
ButtonRelease | XButtonEvent | 鼠标键被按下松开事件 |
MotionNotify | XMotionEvent | 鼠标在窗口内移动事件 |
EnterNotify | XCrossingEvent | 鼠标进入窗口事件 |
LeaveNotify | XCrossingEvent | 鼠标离开窗口事件 |
FocusIn | XFocusChangeEvent | 输入焦点进入当前窗口 |
FocusOut | XFocusChangeEvent | 输入焦点离开当前窗口 |
Expose | XExposeEvent | 当前窗口被显示或者区域需要重绘时产生的事件 |
CreateNotify | XCreateWindowEvent | 窗口被创建时事件 |
DestroyNotify | XDestroyWindowEvent | 窗口被销毁时事件 |
MapNotify | XMapEvent | 窗口显示时事件 |
ConfigureNotify | XConfigureEvent | 窗口位置、大小改变时事件 |
ClientMessage | XClientMessageEvent | 自定义的事件 |
2.Expose事件
Expose事件是窗口绘制事件,当创建的窗口被显示到屏幕时或者窗体中的区域无效需要进行重绘时,X Server产生该事件。该事件对应的结构体如下:
typedef struct {
int type;
unsigned long serial; /* # of last request processed by server */
Bool send_event; /* true if this came from a SendEvent request */
Display *display; /* Display the event was read from */
Window window;
int x, y;
int width, height;
int count; /* if non-zero, at least this many more */
} XExposeEvent;
该结构中的第一字段为type,该字段为事件的类型,xlib中所有事件结构体中的第一个字段都是type。由于XEvent是一个联合体,而该联合体中的第一个成员字段为type,所以XExposeEvent中的type与XEvent联合体中type具有完全一致的内存地址。结构体中的Window用于表示事件发生的窗体;x,y表示重绘区域的左上角坐标,这里的坐标是相对于窗体左上角的偏移量,不是在屏幕上的坐标。width,height为重绘区域的宽度,高度。count用于表示后续是否还有Expose事件,这个字段的作用是X Server并不是完全同步的事件机制,当多个Expose事件产生时,X Server会进行计数。当我们需要向窗口绘制文本、图片时,考虑到性能问题(即使不考虑性能问题,count不为0后续还会有绘制事件,后续的绘制操作会覆盖前面绘制的结果),当count为0时才进行绘制操作。
以下是一个打印出重绘事件左上角坐标和重绘宽度、高度的程序
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
Display *display;
Window window;
int screen;
XEvent event;
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "无法打开X显示器\n");
exit(1);
}
screen = DefaultScreen(display);
window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 400, 300, 1,
BlackPixel(display, screen), WhitePixel(display, screen));
/* 选择要接收的事件类型 */
XSelectInput(display, window, ExposureMask | StructureNotifyMask);
XMapWindow(display, window);
while (1) {
XNextEvent(display, &event);
if (event.type == MapNotify) {
printf("Window has been displayed...\n");
}
/* 绘制消息 */
if (event.type == Expose && event.xexpose.count == 0) {
printf("Expose Rect = (%d,%d,%d,%d)\n",event.xexpose.x,event.xexpose.y,
event.xexpose.width,event.xexpose.height);
}
}
XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
3.键盘事件
使用xlib我们可以让窗口处理键盘输入事件。键盘事件对应的结构体如下
typedef struct {
int type; /* of event */
unsigned long serial; /* # of last request processed by server */
Bool send_event; /* true if this came from a SendEvent request */
Display *display; /* Display the event was read from */
Window window; /* "event" window it is reported relative to */
Window root; /* root window that the event occurred on */
Window subwindow; /* child window */
Time time; /* milliseconds */
int x, y; /* pointer x, y coordinates in event window */
int x_root, y_root; /* coordinates relative to root */
unsigned int state; /* key or button mask */
unsigned int keycode; /* detail */
Bool same_screen; /* same screen flag */
} XKeyEvent;
以下是一个简单的程序,如果我们按下的键盘是Esc键时,退出事件循环,程序结束
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <X11/keysym.h>
#include <X11/keysymdef.h>
int main() {
Display *display;
Window window;
int screen;
XEvent event;
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "无法打开X显示器\n");
exit(1);
}
screen = DefaultScreen(display);
window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 400, 300, 1,
BlackPixel(display, screen), WhitePixel(display, screen));
/* 选择要接收的事件类型 */
XSelectInput(display, window, KeyPressMask);
XMapWindow(display, window);
while (1) {
XNextEvent(display, &event);
if (event.type == KeyPress) {
KeySym keysym = XLookupKeysym(&event.xkey,0);
if (keysym == XK_Escape) {
break;
}
}
}
XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
以上程序中我们使用XLookupKeysym将XKeyEvent中的xkey转换为KeySym,判断按下的键是否为XK_Escape。当然我们可以对if (keysym == XK_Escape) 进行修改,判断如字母a键是否被按下。
if(keysym == XK_a){
printf("a pressed...\n");
}
当我们按下键盘上的a键时,打印以上信息。这里还有一点问题,这里我们只是判断键盘的哪个键被按下,如果这时我按下shift+a或是CapsLk被点亮再按a键时,这里仍然会打印出a pressed。如果需要确定输入的具体是a还是大写的A,我们还需要做一些处理,可以使用Xutf8LookupString获取输入的文本。后续我们将详细记录使用xlib完成一个编辑框、并能够接受输入法输入文本的示例。
4.鼠标按下事件
作为窗口程序,与控制台程序的最大区别是应用程序与鼠标的交互。鼠标事件其对应的事件结构体如下
typedef struct {
int type; /* of event */
unsigned long serial; /* # of last request processed by server */
Bool send_event; /* true if this came from a SendEvent request */
Display *display; /* Display the event was read from */
Window window; /* "event" window it is reported relative to */
Window root; /* root window that the event occurred on */
Window subwindow; /* child window */
Time time; /* milliseconds */
int x, y; /* pointer x, y coordinates in event window */
int x_root, y_root; /* coordinates relative to root */
unsigned int state; /* key or button mask */
unsigned int button; /* detail */
Bool same_screen; /* same screen flag */
} XButtonEvent;
以上结构体中,window为鼠标操作的窗口,time鼠标按下的时间以毫秒为单位,x,y鼠标的按下时的坐标,该坐标为相对窗口左上角的偏移量。x_root,y_root也是鼠标按下时的坐标,不过这个坐标是相对于root也就是桌面窗口左上角的偏移量。button用于记录鼠标的哪个按键被按下,该字段可取的值如下
#define Button1 1
#define Button2 2
#define Button3 3
#define Button4 4
#define Button5 5
Button1为鼠标左键被按下,Button2为鼠标中键被按下,Button3为鼠标右键被按下。这里的Button4和Button5比较有趣,当我们滚动鼠标中键时,在xlib中产生的是鼠标按下(MousePress)事件,向上滚动滚轮时XButtonEvent结构体中button记录的是Button4,向下滚动滚轮时button记录的是Button5。
以下是xlib中处理鼠标按键的示例程序
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
Display *display;
Window window;
int screen;
XEvent event;
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "无法打开X显示器\n");
exit(1);
}
screen = DefaultScreen(display);
window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 400, 300, 1,
BlackPixel(display, screen), WhitePixel(display, screen));
/* 选择要接收的事件类型 */
XSelectInput(display, window, ButtonPressMask);
XMapWindow(display, window);
while (1) {
XNextEvent(display, &event);
if (event.type == ButtonPress) {
if(event.xbutton.button == Button1) {
printf("鼠标左键按下...\n");
}else if (event.xbutton.button == Button2) {
printf("鼠标中键被按下...\n");
}else if (event.xbutton.button == Button3) {
printf("鼠标右键按下...\n");
}else if (event.xbutton.button == Button4) {
printf("鼠标滚轮向上滚动...\n");
}else if (event.xbutton.button == Button5) {
printf("鼠标滚轮向下滚动...\n");
}
}
}
XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
以上程序处理了鼠标各个键的单击事件。但是在xlib中不像windows操作系统那样,提供了鼠标的左键的双击事件。在xlib中如果需要鼠标双击事件,需要记录下鼠标两次单击的时间差,当时间差在一定范围内时,就认为是鼠标的双击事件。以下是一个简单的示例。
5.鼠标进入离开窗口事件
鼠标进入离开窗口事件对应的结构体如下:
typedef struct {
int type; /* of event */
unsigned long serial; /* # of last request processed by server */
Bool send_event; /* true if this came from a SendEvent request */
Display *display; /* Display the event was read from */
Window window; /* "event" window reported relative to */
Window root; /* root window that the event occurred on */
Window subwindow; /* child window */
Time time; /* milliseconds */
int x, y; /* pointer x, y coordinates in event window */
int x_root, y_root; /* coordinates relative to root */
int mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */
int detail;
/*
* NotifyAncestor, NotifyVirtual, NotifyInferior,
* NotifyNonlinear,NotifyNonlinearVirtual
*/
Bool same_screen; /* same screen flag */
Bool focus; /* boolean focus */
unsigned int state; /* key or button mask */
} XCrossingEvent;
以下是一个简单的展示鼠标进入、离开窗体事件处理
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
Display *display;
Window window;
int screen;
XEvent event;
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "无法打开X显示器\n");
exit(1);
}
screen = DefaultScreen(display);
window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 400, 300, 1,
BlackPixel(display, screen), WhitePixel(display, screen));
/* 选择要接收的事件类型 */
XSelectInput(display, window, LeaveWindowMask|EnterWindowMask);
XMapWindow(display, window);
while (1) {
XNextEvent(display, &event);
if (event.type == LeaveNotify) {
printf("鼠标离开窗口\n");
}else if (event.type == EnterNotify) {
printf("鼠标进入窗口\n");
}
}
XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
6.鼠标移动事件
鼠标移动事件对应的结构体如下
typedef struct {
int type; /* of event */
unsigned long serial; /* # of last request processed by server */
Bool send_event; /* true if this came from a SendEvent request */
Display *display; /* Display the event was read from */
Window window; /* "event" window reported relative to */
Window root; /* root window that the event occurred on */
Window subwindow; /* child window */
Time time; /* milliseconds */
int x, y; /* pointer x, y coordinates in event window */
int x_root, y_root; /* coordinates relative to root */
unsigned int state; /* key or button mask */
char is_hint; /* detail */
Bool same_screen; /* same screen flag */
} XMotionEvent;
该结构体多数成员与鼠标按下事件意义相同,但有一个state。这个在XMotionEvent事件中用于表示当鼠标在移动中鼠标的按键是否被按下,以及哪个键被按下。
以下是一个鼠标移动事件处理示例程序
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
Display *display;
Window window;
int screen;
XEvent event;
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "无法打开X显示器\n");
exit(1);
}
screen = DefaultScreen(display);
window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 400, 300, 1,
BlackPixel(display, screen), WhitePixel(display, screen));
/* 选择要接收的事件类型 */
XSelectInput(display, window, ButtonPressMask | PointerMotionMask);
XMapWindow(display, window);
while (1) {
XNextEvent(display, &event);
if (event.type == MotionNotify) {
if (event.xmotion.state & Button1Mask) {
printf("鼠标移动且左键按下...\n");
}else if (event.xmotion.state & Button2Mask) {
printf("鼠标移动且中键按下..\n");
//event.xmotion.state & Button3Mask -- 鼠标移动右键按下
//event.xmotion.state & Button4Mask -- 鼠标移动、向上滚动滚轮
//event.xmotion.state & Button5Mask -- 鼠标移动、向下滚动滚轮
}
}
}
XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
这个示例程序中对于XSelectInput,我们设置了ButtonPressMask和PointerMotionMask。若是只设置PointerMotionMask掩码,则我们的程序只能处理鼠标移动事件,但不能处理鼠标移动的同时,鼠标键被按下事件,因为在掩码中我们设置了不希望处理鼠标按下事件。
7.窗口改变事件
当程序中窗口的位置大小发生改变时,X Server会产生ConfigureNotify事件。ConfigureNotify对应的结构体如下:
typedef struct {
int type;
unsigned long serial; /* # of last request processed by server */
Bool send_event; /* true if this came from a SendEvent request */
Display *display; /* Display the event was read from */
Window event;
Window window;
int x, y;
int width, height;
int border_width;
Window above;
Bool override_redirect;
} XConfigureEvent;
结构体中x,y表示窗口新位置的左上角坐标,width,height分别用于表示窗口改变后的宽度、高度值。以下是一个处理窗口改变事件的程序,当我们使用鼠标拖动窗口或是改变窗口大小时,向控制台输入窗口改变后的坐标大小信息。
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
Display *display;
Window window;
int screen;
XEvent event;
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "无法打开X显示器\n");
exit(1);
}
screen = DefaultScreen(display);
window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 400, 300, 1,
BlackPixel(display, screen), WhitePixel(display, screen));
/* 选择要接收的事件类型 */
XSelectInput(display, window, StructureNotifyMask);
XMapWindow(display, window);
while (1) {
XNextEvent(display, &event);
if (event.type == ConfigureNotify) {
printf("upper left = (%d,%d) width height = (%d,%d)\n",event.xconfigure.x,
event.xconfigure.y,event.xconfigure.width,event.xconfigure.height);
}
}
XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
8.焦点事件
以下是处理窗口失去/获得焦点的示例程序
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
Display *display;
Window window;
int screen;
XEvent event;
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "无法打开X显示器\n");
exit(1);
}
screen = DefaultScreen(display);
window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 400, 300, 1,
BlackPixel(display, screen), WhitePixel(display, screen));
/* 选择要接收的事件类型 */
XSelectInput(display, window, FocusChangeMask);
XMapWindow(display, window);
while (1) {
XNextEvent(display, &event);
if (event.type == FocusIn) {
printf("窗口获取焦点...\n");
}else if (event.type == FocusOut) {
printf("窗口失去焦点...\n");
}
}
XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}