diff --git a/gdk/gdkdisplay.c b/gdk/gdkdisplay.c index b02ab8a541..8e5092c4d9 100644 --- a/gdk/gdkdisplay.c +++ b/gdk/gdkdisplay.c @@ -308,7 +308,11 @@ gdk_display_get_event (GdkDisplay *display) g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL); GDK_DISPLAY_GET_CLASS (display)->queue_events (display); - return _gdk_event_unqueue (display); + + if (display->events_paused) + return NULL; + else + return _gdk_event_unqueue (display); } /** @@ -2003,6 +2007,31 @@ gdk_display_notify_startup_complete (GdkDisplay *display, GDK_DISPLAY_GET_CLASS (display)->notify_startup_complete (display, startup_id); } +void +_gdk_display_set_events_paused (GdkDisplay *display, + gboolean events_paused) +{ + display->events_paused = !!events_paused; +} + +void +_gdk_display_flush_events (GdkDisplay *display) +{ + display->flushing_events = TRUE; + + while (TRUE) + { + GdkEvent *event = _gdk_event_unqueue (display); + if (event == NULL) + break; + + _gdk_event_emit (event); + gdk_event_free (event); + } + + display->flushing_events = FALSE; +} + void _gdk_display_event_data_copy (GdkDisplay *display, const GdkEvent *event, diff --git a/gdk/gdkdisplayprivate.h b/gdk/gdkdisplayprivate.h index 6549f213a5..8f448eba77 100644 --- a/gdk/gdkdisplayprivate.h +++ b/gdk/gdkdisplayprivate.h @@ -114,6 +114,8 @@ struct _GdkDisplay GdkDevice *core_pointer; /* Core pointer device */ guint closed : 1; /* Whether this display has been closed */ + guint events_paused : 1; /* Whether events are blocked */ + guint flushing_events : 1; /* Inside gdk_display_flush_events */ GArray *touch_implicit_grabs; GHashTable *device_grabs; @@ -296,6 +298,9 @@ void _gdk_display_pointer_info_foreach (GdkDisplay *display GdkDisplayPointerInfoForeach func, gpointer user_data); gulong _gdk_display_get_next_serial (GdkDisplay *display); +void _gdk_display_set_events_paused (GdkDisplay *display, + gboolean events_paused); +void _gdk_display_flush_events (GdkDisplay *display); void _gdk_display_event_data_copy (GdkDisplay *display, const GdkEvent *event, GdkEvent *new_event); diff --git a/gdk/gdkevents.c b/gdk/gdkevents.c index 204863c4bf..d068e13c2b 100644 --- a/gdk/gdkevents.c +++ b/gdk/gdkevents.c @@ -85,13 +85,27 @@ _gdk_event_emit (GdkEvent *event) GList* _gdk_event_queue_find_first (GdkDisplay *display) { - GList *tmp_list = display->queued_events; + GList *tmp_list; + GList *pending_motion = NULL; + if (display->events_paused) + return NULL; + + tmp_list = display->queued_events; while (tmp_list) { GdkEventPrivate *event = tmp_list->data; - if (!(event->flags & GDK_EVENT_PENDING)) - return tmp_list; + + if (event->flags & GDK_EVENT_PENDING) + continue; + + if (pending_motion) + return pending_motion; + + if (event->event.type == GDK_MOTION_NOTIFY && !display->flushing_events) + pending_motion = tmp_list; + else + return tmp_list; tmp_list = g_list_next (tmp_list); } @@ -248,6 +262,55 @@ _gdk_event_unqueue (GdkDisplay *display) return event; } +void +_gdk_event_queue_handle_motion_compression (GdkDisplay *display) +{ + GList *tmp_list; + GList *pending_motions = NULL; + GdkWindow *pending_motion_window = NULL; + + /* If the last N events in the event queue are motion notify + * events for the same window, drop all but the last */ + + tmp_list = display->queued_tail; + + while (tmp_list) + { + GdkEventPrivate *event = tmp_list->data; + + if (event->flags & GDK_EVENT_PENDING) + break; + + if (event->event.type != GDK_MOTION_NOTIFY) + break; + + if (pending_motion_window != NULL && + pending_motion_window != event->event.motion.window) + break; + + pending_motion_window = event->event.motion.window; + pending_motions = tmp_list; + + tmp_list = tmp_list->prev; + } + + while (pending_motions && pending_motions->next != NULL) + { + GList *next = pending_motions->next; + display->queued_events = g_list_delete_link (display->queued_events, + pending_motions); + pending_motions = next; + } + + if (pending_motions && + pending_motions == display->queued_events && + pending_motions == display->queued_tail) + { + GdkFrameClock *clock = gdk_window_get_frame_clock (pending_motion_window); + gdk_frame_clock_request_phase (clock, GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS); + } +} + /** * gdk_event_handler_set: * @func: the function to call to handle events from GDK. diff --git a/gdk/gdkframeclock.c b/gdk/gdkframeclock.c index 569ab6b52e..8c6d202102 100644 --- a/gdk/gdkframeclock.c +++ b/gdk/gdkframeclock.c @@ -91,11 +91,13 @@ G_DEFINE_INTERFACE (GdkFrameClock, gdk_frame_clock, G_TYPE_OBJECT) enum { FRAME_REQUESTED, + FLUSH_EVENTS, BEFORE_PAINT, UPDATE, LAYOUT, PAINT, AFTER_PAINT, + RESUME_EVENTS, LAST_SIGNAL }; @@ -120,6 +122,21 @@ gdk_frame_clock_default_init (GdkFrameClockInterface *iface) g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + /** + * GdkFrameClock::flush-events: + * @clock: the frame clock emitting the signal + * + * FIXME. + */ + signals[FLUSH_EVENTS] = + g_signal_new (g_intern_static_string ("flush-events"), + GDK_TYPE_FRAME_CLOCK, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + /** * GdkFrameClock::before-paint: * @clock: the frame clock emitting the signal @@ -202,6 +219,21 @@ gdk_frame_clock_default_init (GdkFrameClockInterface *iface) NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * GdkFrameClock::resume-events: + * @clock: the frame clock emitting the signal + * + * FIXME. + */ + signals[RESUME_EVENTS] = + g_signal_new (g_intern_static_string ("resume-events"), + GDK_TYPE_FRAME_CLOCK, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); } /** diff --git a/gdk/gdkframeclock.h b/gdk/gdkframeclock.h index ae445737df..624f6fef4c 100644 --- a/gdk/gdkframeclock.h +++ b/gdk/gdkframeclock.h @@ -64,12 +64,14 @@ void gdk_frame_clock_target_set_clock (GdkFrameClockTarget *target, #define GDK_FRAME_CLOCK_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GDK_TYPE_FRAME_CLOCK, GdkFrameClockInterface)) typedef enum { - GDK_FRAME_CLOCK_PHASE_NONE = 0, - GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT = 1 << 0, - GDK_FRAME_CLOCK_PHASE_UPDATE = 1 << 1, - GDK_FRAME_CLOCK_PHASE_LAYOUT = 1 << 2, - GDK_FRAME_CLOCK_PHASE_PAINT = 1 << 3, - GDK_FRAME_CLOCK_PHASE_AFTER_PAINT = 1 << 4 + GDK_FRAME_CLOCK_PHASE_NONE = 0, + GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS = 1 << 0, + GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT = 1 << 1, + GDK_FRAME_CLOCK_PHASE_UPDATE = 1 << 2, + GDK_FRAME_CLOCK_PHASE_LAYOUT = 1 << 3, + GDK_FRAME_CLOCK_PHASE_PAINT = 1 << 4, + GDK_FRAME_CLOCK_PHASE_AFTER_PAINT = 1 << 5, + GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS = 1 << 6 } GdkFrameClockPhase; struct _GdkFrameClockInterface @@ -87,11 +89,13 @@ struct _GdkFrameClockInterface /* signals */ /* void (* frame_requested) (GdkFrameClock *clock); */ + /* void (* flush_events) (GdkFrameClock *clock); */ /* void (* before_paint) (GdkFrameClock *clock); */ /* void (* update) (GdkFrameClock *clock); */ /* void (* layout) (GdkFrameClock *clock); */ /* void (* paint) (GdkFrameClock *clock); */ /* void (* after_paint) (GdkFrameClock *clock); */ + /* void (* resume_events) (GdkFrameClock *clock); */ }; GType gdk_frame_clock_get_type (void) G_GNUC_CONST; diff --git a/gdk/gdkframeclockidle.c b/gdk/gdkframeclockidle.c index bf68fd1b64..ff4b479ae4 100644 --- a/gdk/gdkframeclockidle.c +++ b/gdk/gdkframeclockidle.c @@ -39,13 +39,15 @@ struct _GdkFrameClockIdlePrivate guint64 frame_time; guint64 min_next_frame_time; - guint idle_id; + guint flush_idle_id; + guint paint_idle_id; guint freeze_count; GdkFrameClockPhase requested; GdkFrameClockPhase phase; }; +static gboolean gdk_frame_clock_flush_idle (void *data); static gboolean gdk_frame_clock_paint_idle (void *data); static void gdk_frame_clock_idle_finalize (GObject *object); @@ -144,7 +146,7 @@ maybe_start_idle (GdkFrameClockIdle *clock_idle) { GdkFrameClockIdlePrivate *priv = clock_idle->priv; - if (priv->idle_id == 0 && priv->freeze_count == 0 && priv->requested != 0) + if (priv->freeze_count == 0) { guint min_interval = 0; @@ -155,41 +157,89 @@ maybe_start_idle (GdkFrameClockIdle *clock_idle) min_interval = (min_interval_us + 500) / 1000; } - priv->idle_id = gdk_threads_add_timeout_full (GDK_PRIORITY_REDRAW, - min_interval, - gdk_frame_clock_paint_idle, - g_object_ref (clock_idle), - (GDestroyNotify) g_object_unref); + if (priv->flush_idle_id == 0 && + (priv->requested & GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS) != 0) + { + priv->flush_idle_id = gdk_threads_add_timeout_full (GDK_PRIORITY_EVENTS + 1, + min_interval, + gdk_frame_clock_flush_idle, + g_object_ref (clock_idle), + (GDestroyNotify) g_object_unref); + } - gdk_frame_clock_frame_requested (GDK_FRAME_CLOCK (clock_idle)); + if (priv->paint_idle_id == 0 && + (priv->requested & ~GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS) != 0) + { + priv->paint_idle_id = gdk_threads_add_timeout_full (GDK_PRIORITY_REDRAW, + min_interval, + gdk_frame_clock_paint_idle, + g_object_ref (clock_idle), + (GDestroyNotify) g_object_unref); + + gdk_frame_clock_frame_requested (GDK_FRAME_CLOCK (clock_idle)); + } } } +static gboolean +gdk_frame_clock_flush_idle (void *data) +{ + GdkFrameClock *clock = GDK_FRAME_CLOCK (data); + GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock); + GdkFrameClockIdlePrivate *priv = clock_idle->priv; + + priv->flush_idle_id = 0; + + if (priv->phase != GDK_FRAME_CLOCK_PHASE_NONE) + return FALSE; + + priv->phase = GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS; + priv->requested &= ~GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS; + + g_signal_emit_by_name (G_OBJECT (clock), "flush-events"); + + if ((priv->requested & ~GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS) != 0) + priv->phase = GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT; + else + priv->phase = GDK_FRAME_CLOCK_PHASE_NONE; + + return FALSE; +} + static gboolean gdk_frame_clock_paint_idle (void *data) { GdkFrameClock *clock = GDK_FRAME_CLOCK (data); GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock); GdkFrameClockIdlePrivate *priv = clock_idle->priv; + gboolean skip_to_resume_events; - priv->idle_id = 0; + priv->paint_idle_id = 0; + + skip_to_resume_events = + (priv->requested & ~(GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS | GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS)) == 0; switch (priv->phase) { + case GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS: + break; case GDK_FRAME_CLOCK_PHASE_NONE: case GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT: if (priv->freeze_count == 0) { priv->frame_time = compute_frame_time (clock_idle); - priv->phase = GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT; - priv->requested &= ~GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT; - /* We always emit ::before-paint and ::after-paint even if - * not explicitly requested, and unlike other phases, + priv->phase = GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT; + /* We always emit ::before-paint and ::after-paint if + * any of the intermediate phases are requested and * they don't get repeated if you freeze/thaw while * in them. */ - g_signal_emit_by_name (G_OBJECT (clock), "before-paint"); - priv->phase = GDK_FRAME_CLOCK_PHASE_UPDATE; + if (!skip_to_resume_events) + { + priv->requested &= ~GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT; + g_signal_emit_by_name (G_OBJECT (clock), "before-paint"); + } + priv->phase = GDK_FRAME_CLOCK_PHASE_UPDATE; } case GDK_FRAME_CLOCK_PHASE_UPDATE: if (priv->freeze_count == 0) @@ -224,9 +274,24 @@ gdk_frame_clock_paint_idle (void *data) if (priv->freeze_count == 0) { priv->phase = GDK_FRAME_CLOCK_PHASE_AFTER_PAINT; - priv->requested &= ~GDK_FRAME_CLOCK_PHASE_AFTER_PAINT; - g_signal_emit_by_name (G_OBJECT (clock), "after-paint"); - /* the ::after-paint phase doesn't get repeated on freeze/thaw */ + if (!skip_to_resume_events) + { + priv->requested &= ~GDK_FRAME_CLOCK_PHASE_AFTER_PAINT; + g_signal_emit_by_name (G_OBJECT (clock), "after-paint"); + } + /* the ::after-paint phase doesn't get repeated on freeze/thaw, + */ + priv->phase = GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS; + } + case GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS: + if (priv->freeze_count == 0) + { + if (priv->requested & GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS) + { + priv->requested &= ~GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS; + g_signal_emit_by_name (G_OBJECT (clock), "resume-events"); + } + /* the ::resume-event phase doesn't get repeated on freeze/thaw */ priv->phase = GDK_FRAME_CLOCK_PHASE_NONE; } } @@ -276,10 +341,15 @@ gdk_frame_clock_idle_freeze (GdkFrameClock *clock) if (priv->freeze_count == 1) { - if (priv->idle_id) + if (priv->flush_idle_id) { - g_source_remove (priv->idle_id); - priv->idle_id = 0; + g_source_remove (priv->flush_idle_id); + priv->flush_idle_id = 0; + } + if (priv->paint_idle_id) + { + g_source_remove (priv->paint_idle_id); + priv->paint_idle_id = 0; } } } @@ -294,7 +364,14 @@ gdk_frame_clock_idle_thaw (GdkFrameClock *clock) priv->freeze_count--; if (priv->freeze_count == 0) - maybe_start_idle (clock_idle); + { + maybe_start_idle (clock_idle); + /* If nothing is requested so we didn't start an idle, we need + * to skip to the end of the state chain, since the idle won't + * run and do it for us. */ + if (priv->paint_idle_id == 0) + priv->phase = GDK_FRAME_CLOCK_PHASE_NONE; + } } static void diff --git a/gdk/gdkinternals.h b/gdk/gdkinternals.h index fb561e6f74..babef6e7f6 100644 --- a/gdk/gdkinternals.h +++ b/gdk/gdkinternals.h @@ -300,6 +300,9 @@ GList* _gdk_event_queue_insert_after (GdkDisplay *display, GList* _gdk_event_queue_insert_before(GdkDisplay *display, GdkEvent *after_event, GdkEvent *event); + +void _gdk_event_queue_handle_motion_compression (GdkDisplay *display); + void _gdk_event_button_generate (GdkDisplay *display, GdkEvent *event); diff --git a/gdk/gdkwindow.c b/gdk/gdkwindow.c index 0505d3fb6e..0ac0cfeda0 100644 --- a/gdk/gdkwindow.c +++ b/gdk/gdkwindow.c @@ -10038,7 +10038,7 @@ _gdk_windowing_got_event (GdkDisplay *display, { GdkWindow *event_window; gdouble x, y; - gboolean unlink_event; + gboolean unlink_event = FALSE; GdkDeviceGrabInfo *button_release_grab; GdkPointerWindowInfo *pointer_info = NULL; GdkDevice *device, *source_device; @@ -10081,7 +10081,7 @@ _gdk_windowing_got_event (GdkDisplay *display, event_window = event->any.window; if (!event_window) - return; + goto out; #ifdef DEBUG_WINDOW_PRINTING if (event->type == GDK_KEY_PRESS && @@ -10096,13 +10096,13 @@ _gdk_windowing_got_event (GdkDisplay *display, { event_window->native_visibility = event->visibility.state; gdk_window_update_visibility_recursively (event_window, event_window); - return; + goto out; } if (!(is_button_type (event->type) || is_motion_type (event->type)) || event_window->window_type == GDK_WINDOW_ROOT) - return; + goto out; is_toplevel = gdk_window_is_toplevel (event_window); @@ -10195,7 +10195,6 @@ _gdk_windowing_got_event (GdkDisplay *display, _gdk_display_enable_motion_hints (display, device); } - unlink_event = FALSE; if (is_motion_type (event->type)) unlink_event = proxy_pointer_event (display, event, serial); else if (is_button_type (event->type)) @@ -10237,6 +10236,13 @@ _gdk_windowing_got_event (GdkDisplay *display, g_list_free_1 (event_link); gdk_event_free (event); } + + /* This does two things - first it sees if there are motions at the + * end of the queue that can be compressed. Second, if there is just + * a single motion that won't be dispatched because it is a compression + * candidate it queues up flushing the event queue. + */ + _gdk_event_queue_handle_motion_compression (display); } /** @@ -11603,6 +11609,22 @@ gdk_property_delete (GdkWindow *window, GDK_WINDOW_IMPL_GET_CLASS (window->impl)->delete_property (window, property); } +static void +gdk_window_flush_events (GdkFrameClock *clock, + void *data) +{ + GdkWindow *window; + GdkDisplay *display; + + window = GDK_WINDOW (data); + + display = gdk_window_get_display (window); + _gdk_display_flush_events (display); + _gdk_display_set_events_paused (display, TRUE); + + gdk_frame_clock_request_phase (clock, GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS); +} + static void gdk_window_paint_on_clock (GdkFrameClock *clock, void *data) @@ -11616,6 +11638,19 @@ gdk_window_paint_on_clock (GdkFrameClock *clock, gdk_window_process_updates_with_mode (window, PROCESS_UPDATES_WITH_SAME_CLOCK_CHILDREN); } +static void +gdk_window_resume_events (GdkFrameClock *clock, + void *data) +{ + GdkWindow *window; + GdkDisplay *display; + + window = GDK_WINDOW (data); + + display = gdk_window_get_display (window); + _gdk_display_set_events_paused (display, FALSE); +} + /** * gdk_window_set_frame_clock: * @window: window to set frame clock on @@ -11651,17 +11686,31 @@ gdk_window_set_frame_clock (GdkWindow *window, if (clock) { g_object_ref (clock); + g_signal_connect (G_OBJECT (clock), + "flush-events", + G_CALLBACK (gdk_window_flush_events), + window); g_signal_connect (G_OBJECT (clock), "paint", G_CALLBACK (gdk_window_paint_on_clock), window); + g_signal_connect (G_OBJECT (clock), + "resume-events", + G_CALLBACK (gdk_window_resume_events), + window); } if (window->frame_clock) { + g_signal_handlers_disconnect_by_func (G_OBJECT (window->frame_clock), + G_CALLBACK (gdk_window_flush_events), + window); g_signal_handlers_disconnect_by_func (G_OBJECT (window->frame_clock), G_CALLBACK (gdk_window_paint_on_clock), window); + g_signal_handlers_disconnect_by_func (G_OBJECT (window->frame_clock), + G_CALLBACK (gdk_window_resume_events), + window); g_object_unref (window->frame_clock); } diff --git a/tests/Makefile.am b/tests/Makefile.am index 7917905090..59f1678fcd 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -26,6 +26,7 @@ endif noinst_PROGRAMS = $(TEST_PROGS) \ animated-resizing \ + motion-compression \ simple \ flicker \ print-editor \ @@ -152,6 +153,7 @@ endif animated_resizing_DEPENDENCIES = $(TEST_DEPS) flicker_DEPENDENCIES = $(TEST_DEPS) +motion_compression_DEPENDENCIES = $(TEST_DEPS) simple_DEPENDENCIES = $(TEST_DEPS) print_editor_DEPENDENCIES = $(TEST_DEPS) testheightforwidth_DEPENDENCIES = $(TEST_DEPS) diff --git a/tests/motion-compression.c b/tests/motion-compression.c new file mode 100644 index 0000000000..c7effad9a4 --- /dev/null +++ b/tests/motion-compression.c @@ -0,0 +1,72 @@ +#include +#include + +GtkAdjustment *adjustment; +int cursor_x, cursor_y; + +static void +on_motion_notify (GtkWidget *window, + GdkEventMotion *event) +{ + if (event->window == gtk_widget_get_window (window)) + { + float processing_ms = gtk_adjustment_get_value (adjustment); + g_usleep (processing_ms * 1000); + cursor_x = event->x; + cursor_y = event->y; + gtk_widget_queue_draw (window); + } +} + +static void +on_draw (GtkWidget *window, + cairo_t *cr) +{ + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_paint (cr); + + cairo_set_source_rgb (cr, 0, 0.5, 0.5); + + cairo_arc (cr, cursor_x, cursor_y, 10, 0, 2 * M_PI); + cairo_stroke (cr); +} + +int +main (int argc, char **argv) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *label; + GtkWidget *scale; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (window), 300, 300); + gtk_widget_set_app_paintable (window, TRUE); + gtk_widget_add_events (window, GDK_POINTER_MOTION_MASK); + gtk_widget_set_app_paintable (window, TRUE); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (window), vbox); + + adjustment = gtk_adjustment_new (20, 0, 200, 1, 10, 0); + scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adjustment); + gtk_box_pack_end (GTK_BOX (vbox), scale, FALSE, FALSE, 0); + + label = gtk_label_new ("Event processing time (ms):"); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_end (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + g_signal_connect (window, "motion-notify-event", + G_CALLBACK (on_motion_notify), NULL); + g_signal_connect (window, "draw", + G_CALLBACK (on_draw), NULL); + g_signal_connect (window, "destroy", + G_CALLBACK (gtk_main_quit), NULL); + + gtk_widget_show_all (window); + gtk_main (); + + return 0; +}