Various improvements to primary selections and resize handling

* 12to11.man: Document new environment variables.
* Imakefile ($(OBJS)): Also depend on protocol headers.
* README: Update implementation status.
* compositor.h: Update prototypes.
* data_device.c (DataOfferSetActions): Fix error sent when the
offer is invalid.
* dnd.c: Remove out of date comment.

* primary_selection.c (struct _PDataSource): Move declaration to
compositor.h.  New field `atom_types'.
(Offer): Record atom types as well.
(HandleSourceResourceDestroy): Maybe disown the primary
selection, and free offered MIME types.
(XLResourceFromPDataSource, XLPDataSourceHasAtomTarget)
(XLPDataSourceHasTarget, XLPDataSourceTargetCount)
(XLPDataSourceGetTargets): New functions.
(UpdateForSingleReference): Handle foreign selections correctly.
(SetSelection): Try to own the X selection.
(XLSetForeignPrimary, XLClearForeignPrimary): New functions.

* seat.c (XLSeatResizeInProgress): New function.

* xdata.c (struct _ReadTargetsData): New field `selection'.
(struct _DataConversion): Pass send function and source resource
to conversion function.
(last_primary_time): New timestamp variable.
(x_primary_targets, num_x_primary_targets, primary_data_source):
New variables.
(HasSelectionTarget, FindTranslationForMimeType): Accept arg
`is_primary', meaning that the primary selection data should be
used instead.
(ReceiveBody): Extract generic code from Receive.
(Receive): Replace with above macro.
(Finish, SetActions): Correctly post errors on DND-specific
actions.
(ReceivePrimary): New function.
(primary_offer_impl): New interface implementation.
(CreatePrimaryOffer): New function.
(SendOffers1): Extract generic code from SendOffers.
(SendOffers, SendPrimaryOffers): Reimplement in terms of
SendOffers1.
(HandleNewSelection): Handle PRIMARY selection changes.
(TargetsFinishCallback): Pass selection to HandleNewSelection.
(NoticeClipboardChanged): Set TargetsReadData selection.
(NoticePrimaryChanged): New function.
(NoticeClipboardCleared): Free unused data.
(NoticePrimaryCleared): New function.
(HandleSelectionNotify): Handle notifications for both CLIPBOARD
and PRIMARY.
(MimeTypeFromTarget, TypeFromTarget): Accept argument `primary',
meaning to use the primary selection when determining whether or
not to call a conversion callback.
(GetClipboardCallback): Pass resource and functions to the
clipboard callback.
(GetConversionCallback): Update to use provided functions and
resource.
(GetDragCallback, XLNoteSourceDestroyed): Fix typos in debug
string.
(XLNotePrimaryDestroyed): New function.
(NoteLocalSelectionBody, NoteLocalSelectionFooter): Extract
selection-generic code from XLNoteLocalSelection.
(XLNoteLocalSelection): Reimplement in terms of the above two
macros.
(XLNoteLocalPrimary, GetPrimaryCallback): New functions.
(XLInitXData): Select for selection input from XA_PRIMARY as
well.
* xdg_surface.c (AckConfigure): Improve debug code.
(Commit): Clear that flag.
(NoteBounds): If that flag is set, return.
(HandleFreeze): Assume a configure event will arrive after a
sync request; set flags accordingly.
(XLGetXdgSurface): Add HandleFreeze.
(XLXdgRoleSendConfigure): Clear some flags and add new state
flag `StateWaitingForAckCommit'.  Set it.  It means that no
commit has yet happened after ack_configure.
(XLXdgRoleNoteRejectedConfigure): New function.  Clear flags if
the configure event following _NET_WM_SYNC_REQUEST was rejected.
* xdg_toplevel.c (struct _XdgToplevel): New state flags
StatePendingResize, StateConfigureSize,
StatePendingConfigureStates.  New field `configuration_timer'.
(batch_state_changes): New flag.
(DestroyBacking): Clear configuration timer.
(NoteConfigureTime): New function; apply queued state changes.
(HandleWmStateChange, HandleConfigureEvent): By default, queue
state changes and configure events so they can be applied in one
go.
(Unmap): Clear delayed configuration timer.
(Commit, CommitInsideFrame, PostResize): Improve movement delta
adjustment for min_width and min_height, and queue pending
motion for after ack_configure.
(XLInitXdgToplevels): Set `batch_state_changes' depending on the
DIRECT_STATE_CHANGES' environment variable.
This commit is contained in:
oldosfan 2022-09-15 02:08:55 +00:00
parent 40d19a4595
commit 112c4d4fbe
11 changed files with 1065 additions and 177 deletions

View file

@ -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

View file

@ -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 $< $@

4
README
View file

@ -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.

View file

@ -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. */

View file

@ -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;
}

3
dnd.c
View file

@ -30,9 +30,6 @@ along with 12to11. If not, see <https://www.gnu.org/licenses/>. */
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,

View file

@ -24,7 +24,6 @@ along with 12to11. If not, see <https://www.gnu.org/licenses/>. */
#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;
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)
{

6
seat.c
View file

@ -5129,3 +5129,9 @@ XLSeatGetEffectiveModifiers (Seat *seat)
{
return seat->base | seat->locked | seat->latched;
}
Bool
XLSeatResizeInProgress (Seat *seat)
{
return seat->resize_in_progress;
}

596
xdata.c
View file

@ -30,10 +30,11 @@ along with 12to11. If not, see <https://www.gnu.org/licenses/>. */
#include <iconv.h>
#include "compositor.h"
#include "primary-selection-unstable-v1.h"
#include <X11/extensions/Xfixes.h>
/* 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,13 +383,22 @@ 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]])))
&& 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,12 +867,11 @@ 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,
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;

View file

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

View file

@ -42,6 +42,9 @@ enum
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)
{
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);
if (!MaybePostDelayedConfigure (toplevel, StatePendingConfigureSize))
{
XLXdgRoleCalcNewWindowSize (toplevel->role,
ConfigureWidth (event),
ConfigureHeight (event),
&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