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); +}