303 lines
7.7 KiB
C++
303 lines
7.7 KiB
C++
/*
|
|
* 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 <nana/filesystem/filesystem.hpp>
|
|
|
|
#include "theme.hpp"
|
|
#include <X11/Xcursor/Xcursor.h>
|
|
|
|
#include <vector>
|
|
|
|
|
|
namespace nana{
|
|
namespace detail
|
|
{
|
|
|
|
struct xdnd_data
|
|
{
|
|
Atom requested_action;
|
|
std::vector<std::filesystem::path> 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<Window>(xclient.data.l[0]);
|
|
bool is_accepted_by_target = (xclient.data.l[1] & 1);
|
|
|
|
if(xclient.data.l[1] & 0x2)
|
|
{
|
|
rectangle rct{
|
|
static_cast<int>(xclient.data.l[2] >> 16),
|
|
static_cast<int>(xclient.data.l[2] & 0xFFFF),
|
|
static_cast<unsigned>(xclient.data.l[3] >> 16),
|
|
static_cast<unsigned>(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<unsigned char*>(&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<Window, nana::rectangle> mvout_table_;
|
|
|
|
struct cursor_rep
|
|
{
|
|
Cursor dnd_copy{ 0 };
|
|
Cursor dnd_move{ 0 };
|
|
Cursor dnd_none{ 0 };
|
|
}cursor_;
|
|
}; //end class xdnd_protocol
|
|
}
|
|
}
|
|
|
|
#endif |