clutter中的GSource

转载时请注明出处和作者联系方式
文章出处:
http://blog.csdn.net/jack0106 
作者联系方式:冯牮 
[email protected]

 

clutter是一个GUI库,使用opengl作为底层的绘图引擎。整个库使用c语言开发,基于glib和gobject对象系统。通常情况,main函数的形式如下:

 

int main (int argc, char *argv[]) { ClutterActor *stage; clutter_init (&argc, &argv); //create the stage stage = clutter_stage_get_default (); /** * add actors to the stage */ //run main loop clutter_main(); return 0; }

 

熟悉gtk的朋友可以看出,main函数中的这种代码结构,和gtk程序中的main函数结构很类似,这是因为clutter和gtk中的主事件循环,都是基于GMainLoop以及GSource,而且他们的设计上,本身就有某种相似性。

 

最近把clutter-0.2,clutter-0.4和clutter-1.0版本的源代码翻出来看了看,主要是看其中事件循环这一部分的代码,学习一下其中的代码实现方法。

 

gui程序,从原理上来说,就是接收用户的输入(比如键盘输入,鼠标输入等),然后针对这些不同的输入事件,做出不同的处理。在GMainLoop这个框架中,就需要继承GSource,并且重新实现prepare/check/dispatch这三个虚函数,然后把这个子类GSource添加到主事件循环中。clutter-0.2的代码量最小,所以以此作为分析学习的切入点。下面是从clutter-0.2中取出的代码片段,我们重点看一下clutter中实现的一个GSource子类--ClutterXEventSource

 

typedef struct { GSource source; Display *display; GPollFD event_poll_fd; } ClutterXEventSource; static gboolean x_event_prepare (GSource *source, gint *timeout) { Display *display = ((ClutterXEventSource*)source)->display; *timeout = -1; return XPending (display); } static gboolean x_event_check (GSource *source) { ClutterXEventSource *display_source = (ClutterXEventSource*)source; gboolean retval; if (display_source->event_poll_fd.revents & G_IO_IN) retval = XPending (display_source->display); else retval = FALSE; return retval; } static gboolean x_event_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { Display *display = ((ClutterXEventSource*)source)->display; ClutterXEventFunc event_func = (ClutterXEventFunc) callback; XEvent xev; if (XPending (display)) { XNextEvent (display, &xev); if (event_func) (*event_func) (&xev, user_data); } return TRUE; } static void clutter_dispatch_x_event (XEvent *xevent, gpointer data) { ClutterMainContext *ctx = CLUTTER_CONTEXT (); ClutterEvent event; ClutterStage *stage = ctx->stage; gboolean emit_input_event = FALSE; switch (xevent->type) { case Expose: { XEvent foo_xev; /* Cheap compress */ while (XCheckTypedWindowEvent(ctx->xdpy, xevent->xexpose.window, Expose, &foo_xev)); /* FIXME: need to make stage an 'actor' so can que * a paint direct from there rather than hack here... */ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); } break; case KeyPress: translate_key_event ((ClutterKeyEvent *) &event, xevent); g_signal_emit_by_name (stage, "key-press-event", &event); emit_input_event = TRUE; break; case KeyRelease: translate_key_event ((ClutterKeyEvent *) &event, xevent); g_signal_emit_by_name (stage, "key-release-event", &event); emit_input_event = TRUE; break; case ButtonPress: translate_button_event ((ClutterButtonEvent *) &event, xevent); g_signal_emit_by_name (stage, "button-press-event", &event); emit_input_event = TRUE; break; case ButtonRelease: translate_button_event ((ClutterButtonEvent *) &event, xevent); g_signal_emit_by_name (stage, "button-release-event", &event); emit_input_event = TRUE; break; case MotionNotify: translate_motion_event ((ClutterMotionEvent *) &event, xevent); g_signal_emit_by_name (stage, "motion-event", &event); emit_input_event = TRUE; break; } if (emit_input_event) g_signal_emit_by_name (stage, "input-event", &event); } static void events_init() { ClutterMainContext *clutter_context; GMainContext *gmain_context; int connection_number; GSource *source; ClutterXEventSource *display_source; clutter_context = clutter_context_get_default (); gmain_context = g_main_context_default (); g_main_context_ref (gmain_context); connection_number = ConnectionNumber (clutter_context->xdpy); source = g_source_new ((GSourceFuncs *)&x_event_funcs, sizeof (ClutterXEventSource)); display_source = (ClutterXEventSource *)source; display_source->event_poll_fd.fd = connection_number; display_source->event_poll_fd.events = G_IO_IN; display_source->display = clutter_context->xdpy; g_source_add_poll (source, &display_source->event_poll_fd); g_source_set_can_recurse (source, TRUE); g_source_set_callback (source, (GSourceFunc) clutter_dispatch_x_event, NULL /* no userdata */, NULL); g_source_attach (source, gmain_context); g_source_unref (source); }

 

这个版本的clutter,是运行在X11环境上的。Xserver管理了所有的输入设备,将所有的输入事件转换成Xevent,然后分发给对应的Xclient。Xclietn和Xserver之间,则是通过一个socket进行协议通信。所以ClutterXEventSource中,定义了一个GPollFD  event_poll_fd,这个变量中,保存的就是cultter和Xserver建立的socket连接的文件描述符。

 

prepare/check/dispatch这三个函数的代码,也比较简单,原理上看,就是等待XServer发送过来的用户输入事件,然后就进入dispatch函数,在dispatch函数,获取Xserver发送过来的XEvent,紧接着就调用clutter_dispatch_x_event()。

 

到这一步为止,用户通过键盘或鼠标的输入,就已经传递到了clutter内部,ClutterXEventSource的工作,就算是完成了。剩下的操作,是clutter对这个XEvent的转换(数据类型转换等等),以及在内部的分发,传递和处理(将event分发到对应的ClutterActor,然后对应的ClutterActor处理这个event),这个分发过程的细节,不在此次的讨论范围内。当分发和处理结束后,就将进入GMainLoop的下一次循环调度,当用户再次输入的时候,dispatch函数,将被再次调用。这样的话,整个GUI图形环境就运行起来了,ClutterXEventSource接收用户的输入,然后clutter处理这个输入事件,使用opengl重新绘制图形,然后这一次事件处理结束,clutter等待下一次用户输入。

 

clutter这个库的一个优势,就是可以方便的制作出动画效果。动画的原理,就是在固定的时间间隔上,重新绘制视图,只要这个时间间隔选取的合适,就可以产生动画效果。要在GMainLoop中实现动画,就需要用到另外一个GSource的子类--GTimeoutSource(GTimeoutSource是一个内部对象,作为使用者,我们只需要使用g_timeout_*这一簇函数就行了),设置好合适的时间间隔后,类似的,在GTimeoutSource的dispatch函数中,调用clutter的绘图引擎重新渲染视图,就可以产生动画的效果。

 

clutter中事件循环的基本原理就是这样,因为参考的是clutter-0.2版本的代码,代码量比较小,所以分析起来也比较容易。熟悉了这个版本的代码后,就可以看更高版本的代码实现细节了,我选的是0.4和1.0版本的源代码,看完这两版代码后发现,从0.4版本开始,event相关的代码部分,就已经成型并且基本稳定,考虑到1.0才是clutter的正式版,因此选取clutter-1.0来进行说明。

 

clutter-0.2只能运行在X11环境中,因此在构造ClutterXEventSource的时候,代码没有设计更多的层次,在初始化代码中,直接就初始化了ClutterXEventSource。到了clutter-1.0中,采用面向对象思想进行了更好的设计,并且匹配了多种后端,包括X11/win32/sdl/egl等等。在clutter的初始化阶段,直接调用后端的初始化代码,然后再由每个后端各自初始化自己的事件循环。因此,在clutter中定义了一个纯虚类,ClutterBackend,这个纯虚类有如下的一系列函数接口,不同的后端,只要继承这个ClutterBackend并且实现如下的函数就可以了。

 

struct _ClutterBackendClass { /*< private >*/ GObjectClass parent_class; /* vfuncs */ gboolean (* pre_parse) (ClutterBackend *backend, GError **error); gboolean (* post_parse) (ClutterBackend *backend, GError **error); ClutterActor * (* create_stage) (ClutterBackend *backend, ClutterStage *wrapper, GError **error); void (* init_events) (ClutterBackend *backend); void (* init_features) (ClutterBackend *backend); void (* add_options) (ClutterBackend *backend, GOptionGroup *group); ClutterFeatureFlags (* get_features) (ClutterBackend *backend); void (* redraw) (ClutterBackend *backend, ClutterStage *stage); gboolean (* create_context) (ClutterBackend *backend, gboolean is_offscreen, GError **error); void (* ensure_context) (ClutterBackend *backend, ClutterStage *stage); /* signals */ void (* resolution_changed) (ClutterBackend *backend); void (* font_changed) (ClutterBackend *backend); }; 我们只关注clutter中的事件循环,只需要看后端的void (* init_events) (ClutterBackend *backend)这个函数实现。还是以X11后端为例,先给出其中的代码片段。 typedef struct _ClutterBackendX11 ClutterBackendX11; struct _ClutterBackendX11 { ClutterBackend parent_instance; Display *xdpy; Window xwin_root; Screen *xscreen; int xscreen_num; gchar *display_name; /* event source */ GSource *event_source; ... }; static void clutter_backend_x11_init_events (ClutterBackend *backend) { CLUTTER_NOTE (EVENT, "initialising the event loop"); if (!_no_xevent_retrieval) _clutter_backend_x11_events_init (backend); } void _clutter_backend_x11_events_init (ClutterBackend *backend) { ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (backend); GSource *source; ClutterEventSource *event_source; int connection_number; connection_number = ConnectionNumber (backend_x11->xdpy); CLUTTER_NOTE (EVENT, "Connection number: %d", connection_number); source = backend_x11->event_source = clutter_event_source_new (backend); event_source = (ClutterEventSource *) source; g_source_set_priority (source, CLUTTER_PRIORITY_EVENTS); event_source->event_poll_fd.fd = connection_number; event_source->event_poll_fd.events = G_IO_IN; event_sources = g_list_prepend (event_sources, event_source); g_source_add_poll (source, &event_source->event_poll_fd); g_source_set_can_recurse (source, TRUE); g_source_attach (source, NULL); } ... ... typedef struct _ClutterEventSource ClutterEventSource; struct _ClutterEventSource { GSource source; ClutterBackend *backend; GPollFD event_poll_fd; }; static gboolean clutter_event_prepare (GSource *source, gint *timeout) { ClutterBackend *backend = ((ClutterEventSource *) source)->backend; gboolean retval; clutter_threads_enter (); *timeout = -1; retval = (clutter_events_pending () || check_xpending (backend)); clutter_threads_leave (); return retval; } static gboolean clutter_event_check (GSource *source) { ClutterEventSource *event_source = (ClutterEventSource *) source; ClutterBackend *backend = event_source->backend; gboolean retval; clutter_threads_enter (); if (event_source->event_poll_fd.revents & G_IO_IN) retval = (clutter_events_pending () || check_xpending (backend)); else retval = FALSE; clutter_threads_leave (); return retval; } static gboolean clutter_event_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { ClutterBackend *backend = ((ClutterEventSource *) source)->backend; ClutterEvent *event; clutter_threads_enter (); /* Grab the event(s), translate and figure out double click. * The push onto queue (stack) if valid. */ events_queue (backend); /* Pop an event off the queue if any */ event = clutter_event_get (); if (event) { /* forward the event into clutter for emission etc. */ clutter_do_event (event); clutter_event_free (event); } clutter_threads_leave (); return TRUE; } static GSource * clutter_event_source_new (ClutterBackend *backend) { GSource *source = g_source_new (&event_funcs, sizeof (ClutterEventSource)); ClutterEventSource *event_source = (ClutterEventSource *) source; event_source->backend = backend; return source; }

 

ClutterBackendX11结构体,就是ClutterBackend的一个子类,clutter_backend_x11_init_events函数,就是这个子类重新实现的init_events虚函数。

 

ClutterEventSource以及它的prepare/check/dispatch这3个函数,和clutter-0.2版本中的区别不大,结构都是一致的,clutter_event_dispatch函数,是用户输入事件对应的代码的入口点,在这个函数中,clutter获取Xevent,然后转换event并且分发处理,事件处理结束后,函数返回,进入poll下一次循环调度。

 

需要特别说明的一点是,从结构上来看,由于事件循环的初始化,是包含在后端的初始化过程中的,所以ClutterEventSource可以设计成ClutterBackendX11的一个成员变量,ClutterBackendX11中的event_source指针,就是指向这个成员变量。但是,从代码中可以看到,在ClutterEventSource中,也有一个指针backend,指向包含该source的ClutterBackend,这是因为clutter_event_dispatch这个函数的第一个参数是GSource,在dispatch函数中,如果要使用backend,就只能通过ClutterEventSource->backend这个指针来获取。

 

写到这里,还想起一件事,顺便一起记录下来,clutter整体的框架,和qt中的Graphics View Framework很类似。clutter中的ClutterStage,相当于qt中的QGraphicsScene,clutter中的ClutterActor,相当于qt中的QGraphicsItem。这两套框架的接口设计,差不多都是一样的,如果熟悉其中的一套框架,那么另外一套框架上手起来也是比较容易的。

 

你可能感兴趣的:(struct,input,callback,Signal,events,gtk)