nana/source/detail/posix/xdnd_protocol.hpp
2018-11-30 07:40:15 +08:00

314 lines
8.4 KiB
C++

/*
* X-Window XDND Protocol Implementation
* Nana C++ Library(http://www.nanapro.org)
* Copyright(C) 2018 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 <vector>
#include <iostream> //debug
namespace nana{
namespace detail
{
struct xdnd_data
{
std::vector<std::filesystem::path> files;
};
class xdnd_protocol
{
public:
enum class xdnd_status_state
{
normal,
position_sent,
status_ignore
};
xdnd_protocol(Window source):
spec_(nana::detail::platform_spec::instance()),
source_(source)
{
detail::platform_scope_guard lock;
::XSetSelectionOwner(spec_.open_display(), spec_.atombase().xdnd_selection, source, CurrentTime);
std::cout<<"XSetSelectionOwner "<<source<<std::endl;
}
void mouse_move(Window wd, const nana::point& pos)
{
if(wd != target_)
{
_m_xdnd_leave();
if(_m_xdnd_enter(wd))
_m_xdnd_position(pos);
}
else
_m_xdnd_position(pos);
}
void mouse_leave()
{
_m_xdnd_leave();
}
void mouse_release()
{
_m_xdnd_drop();
}
//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)
{
std::cout<<"Event: XdndStatus"<<std::endl;
if(xdnd_status_state::position_sent != xstate_)
return false;
Window target_wd = static_cast<Window>(xclient.data.l[0]);
bool is_accepted_by_target = (xclient.data.l[1] & 1);
std::cout<<"XdndStatus: Accepted="<<is_accepted_by_target<<std::endl;
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;
std::cout<<". rct=("<<rct.x<<","<<rct.y<<","<<rct.width<<","<<rct.height<<")";
}
}
std::cout<<std::endl;
xstate_ = xdnd_status_state::normal;
if(!is_accepted_by_target)
{
_m_xdnd_leave();
return true;
}
}
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)
{
std::cout<<"Event SelectionRequest: XdndSelection"<<std::endl;
::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;
auto property_name = ::XGetAtomName(spec_.open_display(), xselectionrequest.property); //debug
std::cout<<"SelectionRequest"<<std::endl;
std::cout<<" xdnd_selection:"<<spec_.atombase().xdnd_selection<<std::endl;
std::cout<<" text_uri_list :"<<spec_.atombase().text_uri_list<<std::endl;
std::cout<<" selection="<<xselectionrequest.selection<<std::endl;
std::cout<<" property ="<<xselectionrequest.property<<", name="<<property_name<<std::endl;
std::cout<<" target ="<<xselectionrequest.target<<std::endl;
::XFree(property_name);
if(xselectionrequest.target == spec_.atombase().text_uri_list)
{
std::cout<<"SelectionRequest target = text_uri_list";
if(data.files.size())
{
std::string uri_list;
for(auto& file : data.files)
{
uri_list += "file://";
uri_list += file.u8string();
uri_list += "\r\n";
}
std::cout<<". URIs="<<uri_list<<std::endl;
::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;
}
}
else
std::cout<<"SelectionRequest target = "<<xselectionrequest.target<<std::endl;
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;
std::cout<<"Send XdndEnter, text/uri-list="<<spec_.atombase().text_uri_list<<std::endl;
_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)
{
if(xdnd_status_state::normal != xstate_)
return;
auto i = mvout_table_.find(target_);
if(i != mvout_table_.end() && i->second.is_hit(pos))
return;
std::cout<<"Send: XdndPosition"<<std::endl;
xstate_ = xdnd_status_state::position_sent;
//Send XdndPosition
long position = (pos.x << 16 | pos.y);
_m_client_msg(spec_.atombase().xdnd_position, 0, position, CurrentTime, spec_.atombase().xdnd_action_copy);
}
void _m_xdnd_leave()
{
if(target_)
{
std::cout<<"Send: XdndLeave"<<std::endl;
_m_client_msg(spec_.atombase().xdnd_leave, 0, 0, 0);
target_ = 0;
}
}
void _m_xdnd_drop()
{
std::cout<<"Send: XdndDrop"<<std::endl;
_m_client_msg(spec_.atombase().xdnd_drop, 0, CurrentTime, 0);
target_ = 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 ((actual == XA_ATOM) && (format==32) && count && data)
{
version = int(*(Atom*)data);
std::cout<<"Get:XdndAware version:"<<version<<std::endl;
}
if (data)
::XFree(data);
return version;
}
#if 0 //deprecated
//Check if window has a property
static bool _m_has_property(Window wd, Atom atom, unsigned char** data)
{
Atom type = 0;
int f;
unsigned long n, a;
unsigned char * data_back = nullptr;
if(nullptr == data)
data = &data_back;
if (::XGetWindowProperty(spec_.open_display(), wd, atom, 0, 0, False, AnyPropertyType, &type, &f,&n,&a,data) == Success)
{
//release the *data if failed to get the property or unspecified output buffer
if((0 == type) || data_back)
::XFree(*data);
return (0 != type);
}
return false;
}
#endif
private:
nana::detail::platform_spec& spec_;
Window const source_;
Window target_{ 0 };
xdnd_status_state xstate_{xdnd_status_state::normal};
std::map<Window, nana::rectangle> mvout_table_;
}; //end class xdnd_protocol
}
}
#endif