MacViews: Get c/b/ui/views/tabs to build on Mac
[chromium-blink-merge.git] / ui / views / widget / desktop_aura / desktop_drag_drop_client_aurax11.cc
blob3085f6ab1f7675040f085293f55990c1ff28bc08
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 "third_party/skia/include/core/SkBitmap.h"
13 #include "ui/aura/client/capture_client.h"
14 #include "ui/aura/window.h"
15 #include "ui/aura/window_tree_host.h"
16 #include "ui/base/clipboard/clipboard.h"
17 #include "ui/base/dragdrop/drop_target_event.h"
18 #include "ui/base/dragdrop/os_exchange_data.h"
19 #include "ui/base/dragdrop/os_exchange_data_provider_aurax11.h"
20 #include "ui/base/x/selection_utils.h"
21 #include "ui/base/x/x11_foreign_window_manager.h"
22 #include "ui/base/x/x11_util.h"
23 #include "ui/events/event.h"
24 #include "ui/events/platform/platform_event_source.h"
25 #include "ui/gfx/image/image_skia.h"
26 #include "ui/gfx/screen.h"
27 #include "ui/views/controls/image_view.h"
28 #include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h"
29 #include "ui/views/widget/desktop_aura/x11_topmost_window_finder.h"
30 #include "ui/views/widget/desktop_aura/x11_whole_screen_move_loop.h"
31 #include "ui/views/widget/widget.h"
32 #include "ui/wm/public/drag_drop_client.h"
33 #include "ui/wm/public/drag_drop_delegate.h"
35 using aura::client::DragDropDelegate;
36 using ui::OSExchangeData;
38 namespace {
40 const int kMinXdndVersion = 5;
42 const int kWillAcceptDrop = 1;
43 const int kWantFurtherPosEvents = 2;
45 const char kXdndActionCopy[] = "XdndActionCopy";
46 const char kXdndActionMove[] = "XdndActionMove";
47 const char kXdndActionLink[] = "XdndActionLink";
48 const char kXdndActionDirectSave[] = "XdndActionDirectSave";
50 const char kChromiumDragReciever[] = "_CHROMIUM_DRAG_RECEIVER";
51 const char kXdndSelection[] = "XdndSelection";
52 const char kXdndDirectSave0[] = "XdndDirectSave0";
54 const char* kAtomsToCache[] = {
55 kChromiumDragReciever,
56 "XdndActionAsk",
57 kXdndActionCopy,
58 kXdndActionDirectSave,
59 kXdndActionLink,
60 "XdndActionList",
61 kXdndActionMove,
62 "XdndActionPrivate",
63 "XdndAware",
64 kXdndDirectSave0,
65 "XdndDrop",
66 "XdndEnter",
67 "XdndFinished",
68 "XdndLeave",
69 "XdndPosition",
70 "XdndProxy", // Proxy windows?
71 kXdndSelection,
72 "XdndStatus",
73 "XdndTypeList",
74 ui::Clipboard::kMimeTypeText,
75 NULL
78 // The time to wait for the target to respond after the user has released the
79 // mouse button before ending the move loop.
80 const int kEndMoveLoopTimeoutMs = 1000;
82 // The time to wait since sending the last XdndPosition message before
83 // reprocessing the most recent mouse move event in case that the window
84 // stacking order has changed and |source_current_window_| needs to be updated.
85 const int kRepeatMouseMoveTimeoutMs = 350;
87 // The minimum alpha before we declare a pixel transparent when searching in
88 // our source image.
89 const uint32 kMinAlpha = 32;
91 // |drag_widget_|'s opacity.
92 const unsigned char kDragWidgetOpacity = 0xc0;
94 static base::LazyInstance<
95 std::map< ::Window, views::DesktopDragDropClientAuraX11*> >::Leaky
96 g_live_client_map = LAZY_INSTANCE_INITIALIZER;
98 } // namespace
100 namespace views {
102 DesktopDragDropClientAuraX11*
103 DesktopDragDropClientAuraX11::g_current_drag_drop_client = NULL;
105 class DesktopDragDropClientAuraX11::X11DragContext
106 : public ui::PlatformEventDispatcher {
107 public:
108 X11DragContext(ui::X11AtomCache* atom_cache,
109 ::Window local_window,
110 const XClientMessageEvent& event);
111 virtual ~X11DragContext();
113 // When we receive an XdndPosition message, we need to have all the data
114 // copied from the other window before we process the XdndPosition
115 // message. If we have that data already, dispatch immediately. Otherwise,
116 // delay dispatching until we do.
117 void OnStartXdndPositionMessage(DesktopDragDropClientAuraX11* client,
118 ::Atom suggested_action,
119 ::Window source_window,
120 const gfx::Point& screen_point);
122 // Called to request the next target from the source window. This is only
123 // done on the first XdndPosition; after that, we cache the data offered by
124 // the source window.
125 void RequestNextTarget();
127 // Called when XSelection data has been copied to our process.
128 void OnSelectionNotify(const XSelectionEvent& xselection);
130 // Clones the fetched targets.
131 const ui::SelectionFormatMap& fetched_targets() { return fetched_targets_; }
133 // Reads the "XdndActionList" property from |source_window| and copies it
134 // into |actions|.
135 void ReadActions();
137 // Creates a ui::DragDropTypes::DragOperation representation of the current
138 // action list.
139 int GetDragOperation() const;
141 private:
142 // Masks the X11 atom |xdnd_operation|'s views representation onto
143 // |drag_operation|.
144 void MaskOperation(::Atom xdnd_operation, int* drag_operation) const;
146 // ui::PlatformEventDispatcher:
147 virtual bool CanDispatchEvent(const ui::PlatformEvent& event) override;
148 virtual uint32_t DispatchEvent(const ui::PlatformEvent& event) override;
150 // The atom cache owned by our parent.
151 ui::X11AtomCache* atom_cache_;
153 // The XID of our chrome local aura window handling our events.
154 ::Window local_window_;
156 // The XID of the window that's initiated the drag.
157 unsigned long source_window_;
159 // The DesktopDragDropClientAuraX11 for |source_window_| if |source_window_|
160 // belongs to a Chrome window.
161 DesktopDragDropClientAuraX11* source_client_;
163 // Used to unselect PropertyChangeMask on |source_window_| if |source_window_|
164 // does not belong to a Chrome window when X11DragContext is destroyed.
165 int foreign_window_manager_source_window_id_;
167 // The client we inform once we're done with requesting data.
168 DesktopDragDropClientAuraX11* drag_drop_client_;
170 // Whether we're blocking the handling of an XdndPosition message by waiting
171 // for |unfetched_targets_| to be fetched.
172 bool waiting_to_handle_position_;
174 // Where the cursor is on screen.
175 gfx::Point screen_point_;
177 // A SelectionFormatMap of data that we have in our process.
178 ui::SelectionFormatMap fetched_targets_;
180 // The names of various data types offered by the other window that we
181 // haven't fetched and put in |fetched_targets_| yet.
182 std::vector<Atom> unfetched_targets_;
184 // XdndPosition messages have a suggested action. Qt applications exclusively
185 // use this, instead of the XdndActionList which is backed by |actions_|.
186 Atom suggested_action_;
188 // Possible actions.
189 std::vector<Atom> actions_;
191 DISALLOW_COPY_AND_ASSIGN(X11DragContext);
194 DesktopDragDropClientAuraX11::X11DragContext::X11DragContext(
195 ui::X11AtomCache* atom_cache,
196 ::Window local_window,
197 const XClientMessageEvent& event)
198 : atom_cache_(atom_cache),
199 local_window_(local_window),
200 source_window_(event.data.l[0]),
201 source_client_(
202 DesktopDragDropClientAuraX11::GetForWindow(source_window_)),
203 foreign_window_manager_source_window_id_(0),
204 drag_drop_client_(NULL),
205 waiting_to_handle_position_(false),
206 suggested_action_(None) {
207 bool get_types = ((event.data.l[1] & 1) != 0);
209 if (get_types) {
210 if (!ui::GetAtomArrayProperty(source_window_,
211 "XdndTypeList",
212 &unfetched_targets_)) {
213 return;
215 } else {
216 // data.l[2,3,4] contain the first three types. Unused slots can be None.
217 for (int i = 0; i < 3; ++i) {
218 if (event.data.l[2+i] != None) {
219 unfetched_targets_.push_back(event.data.l[2+i]);
224 if (!source_client_) {
225 // The window doesn't have a DesktopDragDropClientAuraX11, that means it's
226 // created by some other process. Listen for messages on it.
227 ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
228 foreign_window_manager_source_window_id_ =
229 ui::XForeignWindowManager::GetInstance()->RequestEvents(
230 source_window_, PropertyChangeMask);
232 // We must perform a full sync here because we could be racing
233 // |source_window_|.
234 XSync(gfx::GetXDisplay(), False);
235 } else {
236 // This drag originates from an aura window within our process. This means
237 // that we can shortcut the X11 server and ask the owning SelectionOwner
238 // for the data it's offering.
239 fetched_targets_ = source_client_->GetFormatMap();
240 unfetched_targets_.clear();
243 ReadActions();
246 DesktopDragDropClientAuraX11::X11DragContext::~X11DragContext() {
247 if (!source_client_) {
248 // Unsubscribe from message events.
249 ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
250 ui::XForeignWindowManager::GetInstance()->CancelRequest(
251 foreign_window_manager_source_window_id_);
255 void DesktopDragDropClientAuraX11::X11DragContext::OnStartXdndPositionMessage(
256 DesktopDragDropClientAuraX11* client,
257 ::Atom suggested_action,
258 ::Window source_window,
259 const gfx::Point& screen_point) {
260 DCHECK_EQ(source_window_, source_window);
261 suggested_action_ = suggested_action;
263 if (!unfetched_targets_.empty()) {
264 // We have unfetched targets. That means we need to pause the handling of
265 // the position message and ask the other window for its data.
266 screen_point_ = screen_point;
267 drag_drop_client_ = client;
268 waiting_to_handle_position_ = true;
270 fetched_targets_ = ui::SelectionFormatMap();
271 RequestNextTarget();
272 } else {
273 client->CompleteXdndPosition(source_window, screen_point);
277 void DesktopDragDropClientAuraX11::X11DragContext::RequestNextTarget() {
278 ::Atom target = unfetched_targets_.back();
279 unfetched_targets_.pop_back();
281 XConvertSelection(gfx::GetXDisplay(),
282 atom_cache_->GetAtom(kXdndSelection),
283 target,
284 atom_cache_->GetAtom(kChromiumDragReciever),
285 local_window_,
286 CurrentTime);
289 void DesktopDragDropClientAuraX11::X11DragContext::OnSelectionNotify(
290 const XSelectionEvent& event) {
291 if (!waiting_to_handle_position_) {
292 // A misbehaved window may send SelectionNotify without us requesting data
293 // via XConvertSelection().
294 return;
296 DCHECK(drag_drop_client_);
297 DCHECK_EQ(event.property, atom_cache_->GetAtom(kChromiumDragReciever));
299 scoped_refptr<base::RefCountedMemory> data;
300 ::Atom type = None;
301 if (ui::GetRawBytesOfProperty(local_window_, event.property,
302 &data, NULL, &type)) {
303 fetched_targets_.Insert(event.target, data);
306 if (!unfetched_targets_.empty()) {
307 RequestNextTarget();
308 } else {
309 waiting_to_handle_position_ = false;
310 drag_drop_client_->CompleteXdndPosition(source_window_, screen_point_);
311 drag_drop_client_ = NULL;
315 void DesktopDragDropClientAuraX11::X11DragContext::ReadActions() {
316 if (!source_client_) {
317 std::vector<Atom> atom_array;
318 if (!ui::GetAtomArrayProperty(source_window_,
319 "XdndActionList",
320 &atom_array)) {
321 actions_.clear();
322 } else {
323 actions_.swap(atom_array);
325 } else {
326 // We have a property notify set up for other windows in case they change
327 // their action list. Thankfully, the views interface is static and you
328 // can't change the action list after you enter StartDragAndDrop().
329 actions_ = source_client_->GetOfferedDragOperations();
333 int DesktopDragDropClientAuraX11::X11DragContext::GetDragOperation() const {
334 int drag_operation = ui::DragDropTypes::DRAG_NONE;
335 for (std::vector<Atom>::const_iterator it = actions_.begin();
336 it != actions_.end(); ++it) {
337 MaskOperation(*it, &drag_operation);
340 MaskOperation(suggested_action_, &drag_operation);
342 return drag_operation;
345 void DesktopDragDropClientAuraX11::X11DragContext::MaskOperation(
346 ::Atom xdnd_operation,
347 int* drag_operation) const {
348 if (xdnd_operation == atom_cache_->GetAtom(kXdndActionCopy))
349 *drag_operation |= ui::DragDropTypes::DRAG_COPY;
350 else if (xdnd_operation == atom_cache_->GetAtom(kXdndActionMove))
351 *drag_operation |= ui::DragDropTypes::DRAG_MOVE;
352 else if (xdnd_operation == atom_cache_->GetAtom(kXdndActionLink))
353 *drag_operation |= ui::DragDropTypes::DRAG_LINK;
356 bool DesktopDragDropClientAuraX11::X11DragContext::CanDispatchEvent(
357 const ui::PlatformEvent& event) {
358 return event->xany.window == source_window_;
361 uint32_t DesktopDragDropClientAuraX11::X11DragContext::DispatchEvent(
362 const ui::PlatformEvent& event) {
363 if (event->type == PropertyNotify &&
364 event->xproperty.atom == atom_cache_->GetAtom("XdndActionList")) {
365 ReadActions();
366 return ui::POST_DISPATCH_STOP_PROPAGATION;
368 return ui::POST_DISPATCH_NONE;
371 ///////////////////////////////////////////////////////////////////////////////
373 DesktopDragDropClientAuraX11::DesktopDragDropClientAuraX11(
374 aura::Window* root_window,
375 views::DesktopNativeCursorManager* cursor_manager,
376 Display* xdisplay,
377 ::Window xwindow)
378 : root_window_(root_window),
379 xdisplay_(xdisplay),
380 xwindow_(xwindow),
381 atom_cache_(xdisplay_, kAtomsToCache),
382 target_window_(NULL),
383 waiting_on_status_(false),
384 status_received_since_enter_(false),
385 source_provider_(NULL),
386 source_current_window_(None),
387 source_state_(SOURCE_STATE_OTHER),
388 drag_operation_(0),
389 negotiated_operation_(ui::DragDropTypes::DRAG_NONE),
390 grab_cursor_(cursor_manager->GetInitializedCursor(ui::kCursorGrabbing)),
391 copy_grab_cursor_(cursor_manager->GetInitializedCursor(ui::kCursorCopy)),
392 move_grab_cursor_(cursor_manager->GetInitializedCursor(ui::kCursorMove)),
393 weak_ptr_factory_(this) {
394 // Some tests change the DesktopDragDropClientAuraX11 associated with an
395 // |xwindow|.
396 g_live_client_map.Get()[xwindow] = this;
398 // Mark that we are aware of drag and drop concepts.
399 unsigned long xdnd_version = kMinXdndVersion;
400 XChangeProperty(xdisplay_, xwindow_, atom_cache_.GetAtom("XdndAware"),
401 XA_ATOM, 32, PropModeReplace,
402 reinterpret_cast<unsigned char*>(&xdnd_version), 1);
405 DesktopDragDropClientAuraX11::~DesktopDragDropClientAuraX11() {
406 g_live_client_map.Get().erase(xwindow_);
407 // Make sure that all observers are unregistered from source and target
408 // windows. This may be necessary when the parent native widget gets destroyed
409 // while a drag operation is in progress.
410 NotifyDragLeave();
413 // static
414 DesktopDragDropClientAuraX11* DesktopDragDropClientAuraX11::GetForWindow(
415 ::Window window) {
416 std::map< ::Window, DesktopDragDropClientAuraX11*>::const_iterator it =
417 g_live_client_map.Get().find(window);
418 if (it == g_live_client_map.Get().end())
419 return NULL;
420 return it->second;
423 void DesktopDragDropClientAuraX11::Init() {
424 move_loop_ = CreateMoveLoop(this);
427 void DesktopDragDropClientAuraX11::OnXdndEnter(
428 const XClientMessageEvent& event) {
429 DVLOG(1) << "XdndEnter";
431 int version = (event.data.l[1] & 0xff000000) >> 24;
432 if (version < 3) {
433 LOG(ERROR) << "Received old XdndEnter message.";
434 return;
437 // Make sure that we've run ~X11DragContext() before creating another one.
438 target_current_context_.reset();
439 target_current_context_.reset(
440 new X11DragContext(&atom_cache_, xwindow_, event));
442 // In the Windows implementation, we immediately call DesktopDropTargetWin::
443 // Translate(). Here, we wait until we receive an XdndPosition message
444 // because the enter message doesn't convey any positioning
445 // information.
448 void DesktopDragDropClientAuraX11::OnXdndLeave(
449 const XClientMessageEvent& event) {
450 DVLOG(1) << "XdndLeave";
451 NotifyDragLeave();
452 target_current_context_.reset();
455 void DesktopDragDropClientAuraX11::OnXdndPosition(
456 const XClientMessageEvent& event) {
457 DVLOG(1) << "XdndPosition";
459 unsigned long source_window = event.data.l[0];
460 int x_root_window = event.data.l[2] >> 16;
461 int y_root_window = event.data.l[2] & 0xffff;
462 ::Atom suggested_action = event.data.l[4];
464 if (!target_current_context_.get()) {
465 NOTREACHED();
466 return;
469 // If we already have all the data from this drag, we complete it
470 // immediately.
471 target_current_context_->OnStartXdndPositionMessage(
472 this, suggested_action, source_window,
473 gfx::Point(x_root_window, y_root_window));
476 void DesktopDragDropClientAuraX11::OnXdndStatus(
477 const XClientMessageEvent& event) {
478 DVLOG(1) << "XdndStatus";
480 unsigned long source_window = event.data.l[0];
482 if (source_window != source_current_window_)
483 return;
485 if (source_state_ != SOURCE_STATE_PENDING_DROP &&
486 source_state_ != SOURCE_STATE_OTHER) {
487 return;
490 waiting_on_status_ = false;
491 status_received_since_enter_ = true;
493 if (event.data.l[1] & 1) {
494 ::Atom atom_operation = event.data.l[4];
495 negotiated_operation_ = AtomToDragOperation(atom_operation);
496 } else {
497 negotiated_operation_ = ui::DragDropTypes::DRAG_NONE;
500 if (source_state_ == SOURCE_STATE_PENDING_DROP) {
501 // We were waiting on the status message so we could send the XdndDrop.
502 if (negotiated_operation_ == ui::DragDropTypes::DRAG_NONE) {
503 move_loop_->EndMoveLoop();
504 return;
506 source_state_ = SOURCE_STATE_DROPPED;
507 SendXdndDrop(source_window);
508 return;
511 switch (negotiated_operation_) {
512 case ui::DragDropTypes::DRAG_COPY:
513 move_loop_->UpdateCursor(copy_grab_cursor_);
514 break;
515 case ui::DragDropTypes::DRAG_MOVE:
516 move_loop_->UpdateCursor(move_grab_cursor_);
517 break;
518 default:
519 move_loop_->UpdateCursor(grab_cursor_);
520 break;
523 // Note: event.data.[2,3] specify a rectangle. It is a request by the other
524 // window to not send further XdndPosition messages while the cursor is
525 // within it. However, it is considered advisory and (at least according to
526 // the spec) the other side must handle further position messages within
527 // it. GTK+ doesn't bother with this, so neither should we.
529 if (next_position_message_.get()) {
530 // We were waiting on the status message so we could send off the next
531 // position message we queued up.
532 gfx::Point p = next_position_message_->first;
533 unsigned long event_time = next_position_message_->second;
534 next_position_message_.reset();
536 SendXdndPosition(source_window, p, event_time);
540 void DesktopDragDropClientAuraX11::OnXdndFinished(
541 const XClientMessageEvent& event) {
542 DVLOG(1) << "XdndFinished";
543 unsigned long source_window = event.data.l[0];
544 if (source_current_window_ != source_window)
545 return;
547 // Clear |negotiated_operation_| if the drag was rejected.
548 if ((event.data.l[1] & 1) == 0)
549 negotiated_operation_ = ui::DragDropTypes::DRAG_NONE;
551 // Clear |source_current_window_| to avoid sending XdndLeave upon ending the
552 // move loop.
553 source_current_window_ = None;
554 move_loop_->EndMoveLoop();
557 void DesktopDragDropClientAuraX11::OnXdndDrop(
558 const XClientMessageEvent& event) {
559 DVLOG(1) << "XdndDrop";
561 unsigned long source_window = event.data.l[0];
563 int drag_operation = ui::DragDropTypes::DRAG_NONE;
564 if (target_window_) {
565 aura::client::DragDropDelegate* delegate =
566 aura::client::GetDragDropDelegate(target_window_);
567 if (delegate) {
568 ui::OSExchangeData data(new ui::OSExchangeDataProviderAuraX11(
569 xwindow_, target_current_context_->fetched_targets()));
571 ui::DropTargetEvent event(data,
572 target_window_location_,
573 target_window_root_location_,
574 target_current_context_->GetDragOperation());
575 drag_operation = delegate->OnPerformDrop(event);
578 target_window_->RemoveObserver(this);
579 target_window_ = NULL;
582 XEvent xev;
583 xev.xclient.type = ClientMessage;
584 xev.xclient.message_type = atom_cache_.GetAtom("XdndFinished");
585 xev.xclient.format = 32;
586 xev.xclient.window = source_window;
587 xev.xclient.data.l[0] = xwindow_;
588 xev.xclient.data.l[1] = (drag_operation != 0) ? 1 : 0;
589 xev.xclient.data.l[2] = DragOperationToAtom(drag_operation);
591 SendXClientEvent(source_window, &xev);
594 void DesktopDragDropClientAuraX11::OnSelectionNotify(
595 const XSelectionEvent& xselection) {
596 if (target_current_context_)
597 target_current_context_->OnSelectionNotify(xselection);
599 // ICCCM requires us to delete the property passed into SelectionNotify.
600 if (xselection.property != None)
601 XDeleteProperty(xdisplay_, xwindow_, xselection.property);
604 int DesktopDragDropClientAuraX11::StartDragAndDrop(
605 const ui::OSExchangeData& data,
606 aura::Window* root_window,
607 aura::Window* source_window,
608 const gfx::Point& root_location,
609 int operation,
610 ui::DragDropTypes::DragEventSource source) {
611 source_current_window_ = None;
612 DCHECK(!g_current_drag_drop_client);
613 g_current_drag_drop_client = this;
614 waiting_on_status_ = false;
615 next_position_message_.reset();
616 status_received_since_enter_ = false;
617 source_state_ = SOURCE_STATE_OTHER;
618 drag_operation_ = operation;
619 negotiated_operation_ = ui::DragDropTypes::DRAG_NONE;
621 const ui::OSExchangeData::Provider* provider = &data.provider();
622 source_provider_ = static_cast<const ui::OSExchangeDataProviderAuraX11*>(
623 provider);
625 source_provider_->TakeOwnershipOfSelection();
627 std::vector< ::Atom> actions = GetOfferedDragOperations();
628 if (!source_provider_->file_contents_name().empty()) {
629 actions.push_back(atom_cache_.GetAtom(kXdndActionDirectSave));
630 ui::SetStringProperty(
631 xwindow_,
632 atom_cache_.GetAtom(kXdndDirectSave0),
633 atom_cache_.GetAtom(ui::Clipboard::kMimeTypeText),
634 source_provider_->file_contents_name().AsUTF8Unsafe());
636 ui::SetAtomArrayProperty(xwindow_, "XdndActionList", "ATOM", actions);
638 gfx::ImageSkia drag_image = source_provider_->GetDragImage();
639 if (IsValidDragImage(drag_image)) {
640 CreateDragWidget(drag_image);
641 drag_widget_offset_ = source_provider_->GetDragImageOffset();
644 // Chrome expects starting drag and drop to release capture.
645 aura::Window* capture_window =
646 aura::client::GetCaptureClient(root_window)->GetGlobalCaptureWindow();
647 if (capture_window)
648 capture_window->ReleaseCapture();
650 // It is possible for the DesktopWindowTreeHostX11 to be destroyed during the
651 // move loop, which would also destroy this drag-client. So keep track of
652 // whether it is alive after the drag ends.
653 base::WeakPtr<DesktopDragDropClientAuraX11> alive(
654 weak_ptr_factory_.GetWeakPtr());
656 // Windows has a specific method, DoDragDrop(), which performs the entire
657 // drag. We have to emulate this, so we spin off a nested runloop which will
658 // track all cursor movement and reroute events to a specific handler.
659 move_loop_->RunMoveLoop(source_window, grab_cursor_);
661 if (alive) {
662 drag_widget_.reset();
664 source_provider_ = NULL;
665 g_current_drag_drop_client = NULL;
666 drag_operation_ = 0;
667 XDeleteProperty(xdisplay_, xwindow_, atom_cache_.GetAtom("XdndActionList"));
668 XDeleteProperty(xdisplay_, xwindow_, atom_cache_.GetAtom(kXdndDirectSave0));
670 return negotiated_operation_;
672 return ui::DragDropTypes::DRAG_NONE;
675 void DesktopDragDropClientAuraX11::DragUpdate(aura::Window* target,
676 const ui::LocatedEvent& event) {
677 NOTIMPLEMENTED();
680 void DesktopDragDropClientAuraX11::Drop(aura::Window* target,
681 const ui::LocatedEvent& event) {
682 NOTIMPLEMENTED();
685 void DesktopDragDropClientAuraX11::DragCancel() {
686 move_loop_->EndMoveLoop();
689 bool DesktopDragDropClientAuraX11::IsDragDropInProgress() {
690 return !!g_current_drag_drop_client;
693 void DesktopDragDropClientAuraX11::OnWindowDestroyed(aura::Window* window) {
694 DCHECK_EQ(target_window_, window);
695 target_window_ = NULL;
698 void DesktopDragDropClientAuraX11::OnMouseMovement(XMotionEvent* event) {
699 gfx::Point screen_point(event->x_root, event->y_root);
700 if (drag_widget_.get()) {
701 drag_widget_->SetBounds(
702 gfx::Rect(screen_point - drag_widget_offset_,
703 drag_widget_->GetWindowBoundsInScreen().size()));
704 drag_widget_->StackAtTop();
707 repeat_mouse_move_timer_.Stop();
708 ProcessMouseMove(screen_point, event->time);
711 void DesktopDragDropClientAuraX11::OnMouseReleased() {
712 repeat_mouse_move_timer_.Stop();
714 if (source_state_ != SOURCE_STATE_OTHER) {
715 // The user has previously released the mouse and is clicking in
716 // frustration.
717 move_loop_->EndMoveLoop();
718 return;
721 if (source_current_window_ != None) {
722 if (waiting_on_status_) {
723 if (status_received_since_enter_) {
724 // If we are waiting for an XdndStatus message, we need to wait for it
725 // to complete.
726 source_state_ = SOURCE_STATE_PENDING_DROP;
728 // Start timer to end the move loop if the target takes too long to send
729 // the XdndStatus and XdndFinished messages.
730 StartEndMoveLoopTimer();
731 return;
734 move_loop_->EndMoveLoop();
735 return;
738 if (negotiated_operation_ != ui::DragDropTypes::DRAG_NONE) {
739 // Start timer to end the move loop if the target takes too long to send
740 // an XdndFinished message. It is important that StartEndMoveLoopTimer()
741 // is called before SendXdndDrop() because SendXdndDrop()
742 // sends XdndFinished synchronously if the drop target is a Chrome
743 // window.
744 StartEndMoveLoopTimer();
746 // We have negotiated an action with the other end.
747 source_state_ = SOURCE_STATE_DROPPED;
748 SendXdndDrop(source_current_window_);
749 return;
753 move_loop_->EndMoveLoop();
756 void DesktopDragDropClientAuraX11::OnMoveLoopEnded() {
757 if (source_current_window_ != None) {
758 SendXdndLeave(source_current_window_);
759 source_current_window_ = None;
761 target_current_context_.reset();
762 repeat_mouse_move_timer_.Stop();
763 end_move_loop_timer_.Stop();
766 scoped_ptr<X11MoveLoop> DesktopDragDropClientAuraX11::CreateMoveLoop(
767 X11MoveLoopDelegate* delegate) {
768 return scoped_ptr<X11MoveLoop>(new X11WholeScreenMoveLoop(this));
771 XID DesktopDragDropClientAuraX11::FindWindowFor(
772 const gfx::Point& screen_point) {
773 views::X11TopmostWindowFinder finder;
774 ::Window target = finder.FindWindowAt(screen_point);
776 if (target == None)
777 return None;
779 // Figure out which window we should test as XdndAware. If |target| has
780 // XdndProxy, it will set that proxy on target, and if not, |target|'s
781 // original value will remain.
782 ui::GetXIDProperty(target, "XdndProxy", &target);
784 int version;
785 if (ui::GetIntProperty(target, "XdndAware", &version) &&
786 version >= kMinXdndVersion) {
787 return target;
789 return None;
792 void DesktopDragDropClientAuraX11::SendXClientEvent(::Window xid,
793 XEvent* xev) {
794 DCHECK_EQ(ClientMessage, xev->type);
796 // Don't send messages to the X11 message queue if we can help it.
797 DesktopDragDropClientAuraX11* short_circuit = GetForWindow(xid);
798 if (short_circuit) {
799 Atom message_type = xev->xclient.message_type;
800 if (message_type == atom_cache_.GetAtom("XdndEnter")) {
801 short_circuit->OnXdndEnter(xev->xclient);
802 return;
803 } else if (message_type == atom_cache_.GetAtom("XdndLeave")) {
804 short_circuit->OnXdndLeave(xev->xclient);
805 return;
806 } else if (message_type == atom_cache_.GetAtom("XdndPosition")) {
807 short_circuit->OnXdndPosition(xev->xclient);
808 return;
809 } else if (message_type == atom_cache_.GetAtom("XdndStatus")) {
810 short_circuit->OnXdndStatus(xev->xclient);
811 return;
812 } else if (message_type == atom_cache_.GetAtom("XdndFinished")) {
813 short_circuit->OnXdndFinished(xev->xclient);
814 return;
815 } else if (message_type == atom_cache_.GetAtom("XdndDrop")) {
816 short_circuit->OnXdndDrop(xev->xclient);
817 return;
821 // I don't understand why the GTK+ code is doing what it's doing here. It
822 // goes out of its way to send the XEvent so that it receives a callback on
823 // success or failure, and when it fails, it then sends an internal
824 // GdkEvent about the failed drag. (And sending this message doesn't appear
825 // to go through normal xlib machinery, but instead passes through the low
826 // level xProto (the x11 wire format) that I don't understand.
828 // I'm unsure if I have to jump through those hoops, or if XSendEvent is
829 // sufficient.
830 XSendEvent(xdisplay_, xid, False, 0, xev);
833 void DesktopDragDropClientAuraX11::ProcessMouseMove(
834 const gfx::Point& screen_point,
835 unsigned long event_time) {
836 if (source_state_ != SOURCE_STATE_OTHER)
837 return;
839 // Find the current window the cursor is over.
840 ::Window dest_window = FindWindowFor(screen_point);
842 if (source_current_window_ != dest_window) {
843 if (source_current_window_ != None)
844 SendXdndLeave(source_current_window_);
846 source_current_window_ = dest_window;
847 waiting_on_status_ = false;
848 next_position_message_.reset();
849 status_received_since_enter_ = false;
850 negotiated_operation_ = ui::DragDropTypes::DRAG_NONE;
852 if (source_current_window_ != None)
853 SendXdndEnter(source_current_window_);
856 if (source_current_window_ != None) {
857 if (waiting_on_status_) {
858 next_position_message_.reset(
859 new std::pair<gfx::Point, unsigned long>(screen_point, event_time));
860 } else {
861 SendXdndPosition(dest_window, screen_point, event_time);
866 void DesktopDragDropClientAuraX11::StartEndMoveLoopTimer() {
867 end_move_loop_timer_.Start(FROM_HERE,
868 base::TimeDelta::FromMilliseconds(
869 kEndMoveLoopTimeoutMs),
870 this,
871 &DesktopDragDropClientAuraX11::EndMoveLoop);
874 void DesktopDragDropClientAuraX11::EndMoveLoop() {
875 move_loop_->EndMoveLoop();
878 void DesktopDragDropClientAuraX11::DragTranslate(
879 const gfx::Point& root_window_location,
880 scoped_ptr<ui::OSExchangeData>* data,
881 scoped_ptr<ui::DropTargetEvent>* event,
882 aura::client::DragDropDelegate** delegate) {
883 gfx::Point root_location = root_window_location;
884 root_window_->GetHost()->ConvertPointFromNativeScreen(&root_location);
885 aura::Window* target_window =
886 root_window_->GetEventHandlerForPoint(root_location);
887 bool target_window_changed = false;
888 if (target_window != target_window_) {
889 if (target_window_)
890 NotifyDragLeave();
891 target_window_ = target_window;
892 if (target_window_)
893 target_window_->AddObserver(this);
894 target_window_changed = true;
896 *delegate = NULL;
897 if (!target_window_)
898 return;
899 *delegate = aura::client::GetDragDropDelegate(target_window_);
900 if (!*delegate)
901 return;
903 data->reset(new OSExchangeData(new ui::OSExchangeDataProviderAuraX11(
904 xwindow_, target_current_context_->fetched_targets())));
905 gfx::Point location = root_location;
906 aura::Window::ConvertPointToTarget(root_window_, target_window_, &location);
908 target_window_location_ = location;
909 target_window_root_location_ = root_location;
911 int drag_op = target_current_context_->GetDragOperation();
912 // KDE-based file browsers such as Dolphin change the drag operation depending
913 // on whether alt/ctrl/shift was pressed. However once Chromium gets control
914 // over the X11 events, the source application does no longer receive X11
915 // events for key modifier changes, so the dnd operation gets stuck in an
916 // incorrect state. Blink can only dnd-open files of type DRAG_COPY, so the
917 // DRAG_COPY mask is added if the dnd object is a file.
918 if (drag_op & (ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_LINK) &&
919 data->get()->HasFile()) {
920 drag_op |= ui::DragDropTypes::DRAG_COPY;
923 event->reset(new ui::DropTargetEvent(
924 *(data->get()),
925 location,
926 root_location,
927 drag_op));
928 if (target_window_changed)
929 (*delegate)->OnDragEntered(*event->get());
932 void DesktopDragDropClientAuraX11::NotifyDragLeave() {
933 if (!target_window_)
934 return;
935 DragDropDelegate* delegate =
936 aura::client::GetDragDropDelegate(target_window_);
937 if (delegate)
938 delegate->OnDragExited();
939 target_window_->RemoveObserver(this);
940 target_window_ = NULL;
943 ::Atom DesktopDragDropClientAuraX11::DragOperationToAtom(
944 int drag_operation) {
945 if (drag_operation & ui::DragDropTypes::DRAG_COPY)
946 return atom_cache_.GetAtom(kXdndActionCopy);
947 if (drag_operation & ui::DragDropTypes::DRAG_MOVE)
948 return atom_cache_.GetAtom(kXdndActionMove);
949 if (drag_operation & ui::DragDropTypes::DRAG_LINK)
950 return atom_cache_.GetAtom(kXdndActionLink);
952 return None;
955 ui::DragDropTypes::DragOperation
956 DesktopDragDropClientAuraX11::AtomToDragOperation(::Atom atom) {
957 if (atom == atom_cache_.GetAtom(kXdndActionCopy))
958 return ui::DragDropTypes::DRAG_COPY;
959 if (atom == atom_cache_.GetAtom(kXdndActionMove))
960 return ui::DragDropTypes::DRAG_MOVE;
961 if (atom == atom_cache_.GetAtom(kXdndActionLink))
962 return ui::DragDropTypes::DRAG_LINK;
964 return ui::DragDropTypes::DRAG_NONE;
967 std::vector< ::Atom> DesktopDragDropClientAuraX11::GetOfferedDragOperations() {
968 std::vector< ::Atom> operations;
969 if (drag_operation_ & ui::DragDropTypes::DRAG_COPY)
970 operations.push_back(atom_cache_.GetAtom(kXdndActionCopy));
971 if (drag_operation_ & ui::DragDropTypes::DRAG_MOVE)
972 operations.push_back(atom_cache_.GetAtom(kXdndActionMove));
973 if (drag_operation_ & ui::DragDropTypes::DRAG_LINK)
974 operations.push_back(atom_cache_.GetAtom(kXdndActionLink));
975 return operations;
978 ui::SelectionFormatMap DesktopDragDropClientAuraX11::GetFormatMap() const {
979 return source_provider_ ? source_provider_->GetFormatMap() :
980 ui::SelectionFormatMap();
983 void DesktopDragDropClientAuraX11::CompleteXdndPosition(
984 ::Window source_window,
985 const gfx::Point& screen_point) {
986 int drag_operation = ui::DragDropTypes::DRAG_NONE;
987 scoped_ptr<ui::OSExchangeData> data;
988 scoped_ptr<ui::DropTargetEvent> drop_target_event;
989 DragDropDelegate* delegate = NULL;
990 DragTranslate(screen_point, &data, &drop_target_event, &delegate);
991 if (delegate)
992 drag_operation = delegate->OnDragUpdated(*drop_target_event);
994 // Sends an XdndStatus message back to the source_window. l[2,3]
995 // theoretically represent an area in the window where the current action is
996 // the same as what we're returning, but I can't find any implementation that
997 // actually making use of this. A client can return (0, 0) and/or set the
998 // first bit of l[1] to disable the feature, and it appears that gtk neither
999 // sets this nor respects it if set.
1000 XEvent xev;
1001 xev.xclient.type = ClientMessage;
1002 xev.xclient.message_type = atom_cache_.GetAtom("XdndStatus");
1003 xev.xclient.format = 32;
1004 xev.xclient.window = source_window;
1005 xev.xclient.data.l[0] = xwindow_;
1006 xev.xclient.data.l[1] = (drag_operation != 0) ?
1007 (kWantFurtherPosEvents | kWillAcceptDrop) : 0;
1008 xev.xclient.data.l[2] = 0;
1009 xev.xclient.data.l[3] = 0;
1010 xev.xclient.data.l[4] = DragOperationToAtom(drag_operation);
1012 SendXClientEvent(source_window, &xev);
1015 void DesktopDragDropClientAuraX11::SendXdndEnter(::Window dest_window) {
1016 XEvent xev;
1017 xev.xclient.type = ClientMessage;
1018 xev.xclient.message_type = atom_cache_.GetAtom("XdndEnter");
1019 xev.xclient.format = 32;
1020 xev.xclient.window = dest_window;
1021 xev.xclient.data.l[0] = xwindow_;
1022 xev.xclient.data.l[1] = (kMinXdndVersion << 24); // The version number.
1023 xev.xclient.data.l[2] = 0;
1024 xev.xclient.data.l[3] = 0;
1025 xev.xclient.data.l[4] = 0;
1027 std::vector<Atom> targets;
1028 source_provider_->RetrieveTargets(&targets);
1030 if (targets.size() > 3) {
1031 xev.xclient.data.l[1] |= 1;
1032 ui::SetAtomArrayProperty(xwindow_, "XdndTypeList", "ATOM", targets);
1033 } else {
1034 // Pack the targets into the enter message.
1035 for (size_t i = 0; i < targets.size(); ++i)
1036 xev.xclient.data.l[2 + i] = targets[i];
1039 SendXClientEvent(dest_window, &xev);
1042 void DesktopDragDropClientAuraX11::SendXdndLeave(::Window dest_window) {
1043 XEvent xev;
1044 xev.xclient.type = ClientMessage;
1045 xev.xclient.message_type = atom_cache_.GetAtom("XdndLeave");
1046 xev.xclient.format = 32;
1047 xev.xclient.window = dest_window;
1048 xev.xclient.data.l[0] = xwindow_;
1049 xev.xclient.data.l[1] = 0;
1050 xev.xclient.data.l[2] = 0;
1051 xev.xclient.data.l[3] = 0;
1052 xev.xclient.data.l[4] = 0;
1053 SendXClientEvent(dest_window, &xev);
1056 void DesktopDragDropClientAuraX11::SendXdndPosition(
1057 ::Window dest_window,
1058 const gfx::Point& screen_point,
1059 unsigned long event_time) {
1060 waiting_on_status_ = true;
1062 XEvent xev;
1063 xev.xclient.type = ClientMessage;
1064 xev.xclient.message_type = atom_cache_.GetAtom("XdndPosition");
1065 xev.xclient.format = 32;
1066 xev.xclient.window = dest_window;
1067 xev.xclient.data.l[0] = xwindow_;
1068 xev.xclient.data.l[1] = 0;
1069 xev.xclient.data.l[2] = (screen_point.x() << 16) | screen_point.y();
1070 xev.xclient.data.l[3] = event_time;
1071 xev.xclient.data.l[4] = DragOperationToAtom(drag_operation_);
1072 SendXClientEvent(dest_window, &xev);
1074 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html and
1075 // the Xdnd protocol both recommend that drag events should be sent
1076 // periodically.
1077 repeat_mouse_move_timer_.Start(
1078 FROM_HERE,
1079 base::TimeDelta::FromMilliseconds(kRepeatMouseMoveTimeoutMs),
1080 base::Bind(&DesktopDragDropClientAuraX11::ProcessMouseMove,
1081 base::Unretained(this),
1082 screen_point,
1083 event_time));
1086 void DesktopDragDropClientAuraX11::SendXdndDrop(::Window dest_window) {
1087 XEvent xev;
1088 xev.xclient.type = ClientMessage;
1089 xev.xclient.message_type = atom_cache_.GetAtom("XdndDrop");
1090 xev.xclient.format = 32;
1091 xev.xclient.window = dest_window;
1092 xev.xclient.data.l[0] = xwindow_;
1093 xev.xclient.data.l[1] = 0;
1094 xev.xclient.data.l[2] = CurrentTime;
1095 xev.xclient.data.l[3] = None;
1096 xev.xclient.data.l[4] = None;
1097 SendXClientEvent(dest_window, &xev);
1100 void DesktopDragDropClientAuraX11::CreateDragWidget(
1101 const gfx::ImageSkia& image) {
1102 Widget* widget = new Widget;
1103 Widget::InitParams params(Widget::InitParams::TYPE_DRAG);
1104 params.opacity = Widget::InitParams::OPAQUE_WINDOW;
1105 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
1106 params.accept_events = false;
1108 gfx::Point location = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint() -
1109 drag_widget_offset_;
1110 params.bounds = gfx::Rect(location, image.size());
1111 widget->set_focus_on_creation(false);
1112 widget->set_frame_type(Widget::FRAME_TYPE_FORCE_NATIVE);
1113 widget->Init(params);
1114 widget->SetOpacity(kDragWidgetOpacity);
1115 widget->GetNativeWindow()->SetName("DragWindow");
1117 ImageView* image_view = new ImageView();
1118 image_view->SetImage(image);
1119 image_view->SetBounds(0, 0, image.width(), image.height());
1120 widget->SetContentsView(image_view);
1121 widget->Show();
1122 widget->GetNativeWindow()->layer()->SetFillsBoundsOpaquely(false);
1124 drag_widget_.reset(widget);
1127 bool DesktopDragDropClientAuraX11::IsValidDragImage(
1128 const gfx::ImageSkia& image) {
1129 if (image.isNull())
1130 return false;
1132 // Because we need a GL context per window, we do a quick check so that we
1133 // don't make another context if the window would just be displaying a mostly
1134 // transparent image.
1135 const SkBitmap* in_bitmap = image.bitmap();
1136 SkAutoLockPixels in_lock(*in_bitmap);
1137 for (int y = 0; y < in_bitmap->height(); ++y) {
1138 uint32* in_row = in_bitmap->getAddr32(0, y);
1140 for (int x = 0; x < in_bitmap->width(); ++x) {
1141 if (SkColorGetA(in_row[x]) > kMinAlpha)
1142 return true;
1146 return false;
1149 } // namespace views