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.
4
5#include "ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h"
6
7#include <X11/Xatom.h>
8
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"
34
35using aura::client::DragDropDelegate;
36using ui::OSExchangeData;
37
38namespace {
39
40const int kMinXdndVersion = 5;
41
42const int kWillAcceptDrop = 1;
43const int kWantFurtherPosEvents = 2;
44
45const char kXdndActionCopy[] = "XdndActionCopy";
46const char kXdndActionMove[] = "XdndActionMove";
47const char kXdndActionLink[] = "XdndActionLink";
48const char kXdndActionDirectSave[] = "XdndActionDirectSave";
49
50const char kChromiumDragReciever[] = "_CHROMIUM_DRAG_RECEIVER";
51const char kXdndSelection[] = "XdndSelection";
52const char kXdndDirectSave0[] = "XdndDirectSave0";
53
54const 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
76};
77
78// The time to wait for the target to respond after the user has released the
79// mouse button before ending the move loop.
80const int kEndMoveLoopTimeoutMs = 1000;
81
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.
85const int kRepeatMouseMoveTimeoutMs = 350;
86
87// The minimum alpha before we declare a pixel transparent when searching in
88// our source image.
89const uint32 kMinAlpha = 32;
90
91// |drag_widget_|'s opacity.
92const unsigned char kDragWidgetOpacity = 0xc0;
93
94static base::LazyInstance<
95    std::map< ::Window, views::DesktopDragDropClientAuraX11*> >::Leaky
96        g_live_client_map = LAZY_INSTANCE_INITIALIZER;
97
98}  // namespace
99
100namespace views {
101
102DesktopDragDropClientAuraX11*
103DesktopDragDropClientAuraX11::g_current_drag_drop_client = NULL;
104
105class 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();
112
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);
121
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();
126
127  // Called when XSelection data has been copied to our process.
128  void OnSelectionNotify(const XSelectionEvent& xselection);
129
130  // Clones the fetched targets.
131  const ui::SelectionFormatMap& fetched_targets() { return fetched_targets_; }
132
133  // Reads the "XdndActionList" property from |source_window| and copies it
134  // into |actions|.
135  void ReadActions();
136
137  // Creates a ui::DragDropTypes::DragOperation representation of the current
138  // action list.
139  int GetDragOperation() const;
140
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;
145
146  // ui::PlatformEventDispatcher:
147  virtual bool CanDispatchEvent(const ui::PlatformEvent& event) OVERRIDE;
148  virtual uint32_t DispatchEvent(const ui::PlatformEvent& event) OVERRIDE;
149
150  // The atom cache owned by our parent.
151  ui::X11AtomCache* atom_cache_;
152
153  // The XID of our chrome local aura window handling our events.
154  ::Window local_window_;
155
156  // The XID of the window that's initiated the drag.
157  unsigned long source_window_;
158
159  // The DesktopDragDropClientAuraX11 for |source_window_| if |source_window_|
160  // belongs to a Chrome window.
161  DesktopDragDropClientAuraX11* source_client_;
162
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_;
166
167  // The client we inform once we're done with requesting data.
168  DesktopDragDropClientAuraX11* drag_drop_client_;
169
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_;
173
174  // Where the cursor is on screen.
175  gfx::Point screen_point_;
176
177  // A SelectionFormatMap of data that we have in our process.
178  ui::SelectionFormatMap fetched_targets_;
179
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_;
183
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_;
187
188  // Possible actions.
189  std::vector<Atom> actions_;
190
191  DISALLOW_COPY_AND_ASSIGN(X11DragContext);
192};
193
194DesktopDragDropClientAuraX11::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);
208
209  if (get_types) {
210    if (!ui::GetAtomArrayProperty(source_window_,
211                                  "XdndTypeList",
212                                  &unfetched_targets_)) {
213      return;
214    }
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]);
220      }
221    }
222  }
223
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);
231
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();
241  }
242
243  ReadActions();
244}
245
246DesktopDragDropClientAuraX11::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_);
252  }
253}
254
255void 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;
262
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;
269
270    fetched_targets_ = ui::SelectionFormatMap();
271    RequestNextTarget();
272  } else {
273    client->CompleteXdndPosition(source_window, screen_point);
274  }
275}
276
277void DesktopDragDropClientAuraX11::X11DragContext::RequestNextTarget() {
278  ::Atom target = unfetched_targets_.back();
279  unfetched_targets_.pop_back();
280
281  XConvertSelection(gfx::GetXDisplay(),
282                    atom_cache_->GetAtom(kXdndSelection),
283                    target,
284                    atom_cache_->GetAtom(kChromiumDragReciever),
285                    local_window_,
286                    CurrentTime);
287}
288
289void 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;
295  }
296  DCHECK(drag_drop_client_);
297  DCHECK_EQ(event.property, atom_cache_->GetAtom(kChromiumDragReciever));
298
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);
304  }
305
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;
312  }
313}
314
315void 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);
324    }
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();
330  }
331}
332
333int 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);
338  }
339
340  MaskOperation(suggested_action_, &drag_operation);
341
342  return drag_operation;
343}
344
345void 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;
354}
355
356bool DesktopDragDropClientAuraX11::X11DragContext::CanDispatchEvent(
357    const ui::PlatformEvent& event) {
358  return event->xany.window == source_window_;
359}
360
361uint32_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;
367  }
368  return ui::POST_DISPATCH_NONE;
369}
370
371///////////////////////////////////////////////////////////////////////////////
372
373DesktopDragDropClientAuraX11::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;
397
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);
403}
404
405DesktopDragDropClientAuraX11::~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();
411}
412
413// static
414DesktopDragDropClientAuraX11* 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;
421}
422
423void DesktopDragDropClientAuraX11::Init() {
424  move_loop_ = CreateMoveLoop(this);
425}
426
427void DesktopDragDropClientAuraX11::OnXdndEnter(
428    const XClientMessageEvent& event) {
429  DVLOG(1) << "XdndEnter";
430
431  int version = (event.data.l[1] & 0xff000000) >> 24;
432  if (version < 3) {
433    LOG(ERROR) << "Received old XdndEnter message.";
434    return;
435  }
436
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));
441
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.
446}
447
448void DesktopDragDropClientAuraX11::OnXdndLeave(
449    const XClientMessageEvent& event) {
450  DVLOG(1) << "XdndLeave";
451  NotifyDragLeave();
452  target_current_context_.reset();
453}
454
455void DesktopDragDropClientAuraX11::OnXdndPosition(
456    const XClientMessageEvent& event) {
457  DVLOG(1) << "XdndPosition";
458
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];
463
464  if (!target_current_context_.get()) {
465    NOTREACHED();
466    return;
467  }
468
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));
474}
475
476void DesktopDragDropClientAuraX11::OnXdndStatus(
477    const XClientMessageEvent& event) {
478  DVLOG(1) << "XdndStatus";
479
480  unsigned long source_window = event.data.l[0];
481
482  if (source_window != source_current_window_)
483    return;
484
485  if (source_state_ != SOURCE_STATE_PENDING_DROP &&
486      source_state_ != SOURCE_STATE_OTHER) {
487    return;
488  }
489
490  waiting_on_status_ = false;
491  status_received_since_enter_ = true;
492
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;
498  }
499
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;
505    }
506    source_state_ = SOURCE_STATE_DROPPED;
507    SendXdndDrop(source_window);
508    return;
509  }
510
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;
521  }
522
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.
528
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();
535
536    SendXdndPosition(source_window, p, event_time);
537  }
538}
539
540void 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;
546
547  // Clear |negotiated_operation_| if the drag was rejected.
548  if ((event.data.l[1] & 1) == 0)
549    negotiated_operation_ = ui::DragDropTypes::DRAG_NONE;
550
551  // Clear |source_current_window_| to avoid sending XdndLeave upon ending the
552  // move loop.
553  source_current_window_ = None;
554  move_loop_->EndMoveLoop();
555}
556
557void DesktopDragDropClientAuraX11::OnXdndDrop(
558    const XClientMessageEvent& event) {
559  DVLOG(1) << "XdndDrop";
560
561  unsigned long source_window = event.data.l[0];
562
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()));
570
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);
576    }
577
578    target_window_->RemoveObserver(this);
579    target_window_ = NULL;
580  }
581
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);
590
591  SendXClientEvent(source_window, &xev);
592}
593
594void DesktopDragDropClientAuraX11::OnSelectionNotify(
595    const XSelectionEvent& xselection) {
596  if (target_current_context_)
597    target_current_context_->OnSelectionNotify(xselection);
598
599  // ICCCM requires us to delete the property passed into SelectionNotify.
600  if (xselection.property != None)
601    XDeleteProperty(xdisplay_, xwindow_, xselection.property);
602}
603
604int 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;
620
621  const ui::OSExchangeData::Provider* provider = &data.provider();
622  source_provider_ = static_cast<const ui::OSExchangeDataProviderAuraX11*>(
623      provider);
624
625  source_provider_->TakeOwnershipOfSelection();
626
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());
635  }
636  ui::SetAtomArrayProperty(xwindow_, "XdndActionList", "ATOM", actions);
637
638  gfx::ImageSkia drag_image = source_provider_->GetDragImage();
639  if (IsValidDragImage(drag_image)) {
640    CreateDragWidget(drag_image);
641    drag_widget_offset_ = source_provider_->GetDragImageOffset();
642  }
643
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();
649
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());
655
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_);
660
661  if (alive) {
662    drag_widget_.reset();
663
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));
669
670    return negotiated_operation_;
671  }
672  return ui::DragDropTypes::DRAG_NONE;
673}
674
675void DesktopDragDropClientAuraX11::DragUpdate(aura::Window* target,
676                                              const ui::LocatedEvent& event) {
677  NOTIMPLEMENTED();
678}
679
680void DesktopDragDropClientAuraX11::Drop(aura::Window* target,
681                                        const ui::LocatedEvent& event) {
682  NOTIMPLEMENTED();
683}
684
685void DesktopDragDropClientAuraX11::DragCancel() {
686  move_loop_->EndMoveLoop();
687}
688
689bool DesktopDragDropClientAuraX11::IsDragDropInProgress() {
690  return !!g_current_drag_drop_client;
691}
692
693void DesktopDragDropClientAuraX11::OnWindowDestroyed(aura::Window* window) {
694  DCHECK_EQ(target_window_, window);
695  target_window_ = NULL;
696}
697
698void 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();
705  }
706
707  repeat_mouse_move_timer_.Stop();
708  ProcessMouseMove(screen_point, event->time);
709}
710
711void DesktopDragDropClientAuraX11::OnMouseReleased() {
712  repeat_mouse_move_timer_.Stop();
713
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;
719  }
720
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;
727
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;
732      }
733
734      move_loop_->EndMoveLoop();
735      return;
736    }
737
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();
745
746      // We have negotiated an action with the other end.
747      source_state_ = SOURCE_STATE_DROPPED;
748      SendXdndDrop(source_current_window_);
749      return;
750    }
751  }
752
753  move_loop_->EndMoveLoop();
754}
755
756void DesktopDragDropClientAuraX11::OnMoveLoopEnded() {
757  if (source_current_window_ != None) {
758    SendXdndLeave(source_current_window_);
759    source_current_window_ = None;
760  }
761  target_current_context_.reset();
762  repeat_mouse_move_timer_.Stop();
763  end_move_loop_timer_.Stop();
764}
765
766scoped_ptr<X11MoveLoop> DesktopDragDropClientAuraX11::CreateMoveLoop(
767    X11MoveLoopDelegate* delegate) {
768  return scoped_ptr<X11MoveLoop>(new X11WholeScreenMoveLoop(this));
769}
770
771XID DesktopDragDropClientAuraX11::FindWindowFor(
772    const gfx::Point& screen_point) {
773  views::X11TopmostWindowFinder finder;
774  ::Window target = finder.FindWindowAt(screen_point);
775
776  if (target == None)
777    return None;
778
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);
783
784  int version;
785  if (ui::GetIntProperty(target, "XdndAware", &version) &&
786      version >= kMinXdndVersion) {
787    return target;
788  }
789  return None;
790}
791
792void DesktopDragDropClientAuraX11::SendXClientEvent(::Window xid,
793                                                    XEvent* xev) {
794  DCHECK_EQ(ClientMessage, xev->type);
795
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;
818    }
819  }
820
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.
827  //
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);
831}
832
833void DesktopDragDropClientAuraX11::ProcessMouseMove(
834    const gfx::Point& screen_point,
835    unsigned long event_time) {
836  if (source_state_ != SOURCE_STATE_OTHER)
837    return;
838
839  // Find the current window the cursor is over.
840  ::Window dest_window = FindWindowFor(screen_point);
841
842  if (source_current_window_ != dest_window) {
843    if (source_current_window_ != None)
844      SendXdndLeave(source_current_window_);
845
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;
851
852    if (source_current_window_ != None)
853      SendXdndEnter(source_current_window_);
854  }
855
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);
862    }
863  }
864}
865
866void DesktopDragDropClientAuraX11::StartEndMoveLoopTimer() {
867  end_move_loop_timer_.Start(FROM_HERE,
868                             base::TimeDelta::FromMilliseconds(
869                                 kEndMoveLoopTimeoutMs),
870                             this,
871                             &DesktopDragDropClientAuraX11::EndMoveLoop);
872}
873
874void DesktopDragDropClientAuraX11::EndMoveLoop() {
875  move_loop_->EndMoveLoop();
876}
877
878void 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;
895  }
896  *delegate = NULL;
897  if (!target_window_)
898    return;
899  *delegate = aura::client::GetDragDropDelegate(target_window_);
900  if (!*delegate)
901    return;
902
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);
907
908  target_window_location_ = location;
909  target_window_root_location_ = root_location;
910
911  event->reset(new ui::DropTargetEvent(
912      *(data->get()),
913      location,
914      root_location,
915      target_current_context_->GetDragOperation()));
916  if (target_window_changed)
917    (*delegate)->OnDragEntered(*event->get());
918}
919
920void DesktopDragDropClientAuraX11::NotifyDragLeave() {
921  if (!target_window_)
922    return;
923  DragDropDelegate* delegate =
924      aura::client::GetDragDropDelegate(target_window_);
925  if (delegate)
926    delegate->OnDragExited();
927  target_window_->RemoveObserver(this);
928  target_window_ = NULL;
929}
930
931::Atom DesktopDragDropClientAuraX11::DragOperationToAtom(
932    int drag_operation) {
933  if (drag_operation & ui::DragDropTypes::DRAG_COPY)
934    return atom_cache_.GetAtom(kXdndActionCopy);
935  if (drag_operation & ui::DragDropTypes::DRAG_MOVE)
936    return atom_cache_.GetAtom(kXdndActionMove);
937  if (drag_operation & ui::DragDropTypes::DRAG_LINK)
938    return atom_cache_.GetAtom(kXdndActionLink);
939
940  return None;
941}
942
943ui::DragDropTypes::DragOperation
944DesktopDragDropClientAuraX11::AtomToDragOperation(::Atom atom) {
945  if (atom == atom_cache_.GetAtom(kXdndActionCopy))
946    return ui::DragDropTypes::DRAG_COPY;
947  if (atom == atom_cache_.GetAtom(kXdndActionMove))
948    return ui::DragDropTypes::DRAG_MOVE;
949  if (atom == atom_cache_.GetAtom(kXdndActionLink))
950    return ui::DragDropTypes::DRAG_LINK;
951
952  return ui::DragDropTypes::DRAG_NONE;
953}
954
955std::vector< ::Atom> DesktopDragDropClientAuraX11::GetOfferedDragOperations() {
956  std::vector< ::Atom> operations;
957  if (drag_operation_ & ui::DragDropTypes::DRAG_COPY)
958    operations.push_back(atom_cache_.GetAtom(kXdndActionCopy));
959  if (drag_operation_ & ui::DragDropTypes::DRAG_MOVE)
960    operations.push_back(atom_cache_.GetAtom(kXdndActionMove));
961  if (drag_operation_ & ui::DragDropTypes::DRAG_LINK)
962    operations.push_back(atom_cache_.GetAtom(kXdndActionLink));
963  return operations;
964}
965
966ui::SelectionFormatMap DesktopDragDropClientAuraX11::GetFormatMap() const {
967  return source_provider_ ? source_provider_->GetFormatMap() :
968      ui::SelectionFormatMap();
969}
970
971void DesktopDragDropClientAuraX11::CompleteXdndPosition(
972    ::Window source_window,
973    const gfx::Point& screen_point) {
974  int drag_operation = ui::DragDropTypes::DRAG_NONE;
975  scoped_ptr<ui::OSExchangeData> data;
976  scoped_ptr<ui::DropTargetEvent> drop_target_event;
977  DragDropDelegate* delegate = NULL;
978  DragTranslate(screen_point, &data, &drop_target_event, &delegate);
979  if (delegate)
980    drag_operation = delegate->OnDragUpdated(*drop_target_event);
981
982  // Sends an XdndStatus message back to the source_window. l[2,3]
983  // theoretically represent an area in the window where the current action is
984  // the same as what we're returning, but I can't find any implementation that
985  // actually making use of this. A client can return (0, 0) and/or set the
986  // first bit of l[1] to disable the feature, and it appears that gtk neither
987  // sets this nor respects it if set.
988  XEvent xev;
989  xev.xclient.type = ClientMessage;
990  xev.xclient.message_type = atom_cache_.GetAtom("XdndStatus");
991  xev.xclient.format = 32;
992  xev.xclient.window = source_window;
993  xev.xclient.data.l[0] = xwindow_;
994  xev.xclient.data.l[1] = (drag_operation != 0) ?
995      (kWantFurtherPosEvents | kWillAcceptDrop) : 0;
996  xev.xclient.data.l[2] = 0;
997  xev.xclient.data.l[3] = 0;
998  xev.xclient.data.l[4] = DragOperationToAtom(drag_operation);
999
1000  SendXClientEvent(source_window, &xev);
1001}
1002
1003void DesktopDragDropClientAuraX11::SendXdndEnter(::Window dest_window) {
1004  XEvent xev;
1005  xev.xclient.type = ClientMessage;
1006  xev.xclient.message_type = atom_cache_.GetAtom("XdndEnter");
1007  xev.xclient.format = 32;
1008  xev.xclient.window = dest_window;
1009  xev.xclient.data.l[0] = xwindow_;
1010  xev.xclient.data.l[1] = (kMinXdndVersion << 24);  // The version number.
1011  xev.xclient.data.l[2] = 0;
1012  xev.xclient.data.l[3] = 0;
1013  xev.xclient.data.l[4] = 0;
1014
1015  std::vector<Atom> targets;
1016  source_provider_->RetrieveTargets(&targets);
1017
1018  if (targets.size() > 3) {
1019    xev.xclient.data.l[1] |= 1;
1020    ui::SetAtomArrayProperty(xwindow_, "XdndTypeList", "ATOM", targets);
1021  } else {
1022    // Pack the targets into the enter message.
1023    for (size_t i = 0; i < targets.size(); ++i)
1024      xev.xclient.data.l[2 + i] = targets[i];
1025  }
1026
1027  SendXClientEvent(dest_window, &xev);
1028}
1029
1030void DesktopDragDropClientAuraX11::SendXdndLeave(::Window dest_window) {
1031  XEvent xev;
1032  xev.xclient.type = ClientMessage;
1033  xev.xclient.message_type = atom_cache_.GetAtom("XdndLeave");
1034  xev.xclient.format = 32;
1035  xev.xclient.window = dest_window;
1036  xev.xclient.data.l[0] = xwindow_;
1037  xev.xclient.data.l[1] = 0;
1038  xev.xclient.data.l[2] = 0;
1039  xev.xclient.data.l[3] = 0;
1040  xev.xclient.data.l[4] = 0;
1041  SendXClientEvent(dest_window, &xev);
1042}
1043
1044void DesktopDragDropClientAuraX11::SendXdndPosition(
1045    ::Window dest_window,
1046    const gfx::Point& screen_point,
1047    unsigned long event_time) {
1048  waiting_on_status_ = true;
1049
1050  XEvent xev;
1051  xev.xclient.type = ClientMessage;
1052  xev.xclient.message_type = atom_cache_.GetAtom("XdndPosition");
1053  xev.xclient.format = 32;
1054  xev.xclient.window = dest_window;
1055  xev.xclient.data.l[0] = xwindow_;
1056  xev.xclient.data.l[1] = 0;
1057  xev.xclient.data.l[2] = (screen_point.x() << 16) | screen_point.y();
1058  xev.xclient.data.l[3] = event_time;
1059  xev.xclient.data.l[4] = DragOperationToAtom(drag_operation_);
1060  SendXClientEvent(dest_window, &xev);
1061
1062  // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html and
1063  // the Xdnd protocol both recommend that drag events should be sent
1064  // periodically.
1065  repeat_mouse_move_timer_.Start(
1066      FROM_HERE,
1067      base::TimeDelta::FromMilliseconds(kRepeatMouseMoveTimeoutMs),
1068      base::Bind(&DesktopDragDropClientAuraX11::ProcessMouseMove,
1069                 base::Unretained(this),
1070                 screen_point,
1071                 event_time));
1072}
1073
1074void DesktopDragDropClientAuraX11::SendXdndDrop(::Window dest_window) {
1075  XEvent xev;
1076  xev.xclient.type = ClientMessage;
1077  xev.xclient.message_type = atom_cache_.GetAtom("XdndDrop");
1078  xev.xclient.format = 32;
1079  xev.xclient.window = dest_window;
1080  xev.xclient.data.l[0] = xwindow_;
1081  xev.xclient.data.l[1] = 0;
1082  xev.xclient.data.l[2] = CurrentTime;
1083  xev.xclient.data.l[3] = None;
1084  xev.xclient.data.l[4] = None;
1085  SendXClientEvent(dest_window, &xev);
1086}
1087
1088void DesktopDragDropClientAuraX11::CreateDragWidget(
1089    const gfx::ImageSkia& image) {
1090  Widget* widget = new Widget;
1091  Widget::InitParams params(Widget::InitParams::TYPE_DRAG);
1092  params.opacity = Widget::InitParams::OPAQUE_WINDOW;
1093  params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
1094  params.accept_events = false;
1095
1096  gfx::Point location = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint() -
1097                        drag_widget_offset_;
1098  params.bounds = gfx::Rect(location, image.size());
1099  widget->set_focus_on_creation(false);
1100  widget->set_frame_type(Widget::FRAME_TYPE_FORCE_NATIVE);
1101  widget->Init(params);
1102  widget->SetOpacity(kDragWidgetOpacity);
1103  widget->GetNativeWindow()->SetName("DragWindow");
1104
1105  ImageView* image_view = new ImageView();
1106  image_view->SetImage(image);
1107  image_view->SetBounds(0, 0, image.width(), image.height());
1108  widget->SetContentsView(image_view);
1109  widget->Show();
1110  widget->GetNativeWindow()->layer()->SetFillsBoundsOpaquely(false);
1111
1112  drag_widget_.reset(widget);
1113}
1114
1115bool DesktopDragDropClientAuraX11::IsValidDragImage(
1116    const gfx::ImageSkia& image) {
1117  if (image.isNull())
1118    return false;
1119
1120  // Because we need a GL context per window, we do a quick check so that we
1121  // don't make another context if the window would just be displaying a mostly
1122  // transparent image.
1123  const SkBitmap* in_bitmap = image.bitmap();
1124  SkAutoLockPixels in_lock(*in_bitmap);
1125  for (int y = 0; y < in_bitmap->height(); ++y) {
1126    uint32* in_row = in_bitmap->getAddr32(0, y);
1127
1128    for (int x = 0; x < in_bitmap->width(); ++x) {
1129      if (SkColorGetA(in_row[x]) > kMinAlpha)
1130        return true;
1131    }
1132  }
1133
1134  return false;
1135}
1136
1137}  // namespace views
1138