diff --git a/12to11.c b/12to11.c
index 6aa6061..3e6057d 100644
--- a/12to11.c
+++ b/12to11.c
@@ -200,6 +200,9 @@ XLMain (int argc, char **argv)
compositor.wl_event_loop
= wl_display_get_event_loop (wl_display);
+ /* Initialize server time tracking very early. */
+ InitTime ();
+
InitXErrors ();
SubcompositorInit ();
InitSelections ();
diff --git a/12to11.man b/12to11.man
index 2da1b11..8bd7c44 100644
--- a/12to11.man
+++ b/12to11.man
@@ -110,6 +110,12 @@ Specifies the rendering backend the protocol translator uses to
composite the contents of Wayland surfaces onto X windows. This can
either be \fBpicture\fP (the XRender based compositor) or \fBegl\fP
(the OpenGL ES 2.0 based compositor).
+.TP
+.B useDirectPresentation \fP (class \fBUseDirectPresentation\fP)
+If ``True'' or ``true'', this resource enables the use of direct
+presentation on unredirected windows. This is not yet complete
+pending the installation of required functionality in the X server.
+.IP
.SH ENVIRONMENT
Several environment variables exist that modify the behavior of the
protocol translator in one way or another. Most of these are used for
diff --git a/atoms.c b/atoms.c
index d7ff2f6..747d218 100644
--- a/atoms.c
+++ b/atoms.c
@@ -109,6 +109,8 @@ static const char *names[] =
"_NET_WM_WINDOW_TYPE_MENU",
"_NET_WM_WINDOW_TYPE_DND",
"CONNECTOR_ID",
+ "_NET_WM_PID",
+ "_NET_WM_PING",
/* These are automatically generated from mime.txt. */
DirectTransferAtomNames
@@ -131,7 +133,7 @@ Atom _NET_WM_OPAQUE_REGION, _XL_BUFFER_RELEASE, _NET_WM_SYNC_REQUEST_COUNTER,
XdndEnter, XdndPosition, XdndStatus, XdndLeave, XdndDrop, XdndFinished,
_NET_WM_FRAME_TIMINGS, _NET_WM_BYPASS_COMPOSITOR, WM_STATE,
_NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_DND,
- CONNECTOR_ID;
+ CONNECTOR_ID, _NET_WM_PID, _NET_WM_PING;
XrmQuark resource_quark, app_quark, QString;
@@ -286,9 +288,11 @@ XLInitAtoms (void)
_NET_WM_WINDOW_TYPE_MENU = atoms[59];
_NET_WM_WINDOW_TYPE_DND = atoms[60];
CONNECTOR_ID = atoms[61];
+ _NET_WM_PID = atoms[62];
+ _NET_WM_PING = atoms[63];
/* This is automatically generated. */
- DirectTransferAtomInit (atoms, 62);
+ DirectTransferAtomInit (atoms, 64);
/* Now, initialize quarks. */
resource_quark = XrmPermStringToQuark (compositor.resource_name);
diff --git a/compositor.h b/compositor.h
index d1c48e2..50ebf71 100644
--- a/compositor.h
+++ b/compositor.h
@@ -135,6 +135,9 @@ extern TimestampDifference CompareTimeWith (Time, Timestamp);
#define TimestampIs(a, op, b) (CompareTimestamps ((a), (b)) == (op))
#define TimeIs(a, op, b) (CompareTimeWith ((a), (b)) == (op))
+extern Bool HandleOneXEventForTime (XEvent *);
+extern void InitTime (void);
+
/* Defined in renderer.c. */
typedef struct _RenderFuncs RenderFuncs;
@@ -276,11 +279,14 @@ enum
{
/* The render target always preserves previously drawn contents;
IOW, target_age always returns 0. */
- NeverAges = 1,
+ NeverAges = 1,
/* Buffers attached can always be immediately released. */
- ImmediateRelease = 1 << 2,
+ ImmediateRelease = 1 << 2,
/* The render target supports explicit synchronization. */
- SupportsExplicitSync = 1 << 3,
+ SupportsExplicitSync = 1 << 3,
+ /* The render target supports direct presentation, so it is okay
+ for surfaces to be unredirected by the compositing manager. */
+ SupportsDirectPresent = 1 << 4,
};
struct _RenderFuncs
@@ -382,7 +388,8 @@ struct _RenderFuncs
void (*cancel_presentation_callback) (PresentCompletionKey);
/* Cancel any presentation that might have happened to the window
- backing the given target. */
+ backing the given target. This must be called before any normal
+ drawing operations. */
void (*cancel_presentation) (RenderTarget);
/* Some flags. NeverAges means targets always preserve contents
@@ -707,6 +714,7 @@ typedef enum _FrameMode FrameMode;
enum _FrameMode
{
ModeStarted,
+ ModeNotifyDisablePresent,
ModeComplete,
ModePresented,
};
@@ -1150,7 +1158,7 @@ extern Atom _NET_WM_OPAQUE_REGION, _XL_BUFFER_RELEASE,
XdndProxy, XdndEnter, XdndPosition, XdndStatus, XdndLeave, XdndDrop,
XdndFinished, _NET_WM_FRAME_TIMINGS, _NET_WM_BYPASS_COMPOSITOR, WM_STATE,
_NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_DND,
- CONNECTOR_ID;
+ CONNECTOR_ID, _NET_WM_PID, _NET_WM_PING;
extern XrmQuark resource_quark, app_quark, QString;
@@ -1164,7 +1172,32 @@ extern void XLInitAtoms (void);
/* Defined in xdg_wm.c. */
+typedef struct _XdgWmBase XdgWmBase;
+typedef struct _XdgRoleList XdgRoleList;
+
+struct _XdgRoleList
+{
+ /* The next and last elements in this list. */
+ XdgRoleList *next, *last;
+
+ /* The role. */
+ Role *role;
+};
+
+struct _XdgWmBase
+{
+ /* The associated struct wl_resource. */
+ struct wl_resource *resource;
+
+ /* The latest ping sent. */
+ uint32_t last_ping;
+
+ /* List of all surfaces attached. */
+ XdgRoleList list;
+};
+
extern void XLInitXdgWM (void);
+extern void XLXdgWmBaseSendPing (XdgWmBase *);
/* Defined in frame_clock.c. */
@@ -1176,7 +1209,7 @@ extern void XLFrameClockAfterFrame (FrameClock *, void (*) (FrameClock *,
void *),
void *);
extern void XLFrameClockHandleFrameEvent (FrameClock *, XEvent *);
-extern void XLFrameClockStartFrame (FrameClock *, Bool);
+extern Bool XLFrameClockStartFrame (FrameClock *, Bool);
extern void XLFrameClockEndFrame (FrameClock *);
extern Bool XLFrameClockIsFrozen (FrameClock *);
extern Bool XLFrameClockCanBatch (FrameClock *);
@@ -1269,6 +1302,9 @@ extern FrameClock *XLXdgRoleGetFrameClock (Role *);
extern XdgRoleImplementation *XLLookUpXdgToplevel (Window);
extern XdgRoleImplementation *XLLookUpXdgPopup (Window);
+extern void XLXdgRoleHandlePing (Role *, XEvent *, void (*) (XEvent *));
+extern void XLXdgRoleReplyPing (Role *);
+
/* Defined in positioner.c. */
typedef struct _Positioner Positioner;
diff --git a/frame_clock.c b/frame_clock.c
index d670e42..7447ead 100644
--- a/frame_clock.c
+++ b/frame_clock.c
@@ -32,10 +32,6 @@ enum
MaxPresentationAge = 150000,
};
-/* Major and minor versions of the XSync extension. */
-
-static int xsync_major, xsync_minor;
-
/* Whether or not the compositor supports frame synchronization. */
static Bool frame_sync_supported;
@@ -361,17 +357,17 @@ PostEndFrame (FrameClock *clock)
timespec);
}
-static void
+static Bool
StartFrame (FrameClock *clock, Bool urgent, Bool predict)
{
if (clock->frozen)
- return;
+ return False;
if (clock->frozen_until_end_frame)
- return;
+ return False;
if (clock->in_frame)
- return;
+ return False;
if (clock->need_configure)
{
@@ -406,7 +402,7 @@ StartFrame (FrameClock *clock, Bool urgent, Bool predict)
counter itself isn't necessary; the values are used as a flag to
tell us whether or not a frame has been completely drawn. */
if (!frame_sync_supported)
- return;
+ return True;
SetSyncCounter (clock->secondary_counter,
clock->next_frame_id);
@@ -415,6 +411,7 @@ StartFrame (FrameClock *clock, Bool urgent, Bool predict)
PostEndFrame (clock);
clock->need_configure = False;
+ return True;
}
static void
@@ -527,10 +524,10 @@ XLFrameClockAfterFrame (FrameClock *clock,
callback->frame = frame_func;
}
-void
+Bool
XLFrameClockStartFrame (FrameClock *clock, Bool urgent)
{
- StartFrame (clock, urgent, True);
+ return StartFrame (clock, urgent, True);
}
void
@@ -871,24 +868,6 @@ XLStopCursorClock (void)
void
XLInitFrameClock (void)
{
- Bool supported;
- int xsync_event_base, xsync_error_base;
-
- supported = XSyncQueryExtension (compositor.display,
- &xsync_event_base,
- &xsync_error_base);
-
- if (supported)
- supported = XSyncInitialize (compositor.display,
- &xsync_major, &xsync_minor);
-
- if (!supported)
- {
- fprintf (stderr, "A compatible version of the Xsync extension"
- " was not found\n");
- exit (1);
- }
-
if (!getenv ("DISABLE_FRAME_SYNCHRONIZATION"))
frame_sync_supported = XLWmSupportsHint (_NET_WM_FRAME_DRAWN);
diff --git a/picture_renderer.c b/picture_renderer.c
index 2c2df0b..646c17c 100644
--- a/picture_renderer.c
+++ b/picture_renderer.c
@@ -146,8 +146,39 @@ enum
NoPresentation = 2,
};
+/* Structure describing presentation callback. The callback is run
+ upon a given presentation being completed. */
+
+struct _PresentCompletionCallback
+{
+ /* The next and last presentation callbacks. */
+ PresentCompletionCallback *next, *last;
+
+ /* The next and last presentation completion callbacks on the
+ target. */
+ PresentCompletionCallback *target_next, *target_last;
+
+ /* The picture target. */
+ PictureTarget *target;
+
+ /* The callback itself. */
+ PresentCompletionFunc callback;
+
+ /* Data the callback will be called with. */
+ void *data;
+
+ /* The function. */
+ PresentCompletionFunc function;
+
+ /* The presentation ID. */
+ uint32_t id;
+};
+
struct _PictureTarget
{
+ /* The last media stamp counter for the presentation window. */
+ uint64_t last_msc;
+
/* The XID of the picture. */
Picture picture;
@@ -178,6 +209,9 @@ struct _PictureTarget
/* Ongoing buffer activity. */
BufferActivityRecord activity;
+
+ /* List of present completion callbacks. */
+ PresentCompletionCallback completion_callbacks;
};
struct _DrmFormatInfo
@@ -229,27 +263,6 @@ struct _DmaBufRecord
int depth;
};
-/* Structure describing presentation callback. The callback is run
- upon a given presentation being completed. */
-
-struct _PresentCompletionCallback
-{
- /* The next and last presentation callbacks. */
- PresentCompletionCallback *next, *last;
-
- /* The callback itself. */
- PresentCompletionFunc callback;
-
- /* Data the callback will be called with. */
- void *data;
-
- /* The function. */
- PresentCompletionFunc function;
-
- /* The presentation ID. */
- uint32_t id;
-};
-
/* Hash table mapping between presentation windows and targets. */
static XLAssocTable *xid_table;
@@ -369,6 +382,9 @@ static BufferActivityRecord all_activity;
/* List of all presentations that have not yet been completed. */
static PresentCompletionCallback all_completion_callbacks;
+/* Whether or not direct presentation should be used. */
+static Bool use_direct_presentation;
+
/* XRender, DRI3 and XPresent-based renderer. A RenderTarget is just
a Picture. Here is a rough explanation of how the buffer release
machinery works.
@@ -657,6 +673,45 @@ PickVisual (int *depth)
return NULL;
}
+static void
+InitSynchronizedPresentation (void)
+{
+ XrmDatabase rdb;
+ XrmName namelist[3];
+ XrmClass classlist[3];
+ XrmValue value;
+ XrmRepresentation type;
+
+ rdb = XrmGetDatabase (compositor.display);
+
+ if (!rdb)
+ return;
+
+ namelist[1] = XrmStringToQuark ("useDirectPresentation");
+ namelist[0] = app_quark;
+ namelist[2] = NULLQUARK;
+
+ classlist[1] = XrmStringToQuark ("UseDirectPresentation");
+ classlist[0] = resource_quark;
+ classlist[2] = NULLQUARK;
+
+ /* Enable the use of direct presentation if
+ *.UseDirectPresentation.*.useDirectPresentation is true. This is
+ still incomplete, as the features necessary for it to play nice
+ with frame synchronization have not yet been implemented in the X
+ server. */
+
+ if (XrmQGetResource (rdb, namelist, classlist,
+ &type, &value)
+ && type == QString
+ && (!strcmp (value.addr, "True")
+ || !strcmp (value.addr, "true")))
+ use_direct_presentation = True;
+}
+
+/* Forward declaration. */
+static void AddRenderFlag (int);
+
static Bool
InitRenderFuncs (void)
{
@@ -678,6 +733,13 @@ InitRenderFuncs (void)
return False;
}
+ /* Figure out whether or not the user wants synchronized
+ presentation. */
+ InitSynchronizedPresentation ();
+
+ if (use_direct_presentation)
+ AddRenderFlag (SupportsDirectPresent);
+
/* Create an unmapped, InputOnly window, that is used to receive
roundtrip events. */
attrs.override_redirect = True;
@@ -727,6 +789,10 @@ TargetFromDrawable (Drawable drawable, Window window,
/* And the event mask. */
target->standard_event_mask = standard_event_mask;
+ /* Initialize the list of present completion callbacks. */
+ target->completion_callbacks.target_next = &target->completion_callbacks;
+ target->completion_callbacks.target_last = &target->completion_callbacks;
+
return (RenderTarget) (void *) target;
}
@@ -846,6 +912,7 @@ DestroyRenderTarget (RenderTarget target)
PresentRecord *record, *last;
BufferActivityRecord *activity_record, *activity_last;
IdleCallback *idle, *idle_last;
+ PresentCompletionCallback *callback, *callback_last;
pict_target = target.pointer;
@@ -892,6 +959,20 @@ DestroyRenderTarget (RenderTarget target)
XLFree (idle_last);
}
+ /* Detach the target from each present completion callback. */
+ callback = pict_target->completion_callbacks.target_next;
+ while (callback != &pict_target->completion_callbacks)
+ {
+ callback_last = callback;
+ callback = callback->target_next;
+
+ /* Clear callback->target and the target_next/target_last
+ fields. */
+ callback_last->target = NULL;
+ callback_last->target_next = NULL;
+ callback_last->target_last = NULL;
+ }
+
XRenderFreePicture (compositor.display,
pict_target->picture);
XFree (pict_target);
@@ -1203,6 +1284,7 @@ PresentToWindow (RenderTarget target, RenderBuffer source,
XserverRegion region;
PresentRecord *record;
PresentCompletionCallback *callback_rec;
+ uint64_t target_msc;
/* Present SOURCE onto TARGET. Return False if the presentation is
not supported. */
@@ -1236,12 +1318,27 @@ PresentToWindow (RenderTarget target, RenderBuffer source,
else
region = None;
- /* Present the pixmap now from the damage, immediately. */
- XPresentPixmap (compositor.display,
- pict_target->presentation_window, buffer->pixmap,
- ++present_serial, None, region, 0, 0, None,
- None, None, PresentOptionAsync, 0, 0, 0,
- NULL, 0);
+ if (use_direct_presentation)
+ {
+ /* Determine the target msc. Force the next frame after this
+ one by increasing last_msc. */
+ target_msc = (pict_target->last_msc ? ++pict_target->last_msc : 0);
+
+ /* Present the pixmap now from the damage; it will complete upon
+ the next vblank if direct presentation is enabled, or
+ immediately if it is not. */
+ XPresentPixmap (compositor.display, pict_target->presentation_window,
+ buffer->pixmap, ++present_serial, None, region, 0, 0,
+ None, None, None, PresentOptionNone, target_msc, 1, 0,
+ NULL, 0);
+ }
+ else
+ /* Direct presentation is off; present the pixmap asynchronously
+ at an msc of 0. */
+ XPresentPixmap (compositor.display, pict_target->presentation_window,
+ buffer->pixmap, ++present_serial, None, region, 0, 0,
+ None, None, None, PresentOptionAsync, 0, 0, 0, NULL,
+ 0);
if (region)
XFixesDestroyRegion (compositor.display, region);
@@ -1270,6 +1367,11 @@ PresentToWindow (RenderTarget target, RenderBuffer source,
callback_rec->function = callback;
callback_rec->data = data;
callback_rec->id = present_serial;
+ callback_rec->target_next = pict_target->completion_callbacks.target_next;
+ callback_rec->target_last = &pict_target->completion_callbacks;
+ pict_target->completion_callbacks.target_next->target_last = callback_rec;
+ pict_target->completion_callbacks.target_next = callback_rec;
+ callback_rec->target = pict_target;
return callback_rec;
}
@@ -1285,6 +1387,12 @@ CancelPresentationCallback (PresentCompletionKey key)
callback->next->last = callback->last;
callback->last->next = callback->next;
+ if (callback->target_last)
+ {
+ callback->target_last->target_next = callback->target_next;
+ callback->target_next->target_last = callback->target_last;
+ }
+
XLFree (callback);
}
@@ -1352,6 +1460,12 @@ static RenderFuncs picture_render_funcs =
.flags = NeverAges,
};
+static void
+AddRenderFlag (int flag)
+{
+ picture_render_funcs.flags |= flag;
+}
+
static DrmFormatInfo *
FindFormatMatching (XRenderPictFormat *format)
{
@@ -2574,6 +2688,9 @@ static Bool
HandlePresentCompleteNotify (XPresentCompleteNotifyEvent *complete)
{
PresentCompletionCallback *callback, *last;
+#ifdef DEBUG_PRESENT_TIME
+ static uint64_t last_ust;
+#endif
callback = all_completion_callbacks.next;
while (callback != &all_completion_callbacks)
@@ -2588,6 +2705,26 @@ HandlePresentCompleteNotify (XPresentCompleteNotifyEvent *complete)
last->function (last->data);
last->next->last = last->last;
last->last->next = last->next;
+
+ if (last->target && last->target->last_msc < complete->msc)
+ /* Set the last known msc of the target. */
+ last->target->last_msc = complete->msc;
+
+ if (last->target_next)
+ {
+ /* Unlink the callback from the target as well. */
+ last->target_next->target_last = last->target_last;
+ last->target_last->target_next = last->target_next;
+ }
+
+#ifdef DEBUG_PRESENT_TIME
+ fprintf (stderr, "Time taken: %lu us (%g ms) (= 1/%g s)\n",
+ complete->ust - last_ust,
+ (complete->ust - last_ust) / 1000.0,
+ 1000.0 / ((complete->ust - last_ust) / 1000.0));
+ last_ust = complete->ust;
+#endif
+
XLFree (last);
return True;
diff --git a/run.c b/run.c
index 5735a30..7279200 100644
--- a/run.c
+++ b/run.c
@@ -167,6 +167,9 @@ HandleOneXEvent (XEvent *event)
if (XLHandleOneXEventForXSettings (event))
return;
+
+ if (HandleOneXEventForTime (event))
+ return;
}
static void
diff --git a/subcompositor.c b/subcompositor.c
index 52eee84..cc73a4a 100644
--- a/subcompositor.c
+++ b/subcompositor.c
@@ -2577,7 +2577,16 @@ SubcompositorUpdate (Subcompositor *subcompositor)
}
}
else
- RenderCancelPresentation (subcompositor->target);
+ {
+ RenderCancelPresentation (subcompositor->target);
+
+ /* Tell the surface to make the compositor redirect the
+ window again. */
+ if (subcompositor->note_frame)
+ subcompositor->note_frame (ModeNotifyDisablePresent,
+ subcompositor->frame_counter,
+ subcompositor->note_frame_data);
+ }
/* The first view with an attached buffer should be drawn
with PictOpSrc so that transparency is applied correctly,
diff --git a/time.c b/time.c
index 3d797f1..0703c6d 100644
--- a/time.c
+++ b/time.c
@@ -17,14 +17,31 @@ for more details.
You should have received a copy of the GNU General Public License
along with 12to11. If not, see . */
+#include
+#include
+
#include "compositor.h"
+#include
+
/* The latest known time. */
static Timestamp current_time;
+/* Alarms used to keep track of the server time. */
+static XSyncAlarm alarm_a, alarm_b;
+
+/* The server time counter. */
+static XSyncCounter counter;
+
+/* The event and error bases of the synchronization extension. */
+static int xsync_event_base, xsync_error_base;
+
/* Half a month; used as a threshold in various places. */
#define HalfMonth (1U << 31)
+/* The max value of Time. */
+#define MaxTime 0xffffffff
+
/* The protocol translator can run for more than 48 days. That makes
normal X timestamp handling unsafe, as the X server wraps around
timestamps after that much time. This function creates a Timestamp
@@ -92,3 +109,223 @@ CompareTimeWith (Time a, Timestamp b)
{
return CompareTimestamps (TimestampFromClientTime (a), b);
}
+
+
+/* Timestamp tracking. The code below treats INT64 as unsigned, since
+ that won't overflow until long in the far future. */
+
+static XSyncCounter
+FindSystemCounter (const char *name)
+{
+ XSyncSystemCounter *system_counters;
+ int i, num_counters;
+ XSyncCounter counter;
+
+ num_counters = 0;
+ system_counters = XSyncListSystemCounters (compositor.display,
+ &num_counters);
+ counter = None;
+
+ for (i = 0; i < num_counters; ++i)
+ {
+ if (!strcmp (system_counters[i].name, name))
+ {
+ counter = system_counters[i].counter;
+ break;
+ }
+
+ /* Continue looking at the next counter. */
+ }
+
+ if (system_counters)
+ XSyncFreeSystemCounterList (system_counters);
+
+ return counter;
+}
+
+static uint64_t
+ValueToScalar (XSyncValue value)
+{
+ uint64_t low, high;
+
+ low = XSyncValueLow32 (value);
+ high = XSyncValueHigh32 (value);
+
+ return low | (high << 32);
+}
+
+static void
+ScalarToValue (uint64_t scalar, XSyncValue *value)
+{
+ XSyncIntsToValue (value, scalar & 0xffffffff, scalar >> 32);
+}
+
+static void
+StartAlarms (XSyncCounter counter, XSyncValue current_value)
+{
+ uint64_t scalar_value, target;
+ XSyncTrigger trigger;
+ XSyncAlarmAttributes attributes;
+ unsigned long value_mask;
+
+ scalar_value = ValueToScalar (current_value);
+
+ /* Delete existing alarms. */
+
+ if (alarm_a)
+ XSyncDestroyAlarm (compositor.display, alarm_a);
+
+ if (alarm_b)
+ XSyncDestroyAlarm (compositor.display, alarm_b);
+
+ value_mask = (XSyncCACounter | XSyncCATestType
+ | XSyncCAValue | XSyncCAEvents);
+
+ /* Start the first kind of alarm. This alarm assumes that the
+ counter does not wrap around along with the server time.
+
+ The protocol allows for more kinds of server behavior, but all
+ servers either implement the counter as one that wraps around
+ after Time ends, as defined here... */
+
+ if (scalar_value >= HalfMonth)
+ {
+ /* value exceeds HalfMonth. Wait for value to overflow back to
+ 0. */
+ trigger.counter = counter;
+ trigger.test_type = XSyncNegativeComparison;
+ trigger.wait_value = current_value;
+
+ /* Set the trigger and ask for events. */
+ attributes.trigger = trigger;
+ attributes.events = True;
+
+ /* Create the alarm. */
+ alarm_a = XSyncCreateAlarm (compositor.display,
+ value_mask, &attributes);
+ }
+ else
+ {
+ /* value is not yet HalfMonth. Wait for value to exceed
+ HalfMonth - 1. */
+ trigger.counter = counter;
+ trigger.test_type = XSyncPositiveComparison;
+ ScalarToValue (HalfMonth, &trigger.wait_value);
+
+ /* Set the trigger and ask for events. */
+ attributes.trigger = trigger;
+ attributes.events = True;
+
+ /* Create the alarm. */
+ alarm_a = XSyncCreateAlarm (compositor.display,
+ value_mask, &attributes);
+ }
+
+ /* ...or the counter increases indefinitely, with its lower 32 bits
+ representing the server time, which this counter takes into
+ account. */
+ if ((scalar_value & MaxTime) >= HalfMonth)
+ {
+ /* The time exceeds HalfMonth. Wait for the value to overflow
+ time again. */
+ target = (scalar_value & ~MaxTime) + MaxTime + 1;
+ trigger.counter = counter;
+ trigger.test_type = XSyncPositiveComparison;
+ ScalarToValue (target, &trigger.wait_value);
+
+ /* Set the trigger and ask for events. */
+ attributes.trigger = trigger;
+ attributes.events = True;
+
+ /* Create the alarm. */
+ alarm_b = XSyncCreateAlarm (compositor.display,
+ value_mask, &attributes);
+ }
+ else
+ {
+ /* The time is not yet HalfMonth. Wait for the time to exceed
+ HalfMonth - 1. */
+ target = (scalar_value & ~MaxTime) + HalfMonth;
+ trigger.counter = counter;
+ trigger.test_type = XSyncPositiveComparison;
+ ScalarToValue (target, &trigger.wait_value);
+
+ /* Set the trigger and ask for events. */
+ attributes.trigger = trigger;
+ attributes.events = True;
+
+ /* Create the alarm. */
+ alarm_b = XSyncCreateAlarm (compositor.display,
+ value_mask, &attributes);
+ }
+
+ /* Now wait for alarm notifications to arrive. */
+}
+
+static Bool
+HandleAlarmNotify (XSyncAlarmNotifyEvent *notify)
+{
+ if (notify->alarm != alarm_a
+ || notify->alarm != alarm_b)
+ /* We are not interested in this outdated or irrelevant alarm. */
+ return False;
+
+ /* First, synchronize our local time with the server time in the
+ notification. */
+ TimestampFromServerTime (notify->time);
+
+ /* Next, recreate the alarms for the new time. */
+ StartAlarms (counter, notify->counter_value);
+ return True;
+}
+
+Bool
+HandleOneXEventForTime (XEvent *event)
+{
+ if (event->type == xsync_event_base + XSyncAlarmNotify)
+ return HandleAlarmNotify ((XSyncAlarmNotifyEvent *) event);
+
+ return False;
+}
+
+void
+InitTime (void)
+{
+ XSyncValue value;
+ Bool supported;
+ int xsync_major, xsync_minor;
+
+ supported = XSyncQueryExtension (compositor.display,
+ &xsync_event_base,
+ &xsync_error_base);
+
+ if (supported)
+ supported = XSyncInitialize (compositor.display,
+ &xsync_major, &xsync_minor);
+
+ if (!supported)
+ {
+ fprintf (stderr, "A compatible version of the synchronization"
+ " extension was not found\n");
+ exit (1);
+ }
+
+ /* Initialize server timestamp tracking. In order for server time
+ accounting to be absolutely reliable, we must receive an event
+ detailing each change every time it reaches HalfMonth and 0. Set
+ up multiple counter alarms to do that. */
+
+ counter = FindSystemCounter ("SERVERTIME");
+
+ if (!counter)
+ fprintf (stderr, "Server missing required system counter SERVERTIME\n");
+ else
+ {
+ /* Now, obtain the current value of the counter. This cannot
+ fail without calling the error (or IO error) handler. */
+ XSyncQueryCounter (compositor.display, counter, &value);
+
+ /* Start the alarms. */
+ StartAlarms (counter, value);
+ }
+}
diff --git a/xdg_surface.c b/xdg_surface.c
index 2fab550..b0be897 100644
--- a/xdg_surface.c
+++ b/xdg_surface.c
@@ -45,12 +45,14 @@ enum
StateDirtyFrameExtents = (1 << 7),
StateTemporaryBounds = (1 << 8),
StateFrameStarted = (1 << 9),
+ StateAllowUnredirection = (1 << 10),
};
typedef struct _XdgRole XdgRole;
typedef struct _XdgState XdgState;
typedef struct _ReleaseLaterRecord ReleaseLaterRecord;
typedef struct _ReconstrainCallback ReconstrainCallback;
+typedef struct _PingEvent PingEvent;
/* Association between XIDs and surfaces. */
@@ -90,6 +92,12 @@ struct _XdgRole
/* The role object. */
Role role;
+ /* The link to the wm_base's list of surfaces. */
+ XdgRoleList link;
+
+ /* The attached XdgWmBase. Not valid if link->next is NULL. */
+ XdgWmBase *wm_base;
+
/* The window backing this role. */
Window window;
@@ -105,6 +113,9 @@ struct _XdgRole
/* The pending frame ID. */
uint64_t pending_frame;
+ /* List of pending ping events. */
+ XLList *ping_events;
+
/* Number of references to this role. Used when the client
terminates and the Wayland library destroys objects out of
order. */
@@ -174,6 +185,15 @@ struct _ReleaseLaterRecord
ReleaseLaterRecord *next, *last;
};
+struct _PingEvent
+{
+ /* Function called to reply to this event. */
+ void (*reply_func) (XEvent *);
+
+ /* The event. */
+ XEvent event;
+};
+
/* Event base of the XShape extension. */
int shape_base;
@@ -741,9 +761,18 @@ ReleaseBacking (XdgRole *role)
if (--role->refcount)
return;
- /* Sync, and then release all buffers pending release. The sync is
- necessary because the X server does not perform operations
- immediately after the Xlib function is called. */
+ /* Unlink the role if it is still linked. */
+
+ if (role->link.next)
+ {
+ role->link.next->last = role->link.last;
+ role->link.last->next = role->link.next;
+ }
+
+ /* Release all buffers pending release. The sync is necessary
+ because the X server does not perform operations immediately
+ after the Xlib function is called. */
+
XSync (compositor.display, False);
FreeRecords (role->release_records);
@@ -756,6 +785,9 @@ ReleaseBacking (XdgRole *role)
RenderDestroyRenderTarget (role->target);
XDestroyWindow (compositor.display, role->window);
+ /* Free associated ping events. */
+ XLListFree (role->ping_events, XLFree);
+
/* And the association. */
XLDeleteAssoc (surfaces, role->window);
@@ -1146,6 +1178,24 @@ NoteBounds (void *data, int min_x, int min_y,
RunReconstrainCallbacks (role);
}
+static void
+WriteRedirectProperty (XdgRole *role)
+{
+ unsigned long bypass_compositor;
+
+ if (role->state & StateAllowUnredirection)
+ /* The subcompositor determined that the window should be
+ uncomposited to allow for direct buffer flipping. */
+ bypass_compositor = 0;
+ else
+ bypass_compositor = 2;
+
+ XChangeProperty (compositor.display, role->window,
+ _NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL,
+ 32, PropModeReplace,
+ (unsigned char *) &bypass_compositor, 1);
+}
+
static void
NoteFrame (FrameMode mode, uint64_t id, void *data)
{
@@ -1159,16 +1209,25 @@ NoteFrame (FrameMode mode, uint64_t id, void *data)
/* Record this frame counter as the pending frame. */
role->pending_frame = id;
- if (!(role->state & StateFrameStarted))
- {
- role->state |= StateFrameStarted;
- XLFrameClockStartFrame (role->clock, False);
- }
+ if (!(role->state & StateFrameStarted)
+ && XLFrameClockStartFrame (role->clock, False))
+ role->state |= StateFrameStarted;
/* Also run role "commit inside frame" hook. */
if (role->impl && role->impl->funcs.commit_inside_frame)
role->impl->funcs.commit_inside_frame (&role->role,
role->impl);
+ break;
+
+ case ModeNotifyDisablePresent:
+ /* The subcompositor will draw to the frame directly, so make
+ the compositing manager redirect the frame again. */
+
+ if (role->state & StateAllowUnredirection)
+ {
+ role->state &= ~StateAllowUnredirection;
+ WriteRedirectProperty (role);
+ }
break;
@@ -1199,6 +1258,21 @@ NoteFrame (FrameMode mode, uint64_t id, void *data)
being performed. */
|| !IsRoleMapped (role))
RunFrameCallbacksConditionally (role);
+
+ if (mode == ModePresented
+ && renderer_flags & SupportsDirectPresent)
+ {
+ /* Since a presentation was successful, assume future
+ frames will be presented as well. In that case, let
+ the compositing manager unredirect the window, so
+ buffers can be directly flipped to the screen. */
+
+ if (!(role->state & StateAllowUnredirection))
+ {
+ role->state |= StateAllowUnredirection;
+ WriteRedirectProperty (role);
+ }
+ }
}
}
}
@@ -1315,18 +1389,6 @@ NoteDesyncChild (Surface *surface, Role *role)
XLFrameClockSetPredictRefresh (xdg_role->clock);
}
-static void
-WriteRedirectProperty (XdgRole *role)
-{
- unsigned long bypass_compositor;
-
- bypass_compositor = 2;
- XChangeProperty (compositor.display, role->window,
- _NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL,
- 32, PropModeReplace,
- (unsigned char *) &bypass_compositor, 1);
-}
-
static void
HandleFreeze (void *data)
{
@@ -1370,8 +1432,10 @@ XLGetXdgSurface (struct wl_client *client, struct wl_resource *resource,
XSetWindowAttributes attrs;
unsigned int flags;
Surface *surface;
+ XdgWmBase *wm_base;
surface = wl_resource_get_user_data (surface_resource);
+ wm_base = wl_resource_get_user_data (resource);
if (surface->role || (surface->role_type != AnythingType
&& surface->role_type != XdgType))
@@ -1419,6 +1483,14 @@ XLGetXdgSurface (struct wl_client *client, struct wl_resource *resource,
wl_resource_set_implementation (role->role.resource, &xdg_surface_impl,
role, HandleResourceDestroy);
+ /* Link the role onto the wm base. */
+ role->link.next = wm_base->list.next;
+ role->link.last = &wm_base->list;
+ role->link.role = &role->role;
+ wm_base->list.next->last = &role->link;
+ wm_base->list.next = &role->link;
+ role->wm_base = wm_base;
+
/* Add a reference to this role struct since a wl_resource now
refers to it. */
role->refcount++;
@@ -1965,3 +2037,52 @@ XLXdgRoleNoteRejectedConfigure (Role *role)
XLFrameClockUnfreeze (xdg_role->clock);
}
}
+
+void
+XLXdgRoleHandlePing (Role *role, XEvent *event,
+ void (*reply_func) (XEvent *))
+{
+ XdgRole *xdg_role;
+ PingEvent *record;
+
+ xdg_role = XdgRoleFromRole (role);
+
+ /* If the role's xdg_wm_base is detached, just reply to the ping
+ message. */
+ if (!xdg_role->link.next)
+ reply_func (event);
+ else
+ {
+ /* Otherwise, save the event and ping the client. Then, send
+ replies once the client replies. */
+ record = XLMalloc (sizeof *record);
+ record->event = *event;
+ record->reply_func = reply_func;
+ xdg_role->ping_events = XLListPrepend (xdg_role->ping_events,
+ record);
+ XLXdgWmBaseSendPing (xdg_role->wm_base);
+ }
+}
+
+static void
+ReplyPingEvent (void *data)
+{
+ PingEvent *event;
+
+ event = data;
+ event->reply_func (&event->event);
+ XLFree (event);
+}
+
+void
+XLXdgRoleReplyPing (Role *role)
+{
+ XdgRole *xdg_role;
+
+ xdg_role = XdgRoleFromRole (role);
+
+ /* Free the ping event list, calling the reply functions along the
+ way. */
+ XLListFree (xdg_role->ping_events, ReplyPingEvent);
+ xdg_role->ping_events = NULL;
+}
diff --git a/xdg_toplevel.c b/xdg_toplevel.c
index 18948f5..25f40ed 100644
--- a/xdg_toplevel.c
+++ b/xdg_toplevel.c
@@ -17,6 +17,10 @@ for more details.
You should have received a copy of the GNU General Public License
along with 12to11. If not, see . */
+#include
+#include
+#include
+
#include
#include
#include
@@ -934,6 +938,99 @@ HandleWindowGeometryChange (XdgToplevel *toplevel)
hints);
}
+static Bool
+GetClientMachine (XTextProperty *client_machine)
+{
+ struct addrinfo template, *result;
+ int rc;
+ long host_name_max;
+ char *hostname;
+
+ host_name_max = sysconf (_SC_HOST_NAME_MAX);
+
+ if (host_name_max == -1)
+ /* The maximum host name is indeterminate. Use a sane limit like
+ _POSIX_HOST_NAME_MAX. */
+ host_name_max = _POSIX_HOST_NAME_MAX + 1;
+ else
+ host_name_max += 1;
+
+ /* Allocate the buffer holding the hostname. */
+ hostname = alloca (host_name_max + 1);
+
+ /* Get the hostname. */
+ if (gethostname (hostname, host_name_max + 1))
+ /* Obtaining the hostname failed. */
+ return False;
+
+ /* NULL-terminate the hostname. */
+ hostname[host_name_max] = '\0';
+
+ /* Now find the fully-qualified domain name. */
+ memset (&template, 0, sizeof template);
+ template.ai_family = AF_UNSPEC;
+ template.ai_socktype = SOCK_STREAM;
+ template.ai_flags = AI_CANONNAME;
+
+ rc = getaddrinfo (hostname, NULL, &template, &result);
+
+ if (rc || !result)
+ return False;
+
+ /* Copy it to the client machine text property. */
+ client_machine->value
+ = (unsigned char *) XLStrdup (result->ai_canonname);
+ client_machine->encoding = XA_STRING;
+ client_machine->nitems = strlen (result->ai_canonname);
+ client_machine->format = 8;
+
+ /* Free the result. */
+ freeaddrinfo (result);
+ return True;
+}
+
+static void
+WriteCredentialProperties (XdgToplevel *toplevel)
+{
+ struct wl_client *client;
+ pid_t pid;
+ Window window;
+ unsigned long process_id;
+ XTextProperty client_machine;
+
+ /* Write credential properties such as _NET_WM_PID and
+ WM_CLIENT_MACHINE. The PID is obtained from the Wayland
+ connection. */
+
+ client = wl_resource_get_client (toplevel->resource);
+
+ /* Get the credentials of the client. If the Wayland library cannot
+ obtain those credentials, the client is simply disallowed from
+ connecting to this server. */
+ wl_client_get_credentials (client, &pid, NULL, NULL);
+
+ /* Write the _NET_WM_PID property. */
+ window = XLWindowFromXdgRole (toplevel->role);
+ process_id = pid;
+ XChangeProperty (compositor.display, window, _NET_WM_PID,
+ XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char *) &process_id, 1);
+
+ /* First, let Xlib write WM_CLIENT_MACHINE and WM_LOCALE_NAME. */
+ XSetWMProperties (compositor.display, window, NULL, NULL,
+ NULL, 0, NULL, NULL, NULL);
+
+ /* Next, write the fully-qualified client machine if it can be
+ obtained. */
+ if (GetClientMachine (&client_machine))
+ {
+ XSetWMClientMachine (compositor.display, window,
+ &client_machine);
+ XLFree (client_machine.value);
+ return;
+ }
+}
+
static void
Attach (Role *role, XdgRoleImplementation *impl)
{
@@ -952,6 +1049,10 @@ Attach (Role *role, XdgRoleImplementation *impl)
protocols[nproto++] = WM_DELETE_WINDOW;
+ /* _NET_WM_PING should be disabled when the window manager kills
+ clients using XKillClient. */
+ protocols[nproto++] = _NET_WM_PING;
+
if (XLFrameClockSyncSupported ())
protocols[nproto++] = _NET_WM_SYNC_REQUEST;
@@ -960,6 +1061,10 @@ Attach (Role *role, XdgRoleImplementation *impl)
WriteHints (toplevel);
+ /* Write credential properties: _NET_WM_PID, WM_CLIENT_MACHINE,
+ etc. */
+ WriteCredentialProperties (toplevel);
+
/* This tells the window manager not to override size choices made
by the client. */
toplevel->size_hints.flags |= PSize;
@@ -2028,6 +2133,21 @@ SetMinimized (struct wl_client *client, struct wl_resource *resource)
DefaultScreen (compositor.display));
}
+static void
+ReplyToPing (XEvent *event)
+{
+ XEvent copy;
+
+ copy = *event;
+
+ /* Reply to the ping message by sending it back to the window
+ manager. */
+ copy.xclient.window = DefaultRootWindow (compositor.display);
+ XSendEvent (compositor.display, copy.xclient.window,
+ False, (SubstructureRedirectMask
+ | SubstructureNotifyMask), ©);
+}
+
static const struct xdg_toplevel_interface xdg_toplevel_impl =
{
.destroy = Destroy,
@@ -2133,6 +2253,11 @@ XLHandleXEventForXdgToplevels (XEvent *event)
return True;
}
+ else if (event->xclient.data.l[0] == _NET_WM_PING)
+ /* _NET_WM_PING arrived. Record the event and send ping
+ to the client. toplevel->role should be non-NULL
+ here. */
+ XLXdgRoleHandlePing (toplevel->role, event, ReplyToPing);
return False;
}
diff --git a/xdg_wm.c b/xdg_wm.c
index 6e3cb0e..75983f9 100644
--- a/xdg_wm.c
+++ b/xdg_wm.c
@@ -17,15 +17,14 @@ for more details.
You should have received a copy of the GNU General Public License
along with 12to11. If not, see . */
+#include
+
#include "compositor.h"
#include "xdg-shell.h"
/* The xdg_wm_base global. */
static struct wl_global *global_xdg_wm_base;
-/* All xdg_wm_base resources. */
-static XLList *all_xdg_wm_bases;
-
static void
CreatePositioner (struct wl_client *client, struct wl_resource *resource,
uint32_t id)
@@ -44,7 +43,28 @@ static void
Pong (struct wl_client *client, struct wl_resource *resource,
uint32_t serial)
{
- /* TODO... */
+ XdgWmBase *wm_base;
+ XdgRoleList *role;
+
+ /* Ping-pong implementation. Every time a ping request is received
+ from the window manager, it is linked onto the list of all such
+ requests on the toplevel. Then, ping is sent with a serial.
+ Once the pong with the latest serial arrives from the client,
+ pending requests are sent back to the window manager on all
+ windows. */
+ wm_base = wl_resource_get_user_data (resource);
+
+ if (serial == wm_base->last_ping)
+ {
+ /* Reply to the ping events sent to each surface created with
+ this wm_base. */
+ role = wm_base->list.next;
+ while (role != &wm_base->list)
+ {
+ XLXdgRoleReplyPing (role->role);
+ role = role->next;
+ }
+ }
}
static void
@@ -64,27 +84,60 @@ static const struct xdg_wm_base_interface xdg_wm_base_impl =
static void
HandleResourceDestroy (struct wl_resource *resource)
{
- all_xdg_wm_bases = XLListRemove (all_xdg_wm_bases, resource);
+ XdgWmBase *wm_base;
+ XdgRoleList *role, *last;
+
+ wm_base = wl_resource_get_user_data (resource);
+
+ /* Detach each surface. */
+ role = wm_base->list.next;
+ while (role != &wm_base->list)
+ {
+ last = role;
+ role = role->next;
+
+ /* Complete all ping events. */
+ XLXdgRoleReplyPing (last->role);
+
+ /* Tell the surface to not bother unlinking itself. */
+ last->next = NULL;
+ last->last = NULL;
+ last->role = NULL;
+ }
+
+ XLFree (wm_base);
}
static void
HandleBind (struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
- struct wl_resource *resource;
+ XdgWmBase *wm_base;
- resource = wl_resource_create (client, &xdg_wm_base_interface,
- version, id);
+ wm_base = XLSafeMalloc (sizeof *wm_base);
- if (!resource)
+ if (!wm_base)
{
wl_client_post_no_memory (client);
return;
}
- wl_resource_set_implementation (resource, &xdg_wm_base_impl,
- NULL, HandleResourceDestroy);
- all_xdg_wm_bases = XLListPrepend (all_xdg_wm_bases, resource);
+ memset (wm_base, 0, sizeof *wm_base);
+ wm_base->resource
+ = wl_resource_create (client, &xdg_wm_base_interface,
+ version, id);
+
+ if (!wm_base->resource)
+ {
+ XLFree (wm_base);
+ wl_client_post_no_memory (client);
+ return;
+ }
+
+ wl_resource_set_implementation (wm_base->resource, &xdg_wm_base_impl,
+ wm_base, HandleResourceDestroy);
+ wm_base->list.next = &wm_base->list;
+ wm_base->list.last = &wm_base->list;
}
void
@@ -95,3 +148,10 @@ XLInitXdgWM (void)
&xdg_wm_base_interface,
5, NULL, HandleBind);
}
+
+void
+XLXdgWmBaseSendPing (XdgWmBase *wm_base)
+{
+ xdg_wm_base_send_ping (wm_base->resource,
+ ++wm_base->last_ping);
+}