Compare commits

...

51 Commits

Author SHA1 Message Date
Owen W. Taylor
96829e4f40 GtkContainer: fix disconnection from frame clock
We need to disconnect the frame clock when we unrealize (at which
point the old clock is still alive) not in destroy(). Since there
is no common unrealize for containers, trigger this from GtkWidget.
2013-02-14 17:11:28 -05:00
Owen W. Taylor
eee365964a GtkTickCallback: document use of G_SOURCE_CONTINUE/REMOVE for return value
Document that G_SOURCE_CONTINUE and G_SOURCE_REMOVE are the preferred
return values from a tick callback.
2013-02-14 16:34:34 -05:00
Owen W. Taylor
11b8600a15 Don't compress motion events for different devices
A switch of device may be significant for an application, so don't
compress motion events if they are for different devices. This simple
handling isn't sufficient if we have competing event streams from
two different pointer events, but we don't expect this case to be
common.
2013-02-14 16:25:49 -05:00
Owen W. Taylor
7bd133c9b8 Ignore window manager protocol messages for destroyed windows
If we get, for example, a _NET_WM_FRAME_DRAWN or _NET_WM_PING
message on a destroyed window, then we should just ignore it.
2013-02-14 13:52:09 -05:00
Owen W. Taylor
9db6a6aea0 Small documentation fixes for frame synchronization
Found by Benjamin Otte
2013-02-14 13:50:55 -05:00
Owen W. Taylor
0d0d587fd1 gdk_frame_clock_get_frame_time(): use gint64 for time 2013-02-13 08:35:05 -05:00
Owen W. Taylor
3e79f7cbfa Merge with Merge GdkFrameHistory into GdkFrameClock 2013-02-13 01:04:24 -05:00
Owen W. Taylor
0e75a5cabb GdkFrameClock: update documentation 2013-02-13 01:04:23 -05:00
Owen W. Taylor
2d0505968c GdkFrameTimings: add documentation 2013-02-13 01:04:23 -05:00
Owen W. Taylor
ff395d35a7 GdkFrameClock: Clean up the public API
* remove gdk_frame_clock_get_frame_time_val(); a convenience
  function that would rarely be used.
* remove gdk_frame_clock_get_requested() and
  ::frame-requested signal; while we might want to eventually
   be able to track the requested phases for a clock, we don't
  have a current use case.
* Make gdk_frame_clock_freeze/thaw() private: they are only
  used within GTK+ and have complex semantics.
* Remove gdk_frame_clock_get_last_complete(). Another convenience
  function that I don't have a current use case for.
* Rename:
  gdk_frame_clock_get_start() => gdk_frame_clock_get_history_start()
  gdk_frame_clocK_get_current_frame_timings() => gdk_frame_clock_get_timings()
2013-02-13 01:04:23 -05:00
Owen W. Taylor
7f59f34003 GdkFrameTimings: strip down to a minimal public API
Since we're not exporting the ability to create your own frame
clock for now, remove the setters for GdkFrameTimings fields.
Also remove all setters and getters for fields that are more
about implementation than about quantities that are meaningful
to the applcation and just access the fields directly within
GDK.
2013-02-13 01:04:23 -05:00
Owen W. Taylor
bc390a7e1a Merge GdkFrameHistory into GdkFrameClock
Now that GdkFrameClock is a  class, not interface, there's no real advantage
to splitting the frame history into an aggregate object, so directly
merge it into GdkFrameClock.
2013-02-13 01:04:23 -05:00
Owen W. Taylor
fcb7822a65 Change GdkFrameClock from an interface to a class
It's unlikely that anyone will want to have, say, a GtkWidget that
also acts as a GdkFrameClock, so an abstract base class is as
flexible as making GdkFrameClock an interface, but has advantages:

 - If we decide to never make implementing your own frame clock
   possible, we can remove the virtualization.
 - We can put functionality like history into the base class.
 - Avoids the oddity of a interface without a public interface
   VTable, which may cause problems for language bindings.
2013-02-13 01:04:23 -05:00
Owen W. Taylor
b0c18211c8 GdkWindow: make the frame clock an inherent property of the window
Instead of making the frame clock a settable property of a window, make
toplevel windows inherently have a frame clock when created (getting
rid of the default frame clock.) We need to create or destroy frame
clocks when reparenting a window to be a toplevel, or to not be a
toplevel, but otherwise the frame clock for a window is immutable.
2013-02-13 01:04:22 -05:00
Owen W. Taylor
f2ab3ad474 Add gtk_widget_add_tick_callback(), remove GtkTimeline, etc.
Add a very simple GtkWidget function for an "tick" callback, which
is connected to the ::update signal of GdkFrameClock.

Remove:

 - GtkTimeline. The consensus is that it is too complex.
 - GdkPaintClockTarget. In the rare cases where tick callbacks
    aren't sufficient, it's possible to track the
    paint clock with ::realize/::unrealize/::hierarchy-changed.

GtkTimeline is kept using ::update directly to allow using a GtkTimeline
with a paint clock but no widget.
2013-02-13 01:04:22 -05:00
Owen W. Taylor
b9837bb8f9 GdkX11DeviceManagerXI2: handle focus events not on a known window
If we get a focus event for a X window we don't recognize, just
ignore it and avoid a g-critical when
_gdk_device_manager_core_handle_focus() is called with a NULL window.
2013-02-13 01:04:22 -05:00
Owen W. Taylor
4faf94d677 Reimplement _NET_WM_SYNC_REQUEST inside X11 backend
Deprecate gdk_window_enable_synchronized_configure() and
gdk_window_configure_done() and make them no-ops. Implement the
handling of _NET_WM_SYNC_REQUEST in terms of the frame cycle -
we know that all processing will be finished in the next frame
cycle after the ConfigureNotify is received.
2013-02-13 01:04:22 -05:00
Owen W. Taylor
05dc350048 Fix up for newer draft of wm-spec
* 64-bit quantities are consistently ordered low-32-bits / high-32-bits
* data.l[4] in _NET_WM_SYNC_REQUEST indicates which counter to update
2013-02-13 01:04:22 -05:00
Owen W. Taylor
63bc508071 video-timer: add simple example of PLL-style clock adjustment
The first version of the video-timer simply played back the video
according to the wall clock, and showed each frame at the neareste
presentatin time. But an alternative strategy for playing back
video is that if the frame-rate is an integer-divisor of the
display refresh rate, or very close to that, is to change the playback
speed to complete avoid frame drops and changes in latency.
(This would require resampling audio if present.)

Demonstrate this technique by adding a --pll option to the
video-timer demo.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:22 -05:00
Owen W. Taylor
d69dc91f41 video-timer: add a test case for display at a constant frame rate
Add a test case that simulates the timing operaton that goes on
when showing a constant frame rate stream like a video - each
frame is shown at the VBlank interval that is closest to when it
would ideally be timed.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:21 -05:00
Owen W. Taylor
74aaa1ddc8 Add gdk_frame_timings_get_predicted_presentation_time()
For an operation like synchronizing audio to video playback, we need to
be able to predict the time that a frame will be presented. The details
of this depend on the windowing system, so make the backend predict
a presentation time for ::begin-frame and set it on the GdkFrameTimings.

The timing algorithm of GdkFrameClockIdle is adjusted to give predictable
presentation times for frames that are not throttled by the windowing
system.

Helper functions:

 gdk_frame_clock_get_current_frame_timings()
 gdk_frame_clock_get_refresh_info()

are added for operations that would otherwise be needed multiple times
in different locations.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:21 -05:00
Owen W. Taylor
517344f56e Add GDK_DEBUG=frames
Add a debug option to print out detailed statistics about each frame drawn.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:21 -05:00
Owen W. Taylor
11302c12ff animated-resizing: enhance output
Show the average and standard deviation of the latency in addition to
the frame rate. Add options to print the output in machine-readable form,
and to control the frequency and total number of statistics that will be
output.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:21 -05:00
Owen W. Taylor
2ddf61ab6f animated-resizing: make circle size a #define
Clean up the code with a #define for circle size. This also allows
determining we're throttled by number-of-requests or number-of-pixels.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:21 -05:00
Owen W. Taylor
43679422a5 GdkWindowX11: Communicate gdk_frame_timings_get_slept_before() to the compositor
We want the compositor to do different things for frames where
"slept before" is TRUE. Communicate to the compositor that
frame is a no-delay frame (slept_before=FALSE) by ending the frame
by increasing the counter value by 1, and that the frame is a
normal frame (slept_before=TRUE) by increasing the counter value
by 3.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:21 -05:00
Owen W. Taylor
459944ef7e Add gdk_frame_timings_get/set_slept_before()
Add functions that tell us whether the main loop slept before we drew
a frame. Blocking with the frame clock frozen doesn't count as sleeping.
We'll use this to advertise to the compositor whether we
are drawing as fast as possible (and it should do the same) or timing
frames carefully (and it should do the same.)

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:21 -05:00
Owen W. Taylor
90eb0a9195 GdkFrameClockIdle: don't start the tiemout/idle when in a frame
Don't start the idle if we're in the middle of painting a frame -
this will prevent us from getting the timing right when starting
the idle after the frame.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:21 -05:00
Owen W. Taylor
c6966f00ce Add GdkFrameHistory and GdkFrameTimings, handle _NET_WM_FRAME_TIMINGS
In order to be able to track statistics about how well we are drawing,
and in order to be able to do sophisticated things with frame timing
like predicting per-frame latencies and synchronizing audio with video,
we need to be able to track exactly when previous frames were drawn
to the screen.

Information about each frame is stored in a new GdkFrameTimings object.
A new GdkFrameHistory object is added which keeps a queue of recent
GdkFrameTimings (this is added to avoid further complicating the
implementation of GdkFrameClock.)

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:20 -05:00
Owen W. Taylor
439f52937c GdkWindowX11: Only start a frame when we emit damage
Instead of communicating the start of a frame to the window manager
as soon as we begin a frame, start a frame only when we know we've
actually created damage to the contents of a window.

(This uses cairo_set_mime_data() as a notification mechanism - a
clever suggestion from Uli Schlachter.)

The advantage of this is that we aren't forcing the compositor to
do a frame cycle and send _NET_WM_FRAME_DRAWN - depending on how the
compositor is structured that might either cause it to do extra
work or it might send _NET_WM_FRAME_DRAWN early and upset frame
timing.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:20 -05:00
Owen W. Taylor
fb3facb0b2 GdkDisplay: handle multiple calls to _gdk_display_pause_events()
Since events can be paused independently for each window during processing,
make _gdk_display_pause_events() count how many times it is called
and only unpause when unpause_events() is called the same number of
times.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:20 -05:00
Owen W. Taylor
91cb469af2 gdk_display_get_event: don't unqueue events from the windowing system when paused
Unqueuing events from the windowing system when paused could result
in weird reordering if event filters resulted in application-visible
behavior. Since we now resume events when the frame clock is frozen,
we now no longer count on low-level event handling running while
event handling is paused.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:20 -05:00
Owen W. Taylor
3591cc0ece GdkFrameClock: Reverse order of resume-events and afterpaint
Keeping events paused after the end of a frame put us in a
weird state where we had to process and queue events - so that
we would get the message from the compositor - but not deliver
them. Instead resume events before ending the frame.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:20 -05:00
Owen W. Taylor
7a0db9999e Compress motion synchronized with the paint cycle
When we have pending motion events, instead of delivering them
directly, request the new FLUSH_EVENTS phase of the frame clock.
This allows us to compress repeated motion events sent to the
same window.

In the FLUSH_EVENTS phase, which occur at priority GDK_PRIORITY_EVENTS + 1,
we deliver any pending motion events then turn off event delivery
until the end of the next frame. Turning off event delivery means
that we'll reliably paint the compressed motion events even if more
have arrived.

Add a motion-compression test case which demonstrates behavior when
an application takes too long handle motion events. It is unusable
without this patch but behaves fine with the patch.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:20 -05:00
Owen W. Taylor
ffba7c0161 GtkScrolledWindow: use GdkFrameClock for kinetic scrolling
Use GdkFrameClock when animating scrolling via touch, rather
than a timeout.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:19 -05:00
Owen W. Taylor
5431431931 Add a test of an animated resizing window
Add a test of a window with an animated size and contents. The
test accepts load factor command line argument to see how things
work as the drawing of the content requires more GPU resources.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:19 -05:00
Owen W. Taylor
ecd953efa0 GtkTimeline: Wrap around the progress correctly when looping
When we have a looping animation for something like an angle,
we need to make sure that the distance we go past 1.0 becomes
the starting distance for the next frame. This prevents a
stutter at the loop position.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:19 -05:00
Owen W. Taylor
d4fd6820ed GtkTimeline: introspection fixes, add :progress-type property
Fix up introspection information for GtkTimeline, install the
header has a public heaer, and add the property for :progress-type.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:19 -05:00
Owen W. Taylor
5e1ec6dd5b Hook GtkTimeline up to GdkFrameClock
Use GdkFrameClock for the timing of GtkTimeline. This require the
user to provide either a GtkWidget or a GdkFrameClock when creating
the timeline. The default constructor now takes a GtkWidget. If you
want to create a GdkFrameClock without a widget, you need to use
g_object_new() and pass in a GdkFrameClock and GdkScreen.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:19 -05:00
Owen W. Taylor
08ed370d1c GtkTimeline: remove settable FPS
The frames-per-second for an animation should be controlled by how
fast we can process frames and the the frame-rate of the display; it's not
a meaningful app-settable property.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:19 -05:00
Owen W. Taylor
4dfe25aa35 Add back GtkTimeline
Add back the GtkTimeline code that previously made private and
then removed. It will be hooked up to GdkFrameClock. This commit
purely adds the old code back.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:19 -05:00
Owen W. Taylor
59bb3d3a8c Add an UPDATE phase and GdkFrameClockTarget, use for GtkStyleContext
Switch GtkStyleContext to using GdkFrameClock. To do this, add a new
UPDATE phase to GdkFrameClock.

Add a GdkFrameClockTarget interface with a single set_clock() method,
and use this to deal with the fact that GtkWidget only has a frame
clock when realized.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:18 -05:00
Owen W. Taylor
ed835e738d GdkFrameClockIdle: add throttling to 60fps
If the backend is throttling paints, then the frame clock will be
frozen at the end of the frame. If not, then we need to add throttling,
so wait until 16ms after the start of the frame before beginning the
next frame.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:18 -05:00
Owen W. Taylor
377fbd1d84 GdkWindowX11: start off with an odd frame-counter value
By starting with an odd frame counter value, we make the mapping
and initial paint of the window an atomic operation, avoiding
any visual artifacts from an unpainted window.

Possible improvement: start the frame when doing gdk_window_show(),
so that the same improvement occurs for windows that were previously
shown and are being mapped again.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:18 -05:00
Owen W. Taylor
04088e7b81 Freeze the update counter for unmapped windows
When a window is unmapped, freeze its frame clock. This avoids doing
unnecessary work, but also means that we won't block waiting for
_NET_WM_FRAME_DRAWN messages that will never be received since the
frame ended while the window was withdrawn.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:18 -05:00
Owen W. Taylor
5f9fb32b0c Use _NET_WM_FRAME_DRAWN to synchronize frame drawing
As part of the extended _NET_WM_SYNC_REQUEST_COUNTER protocol,
we get a _NET_WM_FRAME_DRAWN message for each frame we draw. Use this
to synchronize the updates we are doing with the compositing manager's
drawing, and ultimately with with display refresh.

We now set the sync request counters on all windows, including
override-redirect windows, since it is also useful to do synchronized,
atomic updates for such windows.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:18 -05:00
Owen W. Taylor
5fda1e49bf Switch to an extended form of _NET_WM_SYNC_REQUEST_COUNTER
By exporting two XSync counters on a toplevel window, we subscribe
to an extended form of the _NET_WM_SYNC_REQUEST_COUNTER protocol,
where the window manager can initiate an atomic frame, as previously,
but the application can also do so by incrementing the new counter to
an odd value, and then to an even value to finish the frame.

See:
https://mail.gnome.org/archives/wm-spec-list/2011-October/msg00006.html

The support for 64-bit integers that GLib requires is used to
simplify the logic.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:18 -05:00
Owen W. Taylor
30f5599163 GdkFrameClock: add freeze/thaw
Add the ability to freeze a frame clock, which pauses its operation,
then thaw it again later to resume.

Initially this is used to implement freezing updates when we are
waiting for ConfigureNotify in response to changing the size of
a toplevel.

We need a per-window clock for this to work properly, so add that
for the X11 backend.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:17 -05:00
Owen W. Taylor
48e82e8b12 GdkFrameClock: Make the phase explicit when requesting the frame
Instead of having gdk_frame_clock_request_frame() have
gdk_frame_clock_request_phase() where we can say what phase we need.
This allows us to know if we get a frame-request during layout whether
it's just a request for drawing from the layout, or whether another
layout phase is needed.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:17 -05:00
Owen W. Taylor
4856eb0c90 Use GdkFrameClock for relayout
Add a ::layout signal to GdkFrameClock and use it instead of an idle
handler to drive the restyling and relayout of containers.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:17 -05:00
Owen W. Taylor
68559ee790 Add GdkFrameClock
Add an object GdkFrameClock that we associate with a GdkWindow.
This tracks when the window needs to be repainted, and will also
be used for other operations in the future like relayout and
updating animations.

Based on a patch from Havoc Pennington:

 https://mail.gnome.org/archives/gtk-devel-list/2010-October/msg00004.html

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:17 -05:00
Owen W. Taylor
1a0f5e8aaa GdkDisplayX11: Don't use substructure events in internal accounting
We may receive events because SubstructureNotifyMask has been selected
for the root window. (Most likely, this would occur because GTK+
is being used inside a window manager like Metacity or Mutter.)
This can confuse various types of internal accounting, so detect
such events and comprehensively ignore them for GDK's internal
purposes. We still need to generate GDK events for these cases
because you can select for substructure events with
GDK_SUBSTRUCTURE_MASK.

https://bugzilla.gnome.org/show_bug.cgi?id=685460
2013-02-13 01:04:17 -05:00
38 changed files with 3932 additions and 393 deletions

View File

@@ -75,11 +75,13 @@ gdk_public_h_sources = \
gdkdisplaymanager.h \
gdkdnd.h \
gdkevents.h \
gdkframetimings.h \
gdkkeys.h \
gdkkeysyms.h \
gdkkeysyms-compat.h \
gdkmain.h \
gdkpango.h \
gdkframeclock.h \
gdkpixbuf.h \
gdkprivate.h \
gdkproperty.h \
@@ -101,6 +103,8 @@ gdk_private_headers = \
gdkdisplaymanagerprivate.h \
gdkdisplayprivate.h \
gdkdndprivate.h \
gdkframeclockidle.h \
gdkframeclockprivate.h \
gdkscreenprivate.h \
gdkinternals.h \
gdkintl.h \
@@ -121,10 +125,13 @@ gdk_c_sources = \
gdkdisplaymanager.c \
gdkdnd.c \
gdkevents.c \
gdkframetimings.c \
gdkglobals.c \
gdkkeys.c \
gdkkeyuni.c \
gdkoffscreenwindow.c \
gdkframeclock.c \
gdkframeclockidle.c \
gdkpango.c \
gdkpixbuf-drawable.c \
gdkrectangle.c \

View File

@@ -1294,20 +1294,6 @@ gdk_broadway_window_begin_move_drag (GdkWindow *window,
}
static void
gdk_broadway_window_enable_synchronized_configure (GdkWindow *window)
{
if (!GDK_IS_WINDOW_IMPL_BROADWAY (window->impl))
return;
}
static void
gdk_broadway_window_configure_finished (GdkWindow *window)
{
if (!WINDOW_IS_TOPLEVEL (window))
return;
}
static gboolean
gdk_broadway_window_beep (GdkWindow *window)
{
@@ -1499,8 +1485,6 @@ gdk_window_impl_broadway_class_init (GdkWindowImplBroadwayClass *klass)
impl_class->set_functions = gdk_broadway_window_set_functions;
impl_class->begin_resize_drag = gdk_broadway_window_begin_resize_drag;
impl_class->begin_move_drag = gdk_broadway_window_begin_move_drag;
impl_class->enable_synchronized_configure = gdk_broadway_window_enable_synchronized_configure;
impl_class->configure_finished = gdk_broadway_window_configure_finished;
impl_class->set_opacity = gdk_broadway_window_set_opacity;
impl_class->set_composited = gdk_broadway_window_set_composited;
impl_class->destroy_notify = gdk_broadway_window_destroy_notify;

View File

@@ -138,7 +138,8 @@ static const GDebugKey gdk_debug_keys[] = {
{"multihead", GDK_DEBUG_MULTIHEAD},
{"xinerama", GDK_DEBUG_XINERAMA},
{"draw", GDK_DEBUG_DRAW},
{"eventloop", GDK_DEBUG_EVENTLOOP}
{"eventloop", GDK_DEBUG_EVENTLOOP},
{"frames", GDK_DEBUG_FRAMES}
};
static gboolean

View File

@@ -307,7 +307,11 @@ gdk_display_get_event (GdkDisplay *display)
{
g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);
if (display->event_pause_count > 0)
return NULL;
GDK_DISPLAY_GET_CLASS (display)->queue_events (display);
return _gdk_event_unqueue (display);
}
@@ -2003,6 +2007,38 @@ gdk_display_notify_startup_complete (GdkDisplay *display,
GDK_DISPLAY_GET_CLASS (display)->notify_startup_complete (display, startup_id);
}
void
_gdk_display_pause_events (GdkDisplay *display)
{
display->event_pause_count++;
}
void
_gdk_display_unpause_events (GdkDisplay *display)
{
g_return_if_fail (display->event_pause_count > 0);
display->event_pause_count--;
}
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,

View File

@@ -113,7 +113,10 @@ struct _GdkDisplay
guint double_click_time; /* Maximum time between clicks in msecs */
GdkDevice *core_pointer; /* Core pointer device */
guint event_pause_count; /* How many times events are blocked */
guint closed : 1; /* Whether this display has been closed */
guint flushing_events : 1; /* Inside gdk_display_flush_events */
GArray *touch_implicit_grabs;
GHashTable *device_grabs;
@@ -296,6 +299,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_pause_events (GdkDisplay *display);
void _gdk_display_unpause_events (GdkDisplay *display);
void _gdk_display_flush_events (GdkDisplay *display);
void _gdk_display_event_data_copy (GdkDisplay *display,
const GdkEvent *event,
GdkEvent *new_event);

View File

@@ -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->event_pause_count > 0)
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,61 @@ _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;
GdkDevice *pending_motion_device = 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;
if (pending_motion_device != NULL &&
pending_motion_device != event->event.motion.device)
break;
pending_motion_window = event->event.motion.window;
pending_motion_device = event->event.motion.device;
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.

563
gdk/gdkframeclock.c Normal file
View File

@@ -0,0 +1,563 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the GTK+ Team and others 1997-2010. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include "gdkframeclockprivate.h"
#include "gdkinternals.h"
/**
* SECTION:frameclock
* @Short_description: Frame clock syncs painting to a window or display
* @Title: Frame clock
*
* A #GdkFrameClock tells the application when to update and repaint a
* window. This may be synced to the vertical refresh rate of the
* monitor, for example. Even when the frame clock uses a simple timer
* rather than a hardware-based vertical sync, the frame clock helps
* because it ensures everything paints at the same time (reducing the
* total number of frames). The frame clock can also automatically
* stop painting when it knows the frames will not be visible, or
* scale back animation framerates.
*
* #GdkFrameClock is designed to be compatible with an OpenGL-based
* implementation or with mozRequestAnimationFrame in Firefox,
* for example.
*
* A frame clock is idle until someone requests a frame with
* gdk_frame_clock_request_phase(). At some later point that makes
* sense for the synchronization being implemented, the clock will
* process a frame and emit signals for each phase that has been
* requested. (See the signals of the #GdkFrameClock class for
* documentation of the phases. GDK_FRAME_CLOCK_PHASE_UPDATE and the
* ::update signal are most interesting for application writers, and
* are used to update the animations, using the frame time given by
* gdk_frame_clock_get_frame_time().
*
* The frame time is reported in microseconds and generally in the same
* timescale as g_get_monotonic_time(), however, it is not the same
* as g_get_monotonic_time(). The frame time does not advance during
* the time a frame is being painted, and outside of a frame, an attempt
* is made so that all calls to gdk_frame_clock_get_frame_time() that
* are called at a "similar" time get the same value. This means that
* if different animations are timed by looking at the difference in
* time between an initial value from gdk_frame_clock_get_frame_time()
* and the value inside the ::update signal of the clock, they will
* stay exactly synchronized.
*/
G_DEFINE_ABSTRACT_TYPE (GdkFrameClock, gdk_frame_clock, G_TYPE_OBJECT)
enum {
FLUSH_EVENTS,
BEFORE_PAINT,
UPDATE,
LAYOUT,
PAINT,
AFTER_PAINT,
RESUME_EVENTS,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
#define FRAME_HISTORY_MAX_LENGTH 16
struct _GdkFrameClockPrivate
{
gint64 frame_counter;
gint n_timings;
gint current;
GdkFrameTimings *timings[FRAME_HISTORY_MAX_LENGTH];
};
static void
gdk_frame_clock_finalize (GObject *object)
{
GdkFrameClockPrivate *priv = GDK_FRAME_CLOCK (object)->priv;
int i;
for (i = 0; i < FRAME_HISTORY_MAX_LENGTH; i++)
if (priv->timings[i] != 0)
gdk_frame_timings_unref (priv->timings[i]);
G_OBJECT_CLASS (gdk_frame_clock_parent_class)->finalize (object);
}
static void
gdk_frame_clock_class_init (GdkFrameClockClass *klass)
{
GObjectClass *gobject_class = (GObjectClass*) klass;
gobject_class->finalize = gdk_frame_clock_finalize;
/**
* GdkFrameClock::flush-events:
* @clock: the frame clock emitting the signal
*
* This signal is used to flush pending motion events that
* are being batched up and compressed together. Applications
* should not handle this signal.
*/
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
*
* This signal begins processing of the frame. Applications
* should generally not handle this signal.
*/
signals[BEFORE_PAINT] =
g_signal_new (g_intern_static_string ("before-paint"),
GDK_TYPE_FRAME_CLOCK,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* GdkFrameClock::update:
* @clock: the frame clock emitting the signal
*
* This signal is emitted as the first step of toolkit and
* application processing of the frame. Animations should
* be updated using gdk_frame_clock_get_frame_time().
* Applications can connect directly to this signal, or
* use gtk_widget_add_tick_callback() as a more convenient
* interface.
*/
signals[UPDATE] =
g_signal_new (g_intern_static_string ("update"),
GDK_TYPE_FRAME_CLOCK,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* GdkFrameClock::layout:
* @clock: the frame clock emitting the signal
*
* This signal is emitted as the second step of toolkit and
* application processing of the frame. Any work to update
* sizes and positions of application elements should be
* performed. GTK normally handles this internally.
*/
signals[LAYOUT] =
g_signal_new (g_intern_static_string ("layout"),
GDK_TYPE_FRAME_CLOCK,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* GdkFrameClock::layout:
* @clock: the frame clock emitting the signal
*
* This signal is emitted as the third step of toolkit and
* application processing of the frame. The frame is
* repainted. GDK normally handles this internally and
* produce expose events, which are turned into GTK
* GtkWidget::draw signals.
*/
signals[PAINT] =
g_signal_new (g_intern_static_string ("paint"),
GDK_TYPE_FRAME_CLOCK,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* GdkFrameClock::after-paint:
* @clock: the frame clock emitting the signal
*
* This signal ends processing of the frame. Applications
* should generally not handle this signal.
*/
signals[AFTER_PAINT] =
g_signal_new (g_intern_static_string ("after-paint"),
GDK_TYPE_FRAME_CLOCK,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* GdkFrameClock::resume-events:
* @clock: the frame clock emitting the signal
*
* This signal is emitted after processing of the frame is
* finished, and is handled internally by GTK+ to resume normal
* event processing. Applications should not handle this signal.
*/
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);
g_type_class_add_private (klass, sizeof (GdkFrameClockPrivate));
}
static void
gdk_frame_clock_init (GdkFrameClock *clock)
{
GdkFrameClockPrivate *priv;
clock->priv = G_TYPE_INSTANCE_GET_PRIVATE (clock,
GDK_TYPE_FRAME_CLOCK,
GdkFrameClockPrivate);
priv = clock->priv;
priv->frame_counter = -1;
priv->current = FRAME_HISTORY_MAX_LENGTH - 1;
}
/**
* gdk_frame_clock_get_frame_time:
* @frame_clock: a #GdkFrameClock
*
* Gets the time that should currently be used for animations. Inside
* the processing of a frame, it's the time used to compute the
* animation position of everything in a frame. Outside of a frame, it's
* the time of the conceptual "previous frame," which may be either
* the actual previous frame time, or if that's too old, an updated
* time.
*
* Since: 3.8
* Return value: a timestamp in microseconds, in the timescale of
* of g_get_monotonic_time().
*/
gint64
gdk_frame_clock_get_frame_time (GdkFrameClock *frame_clock)
{
g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), 0);
return GDK_FRAME_CLOCK_GET_CLASS (frame_clock)->get_frame_time (frame_clock);
}
/**
* gdk_frame_clock_request_phase:
* @frame_clock: a #GdkFrameClock
*
* Asks the frame clock to run a particular phase. The signal
* corresponding the requested phase will be emitted the next
* time the frame clock processes. Multiple calls to
* gdk_frame_clock_request_phase() will be combined togethe
* and only one frame processed.
*
* Since: 3.8
*/
void
gdk_frame_clock_request_phase (GdkFrameClock *frame_clock,
GdkFrameClockPhase phase)
{
g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
GDK_FRAME_CLOCK_GET_CLASS (frame_clock)->request_phase (frame_clock, phase);
}
void
_gdk_frame_clock_freeze (GdkFrameClock *clock)
{
g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
GDK_FRAME_CLOCK_GET_CLASS (clock)->freeze (clock);
}
void
_gdk_frame_clock_thaw (GdkFrameClock *clock)
{
g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
GDK_FRAME_CLOCK_GET_CLASS (clock)->thaw (clock);
}
/**
* gdk_frame_clock_get_frame_counter:
* @frame_clock: a #GdkFrameClock
*
* A #GdkFrameClock maintains a 64-bit counter that increments for
* each frame drawn.
*
* Returns: inside frame processing, the value of the frame counter
* for the current frame. Outside of frame processing, the frame
* counter for the last frame.
* Since: 3.8
*/
gint64
gdk_frame_clock_get_frame_counter (GdkFrameClock *frame_clock)
{
GdkFrameClockPrivate *priv;
g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), 0);
priv = frame_clock->priv;
return priv->frame_counter;
}
/**
* gdk_frame_clock_get_history_start:
* @frame_clock: a #GdkFrameClock
*
* #GdkFrameClock internally keeps a history of #GdkFrameTiming
* objects for recent frames that can be retrieved with
* gdk_frame_clock_get_timings(). The set of stored frames
* is the set from the counter values given by
* gdk_frame_clock_get_history_start() and
* gdk_frame_clock_get_frame_counter(), inclusive.
*
* Return value: the frame counter value for the oldest frame
* that is available in the internal frame history of the
* #GdkFrameClock.
* Since: 3.8
*/
gint64
gdk_frame_clock_get_history_start (GdkFrameClock *frame_clock)
{
GdkFrameClockPrivate *priv;
g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), 0);
priv = frame_clock->priv;
return priv->frame_counter + 1 - priv->n_timings;
}
void
_gdk_frame_clock_begin_frame (GdkFrameClock *frame_clock)
{
GdkFrameClockPrivate *priv;
g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
priv = frame_clock->priv;
priv->frame_counter++;
priv->current = (priv->current + 1) % FRAME_HISTORY_MAX_LENGTH;
if (priv->n_timings < FRAME_HISTORY_MAX_LENGTH)
priv->n_timings++;
else
{
gdk_frame_timings_unref(priv->timings[priv->current]);
}
priv->timings[priv->current] = _gdk_frame_timings_new (priv->frame_counter);
}
/**
* gdk_frame_clock_get_timings:
* @frame_clock: a #GdkFrameClock
* @frame_counter: the frame counter value identifying the frame to
* be received.
*
* Retrieves a #GdkFrameTimings object holding timing information
* for the current frame or a recent frame. The #GdkFrameTimings
* object may not yet be complete: see gdk_frame_timings_get_complete().
*
* Return value: the #GdkFrameTimings object for the specified
* frame, or %NULL if it is not available. See
* gdk_frame_clock_get_history_start().
* Since: 3.8
*/
GdkFrameTimings *
gdk_frame_clock_get_timings (GdkFrameClock *frame_clock,
gint64 frame_counter)
{
GdkFrameClockPrivate *priv;
gint pos;
g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), NULL);
priv = frame_clock->priv;
if (frame_counter > priv->frame_counter)
return NULL;
if (frame_counter <= priv->frame_counter - priv->n_timings)
return NULL;
pos = (priv->current - (priv->frame_counter - frame_counter) + FRAME_HISTORY_MAX_LENGTH) % FRAME_HISTORY_MAX_LENGTH;
return priv->timings[pos];
}
/**
* gdk_frame_clock_get_current_timings:
* @frame_clock: a #GdkFrameClock
*
* Gets the frame timings for the current frame.
*
* Returns: the #GdkFrameTimings for the frame currently being
* processed, or even no frame is being processed, for the
* previous frame. Before any frames have been procesed,
* returns %NULL.
* Since: 3.8
*/
GdkFrameTimings *
gdk_frame_clock_get_current_timings (GdkFrameClock *frame_clock)
{
GdkFrameClockPrivate *priv;
g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), 0);
priv = frame_clock->priv;
return gdk_frame_clock_get_timings (frame_clock, priv->frame_counter);
}
#ifdef G_ENABLE_DEBUG
void
_gdk_frame_clock_debug_print_timings (GdkFrameClock *clock,
GdkFrameTimings *timings)
{
gint64 previous_frame_time = 0;
GdkFrameTimings *previous_timings = gdk_frame_clock_get_timings (clock,
timings->frame_counter - 1);
if (previous_timings != NULL)
previous_frame_time = previous_timings->frame_time;
g_print ("%5" G_GINT64_FORMAT ":", timings->frame_counter);
if (previous_frame_time != 0)
{
g_print (" interval=%-4.1f", (timings->frame_time - previous_frame_time) / 1000.);
g_print (timings->slept_before ? " (sleep)" : " ");
}
if (timings->layout_start_time != 0)
g_print (" layout_start=%-4.1f", (timings->layout_start_time - timings->frame_time) / 1000.);
if (timings->paint_start_time != 0)
g_print (" paint_start=%-4.1f", (timings->paint_start_time - timings->frame_time) / 1000.);
if (timings->frame_end_time != 0)
g_print (" frame_end=%-4.1f", (timings->frame_end_time - timings->frame_time) / 1000.);
if (timings->presentation_time != 0)
g_print (" present=%-4.1f", (timings->presentation_time - timings->frame_time) / 1000.);
if (timings->predicted_presentation_time != 0)
g_print (" predicted=%-4.1f", (timings->predicted_presentation_time - timings->frame_time) / 1000.);
if (timings->refresh_interval != 0)
g_print (" refresh_interval=%-4.1f", timings->refresh_interval / 1000.);
g_print ("\n");
}
#endif /* G_ENABLE_DEBUG */
#define DEFAULT_REFRESH_INTERVAL 16667 /* 16.7ms (1/60th second) */
#define MAX_HISTORY_AGE 150000 /* 150ms */
/**
* gdk_frame_clock_get_refresh_info:
* @frame_clock: a #GdkFrameClock
* @base_time: base time for determining a presentaton time
* @refresh_interval_return: a location to store the determined refresh
* interval, or %NULL. A default refresh interval of 1/60th of
* a second will be stored if no history is present.
* @presentation_time_return: a location to store the next
* candidate presentation time after the given base time.
* 0 will be will be stored if no history is present.
*
* Using the frame history stored in the frame clock, finds the last
* known presentation time and refresh interval, and assuming that
* presentation times are separated by the refresh interval,
* predicts a presentation time that is a multiple of the refresh
* interval after the last presentation time, and later than @base_time.
*
* Since: 3.8
*/
void
gdk_frame_clock_get_refresh_info (GdkFrameClock *frame_clock,
gint64 base_time,
gint64 *refresh_interval_return,
gint64 *presentation_time_return)
{
gint64 frame_counter;
g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
frame_counter = gdk_frame_clock_get_frame_counter (frame_clock);
if (presentation_time_return)
*presentation_time_return = 0;
if (refresh_interval_return)
*refresh_interval_return = DEFAULT_REFRESH_INTERVAL;
while (TRUE)
{
GdkFrameTimings *timings = gdk_frame_clock_get_timings (frame_clock, frame_counter);
gint64 presentation_time;
gint64 refresh_interval;
if (timings == NULL)
return;
refresh_interval = timings->refresh_interval;
presentation_time = timings->presentation_time;
if (presentation_time != 0)
{
if (presentation_time > base_time - MAX_HISTORY_AGE &&
presentation_time_return)
{
if (refresh_interval == 0)
refresh_interval = DEFAULT_REFRESH_INTERVAL;
if (refresh_interval_return)
*refresh_interval_return = refresh_interval;
while (presentation_time < base_time)
presentation_time += refresh_interval;
if (presentation_time_return)
*presentation_time_return = presentation_time;
}
return;
}
frame_counter--;
}
}

105
gdk/gdkframeclock.h Normal file
View File

@@ -0,0 +1,105 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the GTK+ Team and others 1997-2010. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#if !defined (__GDK_H_INSIDE__) && !defined (GDK_COMPILATION)
#error "Only <gdk/gdk.h> can be included directly."
#endif
#ifndef __GDK_FRAME_CLOCK_H__
#define __GDK_FRAME_CLOCK_H__
#include <gdk/gdkframetimings.h>
G_BEGIN_DECLS
#define GDK_TYPE_FRAME_CLOCK (gdk_frame_clock_get_type ())
#define GDK_FRAME_CLOCK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_FRAME_CLOCK, GdkFrameClock))
#define GDK_FRAME_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_FRAME_CLOCK, GdkFrameClockClass))
#define GDK_IS_FRAME_CLOCK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_FRAME_CLOCK))
#define GDK_IS_FRAME_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_FRAME_CLOCK))
#define GDK_FRAME_CLOCK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_FRAME_CLOCK, GdkFrameClockClass))
typedef struct _GdkFrameClock GdkFrameClock;
typedef struct _GdkFrameClockPrivate GdkFrameClockPrivate;
typedef struct _GdkFrameClockClass GdkFrameClockClass;
/**
* GdkFrameClockPhase:
* @GDK_FRAME_CLOCK_PHASE_NONE: no phase
* @GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS: corresponds to GdkFrameClock::flush-events. Should not be handled by applications.
* @GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT: corresponds to GdkFrameClock::before-paint. Should not be handled by applications.
* @GDK_FRAME_CLOCK_PHASE_UPDATE: corresponds to GdkFrameClock::update.
* @GDK_FRAME_CLOCK_PHASE_LAYOUT: corresponds to GdkFrameClock::layout.
* @GDK_FRAME_CLOCK_PHASE_PAINT: corresponds to GdkFrameClock::paint.
* @GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS: corresponds to GdkFrameClock::resume-events. Should not be handled by applications.
* @GDK_FRAME_CLOCK_PHASE_AFTER_PAINT: corresponds to GdkFrameClock::after-paint. Should not be handled by applications.
*
* #GdkFrameClockPhase is used to represent the different paint clock
* phases that can be requested. The element of the enumeration
* correspond to the signals of #GdkPaintClock.
*
* Since: 3.8
**/
typedef enum {
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_RESUME_EVENTS = 1 << 5,
GDK_FRAME_CLOCK_PHASE_AFTER_PAINT = 1 << 6
} GdkFrameClockPhase;
GType gdk_frame_clock_get_type (void) G_GNUC_CONST;
gint64 gdk_frame_clock_get_frame_time (GdkFrameClock *frame_clock);
GDK_AVAILABLE_IN_3_8
void gdk_frame_clock_request_phase (GdkFrameClock *frame_clock,
GdkFrameClockPhase phase);
/* Frame history */
GDK_AVAILABLE_IN_3_8
gint64 gdk_frame_clock_get_frame_counter (GdkFrameClock *frame_clock);
GDK_AVAILABLE_IN_3_8
gint64 gdk_frame_clock_get_history_start (GdkFrameClock *frame_clock);
GDK_AVAILABLE_IN_3_8
GdkFrameTimings *gdk_frame_clock_get_timings (GdkFrameClock *frame_clock,
gint64 frame_counter);
GDK_AVAILABLE_IN_3_8
GdkFrameTimings *gdk_frame_clock_get_current_timings (GdkFrameClock *frame_clock);
GDK_AVAILABLE_IN_3_8
void gdk_frame_clock_get_refresh_info (GdkFrameClock *frame_clock,
gint64 base_time,
gint64 *refresh_interval_return,
gint64 *presentation_time_return);
G_END_DECLS
#endif /* __GDK_FRAME_CLOCK_H__ */

499
gdk/gdkframeclockidle.c Normal file
View File

@@ -0,0 +1,499 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the GTK+ Team and others 1997-2010. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include "gdkinternals.h"
#include "gdkframeclockprivate.h"
#include "gdkframeclockidle.h"
#include "gdk.h"
#define FRAME_INTERVAL 16667 // microseconds
struct _GdkFrameClockIdlePrivate
{
GTimer *timer;
/* timer_base is used to avoid ever going backward */
gint64 timer_base;
gint64 frame_time;
gint64 min_next_frame_time;
gint64 sleep_serial;
guint flush_idle_id;
guint paint_idle_id;
guint freeze_count;
GdkFrameClockPhase requested;
GdkFrameClockPhase phase;
guint in_paint_idle : 1;
};
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);
G_DEFINE_TYPE (GdkFrameClockIdle, gdk_frame_clock_idle, GDK_TYPE_FRAME_CLOCK)
static gint64 sleep_serial;
static gint64 sleep_source_prepare_time;
static GSource *sleep_source;
gboolean
sleep_source_prepare (GSource *source,
gint *timeout)
{
sleep_source_prepare_time = g_source_get_time (source);
*timeout = -1;
return FALSE;
}
gboolean
sleep_source_check (GSource *source)
{
if (g_source_get_time (source) != sleep_source_prepare_time)
sleep_serial++;
return FALSE;
}
gboolean
sleep_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
return TRUE;
}
static GSourceFuncs sleep_source_funcs = {
sleep_source_prepare,
sleep_source_check,
sleep_source_dispatch,
NULL /* finalize */
};
static gint64
get_sleep_serial (void)
{
if (sleep_source == NULL)
{
sleep_source = g_source_new (&sleep_source_funcs, sizeof (GSource));
g_source_set_priority (sleep_source, G_PRIORITY_HIGH);
g_source_attach (sleep_source, NULL);
g_source_unref (sleep_source);
}
return sleep_serial;
}
static void
gdk_frame_clock_idle_init (GdkFrameClockIdle *frame_clock_idle)
{
GdkFrameClockIdlePrivate *priv;
frame_clock_idle->priv = G_TYPE_INSTANCE_GET_PRIVATE (frame_clock_idle,
GDK_TYPE_FRAME_CLOCK_IDLE,
GdkFrameClockIdlePrivate);
priv = frame_clock_idle->priv;
priv->timer = g_timer_new ();
priv->freeze_count = 0;
}
static void
gdk_frame_clock_idle_finalize (GObject *object)
{
GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (object)->priv;
g_timer_destroy (priv->timer);
G_OBJECT_CLASS (gdk_frame_clock_idle_parent_class)->finalize (object);
}
static gint64
compute_frame_time (GdkFrameClockIdle *idle)
{
GdkFrameClockIdlePrivate *priv = idle->priv;
gint64 computed_frame_time;
gint64 elapsed;
elapsed = g_get_monotonic_time () + priv->timer_base;
if (elapsed < priv->frame_time)
{
/* clock went backward. adapt to that by forevermore increasing
* timer_base. For now, assume we've gone forward in time 1ms.
*/
/* hmm. just fix GTimer? */
computed_frame_time = priv->frame_time + 1;
priv->timer_base += (priv->frame_time - elapsed) + 1;
}
else
{
computed_frame_time = elapsed;
}
return computed_frame_time;
}
static gint64
gdk_frame_clock_idle_get_frame_time (GdkFrameClock *clock)
{
GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
gint64 computed_frame_time;
/* can't change frame time during a paint */
if (priv->phase != GDK_FRAME_CLOCK_PHASE_NONE &&
priv->phase != GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS)
return priv->frame_time;
/* Outside a paint, pick something close to "now" */
computed_frame_time = compute_frame_time (GDK_FRAME_CLOCK_IDLE (clock));
/* 16ms is 60fps. We only update frame time that often because we'd
* like to try to keep animations on the same start times.
* get_frame_time() would normally be used outside of a paint to
* record an animation start time for example.
*/
if ((computed_frame_time - priv->frame_time) > FRAME_INTERVAL)
priv->frame_time = computed_frame_time;
return priv->frame_time;
}
static void
maybe_start_idle (GdkFrameClockIdle *clock_idle)
{
GdkFrameClockIdlePrivate *priv = clock_idle->priv;
if (priv->freeze_count == 0 && priv->requested != 0)
{
guint min_interval = 0;
if (priv->min_next_frame_time != 0)
{
gint64 now = compute_frame_time (clock_idle);
gint64 min_interval_us = MAX (priv->min_next_frame_time, now) - now;
min_interval = (min_interval_us + 500) / 1000;
}
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);
}
if (priv->paint_idle_id == 0 &&
!priv->in_paint_idle &&
(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);
}
}
}
static gint64
compute_min_next_frame_time (GdkFrameClockIdle *clock_idle,
gint64 last_frame_time)
{
gint64 presentation_time;
gint64 refresh_interval;
gdk_frame_clock_get_refresh_info (GDK_FRAME_CLOCK (clock_idle),
last_frame_time,
&refresh_interval, &presentation_time);
if (presentation_time == 0)
return last_frame_time + refresh_interval;
else
return presentation_time + refresh_interval / 2;
}
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;
GdkFrameTimings *timings = NULL;
priv->paint_idle_id = 0;
priv->in_paint_idle = TRUE;
priv->min_next_frame_time = 0;
skip_to_resume_events =
(priv->requested & ~(GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS | GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS)) == 0;
if (priv->phase > GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT)
{
timings = gdk_frame_clock_get_current_timings (clock);
}
if (!skip_to_resume_events)
{
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);
_gdk_frame_clock_begin_frame (clock);
timings = gdk_frame_clock_get_current_timings (clock);
timings->frame_time = priv->frame_time;
timings->slept_before = priv->sleep_serial != get_sleep_serial ();
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. */
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)
{
if (priv->requested & GDK_FRAME_CLOCK_PHASE_UPDATE)
{
priv->requested &= ~GDK_FRAME_CLOCK_PHASE_UPDATE;
g_signal_emit_by_name (G_OBJECT (clock), "update");
}
}
case GDK_FRAME_CLOCK_PHASE_LAYOUT:
if (priv->freeze_count == 0)
{
#ifdef G_ENABLE_DEBUG
if ((_gdk_debug_flags & GDK_DEBUG_FRAMES) != 0)
{
if (priv->phase != GDK_FRAME_CLOCK_PHASE_LAYOUT &&
(priv->requested & GDK_FRAME_CLOCK_PHASE_LAYOUT))
timings->layout_start_time = g_get_monotonic_time ();
}
#endif /* G_ENABLE_DEBUG */
priv->phase = GDK_FRAME_CLOCK_PHASE_LAYOUT;
if (priv->requested & GDK_FRAME_CLOCK_PHASE_LAYOUT)
{
priv->requested &= ~GDK_FRAME_CLOCK_PHASE_LAYOUT;
g_signal_emit_by_name (G_OBJECT (clock), "layout");
}
}
case GDK_FRAME_CLOCK_PHASE_PAINT:
if (priv->freeze_count == 0)
{
#ifdef G_ENABLE_DEBUG
if ((_gdk_debug_flags & GDK_DEBUG_FRAMES) != 0)
{
if (priv->phase != GDK_FRAME_CLOCK_PHASE_PAINT &&
(priv->requested & GDK_FRAME_CLOCK_PHASE_PAINT))
timings->paint_start_time = g_get_monotonic_time ();
}
#endif /* G_ENABLE_DEBUG */
priv->phase = GDK_FRAME_CLOCK_PHASE_PAINT;
if (priv->requested & GDK_FRAME_CLOCK_PHASE_PAINT)
{
priv->requested &= ~GDK_FRAME_CLOCK_PHASE_PAINT;
g_signal_emit_by_name (G_OBJECT (clock), "paint");
}
}
case GDK_FRAME_CLOCK_PHASE_AFTER_PAINT:
if (priv->freeze_count == 0)
{
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_NONE;
#ifdef G_ENABLE_DEBUG
if ((_gdk_debug_flags & GDK_DEBUG_FRAMES) != 0)
timings->frame_end_time = g_get_monotonic_time ();
#endif /* G_ENABLE_DEBUG */
}
case GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS:
;
}
}
#ifdef G_ENABLE_DEBUG
if ((_gdk_debug_flags & GDK_DEBUG_FRAMES) != 0)
{
if (timings->complete)
_gdk_frame_clock_debug_print_timings (clock, timings);
}
#endif /* G_ENABLE_DEBUG */
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");
}
if (priv->freeze_count == 0)
priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
priv->in_paint_idle = FALSE;
/* If there is throttling in the backend layer, then we'll do another
* update as soon as the backend unthrottles (if there is work to do),
* otherwise we need to figure when the next frame should be.
*/
if (priv->freeze_count == 0)
{
priv->min_next_frame_time = compute_min_next_frame_time (clock_idle,
priv->frame_time);
maybe_start_idle (clock_idle);
}
if (priv->freeze_count == 0)
priv->sleep_serial = get_sleep_serial ();
return FALSE;
}
static void
gdk_frame_clock_idle_request_phase (GdkFrameClock *clock,
GdkFrameClockPhase phase)
{
GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
GdkFrameClockIdlePrivate *priv = clock_idle->priv;
priv->requested |= phase;
maybe_start_idle (clock_idle);
}
static void
gdk_frame_clock_idle_freeze (GdkFrameClock *clock)
{
GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
priv->freeze_count++;
if (priv->freeze_count == 1)
{
if (priv->flush_idle_id)
{
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;
}
}
}
static void
gdk_frame_clock_idle_thaw (GdkFrameClock *clock)
{
GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
GdkFrameClockIdlePrivate *priv = clock_idle->priv;
g_return_if_fail (priv->freeze_count > 0);
priv->freeze_count--;
if (priv->freeze_count == 0)
{
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;
priv->sleep_serial = get_sleep_serial ();
}
}
static void
gdk_frame_clock_idle_class_init (GdkFrameClockIdleClass *klass)
{
GObjectClass *gobject_class = (GObjectClass*) klass;
GdkFrameClockClass *frame_clock_class = (GdkFrameClockClass *)klass;
gobject_class->finalize = gdk_frame_clock_idle_finalize;
frame_clock_class->get_frame_time = gdk_frame_clock_idle_get_frame_time;
frame_clock_class->request_phase = gdk_frame_clock_idle_request_phase;
frame_clock_class->freeze = gdk_frame_clock_idle_freeze;
frame_clock_class->thaw = gdk_frame_clock_idle_thaw;
g_type_class_add_private (klass, sizeof (GdkFrameClockIdlePrivate));
}
GdkFrameClock *
_gdk_frame_clock_idle_new (void)
{
GdkFrameClockIdle *clock;
clock = g_object_new (GDK_TYPE_FRAME_CLOCK_IDLE, NULL);
return GDK_FRAME_CLOCK (clock);
}

68
gdk/gdkframeclockidle.h Normal file
View File

@@ -0,0 +1,68 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the GTK+ Team and others 1997-2010. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
/* Uninstalled header, internal to GDK */
#ifndef __GDK_FRAME_CLOCK_IDLE_H__
#define __GDK_FRAME_CLOCK_IDLE_H__
#include "gdkframeclockprivate.h"
G_BEGIN_DECLS
#define GDK_TYPE_FRAME_CLOCK_IDLE (gdk_frame_clock_idle_get_type ())
#define GDK_FRAME_CLOCK_IDLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_FRAME_CLOCK_IDLE, GdkFrameClockIdle))
#define GDK_FRAME_CLOCK_IDLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_FRAME_CLOCK_IDLE, GdkFrameClockIdleClass))
#define GDK_IS_FRAME_CLOCK_IDLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_FRAME_CLOCK_IDLE))
#define GDK_IS_FRAME_CLOCK_IDLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_FRAME_CLOCK_IDLE))
#define GDK_FRAME_CLOCK_IDLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_FRAME_CLOCK_IDLE, GdkFrameClockIdleClass))
typedef struct _GdkFrameClockIdle GdkFrameClockIdle;
typedef struct _GdkFrameClockIdlePrivate GdkFrameClockIdlePrivate;
typedef struct _GdkFrameClockIdleClass GdkFrameClockIdleClass;
struct _GdkFrameClockIdle
{
GdkFrameClock parent_instance;
/*< private >*/
GdkFrameClockIdlePrivate *priv;
};
struct _GdkFrameClockIdleClass
{
GdkFrameClockClass parent_class;
};
GType gdk_frame_clock_idle_get_type (void) G_GNUC_CONST;
void _gdk_frame_clock_idle_freeze_updates (GdkFrameClockIdle *clock_idle);
void _gdk_frame_clock_idle_thaw_updates (GdkFrameClockIdle *clock_idle);
G_END_DECLS
#endif /* __GDK_FRAME_CLOCK_IDLE_H__ */

101
gdk/gdkframeclockprivate.h Normal file
View File

@@ -0,0 +1,101 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the GTK+ Team and others 1997-2010. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
/* Uninstalled header, internal to GDK */
#ifndef __GDK_FRAME_CLOCK_PRIVATE_H__
#define __GDK_FRAME_CLOCK_PRIVATE_H__
#include <gdk/gdkframeclock.h>
G_BEGIN_DECLS
typedef struct _GdkFrameClockIdlePrivate GdkFrameClockIdlePrivate;
struct _GdkFrameClock
{
GObject parent_instance;
/*< private >*/
GdkFrameClockPrivate *priv;
};
struct _GdkFrameClockClass
{
GObjectClass parent_class;
gint64 (* get_frame_time) (GdkFrameClock *clock);
void (* request_phase) (GdkFrameClock *clock,
GdkFrameClockPhase phase);
void (* freeze) (GdkFrameClock *clock);
void (* thaw) (GdkFrameClock *clock);
/* signals */
/* 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); */
};
struct _GdkFrameTimings
{
guint ref_count;
gint64 frame_counter;
guint64 cookie;
gint64 frame_time;
gint64 drawn_time;
gint64 presentation_time;
gint64 refresh_interval;
gint64 predicted_presentation_time;
#ifdef G_ENABLE_DEBUG
gint64 layout_start_time;
gint64 paint_start_time;
gint64 frame_end_time;
#endif /* G_ENABLE_DEBUG */
guint complete : 1;
guint slept_before : 1;
};
void _gdk_frame_clock_freeze (GdkFrameClock *clock);
void _gdk_frame_clock_thaw (GdkFrameClock *clock);
void _gdk_frame_clock_begin_frame (GdkFrameClock *clock);
void _gdk_frame_clock_debug_print_timings (GdkFrameClock *clock,
GdkFrameTimings *timings);
GdkFrameTimings *_gdk_frame_timings_new (gint64 frame_counter);
G_END_DECLS
#endif /* __GDK_FRAME_CLOCK_PRIVATE_H__ */

219
gdk/gdkframetimings.c Normal file
View File

@@ -0,0 +1,219 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 2012 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gdkframeclockprivate.h"
/**
* SECTION:frametimings
* @Short_description: Object holding timing information for a single frame
* @Title: Frame timings
*
* A #GdkFrameTimings object holds timing information for a single frame
* of the application's displays. To retrieve #GdkFrameTimings objects,
* use gdk_frame_clock_get_timings() or gdk_frame_clock_get_current_timings().
* The information in #GdkFrameTimings is useful for precise synchronization
* of video with the event or audio streams, and for measuring
* quality metrics for the application's display, such as latency and jitter.
*/
G_DEFINE_BOXED_TYPE (GdkFrameTimings, gdk_frame_timings,
gdk_frame_timings_ref,
gdk_frame_timings_unref)
GdkFrameTimings *
_gdk_frame_timings_new (gint64 frame_counter)
{
GdkFrameTimings *timings;
timings = g_slice_new0 (GdkFrameTimings);
timings->ref_count = 1;
timings->frame_counter = frame_counter;
return timings;
}
/**
* gdk_frame_timings_ref:
* @timings: a #GdkFrameTimings
*
* Increases the reference count of @timings.
*
* Returns: @timings
* Since: 3.8
*/
GdkFrameTimings *
gdk_frame_timings_ref (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, NULL);
timings->ref_count++;
return timings;
}
/**
* gdk_frame_timings_unref:
* @timings: a #GdkFrameTimings
*
* Decreases the reference count of @timings. If @timings
* is no longer referenced, it will be freed.
*
* Since: 3.8
*/
void
gdk_frame_timings_unref (GdkFrameTimings *timings)
{
g_return_if_fail (timings != NULL);
g_return_if_fail (timings->ref_count > 0);
timings->ref_count--;
if (timings->ref_count == 0)
{
g_slice_free (GdkFrameTimings, timings);
}
}
/**
* gdk_frame_timings_get_frame_counter:
* @timings: a #GdkFrameTimings
*
* Gets the frame counter value of the #GdkFrameClock when this
* this frame was drawn.
*
* Returns: the frame counter value for this frame
* Since: 3.8
*/
gint64
gdk_frame_timings_get_frame_counter (GdkFrameTimings *timings)
{
return timings->frame_counter;
}
/**
* gdk_frame_timings_complete:
* @timings: a #GdkFrameTimings
*
* The timing information in a #GdkFrameTimings is filled in
* incrementally as the frame as drawn and passed off to the
* window system for processing and display to the user. The
* accessor functions for #GdkFrameTimings can return 0 to
* indicate an unavailable value for two reasons: either because
* the information is not yet available, or because it isn't
* available at all. Once gdk_frame_timings_complete() returns
* %TRUE for a frame, you can be certain that no further values
* will become available and be stored in the #GdkFrameTimings.
*
* Returns: %TRUE if all information that will be available
* for the frame has been filled in.
* Since: 3.8
*/
gboolean
gdk_frame_timings_get_complete (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, FALSE);
return timings->complete;
}
/**
* gdk_frame_timings_get_frame_time:
* @timings: A #GdkFrameTimings
*
* Returns the frame time for the frame. This is the time value
* that is typically used to time animations for the frame. See
* gdk_frame_clock_get_frame_time().
*
* Returns: the frame time for the frame, in the timescale
* of g_get_monotonic_time()
*/
gint64
gdk_frame_timings_get_frame_time (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, 0);
return timings->frame_time;
}
/**
* gdk_frame_timings_get_presentation_time:
* @timings: a #GdkFrameTimings
*
* Reurns the presentation time. This is the time at which the frame
* became visible to the user.
*
* Returns: the time the frame was displayed to the user, in the
* timescale of g_get_monotonic_time(), or 0 if no presentation
* time is available. See gdk_frame_timings_get_complete()
* Since: 3.8
*/
gint64
gdk_frame_timings_get_presentation_time (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, 0);
return timings->presentation_time;
}
/**
* gdk_frame_timings_get_predicted_presentation_time:
* @timings: a #GdkFrameTimings
*
* Gets the predicted time at which this frame will be displayed. Although
* no predicted time may be available, if one is available, it will
* be available while the frame is being generated, in contrast to
* gdk_frame_timings_get_presentation_time(), which is only available
* after the frame has been presented. In general, if you are simply
* animating, you should use gdk_frame_clock_get_frame_time() rather
* than this function, but this function is useful for applications
* that want exact control over latency. For example, a movie player
* may want this information for Audio/Video synchronization.
*
* Returns: The predicted time at which the frame will be presented,
* in the timescale of g_get_monotonic_time(), or 0 if no predicted
* presentation time is available.
* Since: 3.8
*/
gint64
gdk_frame_timings_get_predicted_presentation_time (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, 0);
return timings->predicted_presentation_time;
}
/**
* gdk_frame_timings_get_refresh_interval:
* @timings: a #GdkFrameTimings
*
* Gets the natural interval between presentation times for
* the display that this frame was displayed on. Frame presentation
* usually happens during the "vertical blanking interval".
*
* Returns: the refresh interval of the display, in microseconds,
* or 0 if the refresh interval is not available.
* See gdk_frame_timings_get_complete().
* Since: 3.8
*/
gint64
gdk_frame_timings_get_refresh_interval (GdkFrameTimings *timings)
{
g_return_val_if_fail (timings != NULL, 0);
return timings->refresh_interval;
}

56
gdk/gdkframetimings.h Normal file
View File

@@ -0,0 +1,56 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 2012 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#if !defined (__GDK_H_INSIDE__) && !defined (GDK_COMPILATION)
#error "Only <gdk/gdk.h> can be included directly."
#endif
#ifndef __GDK_FRAME_TIMINGS_H__
#define __GDK_FRAME_TIMINGS_H__
#include <glib-object.h>
#include <gdk/gdkversionmacros.h>
G_BEGIN_DECLS
typedef struct _GdkFrameTimings GdkFrameTimings;
GDK_AVAILABLE_IN_3_8
GType gdk_frame_timings_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_3_8
GdkFrameTimings *gdk_frame_timings_ref (GdkFrameTimings *timings);
GDK_AVAILABLE_IN_3_8
void gdk_frame_timings_unref (GdkFrameTimings *timings);
GDK_AVAILABLE_IN_3_8
gint64 gdk_frame_timings_get_frame_counter (GdkFrameTimings *timings);
GDK_AVAILABLE_IN_3_8
gboolean gdk_frame_timings_get_complete (GdkFrameTimings *timings);
GDK_AVAILABLE_IN_3_8
gint64 gdk_frame_timings_get_frame_time (GdkFrameTimings *timings);
GDK_AVAILABLE_IN_3_8
gint64 gdk_frame_timings_get_presentation_time (GdkFrameTimings *timings);
GDK_AVAILABLE_IN_3_8
gint64 gdk_frame_timings_get_refresh_interval (GdkFrameTimings *timings);
GDK_AVAILABLE_IN_3_8
gint64 gdk_frame_timings_get_predicted_presentation_time (GdkFrameTimings *timings);
G_END_DECLS
#endif /* __GDK_FRAME_TIMINGS_H__ */

View File

@@ -82,7 +82,8 @@ typedef enum {
GDK_DEBUG_MULTIHEAD = 1 << 7,
GDK_DEBUG_XINERAMA = 1 << 8,
GDK_DEBUG_DRAW = 1 << 9,
GDK_DEBUG_EVENTLOOP = 1 << 10
GDK_DEBUG_EVENTLOOP = 1 << 10,
GDK_DEBUG_FRAMES = 1 << 11
} GdkDebugFlag;
typedef enum {
@@ -265,6 +266,8 @@ struct _GdkWindow
gulong device_changed_handler_id;
guint num_offscreen_children;
GdkFrameClock *frame_clock; /* NULL to use from parent or default */
};
#define GDK_WINDOW_TYPE(d) (((GDK_WINDOW (d)))->window_type)
@@ -298,6 +301,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);
@@ -423,7 +429,6 @@ cairo_surface_t * _gdk_offscreen_window_create_surface (GdkWindow *window,
gint width,
gint height);
G_END_DECLS
#endif /* __GDK_INTERNALS_H__ */

View File

@@ -37,6 +37,7 @@
#include "gdkdeviceprivate.h"
#include "gdkvisualprivate.h"
#include "gdkmarshalers.h"
#include "gdkframeclockidle.h"
#include "gdkwindowimpl.h"
#include <math.h>
@@ -238,6 +239,9 @@ static void gdk_window_invalidate_rect_full (GdkWindow *window,
static void _gdk_window_propagate_has_alpha_background (GdkWindow *window);
static cairo_surface_t *gdk_window_ref_impl_surface (GdkWindow *window);
static void gdk_window_set_frame_clock (GdkWindow *window,
GdkFrameClock *clock);
static guint signals[LAST_SIGNAL] = { 0 };
static gpointer parent_class = NULL;
@@ -1465,6 +1469,12 @@ gdk_window_new (GdkWindow *parent,
if (window->parent)
window->parent->children = g_list_prepend (window->parent->children, window);
if (window->parent->window_type == GDK_WINDOW_ROOT)
{
GdkFrameClock *frame_clock = g_object_new (GDK_TYPE_FRAME_CLOCK_IDLE, NULL);
gdk_window_set_frame_clock (window, frame_clock);
}
native = FALSE;
if (window->parent->window_type == GDK_WINDOW_ROOT)
native = TRUE; /* Always use native windows for toplevels */
@@ -1715,6 +1725,27 @@ gdk_window_reparent (GdkWindow *window,
}
}
/* If we changed the window type, we might have to set or
* unset the frame clock on the window
*/
if (GDK_WINDOW_TYPE (new_parent) == GDK_WINDOW_ROOT &&
GDK_WINDOW_TYPE (window) != GDK_WINDOW_FOREIGN)
{
if (window->frame_clock == NULL)
{
GdkFrameClock *frame_clock = g_object_new (GDK_TYPE_FRAME_CLOCK_IDLE, NULL);
gdk_window_set_frame_clock (window, frame_clock);
}
}
else
{
if (window->frame_clock != NULL)
{
g_object_run_dispose (G_OBJECT (window->frame_clock));
gdk_window_set_frame_clock (window, NULL);
}
}
/* We might have changed window type for a native windows, so we
need to change the event mask too. */
if (gdk_window_has_impl (window))
@@ -2034,6 +2065,12 @@ _gdk_window_destroy_hierarchy (GdkWindow *window,
}
}
if (window->frame_clock)
{
g_object_run_dispose (G_OBJECT (window->frame_clock));
gdk_window_set_frame_clock (window, NULL);
}
gdk_window_free_paint_stack (window);
if (window->background)
@@ -3778,7 +3815,6 @@ gdk_cairo_create (GdkWindow *window)
/* Code for dirty-region queueing
*/
static GSList *update_windows = NULL;
static guint update_idle = 0;
static gboolean debug_updates = FALSE;
static inline gboolean
@@ -3877,14 +3913,6 @@ gdk_window_remove_update_window (GdkWindow *window)
update_windows = g_slist_remove (update_windows, window);
}
static gboolean
gdk_window_update_idle (gpointer data)
{
gdk_window_process_all_updates ();
return FALSE;
}
static gboolean
gdk_window_is_toplevel_frozen (GdkWindow *window)
{
@@ -3898,16 +3926,20 @@ gdk_window_is_toplevel_frozen (GdkWindow *window)
static void
gdk_window_schedule_update (GdkWindow *window)
{
GdkFrameClock *frame_clock;
if (window &&
(window->update_freeze_count ||
gdk_window_is_toplevel_frozen (window)))
return;
if (!update_idle)
update_idle =
gdk_threads_add_idle_full (GDK_PRIORITY_REDRAW,
gdk_window_update_idle,
NULL, NULL);
/* If there's no frame clock (a foreign window), then the invalid
* region will just stick around unless gdk_window_process_updates()
* is called. */
frame_clock = gdk_window_get_frame_clock (window);
if (frame_clock)
gdk_frame_clock_request_phase (gdk_window_get_frame_clock (window),
GDK_FRAME_CLOCK_PHASE_PAINT);
}
void
@@ -4250,18 +4282,13 @@ gdk_window_process_all_updates (void)
/* We can't do this now since that would recurse, so
delay it until after the recursion is done. */
got_recursive_update = TRUE;
update_idle = 0;
return;
}
in_process_all_updates = TRUE;
got_recursive_update = FALSE;
if (update_idle)
g_source_remove (update_idle);
update_windows = NULL;
update_idle = 0;
before_process_all_updates ();
@@ -4296,31 +4323,20 @@ gdk_window_process_all_updates (void)
redraw now so that it eventually happens,
otherwise we could miss an update if nothing
else schedules an update. */
if (got_recursive_update && !update_idle)
update_idle =
gdk_threads_add_idle_full (GDK_PRIORITY_REDRAW,
gdk_window_update_idle,
NULL, NULL);
if (got_recursive_update)
gdk_window_schedule_update (NULL);
}
/**
* gdk_window_process_updates:
* @window: a #GdkWindow
* @update_children: whether to also process updates for child windows
*
* Sends one or more expose events to @window. The areas in each
* expose event will cover the entire update area for the window (see
* gdk_window_invalidate_region() for details). Normally GDK calls
* gdk_window_process_all_updates() on your behalf, so there's no
* need to call this function unless you want to force expose events
* to be delivered immediately and synchronously (vs. the usual
* case, where GDK delivers them in an idle handler). Occasionally
* this is useful to produce nicer scrolling behavior, for example.
*
**/
void
gdk_window_process_updates (GdkWindow *window,
gboolean update_children)
enum {
PROCESS_UPDATES_NO_RECURSE,
PROCESS_UPDATES_WITH_ALL_CHILDREN,
PROCESS_UPDATES_WITH_SAME_CLOCK_CHILDREN
};
static void
gdk_window_process_updates_with_mode (GdkWindow *window,
int recurse_mode)
{
GdkWindow *impl_window;
@@ -4346,7 +4362,7 @@ gdk_window_process_updates (GdkWindow *window,
gdk_window_remove_update_window ((GdkWindow *)impl_window);
}
if (update_children)
if (recurse_mode != PROCESS_UPDATES_NO_RECURSE)
{
/* process updates in reverse stacking order so composition or
* painting over achieves the desired effect for offscreen windows
@@ -4358,8 +4374,14 @@ gdk_window_process_updates (GdkWindow *window,
for (node = g_list_last (children); node; node = node->prev)
{
gdk_window_process_updates (node->data, TRUE);
g_object_unref (node->data);
GdkWindow *child = node->data;
if (recurse_mode == PROCESS_UPDATES_WITH_ALL_CHILDREN ||
(recurse_mode == PROCESS_UPDATES_WITH_SAME_CLOCK_CHILDREN &&
child->frame_clock == NULL))
{
gdk_window_process_updates (child, TRUE);
}
g_object_unref (child);
}
g_list_free (children);
@@ -4368,6 +4390,33 @@ gdk_window_process_updates (GdkWindow *window,
g_object_unref (window);
}
/**
* gdk_window_process_updates:
* @window: a #GdkWindow
* @update_children: whether to also process updates for child windows
*
* Sends one or more expose events to @window. The areas in each
* expose event will cover the entire update area for the window (see
* gdk_window_invalidate_region() for details). Normally GDK calls
* gdk_window_process_all_updates() on your behalf, so there's no
* need to call this function unless you want to force expose events
* to be delivered immediately and synchronously (vs. the usual
* case, where GDK delivers them in an idle handler). Occasionally
* this is useful to produce nicer scrolling behavior, for example.
*
**/
void
gdk_window_process_updates (GdkWindow *window,
gboolean update_children)
{
g_return_if_fail (GDK_IS_WINDOW (window));
return gdk_window_process_updates_with_mode (window,
update_children ?
PROCESS_UPDATES_WITH_ALL_CHILDREN :
PROCESS_UPDATES_NO_RECURSE);
}
static void
gdk_window_invalidate_rect_full (GdkWindow *window,
const GdkRectangle *rect,
@@ -4854,6 +4903,7 @@ gdk_window_freeze_toplevel_updates_libgtk_only (GdkWindow *window)
g_return_if_fail (window->window_type != GDK_WINDOW_CHILD);
window->update_and_descendants_freeze_count++;
_gdk_frame_clock_freeze (gdk_window_get_frame_clock (window));
}
/**
@@ -4874,6 +4924,7 @@ gdk_window_thaw_toplevel_updates_libgtk_only (GdkWindow *window)
g_return_if_fail (window->update_and_descendants_freeze_count > 0);
window->update_and_descendants_freeze_count--;
_gdk_frame_clock_thaw (gdk_window_get_frame_clock (window));
gdk_window_schedule_update (window);
}
@@ -9971,7 +10022,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;
@@ -10014,7 +10065,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 &&
@@ -10029,13 +10080,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);
@@ -10128,7 +10179,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))
@@ -10170,6 +10220,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);
}
/**
@@ -11142,44 +11199,28 @@ gdk_window_begin_move_drag (GdkWindow *window,
* gdk_window_enable_synchronized_configure:
* @window: a toplevel #GdkWindow
*
* Indicates that the application will cooperate with the window
* system in synchronizing the window repaint with the window
* manager during resizing operations. After an application calls
* this function, it must call gdk_window_configure_finished() every
* time it has finished all processing associated with a set of
* Configure events. Toplevel GTK+ windows automatically use this
* protocol.
*
* On X, calling this function makes @window participate in the
* _NET_WM_SYNC_REQUEST window manager protocol.
* Does nothing, present only for compatiblity.
*
* Since: 2.6
* Deprecated: 3.8: this function is no longer needed
**/
void
gdk_window_enable_synchronized_configure (GdkWindow *window)
{
GDK_WINDOW_IMPL_GET_CLASS (window->impl)->enable_synchronized_configure (window);
}
/**
* gdk_window_configure_finished:
* @window: a toplevel #GdkWindow
*
* Signal to the window system that the application has finished
* handling Configure events it has received. Window Managers can
* use this to better synchronize the frame repaint with the
* application. GTK+ applications will automatically call this
* function when appropriate.
*
* This function can only be called if gdk_window_enable_synchronized_configure()
* was called previously.
* Does nothing, present only for compatiblity.
*
* Since: 2.6
* Deprecated: 3.8: this function is no longer needed
**/
void
gdk_window_configure_finished (GdkWindow *window)
{
GDK_WINDOW_IMPL_GET_CLASS (window->impl)->configure_finished (window);
}
/**
@@ -11535,3 +11576,113 @@ 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_pause_events (display);
gdk_frame_clock_request_phase (clock, GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS);
}
static void
gdk_window_paint_on_clock (GdkFrameClock *clock,
void *data)
{
GdkWindow *window;
window = GDK_WINDOW (data);
/* Update window and any children on the same 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_unpause_events (display);
}
static void
gdk_window_set_frame_clock (GdkWindow *window,
GdkFrameClock *clock)
{
g_return_if_fail (GDK_IS_WINDOW (window));
g_return_if_fail (clock == NULL || GDK_IS_FRAME_CLOCK (clock));
g_return_if_fail (clock == NULL || gdk_window_is_toplevel (window));
if (clock == window->frame_clock)
return;
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);
}
window->frame_clock = clock;
}
/**
* gdk_window_get_frame_clock:
* @window: window to get frame clock for
*
* Gets the frame clock for the window. The frame clock for a window
* never changes unless the window is reparented to a new toplevel
* window.
*
* Since: 3.8
* Return value: (transfer none): the frame clock
*/
GdkFrameClock*
gdk_window_get_frame_clock (GdkWindow *window)
{
GdkWindow *toplevel;
g_return_val_if_fail (GDK_IS_WINDOW (window), NULL);
toplevel = gdk_window_get_toplevel (window);
return toplevel->frame_clock;
}

View File

@@ -32,6 +32,7 @@
#include <gdk/gdkversionmacros.h>
#include <gdk/gdktypes.h>
#include <gdk/gdkevents.h>
#include <gdk/gdkframeclock.h>
G_BEGIN_DECLS
@@ -883,7 +884,9 @@ void gdk_window_constrain_size (GdkGeometry *geometry,
gint *new_width,
gint *new_height);
GDK_DEPRECATED_IN_3_8
void gdk_window_enable_synchronized_configure (GdkWindow *window);
GDK_DEPRECATED_IN_3_8
void gdk_window_configure_finished (GdkWindow *window);
GdkWindow *gdk_get_default_root_window (void);
@@ -901,6 +904,10 @@ void gdk_window_set_support_multidevice (GdkWindow *window,
gboolean support_multidevice);
gboolean gdk_window_get_support_multidevice (GdkWindow *window);
/* Frame clock */
GDK_AVAILABLE_IN_3_8
GdkFrameClock* gdk_window_get_frame_clock (GdkWindow *window);
G_END_DECLS
#endif /* __GDK_WINDOW_H__ */

View File

@@ -3002,16 +3002,6 @@ gdk_quartz_window_set_group (GdkWindow *window,
/* FIXME: Implement */
}
static void
gdk_quartz_window_enable_synchronized_configure (GdkWindow *window)
{
}
static void
gdk_quartz_window_configure_finished (GdkWindow *window)
{
}
static void
gdk_quartz_window_destroy_notify (GdkWindow *window)
{
@@ -3129,8 +3119,6 @@ gdk_window_impl_quartz_class_init (GdkWindowImplQuartzClass *klass)
impl_class->set_functions = gdk_quartz_window_set_functions;
impl_class->begin_resize_drag = gdk_quartz_window_begin_resize_drag;
impl_class->begin_move_drag = gdk_quartz_window_begin_move_drag;
impl_class->enable_synchronized_configure = gdk_quartz_window_enable_synchronized_configure;
impl_class->configure_finished = gdk_quartz_window_configure_finished;
impl_class->set_opacity = gdk_quartz_window_set_opacity;
impl_class->destroy_notify = gdk_quartz_window_destroy_notify;
impl_class->register_dnd = _gdk_quartz_window_register_dnd;

View File

@@ -1468,21 +1468,6 @@ gdk_wayland_window_begin_move_drag (GdkWindow *window,
gdk_device_ungrab (device, timestamp);
}
static void
gdk_wayland_window_enable_synchronized_configure (GdkWindow *window)
{
}
static void
gdk_wayland_window_configure_finished (GdkWindow *window)
{
if (!WINDOW_IS_TOPLEVEL (window))
return;
if (!GDK_IS_WINDOW_IMPL_WAYLAND (window->impl))
return;
}
static void
gdk_wayland_window_set_opacity (GdkWindow *window,
gdouble opacity)
@@ -1666,8 +1651,6 @@ _gdk_window_impl_wayland_class_init (GdkWindowImplWaylandClass *klass)
impl_class->set_functions = gdk_wayland_window_set_functions;
impl_class->begin_resize_drag = gdk_wayland_window_begin_resize_drag;
impl_class->begin_move_drag = gdk_wayland_window_begin_move_drag;
impl_class->enable_synchronized_configure = gdk_wayland_window_enable_synchronized_configure;
impl_class->configure_finished = gdk_wayland_window_configure_finished;
impl_class->set_opacity = gdk_wayland_window_set_opacity;
impl_class->set_composited = gdk_wayland_window_set_composited;
impl_class->destroy_notify = gdk_wayland_window_destroy_notify;

View File

@@ -3248,18 +3248,6 @@ gdk_win32_window_lookup_for_display (GdkDisplay *display,
return (GdkWindow*) gdk_win32_handle_table_lookup (anid);
}
static void
gdk_win32_window_enable_synchronized_configure (GdkWindow *window)
{
/* nothing - no window manager to cooperate with */
}
static void
gdk_win32_window_configure_finished (GdkWindow *window)
{
/* nothing - no window manager to cooperate with */
}
static void
gdk_win32_window_set_opacity (GdkWindow *window,
gdouble opacity)
@@ -3618,8 +3606,6 @@ gdk_window_impl_win32_class_init (GdkWindowImplWin32Class *klass)
impl_class->begin_resize_drag = gdk_win32_window_begin_resize_drag;
impl_class->begin_move_drag = gdk_win32_window_begin_move_drag;
impl_class->enable_synchronized_configure = gdk_win32_window_enable_synchronized_configure;
impl_class->configure_finished = gdk_win32_window_configure_finished;
impl_class->set_opacity = gdk_win32_window_set_opacity;
//impl_class->set_composited = gdk_win32_window_set_composited;
impl_class->destroy_notify = gdk_win32_window_destroy_notify;

View File

@@ -1569,22 +1569,25 @@ gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
case XI_FocusIn:
case XI_FocusOut:
{
XIEnterEvent *xev = (XIEnterEvent *) ev;
GdkDevice *device, *source_device;
if (window)
{
XIEnterEvent *xev = (XIEnterEvent *) ev;
GdkDevice *device, *source_device;
device = g_hash_table_lookup (device_manager->id_table,
GINT_TO_POINTER (xev->deviceid));
device = g_hash_table_lookup (device_manager->id_table,
GINT_TO_POINTER (xev->deviceid));
source_device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->sourceid));
source_device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->sourceid));
_gdk_device_manager_core_handle_focus (window,
xev->event,
device,
source_device,
(ev->evtype == XI_FocusIn) ? TRUE : FALSE,
xev->detail,
xev->mode);
_gdk_device_manager_core_handle_focus (window,
xev->event,
device,
source_device,
(ev->evtype == XI_FocusIn) ? TRUE : FALSE,
xev->detail,
xev->mode);
}
return_val = FALSE;
}

View File

@@ -26,6 +26,7 @@
#include "gdkdisplay.h"
#include "gdkeventsource.h"
#include "gdkeventtranslator.h"
#include "gdkframeclockprivate.h"
#include "gdkinternals.h"
#include "gdkscreen.h"
#include "gdkinternals.h"
@@ -382,15 +383,11 @@ gdk_check_wm_state_changed (GdkWindow *window)
do_net_wm_state_changes (window);
}
static GdkWindow *
get_event_window (GdkEventTranslator *translator,
XEvent *xevent)
static Window
get_event_xwindow (XEvent *xevent)
{
GdkDisplay *display;
Window xwindow;
display = (GdkDisplay *) translator;
switch (xevent->type)
{
case DestroyNotify:
@@ -405,11 +402,20 @@ get_event_window (GdkEventTranslator *translator,
case ConfigureNotify:
xwindow = xevent->xconfigure.window;
break;
case ReparentNotify:
xwindow = xevent->xreparent.window;
break;
case GravityNotify:
xwindow = xevent->xgravity.window;
break;
case CirculateNotify:
xwindow = xevent->xcirculate.window;
break;
default:
xwindow = xevent->xany.window;
}
return gdk_x11_window_lookup_for_display (display, xwindow);
return xwindow;
}
static gboolean
@@ -418,7 +424,9 @@ gdk_x11_display_translate_event (GdkEventTranslator *translator,
GdkEvent *event,
XEvent *xevent)
{
Window xwindow;
GdkWindow *window;
gboolean is_substructure;
GdkWindowImplX11 *window_impl = NULL;
GdkScreen *screen = NULL;
GdkX11Screen *x11_screen = NULL;
@@ -426,12 +434,20 @@ gdk_x11_display_translate_event (GdkEventTranslator *translator,
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
gboolean return_val;
/* Find the GdkWindow that this event relates to.
* Basically this means substructure events
* are reported same as structure events
/* Find the GdkWindow that this event relates to. If that's
* not the same as the window that the event was sent to,
* we are getting an event from SubstructureNotifyMask.
* We ignore such events for internal operation, but we
* need to report them to the application because of
* GDK_SUBSTRUCTURE_MASK (which should be removed at next
* opportunity.) The most likely reason for getting these
* events is when we are used in the Metacity or Mutter
* window managers.
*/
window = get_event_window (translator, xevent);
xwindow = get_event_xwindow (xevent);
is_substructure = xwindow != xevent->xany.window;
window = gdk_x11_window_lookup_for_display (display, xwindow);
if (window)
{
/* We may receive events such as NoExpose/GraphicsExpose
@@ -460,7 +476,7 @@ gdk_x11_display_translate_event (GdkEventTranslator *translator,
}
}
if (xevent->type == DestroyNotify)
if (xevent->type == DestroyNotify && !is_substructure)
{
int i, n;
@@ -622,8 +638,7 @@ gdk_x11_display_translate_event (GdkEventTranslator *translator,
g_message ("destroy notify:\twindow: %ld",
xevent->xdestroywindow.window));
/* Ignore DestroyNotify from SubstructureNotifyMask */
if (xevent->xdestroywindow.window == xevent->xdestroywindow.event)
if (!is_substructure)
{
event->any.type = GDK_DESTROY;
event->any.window = window;
@@ -646,27 +661,39 @@ gdk_x11_display_translate_event (GdkEventTranslator *translator,
event->any.type = GDK_UNMAP;
event->any.window = window;
/* If the WM supports the _NET_WM_STATE_HIDDEN hint, we do not want to
* interpret UnmapNotify events as implying iconic state.
* http://bugzilla.gnome.org/show_bug.cgi?id=590726.
*/
if (screen &&
!gdk_x11_screen_supports_net_wm_hint (screen,
gdk_atom_intern_static_string ("_NET_WM_STATE_HIDDEN")))
{
/* If we are shown (not withdrawn) and get an unmap, it means we were
* iconified in the X sense. If we are withdrawn, and get an unmap, it
* means we hid the window ourselves, so we will have already flipped
* the iconified bit off.
if (window && !is_substructure)
{
/* If the WM supports the _NET_WM_STATE_HIDDEN hint, we do not want to
* interpret UnmapNotify events as implying iconic state.
* http://bugzilla.gnome.org/show_bug.cgi?id=590726.
*/
if (window && GDK_WINDOW_IS_MAPPED (window))
gdk_synthesize_window_state (window,
0,
GDK_WINDOW_STATE_ICONIFIED);
}
if (screen &&
!gdk_x11_screen_supports_net_wm_hint (screen,
gdk_atom_intern_static_string ("_NET_WM_STATE_HIDDEN")))
{
/* If we are shown (not withdrawn) and get an unmap, it means we were
* iconified in the X sense. If we are withdrawn, and get an unmap, it
* means we hid the window ourselves, so we will have already flipped
* the iconified bit off.
*/
if (GDK_WINDOW_IS_MAPPED (window))
gdk_synthesize_window_state (window,
0,
GDK_WINDOW_STATE_ICONIFIED);
}
if (window)
_gdk_x11_window_grab_check_unmap (window, xevent->xany.serial);
if (window_impl->toplevel &&
window_impl->toplevel->frame_pending)
{
window_impl->toplevel->frame_pending = FALSE;
_gdk_frame_clock_thaw (gdk_window_get_frame_clock (event->any.window));
}
if (toplevel)
gdk_window_freeze_toplevel_updates_libgtk_only (window);
_gdk_x11_window_grab_check_unmap (window, xevent->xany.serial);
}
break;
@@ -678,11 +705,17 @@ gdk_x11_display_translate_event (GdkEventTranslator *translator,
event->any.type = GDK_MAP;
event->any.window = window;
/* Unset iconified if it was set */
if (window && (window->state & GDK_WINDOW_STATE_ICONIFIED))
gdk_synthesize_window_state (window,
GDK_WINDOW_STATE_ICONIFIED,
0);
if (window && !is_substructure)
{
/* Unset iconified if it was set */
if (window->state & GDK_WINDOW_STATE_ICONIFIED)
gdk_synthesize_window_state (window,
GDK_WINDOW_STATE_ICONIFIED,
0);
if (toplevel)
gdk_window_thaw_toplevel_updates_libgtk_only (window);
}
break;
@@ -728,10 +761,11 @@ gdk_x11_display_translate_event (GdkEventTranslator *translator,
}
#ifdef HAVE_XSYNC
if (toplevel && display_x11->use_sync && !XSyncValueIsZero (toplevel->pending_counter_value))
if (!is_substructure && toplevel && display_x11->use_sync && toplevel->pending_counter_value != 0)
{
toplevel->current_counter_value = toplevel->pending_counter_value;
XSyncIntToValue (&toplevel->pending_counter_value, 0);
toplevel->configure_counter_value = toplevel->pending_counter_value;
toplevel->configure_counter_value_is_extended = toplevel->pending_counter_value_is_extended;
toplevel->pending_counter_value = 0;
}
#endif
@@ -773,21 +807,24 @@ gdk_x11_display_translate_event (GdkEventTranslator *translator,
event->configure.x = xevent->xconfigure.x;
event->configure.y = xevent->xconfigure.y;
}
window->x = event->configure.x;
window->y = event->configure.y;
window->width = xevent->xconfigure.width;
window->height = xevent->xconfigure.height;
if (!is_substructure)
{
window->x = event->configure.x;
window->y = event->configure.y;
window->width = xevent->xconfigure.width;
window->height = xevent->xconfigure.height;
_gdk_window_update_size (window);
_gdk_x11_window_update_size (GDK_WINDOW_IMPL_X11 (window->impl));
_gdk_window_update_size (window);
_gdk_x11_window_update_size (GDK_WINDOW_IMPL_X11 (window->impl));
if (window->resize_count >= 1)
{
window->resize_count -= 1;
if (window->resize_count >= 1)
{
window->resize_count -= 1;
if (window->resize_count == 0)
_gdk_x11_moveresize_configure_done (display, window);
}
if (window->resize_count == 0)
_gdk_x11_moveresize_configure_done (display, window);
}
}
}
break;
@@ -1021,6 +1058,25 @@ gdk_x11_display_translate_event (GdkEventTranslator *translator,
return return_val;
}
static GdkFrameTimings *
find_frame_timings (GdkFrameClock *clock,
guint64 serial)
{
gint64 start_frame, end_frame, i;
start_frame = gdk_frame_clock_get_history_start (clock);
end_frame = gdk_frame_clock_get_frame_counter (clock);
for (i = end_frame; i >= start_frame; i--)
{
GdkFrameTimings *timings = gdk_frame_clock_get_timings (clock, i);
if (timings->cookie == serial)
return timings;
}
return NULL;
}
GdkFilterReturn
_gdk_wm_protocols_filter (GdkXEvent *xev,
GdkEvent *event,
@@ -1031,7 +1087,7 @@ _gdk_wm_protocols_filter (GdkXEvent *xev,
GdkDisplay *display;
Atom atom;
if (!GDK_IS_X11_WINDOW (win))
if (!GDK_IS_X11_WINDOW (win) || GDK_WINDOW_DESTROYED (win))
return GDK_FILTER_CONTINUE;
if (xevent->type != ClientMessage)
@@ -1039,6 +1095,82 @@ _gdk_wm_protocols_filter (GdkXEvent *xev,
display = GDK_WINDOW_DISPLAY (win);
/* This isn't actually WM_PROTOCOLS because that wouldn't leave enough space
* in the message for everything that gets stuffed in */
if (xevent->xclient.message_type == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_FRAME_DRAWN"))
{
GdkWindowImplX11 *window_impl;
window_impl = GDK_WINDOW_IMPL_X11 (win->impl);
if (window_impl->toplevel)
{
guint32 d0 = xevent->xclient.data.l[0];
guint32 d1 = xevent->xclient.data.l[1];
guint32 d2 = xevent->xclient.data.l[2];
guint32 d3 = xevent->xclient.data.l[3];
guint64 serial = ((guint64)d1 << 32) | d0;
gint64 frame_drawn_time = ((guint64)d3 << 32) | d2;
gint64 refresh_interval, presentation_time;
GdkFrameClock *clock = gdk_window_get_frame_clock (win);
GdkFrameTimings *timings = find_frame_timings (clock, serial);
if (timings)
timings->drawn_time = frame_drawn_time;
if (window_impl->toplevel->frame_pending)
{
window_impl->toplevel->frame_pending = FALSE;
_gdk_frame_clock_thaw (clock);
}
gdk_frame_clock_get_refresh_info (clock,
frame_drawn_time,
&refresh_interval,
&presentation_time);
if (presentation_time != 0)
window_impl->toplevel->throttled_presentation_time = presentation_time + refresh_interval;
}
return GDK_FILTER_REMOVE;
}
if (xevent->xclient.message_type == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_FRAME_TIMINGS"))
{
GdkWindowImplX11 *window_impl;
window_impl = GDK_WINDOW_IMPL_X11 (win->impl);
if (window_impl->toplevel)
{
guint32 d0 = xevent->xclient.data.l[0];
guint32 d1 = xevent->xclient.data.l[1];
guint32 d2 = xevent->xclient.data.l[2];
guint32 d3 = xevent->xclient.data.l[3];
guint64 serial = ((guint64)d1 << 32) | d0;
GdkFrameClock *clock = gdk_window_get_frame_clock (win);
GdkFrameTimings *timings = find_frame_timings (clock, serial);
if (timings)
{
gint32 presentation_time_offset = (gint32)d2;
gint32 refresh_interval = d3;
if (timings->drawn_time && presentation_time_offset)
timings->presentation_time = timings->drawn_time + presentation_time_offset;
if (refresh_interval)
timings->refresh_interval = refresh_interval;
timings->complete = TRUE;
#ifdef G_ENABLE_DEBUG
if ((_gdk_debug_flags & GDK_DEBUG_FRAMES) != 0)
_gdk_frame_clock_debug_print_timings (clock, timings);
#endif /* G_ENABLE_DEBUG */
}
}
}
if (xevent->xclient.message_type != gdk_x11_get_xatom_by_name_for_display (display, "WM_PROTOCOLS"))
return GDK_FILTER_CONTINUE;
@@ -1066,7 +1198,7 @@ _gdk_wm_protocols_filter (GdkXEvent *xev,
}
else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "WM_TAKE_FOCUS"))
{
GdkToplevelX11 *toplevel = _gdk_x11_window_get_toplevel (event->any.window);
GdkToplevelX11 *toplevel = _gdk_x11_window_get_toplevel (win);
/* There is no way of knowing reliably whether we are viewable;
* so trap errors asynchronously around the XSetInputFocus call
@@ -1100,13 +1232,12 @@ _gdk_wm_protocols_filter (GdkXEvent *xev,
else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_SYNC_REQUEST") &&
GDK_X11_DISPLAY (display)->use_sync)
{
GdkToplevelX11 *toplevel = _gdk_x11_window_get_toplevel (event->any.window);
GdkToplevelX11 *toplevel = _gdk_x11_window_get_toplevel (win);
if (toplevel)
{
#ifdef HAVE_XSYNC
XSyncIntsToValue (&toplevel->pending_counter_value,
xevent->xclient.data.l[2],
xevent->xclient.data.l[3]);
toplevel->pending_counter_value = xevent->xclient.data.l[2] + ((gint64)xevent->xclient.data.l[3] << 32);
toplevel->pending_counter_value_is_extended = xevent->xclient.data.l[4] != 0;
#endif
}
return GDK_FILTER_REMOVE;

View File

@@ -276,8 +276,12 @@ gdk_event_source_prepare (GSource *source,
gdk_threads_enter ();
*timeout = -1;
retval = (_gdk_event_queue_find_first (display) != NULL ||
gdk_check_xpending (display));
if (display->event_pause_count > 0)
retval = FALSE;
else
retval = (_gdk_event_queue_find_first (display) != NULL ||
gdk_check_xpending (display));
gdk_threads_leave ();
@@ -292,7 +296,9 @@ gdk_event_source_check (GSource *source)
gdk_threads_enter ();
if (event_source->event_poll_fd.revents & G_IO_IN)
if (event_source->display->event_pause_count > 0)
retval = FALSE;
else if (event_source->event_poll_fd.revents & G_IO_IN)
retval = (_gdk_event_queue_find_first (event_source->display) != NULL ||
gdk_check_xpending (event_source->display));
else

View File

@@ -32,6 +32,7 @@
#include "gdkvisualprivate.h"
#include "gdkinternals.h"
#include "gdkdeviceprivate.h"
#include "gdkframeclockprivate.h"
#include "gdkasync.h"
#include "gdkeventsource.h"
#include "gdkdisplay-x11.h"
@@ -199,6 +200,234 @@ _gdk_x11_window_update_size (GdkWindowImplX11 *impl)
}
}
static void
set_sync_counter(Display *display,
XSyncCounter counter,
gint64 value)
{
XSyncValue sync_value;
XSyncIntsToValue(&sync_value,
value & G_GINT64_CONSTANT(0xFFFFFFFF),
value >> 32);
XSyncSetCounter(display, counter, sync_value);
}
static void
window_pre_damage (GdkWindow *window)
{
GdkWindow *toplevel_window = gdk_window_get_toplevel (window);
GdkWindowImplX11 *impl;
if (!toplevel_window || !WINDOW_IS_TOPLEVEL (toplevel_window))
return;
impl = GDK_WINDOW_IMPL_X11 (toplevel_window->impl);
if (impl->toplevel->in_frame &&
impl->toplevel->current_counter_value % 2 == 0)
{
impl->toplevel->current_counter_value += 1;
set_sync_counter(GDK_WINDOW_XDISPLAY (impl->wrapper),
impl->toplevel->extended_update_counter,
impl->toplevel->current_counter_value);
}
}
static void
on_surface_changed (void *data)
{
GdkWindow *window = data;
window_pre_damage (window);
}
/* We want to know when cairo drawing causes damage to the window,
* so we engage in the _NET_WM_FRAME_DRAWN protocol with the
* window only when there actually is drawing. To do that we use
* a technique (hack) suggested by Uli Schlachter - if we set
* a dummy "mime data" on the cairo surface (this facility is
* used to attach JPEG data to an imager), then cairo wil flush
* and remove the mime data before making any changes to the window.
*/
static void
hook_surface_changed (GdkWindow *window)
{
GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl);
if (impl->cairo_surface)
cairo_surface_set_mime_data (impl->cairo_surface,
"x-gdk/change-notify",
(unsigned char *)"X",
1,
on_surface_changed,
window);
}
static void
unhook_surface_changed (GdkWindow *window)
{
GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl);
if (impl->cairo_surface)
cairo_surface_set_mime_data (impl->cairo_surface,
"x-gdk/change-notify",
NULL, 0,
NULL, NULL);
}
static void
gdk_x11_window_predict_presentation_time (GdkWindow *window)
{
GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl);
GdkFrameClock *clock;
GdkFrameTimings *timings;
gint64 presentation_time;
gint64 refresh_interval;
if (!WINDOW_IS_TOPLEVEL (window))
return;
clock = gdk_window_get_frame_clock (window);
timings = gdk_frame_clock_get_current_timings (clock);
gdk_frame_clock_get_refresh_info (clock,
timings->frame_time,
&refresh_interval, &presentation_time);
if (presentation_time != 0)
{
if (timings->slept_before)
{
presentation_time += refresh_interval;
}
else
{
if (presentation_time < timings->frame_time + refresh_interval / 2)
presentation_time += refresh_interval;
}
}
else
{
if (timings->slept_before)
presentation_time = timings->frame_time + refresh_interval + refresh_interval / 2;
else
presentation_time = timings->frame_time + refresh_interval;
}
if (presentation_time < impl->toplevel->throttled_presentation_time)
presentation_time = impl->toplevel->throttled_presentation_time;
timings->predicted_presentation_time = presentation_time;
}
static void
gdk_x11_window_begin_frame (GdkWindow *window)
{
GdkWindowImplX11 *impl;
g_return_if_fail (GDK_IS_WINDOW (window));
impl = GDK_WINDOW_IMPL_X11 (window->impl);
if (!WINDOW_IS_TOPLEVEL (window) ||
impl->toplevel->extended_update_counter == None)
return;
impl->toplevel->in_frame = TRUE;
if (impl->toplevel->configure_counter_value != 0 &&
impl->toplevel->configure_counter_value_is_extended)
{
impl->toplevel->current_counter_value = impl->toplevel->configure_counter_value;
if ((impl->toplevel->current_counter_value % 2) == 1)
impl->toplevel->current_counter_value += 1;
impl->toplevel->configure_counter_value = 0;
window_pre_damage (window);
}
else
{
hook_surface_changed (window);
}
}
static void
gdk_x11_window_end_frame (GdkWindow *window)
{
GdkFrameClock *clock;
GdkFrameTimings *timings;
GdkWindowImplX11 *impl;
g_return_if_fail (GDK_IS_WINDOW (window));
impl = GDK_WINDOW_IMPL_X11 (window->impl);
if (!WINDOW_IS_TOPLEVEL (window) ||
impl->toplevel->extended_update_counter == None ||
!impl->toplevel->in_frame)
return;
clock = gdk_window_get_frame_clock (window);
timings = gdk_frame_clock_get_current_timings (clock);
impl->toplevel->in_frame = FALSE;
if (impl->toplevel->current_counter_value % 2 == 1)
{
#ifdef G_ENABLE_DEBUG
if ((_gdk_debug_flags & GDK_DEBUG_FRAMES) != 0)
{
XImage *image = XGetImage (GDK_WINDOW_XDISPLAY (window),
GDK_WINDOW_XID (window),
0, 0, 1, 1,
(1 << 24) - 1,
ZPixmap);
XDestroyImage (image);
}
#endif /* G_ENABLE_DEBUG */
/* An increment of 3 means that the frame was not drawn as fast as possible,
* but rather at a particular time. This can trigger different handling from
* the compositor.
*/
if (timings->slept_before)
impl->toplevel->current_counter_value += 3;
else
impl->toplevel->current_counter_value += 1;
set_sync_counter(GDK_WINDOW_XDISPLAY (impl->wrapper),
impl->toplevel->extended_update_counter,
impl->toplevel->current_counter_value);
if (gdk_x11_screen_supports_net_wm_hint (gdk_window_get_screen (window),
gdk_atom_intern_static_string ("_NET_WM_FRAME_DRAWN")))
{
impl->toplevel->frame_pending = TRUE;
_gdk_frame_clock_freeze (gdk_window_get_frame_clock (window));
timings->cookie = impl->toplevel->current_counter_value;
}
}
unhook_surface_changed (window);
if (impl->toplevel->configure_counter_value != 0 &&
!impl->toplevel->configure_counter_value_is_extended)
{
set_sync_counter (GDK_WINDOW_XDISPLAY (window),
impl->toplevel->update_counter,
impl->toplevel->configure_counter_value);
impl->toplevel->configure_counter_value = 0;
}
if (!impl->toplevel->frame_pending)
timings->complete = TRUE;
}
/*****************************************************
* X11 specific implementations of generic functions *
*****************************************************/
@@ -242,6 +471,9 @@ gdk_x11_ref_cairo_surface (GdkWindow *window)
if (impl->cairo_surface)
cairo_surface_set_user_data (impl->cairo_surface, &gdk_x11_cairo_key,
impl, gdk_x11_cairo_surface_destroy);
if (WINDOW_IS_TOPLEVEL (window) && impl->toplevel->in_frame)
hook_surface_changed (window);
}
else
cairo_surface_reference (impl->cairo_surface);
@@ -261,6 +493,9 @@ gdk_window_impl_x11_finalize (GObject *object)
wrapper = impl->wrapper;
if (WINDOW_IS_TOPLEVEL (wrapper) && impl->toplevel->in_frame)
unhook_surface_changed (wrapper);
_gdk_x11_window_grab_check_destroy (wrapper);
if (!GDK_WINDOW_DESTROYED (wrapper))
@@ -602,29 +837,32 @@ ensure_sync_counter (GdkWindow *window)
{
GdkDisplay *display = GDK_WINDOW_DISPLAY (window);
GdkToplevelX11 *toplevel = _gdk_x11_window_get_toplevel (window);
GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl);
if (toplevel && impl->use_synchronized_configure &&
if (toplevel &&
toplevel->update_counter == None &&
GDK_X11_DISPLAY (display)->use_sync)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
XSyncValue value;
Atom atom;
XID counters[2];
XSyncIntToValue (&value, 0);
toplevel->update_counter = XSyncCreateCounter (xdisplay, value);
toplevel->extended_update_counter = XSyncCreateCounter (xdisplay, value);
atom = gdk_x11_get_xatom_by_name_for_display (display,
"_NET_WM_SYNC_REQUEST_COUNTER");
counters[0] = toplevel->update_counter;
counters[1] = toplevel->extended_update_counter;
XChangeProperty (xdisplay, GDK_WINDOW_XID (window),
atom, XA_CARDINAL,
32, PropModeReplace,
(guchar *)&toplevel->update_counter, 1);
(guchar *)counters, 2);
XSyncIntToValue (&toplevel->current_counter_value, 0);
toplevel->current_counter_value = 0;
}
}
#endif
@@ -698,6 +936,44 @@ setup_toplevel_window (GdkWindow *window,
gdk_x11_window_set_user_time (window, GDK_X11_DISPLAY (x11_screen->display)->user_time);
ensure_sync_counter (window);
/* Start off in a frozen state - we'll finish this when we first paint */
gdk_x11_window_begin_frame (window);
}
static void
on_frame_clock_before_paint (GdkFrameClock *clock,
GdkWindow *window)
{
gdk_x11_window_predict_presentation_time (window);
gdk_x11_window_begin_frame (window);
}
static void
on_frame_clock_after_paint (GdkFrameClock *clock,
GdkWindow *window)
{
gdk_x11_window_end_frame (window);
}
static void
connect_frame_clock (GdkWindow *window)
{
GdkWindowImplX11 *impl;
impl = GDK_WINDOW_IMPL_X11 (window->impl);
if (WINDOW_IS_TOPLEVEL (window) && !impl->frame_clock_connected)
{
GdkFrameClock *frame_clock = gdk_window_get_frame_clock (window);
g_signal_connect (frame_clock, "before-paint",
G_CALLBACK (on_frame_clock_before_paint), window);
g_signal_connect (frame_clock, "after-paint",
G_CALLBACK (on_frame_clock_after_paint), window);
impl->frame_clock_connected = TRUE;
}
}
void
@@ -856,6 +1132,11 @@ _gdk_x11_display_create_window_impl (GdkDisplay *display,
gdk_x11_event_source_select_events ((GdkEventSource *) display_x11->event_source,
GDK_WINDOW_XID (window), event_mask,
StructureNotifyMask | PropertyChangeMask);
connect_frame_clock (window);
if (GDK_WINDOW_TYPE (window) != GDK_WINDOW_CHILD)
gdk_window_freeze_toplevel_updates_libgtk_only (window);
}
static GdkEventMask
@@ -1001,7 +1282,7 @@ gdk_toplevel_x11_free_contents (GdkDisplay *display,
toplevel->update_counter);
toplevel->update_counter = None;
XSyncIntToValue (&toplevel->current_counter_value, 0);
toplevel->current_counter_value = 0;
}
#endif
}
@@ -1473,6 +1754,8 @@ window_x11_move (GdkWindow *window,
if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_CHILD)
{
/* The window isn't actually damaged, but it's parent is */
window_pre_damage (window);
_gdk_x11_window_move_resize_child (window,
x, y,
window->width, window->height);
@@ -1502,6 +1785,8 @@ window_x11_resize (GdkWindow *window,
if (height < 1)
height = 1;
window_pre_damage (window);
if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_CHILD)
{
_gdk_x11_window_move_resize_child (window,
@@ -1545,6 +1830,8 @@ window_x11_move_resize (GdkWindow *window,
if (height < 1)
height = 1;
window_pre_damage (window);
if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_CHILD)
{
_gdk_x11_window_move_resize_child (window, x, y, width, height);
@@ -1614,6 +1901,12 @@ gdk_window_x11_reparent (GdkWindow *window,
_gdk_x11_window_tmp_reset_parent_bg (window);
_gdk_x11_window_tmp_reset_bg (window, TRUE);
if (WINDOW_IS_TOPLEVEL (window))
connect_frame_clock (window);
else
/* old frame clock was disposed, our signal handlers removed */
impl->frame_clock_connected = FALSE;
if (GDK_WINDOW_TYPE (new_parent) == GDK_WINDOW_FOREIGN)
new_parent = gdk_screen_get_root_window (GDK_WINDOW_SCREEN (window));
@@ -4817,59 +5110,6 @@ gdk_x11_window_begin_move_drag (GdkWindow *window,
emulate_move_drag (window, device, button, root_x, root_y, timestamp);
}
static void
gdk_x11_window_enable_synchronized_configure (GdkWindow *window)
{
GdkWindowImplX11 *impl;
if (!GDK_IS_WINDOW_IMPL_X11 (window->impl))
return;
impl = GDK_WINDOW_IMPL_X11 (window->impl);
if (!impl->use_synchronized_configure)
{
/* This basically means you want to do fancy X specific stuff, so
ensure we have a native window */
gdk_window_ensure_native (window);
impl->use_synchronized_configure = TRUE;
ensure_sync_counter (window);
}
}
static void
gdk_x11_window_configure_finished (GdkWindow *window)
{
GdkWindowImplX11 *impl;
if (!WINDOW_IS_TOPLEVEL (window))
return;
impl = GDK_WINDOW_IMPL_X11 (window->impl);
if (!impl->use_synchronized_configure)
return;
#ifdef HAVE_XSYNC
if (!GDK_WINDOW_DESTROYED (window))
{
GdkDisplay *display = GDK_WINDOW_DISPLAY (window);
GdkToplevelX11 *toplevel = _gdk_x11_window_get_toplevel (window);
if (toplevel && toplevel->update_counter != None &&
GDK_X11_DISPLAY (display)->use_sync &&
!XSyncValueIsZero (toplevel->current_counter_value))
{
XSyncSetCounter (GDK_WINDOW_XDISPLAY (window),
toplevel->update_counter,
toplevel->current_counter_value);
XSyncIntToValue (&toplevel->current_counter_value, 0);
}
}
#endif
}
static gboolean
gdk_x11_window_beep (GdkWindow *window)
{
@@ -5141,8 +5381,6 @@ gdk_window_impl_x11_class_init (GdkWindowImplX11Class *klass)
impl_class->set_functions = gdk_x11_window_set_functions;
impl_class->begin_resize_drag = gdk_x11_window_begin_resize_drag;
impl_class->begin_move_drag = gdk_x11_window_begin_move_drag;
impl_class->enable_synchronized_configure = gdk_x11_window_enable_synchronized_configure;
impl_class->configure_finished = gdk_x11_window_configure_finished;
impl_class->set_opacity = gdk_x11_window_set_opacity;
impl_class->set_composited = gdk_x11_window_set_composited;
impl_class->destroy_notify = gdk_x11_window_destroy_notify;

View File

@@ -72,7 +72,7 @@ struct _GdkWindowImplX11
guint no_bg : 1; /* Set when the window background is temporarily
* unset during resizing and scaling */
guint override_redirect : 1;
guint use_synchronized_configure : 1;
guint frame_clock_connected : 1;
cairo_surface_t *cairo_surface;
@@ -126,7 +126,17 @@ struct _GdkToplevelX11
/* Set if the WM is presenting us as focused, i.e. with active decorations
*/
guint have_focused : 1;
guint in_frame : 1;
/* If we're expecting a response from the compositor after painting a frame */
guint frame_pending : 1;
/* Whether pending_counter_value/configure_counter_value are updates
* to the extended update counter */
guint pending_counter_value_is_extended : 1;
guint configure_counter_value_is_extended : 1;
gulong map_serial; /* Serial of last transition from unmapped */
cairo_surface_t *icon_pixmap;
@@ -144,11 +154,17 @@ struct _GdkToplevelX11
#ifdef HAVE_XSYNC
XID update_counter;
XSyncValue pending_counter_value; /* latest _NET_WM_SYNC_REQUEST value received */
XSyncValue current_counter_value; /* Latest _NET_WM_SYNC_REQUEST value received
* where we have also seen the corresponding
* ConfigureNotify
*/
XID extended_update_counter;
gint64 pending_counter_value; /* latest _NET_WM_SYNC_REQUEST value received */
gint64 configure_counter_value; /* Latest _NET_WM_SYNC_REQUEST value received
* where we have also seen the corresponding
* ConfigureNotify
*/
gint64 current_counter_value;
/* After a _NET_WM_FRAME_DRAWN message, this is the soonest that we think
* frame after will be presented */
gint64 throttled_presentation_time;
#endif
};

View File

@@ -236,6 +236,9 @@ struct _GtkContainerPrivate
{
GtkWidget *focus_child;
guint resize_handler;
GdkFrameClock *resize_clock;
guint border_width : 16;
guint has_focus_chain : 1;
@@ -344,8 +347,6 @@ static const gchar vadjustment_key[] = "gtk-vadjustment";
static guint vadjustment_key_id = 0;
static const gchar hadjustment_key[] = "gtk-hadjustment";
static guint hadjustment_key_id = 0;
static GSList *container_resize_queue = NULL;
static GSList *container_restyle_queue = NULL;
static guint container_signals[LAST_SIGNAL] = { 0 };
static GtkWidgetClass *parent_class = NULL;
extern GParamSpecPool *_gtk_widget_child_property_pool;
@@ -1357,11 +1358,9 @@ gtk_container_destroy (GtkWidget *widget)
if (priv->resize_pending)
_gtk_container_dequeue_resize_handler (container);
if (priv->restyle_pending)
{
container_restyle_queue = g_slist_remove (container_restyle_queue, container);
priv->restyle_pending = FALSE;
}
priv->restyle_pending = FALSE;
if (priv->focus_child)
{
@@ -1553,7 +1552,6 @@ _gtk_container_dequeue_resize_handler (GtkContainer *container)
g_return_if_fail (GTK_IS_CONTAINER (container));
g_return_if_fail (container->priv->resize_pending);
container_resize_queue = g_slist_remove (container_resize_queue, container);
container->priv->resize_pending = FALSE;
}
@@ -1630,12 +1628,10 @@ gtk_container_set_reallocate_redraws (GtkContainer *container,
container->priv->reallocate_redraws = needs_redraws ? TRUE : FALSE;
}
static gboolean
gtk_container_idle_sizer (gpointer data)
static void
gtk_container_idle_sizer (GdkFrameClock *clock,
GtkContainer *container)
{
GSList *slist;
gint64 current_time;
/* We validate the style contexts in a single loop before even trying
* to handle resizes instead of doing validations inline.
* This is mostly necessary for compatibility reasons with old code,
@@ -1646,16 +1642,13 @@ gtk_container_idle_sizer (gpointer data)
* sane values. So the result of an invalid style context will never be
* a program crash, but only a wrong layout or rendering.
*/
current_time = g_get_monotonic_time ();
slist = container_restyle_queue;
container_restyle_queue = NULL;
while (slist)
if (container->priv->restyle_pending)
{
GSList *next = slist->next;
GtkContainer *container = slist->data;
GtkBitmask *empty;
gint64 current_time;
empty = _gtk_bitmask_new ();
current_time = g_get_monotonic_time ();
container->priv->restyle_pending = FALSE;
_gtk_style_context_validate (gtk_widget_get_style_context (GTK_WIDGET (container)),
@@ -1663,8 +1656,6 @@ gtk_container_idle_sizer (gpointer data)
0,
empty);
g_slist_free_1 (slist);
slist = next;
_gtk_bitmask_free (empty);
}
@@ -1674,35 +1665,52 @@ gtk_container_idle_sizer (gpointer data)
* than trying to explicitely work around them with some extra flags,
* since it doesn't cause any actual harm.
*/
while (container_resize_queue)
if (container->priv->resize_pending)
{
GtkContainer *container;
slist = container_resize_queue;
container_resize_queue = slist->next;
container = slist->data;
g_slist_free_1 (slist);
container->priv->resize_pending = FALSE;
gtk_container_check_resize (container);
}
gdk_window_process_all_updates ();
return container_resize_queue != NULL || container_restyle_queue != NULL;
if (!container->priv->restyle_pending && !container->priv->resize_pending)
{
_gtk_container_stop_idle_sizer (container);
}
else
{
gdk_frame_clock_request_phase (clock,
GDK_FRAME_CLOCK_PHASE_LAYOUT);
}
}
static void
gtk_container_start_idle_sizer (GtkContainer *container)
{
/* already started */
if (container_resize_queue != NULL ||
container_restyle_queue != NULL)
GdkFrameClock *clock;
if (container->priv->resize_handler != 0)
return;
gdk_threads_add_idle_full (GTK_PRIORITY_RESIZE,
gtk_container_idle_sizer,
NULL, NULL);
clock = gtk_widget_get_frame_clock (GTK_WIDGET (container));
if (clock == NULL)
return;
container->priv->resize_clock = clock;
container->priv->resize_handler = g_signal_connect (clock, "layout",
G_CALLBACK (gtk_container_idle_sizer), container);
gdk_frame_clock_request_phase (clock,
GDK_FRAME_CLOCK_PHASE_LAYOUT);
}
void
_gtk_container_stop_idle_sizer (GtkContainer *container)
{
if (container->priv->resize_handler == 0)
return;
g_signal_handler_disconnect (container->priv->resize_clock,
container->priv->resize_handler);
container->priv->resize_handler = 0;
container->priv->resize_clock = NULL;
}
static void
@@ -1725,7 +1733,6 @@ gtk_container_queue_resize_handler (GtkContainer *container)
{
container->priv->resize_pending = TRUE;
gtk_container_start_idle_sizer (container);
container_resize_queue = g_slist_prepend (container_resize_queue, container);
}
break;
@@ -1780,8 +1787,6 @@ _gtk_container_queue_restyle (GtkContainer *container)
return;
gtk_container_start_idle_sizer (container);
container_restyle_queue = g_slist_prepend (container_restyle_queue, container);
priv->restyle_pending = TRUE;
}
@@ -1815,6 +1820,13 @@ _gtk_container_resize_invalidate (GtkContainer *container)
_gtk_container_queue_resize_internal (container, TRUE);
}
void
_gtk_container_maybe_start_idle_sizer (GtkContainer *container)
{
if (container->priv->restyle_pending || container->priv->resize_pending)
gtk_container_start_idle_sizer (container);
}
void
gtk_container_check_resize (GtkContainer *container)
{

View File

@@ -39,6 +39,8 @@ GList * _gtk_container_focus_sort (GtkContainer *container,
GtkWidget *old_focus);
gboolean _gtk_container_get_reallocate_redraws (GtkContainer *container);
void _gtk_container_stop_idle_sizer (GtkContainer *container);
void _gtk_container_maybe_start_idle_sizer (GtkContainer *container);
G_END_DECLS

View File

@@ -126,7 +126,6 @@
#define TOUCH_BYPASS_CAPTURED_THRESHOLD 30
/* Kinetic scrolling */
#define FRAME_INTERVAL (1000 / 60)
#define MAX_OVERSHOOT_DISTANCE 50
#define FRICTION_DECELERATION 0.003
#define OVERSHOOT_INVERSE_ACCELERATION 0.003
@@ -284,6 +283,8 @@ static gboolean _gtk_scrolled_window_set_adjustment_value (GtkScrolledWindo
gboolean allow_overshooting,
gboolean snap_to_border);
static void gtk_scrolled_window_cancel_deceleration (GtkScrolledWindow *scrolled_window);
static guint signals[LAST_SIGNAL] = {0};
G_DEFINE_TYPE (GtkScrolledWindow, gtk_scrolled_window, GTK_TYPE_BIN)
@@ -2383,10 +2384,10 @@ _gtk_scrolled_window_set_adjustment_value (GtkScrolledWindow *scrolled_window,
return (*prev_value != value);
}
static gboolean
scrolled_window_deceleration_cb (gpointer user_data)
static void
scrolled_window_deceleration_cb (GdkFrameClock *frame_clock,
KineticScrollData *data)
{
KineticScrollData *data = user_data;
GtkScrolledWindow *scrolled_window = data->scrolled_window;
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
GtkAdjustment *hadjustment, *vadjustment;
@@ -2401,7 +2402,7 @@ scrolled_window_deceleration_cb (gpointer user_data)
_gtk_scrolled_window_get_overshoot (scrolled_window,
&old_overshoot_x, &old_overshoot_y);
current_time = g_get_monotonic_time ();
current_time = gdk_frame_clock_get_frame_time (frame_clock);
elapsed = (current_time - data->last_deceleration_time) / 1000;
data->last_deceleration_time = current_time;
@@ -2495,12 +2496,9 @@ scrolled_window_deceleration_cb (gpointer user_data)
if (overshoot_x != 0 || overshoot_y != 0 ||
data->x_velocity != 0 || data->y_velocity != 0)
return TRUE;
gdk_frame_clock_request_phase (frame_clock, GDK_FRAME_CLOCK_PHASE_UPDATE);
else
{
priv->deceleration_id = 0;
return FALSE;
}
gtk_scrolled_window_cancel_deceleration (scrolled_window);
}
static void
@@ -2510,7 +2508,11 @@ gtk_scrolled_window_cancel_deceleration (GtkScrolledWindow *scrolled_window)
if (priv->deceleration_id)
{
g_source_remove (priv->deceleration_id);
GdkFrameClock *frame_clock;
frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (scrolled_window));
g_signal_handler_disconnect (frame_clock,
priv->deceleration_id);
priv->deceleration_id = 0;
}
}
@@ -2519,12 +2521,15 @@ static void
gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window)
{
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
GdkFrameClock *frame_clock;
KineticScrollData *data;
gdouble angle;
frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (scrolled_window));
data = g_new0 (KineticScrollData, 1);
data->scrolled_window = scrolled_window;
data->last_deceleration_time = g_get_monotonic_time ();
data->last_deceleration_time = gdk_frame_clock_get_frame_time (frame_clock);
data->x_velocity = priv->x_velocity;
data->y_velocity = priv->y_velocity;
@@ -2536,10 +2541,12 @@ gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window)
data->vel_sine = sin (angle);
scrolled_window->priv->deceleration_id =
gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT,
FRAME_INTERVAL,
scrolled_window_deceleration_cb,
data, (GDestroyNotify) g_free);
g_signal_connect_data (frame_clock, "update",
G_CALLBACK (scrolled_window_deceleration_cb),
data,
(GClosureNotify) g_free,
0);
gdk_frame_clock_request_phase (frame_clock, GDK_FRAME_CLOCK_PHASE_UPDATE);
}
static gboolean

View File

@@ -358,20 +358,21 @@ struct _GtkStyleContextPrivate
GtkStyleCascade *cascade;
GtkStyleContext *animation_list_prev;
GtkStyleContext *animation_list_next;
GtkStyleContext *parent;
GSList *children;
GtkWidget *widget;
GtkWidget *widget;
GtkWidgetPath *widget_path;
GHashTable *style_data;
GtkStyleInfo *info;
GdkFrameClock *frame_clock;
guint frame_clock_update_id;
GtkCssChange relevant_changes;
GtkCssChange pending_changes;
const GtkBitmask *invalidating_context;
guint animating : 1;
guint invalid : 1;
};
@@ -379,6 +380,7 @@ enum {
PROP_0,
PROP_SCREEN,
PROP_DIRECTION,
PROP_FRAME_CLOCK,
PROP_PARENT
};
@@ -388,8 +390,6 @@ enum {
};
static guint signals[LAST_SIGNAL] = { 0 };
static GtkStyleContext *_running_animations = NULL;
guint _running_animations_timer_id = 0;
static void gtk_style_context_finalize (GObject *object);
@@ -404,6 +404,9 @@ static void gtk_style_context_impl_get_property (GObject *object,
static StyleData *style_data_lookup (GtkStyleContext *context);
static void gtk_style_context_disconnect_update (GtkStyleContext *context);
static void gtk_style_context_connect_update (GtkStyleContext *context);
G_DEFINE_TYPE (GtkStyleContext, gtk_style_context, G_TYPE_OBJECT)
static void
@@ -442,6 +445,13 @@ gtk_style_context_class_init (GtkStyleContextClass *klass)
P_("The associated GdkScreen"),
GDK_TYPE_SCREEN,
GTK_PARAM_READWRITE));
g_object_class_install_property (object_class,
PROP_FRAME_CLOCK,
g_param_spec_object ("paint-clock",
P_("FrameClock"),
P_("The associated GdkFrameClock"),
GDK_TYPE_FRAME_CLOCK,
GTK_PARAM_READWRITE));
g_object_class_install_property (object_class,
PROP_DIRECTION,
g_param_spec_enum ("direction",
@@ -724,19 +734,14 @@ gtk_style_context_init (GtkStyleContext *style_context)
_gtk_style_cascade_get_for_screen (priv->screen));
}
static gboolean
gtk_style_context_do_animations (gpointer unused)
static void
gtk_style_context_update (GdkFrameClock *clock,
GtkStyleContext *context)
{
GtkStyleContext *context;
_gtk_style_context_queue_invalidate (context, GTK_CSS_CHANGE_ANIMATE);
for (context = _running_animations;
context != NULL;
context = context->priv->animation_list_next)
{
_gtk_style_context_queue_invalidate (context, GTK_CSS_CHANGE_ANIMATE);
}
return TRUE;
/* A little blech to request one more than we need */
gdk_frame_clock_request_phase (clock, GDK_FRAME_CLOCK_PHASE_UPDATE);
}
static gboolean
@@ -744,8 +749,35 @@ gtk_style_context_is_animating (GtkStyleContext *context)
{
GtkStyleContextPrivate *priv = context->priv;
return priv->animation_list_prev != NULL
|| _running_animations == context;
return priv->animating;
}
static void
gtk_style_context_disconnect_update (GtkStyleContext *context)
{
GtkStyleContextPrivate *priv = context->priv;
if (priv->frame_clock && priv->frame_clock_update_id)
{
g_signal_handler_disconnect (priv->frame_clock,
priv->frame_clock_update_id);
priv->frame_clock_update_id = 0;
}
}
static void
gtk_style_context_connect_update (GtkStyleContext *context)
{
GtkStyleContextPrivate *priv = context->priv;
if (priv->frame_clock && priv->frame_clock_update_id == 0)
{
priv->frame_clock_update_id = g_signal_connect (priv->frame_clock,
"update",
G_CALLBACK (gtk_style_context_update),
context);
gdk_frame_clock_request_phase (priv->frame_clock, GDK_FRAME_CLOCK_PHASE_UPDATE);
}
}
static void
@@ -756,25 +788,9 @@ gtk_style_context_stop_animating (GtkStyleContext *context)
if (!gtk_style_context_is_animating (context))
return;
if (priv->animation_list_prev == NULL)
{
_running_animations = priv->animation_list_next;
priv->animating = FALSE;
if (_running_animations == NULL)
{
/* we were the last animation */
g_source_remove (_running_animations_timer_id);
_running_animations_timer_id = 0;
}
}
else
priv->animation_list_prev->priv->animation_list_next = priv->animation_list_next;
if (priv->animation_list_next)
priv->animation_list_next->priv->animation_list_prev = priv->animation_list_prev;
priv->animation_list_next = NULL;
priv->animation_list_prev = NULL;
gtk_style_context_disconnect_update (context);
}
static void
@@ -785,19 +801,9 @@ gtk_style_context_start_animating (GtkStyleContext *context)
if (gtk_style_context_is_animating (context))
return;
if (_running_animations == NULL)
{
_running_animations_timer_id = gdk_threads_add_timeout (25,
gtk_style_context_do_animations,
NULL);
_running_animations = context;
}
else
{
priv->animation_list_next = _running_animations;
_running_animations->priv->animation_list_prev = context;
_running_animations = context;
}
priv->animating = TRUE;
gtk_style_context_connect_update (context);
}
static gboolean
@@ -886,6 +892,10 @@ gtk_style_context_impl_set_property (GObject *object,
g_value_get_enum (value));
G_GNUC_END_IGNORE_DEPRECATIONS;
break;
case PROP_FRAME_CLOCK:
gtk_style_context_set_frame_clock (style_context,
g_value_get_object (value));
break;
case PROP_PARENT:
gtk_style_context_set_parent (style_context,
g_value_get_object (value));
@@ -918,6 +928,9 @@ gtk_style_context_impl_get_property (GObject *object,
g_value_set_enum (value, gtk_style_context_get_direction (style_context));
G_GNUC_END_IGNORE_DEPRECATIONS;
break;
case PROP_FRAME_CLOCK:
g_value_set_object (value, priv->frame_clock);
break;
case PROP_PARENT:
g_value_set_object (value, priv->parent);
break;
@@ -2614,6 +2627,70 @@ gtk_style_context_get_screen (GtkStyleContext *context)
return priv->screen;
}
/**
* gtk_style_context_set_frame_clock:
* @context: a #GdkFrameClock
* @frame_clock: a #GdkFrameClock
*
* Attaches @context to the given frame clock.
*
* The frame clock is used for the timing of animations.
*
* If you are using a #GtkStyleContext returned from
* gtk_widget_get_style_context(), you do not need to
* call this yourself.
*
* Since: 3.8
**/
void
gtk_style_context_set_frame_clock (GtkStyleContext *context,
GdkFrameClock *frame_clock)
{
GtkStyleContextPrivate *priv;
g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
g_return_if_fail (frame_clock == NULL || GDK_IS_FRAME_CLOCK (frame_clock));
priv = context->priv;
if (priv->frame_clock == frame_clock)
return;
if (priv->animating)
gtk_style_context_disconnect_update (context);
if (priv->frame_clock)
g_object_unref (priv->frame_clock);
priv->frame_clock = frame_clock;
if (priv->frame_clock)
g_object_ref (priv->frame_clock);
if (priv->animating)
gtk_style_context_connect_update (context);
g_object_notify (G_OBJECT (context), "paint-clock");
}
/**
* gtk_style_context_get_frame_clock:
* @context: a #GtkStyleContext
*
* Returns the #GdkFrameClock to which @context is attached.
*
* Returns: (transfer none): a #GdkFrameClock, or %NULL
* if @context does not have an attached frame clock.
* Since: 3.8
**/
GdkFrameClock *
gtk_style_context_get_frame_clock (GtkStyleContext *context)
{
GtkStyleContextPrivate *priv;
g_return_val_if_fail (GTK_IS_STYLE_CONTEXT (context), NULL);
priv = context->priv;
return priv->frame_clock;
}
/**
* gtk_style_context_set_direction:
* @context: a #GtkStyleContext

View File

@@ -836,6 +836,13 @@ void gtk_style_context_set_screen (GtkStyleContext *context,
GdkScreen *screen);
GdkScreen * gtk_style_context_get_screen (GtkStyleContext *context);
GDK_AVAILABLE_IN_3_8
void gtk_style_context_set_frame_clock (GtkStyleContext *context,
GdkFrameClock *frame_clock);
GDK_AVAILABLE_IN_3_8
GdkFrameClock *gtk_style_context_get_frame_clock (GtkStyleContext *context);
GDK_DEPRECATED_IN_3_8_FOR(gtk_style_context_set_state)
void gtk_style_context_set_direction (GtkStyleContext *context,
GtkTextDirection direction);

View File

@@ -409,6 +409,9 @@ struct _GtkWidgetPrivate
/* The widget's parent */
GtkWidget *parent;
/* Animations and other things to update on clock ticks */
GList *tick_callbacks;
#ifdef G_ENABLE_DEBUG
/* Number of gtk_widget_push_verify_invariants () */
guint verifying_invariants_count;
@@ -710,6 +713,10 @@ static void gtk_widget_set_device_enabled_internal (GtkWidget *widget,
GdkDevice *device,
gboolean recurse,
gboolean enabled);
static void gtk_widget_on_frame_clock_update (GdkFrameClock *frame_clock,
GtkWidget *widget);
static gboolean event_window_is_still_viewable (GdkEvent *event);
static void gtk_cairo_set_event (cairo_t *cr,
GdkEventExpose *event);
@@ -4505,6 +4512,222 @@ gtk_widget_update_devices_mask (GtkWidget *widget,
gtk_widget_set_device_enabled_internal (widget, GDK_DEVICE (l->data), recurse, TRUE);
}
typedef struct _GtkTickCallbackInfo GtkTickCallbackInfo;
struct _GtkTickCallbackInfo
{
guint refcount;
guint id;
GtkTickCallback callback;
gpointer user_data;
GDestroyNotify notify;
guint destroyed : 1;
};
static void
ref_tick_callback_info (GtkTickCallbackInfo *info)
{
info->refcount++;
}
static void
unref_tick_callback_info (GtkWidget *widget,
GtkTickCallbackInfo *info,
GList *link)
{
GtkWidgetPrivate *priv = widget->priv;
info->refcount--;
if (info->refcount == 0)
{
priv->tick_callbacks = g_list_delete_link (priv->tick_callbacks, link);
if (info->notify)
info->notify (info->user_data);
g_slice_free (GtkTickCallbackInfo, info);
}
if (priv->tick_callbacks == NULL && priv->realized)
{
GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (widget);
g_signal_handlers_disconnect_by_func (frame_clock,
(gpointer) gtk_widget_on_frame_clock_update,
widget);
}
}
static void
destroy_tick_callback_info (GtkWidget *widget,
GtkTickCallbackInfo *info,
GList *link)
{
if (!info->destroyed)
{
info->destroyed = TRUE;
unref_tick_callback_info (widget, info, link);
}
}
static void
gtk_widget_on_frame_clock_update (GdkFrameClock *frame_clock,
GtkWidget *widget)
{
GtkWidgetPrivate *priv = widget->priv;
GList *l;
for (l = priv->tick_callbacks; l;)
{
GtkTickCallbackInfo *info = l->data;
GList *next;
ref_tick_callback_info (info);
if (!info->destroyed)
{
if (info->callback (widget,
frame_clock,
info->user_data) == G_SOURCE_REMOVE)
{
destroy_tick_callback_info (widget, info, l);
}
}
next = l->next;
unref_tick_callback_info (widget, info, l);
l = next;
}
if (priv->tick_callbacks != NULL)
gdk_frame_clock_request_phase (frame_clock,
GDK_FRAME_CLOCK_PHASE_UPDATE);
}
static guint tick_callback_id;
/**
* gtk_widget_add_tick_callback:
* @widget: a #GtkWidget
* @callback: function to call for updating animations
* @user_data: data to pass to @callback
* @notify: function to call to free @user_data when the callback is removed.
*
* Queues a animation frame update and adds a callback to be called
* before each frame. Until the tick callback is removed, it will be
* called frequently (usually at the frame rate of the output device
* or as quickly as the application an be repainted, whichever is
* slower). For this reason, is most suitable for handling graphics
* that change every frame or every few frames. The tick callback does
* not automatically imply a relayout or repaint. If you want a
* repaint or relayout, and aren't changing widget properties that
* would trigger that (for example, changing the text of a #GtkLabel),
* then you will have to call gtk_widget_queue_resize() or
* gtk_widget_queue_draw_area() yourself.
*
* gtk_frame_clock_get_frame_time() should generally be used for timing
* continuous animations and
* gtk_frame_timings_get_predicted_presentation_time() if you are
* trying to display isolated frames particular times.
*
* This is a more convenient alternative to connecting directly to the
* ::update signal of GdkFrameClock, since you don't have to worry about
* when a #GdkFrameClock is assigned to a widget.
*/
guint
gtk_widget_add_tick_callback (GtkWidget *widget,
GtkTickCallback callback,
gpointer user_data,
GDestroyNotify notify)
{
GtkWidgetPrivate *priv;
GtkTickCallbackInfo *info;
g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
priv = widget->priv;
if (priv->tick_callbacks == NULL && priv->realized)
{
GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (widget);
g_signal_connect (frame_clock, "update",
G_CALLBACK (gtk_widget_on_frame_clock_update),
widget);
gdk_frame_clock_request_phase (frame_clock,
GDK_FRAME_CLOCK_PHASE_UPDATE);
}
info = g_slice_new (GtkTickCallbackInfo);
info->refcount = 1;
info->id = ++tick_callback_id;
info->callback = callback;
info->user_data = user_data;
info->notify = notify;
priv->tick_callbacks = g_list_prepend (priv->tick_callbacks,
info);
return info->id;
}
void
gtk_widget_remove_tick_callback (GtkWidget *widget,
guint id)
{
GtkWidgetPrivate *priv;
GList *l;
g_return_if_fail (GTK_IS_WIDGET (widget));
priv = widget->priv;
for (l = priv->tick_callbacks; l; l = l->next)
{
GtkTickCallbackInfo *info = l->data;
if (info->id == id)
destroy_tick_callback_info (widget, info, l);
}
}
static void
gtk_widget_connect_frame_clock (GtkWidget *widget,
GdkFrameClock *frame_clock)
{
GtkWidgetPrivate *priv = widget->priv;
if (GTK_IS_CONTAINER (widget))
_gtk_container_maybe_start_idle_sizer (GTK_CONTAINER (widget));
if (priv->tick_callbacks != NULL)
{
g_signal_connect (frame_clock, "update",
G_CALLBACK (gtk_widget_on_frame_clock_update),
widget);
gdk_frame_clock_request_phase (frame_clock,
GDK_FRAME_CLOCK_PHASE_UPDATE);
}
if (priv->context)
gtk_style_context_set_frame_clock (priv->context, frame_clock);
}
static void
gtk_widget_disconnect_frame_clock (GtkWidget *widget,
GdkFrameClock *frame_clock)
{
GtkWidgetPrivate *priv = widget->priv;
if (GTK_IS_CONTAINER (widget))
_gtk_container_stop_idle_sizer (GTK_CONTAINER (widget));
if (priv->tick_callbacks)
g_signal_handlers_disconnect_by_func (frame_clock,
(gpointer) gtk_widget_on_frame_clock_update,
widget);
if (priv->context)
gtk_style_context_set_frame_clock (priv->context, NULL);
}
/**
* gtk_widget_realize:
* @widget: a #GtkWidget
@@ -4585,6 +4808,9 @@ gtk_widget_realize (GtkWidget *widget)
_gtk_widget_enable_device_events (widget);
gtk_widget_update_devices_mask (widget, TRUE);
gtk_widget_connect_frame_clock (widget,
gtk_widget_get_frame_clock (widget));
gtk_widget_pop_verify_invariants (widget);
}
}
@@ -4617,6 +4843,9 @@ gtk_widget_unrealize (GtkWidget *widget)
if (widget->priv->mapped)
gtk_widget_unmap (widget);
gtk_widget_disconnect_frame_clock (widget,
gtk_widget_get_frame_clock (widget));
g_signal_emit (widget, widget_signals[UNREALIZE], 0);
g_assert (!widget->priv->mapped);
gtk_widget_set_realized (widget, FALSE);
@@ -4777,6 +5006,60 @@ gtk_widget_queue_resize_no_redraw (GtkWidget *widget)
_gtk_size_group_queue_resize (widget, 0);
}
/**
* gtk_widget_get_frame_clock:
* @widget: a #GtkWidget
*
* Obtains the frame clock for a widget. The frame clock is a global
* "ticker" that can be used to drive animations and repaints. The
* most common reason to get the frame clock is to call
* gdk_frame_clock_get_frame_time(), in order to get a time to use for
* animating. For example you might record the start of the animation
* with an initial value from gdk_frame_clock_get_frame_time(), and
* then update the animation by calling
* gdk_frame_clock_get_frame_time() again during each repaint.
*
* gdk_frame_clock_request_phase() will result in a new frame on the
* clock, but won't necessarily repaint any widgets. To repaint a
* widget, you have to use gtk_widget_queue_draw() which invalidates
* the widget (thus scheduling it to receive a draw on the next
* frame). gtk_widget_queue_draw() will also end up requesting a frame
* on the appropriate frame clock.
*
* A widget's frame clock will not change while the widget is
* mapped. Reparenting a widget (which implies a temporary unmap) can
* change the widget's frame clock.
*
* Unrealized widgets do not have a frame clock.
*
* Since: 3.0
* Return value: (transfer none): a #GdkFrameClock (or #NULL if widget is unrealized)
*/
GdkFrameClock*
gtk_widget_get_frame_clock (GtkWidget *widget)
{
g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
if (widget->priv->realized)
{
/* We use gtk_widget_get_toplevel() here to make it explicit that
* the frame clock is a property of the toplevel that a widget
* is anchored to; gdk_window_get_toplevel() will go up the
* hierarchy anyways, but should squash any funny business with
* reparenting windows and widgets.
*/
GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
GdkWindow *window = gtk_widget_get_window (toplevel);
g_assert (window != NULL);
return gdk_window_get_frame_clock (window);
}
else
{
return NULL;
}
}
/**
* gtk_widget_size_request:
* @widget: a #GtkWidget
@@ -8453,6 +8736,17 @@ gtk_widget_propagate_hierarchy_changed_recurse (GtkWidget *widget,
priv->anchored = new_anchored;
/* This can only happen with gtk_widget_reparent() */
if (priv->realized)
{
if (new_anchored)
gtk_widget_connect_frame_clock (widget,
gtk_widget_get_frame_clock (widget));
else
gtk_widget_disconnect_frame_clock (widget,
gtk_widget_get_frame_clock (info->previous_toplevel));
}
g_signal_emit (widget, widget_signals[HIERARCHY_CHANGED], 0, info->previous_toplevel);
do_screen_change (widget, info->previous_screen, info->new_screen);
@@ -10461,6 +10755,7 @@ gtk_widget_real_destroy (GtkWidget *object)
/* gtk_object_destroy() will already hold a refcount on object */
GtkWidget *widget = GTK_WIDGET (object);
GtkWidgetPrivate *priv = widget->priv;
GList *l;
if (GTK_WIDGET_GET_CLASS (widget)->priv->accessible_type != GTK_TYPE_ACCESSIBLE)
{
@@ -10482,6 +10777,13 @@ gtk_widget_real_destroy (GtkWidget *object)
gtk_grab_remove (widget);
for (l = priv->tick_callbacks; l;)
{
GList *next = l->next;
destroy_tick_callback_info (widget, l->data, l);
l = next;
}
if (priv->style)
g_object_unref (priv->style);
priv->style = gtk_widget_get_default_style ();
@@ -14396,6 +14698,7 @@ gtk_widget_get_style_context (GtkWidget *widget)
if (G_UNLIKELY (priv->context == NULL))
{
GdkScreen *screen;
GdkFrameClock *frame_clock;
priv->context = gtk_style_context_new ();
@@ -14405,6 +14708,10 @@ gtk_widget_get_style_context (GtkWidget *widget)
if (screen)
gtk_style_context_set_screen (priv->context, screen);
frame_clock = gtk_widget_get_frame_clock (widget);
if (frame_clock)
gtk_style_context_set_frame_clock (priv->context, frame_clock);
if (priv->parent)
gtk_style_context_set_parent (priv->context,
gtk_widget_get_style_context (priv->parent));

View File

@@ -86,6 +86,21 @@ typedef GdkRectangle GtkAllocation;
typedef void (*GtkCallback) (GtkWidget *widget,
gpointer data);
/**
* GtkTickCallback:
* @widget: the widget
* @frame_clock: the frame clock for the widget (same as calling gtk_widget_get_frame_clock())
* @user_data: user data passed to gtk_widget_add_tick_callback().
*
* Callback type for adding a function to update animations. See gtk_widget_add_tick_callback().
*
* Returns: %G_SOURCE_CONTINUE if the tick callback should continue to be called,
* %G_SOURCE_REMOVE if the tick callback should be removed.
*/
typedef gboolean (*GtkTickCallback) (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer user_data);
/**
* GtkRequisition:
* @width: the widget's desired width
@@ -473,6 +488,8 @@ void gtk_widget_queue_draw_region (GtkWidget *widget,
const cairo_region_t*region);
void gtk_widget_queue_resize (GtkWidget *widget);
void gtk_widget_queue_resize_no_redraw (GtkWidget *widget);
GdkFrameClock* gtk_widget_get_frame_clock (GtkWidget *widget);
GDK_DEPRECATED_IN_3_0_FOR(gtk_widget_get_preferred_size)
void gtk_widget_size_request (GtkWidget *widget,
GtkRequisition *requisition);
@@ -903,6 +920,18 @@ void gtk_widget_insert_action_group (GtkWidg
const gchar *name,
GActionGroup *group);
GDK_AVAILABLE_IN_3_8
guint gtk_widget_add_tick_callback (GtkWidget *widget,
GtkTickCallback callback,
gpointer user_data,
GDestroyNotify notify);
GDK_AVAILABLE_IN_3_8
void gtk_widget_remove_tick_callback (GtkWidget *widget,
guint id);
G_END_DECLS
#endif /* __GTK_WIDGET_H__ */

View File

@@ -5164,7 +5164,6 @@ gtk_window_realize (GtkWidget *widget)
gtk_style_context_set_background (gtk_widget_get_style_context (widget), gdk_window);
gdk_window_enable_synchronized_configure (gdk_window);
return;
}
@@ -5238,8 +5237,6 @@ gtk_window_realize (GtkWidget *widget)
gdk_window = gdk_window_new (parent_window, &attributes, attributes_mask);
gtk_widget_set_window (widget, gdk_window);
gdk_window_enable_synchronized_configure (gdk_window);
gtk_widget_register_window (widget, gdk_window);
context = gtk_widget_get_style_context (widget);
@@ -5580,7 +5577,6 @@ gtk_window_configure_event (GtkWidget *widget,
if (GTK_WIDGET_CLASS (gtk_window_parent_class)->configure_event)
return GTK_WIDGET_CLASS (gtk_window_parent_class)->configure_event (widget, event);
gdk_window_configure_finished (gtk_widget_get_window (widget));
return FALSE;
}
@@ -5614,7 +5610,6 @@ gtk_window_configure_event (GtkWidget *widget,
(allocation.width == event->width &&
allocation.height == event->height))
{
gdk_window_configure_finished (gtk_widget_get_window (widget));
return TRUE;
}
@@ -7153,10 +7148,6 @@ gtk_window_move_resize (GtkWindow *window)
set_grip_position (window);
update_grip_visibility (window);
gdk_window_process_updates (gdk_window, TRUE);
gdk_window_configure_finished (gdk_window);
/* If the configure request changed, it means that
* we either:
* 1) coincidentally changed hints or widget properties

View File

@@ -25,9 +25,12 @@ testsocket_programs = testsocket testsocket_child
endif
noinst_PROGRAMS = $(TEST_PROGS) \
animated-resizing \
motion-compression \
simple \
flicker \
print-editor \
video-timer \
testaccel \
testadjustsize \
testappchooser \
@@ -149,9 +152,12 @@ endif
endif
animated_resizing_DEPENDENCIES = $(TEST_DEPS)
flicker_DEPENDENCIES = $(TEST_DEPS)
motion_compression_DEPENDENCIES = $(TEST_DEPS)
simple_DEPENDENCIES = $(TEST_DEPS)
print_editor_DEPENDENCIES = $(TEST_DEPS)
video_timer_DEPENDENCIES = $(TEST_DEPS)
testheightforwidth_DEPENDENCIES = $(TEST_DEPS)
testicontheme_DEPENDENCIES = $(TEST_DEPS)
testiconview_DEPENDENCIES = $(TEST_DEPS)
@@ -249,6 +255,16 @@ testpixbuf_scale_DEPENDENCIES = $(TEST_DEPS)
testgmenu_DEPENDENCIES = $(TEST_DEPS)
testlogout_DEPENDENCIES = $(TEST_DEPS)
animated_resizing_SOURCES = \
animated-resizing.c \
variable.c \
variable.h
video_timer_SOURCES = \
video-timer.c \
variable.c \
variable.h
testboxcss_SOURCES = \
testboxcss.c \
prop-editor.c

324
tests/animated-resizing.c Normal file
View File

@@ -0,0 +1,324 @@
/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include <gtk/gtk.h>
#include <math.h>
#include <string.h>
#include "variable.h"
#define RADIUS 64
#define DIAMETER (2*RADIUS)
#define WIDTH 600
#define HEIGHT 600
#define WINDOW_SIZE_JITTER 200
#define CYCLE_TIME 5.
static GtkWidget *window;
static int window_width = WIDTH, window_height = HEIGHT;
gint64 start_frame_time;
static double angle;
static int max_stats = -1;
static double statistics_time = 5.;
static double load_factor = 1.0;
static double cb_no_resize = FALSE;
static gboolean machine_readable = FALSE;
static cairo_surface_t *source_surface;
static void
ensure_resources(cairo_surface_t *target)
{
cairo_t *cr;
int i, j;
if (source_surface != NULL)
return;
source_surface = cairo_surface_create_similar (target, CAIRO_CONTENT_COLOR_ALPHA,
16 * DIAMETER, 16 * DIAMETER);
cr = cairo_create(source_surface);
cairo_save(cr);
cairo_set_source_rgba(cr, 0, 0, 0, 0);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr);
cairo_restore(cr);
cairo_set_line_width(cr, 1.0);
for (j = 0; j < 16; j++)
for (i = 0; i < 16; i++)
{
cairo_set_source_rgba(cr,
((i * 41) % 16) / 15.,
((i * 31) % 16) / 15.,
((i * 23) % 16) / 15.,
0.25);
cairo_arc(cr,
i * DIAMETER + RADIUS, j * DIAMETER + RADIUS,
RADIUS - 0.5, 0, 2 * M_PI);
cairo_fill_preserve(cr);
cairo_set_source_rgba(cr,
((i * 41) % 16) / 15.,
((i * 31) % 16) / 15.,
((i * 23) % 16) / 15.,
1.0);
cairo_stroke(cr);
}
}
static void
on_window_draw (GtkWidget *widget,
cairo_t *cr)
{
GRand *rand = g_rand_new_with_seed(0);
int i;
int width, height;
width = gtk_widget_get_allocated_width (widget);
height = gtk_widget_get_allocated_height (widget);
ensure_resources (cairo_get_target (cr));
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_paint(cr);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_set_line_width(cr, 1.0);
cairo_rectangle (cr, 0.5, 0.5, width - 1, height - 1);
cairo_stroke (cr);
for(i = 0; i < load_factor * 150; i++)
{
int source = g_rand_int_range(rand, 0, 255);
double phi = g_rand_double_range(rand, 0, 2 * M_PI) + angle;
double r = g_rand_double_range(rand, 0, width / 2 - RADIUS);
int x, y;
int source_x = (source % 16) * DIAMETER;
int source_y = (source / 16) * DIAMETER;
x = round(width / 2 + r * cos(phi) - RADIUS);
y = round(height / 2 - r * sin(phi) - RADIUS);
cairo_set_source_surface(cr, source_surface,
x - source_x, y - source_y);
cairo_rectangle(cr, x, y, DIAMETER, DIAMETER);
cairo_fill(cr);
}
g_rand_free(rand);
}
static void
print_double (const char *description,
double value)
{
if (machine_readable)
g_print ("%g\t", value);
else
g_print ("%s: %g\n", description, value);
}
static void
print_variable (const char *description,
Variable *variable)
{
if (variable->weight != 0)
{
if (machine_readable)
g_print ("%g\t%g\t",
variable_mean (variable),
variable_standard_deviation (variable));
else
g_print ("%s: %g +/- %g\n", description,
variable_mean (variable),
variable_standard_deviation (variable));
}
else
{
if (machine_readable)
g_print ("-\t-\t");
else
g_print ("%s: <n/a>\n", description);
}
}
static void
handle_frame_stats (GdkFrameClock *frame_clock)
{
static int num_stats = 0;
static double last_print_time = 0;
static int frames_since_last_print = 0;
static gint64 frame_counter;
static gint64 last_handled_frame = -1;
static Variable latency = VARIABLE_INIT;
double current_time;
current_time = g_get_monotonic_time ();
if (current_time >= last_print_time + 1000000 * statistics_time)
{
if (frames_since_last_print)
{
if (num_stats == 0 && machine_readable)
{
g_print ("# load_factor frame_rate latency\n");
}
num_stats++;
if (machine_readable)
g_print ("%g ", load_factor);
print_double ("Frame rate ",
frames_since_last_print / ((current_time - last_print_time) / 1000000.));
print_variable ("Latency", &latency);
g_print ("\n");
}
last_print_time = current_time;
frames_since_last_print = 0;
variable_reset (&latency);
if (num_stats == max_stats)
gtk_main_quit ();
}
frames_since_last_print++;
for (frame_counter = last_handled_frame;
frame_counter < gdk_frame_clock_get_frame_counter (frame_clock);
frame_counter++)
{
GdkFrameTimings *timings = gdk_frame_clock_get_timings (frame_clock, frame_counter);
GdkFrameTimings *previous_timings = gdk_frame_clock_get_timings (frame_clock, frame_counter - 1);
if (!timings || gdk_frame_timings_get_complete (timings))
last_handled_frame = frame_counter;
if (timings && gdk_frame_timings_get_complete (timings) && previous_timings &&
gdk_frame_timings_get_presentation_time (timings) != 0 &&
gdk_frame_timings_get_presentation_time (previous_timings) != 0)
{
double display_time = (gdk_frame_timings_get_presentation_time (timings) - gdk_frame_timings_get_presentation_time (previous_timings)) / 1000.;
double frame_latency = (gdk_frame_timings_get_presentation_time (previous_timings) - gdk_frame_timings_get_frame_time (previous_timings)) / 1000. + display_time / 2;
variable_add_weighted (&latency, frame_latency, display_time);
}
}
}
static void
on_frame (double progress)
{
GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (window);
int jitter;
if (frame_clock)
handle_frame_stats (frame_clock);
angle = 2 * M_PI * progress;
jitter = WINDOW_SIZE_JITTER * sin(angle);
if (!cb_no_resize)
{
window_width = WIDTH + jitter;
window_height = HEIGHT + jitter;
}
gtk_window_resize (GTK_WINDOW (window),
window_width, window_height);
gtk_widget_queue_draw (window);
}
static gboolean
tick_callback (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer user_data)
{
gint64 frame_time = gdk_frame_clock_get_frame_time (frame_clock);
double scaled_time;
if (start_frame_time == 0)
start_frame_time = frame_time;
scaled_time = (frame_time - start_frame_time) / (CYCLE_TIME * 1000000);
on_frame (scaled_time - floor (scaled_time));
return G_SOURCE_CONTINUE;
}
static gboolean
on_map_event (GtkWidget *widget,
GdkEventAny *event)
{
gtk_widget_add_tick_callback (window, tick_callback, NULL, NULL);
return FALSE;
}
static GOptionEntry options[] = {
{ "factor", 'f', 0, G_OPTION_ARG_DOUBLE, &load_factor, "Load factor", "FACTOR" },
{ "max-statistics", 'm', 0, G_OPTION_ARG_INT, &max_stats, "Maximum statistics printed", NULL },
{ "machine-readable", 0, 0, G_OPTION_ARG_NONE, &machine_readable, "Print statistics in columns", NULL },
{ "no-resize", 'n', 0, G_OPTION_ARG_NONE, &cb_no_resize, "No Resize", NULL },
{ "statistics-time", 's', 0, G_OPTION_ARG_DOUBLE, &statistics_time, "Statistics accumulation time", "TIME" },
{ NULL }
};
int
main(int argc, char **argv)
{
GError *error = NULL;
GdkScreen *screen;
GdkRectangle monitor_bounds;
if (!gtk_init_with_args (&argc, &argv, "",
options, NULL, &error))
{
g_printerr ("Option parsing failed: %s\n", error->message);
return 1;
}
g_print ("%sLoad factor: %g\n",
machine_readable ? "# " : "",
load_factor);
g_print ("%sResizing?: %s\n",
machine_readable ? "# " : "",
cb_no_resize ? "no" : "yes");
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_keep_above (GTK_WINDOW (window), TRUE);
gtk_window_set_gravity (GTK_WINDOW (window), GDK_GRAVITY_CENTER);
gtk_widget_set_app_paintable (window, TRUE);
g_signal_connect (window, "draw",
G_CALLBACK (on_window_draw), NULL);
g_signal_connect (window, "destroy",
G_CALLBACK (gtk_main_quit), NULL);
g_signal_connect (window, "map-event",
G_CALLBACK (on_map_event), NULL);
on_frame (0.);
screen = gtk_widget_get_screen (window);
gdk_screen_get_monitor_geometry (screen,
gdk_screen_get_primary_monitor (screen),
&monitor_bounds);
gtk_window_move (GTK_WINDOW (window),
monitor_bounds.x + (monitor_bounds.width - window_width) / 2,
monitor_bounds.y + (monitor_bounds.height - window_height) / 2);
gtk_widget_show (window);
gtk_main ();
return 0;
}

View File

@@ -0,0 +1,72 @@
#include <gtk/gtk.h>
#include <math.h>
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;
}

43
tests/variable.c Normal file
View File

@@ -0,0 +1,43 @@
/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include <math.h>
#include "variable.h"
void
variable_add_weighted (Variable *variable,
double value,
double weight)
{
variable->weight += weight;
variable->sum += weight * value;
variable->sum2 += weight * value * value;
}
void
variable_add (Variable *variable,
double value)
{
variable_add_weighted (variable, value, 1.);
}
double
variable_mean (Variable *variable)
{
return variable->sum / variable->weight;
}
double
variable_standard_deviation (Variable *variable)
{
double mean = variable_mean (variable);
return sqrt (variable->sum2 / variable->weight - mean * mean);
}
void
variable_reset (Variable *variable)
{
variable->weight = 0;
variable->sum = 0;
variable->sum2 = 0;
}

23
tests/variable.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef __VARIABLE_H__
#define __VARAIBLE_H__
typedef struct
{
double weight;
double sum;
double sum2;
} Variable;
#define VARIABLE_INIT { 0, 0.0, 0.0 }
void variable_add_weighted (Variable *variable,
double value,
double weight);
void variable_add (Variable *variable,
double value);
double variable_mean (Variable *variable);
double variable_standard_deviation (Variable *variable);
void variable_reset (Variable *variable);
#endif /* __VARAIBLE_H__ */

401
tests/video-timer.c Normal file
View File

@@ -0,0 +1,401 @@
#include <math.h>
#include <gtk/gtk.h>
#include "variable.h"
typedef struct {
gdouble angle;
gint64 stream_time;
gint64 clock_time;
gint64 frame_counter;
} FrameData;
static FrameData *displayed_frame;
static GtkWidget *window;
static GList *past_frames;
static Variable latency_error = VARIABLE_INIT;
static Variable time_factor_stats = VARIABLE_INIT;
static int dropped_frames = 0;
static int n_frames = 0;
static gboolean pll;
static int fps = 24;
/* Thread-safe frame queue */
#define MAX_QUEUE_LENGTH 5
static GQueue *frame_queue;
static GMutex frame_mutex;
static GCond frame_cond;
static void
queue_frame (FrameData *frame_data)
{
g_mutex_lock (&frame_mutex);
while (frame_queue->length == MAX_QUEUE_LENGTH)
g_cond_wait (&frame_cond, &frame_mutex);
g_queue_push_tail (frame_queue, frame_data);
g_mutex_unlock (&frame_mutex);
}
static FrameData *
unqueue_frame (void)
{
FrameData *frame_data;
g_mutex_lock (&frame_mutex);
if (frame_queue->length > 0)
{
frame_data = g_queue_pop_head (frame_queue);
g_cond_signal (&frame_cond);
}
else
{
frame_data = NULL;
}
g_mutex_unlock (&frame_mutex);
return frame_data;
}
static FrameData *
peek_pending_frame (void)
{
FrameData *frame_data;
g_mutex_lock (&frame_mutex);
if (frame_queue->head)
frame_data = frame_queue->head->data;
else
frame_data = NULL;
g_mutex_unlock (&frame_mutex);
return frame_data;
}
static FrameData *
peek_next_frame (void)
{
FrameData *frame_data;
g_mutex_lock (&frame_mutex);
if (frame_queue->head && frame_queue->head->next)
frame_data = frame_queue->head->next->data;
else
frame_data = NULL;
g_mutex_unlock (&frame_mutex);
return frame_data;
}
/* Frame producer thread */
static gpointer
create_frames_thread (gpointer data)
{
int frame_count = 0;
while (TRUE)
{
FrameData *frame_data = g_slice_new0 (FrameData);
frame_data->angle = 2 * M_PI * (frame_count % fps) / (double)fps;
frame_data->stream_time = (G_GINT64_CONSTANT (1000000) * frame_count) / fps;
queue_frame (frame_data);
frame_count++;
}
return NULL;
}
/* Clock management:
*
* The logic here, which is activated by the --pll argument
* demonstrates adjusting the playback rate so that the frames exactly match
* when they are displayed both frequency and phase. If there was an
* accompanying audio track, you would need to resample the audio to match
* the clock.
*
* The algorithm isn't exactly a PLL - I wrote it first that way, but
* it oscillicated before coming into sync and this approach was easier than
* fine-tuning the PLL filter.
*
* A more complicated algorithm could also establish sync when the playback
* rate isn't exactly an integral divisor of the VBlank rate, such as 24fps
* video on a 60fps display.
*/
#define PRE_BUFFER_TIME 500000
static gint64 stream_time_base;
static gint64 clock_time_base;
static double time_factor = 1.0;
static double frequency_time_factor = 1.0;
static double phase_time_factor = 1.0;
static gint64
stream_time_to_clock_time (gint64 stream_time)
{
return clock_time_base + (stream_time - stream_time_base) * time_factor;
}
static void
adjust_clock_for_phase (gint64 frame_clock_time,
gint64 presentation_time)
{
static gint count = 0;
static gint64 previous_frame_clock_time;
static gint64 previous_presentation_time;
gint64 phase = presentation_time - frame_clock_time;
count++;
if (count >= fps) /* Give a second of warmup */
{
gint64 time_delta = frame_clock_time - previous_frame_clock_time;
gint64 previous_phase = previous_presentation_time - previous_frame_clock_time;
double expected_phase_delta;
stream_time_base += (frame_clock_time - clock_time_base) / time_factor;
clock_time_base = frame_clock_time;
expected_phase_delta = time_delta * (1 - phase_time_factor);
/* If the phase is increasing that means the computed clock times are
* increasing too slowly. We increase the frequency time factor to compensate,
* but decrease the compensation so that it takes effect over 1 second to
* avoid jitter */
frequency_time_factor += (phase - previous_phase - expected_phase_delta) / (double)time_delta / fps;
/* We also want to increase or decrease the frequency to bring the phase
* into sync. We do that again so that the phase should sync up over 1 seconds
*/
phase_time_factor = 1 + phase / 2000000.;
time_factor = frequency_time_factor * phase_time_factor;
}
previous_frame_clock_time = frame_clock_time;
previous_presentation_time = presentation_time;
}
/* Drawing */
static void
on_window_draw (GtkWidget *widget,
cairo_t *cr)
{
GdkRectangle allocation;
double cx, cy, r;
cairo_set_source_rgb (cr, 1., 1., 1.);
cairo_paint (cr);
cairo_set_source_rgb (cr, 0., 0., 0.);
gtk_widget_get_allocation (widget, &allocation);
cx = allocation.width / 2.;
cy = allocation.height / 2.;
r = MIN (allocation.width, allocation.height) / 2.;
cairo_arc (cr, cx, cy, r,
0, 2 * M_PI);
cairo_stroke (cr);
if (displayed_frame)
{
cairo_move_to (cr, cx, cy);
cairo_line_to (cr,
cx + r * cos(displayed_frame->angle - M_PI / 2),
cy + r * sin(displayed_frame->angle - M_PI / 2));
cairo_stroke (cr);
if (displayed_frame->frame_counter == 0)
{
GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (window);
displayed_frame->frame_counter = gdk_frame_clock_get_frame_counter (frame_clock);
}
}
}
static void
collect_old_frames (void)
{
GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (window);
GList *l, *l_next;
for (l = past_frames; l; l = l_next)
{
FrameData *frame_data = l->data;
gboolean remove = FALSE;
l_next = l->next;
GdkFrameTimings *timings = gdk_frame_clock_get_timings (frame_clock,
frame_data->frame_counter);
if (timings == NULL)
{
remove = TRUE;
}
else if (gdk_frame_timings_get_complete (timings))
{
gint64 presentation_time = gdk_frame_timings_get_predicted_presentation_time (timings);
gint64 refresh_interval = gdk_frame_timings_get_refresh_interval (timings);
if (pll &&
presentation_time && refresh_interval &&
presentation_time > frame_data->clock_time - refresh_interval / 2 &&
presentation_time < frame_data->clock_time + refresh_interval / 2)
adjust_clock_for_phase (frame_data->clock_time, presentation_time);
if (presentation_time)
variable_add (&latency_error,
presentation_time - frame_data->clock_time);
remove = TRUE;
}
if (remove)
{
past_frames = g_list_delete_link (past_frames, l);
g_slice_free (FrameData, frame_data);
}
}
}
static void
print_statistics (void)
{
gint64 now = g_get_monotonic_time ();
static gint64 last_print_time = 0;
if (last_print_time == 0)
last_print_time = now;
else if (now -last_print_time > 5000000)
{
g_print ("dropped_frames: %d/%d\n",
dropped_frames, n_frames);
g_print ("collected_frames: %g/%d\n",
latency_error.weight, n_frames);
g_print ("latency_error: %g +/- %g\n",
variable_mean (&latency_error),
variable_standard_deviation (&latency_error));
if (pll)
g_print ("playback rate adjustment: %g +/- %g %%\n",
(variable_mean (&time_factor_stats) - 1) * 100,
variable_standard_deviation (&time_factor_stats) * 100);
variable_reset (&latency_error);
variable_reset (&time_factor_stats);
dropped_frames = 0;
n_frames = 0;
last_print_time = now;
}
}
static void
on_update (GdkFrameClock *frame_clock,
gpointer data)
{
GdkFrameTimings *timings = gdk_frame_clock_get_current_timings (frame_clock);
gint64 frame_time = gdk_frame_timings_get_frame_time (timings);
gint64 predicted_presentation_time = gdk_frame_timings_get_predicted_presentation_time (timings);
gint64 refresh_interval;
FrameData *pending_frame;
if (clock_time_base == 0)
clock_time_base = frame_time + PRE_BUFFER_TIME;
gdk_frame_clock_get_refresh_info (frame_clock, frame_time,
&refresh_interval, NULL);
pending_frame = peek_pending_frame ();
if (stream_time_to_clock_time (pending_frame->stream_time)
< predicted_presentation_time + refresh_interval / 2)
{
while (TRUE)
{
FrameData *next_frame = peek_next_frame ();
if (next_frame &&
stream_time_to_clock_time (next_frame->stream_time)
< predicted_presentation_time + refresh_interval / 2)
{
g_slice_free (FrameData, unqueue_frame ());
n_frames++;
dropped_frames++;
pending_frame = next_frame;
}
else
break;
}
if (displayed_frame)
past_frames = g_list_prepend (past_frames, displayed_frame);
n_frames++;
displayed_frame = unqueue_frame ();
displayed_frame->clock_time = stream_time_to_clock_time (displayed_frame->stream_time);
displayed_frame->frame_counter = gdk_frame_timings_get_frame_counter (timings);
variable_add (&time_factor_stats, time_factor);
collect_old_frames ();
print_statistics ();
gtk_widget_queue_draw (window);
}
gdk_frame_clock_request_phase (frame_clock, GDK_FRAME_CLOCK_PHASE_UPDATE);
}
static GOptionEntry options[] = {
{ "pll", 'p', 0, G_OPTION_ARG_NONE, &pll, "Sync frame rate to refresh", NULL },
{ "fps", 'f', 0, G_OPTION_ARG_INT, &fps, "Frame rate", "FPS" },
{ NULL }
};
int
main(int argc, char **argv)
{
GError *error = NULL;
GdkFrameClock *frame_clock;
if (!gtk_init_with_args (&argc, &argv, "",
options, NULL, &error))
{
g_printerr ("Option parsing failed: %s\n", error->message);
return 1;
}
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_app_paintable (window, TRUE);
gtk_window_set_default_size (GTK_WINDOW (window), 300, 300);
g_signal_connect (window, "draw",
G_CALLBACK (on_window_draw), NULL);
g_signal_connect (window, "destroy",
G_CALLBACK (gtk_main_quit), NULL);
gtk_widget_show (window);
frame_queue = g_queue_new ();
g_mutex_init (&frame_mutex);
g_cond_init (&frame_cond);
g_thread_new ("Create Frames", create_frames_thread, NULL);
frame_clock = gtk_widget_get_frame_clock (window);
g_signal_connect (frame_clock, "update",
G_CALLBACK (on_update), NULL);
gdk_frame_clock_request_phase (frame_clock, GDK_FRAME_CLOCK_PHASE_UPDATE);
gtk_main ();
return 0;
}