[mutter] wayland: Implement the (so far internal) primary selection protocol
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [mutter] wayland: Implement the (so far internal) primary selection protocol
- Date: Fri, 26 Feb 2016 18:57:07 +0000 (UTC)
commit 7c114360d0d9b15748999d0c759684dc18bd2cb3
Author: Carlos Garnacho <carlosg gnome org>
Date: Wed Feb 3 18:39:58 2016 +0100
wayland: Implement the (so far internal) primary selection protocol
Add an additional MetaWaylandDataSource implementation for primary selection
sources, and methods to set primary selection offers. Primary selection
sets altogether a different channel than the clipboard selection, those don't
cross in any way.
Also, the bridge for the X11 PRIMARY selection atom has been added, which
adds all the necessary handling to translate primary selection both ways
with wayland and X11 applications.
https://bugzilla.gnome.org/show_bug.cgi?id=762560
src/wayland/meta-wayland-data-device-private.h | 6 +
src/wayland/meta-wayland-data-device.c | 363 ++++++++++++++++++++++--
src/wayland/meta-wayland-data-device.h | 7 +
src/wayland/meta-xwayland-selection.c | 17 +-
4 files changed, 368 insertions(+), 25 deletions(-)
---
diff --git a/src/wayland/meta-wayland-data-device-private.h b/src/wayland/meta-wayland-data-device-private.h
index 9f5377a..5e65130 100644
--- a/src/wayland/meta-wayland-data-device-private.h
+++ b/src/wayland/meta-wayland-data-device-private.h
@@ -31,4 +31,10 @@ G_DECLARE_FINAL_TYPE (MetaWaylandDataSourceWayland,
META, WAYLAND_DATA_SOURCE_WAYLAND,
MetaWaylandDataSource);
+#define META_TYPE_WAYLAND_DATA_SOURCE_PRIMARY (meta_wayland_data_source_primary_get_type ())
+G_DECLARE_FINAL_TYPE (MetaWaylandDataSourcePrimary,
+ meta_wayland_data_source_primary,
+ META, WAYLAND_DATA_SOURCE_PRIMARY,
+ MetaWaylandDataSourceWayland);
+
#endif /* META_WAYLAND_DATA_DEVICE_PRIVATE_H */
diff --git a/src/wayland/meta-wayland-data-device.c b/src/wayland/meta-wayland-data-device.c
index b0652a0..c6a98ff 100644
--- a/src/wayland/meta-wayland-data-device.c
+++ b/src/wayland/meta-wayland-data-device.c
@@ -37,6 +37,8 @@
#include "meta-wayland-private.h"
#include "meta-dnd-actor-private.h"
+#include "gtk-primary-selection-server-protocol.h"
+
#define ALL_ACTIONS (WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | \
WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | \
WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
@@ -70,13 +72,24 @@ typedef struct _MetaWaylandDataSourceWayland
struct wl_resource *resource;
} MetaWaylandDataSourceWayland;
+typedef struct _MetaWaylandDataSourcePrimary
+{
+ MetaWaylandDataSourceWayland parent;
+
+ struct wl_resource *resource;
+} MetaWaylandDataSourcePrimary;
+
G_DEFINE_TYPE_WITH_PRIVATE (MetaWaylandDataSource, meta_wayland_data_source,
G_TYPE_OBJECT);
G_DEFINE_TYPE (MetaWaylandDataSourceWayland, meta_wayland_data_source_wayland,
META_TYPE_WAYLAND_DATA_SOURCE);
+G_DEFINE_TYPE (MetaWaylandDataSourcePrimary, meta_wayland_data_source_primary,
+ META_TYPE_WAYLAND_DATA_SOURCE);
static MetaWaylandDataSource *
meta_wayland_data_source_wayland_new (struct wl_resource *resource);
+static MetaWaylandDataSource *
+meta_wayland_data_source_primary_new (struct wl_resource *resource);
static void
drag_grab_data_source_destroyed (gpointer data, GObject *where_the_object_was);
@@ -160,7 +173,8 @@ static void
meta_wayland_data_source_target (MetaWaylandDataSource *source,
const char *mime_type)
{
- META_WAYLAND_DATA_SOURCE_GET_CLASS (source)->target (source, mime_type);
+ if (META_WAYLAND_DATA_SOURCE_GET_CLASS (source)->target)
+ META_WAYLAND_DATA_SOURCE_GET_CLASS (source)->target (source, mime_type);
}
void
@@ -351,7 +365,8 @@ data_offer_receive (struct wl_client *client, struct wl_resource *resource,
}
static void
-data_offer_destroy (struct wl_client *client, struct wl_resource *resource)
+default_destructor (struct wl_client *client,
+ struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
@@ -425,12 +440,43 @@ data_offer_set_actions (struct wl_client *client,
static const struct wl_data_offer_interface data_offer_interface = {
data_offer_accept,
data_offer_receive,
- data_offer_destroy,
+ default_destructor,
data_offer_finish,
data_offer_set_actions,
};
static void
+primary_offer_receive (struct wl_client *client, struct wl_resource *resource,
+ const char *mime_type, int32_t fd)
+{
+ MetaWaylandDataOffer *offer = wl_resource_get_user_data (resource);
+ MetaWaylandDataSource *source = offer->source;
+ MetaWaylandSeat *seat;
+
+ if (!offer->source)
+ {
+ close (fd);
+ return;
+ }
+
+ seat = meta_wayland_data_source_get_seat (source);
+
+ if (wl_resource_get_client (offer->resource) !=
+ meta_wayland_keyboard_get_focus_client (&seat->keyboard))
+ {
+ close (fd);
+ return;
+ }
+
+ meta_wayland_data_source_send (offer->source, mime_type, fd);
+}
+
+static const struct gtk_primary_selection_offer_interface primary_offer_interface = {
+ primary_offer_receive,
+ default_destructor,
+};
+
+static void
meta_wayland_data_source_notify_drop_performed (MetaWaylandDataSource *source)
{
META_WAYLAND_DATA_SOURCE_GET_CLASS (source)->drop_performed (source);
@@ -500,6 +546,35 @@ meta_wayland_data_source_send_offer (MetaWaylandDataSource *source,
return offer->resource;
}
+static struct wl_resource *
+meta_wayland_data_source_send_primary_offer (MetaWaylandDataSource *source,
+ struct wl_resource *target)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+ MetaWaylandDataOffer *offer = g_slice_new0 (MetaWaylandDataOffer);
+ char **p;
+
+ offer->source = source;
+ g_object_add_weak_pointer (G_OBJECT (source), (gpointer *)&offer->source);
+ offer->resource = wl_resource_create (wl_resource_get_client (target),
+ >k_primary_selection_offer_interface,
+ wl_resource_get_version (target), 0);
+ wl_resource_set_implementation (offer->resource,
+ &primary_offer_interface,
+ offer,
+ destroy_data_offer);
+
+ gtk_primary_selection_device_send_data_offer (target, offer->resource);
+
+ wl_array_for_each (p, &priv->mime_types)
+ gtk_primary_selection_offer_send_offer (offer->resource, *p);
+
+ meta_wayland_data_source_set_current_offer (source, offer);
+
+ return offer->resource;
+}
+
static void
data_source_offer (struct wl_client *client,
struct wl_resource *resource, const char *type)
@@ -511,12 +586,6 @@ data_source_offer (struct wl_client *client,
}
static void
-data_source_destroy (struct wl_client *client, struct wl_resource *resource)
-{
- wl_resource_destroy (resource);
-}
-
-static void
data_source_set_actions (struct wl_client *client,
struct wl_resource *resource,
uint32_t dnd_actions)
@@ -557,10 +626,26 @@ data_source_set_actions (struct wl_client *client,
static struct wl_data_source_interface data_source_interface = {
data_source_offer,
- data_source_destroy,
+ default_destructor,
data_source_set_actions
};
+static void
+primary_source_offer (struct wl_client *client,
+ struct wl_resource *resource,
+ const char *type)
+{
+ MetaWaylandDataSource *source = wl_resource_get_user_data (resource);
+
+ if (!meta_wayland_data_source_add_mime_type (source, type))
+ wl_resource_post_no_memory (resource);
+}
+
+static struct gtk_primary_selection_source_interface primary_source_interface = {
+ primary_source_offer,
+ default_destructor,
+};
+
struct _MetaWaylandDragGrab {
MetaWaylandPointerGrab generic;
@@ -1109,6 +1194,42 @@ meta_wayland_data_source_wayland_class_init (MetaWaylandDataSourceWaylandClass *
}
static void
+meta_wayland_data_source_primary_send (MetaWaylandDataSource *source,
+ const gchar *mime_type,
+ gint fd)
+{
+ MetaWaylandDataSourcePrimary *source_primary;
+
+ source_primary = META_WAYLAND_DATA_SOURCE_PRIMARY (source);
+ gtk_primary_selection_source_send_send (source_primary->resource,
+ mime_type, fd);
+}
+
+static void
+meta_wayland_data_source_primary_cancel (MetaWaylandDataSource *source)
+{
+ MetaWaylandDataSourcePrimary *source_primary;
+
+ source_primary = META_WAYLAND_DATA_SOURCE_PRIMARY (source);
+ gtk_primary_selection_source_send_cancelled (source_primary->resource);
+}
+
+static void
+meta_wayland_data_source_primary_init (MetaWaylandDataSourcePrimary *source_primary)
+{
+}
+
+static void
+meta_wayland_data_source_primary_class_init (MetaWaylandDataSourcePrimaryClass *klass)
+{
+ MetaWaylandDataSourceClass *data_source_class =
+ META_WAYLAND_DATA_SOURCE_CLASS (klass);
+
+ data_source_class->send = meta_wayland_data_source_primary_send;
+ data_source_class->cancel = meta_wayland_data_source_primary_cancel;
+}
+
+static void
meta_wayland_data_source_finalize (GObject *object)
{
MetaWaylandDataSource *source = META_WAYLAND_DATA_SOURCE (object);
@@ -1299,6 +1420,7 @@ meta_wayland_data_device_set_selection (MetaWaylandDataDevice *data_device,
if (source)
{
+ meta_wayland_data_source_set_seat (source, seat);
g_object_weak_ref (G_OBJECT (source),
selection_data_source_destroyed,
data_device);
@@ -1339,16 +1461,114 @@ data_device_set_selection (struct wl_client *client,
meta_wayland_data_device_set_selection (data_device, source, serial);
}
+static const struct wl_data_device_interface data_device_interface = {
+ data_device_start_drag,
+ data_device_set_selection,
+ default_destructor,
+};
+
static void
-data_device_release(struct wl_client *client, struct wl_resource *resource)
+primary_source_destroyed (gpointer data,
+ GObject *object_was_here)
{
- wl_resource_destroy(resource);
+ MetaWaylandDataDevice *data_device = data;
+ MetaWaylandSeat *seat = wl_container_of (data_device, seat, data_device);
+ struct wl_client *focus_client = NULL;
+
+ data_device->primary_data_source = NULL;
+
+ focus_client = meta_wayland_keyboard_get_focus_client (&seat->keyboard);
+ if (focus_client)
+ {
+ struct wl_resource *data_device_resource;
+
+ data_device_resource = wl_resource_find_for_client (&data_device->primary_resource_list, focus_client);
+ if (data_device_resource)
+ gtk_primary_selection_device_send_selection (data_device_resource, NULL);
+ }
}
-static const struct wl_data_device_interface data_device_interface = {
- data_device_start_drag,
- data_device_set_selection,
- data_device_release,
+void
+meta_wayland_data_device_set_primary (MetaWaylandDataDevice *data_device,
+ MetaWaylandDataSource *source,
+ guint32 serial)
+{
+ MetaWaylandSeat *seat = wl_container_of (data_device, seat, data_device);
+ struct wl_resource *data_device_resource, *offer;
+ struct wl_client *focus_client;
+
+ if (META_IS_WAYLAND_DATA_SOURCE_PRIMARY (source))
+ {
+ struct wl_resource *resource;
+
+ resource = META_WAYLAND_DATA_SOURCE_PRIMARY (source)->resource;
+
+ if (wl_resource_get_client (resource) !=
+ meta_wayland_keyboard_get_focus_client (&seat->keyboard))
+ return;
+ }
+
+ if (data_device->primary_data_source &&
+ data_device->primary_serial - serial < UINT32_MAX / 2)
+ return;
+
+ if (data_device->primary_data_source)
+ {
+ meta_wayland_data_source_cancel (data_device->primary_data_source);
+ g_object_weak_unref (G_OBJECT (data_device->primary_data_source),
+ primary_source_destroyed,
+ data_device);
+ }
+
+ data_device->primary_data_source = source;
+ data_device->primary_serial = serial;
+
+ focus_client = meta_wayland_keyboard_get_focus_client (&seat->keyboard);
+ if (focus_client)
+ {
+ data_device_resource = wl_resource_find_for_client (&data_device->primary_resource_list, focus_client);
+ if (data_device_resource)
+ {
+ if (data_device->primary_data_source)
+ {
+ offer = meta_wayland_data_source_send_primary_offer (data_device->primary_data_source,
+ data_device_resource);
+ gtk_primary_selection_device_send_selection (data_device_resource, offer);
+ }
+ else
+ {
+ gtk_primary_selection_device_send_selection (data_device_resource, NULL);
+ }
+ }
+ }
+
+ if (source)
+ {
+ meta_wayland_data_source_set_seat (source, seat);
+ g_object_weak_ref (G_OBJECT (source),
+ primary_source_destroyed,
+ data_device);
+ }
+
+ wl_signal_emit (&data_device->primary_ownership_signal, source);
+}
+
+static void
+primary_device_set_selection (struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *source_resource,
+ uint32_t serial)
+{
+ MetaWaylandDataDevice *data_device = wl_resource_get_user_data (resource);
+ MetaWaylandDataSource *source;
+
+ source = wl_resource_get_user_data (source_resource);
+ meta_wayland_data_device_set_primary (data_device, source, serial);
+}
+
+static const struct gtk_primary_selection_device_interface primary_device_interface = {
+ primary_device_set_selection,
+ default_destructor,
};
static void
@@ -1390,6 +1610,51 @@ static const struct wl_data_device_manager_interface manager_interface = {
};
static void
+destroy_primary_source (struct wl_resource *resource)
+{
+ MetaWaylandDataSourcePrimary *source = wl_resource_get_user_data (resource);
+
+ source->resource = NULL;
+ g_object_unref (source);
+}
+
+static void
+primary_device_manager_create_source (struct wl_client *client,
+ struct wl_resource *manager_resource,
+ guint32 id)
+{
+ struct wl_resource *source_resource;
+
+ source_resource =
+ wl_resource_create (client, >k_primary_selection_source_interface,
+ wl_resource_get_version (manager_resource),
+ id);
+ meta_wayland_data_source_primary_new (source_resource);
+}
+
+static void
+primary_device_manager_get_device (struct wl_client *client,
+ struct wl_resource *manager_resource,
+ guint32 id,
+ struct wl_resource *seat_resource)
+{
+ MetaWaylandSeat *seat = wl_resource_get_user_data (seat_resource);
+ struct wl_resource *cr;
+
+ cr = wl_resource_create (client, >k_primary_selection_device_interface,
+ wl_resource_get_version (manager_resource), id);
+ wl_resource_set_implementation (cr, &primary_device_interface,
+ &seat->data_device, unbind_resource);
+ wl_list_insert (&seat->data_device.primary_resource_list, wl_resource_get_link (cr));
+}
+
+static const struct gtk_primary_selection_device_manager_interface primary_manager_interface = {
+ primary_device_manager_create_source,
+ primary_device_manager_get_device,
+ default_destructor,
+};
+
+static void
bind_manager (struct wl_client *client,
void *data, guint32 version, guint32 id)
{
@@ -1398,6 +1663,19 @@ bind_manager (struct wl_client *client,
wl_resource_set_implementation (resource, &manager_interface, NULL, NULL);
}
+static void
+bind_primary_manager (struct wl_client *client,
+ void *data,
+ uint32_t version,
+ uint32_t id)
+{
+ struct wl_resource *resource;
+
+ resource = wl_resource_create (client, >k_primary_selection_device_manager_interface,
+ version, id);
+ wl_resource_set_implementation (resource, &primary_manager_interface, NULL, NULL);
+}
+
void
meta_wayland_data_device_manager_init (MetaWaylandCompositor *compositor)
{
@@ -1406,13 +1684,20 @@ meta_wayland_data_device_manager_init (MetaWaylandCompositor *compositor)
META_WL_DATA_DEVICE_MANAGER_VERSION,
NULL, bind_manager) == NULL)
g_error ("Could not create data_device");
+
+ if (wl_global_create (compositor->wayland_display,
+ >k_primary_selection_device_manager_interface,
+ 1, NULL, bind_primary_manager) == NULL)
+ g_error ("Could not create data_device");
}
void
meta_wayland_data_device_init (MetaWaylandDataDevice *data_device)
{
wl_list_init (&data_device->resource_list);
+ wl_list_init (&data_device->primary_resource_list);
wl_signal_init (&data_device->selection_ownership_signal);
+ wl_signal_init (&data_device->primary_ownership_signal);
wl_signal_init (&data_device->dnd_ownership_signal);
}
@@ -1435,17 +1720,34 @@ meta_wayland_data_device_set_keyboard_focus (MetaWaylandDataDevice *data_device)
return;
data_device_resource = wl_resource_find_for_client (&data_device->resource_list, focus_client);
- if (!data_device_resource)
- return;
+ if (data_device_resource)
+ {
+ source = data_device->selection_data_source;
+ if (source)
+ {
+ offer = meta_wayland_data_source_send_offer (source, data_device_resource);
+ wl_data_device_send_selection (data_device_resource, offer);
+ }
+ else
+ {
+ wl_data_device_send_selection (data_device_resource, NULL);
+ }
+ }
- source = data_device->selection_data_source;
- if (source)
+ data_device_resource = wl_resource_find_for_client (&data_device->primary_resource_list, focus_client);
+ if (data_device_resource)
{
- offer = meta_wayland_data_source_send_offer (source, data_device_resource);
- wl_data_device_send_selection (data_device_resource, offer);
+ source = data_device->primary_data_source;
+ if (source)
+ {
+ offer = meta_wayland_data_source_send_primary_offer (source, data_device_resource);
+ gtk_primary_selection_device_send_selection (data_device_resource, offer);
+ }
+ else
+ {
+ gtk_primary_selection_device_send_selection (data_device_resource, NULL);
+ }
}
- else
- wl_data_device_send_selection (data_device_resource, NULL);
}
gboolean
@@ -1486,6 +1788,19 @@ meta_wayland_data_source_wayland_new (struct wl_resource *resource)
return META_WAYLAND_DATA_SOURCE (source_wayland);
}
+static MetaWaylandDataSource *
+meta_wayland_data_source_primary_new (struct wl_resource *resource)
+{
+ MetaWaylandDataSourcePrimary *source_primary =
+ g_object_new (META_TYPE_WAYLAND_DATA_SOURCE_PRIMARY, NULL);
+
+ source_primary->resource = resource;
+ wl_resource_set_implementation (resource, &primary_source_interface,
+ source_primary, destroy_primary_source);
+
+ return META_WAYLAND_DATA_SOURCE (source_primary);
+}
+
gboolean
meta_wayland_data_source_add_mime_type (MetaWaylandDataSource *source,
const gchar *mime_type)
diff --git a/src/wayland/meta-wayland-data-device.h b/src/wayland/meta-wayland-data-device.h
index cdb4f05..f48b135 100644
--- a/src/wayland/meta-wayland-data-device.h
+++ b/src/wayland/meta-wayland-data-device.h
@@ -55,15 +55,19 @@ struct _MetaWaylandDataSourceClass
struct _MetaWaylandDataDevice
{
uint32_t selection_serial;
+ uint32_t primary_serial;
MetaWaylandDataSource *selection_data_source;
MetaWaylandDataSource *dnd_data_source;
+ MetaWaylandDataSource *primary_data_source;
struct wl_listener selection_data_source_listener;
struct wl_list resource_list;
+ struct wl_list primary_resource_list;
MetaWaylandDragGrab *current_grab;
struct wl_client *focus_client;
struct wl_signal selection_ownership_signal;
struct wl_signal dnd_ownership_signal;
+ struct wl_signal primary_ownership_signal;
};
void meta_wayland_data_device_manager_init (MetaWaylandCompositor *compositor);
@@ -80,6 +84,9 @@ void meta_wayland_data_device_set_dnd_source (MetaWaylandDataDevice *data_de
void meta_wayland_data_device_set_selection (MetaWaylandDataDevice *data_device,
MetaWaylandDataSource *source,
guint32 serial);
+void meta_wayland_data_device_set_primary (MetaWaylandDataDevice *data_device,
+ MetaWaylandDataSource *source,
+ guint32 serial);
gboolean meta_wayland_data_source_add_mime_type (MetaWaylandDataSource *source,
const gchar *mime_type);
diff --git a/src/wayland/meta-xwayland-selection.c b/src/wayland/meta-xwayland-selection.c
index 5d3405a..42354a7 100644
--- a/src/wayland/meta-xwayland-selection.c
+++ b/src/wayland/meta-xwayland-selection.c
@@ -91,6 +91,7 @@ struct _MetaWaylandDataSourceXWayland
struct _MetaXWaylandSelection {
MetaSelectionBridge clipboard;
+ MetaSelectionBridge primary;
MetaDndBridge dnd;
};
@@ -396,6 +397,8 @@ atom_to_selection_bridge (MetaWaylandCompositor *compositor,
if (selection_atom == selection_data->clipboard.selection_atom)
return &selection_data->clipboard;
+ else if (selection_atom == selection_data->primary.selection_atom)
+ return &selection_data->primary;
else if (selection_atom == selection_data->dnd.selection.selection_atom)
return &selection_data->dnd.selection;
else
@@ -530,6 +533,8 @@ data_device_get_active_source_for_atom (MetaWaylandDataDevice *data_device,
{
if (selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD"))
return data_device->selection_data_source;
+ else if (selection_atom == gdk_x11_get_xatom_by_name ("PRIMARY"))
+ return data_device->primary_data_source;
else if (selection_atom == xdnd_atoms[ATOM_DND_SELECTION])
return data_device->dnd_data_source;
else
@@ -1058,6 +1063,11 @@ meta_xwayland_selection_get_x11_targets (MetaWaylandCompositor *compositor,
meta_wayland_data_device_set_selection (&compositor->seat->data_device, data_source,
wl_display_next_serial (compositor->wayland_display));
}
+ else if (selection->selection_atom == gdk_x11_get_xatom_by_name ("PRIMARY"))
+ {
+ meta_wayland_data_device_set_primary (&compositor->seat->data_device, data_source,
+ wl_display_next_serial (compositor->wayland_display));
+ }
}
else
g_object_unref (data_source);
@@ -1529,7 +1539,8 @@ meta_xwayland_selection_handle_xfixes_selection_notify (MetaWaylandCompositor *c
if (!selection)
return FALSE;
- if (selection->selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD"))
+ if (selection->selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD") ||
+ selection->selection_atom == gdk_x11_get_xatom_by_name ("PRIMARY"))
{
if (event->owner == None)
{
@@ -1712,6 +1723,9 @@ meta_xwayland_init_selection (void)
init_selection_bridge (&manager->selection_data->clipboard,
gdk_x11_get_xatom_by_name ("CLIPBOARD"),
&compositor->seat->data_device.selection_ownership_signal);
+ init_selection_bridge (&manager->selection_data->primary,
+ gdk_x11_get_xatom_by_name ("PRIMARY"),
+ &compositor->seat->data_device.primary_ownership_signal);
init_selection_bridge (&manager->selection_data->dnd.selection,
xdnd_atoms[ATOM_DND_SELECTION],
&compositor->seat->data_device.dnd_ownership_signal);
@@ -1730,6 +1744,7 @@ meta_xwayland_shutdown_selection (void)
meta_xwayland_shutdown_dnd (manager);
shutdown_selection_bridge (&selection->clipboard);
+ shutdown_selection_bridge (&selection->primary);
shutdown_selection_bridge (&selection->dnd.selection);
g_slice_free (MetaXWaylandSelection, selection);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]