gecko-dev/widget/gtk/nsClipboardWayland.cpp

916 строки
31 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ArrayUtils.h"
#include "nsArrayUtils.h"
#include "nsClipboard.h"
#include "nsClipboardWayland.h"
#include "nsSupportsPrimitives.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsPrimitiveHelpers.h"
#include "nsImageToPixbuf.h"
#include "nsStringStream.h"
#include "mozilla/RefPtr.h"
#include "mozilla/TimeStamp.h"
#include "nsDragService.h"
#include "mozwayland/mozwayland.h"
#include "nsWaylandDisplay.h"
#include <gtk/gtk.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
using namespace mozilla;
using namespace mozilla::widget;
const char* nsRetrievalContextWayland::sTextMimeTypes[TEXT_MIME_TYPES_NUM] = {
"text/plain;charset=utf-8", "UTF8_STRING", "COMPOUND_TEXT"};
static inline GdkDragAction wl_to_gdk_actions(uint32_t dnd_actions) {
GdkDragAction actions = GdkDragAction(0);
if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
actions = GdkDragAction(actions | GDK_ACTION_COPY);
if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
actions = GdkDragAction(actions | GDK_ACTION_MOVE);
return actions;
}
static inline uint32_t gdk_to_wl_actions(GdkDragAction action) {
uint32_t dnd_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
if (action & (GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_PRIVATE))
dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
if (action & GDK_ACTION_MOVE)
dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
return dnd_actions;
}
static GtkWidget* get_gtk_widget_for_wl_surface(struct wl_surface* surface) {
GdkWindow* gdkParentWindow =
static_cast<GdkWindow*>(wl_surface_get_user_data(surface));
gpointer user_data = nullptr;
gdk_window_get_user_data(gdkParentWindow, &user_data);
return GTK_WIDGET(user_data);
}
void DataOffer::AddMIMEType(const char* aMimeType) {
GdkAtom atom = gdk_atom_intern(aMimeType, FALSE);
mTargetMIMETypes.AppendElement(atom);
}
GdkAtom* DataOffer::GetTargets(int* aTargetNum) {
int length = mTargetMIMETypes.Length();
if (!length) {
*aTargetNum = 0;
return nullptr;
}
GdkAtom* targetList =
reinterpret_cast<GdkAtom*>(g_malloc(sizeof(GdkAtom) * length));
for (int32_t j = 0; j < length; j++) {
targetList[j] = mTargetMIMETypes[j];
}
*aTargetNum = length;
return targetList;
}
bool DataOffer::HasTarget(const char* aMimeType) {
int length = mTargetMIMETypes.Length();
for (int32_t j = 0; j < length; j++) {
if (mTargetMIMETypes[j] == gdk_atom_intern(aMimeType, FALSE)) {
LOGCLIP(("DataOffer::HasTarget() we have mime %s\n", aMimeType));
return true;
}
}
LOGCLIP(("DataOffer::HasTarget() missing mime %s\n", aMimeType));
return false;
}
char* DataOffer::GetData(wl_display* aDisplay, const char* aMimeType,
uint32_t* aContentLength) {
LOGCLIP(("DataOffer::GetData() mime %s\n", aMimeType));
int pipe_fd[2];
if (pipe(pipe_fd) == -1) return nullptr;
if (!RequestDataTransfer(aMimeType, pipe_fd[1])) {
NS_WARNING("DataOffer::RequestDataTransfer() failed!");
close(pipe_fd[0]);
close(pipe_fd[1]);
return nullptr;
}
close(pipe_fd[1]);
wl_display_flush(aDisplay);
struct pollfd fds;
fds.fd = pipe_fd[0];
fds.events = POLLIN;
int pollReturn = -1;
#define MAX_CLIPBOARD_POLL_ATTEMPTS 10
for (int i = 0; i < MAX_CLIPBOARD_POLL_ATTEMPTS; i++) {
pollReturn = poll(&fds, 1, kClipboardTimeout / 1000);
// ret > 0 means we have data available
// ret = 0 means poll timeout expired
// ret < 0 means poll failed with error
if (pollReturn >= 0) {
break;
}
// We should try again for EINTR/EAGAIN errors,
// quit for all other ones.
if (errno != EINTR && errno != EAGAIN) {
break;
}
}
// Quit for poll error() and timeout
if (pollReturn <= 0) {
NS_WARNING("DataOffer::RequestDataTransfer() poll timeout!");
close(pipe_fd[0]);
return nullptr;
}
GIOChannel* channel = g_io_channel_unix_new(pipe_fd[0]);
GError* error = nullptr;
char* clipboardData = nullptr;
g_io_channel_set_encoding(channel, nullptr, &error);
if (!error) {
gsize length = 0;
g_io_channel_read_to_end(channel, &clipboardData, &length, &error);
if (length == 0) {
// We don't have valid clipboard data although
// g_io_channel_read_to_end() allocated clipboardData for us.
// Release it now and return nullptr to indicate
// we don't have reqested data flavour.
g_free((void*)clipboardData);
clipboardData = nullptr;
}
*aContentLength = length;
}
if (error) {
NS_WARNING(
nsPrintfCString("Unexpected error when reading clipboard data: %s",
error->message)
.get());
g_error_free(error);
}
g_io_channel_unref(channel);
close(pipe_fd[0]);
LOGCLIP((" Got clipboard data length %d\n", *aContentLength));
return clipboardData;
}
bool WaylandDataOffer::RequestDataTransfer(const char* aMimeType, int fd) {
if (mWaylandDataOffer) {
wl_data_offer_receive(mWaylandDataOffer, aMimeType, fd);
return true;
}
return false;
}
void WaylandDataOffer::DragOfferAccept(const char* aMimeType, uint32_t aTime) {
wl_data_offer_accept(mWaylandDataOffer, aTime, aMimeType);
}
/* We follow logic of gdk_wayland_drag_context_commit_status()/gdkdnd-wayland.c
* here.
*/
void WaylandDataOffer::SetDragStatus(GdkDragAction aPreferredAction,
uint32_t aTime) {
uint32_t preferredAction = gdk_to_wl_actions(aPreferredAction);
uint32_t allActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
/* We only don't choose a preferred action if we don't accept any.
* If we do accept any, it is currently alway copy and move
*/
if (preferredAction != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) {
allActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
}
wl_data_offer_set_actions(mWaylandDataOffer, allActions, preferredAction);
/* Workaround Wayland D&D architecture here. To get the data_device_drop()
signal (which routes to nsDragService::GetData() call) we need to
accept at least one mime type before data_device_leave().
Real wl_data_offer_accept() for actualy requested data mime type is
called from nsDragService::GetData().
*/
if (mTargetMIMETypes[0]) {
wl_data_offer_accept(mWaylandDataOffer, aTime,
gdk_atom_name(mTargetMIMETypes[0]));
}
}
void WaylandDataOffer::SetSelectedDragAction(uint32_t aWaylandAction) {
mSelectedDragAction = aWaylandAction;
}
GdkDragAction WaylandDataOffer::GetSelectedDragAction() {
return wl_to_gdk_actions(mSelectedDragAction);
}
void WaylandDataOffer::SetAvailableDragActions(uint32_t aWaylandActions) {
mAvailableDragActions = aWaylandActions;
}
GdkDragAction WaylandDataOffer::GetAvailableDragActions() {
return wl_to_gdk_actions(mAvailableDragActions);
}
void WaylandDataOffer::SetWaylandDragContext(
nsWaylandDragContext* aDragContext) {
mDragContext = aDragContext;
}
nsWaylandDragContext* WaylandDataOffer::GetWaylandDragContext() {
return mDragContext;
}
static void data_offer_offer(void* data, struct wl_data_offer* wl_data_offer,
const char* type) {
auto* offer = static_cast<DataOffer*>(data);
offer->AddMIMEType(type);
}
/* Advertise all available drag and drop actions from source.
* We don't use that but follow gdk_wayland_drag_context_commit_status()
* from gdkdnd-wayland.c here.
*/
static void data_offer_source_actions(void* data,
struct wl_data_offer* wl_data_offer,
uint32_t source_actions) {
auto* offer = static_cast<WaylandDataOffer*>(data);
offer->SetAvailableDragActions(source_actions);
}
/* Advertise recently selected drag and drop action by compositor, based
* on source actions and user choice (key modifiers, etc.).
*/
static void data_offer_action(void* data, struct wl_data_offer* wl_data_offer,
uint32_t dnd_action) {
auto* offer = static_cast<WaylandDataOffer*>(data);
offer->SetSelectedDragAction(dnd_action);
/* Mimic GTK which triggers the motion event callback */
nsWaylandDragContext* dropContext = offer->GetWaylandDragContext();
if (dropContext) {
uint32_t time;
nscoord x, y;
dropContext->GetLastDropInfo(&time, &x, &y);
WindowDragMotionHandler(dropContext->GetWidget(), nullptr, dropContext, x,
y, time);
}
}
/* wl_data_offer callback description:
*
* data_offer_offer - Is called for each MIME type available at wl_data_offer.
* data_offer_source_actions - This event indicates the actions offered by
* the data source.
* data_offer_action - This event indicates the action selected by
* the compositor after matching the source/destination
* side actions.
*/
static const moz_wl_data_offer_listener data_offer_listener = {
data_offer_offer, data_offer_source_actions, data_offer_action};
WaylandDataOffer::WaylandDataOffer(wl_data_offer* aWaylandDataOffer)
: mWaylandDataOffer(aWaylandDataOffer),
mDragContext(nullptr),
mSelectedDragAction(0),
mAvailableDragActions(0) {
wl_data_offer_add_listener(
mWaylandDataOffer, (struct wl_data_offer_listener*)&data_offer_listener,
this);
}
WaylandDataOffer::~WaylandDataOffer(void) {
if (mWaylandDataOffer) {
wl_data_offer_destroy(mWaylandDataOffer);
}
}
bool PrimaryDataOffer::RequestDataTransfer(const char* aMimeType, int fd) {
if (mPrimaryDataOfferGtk) {
gtk_primary_selection_offer_receive(mPrimaryDataOfferGtk, aMimeType, fd);
return true;
}
if (mPrimaryDataOfferZwpV1) {
zwp_primary_selection_offer_v1_receive(mPrimaryDataOfferZwpV1, aMimeType,
fd);
return true;
}
return false;
}
static void primary_data_offer(
void* data, gtk_primary_selection_offer* primary_selection_offer,
const char* mime_type) {
auto* offer = static_cast<DataOffer*>(data);
offer->AddMIMEType(mime_type);
}
static void primary_data_offer(
void* data, zwp_primary_selection_offer_v1* primary_selection_offer,
const char* mime_type) {
auto* offer = static_cast<DataOffer*>(data);
offer->AddMIMEType(mime_type);
}
/* gtk_primary_selection_offer_listener callback description:
*
* primary_data_offer - Is called for each MIME type available at
* gtk_primary_selection_offer.
*/
static const struct gtk_primary_selection_offer_listener
primary_selection_offer_listener_gtk = {primary_data_offer};
static const struct zwp_primary_selection_offer_v1_listener
primary_selection_offer_listener_zwp_v1 = {primary_data_offer};
PrimaryDataOffer::PrimaryDataOffer(
gtk_primary_selection_offer* aPrimaryDataOffer)
: mPrimaryDataOfferGtk(aPrimaryDataOffer), mPrimaryDataOfferZwpV1(nullptr) {
gtk_primary_selection_offer_add_listener(
aPrimaryDataOffer, &primary_selection_offer_listener_gtk, this);
}
PrimaryDataOffer::PrimaryDataOffer(
zwp_primary_selection_offer_v1* aPrimaryDataOffer)
: mPrimaryDataOfferGtk(nullptr), mPrimaryDataOfferZwpV1(aPrimaryDataOffer) {
zwp_primary_selection_offer_v1_add_listener(
aPrimaryDataOffer, &primary_selection_offer_listener_zwp_v1, this);
}
PrimaryDataOffer::~PrimaryDataOffer(void) {
if (mPrimaryDataOfferGtk) {
gtk_primary_selection_offer_destroy(mPrimaryDataOfferGtk);
}
if (mPrimaryDataOfferZwpV1) {
zwp_primary_selection_offer_v1_destroy(mPrimaryDataOfferZwpV1);
}
}
NS_IMPL_ISUPPORTS(nsWaylandDragContext, nsISupports);
nsWaylandDragContext::nsWaylandDragContext(WaylandDataOffer* aDataOffer,
wl_display* aDisplay)
: mDataOffer(aDataOffer),
mDisplay(aDisplay),
mTime(0),
mGtkWidget(nullptr),
mX(0),
mY(0) {
aDataOffer->SetWaylandDragContext(this);
}
void nsWaylandDragContext::DropDataEnter(GtkWidget* aGtkWidget, uint32_t aTime,
nscoord aX, nscoord aY) {
mTime = aTime;
mGtkWidget = aGtkWidget;
mX = aX;
mY = aY;
}
void nsWaylandDragContext::DropMotion(uint32_t aTime, nscoord aX, nscoord aY) {
mTime = aTime;
mX = aX;
mY = aY;
}
void nsWaylandDragContext::GetLastDropInfo(uint32_t* aTime, nscoord* aX,
nscoord* aY) {
*aTime = mTime;
*aX = mX;
*aY = mY;
}
void nsWaylandDragContext::SetDragStatus(GdkDragAction aPreferredAction) {
mDataOffer->SetDragStatus(aPreferredAction, mTime);
}
GdkDragAction nsWaylandDragContext::GetAvailableDragActions() {
GdkDragAction gdkAction = mDataOffer->GetSelectedDragAction();
// We emulate gdk_drag_context_get_actions() here.
if (!gdkAction) {
gdkAction = mDataOffer->GetAvailableDragActions();
}
return gdkAction;
}
GList* nsWaylandDragContext::GetTargets() {
int targetNums;
GdkAtom* atoms = mDataOffer->GetTargets(&targetNums);
GList* targetList = nullptr;
for (int i = 0; i < targetNums; i++) {
targetList = g_list_append(targetList, GDK_ATOM_TO_POINTER(atoms[i]));
}
return targetList;
}
char* nsWaylandDragContext::GetData(const char* aMimeType,
uint32_t* aContentLength) {
mDataOffer->DragOfferAccept(aMimeType, mTime);
return mDataOffer->GetData(mDisplay, aMimeType, aContentLength);
}
void nsRetrievalContextWayland::RegisterNewDataOffer(
wl_data_offer* aWaylandDataOffer) {
DataOffer* dataOffer = static_cast<DataOffer*>(
g_hash_table_lookup(mActiveOffers, aWaylandDataOffer));
MOZ_ASSERT(
dataOffer == nullptr,
"Registered WaylandDataOffer already exists. Wayland protocol error?");
if (!dataOffer) {
dataOffer = new WaylandDataOffer(aWaylandDataOffer);
g_hash_table_insert(mActiveOffers, aWaylandDataOffer, dataOffer);
}
}
void nsRetrievalContextWayland::RegisterNewDataOffer(
gtk_primary_selection_offer* aPrimaryDataOffer) {
DataOffer* dataOffer = static_cast<DataOffer*>(
g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
MOZ_ASSERT(
dataOffer == nullptr,
"Registered PrimaryDataOffer already exists. Wayland protocol error?");
if (!dataOffer) {
dataOffer = new PrimaryDataOffer(aPrimaryDataOffer);
g_hash_table_insert(mActiveOffers, aPrimaryDataOffer, dataOffer);
}
}
void nsRetrievalContextWayland::RegisterNewDataOffer(
zwp_primary_selection_offer_v1* aPrimaryDataOffer) {
DataOffer* dataOffer = static_cast<DataOffer*>(
g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
MOZ_ASSERT(
dataOffer == nullptr,
"Registered PrimaryDataOffer already exists. Wayland protocol error?");
if (!dataOffer) {
dataOffer = new PrimaryDataOffer(aPrimaryDataOffer);
g_hash_table_insert(mActiveOffers, aPrimaryDataOffer, dataOffer);
}
}
void nsRetrievalContextWayland::SetClipboardDataOffer(
wl_data_offer* aWaylandDataOffer) {
// Delete existing clipboard data offer
mClipboardOffer = nullptr;
// null aWaylandDataOffer indicates that our clipboard content
// is no longer valid and should be release.
if (aWaylandDataOffer != nullptr) {
DataOffer* dataOffer = static_cast<DataOffer*>(
g_hash_table_lookup(mActiveOffers, aWaylandDataOffer));
NS_ASSERTION(dataOffer, "We're missing stored clipboard data offer!");
if (dataOffer) {
g_hash_table_remove(mActiveOffers, aWaylandDataOffer);
mClipboardOffer = WrapUnique(dataOffer);
}
}
}
void nsRetrievalContextWayland::SetPrimaryDataOffer(
gtk_primary_selection_offer* aPrimaryDataOffer) {
// Release any primary offer we have.
mPrimaryOffer = nullptr;
// aPrimaryDataOffer can be null which means we lost
// the mouse selection.
if (aPrimaryDataOffer) {
DataOffer* dataOffer = static_cast<DataOffer*>(
g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
NS_ASSERTION(dataOffer, "We're missing primary data offer!");
if (dataOffer) {
g_hash_table_remove(mActiveOffers, aPrimaryDataOffer);
mPrimaryOffer = WrapUnique(dataOffer);
}
}
}
void nsRetrievalContextWayland::SetPrimaryDataOffer(
zwp_primary_selection_offer_v1* aPrimaryDataOffer) {
// Release any primary offer we have.
mPrimaryOffer = nullptr;
// aPrimaryDataOffer can be null which means we lost
// the mouse selection.
if (aPrimaryDataOffer) {
DataOffer* dataOffer = static_cast<DataOffer*>(
g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
NS_ASSERTION(dataOffer, "We're missing primary data offer!");
if (dataOffer) {
g_hash_table_remove(mActiveOffers, aPrimaryDataOffer);
mPrimaryOffer = WrapUnique(dataOffer);
}
}
}
void nsRetrievalContextWayland::AddDragAndDropDataOffer(
wl_data_offer* aDropDataOffer) {
// Remove any existing D&D contexts.
mDragContext = nullptr;
WaylandDataOffer* dataOffer = static_cast<WaylandDataOffer*>(
g_hash_table_lookup(mActiveOffers, aDropDataOffer));
NS_ASSERTION(dataOffer, "We're missing drag and drop data offer!");
if (dataOffer) {
g_hash_table_remove(mActiveOffers, aDropDataOffer);
mDragContext = new nsWaylandDragContext(dataOffer, mDisplay->GetDisplay());
}
}
nsWaylandDragContext* nsRetrievalContextWayland::GetDragContext(void) {
return mDragContext;
}
void nsRetrievalContextWayland::ClearDragAndDropDataOffer(void) {
mDragContext = nullptr;
}
// We have a new fresh data content.
// We should attach listeners to it and save for further use.
static void data_device_data_offer(void* data,
struct wl_data_device* data_device,
struct wl_data_offer* offer) {
LOGCLIP(("data_device_data_offer() callback\n"));
nsRetrievalContextWayland* context =
static_cast<nsRetrievalContextWayland*>(data);
context->RegisterNewDataOffer(offer);
}
// The new fresh data content is clipboard.
static void data_device_selection(void* data,
struct wl_data_device* wl_data_device,
struct wl_data_offer* offer) {
LOGCLIP(("data_device_selection() callback\n"));
nsRetrievalContextWayland* context =
static_cast<nsRetrievalContextWayland*>(data);
context->SetClipboardDataOffer(offer);
}
// The new fresh wayland data content is drag and drop.
static void data_device_enter(void* data, struct wl_data_device* data_device,
uint32_t time, struct wl_surface* surface,
int32_t x_fixed, int32_t y_fixed,
struct wl_data_offer* offer) {
nsRetrievalContextWayland* context =
static_cast<nsRetrievalContextWayland*>(data);
context->AddDragAndDropDataOffer(offer);
nsWaylandDragContext* dragContext = context->GetDragContext();
GtkWidget* gtkWidget = get_gtk_widget_for_wl_surface(surface);
if (!gtkWidget) {
NS_WARNING("DragAndDrop: Unable to get GtkWidget for wl_surface!");
return;
}
LOGDRAG(("nsWindow data_device_enter for GtkWidget %p\n", (void*)gtkWidget));
dragContext->DropDataEnter(gtkWidget, time, wl_fixed_to_int(x_fixed),
wl_fixed_to_int(y_fixed));
}
static void data_device_leave(void* data, struct wl_data_device* data_device) {
nsRetrievalContextWayland* context =
static_cast<nsRetrievalContextWayland*>(data);
nsWaylandDragContext* dropContext = context->GetDragContext();
WindowDragLeaveHandler(dropContext->GetWidget());
LOGDRAG(("nsWindow data_device_leave for GtkWidget %p\n",
(void*)dropContext->GetWidget()));
context->ClearDragAndDropDataOffer();
}
static void data_device_motion(void* data, struct wl_data_device* data_device,
uint32_t time, int32_t x_fixed,
int32_t y_fixed) {
nsRetrievalContextWayland* context =
static_cast<nsRetrievalContextWayland*>(data);
nsWaylandDragContext* dropContext = context->GetDragContext();
nscoord x = wl_fixed_to_int(x_fixed);
nscoord y = wl_fixed_to_int(y_fixed);
dropContext->DropMotion(time, x, y);
LOGDRAG(("nsWindow data_device_motion for GtkWidget %p\n",
(void*)dropContext->GetWidget()));
WindowDragMotionHandler(dropContext->GetWidget(), nullptr, dropContext, x, y,
time);
}
static void data_device_drop(void* data, struct wl_data_device* data_device) {
nsRetrievalContextWayland* context =
static_cast<nsRetrievalContextWayland*>(data);
nsWaylandDragContext* dropContext = context->GetDragContext();
uint32_t time;
nscoord x, y;
dropContext->GetLastDropInfo(&time, &x, &y);
LOGDRAG(("nsWindow data_device_drop GtkWidget %p\n",
(void*)dropContext->GetWidget()));
WindowDragDropHandler(dropContext->GetWidget(), nullptr, dropContext, x, y,
time);
}
/* wl_data_device callback description:
*
* data_device_data_offer - It's called when there's a new wl_data_offer
* available. We need to attach wl_data_offer_listener
* to it to get available MIME types.
*
* data_device_selection - It's called when the new wl_data_offer
* is a clipboard content.
*
* data_device_enter - It's called when the new wl_data_offer is a drag & drop
* content and it's tied to actual wl_surface.
* data_device_leave - It's called when the wl_data_offer (drag & dop) is not
* valid any more.
* data_device_motion - It's called when the drag and drop selection moves
* across wl_surface.
* data_device_drop - It's called when D&D operation is sucessfully finished
* and we can read the data from D&D.
* It's generated only if we call wl_data_offer_accept() and
* wl_data_offer_set_actions() from data_device_motion
* callback.
*/
static const struct wl_data_device_listener data_device_listener = {
data_device_data_offer, data_device_enter, data_device_leave,
data_device_motion, data_device_drop, data_device_selection};
static void primary_selection_data_offer(
void* data, struct gtk_primary_selection_device* primary_selection_device,
struct gtk_primary_selection_offer* primary_offer) {
LOGCLIP(("primary_selection_data_offer() callback\n"));
// create and add listener
nsRetrievalContextWayland* context =
static_cast<nsRetrievalContextWayland*>(data);
context->RegisterNewDataOffer(primary_offer);
}
static void primary_selection_data_offer(
void* data,
struct zwp_primary_selection_device_v1* primary_selection_device,
struct zwp_primary_selection_offer_v1* primary_offer) {
LOGCLIP(("primary_selection_data_offer() callback\n"));
// create and add listener
nsRetrievalContextWayland* context =
static_cast<nsRetrievalContextWayland*>(data);
context->RegisterNewDataOffer(primary_offer);
}
static void primary_selection_selection(
void* data, struct gtk_primary_selection_device* primary_selection_device,
struct gtk_primary_selection_offer* primary_offer) {
LOGCLIP(("primary_selection_selection() callback\n"));
nsRetrievalContextWayland* context =
static_cast<nsRetrievalContextWayland*>(data);
context->SetPrimaryDataOffer(primary_offer);
}
static void primary_selection_selection(
void* data,
struct zwp_primary_selection_device_v1* primary_selection_device,
struct zwp_primary_selection_offer_v1* primary_offer) {
LOGCLIP(("primary_selection_selection() callback\n"));
nsRetrievalContextWayland* context =
static_cast<nsRetrievalContextWayland*>(data);
context->SetPrimaryDataOffer(primary_offer);
}
/* gtk_primary_selection_device callback description:
*
* primary_selection_data_offer - It's called when there's a new
* gtk_primary_selection_offer available. We need to
* attach gtk_primary_selection_offer_listener to it
* to get available MIME types.
*
* primary_selection_selection - It's called when the new
* gtk_primary_selection_offer is a primary selection
* content. It can be also called with
* gtk_primary_selection_offer = null which means
* there's no primary selection.
*/
static const struct gtk_primary_selection_device_listener
primary_selection_device_listener_gtk = {
primary_selection_data_offer,
primary_selection_selection,
};
static const struct zwp_primary_selection_device_v1_listener
primary_selection_device_listener_zwp_v1 = {
primary_selection_data_offer,
primary_selection_selection,
};
bool nsRetrievalContextWayland::HasSelectionSupport(void) {
return (mDisplay->GetPrimarySelectionDeviceManagerZwpV1() != nullptr ||
mDisplay->GetPrimarySelectionDeviceManagerGtk() != nullptr);
}
nsRetrievalContextWayland::nsRetrievalContextWayland(void)
: mInitialized(false),
mDisplay(WaylandDisplayGet()),
mActiveOffers(g_hash_table_new(NULL, NULL)),
mClipboardOffer(nullptr),
mPrimaryOffer(nullptr),
mDragContext(nullptr),
mClipboardRequestNumber(0),
mClipboardData(nullptr),
mClipboardDataLength(0) {
wl_data_device* dataDevice = wl_data_device_manager_get_data_device(
mDisplay->GetDataDeviceManager(), mDisplay->GetSeat());
wl_data_device_add_listener(dataDevice, &data_device_listener, this);
if (mDisplay->GetPrimarySelectionDeviceManagerZwpV1()) {
zwp_primary_selection_device_v1* primaryDataDevice =
zwp_primary_selection_device_manager_v1_get_device(
mDisplay->GetPrimarySelectionDeviceManagerZwpV1(),
mDisplay->GetSeat());
zwp_primary_selection_device_v1_add_listener(
primaryDataDevice, &primary_selection_device_listener_zwp_v1, this);
} else if (mDisplay->GetPrimarySelectionDeviceManagerGtk()) {
gtk_primary_selection_device* primaryDataDevice =
gtk_primary_selection_device_manager_get_device(
mDisplay->GetPrimarySelectionDeviceManagerGtk(),
mDisplay->GetSeat());
gtk_primary_selection_device_add_listener(
primaryDataDevice, &primary_selection_device_listener_gtk, this);
}
mInitialized = true;
}
static gboolean offer_hash_remove(gpointer wl_offer, gpointer aDataOffer,
gpointer user_data) {
#ifdef DEBUG
nsPrintfCString msg("nsRetrievalContextWayland(): leaked nsDataOffer %p\n",
aDataOffer);
NS_WARNING(msg.get());
#endif
delete static_cast<DataOffer*>(aDataOffer);
return true;
}
nsRetrievalContextWayland::~nsRetrievalContextWayland(void) {
g_hash_table_foreach_remove(mActiveOffers, offer_hash_remove, nullptr);
g_hash_table_destroy(mActiveOffers);
}
GdkAtom* nsRetrievalContextWayland::GetTargets(int32_t aWhichClipboard,
int* aTargetNum) {
if (GetSelectionAtom(aWhichClipboard) == GDK_SELECTION_CLIPBOARD) {
if (mClipboardOffer) {
return mClipboardOffer->GetTargets(aTargetNum);
}
} else {
if (mPrimaryOffer) {
return mPrimaryOffer->GetTargets(aTargetNum);
}
}
*aTargetNum = 0;
return nullptr;
}
struct FastTrackClipboard {
FastTrackClipboard(int aClipboardRequestNumber,
nsRetrievalContextWayland* aRetrievalContex)
: mClipboardRequestNumber(aClipboardRequestNumber),
mRetrievalContex(aRetrievalContex) {}
int mClipboardRequestNumber;
nsRetrievalContextWayland* mRetrievalContex;
};
static void wayland_clipboard_contents_received(
GtkClipboard* clipboard, GtkSelectionData* selection_data, gpointer data) {
LOGCLIP(("wayland_clipboard_contents_received() callback\n"));
FastTrackClipboard* fastTrack = static_cast<FastTrackClipboard*>(data);
fastTrack->mRetrievalContex->TransferFastTrackClipboard(
fastTrack->mClipboardRequestNumber, selection_data);
delete fastTrack;
}
void nsRetrievalContextWayland::TransferFastTrackClipboard(
int aClipboardRequestNumber, GtkSelectionData* aSelectionData) {
if (mClipboardRequestNumber == aClipboardRequestNumber) {
int dataLength = gtk_selection_data_get_length(aSelectionData);
if (dataLength > 0) {
mClipboardDataLength = dataLength;
mClipboardData = reinterpret_cast<char*>(
g_malloc(sizeof(char) * (mClipboardDataLength + 1)));
memcpy(mClipboardData, gtk_selection_data_get_data(aSelectionData),
sizeof(char) * mClipboardDataLength);
mClipboardData[mClipboardDataLength] = '\0';
}
} else {
NS_WARNING("Received obsoleted clipboard data!");
}
}
const char* nsRetrievalContextWayland::GetClipboardData(
const char* aMimeType, int32_t aWhichClipboard, uint32_t* aContentLength) {
NS_ASSERTION(mClipboardData == nullptr && mClipboardDataLength == 0,
"Looks like we're leaking clipboard data here!");
LOGCLIP(("nsRetrievalContextWayland::GetClipboardData [%p] mime %s\n", this,
aMimeType));
/* If actual clipboard data is owned by us we don't need to go
* through Wayland but we ask Gtk+ to directly call data
* getter callback nsClipboard::SelectionGetEvent().
* see gtk_selection_convert() at gtk+/gtkselection.c.
*/
GdkAtom selection = GetSelectionAtom(aWhichClipboard);
if (gdk_selection_owner_get(selection)) {
LOGCLIP((" Internal clipboard content\n"));
mClipboardRequestNumber++;
gtk_clipboard_request_contents(
gtk_clipboard_get(selection), gdk_atom_intern(aMimeType, FALSE),
wayland_clipboard_contents_received,
new FastTrackClipboard(mClipboardRequestNumber, this));
} else {
LOGCLIP((" Remote clipboard content\n"));
const auto& dataOffer =
(selection == GDK_SELECTION_PRIMARY) ? mPrimaryOffer : mClipboardOffer;
if (!dataOffer) {
// Something went wrong. We're requested to provide clipboard data
// but we haven't got any from wayland.
NS_WARNING("Requested data without valid DataOffer!");
mClipboardData = nullptr;
mClipboardDataLength = 0;
} else {
mClipboardData = dataOffer->GetData(mDisplay->GetDisplay(), aMimeType,
&mClipboardDataLength);
}
}
*aContentLength = mClipboardDataLength;
return reinterpret_cast<const char*>(mClipboardData);
}
const char* nsRetrievalContextWayland::GetClipboardText(
int32_t aWhichClipboard) {
LOGCLIP(("nsRetrievalContextWayland::GetClipboardText [%p]\n", this));
GdkAtom selection = GetSelectionAtom(aWhichClipboard);
const auto& dataOffer =
(selection == GDK_SELECTION_PRIMARY) ? mPrimaryOffer : mClipboardOffer;
if (!dataOffer) return nullptr;
for (unsigned int i = 0; i < TEXT_MIME_TYPES_NUM; i++) {
if (dataOffer->HasTarget(sTextMimeTypes[i])) {
uint32_t unused;
return GetClipboardData(sTextMimeTypes[i], aWhichClipboard, &unused);
}
}
return nullptr;
}
void nsRetrievalContextWayland::ReleaseClipboardData(
const char* aClipboardData) {
LOGCLIP(("nsRetrievalContextWayland::ReleaseClipboardData [%p]\n", this));
NS_ASSERTION(aClipboardData == mClipboardData,
"Releasing unknown clipboard data!");
g_free((void*)aClipboardData);
mClipboardData = nullptr;
mClipboardDataLength = 0;
}