Chromium Code Reviews| Index: ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc |
| diff --git a/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc b/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc |
| index ef2d6f86559891ffb696604079d8b013853b4009..88b84b60a52d364baf5100dcb880ab3c1584dd17 100644 |
| --- a/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc |
| +++ b/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc |
| @@ -16,6 +16,7 @@ |
| #include "ui/base/dragdrop/os_exchange_data.h" |
| #include "ui/base/dragdrop/os_exchange_data_provider_aurax11.h" |
| #include "ui/base/events/event.h" |
| +#include "ui/base/x/selection_utils.h" |
| #include "ui/base/x/x11_util.h" |
| #include "ui/views/widget/desktop_aura/desktop_root_window_host_x11.h" |
| @@ -24,11 +25,17 @@ using ui::OSExchangeData; |
| namespace { |
| +const int kMinXdndVersion = 5; |
| + |
| const char kXdndActionCopy[] = "XdndActionCopy"; |
| const char kXdndActionMove[] = "XdndActionMove"; |
| const char kXdndActionLink[] = "XdndActionLink"; |
| +const char kChromiumDragReciever[] = "CHROMIUM_DRAG_RECEIVER"; |
|
Daniel Erat
2013/06/17 22:09:57
nit: prefix vendor-specific atoms with underscores
|
| +const char kXdndSelection[] = "XdndSelection"; |
| + |
| const char* kAtomsToCache[] = { |
| + kChromiumDragReciever, |
| "XdndActionAsk", |
| kXdndActionCopy, |
| kXdndActionLink, |
| @@ -42,24 +49,119 @@ const char* kAtomsToCache[] = { |
| "XdndLeave", |
| "XdndPosition", |
| "XdndProxy", // Proxy windows? |
| - "XdndSelection", |
| + kXdndSelection, |
| "XdndStatus", |
| "XdndTypeList", |
| NULL |
| }; |
| +class DragTargetWindowFinder : public ui::EnumerateWindowsDelegate { |
|
Daniel Erat
2013/06/17 22:09:57
please add a comment describing what this class do
|
| + public: |
| + DragTargetWindowFinder(XID ignored_icon_window, |
| + gfx::Point screen_loc) |
| + : ignored_icon_window_(ignored_icon_window), |
| + output_window_(None), |
| + screen_loc_(screen_loc) { |
| + ui::EnumerateTopLevelWindows(this); |
| + } |
| + |
| + virtual ~DragTargetWindowFinder() {} |
| + |
| + XID window() const { return output_window_; } |
| + |
| + protected: |
| + virtual bool ShouldStopIterating(XID window) OVERRIDE { |
| + if (window == ignored_icon_window_) |
| + return false; |
| + |
| + if (!ui::IsWindowVisible(window)) |
| + return false; |
| + |
| + if (!ui::WindowContainsPoint(window, screen_loc_)) |
| + return false; |
| + |
| + if (ui::PropertyExists(window, "WM_STATE")) { |
| + output_window_ = window; |
| + return true; |
| + } |
| + |
| + return false; |
| + } |
| + |
| + private: |
| + XID ignored_icon_window_; |
| + XID output_window_; |
| + gfx::Point screen_loc_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(DragTargetWindowFinder); |
| +}; |
| + |
| +// Returns the topmost X11 window at |screen_point| if it is advertising that |
| +// is supports the Xdnd protocol. Will return the window under the pointer as |
| +// |mouse_window|. If there's a Xdnd aware window, it will be returned in |
| +// |dest_window|. |
| +void FindWindowFor(const gfx::Point& screen_point, |
| + ::Window* mouse_window, ::Window* dest_window) { |
| + DragTargetWindowFinder finder(None, screen_point); |
| + *mouse_window = finder.window(); |
| + |
| + if (finder.window() == None) { |
| + NOTIMPLEMENTED(); |
|
Daniel Erat
2013/06/17 22:09:57
this NOTIMPLEMENTED() seems weird to me -- what do
Elliot Glaysher
2013/06/18 00:06:39
I wanted a nonfatal log. Deleted.
|
| + *dest_window = None; |
|
Daniel Erat
2013/06/17 22:09:57
how about clearing dest_window at the beginning of
|
| + return; |
| + } |
| + |
| + // Figure out which window we should test as XdndAware. If mouse_window has |
| + // XdndProxy, it will set that proxy on target, and if not, |target|'s |
| + // original value will remain. |
| + XID target = *mouse_window; |
| + ui::GetXIDProperty(*mouse_window, "XdndProxy", &target); |
| + |
| + int version; |
| + if (ui::GetIntProperty(target, "XdndAware", &version) && |
| + version >= kMinXdndVersion) { |
| + *dest_window = target; |
| + } else { |
| + *dest_window = None; |
| + } |
| +} |
| + |
| } // namespace |
| namespace views { |
| +std::map< ::Window, DesktopDragDropClientAuraX11*> |
| + DesktopDragDropClientAuraX11::g_live_client_map; |
| + |
| class DesktopDragDropClientAuraX11::X11DragContext : |
| public base::MessageLoop::Dispatcher { |
| public: |
| X11DragContext(ui::X11AtomCache* atom_cache, |
| + ::Window local_window, |
| const XClientMessageEvent& event); |
| virtual ~X11DragContext(); |
| - const std::vector<Atom>& targets() { return targets_; } |
| + // When we receive an XdndPosition message, we need to have all the data |
| + // copied from the other window before we process the XdndPosition |
| + // message. If we have that data already, dispatch immediately. Otherwise, |
| + // delay dispatching until we do. |
| + void OnStartXdndPositionMessage(DesktopDragDropClientAuraX11* client, |
| + ::Window source_window, |
| + const gfx::Point& screen_point); |
| + |
| + // Called to request the next target from the source window. This is only |
| + // done on the first XdndPosition; after that, we cache the data offered by |
| + // the source window. |
| + void RequestNextTarget(); |
| + |
| + // Called when XSelection data has been copied to our process. |
| + void OnSelectionNotify(const XSelectionEvent& xselection); |
| + |
| + // Clones the fetched targets. |
| + ui::SelectionFormatMap* CloneFetchedTargets() { |
| + DCHECK(fetched_targets_); |
| + return fetched_targets_->Clone(); |
| + } |
| // Reads the "XdndActionList" property from |source_window| and copies it |
| // into |actions|. |
| @@ -76,11 +178,28 @@ class DesktopDragDropClientAuraX11::X11DragContext : |
| // The atom cache owned by our parent. |
| ui::X11AtomCache* atom_cache_; |
| + // The XID of our chrome local aura window handling our events. |
| + ::Window local_window_; |
| + |
| // The XID of the window that's initiated the drag. |
| unsigned long source_window_; |
| - // targets. |
| - std::vector<Atom> targets_; |
| + // The client we inform once we're done with requesting data. |
| + DesktopDragDropClientAuraX11* drag_drop_client_; |
| + |
| + // Whether we're blocking the handling of an XdndPosition message by waiting |
| + // for |unfetched_targets_| to be fetched. |
| + bool waiting_to_handle_position_; |
| + |
| + // Where the cursor is on screen |
|
Daniel Erat
2013/06/17 22:09:57
nit: add trailing period
|
| + gfx::Point screen_point_; |
| + |
| + // A SelectionFormatMap of data that we have in our process. |
| + scoped_ptr<ui::SelectionFormatMap> fetched_targets_; |
| + |
| + // The names of various data types offered by the other window that we |
| + // haven't fetched and put in |fetched_targets_| yet. |
| + std::vector<Atom> unfetched_targets_; |
| // supplied actions |
| std::vector<Atom> actions_; |
| @@ -90,60 +209,139 @@ class DesktopDragDropClientAuraX11::X11DragContext : |
| DesktopDragDropClientAuraX11::X11DragContext::X11DragContext( |
| ui::X11AtomCache* atom_cache, |
| + ::Window local_window, |
| const XClientMessageEvent& event) |
| : atom_cache_(atom_cache), |
| - source_window_(event.data.l[0]) { |
| + local_window_(local_window), |
| + source_window_(event.data.l[0]), |
| + drag_drop_client_(NULL), |
| + waiting_to_handle_position_(false) { |
| bool get_types = ((event.data.l[1] & 1) != 0); |
| if (get_types) { |
| if (!ui::GetAtomArrayProperty(source_window_, |
| "XdndTypeList", |
| - &targets_)) { |
| + &unfetched_targets_)) { |
| return; |
| } |
| } else { |
| // data.l[2,3,4] contain the first three types. Unused slots can be None. |
| for (int i = 0; i < 3; ++i) { |
| if (event.data.l[2+i] != None) { |
| - targets_.push_back(event.data.l[2+i]); |
| + unfetched_targets_.push_back(event.data.l[2+i]); |
| } |
| } |
| } |
| - // TODO(erg): If this window is part of our process, don't listen through the |
| - // MessagePump, but instead listen to the DesktopDragDropClientAuraX11 |
| - // associated with that window. |
| - base::MessagePumpAuraX11::Current()->AddDispatcherForWindow( |
| - this, source_window_); |
| - XSelectInput(base::MessagePumpAuraX11::GetDefaultXDisplay(), |
| - source_window_, PropertyChangeMask); |
| - |
| - // We must perform a full sync here because we could be racing |
| - // |source_window_|. |
| - XSync(base::MessagePumpAuraX11::GetDefaultXDisplay(), False); |
| + DesktopDragDropClientAuraX11* client = |
| + DesktopDragDropClientAuraX11::GetForWindow(source_window_); |
| + if (!client) { |
| + // The window doesn't have a DesktopDragDropClientAuraX11, that means its |
|
Daniel Erat
2013/06/17 22:09:57
nit: s/its/it's/
|
| + // created by some other process. Listen for messages on it. |
| + base::MessagePumpAuraX11::Current()->AddDispatcherForWindow( |
| + this, source_window_); |
| + XSelectInput(base::MessagePumpAuraX11::GetDefaultXDisplay(), |
| + source_window_, PropertyChangeMask); |
| + |
| + // We must perform a full sync here because we could be racing |
| + // |source_window_|. |
| + XSync(base::MessagePumpAuraX11::GetDefaultXDisplay(), False); |
| + } else { |
| + // This drag originates from an aura window within our process. This means |
| + // that we can shortcut the X11 server and ask the owning SelectionOwner |
| + // for the data its offering. |
|
Daniel Erat
2013/06/17 22:09:57
nit: s/its/it's/
|
| + fetched_targets_.reset(client->CloneFormatMap()); |
| + unfetched_targets_.clear(); |
| + } |
| ReadActions(); |
| } |
| DesktopDragDropClientAuraX11::X11DragContext::~X11DragContext() { |
| - // Unsubscribe to message events. |
| - base::MessagePumpAuraX11::Current()->RemoveDispatcherForWindow( |
| - source_window_); |
| + DesktopDragDropClientAuraX11* client = |
| + DesktopDragDropClientAuraX11::GetForWindow(source_window_); |
| + if (!client) { |
| + // Unsubscribe to message events. |
|
Daniel Erat
2013/06/17 22:09:57
nit: s/to/from/
|
| + base::MessagePumpAuraX11::Current()->RemoveDispatcherForWindow( |
| + source_window_); |
| + } |
| } |
| -void DesktopDragDropClientAuraX11::X11DragContext::ReadActions() { |
| - std::vector<Atom> atom_array; |
| +void DesktopDragDropClientAuraX11::X11DragContext::OnStartXdndPositionMessage( |
| + DesktopDragDropClientAuraX11* client, |
| + ::Window source_window, |
| + const gfx::Point& screen_point) { |
| + DCHECK_EQ(source_window_, source_window); |
| + |
| + if (!unfetched_targets_.empty()) { |
| + // We have unfetched targets. That means we need to pause the handling of |
| + // the position message and ask the other window for its data. |
| + screen_point_ = screen_point; |
| + drag_drop_client_ = client; |
| + waiting_to_handle_position_ = true; |
| + |
| + // At this point, we need to start |
|
Daniel Erat
2013/06/17 22:09:57
fix this comment
|
| + fetched_targets_.reset(new ui::SelectionFormatMap); |
| + RequestNextTarget(); |
| + } else { |
| + client->CompleteXdndPosition(source_window, screen_point); |
| + } |
| +} |
| - // TODO(erg): The GTK+ code has a fast path that short circuits talking over |
| - // X11 for local windows. When we become a drop source, we should have a |
| - // similar fast path. |
| +void DesktopDragDropClientAuraX11::X11DragContext::RequestNextTarget() { |
| + ::Atom target = unfetched_targets_.back(); |
| + unfetched_targets_.pop_back(); |
| + |
| + XConvertSelection(base::MessagePumpAuraX11::GetDefaultXDisplay(), |
| + atom_cache_->GetAtom(kXdndSelection), |
| + target, |
| + atom_cache_->GetAtom(kChromiumDragReciever), |
| + local_window_, |
| + CurrentTime); |
| +} |
| - if (!ui::GetAtomArrayProperty(source_window_, |
| - "XdndActionList", |
| - &atom_array)) { |
| - actions_.clear(); |
| +void DesktopDragDropClientAuraX11::X11DragContext::OnSelectionNotify( |
| + const XSelectionEvent& event) { |
| + DCHECK(waiting_to_handle_position_); |
| + DCHECK(drag_drop_client_); |
| + |
| + unsigned char* data = NULL; |
| + size_t data_bytes = 0; |
| + ::Atom type = None; |
| + if (ui::GetRawBytesOfProperty(local_window_, event.property, |
| + &data, &data_bytes, NULL, &type)) { |
| + char* copied_data = new char[data_bytes]; |
| + memcpy(copied_data, data, data_bytes); |
| + fetched_targets_->Insert(event.target, copied_data, data_bytes); |
| + XFree(data); |
| + } |
| + |
| + if (!unfetched_targets_.empty()) { |
| + RequestNextTarget(); |
| + } else { |
| + waiting_to_handle_position_ = false; |
| + drag_drop_client_->CompleteXdndPosition(source_window_, screen_point_); |
| + drag_drop_client_ = NULL; |
| + } |
| +} |
| + |
| +void DesktopDragDropClientAuraX11::X11DragContext::ReadActions() { |
| + DesktopDragDropClientAuraX11* client = |
| + DesktopDragDropClientAuraX11::GetForWindow(source_window_); |
| + if (!client) { |
| + std::vector<Atom> atom_array; |
| + if (!ui::GetAtomArrayProperty(source_window_, |
| + "XdndActionList", |
| + &atom_array)) { |
| + actions_.clear(); |
| + } else { |
| + actions_.swap(atom_array); |
| + } |
| } else { |
| - actions_.swap(atom_array); |
| + // We have a property notify set up for other windows in case they change |
| + // their action list. Thankfully, the views interface is static and you |
| + // can't change the action list after you enter StartDragAndDrop(). |
| + actions_ = client->GetOfferedDragOperations(); |
| } |
| } |
| @@ -178,22 +376,40 @@ DesktopDragDropClientAuraX11::DesktopDragDropClientAuraX11( |
| aura::RootWindow* root_window, |
| Display* xdisplay, |
| ::Window xwindow) |
| - : root_window_host_(root_window_host), |
| + : move_loop_(this), |
| + root_window_host_(root_window_host), |
| root_window_(root_window), |
| xdisplay_(xdisplay), |
| xwindow_(xwindow), |
| atom_cache_(xdisplay_, kAtomsToCache), |
| target_window_(NULL), |
| + source_provider_(NULL), |
| + source_current_window_(None), |
| drag_drop_in_progress_(false), |
| - drag_operation_(0) { |
| + drag_operation_(0), |
| + resulting_operation_(0) { |
| + DCHECK(g_live_client_map.find(xwindow) == g_live_client_map.end()); |
|
Daniel Erat
2013/06/17 22:09:57
nit: can you use DCHECK_EQ() here?
Elliot Glaysher
2013/06/18 00:06:39
You can't use DCHECK_EQ with iterators.
|
| + g_live_client_map.insert(std::make_pair(xwindow, this)); |
| + |
| // Mark that we are aware of drag and drop concepts. |
| - unsigned long xdnd_version = 5; |
| + unsigned long xdnd_version = kMinXdndVersion; |
| XChangeProperty(xdisplay_, xwindow_, atom_cache_.GetAtom("XdndAware"), |
| XA_ATOM, 32, PropModeReplace, |
| reinterpret_cast<unsigned char*>(&xdnd_version), 1); |
| } |
| DesktopDragDropClientAuraX11::~DesktopDragDropClientAuraX11() { |
| + g_live_client_map.erase(xwindow_); |
| +} |
| + |
| +// static |
| +DesktopDragDropClientAuraX11* DesktopDragDropClientAuraX11::GetForWindow( |
| + ::Window window) { |
| + std::map< ::Window, DesktopDragDropClientAuraX11*>::const_iterator it = |
| + g_live_client_map.find(window); |
| + if (it == g_live_client_map.end()) |
| + return NULL; |
| + return it->second; |
| } |
| void DesktopDragDropClientAuraX11::OnXdndEnter( |
| @@ -207,8 +423,9 @@ void DesktopDragDropClientAuraX11::OnXdndEnter( |
| } |
| // Make sure that we've run ~X11DragContext() before creating another one. |
| - current_context_.reset(); |
| - current_context_.reset(new X11DragContext(&atom_cache_, event)); |
| + target_current_context_.reset(); |
| + target_current_context_.reset( |
| + new X11DragContext(&atom_cache_, xwindow_, event)); |
| // In the Windows implementation, we immediately call DesktopDropTargetWin:: |
| // Translate(). Here, we wait until we receive an XdndPosition message |
| @@ -218,8 +435,9 @@ void DesktopDragDropClientAuraX11::OnXdndEnter( |
| void DesktopDragDropClientAuraX11::OnXdndLeave( |
| const XClientMessageEvent& event) { |
| + DVLOG(1) << "XdndLeave"; |
| NotifyDragLeave(); |
| - current_context_.reset(); |
| + target_current_context_.reset(); |
| } |
| void DesktopDragDropClientAuraX11::OnXdndPosition( |
| @@ -230,48 +448,60 @@ void DesktopDragDropClientAuraX11::OnXdndPosition( |
| int x_root_window = event.data.l[2] >> 16; |
| int y_root_window = event.data.l[2] & 0xffff; |
| - if (!current_context_.get()) { |
| + if (!target_current_context_.get()) { |
| NOTREACHED(); |
| return; |
| } |
| - int drag_operation = ui::DragDropTypes::DRAG_NONE; |
| - scoped_ptr<ui::OSExchangeData> data; |
| - scoped_ptr<ui::DropTargetEvent> drop_target_event; |
| - DragDropDelegate* delegate; |
| - DragTranslate(gfx::Point(x_root_window, y_root_window), |
| - &data, &drop_target_event, &delegate); |
| - if (delegate) |
| - drag_operation = delegate->OnDragUpdated(*drop_target_event); |
| - |
| - // Sends an XdndStatus message back to the source_window. l[2,3] |
| - // theoretically represent an area in the window where the current action is |
| - // the same as what we're returning, but I can't find any implementation that |
| - // actually making use of this. A client can return (0, 0) and/or set the |
| - // first bit of l[1] to disable the feature, and it appears that gtk neither |
| - // sets this nor respects it if set. |
| - XEvent xev; |
| - xev.xclient.type = ClientMessage; |
| - xev.xclient.message_type = atom_cache_.GetAtom("XdndStatus"); |
| - xev.xclient.format = 32; |
| - xev.xclient.window = source_window; |
| - xev.xclient.data.l[0] = xwindow_; |
| - xev.xclient.data.l[1] = (drag_operation != 0) ? (2 | 1) : 0; |
| - xev.xclient.data.l[2] = 0; |
| - xev.xclient.data.l[3] = 0; |
| - xev.xclient.data.l[4] = DragOperationToAtom(drag_operation); |
| - |
| - SendXClientEvent(source_window, &xev); |
| + // If we already have all the data from this drag, we complete it |
| + // immediately. |
| + target_current_context_->OnStartXdndPositionMessage( |
| + this, source_window, gfx::Point(x_root_window, y_root_window)); |
| } |
| void DesktopDragDropClientAuraX11::OnXdndStatus( |
| const XClientMessageEvent& event) { |
| DVLOG(1) << "XdndStatus"; |
| + |
| + unsigned long source_window = event.data.l[0]; |
| + if (event.data.l[1] & 1) |
| + negotiated_operation_[source_window] = event.data.l[4]; |
| + |
| + // TODO(erg): event.data.[2,3] specify a rectangle. It is a request by the |
|
Daniel Erat
2013/06/17 22:09:57
is this really a TODO, or just a note? sounds like
|
| + // other window to not send further XdndPosition messages while the cursor is |
| + // within it. However, it is considered advisory and (at least according to |
| + // the spec) the other side must handle further position messages within |
| + // it. GTK+ doesn't bother with this, so neither should we. |
| + |
| + waiting_on_status_.erase(source_window); |
| + |
| + // TODO(erg): We should be using the response to try to update the cursor or |
| + // something. |
| + |
| + if (ContainsKey(pending_drop_, source_window)) { |
| + // We were waiting on the status message so we could send the XdndDrop. |
| + SendXdndDrop(source_window); |
| + return; |
| + } |
| + |
| + NextPositionMap::iterator it = next_position_message_.find(source_window); |
| + if (it != next_position_message_.end()) { |
| + // We were waiting on the status message so we could send off the next |
| + // position message we queued up. |
| + gfx::Point p = it->second.first; |
| + unsigned long time = it->second.second; |
| + next_position_message_.erase(it); |
| + |
| + SendXdndPosition(source_window, p, time); |
| + } |
| } |
| void DesktopDragDropClientAuraX11::OnXdndFinished( |
| const XClientMessageEvent& event) { |
| DVLOG(1) << "XdndFinished"; |
| + resulting_operation_ = AtomToDragOperation( |
| + negotiated_operation_[event.data.l[0]]); |
| + move_loop_.EndMoveLoop(); |
| } |
| void DesktopDragDropClientAuraX11::OnXdndDrop( |
| @@ -286,11 +516,12 @@ void DesktopDragDropClientAuraX11::OnXdndDrop( |
| aura::client::GetDragDropDelegate(target_window_); |
| if (delegate) { |
| ui::OSExchangeData data(new ui::OSExchangeDataProviderAuraX11( |
| - root_window_host_, xwindow_, current_context_->targets())); |
| + xwindow_, target_current_context_->CloneFetchedTargets())); |
| + |
| ui::DropTargetEvent event(data, |
| target_window_location_, |
| target_window_root_location_, |
| - current_context_->GetDragOperation()); |
| + target_current_context_->GetDragOperation()); |
| drag_operation = delegate->OnPerformDrop(event); |
| } |
| @@ -310,6 +541,16 @@ void DesktopDragDropClientAuraX11::OnXdndDrop( |
| SendXClientEvent(source_window, &xev); |
| } |
| +void DesktopDragDropClientAuraX11::OnSelectionNotify( |
| + const XSelectionEvent& xselection) { |
| + if (!target_current_context_) { |
| + NOTIMPLEMENTED(); |
| + return; |
| + } |
| + |
| + target_current_context_->OnSelectionNotify(xselection); |
| +} |
| + |
| int DesktopDragDropClientAuraX11::StartDragAndDrop( |
| const ui::OSExchangeData& data, |
| aura::RootWindow* root_window, |
| @@ -317,16 +558,31 @@ int DesktopDragDropClientAuraX11::StartDragAndDrop( |
| const gfx::Point& root_location, |
| int operation, |
| ui::DragDropTypes::DragEventSource source) { |
| + source_current_window_ = None; |
| + drag_drop_in_progress_ = true; |
| + drag_operation_ = operation; |
| + resulting_operation_ = ui::DragDropTypes::DRAG_NONE; |
| + |
| + const ui::OSExchangeData::Provider* provider = &data.provider(); |
| + source_provider_ = static_cast<const ui::OSExchangeDataProviderAuraX11*>( |
| + provider); |
| + |
| + source_provider_->TakeOwnershipOfSelection(); |
| + |
| + std::vector< ::Atom> actions = GetOfferedDragOperations(); |
| + ui::SetAtomArrayProperty(xwindow_, "XdndActionList", "ATOM", actions); |
| + |
| // Windows has a specific method, DoDragDrop(), which performs the entire |
| // drag. We have to emulate this, so we spin off a nested runloop which will |
| // track all cursor movement and reroute events to a specific handler. |
| + move_loop_.RunMoveLoop(source_window); |
| - NOTIMPLEMENTED(); |
| - |
| - // TODO(erg): Once this is implemented, make sure to reenable the |
| - // NativeTextfieldViewsTest_DragAndDrop* tests. |
| + source_provider_ = NULL; |
| + drag_drop_in_progress_ = false; |
| + drag_operation_ = 0; |
| + XDeleteProperty(xdisplay_, xwindow_, atom_cache_.GetAtom("XdndActionList")); |
| - return ui::DragDropTypes::DRAG_NONE; |
| + return resulting_operation_; |
| } |
| void DesktopDragDropClientAuraX11::DragUpdate(aura::Window* target, |
| @@ -340,7 +596,7 @@ void DesktopDragDropClientAuraX11::Drop(aura::Window* target, |
| } |
| void DesktopDragDropClientAuraX11::DragCancel() { |
| - NOTIMPLEMENTED(); |
| + move_loop_.EndMoveLoop(); |
| } |
| bool DesktopDragDropClientAuraX11::IsDragDropInProgress() { |
| @@ -352,6 +608,64 @@ void DesktopDragDropClientAuraX11::OnWindowDestroyed(aura::Window* window) { |
| target_window_ = NULL; |
| } |
| +void DesktopDragDropClientAuraX11::OnMouseMovement(XMotionEvent* event) { |
| + gfx::Point screen_point(event->x_root, event->y_root); |
| + |
| + // Find the current window the cursor is over. |
| + ::Window mouse_window = None; |
| + ::Window dest_window = None; |
| + FindWindowFor(screen_point, &mouse_window, &dest_window); |
| + |
| + if (source_current_window_ != dest_window) { |
| + if (source_current_window_ != None) |
| + SendXdndLeave(source_current_window_); |
| + |
| + source_current_window_ = dest_window; |
| + |
| + if (source_current_window_ != None) { |
| + negotiated_operation_.erase(source_current_window_); |
| + SendXdndEnter(source_current_window_); |
| + } |
| + } |
| + |
| + if (source_current_window_ != None) { |
| + if (ContainsKey(waiting_on_status_, dest_window)) { |
| + next_position_message_[dest_window] = |
| + std::make_pair(screen_point, event->time); |
| + } else { |
| + SendXdndPosition(dest_window, screen_point, event->time); |
| + } |
| + } |
| +} |
| + |
| +void DesktopDragDropClientAuraX11::OnMouseReleased() { |
| + if (source_current_window_ != None) { |
| + if (ContainsKey(waiting_on_status_, source_current_window_)) { |
| + // If we are waiting for an XdndStatus message, we need to wait for it to |
| + // complete. |
| + pending_drop_.insert(source_current_window_); |
| + return; |
| + } |
| + |
| + std::map< ::Window, ::Atom>::iterator it = |
| + negotiated_operation_.find(source_current_window_); |
| + if (it != negotiated_operation_.end() && it->second != None) { |
| + // We have negotiated an action with the other end. |
| + SendXdndDrop(source_current_window_); |
| + return; |
| + } |
| + |
| + SendXdndLeave(source_current_window_); |
| + source_current_window_ = None; |
| + } |
| + |
| + move_loop_.EndMoveLoop(); |
| +} |
| + |
| +void DesktopDragDropClientAuraX11::OnMoveLoopEnded() { |
| + target_current_context_.reset(); |
| +} |
| + |
| void DesktopDragDropClientAuraX11::DragTranslate( |
| const gfx::Point& root_window_location, |
| scoped_ptr<ui::OSExchangeData>* data, |
| @@ -378,7 +692,7 @@ void DesktopDragDropClientAuraX11::DragTranslate( |
| return; |
| data->reset(new OSExchangeData(new ui::OSExchangeDataProviderAuraX11( |
| - root_window_host_, xwindow_, current_context_->targets()))); |
| + xwindow_, target_current_context_->CloneFetchedTargets()))); |
| gfx::Point location = root_location; |
| aura::Window::ConvertPointToTarget(root_window_, target_window_, &location); |
| @@ -389,7 +703,7 @@ void DesktopDragDropClientAuraX11::DragTranslate( |
| *(data->get()), |
| location, |
| root_location, |
| - current_context_->GetDragOperation())); |
| + target_current_context_->GetDragOperation())); |
| if (target_window_changed) |
| (*delegate)->OnDragEntered(*event->get()); |
| } |
| @@ -405,7 +719,7 @@ void DesktopDragDropClientAuraX11::NotifyDragLeave() { |
| target_window_ = NULL; |
| } |
| -unsigned long DesktopDragDropClientAuraX11::DragOperationToAtom( |
| +::Atom DesktopDragDropClientAuraX11::DragOperationToAtom( |
| int drag_operation) { |
| if (drag_operation & ui::DragDropTypes::DRAG_COPY) |
| return atom_cache_.GetAtom(kXdndActionCopy); |
| @@ -417,24 +731,181 @@ unsigned long DesktopDragDropClientAuraX11::DragOperationToAtom( |
| return None; |
| } |
| -void DesktopDragDropClientAuraX11::SendXClientEvent(unsigned long xid, |
| +int DesktopDragDropClientAuraX11::AtomToDragOperation(::Atom atom) { |
| + int drag_operation = ui::DragDropTypes::DRAG_NONE; |
| + if (atom == atom_cache_.GetAtom(kXdndActionCopy)) |
| + drag_operation = ui::DragDropTypes::DRAG_COPY; |
|
Daniel Erat
2013/06/17 22:09:57
nit: remove drag_operation and just return from ea
|
| + else if (atom == atom_cache_.GetAtom(kXdndActionMove)) |
| + drag_operation = ui::DragDropTypes::DRAG_MOVE; |
| + else if (atom == atom_cache_.GetAtom(kXdndActionLink)) |
| + drag_operation = ui::DragDropTypes::DRAG_LINK; |
| + return drag_operation; |
| +} |
| + |
| +std::vector< ::Atom> DesktopDragDropClientAuraX11::GetOfferedDragOperations() { |
| + std::vector< ::Atom> operations; |
| + if (drag_operation_ & ui::DragDropTypes::DRAG_COPY) |
| + operations.push_back(atom_cache_.GetAtom(kXdndActionCopy)); |
| + if (drag_operation_ & ui::DragDropTypes::DRAG_MOVE) |
| + operations.push_back(atom_cache_.GetAtom(kXdndActionMove)); |
| + if (drag_operation_ & ui::DragDropTypes::DRAG_LINK) |
| + operations.push_back(atom_cache_.GetAtom(kXdndActionLink)); |
| + return operations; |
| +} |
| + |
| +ui::SelectionFormatMap* DesktopDragDropClientAuraX11::CloneFormatMap() const { |
| + return source_provider_ ? source_provider_->CloneFormatMap() : NULL; |
| +} |
| + |
| +void DesktopDragDropClientAuraX11::CompleteXdndPosition( |
| + ::Window source_window, |
| + const gfx::Point& screen_point) { |
| + int drag_operation = ui::DragDropTypes::DRAG_NONE; |
| + scoped_ptr<ui::OSExchangeData> data; |
| + scoped_ptr<ui::DropTargetEvent> drop_target_event; |
| + DragDropDelegate* delegate; |
|
Daniel Erat
2013/06/17 22:09:57
nit: initialize to NULL
|
| + DragTranslate(screen_point, &data, &drop_target_event, &delegate); |
| + if (delegate) |
| + drag_operation = delegate->OnDragUpdated(*drop_target_event); |
| + |
| + // Sends an XdndStatus message back to the source_window. l[2,3] |
| + // theoretically represent an area in the window where the current action is |
| + // the same as what we're returning, but I can't find any implementation that |
| + // actually making use of this. A client can return (0, 0) and/or set the |
| + // first bit of l[1] to disable the feature, and it appears that gtk neither |
| + // sets this nor respects it if set. |
| + XEvent xev; |
| + xev.xclient.type = ClientMessage; |
| + xev.xclient.message_type = atom_cache_.GetAtom("XdndStatus"); |
| + xev.xclient.format = 32; |
| + xev.xclient.window = source_window; |
| + xev.xclient.data.l[0] = xwindow_; |
| + xev.xclient.data.l[1] = (drag_operation != 0) ? (2 | 1) : 0; |
|
Daniel Erat
2013/06/17 22:09:57
nit: document what this means in more detail
|
| + xev.xclient.data.l[2] = 0; |
| + xev.xclient.data.l[3] = 0; |
| + xev.xclient.data.l[4] = DragOperationToAtom(drag_operation); |
| + |
| + SendXClientEvent(source_window, &xev); |
| +} |
| + |
| +void DesktopDragDropClientAuraX11::SendXdndEnter(::Window dest_window) { |
| + XEvent xev; |
| + xev.xclient.type = ClientMessage; |
| + xev.xclient.message_type = atom_cache_.GetAtom("XdndEnter"); |
| + xev.xclient.format = 32; |
| + xev.xclient.window = dest_window; |
| + xev.xclient.data.l[0] = xwindow_; |
| + xev.xclient.data.l[1] = (kMinXdndVersion << 24); // The version number. |
| + xev.xclient.data.l[2] = 0; |
| + xev.xclient.data.l[3] = 0; |
| + xev.xclient.data.l[4] = 0; |
| + |
| + std::vector<Atom> targets; |
| + source_provider_->RetrieveTargets(&targets); |
| + |
| + if (targets.size() > 3) { |
| + xev.xclient.data.l[1] |= 1; |
| + ui::SetAtomArrayProperty(xwindow_, "XdndTypeList", "ATOM", targets); |
| + } else { |
| + // Pack the targets into the enter message. |
| + for (size_t i = 0; i < targets.size(); ++i) |
| + xev.xclient.data.l[2 + i] = targets[i]; |
| + } |
| + |
| + SendXClientEvent(dest_window, &xev); |
| +} |
| + |
| +void DesktopDragDropClientAuraX11::SendXdndLeave(::Window dest_window) { |
| + // If we're sending a leave message, don't wait for status messages anymore. |
| + waiting_on_status_.erase(dest_window); |
| + NextPositionMap::iterator it = next_position_message_.find(dest_window); |
| + if (it != next_position_message_.end()) |
| + next_position_message_.erase(it); |
| + |
| + XEvent xev; |
| + xev.xclient.type = ClientMessage; |
| + xev.xclient.message_type = atom_cache_.GetAtom("XdndLeave"); |
| + xev.xclient.format = 32; |
| + xev.xclient.window = dest_window; |
| + xev.xclient.data.l[0] = xwindow_; |
| + xev.xclient.data.l[1] = 0; |
| + xev.xclient.data.l[2] = 0; |
| + xev.xclient.data.l[3] = 0; |
| + xev.xclient.data.l[4] = 0; |
| + SendXClientEvent(dest_window, &xev); |
| +} |
| + |
| +void DesktopDragDropClientAuraX11::SendXdndPosition( |
| + ::Window dest_window, |
| + const gfx::Point& screen_point, |
| + unsigned long time) { |
| + waiting_on_status_.insert(dest_window); |
| + |
| + XEvent xev; |
| + xev.xclient.type = ClientMessage; |
| + xev.xclient.message_type = atom_cache_.GetAtom("XdndPosition"); |
| + xev.xclient.format = 32; |
| + xev.xclient.window = dest_window; |
| + xev.xclient.data.l[0] = xwindow_; |
| + xev.xclient.data.l[1] = 0; |
| + xev.xclient.data.l[2] = (screen_point.x() << 16) | screen_point.y(); |
| + xev.xclient.data.l[3] = time; |
| + xev.xclient.data.l[4] = DragOperationToAtom(drag_operation_); |
| + SendXClientEvent(dest_window, &xev); |
| +} |
| + |
| +void DesktopDragDropClientAuraX11::SendXdndDrop(::Window dest_window) { |
| + XEvent xev; |
| + xev.xclient.type = ClientMessage; |
| + xev.xclient.message_type = atom_cache_.GetAtom("XdndDrop"); |
| + xev.xclient.format = 32; |
| + xev.xclient.window = dest_window; |
| + xev.xclient.data.l[0] = xwindow_; |
| + xev.xclient.data.l[1] = 0; |
| + xev.xclient.data.l[2] = CurrentTime; |
| + xev.xclient.data.l[3] = None; |
| + xev.xclient.data.l[4] = None; |
| + SendXClientEvent(dest_window, &xev); |
| +} |
| + |
| +void DesktopDragDropClientAuraX11::SendXClientEvent(::Window xid, |
| XEvent* xev) { |
| DCHECK_EQ(ClientMessage, xev->type); |
| - // TODO(erg): When I get drag receiving working, shortcut messages to the X |
| - // server and call the receivers DesktopDragDropClientAuraX11 instance |
| - // instead. |
| - // |
| + // Don't send messages to the X11 message queue if we can help it. |
| + DesktopDragDropClientAuraX11* short_circuit = GetForWindow(xid); |
| + if (short_circuit) { |
| + Atom message_type = xev->xclient.message_type; |
| + if (message_type == atom_cache_.GetAtom("XdndEnter")) { |
| + short_circuit->OnXdndEnter(xev->xclient); |
| + return; |
| + } else if (message_type == atom_cache_.GetAtom("XdndLeave")) { |
| + short_circuit->OnXdndLeave(xev->xclient); |
| + return; |
| + } else if (message_type == atom_cache_.GetAtom("XdndPosition")) { |
| + short_circuit->OnXdndPosition(xev->xclient); |
| + return; |
| + } else if (message_type == atom_cache_.GetAtom("XdndStatus")) { |
| + short_circuit->OnXdndStatus(xev->xclient); |
| + return; |
| + } else if (message_type == atom_cache_.GetAtom("XdndFinished")) { |
| + short_circuit->OnXdndFinished(xev->xclient); |
| + return; |
| + } else if (message_type == atom_cache_.GetAtom("XdndDrop")) { |
| + short_circuit->OnXdndDrop(xev->xclient); |
| + return; |
| + } |
| + } |
| + |
| // I don't understand why the GTK+ code is doing what it's doing here. It |
| // goes out of its way to send the XEvent so that it receives a callback on |
| - // success or failure, and when it fails, it then sends an internal GdkEvent |
| - // about the failed drag. (And sending this message doesn't appear to go |
| - // through normal xlib machinery, but instead passes through the low level |
| - // xProto (the x11 wire format) that I don't understand. |
| + // success or failure, and when it fails, it then sends an internal |
| + // GdkEvent about the failed drag. (And sending this message doesn't appear |
| + // to go through normal xlib machinery, but instead passes through the low |
| + // level xProto (the x11 wire format) that I don't understand. |
| // |
| // I'm unsure if I have to jump through those hoops, or if XSendEvent is |
| // sufficient. |
| - |
| XSendEvent(xdisplay_, xid, False, 0, xev); |
| } |