#include "ui/base/x/x11_drag_context.h"
#include "base/logging.h"
#include "base/memory/ref_counted_memory.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/x/x11_drag_drop_client.h"
#include "ui/base/x/x11_util.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/x/atom_cache.h"
#include "ui/gfx/x/connection.h"
#include "ui/gfx/x/xproto.h"
namespace ui {
namespace {
const char kXdndTypeList[] = "XdndTypeList";
const char kXdndSelection[] = "XdndSelection";
const char kXdndActionList[] = "XdndActionList";
const char kXdndActionCopy[] = "XdndActionCopy";
const char kXdndActionMove[] = "XdndActionMove";
const char kXdndActionLink[] = "XdndActionLink";
const char kChromiumDragReciever[] = "_CHROMIUM_DRAG_RECEIVER";
}
XDragContext::XDragContext(x11::Window local_window,
const x11::ClientMessageEvent& event,
const SelectionFormatMap& data)
: local_window_(local_window),
source_window_(static_cast<x11::Window>(event.data.data32[0])) {
XDragDropClient* source_client =
XDragDropClient::GetForWindow(source_window_);
if (!source_client) {
bool get_types_from_property = ((event.data.data32[1] & 1) != 0);
if (get_types_from_property) {
if (!x11::Connection::Get()->GetArrayProperty(source_window_,
x11::GetAtom(kXdndTypeList),
&unfetched_targets_)) {
return;
}
} else {
for (size_t i = 2; i < 5; ++i) {
if (event.data.data32[i]) {
unfetched_targets_.push_back(
static_cast<x11::Atom>(event.data.data32[i]));
}
}
}
#if DCHECK_IS_ON()
DVLOG(1) << "XdndEnter has " << unfetched_targets_.size() << " data types";
for (x11::Atom target : unfetched_targets_) {
DVLOG(1) << "XdndEnter data type: " << static_cast<uint32_t>(target);
}
#endif
x11::Connection::Get()->Sync();
} else {
fetched_targets_ = data;
}
ReadActions();
}
XDragContext::~XDragContext() = default;
void XDragContext::OnXdndPositionMessage(XDragDropClient* client,
x11::Atom suggested_action,
x11::Window source_window,
x11::Time time_stamp,
const gfx::Point& screen_point) {
DCHECK_EQ(source_window_, source_window);
suggested_action_ = suggested_action;
if (!unfetched_targets_.empty()) {
screen_point_ = screen_point;
drag_drop_client_ = client;
position_time_stamp_ = time_stamp;
waiting_to_handle_position_ = true;
fetched_targets_ = SelectionFormatMap();
RequestNextTarget();
} else {
client->CompleteXdndPosition(source_window, screen_point);
}
}
void XDragContext::RequestNextTarget() {
DCHECK(!unfetched_targets_.empty());
DCHECK(drag_drop_client_);
DCHECK(waiting_to_handle_position_);
x11::Atom target = unfetched_targets_.back();
unfetched_targets_.pop_back();
x11::Connection::Get()->ConvertSelection(
{local_window_, x11::GetAtom(kXdndSelection), target,
x11::GetAtom(kChromiumDragReciever), position_time_stamp_});
}
void XDragContext::OnSelectionNotify(const x11::SelectionNotifyEvent& event) {
if (!waiting_to_handle_position_) {
return;
}
DCHECK(drag_drop_client_);
DVLOG(1) << "SelectionNotify, format " << static_cast<uint32_t>(event.target);
auto property = static_cast<x11::Atom>(event.property);
auto target = static_cast<x11::Atom>(event.target);
if (event.property != x11::Atom::None) {
DCHECK_EQ(property, x11::GetAtom(kChromiumDragReciever));
scoped_refptr<base::RefCountedMemory> data;
x11::Atom type = x11::Atom::None;
if (GetRawBytesOfProperty(local_window_, property, &data, &type)) {
fetched_targets_.Insert(target, data);
}
} else {
LOG(ERROR) << "XConvertSelection failed for source-advertised target "
<< static_cast<uint32_t>(event.target);
}
if (!unfetched_targets_.empty()) {
RequestNextTarget();
} else {
waiting_to_handle_position_ = false;
drag_drop_client_->CompleteXdndPosition(source_window_, screen_point_);
drag_drop_client_ = nullptr;
}
}
void XDragContext::ReadActions() {
XDragDropClient* source_client =
XDragDropClient::GetForWindow(source_window_);
if (!source_client) {
std::vector<x11::Atom> atom_array;
if (!x11::Connection::Get()->GetArrayProperty(
source_window_, x11::GetAtom(kXdndActionList), &atom_array)) {
actions_.clear();
} else {
actions_.swap(atom_array);
}
} else {
actions_ = source_client->GetOfferedDragOperations();
}
}
int XDragContext::GetDragOperation() const {
int drag_operation = DragDropTypes::DRAG_NONE;
for (const auto& action : actions_) {
MaskOperation(action, &drag_operation);
}
MaskOperation(suggested_action_, &drag_operation);
return drag_operation;
}
void XDragContext::MaskOperation(x11::Atom xdnd_operation,
int* drag_operation) const {
if (xdnd_operation == x11::GetAtom(kXdndActionCopy)) {
*drag_operation |= DragDropTypes::DRAG_COPY;
} else if (xdnd_operation == x11::GetAtom(kXdndActionMove)) {
*drag_operation |= DragDropTypes::DRAG_MOVE;
} else if (xdnd_operation == x11::GetAtom(kXdndActionLink)) {
*drag_operation |= DragDropTypes::DRAG_LINK;
}
}
bool XDragContext::DispatchPropertyNotifyEvent(
const x11::PropertyNotifyEvent& prop) {
if (prop.atom == x11::GetAtom(kXdndActionList)) {
ReadActions();
return true;
}
return false;
}
}