/* * X-Window XDND Protocol Implementation * Nana C++ Library(http://www.nanapro.org) * Copyright(C) 2018-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) * * @file: nana/detail/posix/xdnd_protocol.hpp * * The XDS is not supported. */ #ifndef NANA_DETAIL_POSIX_XDND_PROTOCOL_INCLUDED #define NANA_DETAIL_POSIX_XDND_PROTOCOL_INCLUDED #include "platform_spec.hpp" #include #include "theme.hpp" #include #include namespace nana{ namespace detail { struct xdnd_data { Atom requested_action; std::vector files; }; class xdnd_protocol { public: enum class xdnd_status_state { normal, position, drop, //Use the 'accept' flag of XdndStatus when mouse has released(XdndDrop has been sent). status_ignore }; xdnd_protocol(Window source): spec_(nana::detail::platform_spec::instance()), source_(source) { auto disp = spec_.open_display(); detail::platform_scope_guard lock; ::XSetSelectionOwner(disp, spec_.atombase().xdnd_selection, source, CurrentTime); cursor_.dnd_copy = ::XcursorLibraryLoadCursor(disp, "dnd-copy"); cursor_.dnd_move = ::XcursorLibraryLoadCursor(disp, "dnd-move"); cursor_.dnd_none = ::XcursorLibraryLoadCursor(disp, "dnd-none"); } ~xdnd_protocol() { auto disp = spec_.open_display(); ::XFreeCursor(disp, cursor_.dnd_copy); ::XFreeCursor(disp, cursor_.dnd_move); ::XFreeCursor(disp, cursor_.dnd_none); } void mouse_move(Window wd, const nana::point& pos, Atom requested_action) { if(wd != target_) { _m_xdnd_leave(); if(_m_xdnd_enter(wd)) _m_xdnd_position(pos, requested_action); } else _m_xdnd_position(pos, requested_action); } void mouse_leave() { _m_xdnd_leave(); } bool mouse_release() { return _m_xdnd_drop(); } Atom executed_action() const { return executed_action_; } //Return true to exit xdnd_protocol event handler bool client_message(const ::XClientMessageEvent& xclient) { auto & atombase = spec_.atombase(); if(atombase.xdnd_status == xclient.message_type) { if(xdnd_status_state::position != xstate_ && xdnd_status_state::drop != xstate_) return false; Window target_wd = static_cast(xclient.data.l[0]); bool is_accepted_by_target = (xclient.data.l[1] & 1); if(xclient.data.l[1] & 0x2) { rectangle rct{ static_cast(xclient.data.l[2] >> 16), static_cast(xclient.data.l[2] & 0xFFFF), static_cast(xclient.data.l[3] >> 16), static_cast(xclient.data.l[3] & 0xFFFF) }; if(!rct.empty()) mvout_table_[target_wd] = rct; } _m_cursor(is_accepted_by_target); if((!is_accepted_by_target) && (xdnd_status_state::drop == xstate_)) { _m_xdnd_leave(); return true; } executed_action_ = xclient.data.l[4]; xstate_ = xdnd_status_state::normal; } else if(atombase.xdnd_finished == xclient.message_type) return true; return false; } void selection_request(const ::XSelectionRequestEvent& xselectionrequest, const xdnd_data& data) { if(spec_.atombase().xdnd_selection == xselectionrequest.selection) { ::XEvent evt; evt.xselection.type = SelectionNotify; evt.xselection.display = xselectionrequest.display; evt.xselection.requestor = xselectionrequest.requestor; evt.xselection.selection = xselectionrequest.selection; evt.xselection.target = 0; evt.xselection.property = 0; evt.xselection.time = xselectionrequest.time; if(xselectionrequest.target == spec_.atombase().text_uri_list) { if(data.files.size()) { std::string uri_list; for(auto& file : data.files) { uri_list += "file://"; uri_list += file.string(); uri_list += "\r\n"; } ::XChangeProperty (spec_.open_display(), xselectionrequest.requestor, xselectionrequest.property, xselectionrequest.target, 8, PropModeReplace, reinterpret_cast(&uri_list.front()), uri_list.size() + 1); evt.xselection.property = xselectionrequest.property; evt.xselection.target = xselectionrequest.target; } } platform_scope_guard lock; ::XSendEvent(spec_.open_display(), xselectionrequest.requestor, False, 0, &evt); ::XFlush(spec_.open_display()); if(0 == evt.xselection.target) _m_xdnd_leave(); } } private: bool _m_xdnd_enter(Window wd) { //xdnd version of the window auto xdnd_ver = _m_xdnd_aware(wd); if(0 == xdnd_ver) return false; target_ = wd; _m_client_msg(spec_.atombase().xdnd_enter, (xdnd_ver << 24), spec_.atombase().text_uri_list, XA_STRING); return true; } void _m_xdnd_position(const nana::point& pos, Atom requested_action) { if(xdnd_status_state::normal != xstate_) return; auto i = mvout_table_.find(target_); if(i != mvout_table_.end() && i->second.is_hit(pos)) return; xstate_ = xdnd_status_state::position; //Send XdndPosition long position = (pos.x << 16 | pos.y); _m_client_msg(spec_.atombase().xdnd_position, 0, position, CurrentTime, requested_action); } void _m_xdnd_leave() { ::XUndefineCursor(spec_.open_display(), source_); if(target_) { _m_client_msg(spec_.atombase().xdnd_leave, 0, 0, 0); target_ = 0; executed_action_ = 0; } } bool _m_xdnd_drop() { ::XUndefineCursor(spec_.open_display(), source_); xstate_ = xdnd_status_state::drop; if(executed_action_) _m_client_msg(spec_.atombase().xdnd_drop, 0, CurrentTime, 0); target_ = 0; return (executed_action_ != 0); } private: //dndversion<<24, fl_XdndURIList, XA_STRING, 0 void _m_client_msg(Atom xdnd_atom, long data1, long data2, long data3, long data4 = 0) { auto const display = spec_.open_display(); XEvent evt; ::memset(&evt, 0, sizeof evt); evt.xany.type = ClientMessage; evt.xany.display = display; evt.xclient.window = target_; evt.xclient.message_type = xdnd_atom; evt.xclient.format = 32; //Target window evt.xclient.data.l[0] = source_; evt.xclient.data.l[1] = data1; evt.xclient.data.l[2] = data2; evt.xclient.data.l[3] = data3; evt.xclient.data.l[4] = data4; ::XSendEvent(display, target_, True, NoEventMask, &evt); } // Returns the XDND version of specified window //@return the XDND version. If the specified window does not have property XdndAware, it returns 0 int _m_xdnd_aware(Window wd) { Atom actual; int format; unsigned long count, remaining; unsigned char *data = 0; ::XGetWindowProperty(spec_.open_display(), wd, spec_.atombase().xdnd_aware, 0, 4, False, XA_ATOM, &actual, &format, &count, &remaining, &data); int version = 0; if (data) { if ((actual == XA_ATOM) && (format==32) && count) version = int(*(Atom*)data); ::XFree(data); } return version; } void _m_cursor(bool accepted) { ::XDefineCursor(spec_.open_display(), source_, (accepted ? cursor_.dnd_move : cursor_.dnd_none)); } private: nana::detail::platform_spec& spec_; Window const source_; Window target_{ 0 }; Atom executed_action_{ 0 }; xdnd_status_state xstate_{xdnd_status_state::normal}; std::map mvout_table_; struct cursor_rep { Cursor dnd_copy{ 0 }; Cursor dnd_move{ 0 }; Cursor dnd_none{ 0 }; }cursor_; }; //end class xdnd_protocol } } #endif