diff --git a/tests/select_helper_multiple.c b/tests/select_helper_multiple.c
new file mode 100644
index 0000000..09eda56
--- /dev/null
+++ b/tests/select_helper_multiple.c
@@ -0,0 +1,245 @@
+/* Tests for the Wayland compositor running on the X server.
+
+Copyright (C) 2022 to various contributors.
+
+This file is part of 12to11.
+
+12to11 is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation, either version 3 of the License, or (at your
+option) any later version.
+
+12to11 is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with 12to11. If not, see . */
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+/* select_helper_multiple.c -- Perform a request for multiple
+ selections.
+
+ There must be at least three arguments: the name of the display,
+ the timestamp at which the selection was acquired, and the
+ target(s). */
+
+/* The display connected to. */
+static Display *display;
+
+/* The selection transfer window. */
+static Window selection_transfer_window;
+
+/* Various atoms. */
+static Atom CLIPBOARD, INCR, MULTIPLE, ATOM_PAIR, *target_atoms;
+
+static void
+wait_for_selection_notify (XEvent *event)
+{
+ while (true)
+ {
+ XNextEvent (display, event);
+
+ if (event->type == SelectionNotify
+ && (event->xselection.requestor
+ == selection_transfer_window)
+ && (event->xselection.selection
+ == CLIPBOARD)
+ && (event->xselection.property
+ == MULTIPLE)
+ && (event->xselection.target
+ == MULTIPLE))
+ return;
+ }
+}
+
+static void
+wait_for_new_value (XEvent *event, Atom property)
+{
+ while (true)
+ {
+ XNextEvent (display, event);
+
+ if (event->type == PropertyNotify
+ && event->xproperty.atom == property
+ && event->xproperty.state == PropertyNewValue)
+ return;
+ }
+}
+
+static size_t
+get_size_for_format (int format)
+{
+ switch (format)
+ {
+ case 32:
+ return sizeof (long);
+
+ case 16:
+ return sizeof (short int);
+
+ case 8:
+ return sizeof (char);
+ }
+
+ /* Should not actually happen. */
+ return 0;
+}
+
+int
+main (int argc, char **argv)
+{
+ XSetWindowAttributes attrs;
+ unsigned long flags, timestamp;
+ char *atom_names[4];
+ Atom atoms[4], actual_type, property, *parameters;
+ XEvent event;
+ int actual_format, i;
+ unsigned long nitems, bytes_after;
+ unsigned char *data;
+
+ if (argc < 4)
+ /* Not enough arguments were specified. */
+ return 1;
+
+ display = XOpenDisplay (argv[1]);
+
+ if (!display)
+ return 1;
+
+ /* Make the window used to transfer selection data. */
+ attrs.override_redirect = True;
+ attrs.event_mask = PropertyChangeMask;
+ flags = CWEventMask | CWOverrideRedirect;
+
+ selection_transfer_window
+ = XCreateWindow (display, DefaultRootWindow (display),
+ -1, -1, 1, 1, 0, CopyFromParent, InputOnly,
+ CopyFromParent, flags, &attrs);
+
+ /* Get the time. */
+ timestamp = strtoul (argv[2], NULL, 10);
+
+ atom_names[0] = (char *) "CLIPBOARD";
+ atom_names[1] = (char *) "INCR";
+ atom_names[2] = (char *) "MULTIPLE";
+ atom_names[3] = (char *) "ATOM_PAIR";
+ XInternAtoms (display, atom_names, 4, False, atoms);
+ CLIPBOARD = atoms[0];
+ INCR = atoms[1];
+ MULTIPLE = atoms[2];
+ ATOM_PAIR = atoms[3];
+
+ /* Intern the target atoms. */
+ target_atoms = malloc (sizeof *target_atoms * argc - 3);
+ XInternAtoms (display, &argv[3], argc - 3, False, target_atoms);
+
+ /* Write each of the atoms as parameters before making the selection
+ request. */
+ atoms[0] = target_atoms[0];
+ atoms[1] = target_atoms[0];
+ XChangeProperty (display, selection_transfer_window, MULTIPLE,
+ ATOM_PAIR, 32, PropModeReplace,
+ (unsigned char *) atoms, 2);
+
+ for (i = 1; i < argc - 3; ++i)
+ {
+ atoms[0] = target_atoms[i];
+ atoms[1] = target_atoms[i];
+ XChangeProperty (display, selection_transfer_window, MULTIPLE,
+ ATOM_PAIR, 32, PropModeAppend,
+ (unsigned char *) atoms, 2);
+ }
+
+ /* Now, request the selection. */
+ XConvertSelection (display, CLIPBOARD, MULTIPLE,
+ MULTIPLE, selection_transfer_window,
+ timestamp);
+
+ /* And wait for the SelectionNotify event. */
+ wait_for_selection_notify (&event);
+
+ /* Selection conversion failed. */
+ if (event.xselection.property == None)
+ return 1;
+
+ /* Now, read the MULTIPLE property again. See which conversions
+ were completed. */
+ XGetWindowProperty (display, selection_transfer_window,
+ event.xselection.property, 0, (argc - 3) * 2,
+ True, ATOM_PAIR,
+ &actual_type, &actual_format, &nitems,
+ &bytes_after, &data);
+
+ if (actual_format != 32 || actual_type != ATOM_PAIR
+ || nitems != (argc - 3) * 2)
+ return 1;
+
+ parameters = (Atom *) data;
+
+ for (i = 0; i < argc - 3; ++i)
+ {
+ if (!parameters[i * 2 + 1])
+ continue;
+
+ property = parameters[i * 2];
+
+ XGetWindowProperty (display, selection_transfer_window,
+ property, 0, 0xffffffff, True, AnyPropertyType,
+ &actual_type, &actual_format, &nitems, &bytes_after,
+ &data);
+
+ if (!data || bytes_after)
+ return 1;
+
+ if (actual_type == INCR)
+ {
+ while (true)
+ {
+ XFree (data);
+
+ wait_for_new_value (&event, property);
+ XGetWindowProperty (display, selection_transfer_window, property, 0,
+ 0xffffffff, True, AnyPropertyType, &actual_type,
+ &actual_format, &nitems, &bytes_after, &data);
+
+ if (!data)
+ return 0;
+
+ if (nitems)
+ {
+ /* Write the selection data to stdout. */
+ if (fwrite (data, get_size_for_format (actual_format),
+ nitems, stdout) != nitems)
+ return 1;
+
+ continue;
+ }
+
+ /* Selection transfer is complete. */
+ fflush (stdout);
+ break;
+ }
+ }
+ else
+ {
+ /* Write the selection data to stdout. */
+ if (fwrite (data, get_size_for_format (actual_format),
+ nitems, stdout) != nitems)
+ return 1;
+
+ /* Return success. */
+ fflush (stdout);
+ }
+ }
+
+ return 0;
+}