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 35 using aura::client::DragDropDelegate; 36 using ui::OSExchangeData; 37 38 namespace { 39 40 const int kMinXdndVersion = 5; 41 42 const int kWillAcceptDrop = 1; 43 const int kWantFurtherPosEvents = 2; 44 45 const char kXdndActionCopy[] = "XdndActionCopy"; 46 const char kXdndActionMove[] = "XdndActionMove"; 47 const char kXdndActionLink[] = "XdndActionLink"; 48 const char kXdndActionDirectSave[] = "XdndActionDirectSave"; 49 50 const char kChromiumDragReciever[] = "_CHROMIUM_DRAG_RECEIVER"; 51 const char kXdndSelection[] = "XdndSelection"; 52 const char kXdndDirectSave0[] = "XdndDirectSave0"; 53 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 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. 80 const 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. 85 const int kRepeatMouseMoveTimeoutMs = 350; 86 87 // The minimum alpha before we declare a pixel transparent when searching in 88 // our source image. 89 const uint32 kMinAlpha = 32; 90 91 // |drag_widget_|'s opacity. 92 const unsigned char kDragWidgetOpacity = 0xc0; 93 94 static base::LazyInstance< 95 std::map< ::Window, views::DesktopDragDropClientAuraX11*> >::Leaky 96 g_live_client_map = LAZY_INSTANCE_INITIALIZER; 97 98 } // namespace 99 100 namespace views { 101 102 DesktopDragDropClientAuraX11* 103 DesktopDragDropClientAuraX11::g_current_drag_drop_client = NULL; 104 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(); 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 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); 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 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_); 252 } 253 } 254 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; 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 277 void 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 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; 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 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); 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 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); 338 } 339 340 MaskOperation(suggested_action_, &drag_operation); 341 342 return drag_operation; 343 } 344 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; 354 } 355 356 bool DesktopDragDropClientAuraX11::X11DragContext::CanDispatchEvent( 357 const ui::PlatformEvent& event) { 358 return event->xany.window == source_window_; 359 } 360 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; 367 } 368 return ui::POST_DISPATCH_NONE; 369 } 370 371 /////////////////////////////////////////////////////////////////////////////// 372 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; 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 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(); 411 } 412 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; 421 } 422 423 void DesktopDragDropClientAuraX11::Init() { 424 move_loop_ = CreateMoveLoop(this); 425 } 426 427 void 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 448 void DesktopDragDropClientAuraX11::OnXdndLeave( 449 const XClientMessageEvent& event) { 450 DVLOG(1) << "XdndLeave"; 451 NotifyDragLeave(); 452 target_current_context_.reset(); 453 } 454 455 void 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 476 void 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 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; 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 557 void 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 594 void 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 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; 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 675 void DesktopDragDropClientAuraX11::DragUpdate(aura::Window* target, 676 const ui::LocatedEvent& event) { 677 NOTIMPLEMENTED(); 678 } 679 680 void DesktopDragDropClientAuraX11::Drop(aura::Window* target, 681 const ui::LocatedEvent& event) { 682 NOTIMPLEMENTED(); 683 } 684 685 void DesktopDragDropClientAuraX11::DragCancel() { 686 move_loop_->EndMoveLoop(); 687 } 688 689 bool DesktopDragDropClientAuraX11::IsDragDropInProgress() { 690 return !!g_current_drag_drop_client; 691 } 692 693 void DesktopDragDropClientAuraX11::OnWindowDestroyed(aura::Window* window) { 694 DCHECK_EQ(target_window_, window); 695 target_window_ = NULL; 696 } 697 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(); 705 } 706 707 repeat_mouse_move_timer_.Stop(); 708 ProcessMouseMove(screen_point, event->time); 709 } 710 711 void 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 756 void 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 766 scoped_ptr<X11MoveLoop> DesktopDragDropClientAuraX11::CreateMoveLoop( 767 X11MoveLoopDelegate* delegate) { 768 return scoped_ptr<X11MoveLoop>(new X11WholeScreenMoveLoop(this)); 769 } 770 771 XID 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 792 void 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 833 void 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 866 void DesktopDragDropClientAuraX11::StartEndMoveLoopTimer() { 867 end_move_loop_timer_.Start(FROM_HERE, 868 base::TimeDelta::FromMilliseconds( 869 kEndMoveLoopTimeoutMs), 870 this, 871 &DesktopDragDropClientAuraX11::EndMoveLoop); 872 } 873 874 void DesktopDragDropClientAuraX11::EndMoveLoop() { 875 move_loop_->EndMoveLoop(); 876 } 877 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; 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 920 void 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 943 ui::DragDropTypes::DragOperation 944 DesktopDragDropClientAuraX11::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 955 std::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 966 ui::SelectionFormatMap DesktopDragDropClientAuraX11::GetFormatMap() const { 967 return source_provider_ ? source_provider_->GetFormatMap() : 968 ui::SelectionFormatMap(); 969 } 970 971 void 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 1003 void 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 1030 void 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 1044 void 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 1074 void 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 1088 void 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 1115 bool 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