From 6c7801f0fdd901e020e4dfabccc9c69a8dc2ab1f Mon Sep 17 00:00:00 2001 From: oldosfan Date: Tue, 13 Sep 2022 11:41:07 +0000 Subject: [PATCH] Implement primary selections, and minor improvements elsewhere * 12to11.c (XLMain): Initialize primary selections. Transfer between X and Wayland programs is still incomplete. * Imakefile (SRCS, OBJS): Add primary selection related objects and sources. (primary-selection-unstable-v1.h): (primary-selection-unstable-v1.c): New targets. * README: Update what is not supported. * compositor.h: New prototypes. * data_device.c (XLDataDeviceSendEnter): Handle resource allocation failures. * mime1.awk: Update generated code for changes in target entry structures. * seat.c (SetFocusSurface): Handle focus change for primary selections as well. (FindSurfaceUnder): Cut off fractional portion instead of rounding the given coordinates, so the correct surface is found when the cursor is moved just inside the rightmost pixel. * surface.c (XLSurfaceRunFrameCallbacks): Handle timestamp overflow. * xdata.c (struct _TargetMapping): Rename atom to atom_flag, and use it to store flags. (MappingAtom, MappingFlag, MappingIsNextDuplicate, MappingSetFlag) (MappingUnsetFlag, MappingIs): New macros. (struct _TargetMappingTable): New structure. (Duplicate): New definition. (direct_transfer): Update duplicate types. (mapping_table): New hash table. (HashMimeString, SetupMappingTable): New functions. (FindTranslationForMimeType, Receive): Use the target mapping table to look up targets instead. (CheckDuplicate): New function. (SendOffers): Call CheckDuplicates. (XLInitXData): Set up duplicate relationship between UTF8_STRING and is conversion entry, and the targets mapping table. --- 12to11.c | 1 + Imakefile | 20 ++++-- README | 7 +- compositor.h | 5 ++ data_device.c | 6 ++ mime1.awk | 2 +- seat.c | 15 +++- surface.c | 8 ++- xdata.c | 186 +++++++++++++++++++++++++++++++++++++++++++++----- 9 files changed, 219 insertions(+), 31 deletions(-) diff --git a/12to11.c b/12to11.c index e88ca82..f7fb456 100644 --- a/12to11.c +++ b/12to11.c @@ -155,6 +155,7 @@ XLMain (int argc, char **argv) XLInitXData (); XLInitXSettings (); XLInitIconSurfaces (); + XLInitPrimarySelection (); /* This has to come after the rest of the initialization. */ DetermineServerTime (); XLRunCompositor (); diff --git a/Imakefile b/Imakefile index 3a4395f..8c89e6a 100644 --- a/Imakefile +++ b/Imakefile @@ -21,14 +21,16 @@ SRCS = 12to11.c run.c alloc.c fns.c output.c compositor.c \ positioner.c xdg_wm.c xdg_surface.c xdg_toplevel.c \ frame_clock.c xerror.c ewmh.c timer.c subsurface.c seat.c \ data_device.c xdg_popup.c linux-dmabuf-unstable-v1.c dmabuf.c \ - buffer.c select.c xdata.c xsettings.c dnd.c icon_surface.c + buffer.c select.c xdata.c xsettings.c dnd.c icon_surface.c \ + primary-selection-unstable-v1.c primary_selection.c OBJS = 12to11.o run.o alloc.o fns.o output.o compositor.o \ xdg-shell.o surface.o region.o shm.o atoms.o subcompositor.o \ positioner.o xdg_wm.o xdg_surface.o xdg_toplevel.o \ frame_clock.o xerror.o ewmh.o timer.o subsurface.o seat.o \ data_device.o xdg_popup.o linux-dmabuf-unstable-v1.o dmabuf.o \ - buffer.o select.o xdata.o xsettings.o dnd.o icon_surface.o + buffer.o select.o xdata.o xsettings.o dnd.o icon_surface.o \ + primary-selection-unstable-v1.o primary_selection.o OPTIMIZE = -O0 ANALYZE = -fanalyzer @@ -53,7 +55,7 @@ CDEBUGFLAGS := -fno-common -Wall -Warith-conversion -Wdate-time \ $(ANALYZE) short_types.txt: media_types.txt -XCOMM remove all data types starting with application/vnd. +XCOMM Remove all data types starting with application/vnd. XCOMM no program really uses them in clipboard data, and they XCOMM waste a lot of space on disk. sed '/application\/vnd/d' media_types.txt > $@ @@ -81,9 +83,17 @@ xdg-shell.h: xdg-shell.xml xdg-shell.c: xdg-shell.xml xdg-shell.h $(WAYLAND_SCANNER) private-code $< $@ +primary-selection-unstable-v1.h: primary-selection-unstable-v1.xml + $(WAYLAND_SCANNER) server-header $< $@ + +primary-selection-unstable-v1.c: primary-selection-unstable-v1.xml \ + primary-selection-unstable-v1.h + $(WAYLAND_SCANNER) private-code $< $@ + cleandir:: - $(RM) linux-dmabuf-unstable-v1.c linux-dmabuf-unstable-v1.h \ - xdg-shell.c xdg-shell.h + $(RM) linux-dmabuf-unstable-v1.c linux-dmabuf-unstable-v1.h \ + xdg-shell.c xdg-shell.h primary-selection-unstable-v1.c \ + primary-selection-unstable-v1.h $(RM) transfer_atoms.h short_types.txt /* Undefine _BSD_SOURCE and _SVID_SOURCE, since both are deprecated diff --git a/README b/README index 113fd34..b4ba266 100644 --- a/README +++ b/README @@ -42,14 +42,15 @@ complete degree: 'wl_seat', version: 7 'wl_data_device_manager', version: 3 'zwp_linux_dmabuf_v1', version: 4 + 'zwp_primary_selection_device_manager_v1', version: 1 With the main caveat being that zwp_linux_dmabuf_v1 has no real support for multiple-provider setups (help wanted). -Primary selections and window decorations are also not supported, even -though they fit in nicely with X window management. +Window decorations are also not supported, even though they fit in +nicely with X window management. -It would also be nice to have pinch gesture support in wl_pointer. +It would also be nice to have pinch gesture support. This directory is organized as follows: diff --git a/compositor.h b/compositor.h index b1779b7..afa50bc 100644 --- a/compositor.h +++ b/compositor.h @@ -965,6 +965,11 @@ extern void XLInitIconSurfaces (void); extern void XLReleaseIconSurface (IconSurface *); extern Bool XLIsWindowIconSurface (Window); +/* Defined in primary_selection.c. */ + +extern void XLInitPrimarySelection (void); +extern void XLPrimarySelectionHandleFocusChange (Seat *); + /* Utility functions that don't belong in a specific file. */ #define ArrayElements(arr) (sizeof (arr) / sizeof (arr)[0]) diff --git a/data_device.c b/data_device.c index ceaa13b..82b3b83 100644 --- a/data_device.c +++ b/data_device.c @@ -1364,6 +1364,11 @@ XLDataDeviceSendEnter (Seat *seat, Surface *surface, double x, double y, /* First, create a data offer corresponding to the data source if it exists. */ resource = AddDataOffer (client, source); + + if (!resource) + /* Allocation of the resource failed. */ + goto next; + offer = wl_resource_get_user_data (resource); offer->dnd_serial = serial; offer->last_action = -1; @@ -1397,6 +1402,7 @@ XLDataDeviceSendEnter (Seat *seat, Surface *surface, double x, double y, } + next: reference = reference->next; } } diff --git a/mime1.awk b/mime1.awk index 4bc44c2..a23f7ee 100644 --- a/mime1.awk +++ b/mime1.awk @@ -26,7 +26,7 @@ BEGIN { match ($0, / ([a-z]+\/[-+.[:alnum:]]+) /, array) name = array[1] gsub (/[[:punct:]]/, "_", name) # Convert to a valid atom name - printf " table[%d + start].atom = %s;\\\n", i++, name + printf " table[%d + start].atom_flag = %s;\\\n", i++, name } END { diff --git a/seat.c b/seat.c index cf0cfcc..713e027 100644 --- a/seat.c +++ b/seat.c @@ -2625,7 +2625,12 @@ SetFocusSurface (Seat *seat, Surface *focus) } if (!focus) - return; + { + /* These changes must be handled even if there is no more focus + surface. */ + XLPrimarySelectionHandleFocusChange (seat); + return; + } seat->focus_surface = focus; seat->focus_destroy_callback @@ -2633,6 +2638,8 @@ SetFocusSurface (Seat *seat, Surface *focus) SendKeyboardEnter (seat, focus); + XLPrimarySelectionHandleFocusChange (seat); + if (seat->data_device) XLDataDeviceHandleFocusChange (seat->data_device); } @@ -2670,8 +2677,10 @@ FindSurfaceUnder (Subcompositor *subcompositor, double x, double y) int x_off, y_off; View *view; - view = SubcompositorLookupView (subcompositor, lrint (x), - lrint (y), &x_off, &y_off); + /* Do not round these figures. Instead, cut off the fractional, + like the X server does when deciding when to set the cursor. */ + view = SubcompositorLookupView (subcompositor, x, y, + &x_off, &y_off); if (view) return ViewGetData (view); diff --git a/surface.c b/surface.c index e8b0a8c..3ea9110 100644 --- a/surface.c +++ b/surface.c @@ -1167,7 +1167,13 @@ XLSurfaceRunFrameCallbacks (Surface *surface, struct timespec time) uint32_t ms_time; XLList *list; - ms_time = time.tv_sec * 1000 + time.tv_nsec / 1000000; + /* I don't know what else is reasonable in case of overflow. */ + + if (IntMultiplyWrapv (time.tv_sec, 1000, &ms_time)) + ms_time = UINT32_MAX; + else if (IntAddWrapv (ms_time, time.tv_nsec / 1000000, + &ms_time)) + ms_time = UINT32_MAX; RunFrameCallbacks (&surface->current_state.frame_callbacks, ms_time); diff --git a/xdata.c b/xdata.c index 4ddfa1e..ee20847 100644 --- a/xdata.c +++ b/xdata.c @@ -42,6 +42,7 @@ typedef struct _ConversionTransferInfo ConversionTransferInfo; typedef struct _WriteInfo WriteInfo; typedef struct _ConversionWriteInfo ConversionWriteInfo; typedef struct _DataConversion DataConversion; +typedef struct _TargetMappingTable TargetMappingTable; struct _ReadTargetsData { @@ -54,8 +55,12 @@ struct _ReadTargetsData struct _TargetMapping { - /* The atom of the X target. */ - Atom atom; + /* The atom of the X target. The top 3 bits of an XID are + guaranteed to be 0, so the 31st bit is used to store a flag + meaning that the next entry is a duplicate of this one, and the + 30th bit is used to store a flag containing state used by + SendOffers. */ + Atom atom_flag; /* The name of the Wayland MIME type. */ const char *mime_type; @@ -64,6 +69,13 @@ struct _TargetMapping void (*translation_func) (Time, Atom, Atom, int); }; +#define MappingAtom(mapping) ((mapping)->atom_flag & 0x1fffffff) +#define MappingFlag(mapping) ((mapping)->atom_flag & (1 << 29)) +#define MappingIsNextDuplicate(mapping) ((mapping)->atom_flag & (1 << 30)) +#define MappingSetFlag(mapping) ((mapping)->atom_flag |= (1 << 29)) +#define MappingUnsetFlag(mapping) ((mapping)->atom_flag &= ~(1 << 29)) +#define MappingIs(mapping, atom) (MappingAtom (mapping) == (atom)) + struct _DataConversion { /* The MIME type of the Wayland offer. */ @@ -179,20 +191,43 @@ struct _ConversionWriteInfo iconv_t cd; }; +struct _TargetMappingTable +{ + /* Array of indices into direct_transfer. */ + unsigned short *buckets[32]; + + /* Number of elements in each array. */ + unsigned short n_elements[32]; + + /* Array of indices into direct_transfer. */ + unsigned short *atom_buckets[16]; + + /* Number of elements in each array. */ + unsigned short n_atom_elements[16]; +}; + /* Base event code of the Xfixes extension. */ static int fixes_event_base; +/* This means the next item in the targets mapping table has the same + MIME type as this one. */ + +#define Duplicate (1U << 30) + /* Map of targets that can be transferred from X to Wayland clients and vice versa. */ static TargetMapping direct_transfer[] = { - { XA_STRING, "text/plain;charset=iso-9889-1" }, - { 0, "text/plain;charset=utf-8" }, - { XA_STRING, "text/plain;charset=utf-8" }, + { XA_STRING, "text/plain;charset=iso-9889-1" }, + { Duplicate, "text/plain;charset=utf-8" }, + { XA_STRING, "text/plain;charset=utf-8" }, /* These mappings are automatically generated. */ DirectTransferMappings }; +/* Lookup table for such mappings. */ +static TargetMappingTable mapping_table; + /* Map of Wayland offer types to X atoms and data conversion functions. */ static DataConversion data_conversions[] = @@ -244,6 +279,53 @@ Accept (struct wl_client *client, struct wl_resource *resource, /* Nothing has to be done here yet. */ } +static unsigned int +HashMimeString (const char *string) +{ + unsigned int i; + + i = 3323198485ul; + for (; *string; ++string) + { + i ^= *string; + i *= 0x5bd1e995; + i ^= i >> 15; + } + return i; +} + +static void +SetupMappingTable (void) +{ + unsigned int idx, i, nelements; + + /* This is needed for the atoms table, since the atom indices are + determined by the X server. */ + XLAssert (ArrayElements (direct_transfer) <= USHRT_MAX); + + for (i = 0; i < ArrayElements (direct_transfer); ++i) + { + idx = HashMimeString (direct_transfer[i].mime_type) % 32; + + nelements = ++mapping_table.n_elements[idx]; + mapping_table.buckets[idx] + = XLRealloc (mapping_table.buckets[idx], + nelements * sizeof (unsigned short)); + mapping_table.buckets[idx][nelements - 1] = i; + + /* Now, set up the lookup table indexed by atoms. It is faster + to compare atoms than strings, so the table is smaller. */ + + idx = MappingAtom (&direct_transfer[i]) % 16; + + nelements = ++mapping_table.n_atom_elements[idx]; + mapping_table.atom_buckets[idx] + = XLRealloc (mapping_table.atom_buckets[idx], + nelements * sizeof (unsigned short)); + mapping_table.atom_buckets[idx][nelements - 1] = i; + } +} + static Bool HasSelectionTarget (Atom atom) { @@ -261,13 +343,18 @@ HasSelectionTarget (Atom atom) static TargetMapping * FindTranslationForMimeType (const char *mime_type) { - int i; + unsigned short *buckets, i; + unsigned int idx; - for (i = 0; i < ArrayElements (direct_transfer); ++i) + idx = HashMimeString (mime_type) % 32; + buckets = mapping_table.buckets[idx]; + + for (i = 0; i < mapping_table.n_elements[idx]; ++i) { - if (!strcmp (direct_transfer[i].mime_type, mime_type) - && HasSelectionTarget (direct_transfer[i].atom)) - return &direct_transfer[i]; + if (!strcmp (direct_transfer[buckets[i]].mime_type, + mime_type) + && HasSelectionTarget (MappingAtom (&direct_transfer[buckets[i]]))) + return &direct_transfer[buckets[i]]; } return NULL; @@ -575,11 +662,13 @@ Receive (struct wl_client *client, struct wl_resource *resource, { if (!translation->translation_func) /* If a corresponding target exists, ask to receive it. */ - PostReceiveDirect (time, CLIPBOARD, translation->atom, fd); + PostReceiveDirect (time, CLIPBOARD, + MappingAtom (translation), fd); else /* Otherwise, use the translation function. */ translation->translation_func (time, CLIPBOARD, - translation->atom, fd); + MappingAtom (translation), + fd); } else close (fd); @@ -632,10 +721,55 @@ CreateOffer (struct wl_client *client, Time time) return resource; } +static Bool +CheckDuplicate (unsigned short index, Atom a) +{ + TargetMapping *start; + + start = &direct_transfer[index]; + + /* If the flag is already set, then this type has already been + sent. */ + if (MappingFlag (start)) + return False; + + /* Set this entry's duplicate flag. */ + MappingSetFlag (start); + + /* As long as the next index still refers to a duplicate of this + item, set its duplicate flag. */ + + while (MappingIsNextDuplicate (start)) + MappingSetFlag (++start); + + /* Do the same backwards. */ + + if (index) + { + start = &direct_transfer[index - 1]; + + while (MappingIsNextDuplicate (start)) + { + MappingSetFlag (start); + + /* If this is now the start of the target mapping table, + break. */ + if (start == direct_transfer) + break; + + start--; + } + } + + return True; +} + static void SendOffers (struct wl_resource *resource, Time time) { int i, j; + unsigned int idx; + unsigned short *buckets; if (time < last_x_selection_change) /* This offer is out of date. */ @@ -644,16 +778,29 @@ SendOffers (struct wl_resource *resource, Time time) for (i = 0; i < num_x_selection_targets; ++i) { /* Offer each type corresponding to this target. */ + idx = x_selection_targets[i] % 16; - for (j = 0; j < ArrayElements (direct_transfer); ++j) + /* N.B. that duplicates do appear in the atom buckets, which + is intentional. */ + buckets = mapping_table.atom_buckets[idx]; + + for (j = 0; j < mapping_table.n_atom_elements[idx]; ++j) { - if (direct_transfer[j].atom == x_selection_targets[i]) - /* If it exists, offer it to the client. TODO: handle - duplicates. */ + if (MappingIs (&direct_transfer[buckets[j]], + x_selection_targets[i]) + && CheckDuplicate (buckets[j], + x_selection_targets[i])) + /* If it exists and was not previously offered, offer it + to the client. */ wl_data_offer_send_offer (resource, - direct_transfer[j].mime_type); + direct_transfer[buckets[j]].mime_type); } } + + /* Clear the duplicate flag of each item in the targets table that + was touched. */ + for (i = 0; i < ArrayElements (direct_transfer); ++i) + MappingUnsetFlag (&direct_transfer[i]); } static void @@ -1751,10 +1898,13 @@ XLInitXData (void) SelectSelectionInput (CLIPBOARD); /* Initialize atoms used in the direct transfer table. */ - direct_transfer[1].atom = UTF8_STRING; + direct_transfer[1].atom_flag = UTF8_STRING | Duplicate; direct_transfer[2].translation_func = PostReceiveConversion; DirectTransferInitializer (direct_transfer, 3); + /* Set up the direct transfer table. */ + SetupMappingTable (); + /* And those used in the data conversions table. */ data_conversions[0].atom = UTF8_STRING; data_conversions[0].type = UTF8_STRING;