Make castv2 performance test work.
[chromium-blink-merge.git] / ui / views / widget / desktop_aura / desktop_drag_drop_client_aurax11.cc
blob5a76cbf7a46d40f643ddfd092cd7e9c5a9155bc5
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h"
7 #include <X11/Xatom.h>
9 #include "base/event_types.h"
10 #include "base/lazy_instance.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "third_party/skia/include/core/SkBitmap.h"
14 #include "ui/aura/client/capture_client.h"
15 #include "ui/aura/window.h"
16 #include "ui/aura/window_tree_host.h"
17 #include "ui/base/clipboard/clipboard.h"
18 #include "ui/base/dragdrop/drop_target_event.h"
19 #include "ui/base/dragdrop/os_exchange_data.h"
20 #include "ui/base/dragdrop/os_exchange_data_provider_aurax11.h"
21 #include "ui/base/x/selection_utils.h"
22 #include "ui/base/x/x11_foreign_window_manager.h"
23 #include "ui/base/x/x11_util.h"
24 #include "ui/events/event.h"
25 #include "ui/events/event_utils.h"
26 #include "ui/events/platform/platform_event_source.h"
27 #include "ui/gfx/image/image_skia.h"
28 #include "ui/gfx/screen.h"
29 #include "ui/views/controls/image_view.h"
30 #include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h"
31 #include "ui/views/widget/desktop_aura/x11_topmost_window_finder.h"
32 #include "ui/views/widget/desktop_aura/x11_whole_screen_move_loop.h"
33 #include "ui/views/widget/widget.h"
34 #include "ui/wm/public/drag_drop_client.h"
35 #include "ui/wm/public/drag_drop_delegate.h"
37 using aura::client::DragDropDelegate;
38 using ui::OSExchangeData;
40 namespace {
42 const int kMinXdndVersion = 5;
44 const int kWillAcceptDrop = 1;
45 const int kWantFurtherPosEvents = 2;
47 const char kXdndActionCopy[] = "XdndActionCopy";
48 const char kXdndActionMove[] = "XdndActionMove";
49 const char kXdndActionLink[] = "XdndActionLink";
50 const char kXdndActionDirectSave[] = "XdndActionDirectSave";
52 const char kChromiumDragReciever[] = "_CHROMIUM_DRAG_RECEIVER";
53 const char kXdndSelection[] = "XdndSelection";
54 const char kXdndDirectSave0[] = "XdndDirectSave0";
56 const char* kAtomsToCache[] = {
57 kChromiumDragReciever,
58 "XdndActionAsk",
59 kXdndActionCopy,
60 kXdndActionDirectSave,
61 kXdndActionLink,
62 "XdndActionList",
63 kXdndActionMove,
64 "XdndActionPrivate",
65 "XdndAware",
66 kXdndDirectSave0,
67 "XdndDrop",
68 "XdndEnter",
69 "XdndFinished",
70 "XdndLeave",
71 "XdndPosition",
72 "XdndProxy", // Proxy windows?
73 kXdndSelection,
74 "XdndStatus",
75 "XdndTypeList",
76 ui::Clipboard::kMimeTypeText,
77 NULL
80 int XGetModifiers() {
81 XDisplay* display = gfx::GetXDisplay();
83 XID root, child;
84 int root_x, root_y, win_x, win_y;
85 unsigned int mask;
86 XQueryPointer(display,
87 DefaultRootWindow(display),
88 &root,
89 &child,
90 &root_x,
91 &root_y,
92 &win_x,
93 &win_y,
94 &mask);
95 int modifiers = ui::EF_NONE;
96 if (mask & ShiftMask)
97 modifiers |= ui::EF_SHIFT_DOWN;
98 if (mask & ControlMask)
99 modifiers |= ui::EF_CONTROL_DOWN;
100 if (mask & Mod1Mask)
101 modifiers |= ui::EF_ALT_DOWN;
102 if (mask & Mod4Mask)
103 modifiers |= ui::EF_COMMAND_DOWN;
104 if (mask & Button1Mask)
105 modifiers |= ui::EF_LEFT_MOUSE_BUTTON;
106 if (mask & Button2Mask)
107 modifiers |= ui::EF_MIDDLE_MOUSE_BUTTON;
108 if (mask & Button3Mask)
109 modifiers |= ui::EF_RIGHT_MOUSE_BUTTON;
110 return modifiers;
113 // The time to wait for the target to respond after the user has released the
114 // mouse button before ending the move loop.
115 const int kEndMoveLoopTimeoutMs = 1000;
117 // The time to wait since sending the last XdndPosition message before
118 // reprocessing the most recent mouse move event in case that the window
119 // stacking order has changed and |source_current_window_| needs to be updated.
120 const int kRepeatMouseMoveTimeoutMs = 350;
122 // The minimum alpha before we declare a pixel transparent when searching in
123 // our source image.
124 const uint32 kMinAlpha = 32;
126 // |drag_widget_|'s opacity.
127 const unsigned char kDragWidgetOpacity = 0xc0;
129 static base::LazyInstance<
130 std::map< ::Window, views::DesktopDragDropClientAuraX11*> >::Leaky
131 g_live_client_map = LAZY_INSTANCE_INITIALIZER;
133 } // namespace
135 namespace views {
137 DesktopDragDropClientAuraX11*
138 DesktopDragDropClientAuraX11::g_current_drag_drop_client = NULL;
140 class DesktopDragDropClientAuraX11::X11DragContext
141 : public ui::PlatformEventDispatcher {
142 public:
143 X11DragContext(ui::X11AtomCache* atom_cache,
144 ::Window local_window,
145 const XClientMessageEvent& event);
146 ~X11DragContext() override;
148 // When we receive an XdndPosition message, we need to have all the data
149 // copied from the other window before we process the XdndPosition
150 // message. If we have that data already, dispatch immediately. Otherwise,
151 // delay dispatching until we do.
152 void OnStartXdndPositionMessage(DesktopDragDropClientAuraX11* client,
153 ::Atom suggested_action,
154 ::Window source_window,
155 const gfx::Point& screen_point);
157 // Called to request the next target from the source window. This is only
158 // done on the first XdndPosition; after that, we cache the data offered by
159 // the source window.
160 void RequestNextTarget();
162 // Called when XSelection data has been copied to our process.
163 void OnSelectionNotify(const XSelectionEvent& xselection);
165 // Clones the fetched targets.
166 const ui::SelectionFormatMap& fetched_targets() { return fetched_targets_; }
168 // Reads the "XdndActionList" property from |source_window| and copies it
169 // into |actions|.
170 void ReadActions();
172 // Creates a ui::DragDropTypes::DragOperation representation of the current
173 // action list.
174 int GetDragOperation() const;
176 DesktopDragDropClientAuraX11* source_client() { return source_client_; }
178 private:
179 // Masks the X11 atom |xdnd_operation|'s views representation onto
180 // |drag_operation|.
181 void MaskOperation(::Atom xdnd_operation, int* drag_operation) const;
183 // ui::PlatformEventDispatcher:
184 bool CanDispatchEvent(const ui::PlatformEvent& event) override;
185 uint32_t DispatchEvent(const ui::PlatformEvent& event) override;
187 // The atom cache owned by our parent.
188 ui::X11AtomCache* atom_cache_;
190 // The XID of our chrome local aura window handling our events.
191 ::Window local_window_;
193 // The XID of the window that's initiated the drag.
194 unsigned long source_window_;
196 // The DesktopDragDropClientAuraX11 for |source_window_| if |source_window_|
197 // belongs to a Chrome window.
198 DesktopDragDropClientAuraX11* source_client_;
200 // Used to unselect PropertyChangeMask on |source_window_| if |source_window_|
201 // does not belong to a Chrome window when X11DragContext is destroyed.
202 int foreign_window_manager_source_window_id_;
204 // The client we inform once we're done with requesting data.
205 DesktopDragDropClientAuraX11* drag_drop_client_;
207 // Whether we're blocking the handling of an XdndPosition message by waiting
208 // for |unfetched_targets_| to be fetched.
209 bool waiting_to_handle_position_;
211 // Where the cursor is on screen.
212 gfx::Point screen_point_;
214 // A SelectionFormatMap of data that we have in our process.
215 ui::SelectionFormatMap fetched_targets_;
217 // The names of various data types offered by the other window that we
218 // haven't fetched and put in |fetched_targets_| yet.
219 std::vector<Atom> unfetched_targets_;
221 // XdndPosition messages have a suggested action. Qt applications exclusively
222 // use this, instead of the XdndActionList which is backed by |actions_|.
223 Atom suggested_action_;
225 // Possible actions.
226 std::vector<Atom> actions_;
228 DISALLOW_COPY_AND_ASSIGN(X11DragContext);
231 DesktopDragDropClientAuraX11::X11DragContext::X11DragContext(
232 ui::X11AtomCache* atom_cache,
233 ::Window local_window,
234 const XClientMessageEvent& event)
235 : atom_cache_(atom_cache),
236 local_window_(local_window),
237 source_window_(event.data.l[0]),
238 source_client_(
239 DesktopDragDropClientAuraX11::GetForWindow(source_window_)),
240 foreign_window_manager_source_window_id_(0),
241 drag_drop_client_(NULL),
242 waiting_to_handle_position_(false),
243 suggested_action_(None) {
244 if (!source_client_) {
245 bool get_types = ((event.data.l[1] & 1) != 0);
247 if (get_types) {
248 if (!ui::GetAtomArrayProperty(source_window_,
249 "XdndTypeList",
250 &unfetched_targets_)) {
251 return;
253 } else {
254 // data.l[2,3,4] contain the first three types. Unused slots can be None.
255 for (int i = 0; i < 3; ++i) {
256 if (event.data.l[2+i] != None) {
257 unfetched_targets_.push_back(event.data.l[2+i]);
262 // The window doesn't have a DesktopDragDropClientAuraX11, that means it's
263 // created by some other process. Listen for messages on it.
264 ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
265 foreign_window_manager_source_window_id_ =
266 ui::XForeignWindowManager::GetInstance()->RequestEvents(
267 source_window_, PropertyChangeMask);
269 // We must perform a full sync here because we could be racing
270 // |source_window_|.
271 XSync(gfx::GetXDisplay(), False);
272 } else {
273 // This drag originates from an aura window within our process. This means
274 // that we can shortcut the X11 server and ask the owning SelectionOwner
275 // for the data it's offering.
276 fetched_targets_ = source_client_->GetFormatMap();
279 ReadActions();
282 DesktopDragDropClientAuraX11::X11DragContext::~X11DragContext() {
283 if (!source_client_) {
284 // Unsubscribe from message events.
285 ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
286 ui::XForeignWindowManager::GetInstance()->CancelRequest(
287 foreign_window_manager_source_window_id_);
291 void DesktopDragDropClientAuraX11::X11DragContext::OnStartXdndPositionMessage(
292 DesktopDragDropClientAuraX11* client,
293 ::Atom suggested_action,
294 ::Window source_window,
295 const gfx::Point& screen_point) {
296 DCHECK_EQ(source_window_, source_window);
297 suggested_action_ = suggested_action;
299 if (!unfetched_targets_.empty()) {
300 // We have unfetched targets. That means we need to pause the handling of
301 // the position message and ask the other window for its data.
302 screen_point_ = screen_point;
303 drag_drop_client_ = client;
304 waiting_to_handle_position_ = true;
306 fetched_targets_ = ui::SelectionFormatMap();
307 RequestNextTarget();
308 } else {
309 client->CompleteXdndPosition(source_window, screen_point);
313 void DesktopDragDropClientAuraX11::X11DragContext::RequestNextTarget() {
314 ::Atom target = unfetched_targets_.back();
315 unfetched_targets_.pop_back();
317 XConvertSelection(gfx::GetXDisplay(),
318 atom_cache_->GetAtom(kXdndSelection),
319 target,
320 atom_cache_->GetAtom(kChromiumDragReciever),
321 local_window_,
322 CurrentTime);
325 void DesktopDragDropClientAuraX11::X11DragContext::OnSelectionNotify(
326 const XSelectionEvent& event) {
327 if (!waiting_to_handle_position_) {
328 // A misbehaved window may send SelectionNotify without us requesting data
329 // via XConvertSelection().
330 return;
332 DCHECK(drag_drop_client_);
333 DCHECK_EQ(event.property, atom_cache_->GetAtom(kChromiumDragReciever));
335 scoped_refptr<base::RefCountedMemory> data;
336 ::Atom type = None;
337 if (ui::GetRawBytesOfProperty(local_window_, event.property,
338 &data, NULL, &type)) {
339 fetched_targets_.Insert(event.target, data);
342 if (!unfetched_targets_.empty()) {
343 RequestNextTarget();
344 } else {
345 waiting_to_handle_position_ = false;
346 drag_drop_client_->CompleteXdndPosition(source_window_, screen_point_);
347 drag_drop_client_ = NULL;
351 void DesktopDragDropClientAuraX11::X11DragContext::ReadActions() {
352 if (!source_client_) {
353 std::vector<Atom> atom_array;
354 if (!ui::GetAtomArrayProperty(source_window_,
355 "XdndActionList",
356 &atom_array)) {
357 actions_.clear();
358 } else {
359 actions_.swap(atom_array);
361 } else {
362 // We have a property notify set up for other windows in case they change
363 // their action list. Thankfully, the views interface is static and you
364 // can't change the action list after you enter StartDragAndDrop().
365 actions_ = source_client_->GetOfferedDragOperations();
369 int DesktopDragDropClientAuraX11::X11DragContext::GetDragOperation() const {
370 int drag_operation = ui::DragDropTypes::DRAG_NONE;
371 for (std::vector<Atom>::const_iterator it = actions_.begin();
372 it != actions_.end(); ++it) {
373 MaskOperation(*it, &drag_operation);
376 MaskOperation(suggested_action_, &drag_operation);
378 return drag_operation;
381 void DesktopDragDropClientAuraX11::X11DragContext::MaskOperation(
382 ::Atom xdnd_operation,
383 int* drag_operation) const {
384 if (xdnd_operation == atom_cache_->GetAtom(kXdndActionCopy))
385 *drag_operation |= ui::DragDropTypes::DRAG_COPY;
386 else if (xdnd_operation == atom_cache_->GetAtom(kXdndActionMove))
387 *drag_operation |= ui::DragDropTypes::DRAG_MOVE;
388 else if (xdnd_operation == atom_cache_->GetAtom(kXdndActionLink))
389 *drag_operation |= ui::DragDropTypes::DRAG_LINK;
392 bool DesktopDragDropClientAuraX11::X11DragContext::CanDispatchEvent(
393 const ui::PlatformEvent& event) {
394 return event->xany.window == source_window_;
397 uint32_t DesktopDragDropClientAuraX11::X11DragContext::DispatchEvent(
398 const ui::PlatformEvent& event) {
399 if (event->type == PropertyNotify &&
400 event->xproperty.atom == atom_cache_->GetAtom("XdndActionList")) {
401 ReadActions();
402 return ui::POST_DISPATCH_STOP_PROPAGATION;
404 return ui::POST_DISPATCH_NONE;
407 ///////////////////////////////////////////////////////////////////////////////
409 DesktopDragDropClientAuraX11::DesktopDragDropClientAuraX11(
410 aura::Window* root_window,
411 views::DesktopNativeCursorManager* cursor_manager,
412 Display* xdisplay,
413 ::Window xwindow)
414 : root_window_(root_window),
415 cursor_manager_(cursor_manager),
416 xdisplay_(xdisplay),
417 xwindow_(xwindow),
418 atom_cache_(xdisplay_, kAtomsToCache),
419 current_modifier_state_(ui::EF_NONE),
420 target_window_(NULL),
421 waiting_on_status_(false),
422 status_received_since_enter_(false),
423 source_provider_(NULL),
424 source_current_window_(None),
425 source_state_(SOURCE_STATE_OTHER),
426 drag_operation_(0),
427 negotiated_operation_(ui::DragDropTypes::DRAG_NONE),
428 weak_ptr_factory_(this) {
429 // Some tests change the DesktopDragDropClientAuraX11 associated with an
430 // |xwindow|.
431 g_live_client_map.Get()[xwindow] = this;
433 // Mark that we are aware of drag and drop concepts.
434 unsigned long xdnd_version = kMinXdndVersion;
435 XChangeProperty(xdisplay_, xwindow_, atom_cache_.GetAtom("XdndAware"),
436 XA_ATOM, 32, PropModeReplace,
437 reinterpret_cast<unsigned char*>(&xdnd_version), 1);
440 DesktopDragDropClientAuraX11::~DesktopDragDropClientAuraX11() {
441 // This is necessary when the parent native widget gets destroyed while a drag
442 // operation is in progress.
443 move_loop_->EndMoveLoop();
444 NotifyDragLeave();
446 g_live_client_map.Get().erase(xwindow_);
449 // static
450 DesktopDragDropClientAuraX11* DesktopDragDropClientAuraX11::GetForWindow(
451 ::Window window) {
452 std::map< ::Window, DesktopDragDropClientAuraX11*>::const_iterator it =
453 g_live_client_map.Get().find(window);
454 if (it == g_live_client_map.Get().end())
455 return NULL;
456 return it->second;
459 void DesktopDragDropClientAuraX11::Init() {
460 move_loop_ = CreateMoveLoop(this);
463 void DesktopDragDropClientAuraX11::OnXdndEnter(
464 const XClientMessageEvent& event) {
465 DVLOG(1) << "XdndEnter";
467 int version = (event.data.l[1] & 0xff000000) >> 24;
468 if (version < 3) {
469 LOG(ERROR) << "Received old XdndEnter message.";
470 return;
473 // Make sure that we've run ~X11DragContext() before creating another one.
474 target_current_context_.reset();
475 target_current_context_.reset(
476 new X11DragContext(&atom_cache_, xwindow_, event));
478 // In the Windows implementation, we immediately call DesktopDropTargetWin::
479 // Translate(). Here, we wait until we receive an XdndPosition message
480 // because the enter message doesn't convey any positioning
481 // information.
484 void DesktopDragDropClientAuraX11::OnXdndLeave(
485 const XClientMessageEvent& event) {
486 DVLOG(1) << "XdndLeave";
487 NotifyDragLeave();
488 target_current_context_.reset();
491 void DesktopDragDropClientAuraX11::OnXdndPosition(
492 const XClientMessageEvent& event) {
493 DVLOG(1) << "XdndPosition";
495 unsigned long source_window = event.data.l[0];
496 int x_root_window = event.data.l[2] >> 16;
497 int y_root_window = event.data.l[2] & 0xffff;
498 ::Atom suggested_action = event.data.l[4];
500 if (!target_current_context_.get()) {
501 NOTREACHED();
502 return;
505 // If we already have all the data from this drag, we complete it
506 // immediately.
507 target_current_context_->OnStartXdndPositionMessage(
508 this, suggested_action, source_window,
509 gfx::Point(x_root_window, y_root_window));
512 void DesktopDragDropClientAuraX11::OnXdndStatus(
513 const XClientMessageEvent& event) {
514 DVLOG(1) << "XdndStatus";
516 unsigned long source_window = event.data.l[0];
518 if (source_window != source_current_window_)
519 return;
521 if (source_state_ != SOURCE_STATE_PENDING_DROP &&
522 source_state_ != SOURCE_STATE_OTHER) {
523 return;
526 waiting_on_status_ = false;
527 status_received_since_enter_ = true;
529 if (event.data.l[1] & 1) {
530 ::Atom atom_operation = event.data.l[4];
531 negotiated_operation_ = AtomToDragOperation(atom_operation);
532 } else {
533 negotiated_operation_ = ui::DragDropTypes::DRAG_NONE;
536 if (source_state_ == SOURCE_STATE_PENDING_DROP) {
537 // We were waiting on the status message so we could send the XdndDrop.
538 if (negotiated_operation_ == ui::DragDropTypes::DRAG_NONE) {
539 move_loop_->EndMoveLoop();
540 return;
542 source_state_ = SOURCE_STATE_DROPPED;
543 SendXdndDrop(source_window);
544 return;
547 int cursor_type = ui::kCursorNull;
548 switch (negotiated_operation_) {
549 case ui::DragDropTypes::DRAG_COPY:
550 cursor_type = ui::kCursorCopy;
551 break;
552 case ui::DragDropTypes::DRAG_MOVE:
553 cursor_type = ui::kCursorMove;
554 break;
555 default:
556 cursor_type = ui::kCursorGrabbing;
557 break;
559 move_loop_->UpdateCursor(cursor_manager_->GetInitializedCursor(cursor_type));
561 // Note: event.data.[2,3] specify a rectangle. It is a request by the other
562 // window to not send further XdndPosition messages while the cursor is
563 // within it. However, it is considered advisory and (at least according to
564 // the spec) the other side must handle further position messages within
565 // it. GTK+ doesn't bother with this, so neither should we.
567 if (next_position_message_.get()) {
568 // We were waiting on the status message so we could send off the next
569 // position message we queued up.
570 gfx::Point p = next_position_message_->first;
571 unsigned long event_time = next_position_message_->second;
572 next_position_message_.reset();
574 SendXdndPosition(source_window, p, event_time);
578 void DesktopDragDropClientAuraX11::OnXdndFinished(
579 const XClientMessageEvent& event) {
580 DVLOG(1) << "XdndFinished";
581 unsigned long source_window = event.data.l[0];
582 if (source_current_window_ != source_window)
583 return;
585 // Clear |negotiated_operation_| if the drag was rejected.
586 if ((event.data.l[1] & 1) == 0)
587 negotiated_operation_ = ui::DragDropTypes::DRAG_NONE;
589 // Clear |source_current_window_| to avoid sending XdndLeave upon ending the
590 // move loop.
591 source_current_window_ = None;
592 move_loop_->EndMoveLoop();
595 void DesktopDragDropClientAuraX11::OnXdndDrop(
596 const XClientMessageEvent& event) {
597 DVLOG(1) << "XdndDrop";
599 unsigned long source_window = event.data.l[0];
601 int drag_operation = ui::DragDropTypes::DRAG_NONE;
602 if (target_window_) {
603 aura::client::DragDropDelegate* delegate =
604 aura::client::GetDragDropDelegate(target_window_);
605 if (delegate) {
606 ui::OSExchangeData data(new ui::OSExchangeDataProviderAuraX11(
607 xwindow_, target_current_context_->fetched_targets()));
609 ui::DropTargetEvent event(data,
610 target_window_location_,
611 target_window_root_location_,
612 target_current_context_->GetDragOperation());
613 if (target_current_context_->source_client()) {
614 event.set_flags(target_current_context_->source_client()
615 ->current_modifier_state());
616 } else {
617 event.set_flags(XGetModifiers());
620 if (!IsDragDropInProgress()) {
621 UMA_HISTOGRAM_COUNTS("Event.DragDrop.ExternalOriginDrop", 1);
624 drag_operation = delegate->OnPerformDrop(event);
627 target_window_->RemoveObserver(this);
628 target_window_ = NULL;
631 XEvent xev;
632 xev.xclient.type = ClientMessage;
633 xev.xclient.message_type = atom_cache_.GetAtom("XdndFinished");
634 xev.xclient.format = 32;
635 xev.xclient.window = source_window;
636 xev.xclient.data.l[0] = xwindow_;
637 xev.xclient.data.l[1] = (drag_operation != 0) ? 1 : 0;
638 xev.xclient.data.l[2] = DragOperationToAtom(drag_operation);
640 SendXClientEvent(source_window, &xev);
643 void DesktopDragDropClientAuraX11::OnSelectionNotify(
644 const XSelectionEvent& xselection) {
645 if (target_current_context_)
646 target_current_context_->OnSelectionNotify(xselection);
648 // ICCCM requires us to delete the property passed into SelectionNotify.
649 if (xselection.property != None)
650 XDeleteProperty(xdisplay_, xwindow_, xselection.property);
653 int DesktopDragDropClientAuraX11::StartDragAndDrop(
654 const ui::OSExchangeData& data,
655 aura::Window* root_window,
656 aura::Window* source_window,
657 const gfx::Point& screen_location,
658 int operation,
659 ui::DragDropTypes::DragEventSource source) {
660 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Start", source,
661 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
663 source_current_window_ = None;
664 DCHECK(!g_current_drag_drop_client);
665 g_current_drag_drop_client = this;
666 waiting_on_status_ = false;
667 next_position_message_.reset();
668 status_received_since_enter_ = false;
669 source_state_ = SOURCE_STATE_OTHER;
670 drag_operation_ = operation;
671 negotiated_operation_ = ui::DragDropTypes::DRAG_NONE;
673 const ui::OSExchangeData::Provider* provider = &data.provider();
674 source_provider_ = static_cast<const ui::OSExchangeDataProviderAuraX11*>(
675 provider);
677 source_provider_->TakeOwnershipOfSelection();
679 std::vector< ::Atom> actions = GetOfferedDragOperations();
680 if (!source_provider_->file_contents_name().empty()) {
681 actions.push_back(atom_cache_.GetAtom(kXdndActionDirectSave));
682 ui::SetStringProperty(
683 xwindow_,
684 atom_cache_.GetAtom(kXdndDirectSave0),
685 atom_cache_.GetAtom(ui::Clipboard::kMimeTypeText),
686 source_provider_->file_contents_name().AsUTF8Unsafe());
688 ui::SetAtomArrayProperty(xwindow_, "XdndActionList", "ATOM", actions);
690 gfx::ImageSkia drag_image = source_provider_->GetDragImage();
691 if (IsValidDragImage(drag_image)) {
692 CreateDragWidget(drag_image);
693 drag_widget_offset_ = source_provider_->GetDragImageOffset();
696 // Chrome expects starting drag and drop to release capture.
697 aura::Window* capture_window =
698 aura::client::GetCaptureClient(root_window)->GetGlobalCaptureWindow();
699 if (capture_window)
700 capture_window->ReleaseCapture();
702 // It is possible for the DesktopWindowTreeHostX11 to be destroyed during the
703 // move loop, which would also destroy this drag-client. So keep track of
704 // whether it is alive after the drag ends.
705 base::WeakPtr<DesktopDragDropClientAuraX11> alive(
706 weak_ptr_factory_.GetWeakPtr());
708 // Windows has a specific method, DoDragDrop(), which performs the entire
709 // drag. We have to emulate this, so we spin off a nested runloop which will
710 // track all cursor movement and reroute events to a specific handler.
711 move_loop_->RunMoveLoop(
712 source_window,
713 cursor_manager_->GetInitializedCursor(ui::kCursorGrabbing));
715 if (alive) {
716 if (negotiated_operation_ == ui::DragDropTypes::DRAG_NONE) {
717 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Cancel", source,
718 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
719 } else {
720 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Drop", source,
721 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
723 drag_widget_.reset();
725 source_provider_ = NULL;
726 g_current_drag_drop_client = NULL;
727 drag_operation_ = 0;
728 XDeleteProperty(xdisplay_, xwindow_, atom_cache_.GetAtom("XdndActionList"));
729 XDeleteProperty(xdisplay_, xwindow_, atom_cache_.GetAtom(kXdndDirectSave0));
731 return negotiated_operation_;
733 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Cancel", source,
734 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
735 return ui::DragDropTypes::DRAG_NONE;
738 void DesktopDragDropClientAuraX11::DragUpdate(aura::Window* target,
739 const ui::LocatedEvent& event) {
740 NOTIMPLEMENTED();
743 void DesktopDragDropClientAuraX11::Drop(aura::Window* target,
744 const ui::LocatedEvent& event) {
745 NOTIMPLEMENTED();
748 void DesktopDragDropClientAuraX11::DragCancel() {
749 move_loop_->EndMoveLoop();
752 bool DesktopDragDropClientAuraX11::IsDragDropInProgress() {
753 return !!g_current_drag_drop_client;
756 void DesktopDragDropClientAuraX11::OnWindowDestroyed(aura::Window* window) {
757 DCHECK_EQ(target_window_, window);
758 target_window_ = NULL;
761 void DesktopDragDropClientAuraX11::OnMouseMovement(
762 const gfx::Point& screen_point,
763 int flags,
764 base::TimeDelta event_time) {
765 if (drag_widget_.get()) {
766 drag_widget_->SetBounds(
767 gfx::Rect(screen_point - drag_widget_offset_,
768 drag_widget_->GetWindowBoundsInScreen().size()));
769 drag_widget_->StackAtTop();
772 const int kModifiers = ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN |
773 ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN |
774 ui::EF_LEFT_MOUSE_BUTTON |
775 ui::EF_MIDDLE_MOUSE_BUTTON |
776 ui::EF_RIGHT_MOUSE_BUTTON;
777 current_modifier_state_ = flags & kModifiers;
779 repeat_mouse_move_timer_.Stop();
780 ProcessMouseMove(screen_point, event_time.InMilliseconds());
783 void DesktopDragDropClientAuraX11::OnMouseReleased() {
784 repeat_mouse_move_timer_.Stop();
786 if (source_state_ != SOURCE_STATE_OTHER) {
787 // The user has previously released the mouse and is clicking in
788 // frustration.
789 move_loop_->EndMoveLoop();
790 return;
793 if (source_current_window_ != None) {
794 if (waiting_on_status_) {
795 if (status_received_since_enter_) {
796 // If we are waiting for an XdndStatus message, we need to wait for it
797 // to complete.
798 source_state_ = SOURCE_STATE_PENDING_DROP;
800 // Start timer to end the move loop if the target takes too long to send
801 // the XdndStatus and XdndFinished messages.
802 StartEndMoveLoopTimer();
803 return;
806 move_loop_->EndMoveLoop();
807 return;
810 if (negotiated_operation_ != ui::DragDropTypes::DRAG_NONE) {
811 // Start timer to end the move loop if the target takes too long to send
812 // an XdndFinished message. It is important that StartEndMoveLoopTimer()
813 // is called before SendXdndDrop() because SendXdndDrop()
814 // sends XdndFinished synchronously if the drop target is a Chrome
815 // window.
816 StartEndMoveLoopTimer();
818 // We have negotiated an action with the other end.
819 source_state_ = SOURCE_STATE_DROPPED;
820 SendXdndDrop(source_current_window_);
821 return;
825 move_loop_->EndMoveLoop();
828 void DesktopDragDropClientAuraX11::OnMoveLoopEnded() {
829 if (source_current_window_ != None) {
830 SendXdndLeave(source_current_window_);
831 source_current_window_ = None;
833 target_current_context_.reset();
834 repeat_mouse_move_timer_.Stop();
835 end_move_loop_timer_.Stop();
838 scoped_ptr<X11MoveLoop> DesktopDragDropClientAuraX11::CreateMoveLoop(
839 X11MoveLoopDelegate* delegate) {
840 return make_scoped_ptr(new X11WholeScreenMoveLoop(this));
843 XID DesktopDragDropClientAuraX11::FindWindowFor(
844 const gfx::Point& screen_point) {
845 views::X11TopmostWindowFinder finder;
846 ::Window target = finder.FindWindowAt(screen_point);
848 if (target == None)
849 return None;
851 // Figure out which window we should test as XdndAware. If |target| has
852 // XdndProxy, it will set that proxy on target, and if not, |target|'s
853 // original value will remain.
854 ui::GetXIDProperty(target, "XdndProxy", &target);
856 int version;
857 if (ui::GetIntProperty(target, "XdndAware", &version) &&
858 version >= kMinXdndVersion) {
859 return target;
861 return None;
864 void DesktopDragDropClientAuraX11::SendXClientEvent(::Window xid,
865 XEvent* xev) {
866 DCHECK_EQ(ClientMessage, xev->type);
868 // Don't send messages to the X11 message queue if we can help it.
869 DesktopDragDropClientAuraX11* short_circuit = GetForWindow(xid);
870 if (short_circuit) {
871 Atom message_type = xev->xclient.message_type;
872 if (message_type == atom_cache_.GetAtom("XdndEnter")) {
873 short_circuit->OnXdndEnter(xev->xclient);
874 return;
875 } else if (message_type == atom_cache_.GetAtom("XdndLeave")) {
876 short_circuit->OnXdndLeave(xev->xclient);
877 return;
878 } else if (message_type == atom_cache_.GetAtom("XdndPosition")) {
879 short_circuit->OnXdndPosition(xev->xclient);
880 return;
881 } else if (message_type == atom_cache_.GetAtom("XdndStatus")) {
882 short_circuit->OnXdndStatus(xev->xclient);
883 return;
884 } else if (message_type == atom_cache_.GetAtom("XdndFinished")) {
885 short_circuit->OnXdndFinished(xev->xclient);
886 return;
887 } else if (message_type == atom_cache_.GetAtom("XdndDrop")) {
888 short_circuit->OnXdndDrop(xev->xclient);
889 return;
893 // I don't understand why the GTK+ code is doing what it's doing here. It
894 // goes out of its way to send the XEvent so that it receives a callback on
895 // success or failure, and when it fails, it then sends an internal
896 // GdkEvent about the failed drag. (And sending this message doesn't appear
897 // to go through normal xlib machinery, but instead passes through the low
898 // level xProto (the x11 wire format) that I don't understand.
900 // I'm unsure if I have to jump through those hoops, or if XSendEvent is
901 // sufficient.
902 XSendEvent(xdisplay_, xid, False, 0, xev);
905 void DesktopDragDropClientAuraX11::ProcessMouseMove(
906 const gfx::Point& screen_point,
907 unsigned long event_time) {
908 if (source_state_ != SOURCE_STATE_OTHER)
909 return;
911 // Find the current window the cursor is over.
912 ::Window dest_window = FindWindowFor(screen_point);
914 if (source_current_window_ != dest_window) {
915 if (source_current_window_ != None)
916 SendXdndLeave(source_current_window_);
918 source_current_window_ = dest_window;
919 waiting_on_status_ = false;
920 next_position_message_.reset();
921 status_received_since_enter_ = false;
922 negotiated_operation_ = ui::DragDropTypes::DRAG_NONE;
924 if (source_current_window_ != None)
925 SendXdndEnter(source_current_window_);
928 if (source_current_window_ != None) {
929 if (waiting_on_status_) {
930 next_position_message_.reset(
931 new std::pair<gfx::Point, unsigned long>(screen_point, event_time));
932 } else {
933 SendXdndPosition(dest_window, screen_point, event_time);
938 void DesktopDragDropClientAuraX11::StartEndMoveLoopTimer() {
939 end_move_loop_timer_.Start(FROM_HERE,
940 base::TimeDelta::FromMilliseconds(
941 kEndMoveLoopTimeoutMs),
942 this,
943 &DesktopDragDropClientAuraX11::EndMoveLoop);
946 void DesktopDragDropClientAuraX11::EndMoveLoop() {
947 move_loop_->EndMoveLoop();
950 void DesktopDragDropClientAuraX11::DragTranslate(
951 const gfx::Point& root_window_location,
952 scoped_ptr<ui::OSExchangeData>* data,
953 scoped_ptr<ui::DropTargetEvent>* event,
954 aura::client::DragDropDelegate** delegate) {
955 gfx::Point root_location = root_window_location;
956 root_window_->GetHost()->ConvertPointFromNativeScreen(&root_location);
957 aura::Window* target_window =
958 root_window_->GetEventHandlerForPoint(root_location);
959 bool target_window_changed = false;
960 if (target_window != target_window_) {
961 if (target_window_)
962 NotifyDragLeave();
963 target_window_ = target_window;
964 if (target_window_)
965 target_window_->AddObserver(this);
966 target_window_changed = true;
968 *delegate = NULL;
969 if (!target_window_)
970 return;
971 *delegate = aura::client::GetDragDropDelegate(target_window_);
972 if (!*delegate)
973 return;
975 data->reset(new OSExchangeData(new ui::OSExchangeDataProviderAuraX11(
976 xwindow_, target_current_context_->fetched_targets())));
977 gfx::Point location = root_location;
978 aura::Window::ConvertPointToTarget(root_window_, target_window_, &location);
980 target_window_location_ = location;
981 target_window_root_location_ = root_location;
983 int drag_op = target_current_context_->GetDragOperation();
984 // KDE-based file browsers such as Dolphin change the drag operation depending
985 // on whether alt/ctrl/shift was pressed. However once Chromium gets control
986 // over the X11 events, the source application does no longer receive X11
987 // events for key modifier changes, so the dnd operation gets stuck in an
988 // incorrect state. Blink can only dnd-open files of type DRAG_COPY, so the
989 // DRAG_COPY mask is added if the dnd object is a file.
990 if (drag_op & (ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_LINK) &&
991 data->get()->HasFile()) {
992 drag_op |= ui::DragDropTypes::DRAG_COPY;
995 event->reset(new ui::DropTargetEvent(
996 *(data->get()),
997 location,
998 root_location,
999 drag_op));
1000 if (target_current_context_->source_client()) {
1001 (*event)->set_flags(
1002 target_current_context_->source_client()->current_modifier_state());
1003 } else {
1004 (*event)->set_flags(XGetModifiers());
1006 if (target_window_changed)
1007 (*delegate)->OnDragEntered(*event->get());
1010 void DesktopDragDropClientAuraX11::NotifyDragLeave() {
1011 if (!target_window_)
1012 return;
1013 DragDropDelegate* delegate =
1014 aura::client::GetDragDropDelegate(target_window_);
1015 if (delegate)
1016 delegate->OnDragExited();
1017 target_window_->RemoveObserver(this);
1018 target_window_ = NULL;
1021 ::Atom DesktopDragDropClientAuraX11::DragOperationToAtom(
1022 int drag_operation) {
1023 if (drag_operation & ui::DragDropTypes::DRAG_COPY)
1024 return atom_cache_.GetAtom(kXdndActionCopy);
1025 if (drag_operation & ui::DragDropTypes::DRAG_MOVE)
1026 return atom_cache_.GetAtom(kXdndActionMove);
1027 if (drag_operation & ui::DragDropTypes::DRAG_LINK)
1028 return atom_cache_.GetAtom(kXdndActionLink);
1030 return None;
1033 ui::DragDropTypes::DragOperation
1034 DesktopDragDropClientAuraX11::AtomToDragOperation(::Atom atom) {
1035 if (atom == atom_cache_.GetAtom(kXdndActionCopy))
1036 return ui::DragDropTypes::DRAG_COPY;
1037 if (atom == atom_cache_.GetAtom(kXdndActionMove))
1038 return ui::DragDropTypes::DRAG_MOVE;
1039 if (atom == atom_cache_.GetAtom(kXdndActionLink))
1040 return ui::DragDropTypes::DRAG_LINK;
1042 return ui::DragDropTypes::DRAG_NONE;
1045 std::vector< ::Atom> DesktopDragDropClientAuraX11::GetOfferedDragOperations() {
1046 std::vector< ::Atom> operations;
1047 if (drag_operation_ & ui::DragDropTypes::DRAG_COPY)
1048 operations.push_back(atom_cache_.GetAtom(kXdndActionCopy));
1049 if (drag_operation_ & ui::DragDropTypes::DRAG_MOVE)
1050 operations.push_back(atom_cache_.GetAtom(kXdndActionMove));
1051 if (drag_operation_ & ui::DragDropTypes::DRAG_LINK)
1052 operations.push_back(atom_cache_.GetAtom(kXdndActionLink));
1053 return operations;
1056 ui::SelectionFormatMap DesktopDragDropClientAuraX11::GetFormatMap() const {
1057 return source_provider_ ? source_provider_->GetFormatMap() :
1058 ui::SelectionFormatMap();
1061 void DesktopDragDropClientAuraX11::CompleteXdndPosition(
1062 ::Window source_window,
1063 const gfx::Point& screen_point) {
1064 int drag_operation = ui::DragDropTypes::DRAG_NONE;
1065 scoped_ptr<ui::OSExchangeData> data;
1066 scoped_ptr<ui::DropTargetEvent> drop_target_event;
1067 DragDropDelegate* delegate = NULL;
1068 DragTranslate(screen_point, &data, &drop_target_event, &delegate);
1069 if (delegate)
1070 drag_operation = delegate->OnDragUpdated(*drop_target_event);
1072 // Sends an XdndStatus message back to the source_window. l[2,3]
1073 // theoretically represent an area in the window where the current action is
1074 // the same as what we're returning, but I can't find any implementation that
1075 // actually making use of this. A client can return (0, 0) and/or set the
1076 // first bit of l[1] to disable the feature, and it appears that gtk neither
1077 // sets this nor respects it if set.
1078 XEvent xev;
1079 xev.xclient.type = ClientMessage;
1080 xev.xclient.message_type = atom_cache_.GetAtom("XdndStatus");
1081 xev.xclient.format = 32;
1082 xev.xclient.window = source_window;
1083 xev.xclient.data.l[0] = xwindow_;
1084 xev.xclient.data.l[1] = (drag_operation != 0) ?
1085 (kWantFurtherPosEvents | kWillAcceptDrop) : 0;
1086 xev.xclient.data.l[2] = 0;
1087 xev.xclient.data.l[3] = 0;
1088 xev.xclient.data.l[4] = DragOperationToAtom(drag_operation);
1090 SendXClientEvent(source_window, &xev);
1093 void DesktopDragDropClientAuraX11::SendXdndEnter(::Window dest_window) {
1094 XEvent xev;
1095 xev.xclient.type = ClientMessage;
1096 xev.xclient.message_type = atom_cache_.GetAtom("XdndEnter");
1097 xev.xclient.format = 32;
1098 xev.xclient.window = dest_window;
1099 xev.xclient.data.l[0] = xwindow_;
1100 xev.xclient.data.l[1] = (kMinXdndVersion << 24); // The version number.
1101 xev.xclient.data.l[2] = 0;
1102 xev.xclient.data.l[3] = 0;
1103 xev.xclient.data.l[4] = 0;
1105 std::vector<Atom> targets;
1106 source_provider_->RetrieveTargets(&targets);
1108 if (targets.size() > 3) {
1109 xev.xclient.data.l[1] |= 1;
1110 ui::SetAtomArrayProperty(xwindow_, "XdndTypeList", "ATOM", targets);
1111 } else {
1112 // Pack the targets into the enter message.
1113 for (size_t i = 0; i < targets.size(); ++i)
1114 xev.xclient.data.l[2 + i] = targets[i];
1117 SendXClientEvent(dest_window, &xev);
1120 void DesktopDragDropClientAuraX11::SendXdndLeave(::Window dest_window) {
1121 XEvent xev;
1122 xev.xclient.type = ClientMessage;
1123 xev.xclient.message_type = atom_cache_.GetAtom("XdndLeave");
1124 xev.xclient.format = 32;
1125 xev.xclient.window = dest_window;
1126 xev.xclient.data.l[0] = xwindow_;
1127 xev.xclient.data.l[1] = 0;
1128 xev.xclient.data.l[2] = 0;
1129 xev.xclient.data.l[3] = 0;
1130 xev.xclient.data.l[4] = 0;
1131 SendXClientEvent(dest_window, &xev);
1134 void DesktopDragDropClientAuraX11::SendXdndPosition(
1135 ::Window dest_window,
1136 const gfx::Point& screen_point,
1137 unsigned long event_time) {
1138 waiting_on_status_ = true;
1140 XEvent xev;
1141 xev.xclient.type = ClientMessage;
1142 xev.xclient.message_type = atom_cache_.GetAtom("XdndPosition");
1143 xev.xclient.format = 32;
1144 xev.xclient.window = dest_window;
1145 xev.xclient.data.l[0] = xwindow_;
1146 xev.xclient.data.l[1] = 0;
1147 xev.xclient.data.l[2] = (screen_point.x() << 16) | screen_point.y();
1148 xev.xclient.data.l[3] = event_time;
1149 xev.xclient.data.l[4] = DragOperationToAtom(drag_operation_);
1150 SendXClientEvent(dest_window, &xev);
1152 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html and
1153 // the Xdnd protocol both recommend that drag events should be sent
1154 // periodically.
1155 repeat_mouse_move_timer_.Start(
1156 FROM_HERE,
1157 base::TimeDelta::FromMilliseconds(kRepeatMouseMoveTimeoutMs),
1158 base::Bind(&DesktopDragDropClientAuraX11::ProcessMouseMove,
1159 base::Unretained(this),
1160 screen_point,
1161 event_time));
1164 void DesktopDragDropClientAuraX11::SendXdndDrop(::Window dest_window) {
1165 XEvent xev;
1166 xev.xclient.type = ClientMessage;
1167 xev.xclient.message_type = atom_cache_.GetAtom("XdndDrop");
1168 xev.xclient.format = 32;
1169 xev.xclient.window = dest_window;
1170 xev.xclient.data.l[0] = xwindow_;
1171 xev.xclient.data.l[1] = 0;
1172 xev.xclient.data.l[2] = CurrentTime;
1173 xev.xclient.data.l[3] = None;
1174 xev.xclient.data.l[4] = None;
1175 SendXClientEvent(dest_window, &xev);
1178 void DesktopDragDropClientAuraX11::CreateDragWidget(
1179 const gfx::ImageSkia& image) {
1180 Widget* widget = new Widget;
1181 Widget::InitParams params(Widget::InitParams::TYPE_DRAG);
1182 params.opacity = Widget::InitParams::OPAQUE_WINDOW;
1183 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
1184 params.accept_events = false;
1186 gfx::Point location = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint() -
1187 drag_widget_offset_;
1188 params.bounds = gfx::Rect(location, image.size());
1189 widget->set_focus_on_creation(false);
1190 widget->set_frame_type(Widget::FRAME_TYPE_FORCE_NATIVE);
1191 widget->Init(params);
1192 widget->SetOpacity(kDragWidgetOpacity);
1193 widget->GetNativeWindow()->SetName("DragWindow");
1195 ImageView* image_view = new ImageView();
1196 image_view->SetImage(image);
1197 image_view->SetBounds(0, 0, image.width(), image.height());
1198 widget->SetContentsView(image_view);
1199 widget->Show();
1200 widget->GetNativeWindow()->layer()->SetFillsBoundsOpaquely(false);
1202 drag_widget_.reset(widget);
1205 bool DesktopDragDropClientAuraX11::IsValidDragImage(
1206 const gfx::ImageSkia& image) {
1207 if (image.isNull())
1208 return false;
1210 // Because we need a GL context per window, we do a quick check so that we
1211 // don't make another context if the window would just be displaying a mostly
1212 // transparent image.
1213 const SkBitmap* in_bitmap = image.bitmap();
1214 SkAutoLockPixels in_lock(*in_bitmap);
1215 for (int y = 0; y < in_bitmap->height(); ++y) {
1216 uint32* in_row = in_bitmap->getAddr32(0, y);
1218 for (int x = 0; x < in_bitmap->width(); ++x) {
1219 if (SkColorGetA(in_row[x]) > kMinAlpha)
1220 return true;
1224 return false;
1227 } // namespace views