diff --git a/12to11.man b/12to11.man
index e651a01..4bc7a68 100644
--- a/12to11.man
+++ b/12to11.man
@@ -9,6 +9,83 @@ starts a Wayland compositor on the next available socket;
Wayland programs will then be displayed through the X server.
.SH OPTIONS
None.
+.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
+debugging, but some may be relevant to users as well.
+.PP
+The
+.B USE_BUILTIN_RESIZE
+environment variable, if set, forces the use of the built-in
+drag-to-resize support provided by the protocol translator, even if
+the X window manager provides its own.
+.PP
+The
+.B DEBUG_REFRESH_PREDICTION
+environment variable, if set, forces the frame clock to predict the
+presentation deadline of the X compositing manager. This is used to
+debug code that is otherwise not run without subsurfaces being
+present.
+.PP
+The
+.B DISABLE_FRAME_SYNCHRONIZATION
+environment variable, if set, disables frame synchronization with the
+X compositing manager. Setting this variable is probably not a good
+idea.
+.PP
+The
+.B SYNCHRONIZE
+environment variable, if set, causes the X library to check for errors
+immediately after issuing a request. The resulting backtraces from
+the error handler then reflect the protocol request that actually
+caused the error, instead of some unpredictable request in the future.
+.PP
+The
+.B APPLY_STATE_WORKAROUND
+environment variable, if set, causes the protocol translator to handle
+toplevel window state changes differently. If Wayland programs do not
+make the transition between fullscreen and maximized states correctly,
+try setting this variable.
+.PP
+The
+.B GLOBAL_SCALE
+environment variable, if set to an integer, overrides the global
+program scale factor normally provided by the
+.I Gdk/WindowScalingFactor
+X setting.
+.PP
+The
+.B OUTPUT_SCALE
+environment variable, if set to an integer, overrides the output scale
+factor that is otherwise set to the global program scale factor.
+Setting this option is only useful to debug Wayland program
+downscaling.
+.PP
+The
+.B DIRECT_STATE_CHANGES
+variable, if set, forces ConfigureNotify events from the window
+manager to be handled directly, without waiting some time for a
+corresponding _NET_WM_STATE event to arrive. This is only
+useful for debugging the window resizing logic.
+.SH BUGS
+There is a hard to catch bug where Wayland programs leaving the
+fullscreen or maximized state may abruptly return to their maximized
+size. Setting the
+.B APPLY_STATE_WORKAROUND
+environment variable may help.
+.PP
+Mozilla Firefox also does not work correctly. Resizing the Firefox
+window leads to screen tearing, and Firefox often resizes its toplevel
+windows outside their normal boundaries, causing invalid screen
+contents to be displayed over other applications.
+.PP
+Using this protocol translator under a window manager that does not at
+least support the
+.B _NET_WM_SYNC_REQUEST
+and
+.B _NET_WM_STATE
+window manager hints will result in Wayland programs running
+incorrectly.
.SH "SEE ALSO"
X(1), Xorg(1)
.SH AUTHOR
diff --git a/Imakefile b/Imakefile
index 8c89e6a..7315a2c 100644
--- a/Imakefile
+++ b/Imakefile
@@ -68,7 +68,8 @@ transfer_atoms.h: short_types.txt mime0.awk mime1.awk mime2.awk mime3.awk \
awk -f mime3.awk short_types.txt >> $@
awk -f mime4.awk short_types.txt >> $@
-$(OBJS): transfer_atoms.h
+$(OBJS): transfer_atoms.h primary-selection-unstable-v1.h \
+ linux-dmabuf-unstable-v1.h xdg-shell.h
linux-dmabuf-unstable-v1.h: linux-dmabuf-unstable-v1.xml
$(WAYLAND_SCANNER) server-header $< $@
diff --git a/README b/README
index b4ba266..a70619a 100644
--- a/README
+++ b/README
@@ -2,8 +2,8 @@ This is a tool for running Wayland applications on an X server,
preferably with a compositing manager running.
It is not yet complete. What is not yet implemented includes support
-for the primary selection, touchscreens, input methods, device
-switching in dmabuf feedback, and the viewporter protocol extension.
+for touchscreens, input methods, device switching in dmabuf feedback,
+and the viewporter protocol extension.
There are also problems with output reporting in subsurfaces.
diff --git a/compositor.h b/compositor.h
index afa50bc..49f40fc 100644
--- a/compositor.h
+++ b/compositor.h
@@ -86,6 +86,10 @@ struct _Compositor
typedef struct _Seat Seat;
+/* Forward declarations from primary_selection.c. */
+
+typedef struct _PDataSource PDataSource;
+
/* Defined in 12to11.c. */
extern Compositor compositor;
@@ -703,6 +707,7 @@ extern void *XLXdgRoleRunOnReconstrain (Role *, void (*) (void *, XEvent *),
extern void XLXdgRoleCancelReconstrainCallback (void *);
extern void XLXdgRoleReconstrain (Role *, XEvent *);
extern void XLXdgRoleMoveBy (Role *, int, int);
+extern void XLXdgRoleNoteRejectedConfigure (Role *);
extern Window XLWindowFromXdgRole (Role *);
extern Subcompositor *XLSubcompositorFromXdgRole (Role *);
@@ -871,6 +876,7 @@ extern void *XLSeatAddModifierCallback (Seat *, void (*) (unsigned int, void *),
void *);
extern void XLSeatRemoveModifierCallback (void *);
extern unsigned int XLSeatGetEffectiveModifiers (Seat *);
+extern Bool XLSeatResizeInProgress (Seat *);
extern Cursor InitDefaultCursor (void);
@@ -933,6 +939,7 @@ extern void XLNoteSourceDestroyed (DataSource *);
extern Bool XLNoteLocalSelection (Seat *, DataSource *);
extern void XLReceiveDataFromSelection (Time, Atom, Atom, int);
extern Bool XLOwnDragSelection (Time, DataSource *);
+extern void XLNotePrimaryDestroyed (PDataSource *);
extern void XLInitXData (void);
@@ -968,7 +975,15 @@ extern Bool XLIsWindowIconSurface (Window);
/* Defined in primary_selection.c. */
extern void XLInitPrimarySelection (void);
+extern void XLSetForeignPrimary (Time, CreateOfferFuncs);
+extern void XLClearForeignPrimary (Time);
+extern Bool XLNoteLocalPrimary (Seat *, PDataSource *);
extern void XLPrimarySelectionHandleFocusChange (Seat *);
+extern struct wl_resource *XLResourceFromPDataSource (PDataSource *);
+extern Bool XLPDataSourceHasAtomTarget (PDataSource *, Atom);
+extern Bool XLPDataSourceHasTarget (PDataSource *, const char *);
+extern int XLPDataSourceTargetCount (PDataSource *);
+extern void XLPDataSourceGetTargets (PDataSource *, Atom *);
/* Utility functions that don't belong in a specific file. */
diff --git a/data_device.c b/data_device.c
index 82b3b83..d708b26 100644
--- a/data_device.c
+++ b/data_device.c
@@ -452,7 +452,7 @@ DataOfferSetActions (struct wl_client *client, struct wl_resource *resource,
if (!(offer->state & IsDragAndDrop))
{
- wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH,
+ wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_OFFER,
"trying to finish non-drag-and-drop data offer");
return;
}
diff --git a/dnd.c b/dnd.c
index 5628303..5045952 100644
--- a/dnd.c
+++ b/dnd.c
@@ -30,9 +30,6 @@ along with 12to11. If not, see . */
Drags between Wayland clients are implemented in seat.c and
data_device.c instead. */
-/* TODO: Handle XdndActionAsk and allow using keyboard modifiers to
- change the selected action. Then, update README. */
-
enum
{
XdndProtocolVersion = 5,
diff --git a/primary_selection.c b/primary_selection.c
index 291c52f..466ac49 100644
--- a/primary_selection.c
+++ b/primary_selection.c
@@ -24,7 +24,6 @@ along with 12to11. If not, see . */
#include "primary-selection-unstable-v1.h"
typedef struct _PDataDevice PDataDevice;
-typedef struct _PDataSource PDataSource;
typedef struct _PDataOffer PDataOffer;
enum
@@ -74,6 +73,9 @@ struct _PDataSource
/* List of all MIME types provided by this source. */
XLList *mime_types;
+ /* List of atoms provided by this source. */
+ XIDList *atom_types;
+
/* Number of MIME types provided by this source. */
int n_mime_types;
};
@@ -90,6 +92,17 @@ static uint32_t last_change_serial;
/* All data devices. */
static PDataDevice all_devices;
+/* A special PDataSource meaning that the primary selection is a
+ foreign selection. */
+static PDataSource foreign_selection_key;
+
+/* Functions to call to obtain zwp_primary_selection_v1_offer
+ resources and send associated data for foreign selections. */
+static CreateOfferFuncs foreign_selection_functions;
+
+/* The time the foreign selection was set. */
+static Time foreign_selection_time;
+
/* Forward declaration. */
static void NoticeChanged (void);
@@ -224,6 +237,8 @@ Offer (struct wl_client *client, struct wl_resource *resource,
this source. */
source->mime_types = XLListPrepend (source->mime_types,
XLStrdup (mime_type));
+ source->atom_types = XIDListPrepend (source->atom_types,
+ InternAtom (mime_type));
source->n_mime_types++;
}
@@ -259,6 +274,10 @@ HandleSourceResourceDestroy (struct wl_resource *resource)
/* And free the MIME types offered by the source. */
XLListFree (source->mime_types, XLFree);
+ XIDListFree (source->atom_types, NULL);
+
+ /* Maybe disown the primary selection. */
+ XLNotePrimaryDestroyed (source);
/* If source is the primary selection, clear it and send the change
to all clients. */
@@ -272,10 +291,92 @@ HandleSourceResourceDestroy (struct wl_resource *resource)
XLFree (source);
}
+struct wl_resource *
+XLResourceFromPDataSource (PDataSource *source)
+{
+ return source->resource;
+}
+
+Bool
+XLPDataSourceHasAtomTarget (PDataSource *source, Atom target)
+{
+ XIDList *list;
+
+ for (list = source->atom_types; list; list = list->next)
+ {
+ if (list->data == target)
+ return True;
+ }
+
+ return False;
+}
+
+Bool
+XLPDataSourceHasTarget (PDataSource *source, const char *mime_type)
+{
+ XLList *list;
+
+ for (list = source->mime_types; list; list = list->next)
+ {
+ if (!strcmp (list->data, mime_type))
+ return True;
+ }
+
+ return False;
+}
+
+int
+XLPDataSourceTargetCount (PDataSource *source)
+{
+ return source->n_mime_types;
+}
+
+void
+XLPDataSourceGetTargets (PDataSource *source, Atom *targets)
+{
+ int i;
+ XIDList *list;
+
+ list = source->atom_types;
+
+ for (i = 0; i < source->n_mime_types; ++i)
+ {
+ targets[i] = list->data;
+ list = list->next;
+ }
+}
/* Device implementation. */
+static void
+UpdateSingleReferenceWithForeignOffer (struct wl_client *client,
+ PDataDevice *reference)
+{
+ struct wl_resource *scratch, *resource;
+ Time time;
+
+ time = foreign_selection_time;
+ resource = foreign_selection_functions.create_offer (client, time);
+ scratch = reference->resource;
+
+ if (!resource)
+ return;
+
+ /* Make the data offer known to the client. */
+ zwp_primary_selection_device_v1_send_data_offer (scratch, resource);
+
+ /* Tell the foreign selection provider to send supported resources
+ to the client. */
+ foreign_selection_functions.send_offers (resource, time);
+
+ /* Finally, tell the client that the offer is a selection. */
+ zwp_primary_selection_device_v1_send_selection (scratch, resource);
+
+ /* Set WasSentOffer since an offer was sent. */
+ reference->flags |= WasSentOffer;
+}
+
static void
UpdateForSingleReference (PDataDevice *reference)
{
@@ -302,6 +403,15 @@ UpdateForSingleReference (PDataDevice *reference)
return;
}
+ /* If the primary selection is foreign, call the foreign selection
+ functions. */
+ if (primary_selection == &foreign_selection_key)
+ {
+ UpdateSingleReferenceWithForeignOffer (client, reference);
+
+ return;
+ }
+
/* Otherwise, create a data offer for that client. */
offer = AddDataOffer (client, primary_selection);
@@ -392,8 +502,16 @@ static void
SetSelection (struct wl_client *client, struct wl_resource *resource,
struct wl_resource *source_resource, uint32_t serial)
{
+ PDataDevice *device;
+ PDataSource *source;
struct wl_resource *scratch;
+ device = wl_resource_get_user_data (resource);
+
+ if (!device->seat)
+ /* This device is inert, since the seat has been deleted. */
+ return;
+
if (serial < last_change_serial)
/* This change is out of date. Do nothing. */
return;
@@ -401,19 +519,34 @@ SetSelection (struct wl_client *client, struct wl_resource *resource,
/* Otherwise, set the primary selection. */
last_change_serial = serial;
+ if (source_resource)
+ source = wl_resource_get_user_data (source_resource);
+ else
+ source = NULL;
+
+ /* Try to own the X primary selection. If it fails, refrain from
+ changing the current selection data. */
+ if (!XLNoteLocalPrimary (device->seat, source))
+ return;
+
if (primary_selection)
{
/* The primary selection already exists. Send the cancelled
event and clear the primary selection variable. */
scratch = primary_selection->resource;
- primary_selection = NULL;
- zwp_primary_selection_source_v1_send_cancelled (scratch);
+ if (primary_selection != &foreign_selection_key)
+ /* The special foreign selection has no associated
+ wl_resource. */
+ zwp_primary_selection_source_v1_send_cancelled (scratch);
+
+ /* Clear primary_selection. */
+ primary_selection = NULL;
}
if (source_resource)
/* Make source_resource the new primary selection. */
- primary_selection = wl_resource_get_user_data (source_resource);
+ primary_selection = source;
/* Now, send the updated primary selection to clients. */
NoticeChanged ();
@@ -585,6 +718,56 @@ HandleBind (struct wl_client *client, void *data, uint32_t version,
wl_resource_set_implementation (resource, &manager_impl, NULL, NULL);
}
+
+
+void
+XLSetForeignPrimary (Time time, CreateOfferFuncs functions)
+{
+ uint32_t serial;
+ struct wl_resource *scratch;
+
+ if (time < foreign_selection_time)
+ return;
+
+ serial = wl_display_next_serial (compositor.wl_display);
+
+ /* Use this serial to prevent clients from changing the selection
+ again until the next event is sent. */
+ last_change_serial = serial;
+
+ foreign_selection_time = time;
+ foreign_selection_functions = functions;
+
+ if (primary_selection
+ && primary_selection != &foreign_selection_key)
+ {
+ scratch = primary_selection->resource;
+ zwp_primary_selection_source_v1_send_cancelled (scratch);
+ }
+
+ /* Use a special value of primary_selection to mean that foreign
+ selections are in use. */
+ primary_selection = &foreign_selection_key;
+
+ /* Send new data offers to current clients. */
+ NoticeChanged ();
+}
+
+void
+XLClearForeignPrimary (Time time)
+{
+ if (time < foreign_selection_time)
+ return;
+
+ if (primary_selection == &foreign_selection_key)
+ {
+ primary_selection = NULL;
+ NoticeChanged ();
+ }
+
+ foreign_selection_time = time;
+}
+
void
XLInitPrimarySelection (void)
{
diff --git a/seat.c b/seat.c
index 713e027..dac20c0 100644
--- a/seat.c
+++ b/seat.c
@@ -5129,3 +5129,9 @@ XLSeatGetEffectiveModifiers (Seat *seat)
{
return seat->base | seat->locked | seat->latched;
}
+
+Bool
+XLSeatResizeInProgress (Seat *seat)
+{
+ return seat->resize_in_progress;
+}
diff --git a/xdata.c b/xdata.c
index ee20847..e9aa089 100644
--- a/xdata.c
+++ b/xdata.c
@@ -30,10 +30,11 @@ along with 12to11. If not, see . */
#include
#include "compositor.h"
+#include "primary-selection-unstable-v1.h"
#include
-/* X11 data source for Wayland clients. */
+/* X11 data transfer to and from Wayland clients. */
typedef struct _ReadTargetsData ReadTargetsData;
typedef struct _TargetMapping TargetMapping;
@@ -51,6 +52,9 @@ struct _ReadTargetsData
/* Number of atoms read. */
int n_atoms;
+
+ /* What selection is being read from. */
+ Atom selection;
};
struct _TargetMapping
@@ -88,7 +92,11 @@ struct _DataConversion
Atom atom;
/* An alternative GetClipboardCallback, if any. */
- GetDataFunc (*clipboard_callback) (WriteTransfer *, Atom, Atom *);
+ GetDataFunc (*clipboard_callback) (WriteTransfer *, Atom, Atom *,
+ struct wl_resource *,
+ void (*) (struct wl_resource *,
+ const char *, int),
+ Bool);
};
enum
@@ -243,12 +251,21 @@ static Time last_x_selection_change;
time any client did that. */
static Time last_clipboard_time, last_clipboard_change;
+/* The last time ownership over PRIMARY changed. */
+static Time last_primary_time;
+
/* The currently supported selection targets. */
static Atom *x_selection_targets;
/* The number of targets in that array. */
static int num_x_selection_targets;
+/* The currently supported primary selection targets. */
+static Atom *x_primary_targets;
+
+/* The number of targets in that array. */
+static int num_x_primary_targets;
+
/* Data source currently being used to provide the X clipboard. */
static DataSource *selection_data_source;
@@ -256,6 +273,10 @@ static DataSource *selection_data_source;
selection. */
static DataSource *drag_data_source;
+/* Data source currently being used to provide the primary
+ selection. */
+static PDataSource *primary_data_source;
+
#ifdef DEBUG
static void __attribute__ ((__format__ (gnu_printf, 1, 2)))
@@ -327,10 +348,23 @@ SetupMappingTable (void)
}
static Bool
-HasSelectionTarget (Atom atom)
+HasSelectionTarget (Atom atom, Bool is_primary)
{
int i;
+ if (is_primary)
+ {
+ /* Do this for the primary selection instead. */
+
+ for (i = 0; i < num_x_primary_targets; ++i)
+ {
+ if (x_primary_targets[i] == atom)
+ return True;
+ }
+
+ return False;
+ }
+
for (i = 0; i < num_x_selection_targets; ++i)
{
if (x_selection_targets[i] == atom)
@@ -341,7 +375,7 @@ HasSelectionTarget (Atom atom)
}
static TargetMapping *
-FindTranslationForMimeType (const char *mime_type)
+FindTranslationForMimeType (const char *mime_type, Bool is_primary)
{
unsigned short *buckets, i;
unsigned int idx;
@@ -349,12 +383,21 @@ FindTranslationForMimeType (const char *mime_type)
idx = HashMimeString (mime_type) % 32;
buckets = mapping_table.buckets[idx];
+ DebugPrint ("Looking for translation of MIME type %s\n",
+ mime_type);
+
for (i = 0; i < mapping_table.n_elements[idx]; ++i)
{
if (!strcmp (direct_transfer[buckets[i]].mime_type,
mime_type)
- && HasSelectionTarget (MappingAtom (&direct_transfer[buckets[i]])))
- return &direct_transfer[buckets[i]];
+ && HasSelectionTarget (MappingAtom (&direct_transfer[buckets[i]]),
+ is_primary))
+ {
+ DebugPrint ("Found translation for MIME type %s\n",
+ mime_type);
+
+ return &direct_transfer[buckets[i]];
+ }
}
return NULL;
@@ -644,34 +687,40 @@ PostReceiveDirect (Time time, Atom selection, Atom target, int fd)
static void PostReceiveConversion (Time, Atom, Atom, int);
+#define ReceiveBody(selection, primary) \
+ Time time; \
+ TargetMapping *translation; \
+ \
+ DebugPrint ("Receiving %s from X " #selection " \n", \
+ mime_type); \
+ \
+ /* Cast to intptr_t to silence warnings when the pointer type is \
+ larger than long. */ \
+ time = (Time) (intptr_t) wl_resource_get_user_data (resource); \
+ \
+ /* Find which selection target corresponds to MIME_TYPE. */ \
+ translation = FindTranslationForMimeType (mime_type, primary); \
+ \
+ if (translation) \
+ { \
+ if (!translation->translation_func) \
+ /* If a corresponding target exists, ask to receive it. */ \
+ PostReceiveDirect (time, selection, \
+ MappingAtom (translation), fd); \
+ else \
+ /* Otherwise, use the translation function. */ \
+ translation->translation_func (time, selection, \
+ MappingAtom (translation), \
+ fd); \
+ } \
+ else \
+ close (fd)
+
static void
Receive (struct wl_client *client, struct wl_resource *resource,
const char *mime_type, int fd)
{
- Time time;
- TargetMapping *translation;
-
- /* Cast to intptr_t to silence warnings when the pointer type is
- larger than long. */
- time = (Time) (intptr_t) wl_resource_get_user_data (resource);
-
- /* Find which selection target corresponds to MIME_TYPE. */
- translation = FindTranslationForMimeType (mime_type);
-
- if (translation)
- {
- if (!translation->translation_func)
- /* If a corresponding target exists, ask to receive it. */
- PostReceiveDirect (time, CLIPBOARD,
- MappingAtom (translation), fd);
- else
- /* Otherwise, use the translation function. */
- translation->translation_func (time, CLIPBOARD,
- MappingAtom (translation),
- fd);
- }
- else
- close (fd);
+ ReceiveBody (CLIPBOARD, False);
}
static void
@@ -683,14 +732,16 @@ Destroy (struct wl_client *client, struct wl_resource *resource)
static void
Finish (struct wl_client *client, struct wl_resource *resource)
{
- /* TODO... */
+ wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH,
+ "trying to finish foreign data offer");
}
static void
SetActions (struct wl_client *client, struct wl_resource *resource,
uint32_t dnd_actions, uint32_t preferred_action)
{
- /* TODO... */
+ wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_OFFER,
+ "trying to finish non-drag-and-drop data offer");
}
static const struct wl_data_offer_interface wl_data_offer_impl =
@@ -721,6 +772,38 @@ CreateOffer (struct wl_client *client, Time time)
return resource;
}
+static void
+ReceivePrimary (struct wl_client *client, struct wl_resource *resource,
+ const char *mime_type, int fd)
+{
+ ReceiveBody (XA_PRIMARY, True);
+}
+
+static struct zwp_primary_selection_offer_v1_interface primary_offer_impl =
+ {
+ .receive = ReceivePrimary,
+ .destroy = Destroy,
+ };
+
+static struct wl_resource *
+CreatePrimaryOffer (struct wl_client *client, Time time)
+{
+ struct wl_resource *resource;
+
+ resource = wl_resource_create (client,
+ &zwp_primary_selection_offer_v1_interface,
+ 1, 0);
+ if (!resource)
+ /* If allocating the resource failed, return NULL. */
+ return NULL;
+
+ /* Otherwise, set the user_data to the time of the selection
+ change. */
+ wl_resource_set_implementation (resource, &primary_offer_impl,
+ (void *) time, NULL);
+ return resource;
+}
+
static Bool
CheckDuplicate (unsigned short index, Atom a)
{
@@ -765,20 +848,17 @@ CheckDuplicate (unsigned short index, Atom a)
}
static void
-SendOffers (struct wl_resource *resource, Time time)
+SendOffers1 (struct wl_resource *resource, int ntargets, Atom *targets,
+ void (*send_offer_func) (struct wl_resource *, const char *))
{
int i, j;
unsigned int idx;
unsigned short *buckets;
- if (time < last_x_selection_change)
- /* This offer is out of date. */
- return;
-
- for (i = 0; i < num_x_selection_targets; ++i)
+ for (i = 0; i < ntargets; ++i)
{
/* Offer each type corresponding to this target. */
- idx = x_selection_targets[i] % 16;
+ idx = targets[i] % 16;
/* N.B. that duplicates do appear in the atom buckets, which
is intentional. */
@@ -787,13 +867,12 @@ SendOffers (struct wl_resource *resource, Time time)
for (j = 0; j < mapping_table.n_atom_elements[idx]; ++j)
{
if (MappingIs (&direct_transfer[buckets[j]],
- x_selection_targets[i])
- && CheckDuplicate (buckets[j],
- x_selection_targets[i]))
+ targets[i])
+ && CheckDuplicate (buckets[j], targets[i]))
/* If it exists and was not previously offered, offer it
to the client. */
- wl_data_offer_send_offer (resource,
- direct_transfer[buckets[j]].mime_type);
+ send_offer_func (resource,
+ direct_transfer[buckets[j]].mime_type);
}
}
@@ -804,10 +883,62 @@ SendOffers (struct wl_resource *resource, Time time)
}
static void
-HandleNewSelection (Time time, Atom *targets, int ntargets)
+SendOffers (struct wl_resource *resource, Time time)
+{
+ if (time < last_x_selection_change)
+ /* This offer is out of date. */
+ return;
+
+ SendOffers1 (resource, num_x_selection_targets,
+ x_selection_targets, wl_data_offer_send_offer);
+}
+
+static void
+SendPrimaryOffers (struct wl_resource *resource, Time time)
+{
+ if (time < last_primary_time)
+ /* This offer is out of date. */
+ return;
+
+ SendOffers1 (resource, num_x_primary_targets,
+ x_primary_targets,
+ zwp_primary_selection_offer_v1_send_offer);
+}
+
+static void
+HandleNewSelection (Time time, Atom selection, Atom *targets,
+ int ntargets)
{
CreateOfferFuncs funcs;
+ if (selection == XA_PRIMARY)
+ {
+ /* The primary selection changed, and now has the given
+ targets. */
+
+ if (time < last_primary_time)
+ {
+ XLFree (targets);
+ return;
+ }
+
+ /* Set up the new primary targets. */
+ XLFree (x_primary_targets);
+ x_primary_targets = targets;
+ num_x_primary_targets = ntargets;
+ last_primary_time = time;
+
+ /* Add the right functions and set them as the foreign primary
+ selection handler at TIME. */
+ funcs.create_offer = CreatePrimaryOffer;
+ funcs.send_offers = SendPrimaryOffers;
+
+ XLSetForeignPrimary (time, funcs);
+ return;
+ }
+
+ /* Else, the selection that changed is CLIPBOARD. */
+
/* Ignore outdated selection changes. */
if (time < last_x_selection_change)
{
@@ -888,13 +1019,14 @@ TargetsFinishCallback (ReadTransfer *transfer, Bool success)
data = GetTransferData (transfer);
if (success)
- DebugPrint ("Received targets from CLIPBOARD\n");
+ DebugPrint ("Received targets from %lu\n", data->selection);
else
- DebugPrint ("Failed to obtain targets from CLIPBOARD\n");
+ DebugPrint ("Failed to obtain targets\n");
if (success)
HandleNewSelection (GetTransferTime (transfer),
- data->atoms, data->n_atoms);
+ data->selection, data->atoms,
+ data->n_atoms);
else
/* HandleNewSelection keeps data->atoms around for a while. */
XLFree (data->atoms);
@@ -913,12 +1045,28 @@ NoticeClipboardChanged (Time time)
ReadTargetsData *data;
data = XLCalloc (1, sizeof *data);
+ data->selection = CLIPBOARD;
ConvertSelectionFuncs (CLIPBOARD, TARGETS, time, data,
NULL, TargetsReadCallback,
TargetsFinishCallback);
}
+/* The same, but for the primary selection. */
+
+static void
+NoticePrimaryChanged (Time time)
+{
+ ReadTargetsData *data;
+
+ data = XLCalloc (1, sizeof *data);
+ data->selection = XA_PRIMARY;
+
+ ConvertSelectionFuncs (XA_PRIMARY, TARGETS, time, data,
+ NULL, TargetsReadCallback,
+ TargetsFinishCallback);
+}
+
static void
NoticeClipboardCleared (Time time)
{
@@ -926,8 +1074,33 @@ NoticeClipboardCleared (Time time)
if (time < last_x_selection_change)
return;
+ DebugPrint ("CLIPBOARD was cleared at %lu\n", time);
+
last_x_selection_change = time;
XLClearForeignSelection (time);
+
+ /* Free data that is no longer used. */
+ XLFree (x_selection_targets);
+ x_selection_targets = NULL;
+ num_x_selection_targets = 0;
+}
+
+static void
+NoticePrimaryCleared (Time time)
+{
+ /* Ignore outdated events. */
+ if (time < last_primary_time)
+ return;
+
+ DebugPrint ("PRIMARY was cleared at %lu\n", time);
+
+ last_primary_time = time;
+ XLClearForeignPrimary (time);
+
+ /* Free data that is no longer used. */
+ XLFree (x_primary_targets);
+ x_primary_targets = NULL;
+ num_x_primary_targets = 0;
}
static void
@@ -943,11 +1116,20 @@ HandleSelectionNotify (XFixesSelectionNotifyEvent *event)
disowning the selection were successful. */
last_clipboard_change = event->selection_timestamp;
+ if (event->selection == XA_PRIMARY
+ && event->selection_timestamp > last_primary_time)
+ last_primary_time = event->selection_timestamp;
+
if (event->owner != None
&& event->selection == CLIPBOARD)
NoticeClipboardChanged (event->timestamp);
- else
+ else if (event->selection == CLIPBOARD)
NoticeClipboardCleared (event->timestamp);
+ else if (event->owner != None
+ && event->selection == XA_PRIMARY)
+ NoticePrimaryChanged (event->timestamp);
+ else if (event->selection == XA_PRIMARY)
+ NoticePrimaryCleared (event->timestamp);
}
Bool
@@ -1001,17 +1183,25 @@ GetDataConversion (Atom target)
}
static const char *
-MimeTypeFromTarget (Atom target)
+MimeTypeFromTarget (Atom target, Bool primary)
{
DataConversion *conversion;
static char *string;
+ Bool missing_type;
/* TODO: replace XGetAtomName with something more efficient. */
if (string)
XFree (string);
string = NULL;
- if (!XLDataSourceHasAtomTarget (selection_data_source, target))
+ if (!primary)
+ missing_type = !XLDataSourceHasAtomTarget (selection_data_source,
+ target);
+ else
+ missing_type = !XLPDataSourceHasAtomTarget (primary_data_source,
+ target);
+
+ if (missing_type)
{
/* If the data source does not itself provide the specified
target, then a conversion function is in use. */
@@ -1032,11 +1222,19 @@ MimeTypeFromTarget (Atom target)
}
static Atom
-TypeFromTarget (Atom target)
+TypeFromTarget (Atom target, Bool primary)
{
DataConversion *conversion;
+ Bool missing_type;
- if (!XLDataSourceHasAtomTarget (selection_data_source, target))
+ if (!primary)
+ missing_type = !XLDataSourceHasAtomTarget (selection_data_source,
+ target);
+ else
+ missing_type = !XLPDataSourceHasAtomTarget (primary_data_source,
+ target);
+
+ if (missing_type)
{
/* If the data source does not itself provide the specified
target, then a conversion function is in use. Use the type
@@ -1183,11 +1381,14 @@ GetClipboardCallback (WriteTransfer *transfer, Atom target,
WriteInfo *info;
int pipe[2], rc;
DataConversion *conversion;
+ struct wl_resource *resource;
/* If the selection data source is destroyed, the selection will be
disowned. */
XLAssert (selection_data_source != NULL);
+ resource = XLResourceFromDataSource (selection_data_source);
+
if (!XLDataSourceHasAtomTarget (selection_data_source, target))
{
/* If the data source does not itself provide the specified
@@ -1202,7 +1403,9 @@ GetClipboardCallback (WriteTransfer *transfer, Atom target,
if (conversion->clipboard_callback)
return conversion->clipboard_callback (transfer, target,
- type);
+ type, resource,
+ wl_data_source_send_send,
+ False);
/* Otherwise, use the default callback. */
DebugPrint ("Conversion to type %lu specified with default callback\n",
@@ -1223,12 +1426,13 @@ GetClipboardCallback (WriteTransfer *transfer, Atom target,
DebugPrint ("Created pipe (%d, %d)\n", pipe[0], pipe[1]);
/* Send the write end of the pipe to the source. */
- wl_data_source_send_send (XLResourceFromDataSource (selection_data_source),
- MimeTypeFromTarget (target), pipe[1]);
+ wl_data_source_send_send (resource,
+ MimeTypeFromTarget (target, False),
+ pipe[1]);
close (pipe[1]);
/* And set the prop type appropriately. */
- *type = TypeFromTarget (target);
+ *type = TypeFromTarget (target, False);
/* Create the transfer info structure. */
info = XLMalloc (sizeof *info);
@@ -1416,8 +1620,11 @@ ConversionReadFunc (WriteTransfer *transfer, unsigned char *buffer,
}
static GetDataFunc
-GetConversionCallback (WriteTransfer *transfer, Atom target,
- Atom *type)
+GetConversionCallback (WriteTransfer *transfer, Atom target, Atom *type,
+ struct wl_resource *source,
+ void (*send_send) (struct wl_resource *, const char *,
+ int),
+ Bool is_primary)
{
ConversionWriteInfo *info;
int pipe[2], rc;
@@ -1441,8 +1648,7 @@ GetConversionCallback (WriteTransfer *transfer, Atom target,
return NULL;
/* Then, send the write end of the pipe to the data source. */
- wl_data_source_send_send (XLResourceFromDataSource (selection_data_source),
- "text/plain;charset=utf-8", pipe[1]);
+ send_send (source, "text/plain;charset=utf-8", pipe[1]);
close (pipe[1]);
/* Following that, set the property type correctly. */
@@ -1677,7 +1883,7 @@ GetDragCallback (WriteTransfer *transfer, Atom target,
be disowned. */
XLAssert (drag_data_source != NULL);
- DebugPrint ("Entered GetClipboardCallback; target is %lu\n",
+ DebugPrint ("Entered GetDragCallback; target is %lu\n",
target);
/* First, create a non-blocking pipe. We will give the write end to
@@ -1757,13 +1963,26 @@ XLNoteSourceDestroyed (DataSource *source)
if (source == drag_data_source)
{
DebugPrint ("Disowning XdndSelection\nThis is being done in response"
- "to the data source being destroyed.\n");
+ " to the data source being destroyed.\n");
/* Disown the selection. */
DisownSelection (XdndSelection);
}
}
+void
+XLNotePrimaryDestroyed (PDataSource *source)
+{
+ if (source == primary_data_source)
+ {
+ DebugPrint ("Disowning PRIMARY\nThis is being done in response"
+ " to the data source being destroyed.\n");
+
+ /* Disown the selection. */
+ DisownSelection (XA_PRIMARY);
+ }
+}
+
static Bool
FindTargetInArray (Atom *targets, int ntargets, Atom atom)
{
@@ -1778,52 +1997,96 @@ FindTargetInArray (Atom *targets, int ntargets, Atom atom)
return False;
}
+/* FIXME: this should really be something other than two giant
+ macros... */
+
+#define NoteLocalSelectionBody(callback, time_1, time_2, atom, field, foreign, n_foreign) \
+ Time time; \
+ Atom *targets; \
+ int ntargets, i, n_data_conversions; \
+ Bool rc; \
+ \
+ if (source == NULL) \
+ { \
+ DebugPrint ("Disowning " #atom " at %lu (vs. last change %lu)\n", \
+ time_1, time_2); \
+ \
+ /* Disown the selection. */ \
+ DisownSelection (atom); \
+ field = NULL; \
+ \
+ /* Return whether or not the selection was actually \
+ disowned. */ \
+ return time_1 >= time_2; \
+ } \
+ \
+ time = XLSeatGetLastUserTime (seat); \
+ \
+ DebugPrint ("Acquiring ownership of " #atom " at %lu\n", time); \
+ \
+ if (!time) \
+ /* Nothing has yet happened on the seat. */ \
+ return False; \
+ \
+ if (time < time_1 || time < time_2) \
+ /* TIME is out of date. */ \
+ return False; \
+ \
+ DebugPrint ("Setting callback function for " #atom "\n"); \
+ \
+ /* Since the local selection is now set, free foreign selection \
+ data. */ \
+ XLFree (foreign); \
+ n_foreign = 0; \
+ foreign = NULL; \
+ \
+ /* Otherwise, set the clipboard change time to TIME. */ \
+ time_1 = time; \
+ time_2 = time; \
+ \
+
+#define NoteLocalSelectionFooter(callback, atom, field, has_target) \
+ /* Now, look to see what standard X targets the client doesn't \
+ offer, and add them to the targets array. \
+ \
+ Most functioning Wayland clients will also offer the typical X \
+ selection targets such as STRING and UTF8_STRING in addition to \
+ MIME types; when they do, they perform the data conversion \
+ conversion better than us. */ \
+ \
+ for (i = 0; i < ArrayElements (data_conversions); ++i) \
+ { \
+ /* If the source provides MIME typed-data for a format we know \
+ how to convert, announce the associated selection conversion \
+ targets. */ \
+ \
+ if (has_target (source, \
+ data_conversions[i].mime_type) \
+ && !FindTargetInArray (targets, ntargets, \
+ data_conversions[i].type)) \
+ { \
+ DebugPrint ("Client doesn't provide standard X conversion" \
+ " target for %s; adding it\n", \
+ data_conversions[i].mime_type); \
+ \
+ targets[ntargets++] = data_conversions[i].type; \
+ } \
+ } \
+ \
+ /* And own the selection. */ \
+ field = source; \
+ \
+ rc = OwnSelection (time, atom, callback, targets, ntargets); \
+ XLFree (targets); \
+ return rc \
+
Bool
XLNoteLocalSelection (Seat *seat, DataSource *source)
{
- Time time;
- Atom *targets;
- int ntargets, i, n_data_conversions;
- Bool rc;
-
- if (source == NULL)
- {
- DebugPrint ("Disowning CLIPBOARD at %lu (vs. last change %lu)\n",
- last_clipboard_time, last_x_selection_change);
-
- /* Disown the selection. */
- DisownSelection (CLIPBOARD);
- selection_data_source = NULL;
-
- /* Return whether or not the selection was actually
- disowned. */
- return last_clipboard_time >= last_x_selection_change;
- }
-
- time = XLSeatGetLastUserTime (seat);
-
- DebugPrint ("Acquiring ownership of CLIPBOARD at %lu\n", time);
-
- if (!time)
- /* Nothing has yet happened on the seat. */
- return False;
-
- if (time < last_clipboard_time
- || time < last_clipboard_change)
- /* TIME is out of date. */
- return False;
-
- DebugPrint ("Setting callback function for CLIPBOARD\n");
-
- /* Since the local selection is now set, free foreign selection
- data. */
- XLFree (x_selection_targets);
- num_x_selection_targets = 0;
- x_selection_targets = NULL;
-
- /* Otherwise, set the clipboard change time to TIME. */
- last_clipboard_time = time;
- last_clipboard_change = time;
+ NoteLocalSelectionBody (GetClipboardCallback, last_clipboard_time,
+ last_x_selection_change, CLIPBOARD,
+ selection_data_source, x_selection_targets,
+ num_x_selection_targets);
/* And copy the targets from the data source. */
ntargets = XLDataSourceTargetCount (source);
@@ -1831,41 +2094,113 @@ XLNoteLocalSelection (Seat *seat, DataSource *source)
targets = XLMalloc (sizeof *targets * (ntargets + n_data_conversions));
XLDataSourceGetTargets (source, targets);
- /* Now, look to see what standard X targets the client doesn't
- offer, and add them to the targets array.
+ NoteLocalSelectionFooter (GetClipboardCallback, CLIPBOARD,
+ selection_data_source,
+ XLDataSourceHasTarget);
+}
- Most functioning Wayland clients will also offer the typical X
- selection targets such as STRING and UTF8_STRING in addition to
- MIME types; when they do, they perform the data conversion
- conversion better than us. */
+static GetDataFunc
+GetPrimaryCallback (WriteTransfer *transfer, Atom target,
+ Atom *type)
+{
+ WriteInfo *info;
+ int pipe[2], rc;
+ DataConversion *conversion;
+ struct wl_resource *resource;
- for (i = 0; i < ArrayElements (data_conversions); ++i)
+ /* If the selection data source is destroyed, the selection will be
+ disowned. */
+ XLAssert (primary_data_source != NULL);
+
+ resource = XLResourceFromPDataSource (primary_data_source);
+
+ if (!XLPDataSourceHasAtomTarget (primary_data_source, target))
{
- /* If the source provides MIME typed-data for a format we know
- how to convert, announce the associated selection conversion
- targets. */
+ /* If the data source does not itself provide the specified
+ target, then a conversion function is in use. Call the
+ clipboard callback specified in the conversion table entry,
+ if it exists. */
+ conversion = GetDataConversion (target);
- if (XLDataSourceHasTarget (source,
- data_conversions[i].mime_type)
- && !FindTargetInArray (targets, ntargets,
- data_conversions[i].type))
- {
- DebugPrint ("Client doesn't provide standard X conversion"
- " target for %s; adding it\n",
- data_conversions[i].mime_type);
+ if (!conversion)
+ /* There must be a data conversion here. */
+ abort ();
- targets[ntargets++] = data_conversions[i].type;
- }
+ /* This is ugly but the only reasonable way to fit the following
+ function in 80 columns. */
+
+#define Function zwp_primary_selection_source_v1_send_send
+
+ if (conversion->clipboard_callback)
+ return conversion->clipboard_callback (transfer, target,
+ type, resource,
+ Function, True);
+
+#undef Function
+
+ /* Otherwise, use the default callback. */
+ DebugPrint ("Conversion to type %lu specified with default callback\n",
+ target);
}
- /* And own the selection. */
- selection_data_source = source;
+ DebugPrint ("Entered GetPrimaryCallback; target is %lu\n",
+ target);
- rc = OwnSelection (time, CLIPBOARD, GetClipboardCallback,
- targets, ntargets);
- XLFree (targets);
+ /* First, create a non-blocking pipe. We will give the write end to
+ the client. */
+ rc = pipe2 (pipe, O_NONBLOCK);
- return rc;
+ if (rc)
+ /* Creating the pipe failed. */
+ return NULL;
+
+ DebugPrint ("Created pipe (%d, %d)\n", pipe[0], pipe[1]);
+
+ /* Send the write end of the pipe to the source. */
+ zwp_primary_selection_source_v1_send_send (resource,
+ MimeTypeFromTarget (target, True),
+ pipe[1]);
+ close (pipe[1]);
+
+ /* And set the prop type appropriately. */
+ *type = TypeFromTarget (target, True);
+
+ /* Create the transfer info structure. */
+ info = XLMalloc (sizeof *info);
+ info->fd = pipe[0];
+ info->flags = 0;
+
+ DebugPrint ("Adding the read callback\n");
+
+ /* Wait for info to become readable. */
+ info->read_callback = XLAddReadFd (info->fd, transfer,
+ NoticeTransferReadable);
+
+ /* Set info as the transfer data. */
+ SetWriteTransferData (transfer, info);
+
+ return ClipboardReadFunc;
+}
+
+Bool
+XLNoteLocalPrimary (Seat *seat, PDataSource *source)
+{
+ /* I forgot why there are two variables for tracking the clipboard
+ time, so just use one here. */
+ NoteLocalSelectionBody (GetPrimaryCallback, last_primary_time,
+ last_primary_time, XA_PRIMARY,
+ primary_data_source, x_primary_targets,
+ num_x_primary_targets);
+
+ /* And copy the targets from the data source. */
+ ntargets = XLPDataSourceTargetCount (source);
+ n_data_conversions = ArrayElements (data_conversions);
+ targets = XLMalloc (sizeof *targets * (ntargets + n_data_conversions));
+ XLPDataSourceGetTargets (source, targets);
+
+ NoteLocalSelectionFooter (GetPrimaryCallback, XA_PRIMARY,
+ primary_data_source,
+ XLPDataSourceHasTarget);
}
void
@@ -1896,6 +2231,7 @@ XLInitXData (void)
}
SelectSelectionInput (CLIPBOARD);
+ SelectSelectionInput (XA_PRIMARY);
/* Initialize atoms used in the direct transfer table. */
direct_transfer[1].atom_flag = UTF8_STRING | Duplicate;
diff --git a/xdg_surface.c b/xdg_surface.c
index b149558..c5218d8 100644
--- a/xdg_surface.c
+++ b/xdg_surface.c
@@ -35,7 +35,9 @@ enum
StateLateFrame = (1 << 1),
StatePendingWindowGeometry = (1 << 2),
StateWaitingForAckConfigure = (1 << 3),
- StateLateFrameAcked = (1 << 4),
+ StateWaitingForAckCommit = (1 << 4),
+ StateLateFrameAcked = (1 << 5),
+ StateMaybeConfigure = (1 << 6),
};
typedef struct _XdgRole XdgRole;
@@ -599,6 +601,10 @@ AckConfigure (struct wl_client *client, struct wl_resource *resource,
if (XLFrameClockIsFrozen (xdg_role->clock)
&& xdg_role->role.surface)
RunFrameCallbacksConditionally (xdg_role);
+
+#ifdef DEBUG_GEOMETRY_CALCULATION
+ fprintf (stderr, "Client acknowledged configuration\n");
+#endif
}
if (xdg_role->impl)
@@ -656,6 +662,10 @@ Commit (Surface *surface, Role *role)
xdg_role->state &= ~StatePendingWindowGeometry;
}
+ /* This flag means no commit has happened after an
+ ack_configure. */
+ xdg_role->state &= ~StateWaitingForAckCommit;
+
/* A frame is already in progress, so instead say that an urgent
update is needed immediately after the frame completes. In any
case, don't run frame callbacks upon buffer release anymore. */
@@ -1062,6 +1072,13 @@ NoteBounds (void *data, int min_x, int min_y,
Don't resize the window until it's acknowledged. */
return;
+ if (role->state & StateWaitingForAckCommit)
+ /* Don't resize the window until all configure events are
+ acknowledged. We wait for a commit on the xdg_toplevel to do
+ this, because Firefox updates subsurfaces while the old size is
+ still in effect. */
+ return;
+
/* Avoid resizing the window should its actual size not have
changed. */
@@ -1071,6 +1088,10 @@ NoteBounds (void *data, int min_x, int min_y,
if (role->bounds_width != bounds_width
|| role->bounds_height != bounds_height)
{
+#ifdef DEBUG_GEOMETRY_CALCULATION
+ fprintf (stderr, "Resizing to: %d %d\n", bounds_width, bounds_height);
+#endif
+
if (role->impl->funcs.note_window_pre_resize)
role->impl->funcs.note_window_pre_resize (&role->role,
role->impl,
@@ -1225,6 +1246,24 @@ WriteRedirectProperty (XdgRole *role)
(unsigned char *) &bypass_compositor, 1);
}
+static void
+HandleFreeze (void *data)
+{
+ XdgRole *role;
+
+ role = data;
+
+ /* _NET_WM_SYNC_REQUEST events should be succeeded by a
+ ConfigureNotify event. */
+ role->state |= StateWaitingForAckConfigure;
+ role->state |= StateWaitingForAckCommit;
+
+ /* This flag means the WaitingForAckConfigure was caused by a
+ _NET_WM_SYNC_REQUEST, and the following ConfigureNotify event
+ might not lead to a configure event being sent. */
+ role->state |= StateMaybeConfigure;
+}
+
void
XLGetXdgSurface (struct wl_client *client, struct wl_resource *resource,
uint32_t id, struct wl_resource *surface_resource)
@@ -1331,6 +1370,8 @@ XLGetXdgSurface (struct wl_client *client, struct wl_resource *resource,
role->subcompositor = MakeSubcompositor ();
role->clock = XLMakeFrameClockForWindow (role->window);
+ XLFrameClockSetFreezeCallback (role->clock, HandleFreeze, role);
+
SubcompositorSetTarget (role->subcompositor, role->picture);
SubcompositorSetInputCallback (role->subcompositor,
InputRegionChanged, role);
@@ -1412,6 +1453,16 @@ XLXdgRoleSendConfigure (Role *role, uint32_t serial)
xdg_role = XdgRoleFromRole (role);
xdg_role->conf_serial = serial;
xdg_role->state |= StateWaitingForAckConfigure;
+ xdg_role->state |= StateWaitingForAckCommit;
+
+ /* We know know that the ConfigureNotify event following any
+ _NET_WM_SYNC_REQUEST event was accepted, so clear the maybe
+ configure flag. */
+ xdg_role->state &= ~StateMaybeConfigure;
+
+#ifdef DEBUG_GEOMETRY_CALCULATION
+ fprintf (stderr, "Waiting for ack_configure...\n");
+#endif
xdg_surface_send_configure (role->resource, serial);
}
@@ -1767,3 +1818,24 @@ XLLookUpXdgPopup (Window window)
return role->impl;
}
+
+void
+XLXdgRoleNoteRejectedConfigure (Role *role)
+{
+ XdgRole *xdg_role;
+
+ xdg_role = XdgRoleFromRole (role);
+
+ if (xdg_role->state & StateMaybeConfigure)
+ {
+ /* A configure event immediately following _NET_WM_SYNC_REQUEST
+ was rejected, meaning that we do not have to change anything
+ before unfreezing the frame clock. */
+ xdg_role->state &= ~StateWaitingForAckConfigure;
+ xdg_role->state &= ~StateWaitingForAckCommit;
+ xdg_role->state &= ~StateMaybeConfigure;
+
+ /* Unfreeze the frame clock now. */
+ XLFrameClockUnfreeze (xdg_role->clock);
+ }
+}
diff --git a/xdg_toplevel.c b/xdg_toplevel.c
index d85f2e9..664bb07 100644
--- a/xdg_toplevel.c
+++ b/xdg_toplevel.c
@@ -37,11 +37,14 @@ typedef enum _How How;
enum
{
- StateIsMapped = 1,
- StateMissingState = (1 << 1),
- StatePendingMaxSize = (1 << 2),
- StatePendingMinSize = (1 << 3),
- StatePendingAckMovement = (1 << 4),
+ StateIsMapped = 1,
+ StateMissingState = (1 << 1),
+ StatePendingMaxSize = (1 << 2),
+ StatePendingMinSize = (1 << 3),
+ StatePendingAckMovement = (1 << 4),
+ StatePendingResize = (1 << 5),
+ StatePendingConfigureSize = (1 << 6),
+ StatePendingConfigureStates = (1 << 7),
};
enum
@@ -195,10 +198,26 @@ struct _XdgToplevel
int width01, height01, width10, height10;
int width00, height00, width11, height11;
+ /* The width, height, west and north motion of the next resize. */
+ int resize_width, resize_height, resize_west, resize_north;
+
/* Mask of what this toplevel is allowed to do. It is first set
based on _NET_SUPPORTED upon toplevel creation, and then
_NET_WM_ALLOWED_ACTIONS. */
int supported;
+
+ /* Timer for completing window state changes. The order of
+ _NET_WM_STATE changes and ConfigureNotify events is not
+ predictable, so we batch up both kinds of events with a 0.01
+ second delay by default, before sending the resulting
+ ConfigureNotify event. However, if drag-to-resize is in
+ progress, no such delay is effected. */
+#define DefaultStateDelayNanoseconds 10000000
+ Timer *configuration_timer;
+
+ /* The width and height used by that timer if
+ StatePendingConfigureSize is set. */
+ int configure_width, configure_height;
};
/* iconv context used to convert between UTF-8 and Latin-1. */
@@ -208,6 +227,10 @@ static iconv_t latin_1_cd;
with configure events. */
static Bool apply_state_workaround;
+/* Whether or not to batch together state and size changes that arrive
+ at almost the same time. */
+static Bool batch_state_changes;
+
static XdgUnmapCallback *
RunOnUnmap (XdgToplevel *toplevel, void (*unmap) (void *),
void *data)
@@ -290,6 +313,10 @@ DestroyBacking (XdgToplevel *toplevel)
if (--toplevel->refcount)
return;
+ /* If there is a pending configuration timer, remove it. */
+ if (toplevel->configuration_timer)
+ RemoveTimer (toplevel->configuration_timer);
+
if (toplevel->parent_callback)
CancelUnmapCallback (toplevel->parent_callback);
@@ -325,6 +352,109 @@ SendConfigure (XdgToplevel *toplevel, unsigned int width,
toplevel->conf_serial = serial;
}
+/* Forward declaration. */
+
+static void SendStates (XdgToplevel *);
+static void WriteStates (XdgToplevel *);
+
+static void
+NoteConfigureTime (Timer *timer, void *data, struct timespec time)
+{
+ XdgToplevel *toplevel;
+ int width, height, effective_width, effective_height;
+
+ toplevel = data;
+
+ /* If only the window state changed, call SendStates. */
+ if (!(toplevel->state & StatePendingConfigureSize))
+ SendStates (toplevel);
+ else
+ {
+ /* If the states changed, write them. */
+ if (toplevel->state & StatePendingConfigureStates)
+ WriteStates (toplevel);
+
+ effective_width = toplevel->configure_width / global_scale_factor;
+ effective_height = toplevel->configure_height / global_scale_factor;
+
+ /* Compute the geometry for the configure event based on the
+ current size of the toplevel. */
+ XLXdgRoleCalcNewWindowSize (toplevel->role,
+ effective_width,
+ effective_height,
+ &width, &height);
+
+ /* Send the configure event. */
+ SendConfigure (toplevel, width, height);
+
+ /* Mark the state has having been calculated if some state
+ transition has occured. */
+ if (toplevel->toplevel_state.fullscreen
+ || toplevel->toplevel_state.maximized)
+ toplevel->state &= ~StateMissingState;
+ }
+
+ /* Clear the pending size and state flags. */
+ toplevel->state &= ~StatePendingConfigureSize;
+ toplevel->state &= ~StatePendingConfigureStates;
+
+ /* Cancel and clear the timer. */
+ RemoveTimer (timer);
+ toplevel->configuration_timer = NULL;
+}
+
+static void
+FlushConfigurationTimer (XdgToplevel *toplevel)
+{
+ if (!toplevel->configuration_timer)
+ return;
+
+ /* Cancel the configuration timer and flush pending state to the
+ state array. It is assumed that a configure event will be sent
+ immediately afterwards. */
+
+ if (toplevel->state & StatePendingConfigureStates)
+ WriteStates (toplevel);
+
+ /* Clear the pending size and state flags. */
+ toplevel->state &= ~StatePendingConfigureSize;
+ toplevel->state &= ~StatePendingConfigureStates;
+
+ /* Cancel and clear the timer. */
+ RemoveTimer (toplevel->configuration_timer);
+ toplevel->configuration_timer = NULL;
+}
+
+static Bool
+MaybePostDelayedConfigure (XdgToplevel *toplevel, int flag)
+{
+ XLList *tem;
+
+ if (!batch_state_changes)
+ return False;
+
+ toplevel->state |= flag;
+
+ if (toplevel->configuration_timer)
+ {
+ /* The timer is already ticking... */
+ RetimeTimer (toplevel->configuration_timer);
+ return True;
+ }
+
+ /* If some seat is being resized, return False. */
+ for (tem = live_seats; tem; tem = tem->next)
+ {
+ if (XLSeatResizeInProgress (tem->data))
+ return False;
+ }
+
+ toplevel->configuration_timer
+ = AddTimer (NoteConfigureTime, toplevel,
+ MakeTimespec (0, DefaultStateDelayNanoseconds));
+ return True;
+}
+
static void
WriteStates (XdgToplevel *toplevel)
{
@@ -477,7 +607,9 @@ HandleWmStateChange (XdgToplevel *toplevel)
state->maximized = True;
}
- if (memcmp (&old, &state, sizeof *state))
+ if (memcmp (&old, &state, sizeof *state)
+ && !MaybePostDelayedConfigure (toplevel,
+ StatePendingConfigureStates))
/* Finally, send states if they changed. */
SendStates (toplevel);
@@ -826,6 +958,11 @@ Unmap (XdgToplevel *toplevel)
memset (&toplevel->state, 0, sizeof toplevel->states);
+ /* If there is a pending configure timer, remove it. */
+ if (toplevel->configuration_timer)
+ RemoveTimer (toplevel->configuration_timer);
+ toplevel->configuration_timer = NULL;
+
XLListFree (toplevel->resize_callbacks,
XLSeatCancelResizeCallback);
toplevel->resize_callbacks = NULL;
@@ -909,6 +1046,7 @@ Commit (Role *role, Surface *surface, XdgRoleImplementation *impl)
if (toplevel->state & StateIsMapped)
Unmap (toplevel);
+ FlushConfigurationTimer (toplevel);
SendConfigure (toplevel, 0, 0);
}
else if (!toplevel->conf_reply)
@@ -919,6 +1057,67 @@ Commit (Role *role, Surface *surface, XdgRoleImplementation *impl)
}
}
+static void
+PostResize1 (XdgToplevel *toplevel, int west_motion, int north_motion,
+ int new_width, int new_height)
+{
+ /* FIXME: the two computations below are still not completely
+ right. */
+
+ if (new_width < toplevel->min_width)
+ {
+ west_motion += toplevel->min_width - new_width;
+
+ /* Don't move too far west. */
+ if (west_motion > 0)
+ west_motion = 0;
+
+ new_width = toplevel->min_width;
+ }
+
+ if (new_height < toplevel->min_height)
+ {
+ north_motion += toplevel->min_height - new_height;
+
+ /* Don't move too far north. */
+ if (north_motion > 0)
+ north_motion = 0;
+
+ new_height = toplevel->min_height;
+ }
+
+ if (!(toplevel->state & StatePendingAckMovement))
+ {
+ FlushConfigurationTimer (toplevel);
+ SendConfigure (toplevel, new_width, new_height);
+
+ toplevel->ack_west += west_motion;
+ toplevel->ack_north += north_motion;
+ toplevel->state |= StatePendingAckMovement;
+
+ /* Clear extra state flags that are no longer useful. */
+ toplevel->state &= ~StatePendingResize;
+ toplevel->resize_west = 0;
+ toplevel->resize_north = 0;
+ toplevel->resize_width = 0;
+ toplevel->resize_height = 0;
+ }
+ else
+ {
+ /* A configure event has been posted but has not yet been fully
+ processed. Accumulate the new width, height, west and north
+ values, and send another configure event once it really does
+ arrive, and the previous changes have been committed. */
+
+ toplevel->state |= StatePendingResize;
+
+ toplevel->resize_west += west_motion;
+ toplevel->resize_north += north_motion;
+ toplevel->resize_width = new_width;
+ toplevel->resize_height = new_height;
+ }
+}
+
static void
CommitInsideFrame (Role *role, XdgRoleImplementation *impl)
{
@@ -935,6 +1134,14 @@ CommitInsideFrame (Role *role, XdgRoleImplementation *impl)
toplevel->ack_west = 0;
toplevel->ack_north = 0;
toplevel->state &= ~StatePendingAckMovement;
+
+ /* This pending movement has completed. Apply postponed state,
+ if there is any. */
+ if (toplevel->state & StatePendingResize)
+ PostResize1 (toplevel, toplevel->resize_west,
+ toplevel->resize_north,
+ toplevel->resize_width,
+ toplevel->resize_height);
}
}
@@ -994,7 +1201,14 @@ HandleConfigureEvent (XdgToplevel *toplevel, XEvent *event)
if (event->xconfigure.width == toplevel->width
&& event->xconfigure.height == toplevel->height)
- return True;
+ {
+ if (!toplevel->configuration_timer)
+ /* No configure event will be generated in the future.
+ Unfreeze the frame clock. */
+ XLXdgRoleNoteRejectedConfigure (toplevel->role);
+
+ return True;
+ }
/* Try to guess if the window state was restored to some earlier
value, and set it now, to avoid race conditions when some clients
@@ -1005,16 +1219,21 @@ HandleConfigureEvent (XdgToplevel *toplevel, XEvent *event)
event->xconfigure.height))
WriteStates (toplevel);
- XLXdgRoleCalcNewWindowSize (toplevel->role,
- ConfigureWidth (event),
- ConfigureHeight (event),
- &width, &height);
+ if (!MaybePostDelayedConfigure (toplevel, StatePendingConfigureSize))
+ {
+ XLXdgRoleCalcNewWindowSize (toplevel->role,
+ ConfigureWidth (event),
+ ConfigureHeight (event),
+ &width, &height);
- SendConfigure (toplevel, width, height);
+ SendConfigure (toplevel, width, height);
+ }
/* Set toplevel->width and toplevel->height correctly. */
toplevel->width = event->xconfigure.width;
toplevel->height = event->xconfigure.height;
+ toplevel->configure_width = toplevel->width;
+ toplevel->configure_height = toplevel->height;
/* Also set the bounds width and height to avoid resizing
the window. */
@@ -1177,27 +1396,8 @@ PostResize (Role *role, XdgRoleImplementation *impl, int west_motion,
XdgToplevel *toplevel;
toplevel = ToplevelFromRoleImpl (impl);
-
- if (new_width < toplevel->min_width)
- {
- new_width = toplevel->min_width;
-
- /* FIXME: this computation is not correct, just "good
- enough". */
- west_motion = 0;
- }
-
- if (new_height < toplevel->min_height)
- {
- new_height = toplevel->min_height;
- north_motion = 0;
- }
-
- SendConfigure (toplevel, new_width, new_height);
-
- toplevel->ack_west += west_motion;
- toplevel->ack_north += north_motion;
- toplevel->state |= StatePendingAckMovement;
+ PostResize1 (toplevel, west_motion, north_motion,
+ new_width, new_height);
}
static void
@@ -1858,6 +2058,7 @@ XLInitXdgToplevels (void)
{
latin_1_cd = iconv_open ("ISO-8859-1", "UTF-8");
apply_state_workaround = (getenv ("APPLY_STATE_WORKAROUND") != NULL);
+ batch_state_changes = !getenv ("DIRECT_STATE_CHANGES");
}
Bool