// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/x/selection_requestor.h"
#include "base/run_loop.h"
#include "ui/base/x/selection_utils.h"
#include "ui/base/x/x11_util.h"
#include "ui/events/platform/platform_event_dispatcher.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/x/x11_types.h"
namespace ui {
namespace {
const char kChromeSelection[] = "CHROME_SELECTION";
const char* kAtomsToCache[] = {
kChromeSelection,
NULL
};
} // namespace
SelectionRequestor::SelectionRequestor(Display* x_display,
Window x_window,
Atom selection_name,
PlatformEventDispatcher* dispatcher)
: x_display_(x_display),
x_window_(x_window),
selection_name_(selection_name),
dispatcher_(dispatcher),
atom_cache_(x_display_, kAtomsToCache) {
}
SelectionRequestor::~SelectionRequestor() {}
bool SelectionRequestor::PerformBlockingConvertSelection(
Atom target,
scoped_refptr<base::RefCountedMemory>* out_data,
size_t* out_data_bytes,
size_t* out_data_items,
Atom* out_type) {
// The name of the property that we are either:
// - Passing as a parameter with the XConvertSelection() request.
// OR
// - Asking the selection owner to set on |x_window_|.
Atom property = atom_cache_.GetAtom(kChromeSelection);
XConvertSelection(x_display_,
selection_name_,
target,
property,
x_window_,
CurrentTime);
// Now that we've thrown our message off to the X11 server, we block waiting
// for a response.
PendingRequest pending_request(target);
BlockTillSelectionNotifyForRequest(&pending_request);
bool success = false;
if (pending_request.returned_property == property) {
success = ui::GetRawBytesOfProperty(x_window_,
pending_request.returned_property,
out_data, out_data_bytes,
out_data_items, out_type);
}
if (pending_request.returned_property != None)
XDeleteProperty(x_display_, x_window_, pending_request.returned_property);
return success;
}
void SelectionRequestor::PerformBlockingConvertSelectionWithParameter(
Atom target,
const std::vector< ::Atom>& parameter) {
SetAtomArrayProperty(x_window_, kChromeSelection, "ATOM", parameter);
PerformBlockingConvertSelection(target, NULL, NULL, NULL, NULL);
}
SelectionData SelectionRequestor::RequestAndWaitForTypes(
const std::vector< ::Atom>& types) {
for (std::vector< ::Atom>::const_iterator it = types.begin();
it != types.end(); ++it) {
scoped_refptr<base::RefCountedMemory> data;
size_t data_bytes = 0;
::Atom type = None;
if (PerformBlockingConvertSelection(*it,
&data,
&data_bytes,
NULL,
&type) &&
type == *it) {
return SelectionData(type, data);
}
}
return SelectionData();
}
void SelectionRequestor::OnSelectionNotify(const XSelectionEvent& event) {
// Find the PendingRequest for the corresponding XConvertSelection call. If
// there are multiple pending requests on the same target, satisfy them in
// FIFO order.
PendingRequest* request_notified = NULL;
if (selection_name_ == event.selection) {
for (std::list<PendingRequest*>::iterator iter = pending_requests_.begin();
iter != pending_requests_.end(); ++iter) {
PendingRequest* request = *iter;
if (request->returned)
continue;
if (request->target != event.target)
continue;
request_notified = request;
break;
}
}
// This event doesn't correspond to any XConvertSelection calls that we
// issued in PerformBlockingConvertSelection. This shouldn't happen, but any
// client can send any message, so it can happen.
if (!request_notified) {
// ICCCM requires us to delete the property passed into SelectionNotify. If
// |request_notified| is true, the property will be deleted when the run
// loop has quit.
if (event.property != None)
XDeleteProperty(x_display_, x_window_, event.property);
return;
}
request_notified->returned_property = event.property;
request_notified->returned = true;
if (!request_notified->quit_closure.is_null())
request_notified->quit_closure.Run();
}
void SelectionRequestor::BlockTillSelectionNotifyForRequest(
PendingRequest* request) {
pending_requests_.push_back(request);
const int kMaxWaitTimeForClipboardResponse = 300;
if (PlatformEventSource::GetInstance()) {
base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
base::RunLoop run_loop;
request->quit_closure = run_loop.QuitClosure();
loop->PostDelayedTask(
FROM_HERE,
request->quit_closure,
base::TimeDelta::FromMilliseconds(kMaxWaitTimeForClipboardResponse));
run_loop.Run();
} else {
// This occurs if PerformBlockingConvertSelection() is called during
// shutdown and the PlatformEventSource has already been destroyed.
base::TimeTicks start = base::TimeTicks::Now();
while (!request->returned) {
if (XPending(x_display_)) {
XEvent event;
XNextEvent(x_display_, &event);
dispatcher_->DispatchEvent(&event);
}
base::TimeDelta wait_time = base::TimeTicks::Now() - start;
if (wait_time.InMilliseconds() > kMaxWaitTimeForClipboardResponse)
break;
}
}
DCHECK(!pending_requests_.empty());
DCHECK_EQ(request, pending_requests_.back());
pending_requests_.pop_back();
}
SelectionRequestor::PendingRequest::PendingRequest(Atom target)
: target(target),
returned_property(None),
returned(false) {
}
SelectionRequestor::PendingRequest::~PendingRequest() {
}
} // namespace ui