2
0
Эх сурвалжийг харах

Merge pull request #4874 from Hinsbart/x11_dragndrop

x11: Add support for filesystem drag & drop using xdnd
Rémi Verschelde 9 жил өмнө
parent
commit
d31696e3db

+ 165 - 0
platform/x11/os_x11.cpp

@@ -105,6 +105,7 @@ void OS_X11::initialize(const VideoMode& p_desired,int p_video_driver,int p_audi
 	last_timestamp=0;
 	last_mouse_pos_valid=false;
 	last_keyrelease_time=0;
+	xdnd_version = 0;
 
 	if (get_render_thread_mode()==RENDER_SEPARATE_THREAD) {
 		XInitThreads();
@@ -415,6 +416,19 @@ void OS_X11::initialize(const VideoMode& p_desired,int p_video_driver,int p_audi
 	}
 	set_cursor_shape(CURSOR_BUSY);
 
+	//Set Xdnd (drag & drop) support
+	Atom XdndAware = XInternAtom(x11_display, "XdndAware", False);
+	Atom version=5;
+	XChangeProperty(x11_display, x11_window, XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*)&version, 1);
+
+	xdnd_enter = XInternAtom(x11_display, "XdndEnter", False);
+	xdnd_position = XInternAtom(x11_display, "XdndPosition", False);
+	xdnd_status = XInternAtom(x11_display, "XdndStatus", False);
+	xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False);
+	xdnd_drop = XInternAtom(x11_display, "XdndDrop", False);
+	xdnd_finished = XInternAtom(x11_display, "XdndFinished", False);
+	xdnd_selection = XInternAtom(x11_display, "XdndSelection", False);
+	requested = None;
 
 	visual_server->init();
 	//
@@ -1166,6 +1180,72 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
 	input->parse_input_event( event);
 }
 
+struct Property
+{
+	unsigned char *data;
+	int format, nitems;
+	Atom type;
+};
+
+static Property read_property(Display* p_display, Window p_window, Atom p_property) {
+
+	Atom actual_type;
+	int actual_format;
+	unsigned long nitems;
+	unsigned long bytes_after;
+	unsigned char *ret=0;
+
+	int read_bytes = 1024;
+
+	//Keep trying to read the property until there are no
+	//bytes unread.
+	do
+	{
+		if(ret != 0)
+			XFree(ret);
+
+		XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType,
+					   &actual_type, &actual_format, &nitems, &bytes_after,
+					   &ret);
+
+		read_bytes *= 2;
+
+	}while(bytes_after != 0);
+
+	Property p = {ret, actual_format, nitems, actual_type};
+
+	return p;
+}
+
+static Atom pick_target_from_list(Display* p_display, Atom *p_list, int p_count) {
+
+	static const char* target_type = "text/uri-list";
+
+	for (int i = 0; i < p_count; i++) {
+
+		Atom atom = p_list[i];
+
+		if (atom != None && String(XGetAtomName(p_display, atom)) == target_type)
+				return atom;
+	}
+	return None;
+}
+
+static Atom pick_target_from_atoms(Display* p_disp, Atom p_t1, Atom p_t2, Atom p_t3) {
+
+	static const char* target_type = "text/uri-list";
+	if (p_t1 != None && String(XGetAtomName(p_disp, p_t1)) == target_type)
+		return p_t1;
+
+	if (p_t2 != None && String(XGetAtomName(p_disp, p_t2)) == target_type)
+		return p_t2;
+
+	if (p_t3 != None && String(XGetAtomName(p_disp, p_t3)) == target_type)
+		return p_t3;
+
+	return None;
+}
+
 void OS_X11::process_xevents() {
 
 	//printf("checking events %i\n", XPending(x11_display));
@@ -1456,11 +1536,96 @@ void OS_X11::process_xevents() {
 			XFlush (x11_display);
 		} break;
 
+		case SelectionNotify:
+
+			if (event.xselection.target == requested) {
+
+				Property p = read_property(x11_display, x11_window, XInternAtom(x11_display, "PRIMARY", 0));
+
+				Vector<String> files = String((char *)p.data).split("\n", false);
+				for (int i = 0; i < files.size(); i++) {
+					files[i] = files[i].replace("file://", "").replace("%20", " ").strip_escapes();
+				}
+				main_loop->drop_files(files);
+
+				//Reply that all is well.
+				XClientMessageEvent m;
+				memset(&m, sizeof(m), 0);
+				m.type = ClientMessage;
+				m.display = x11_display;
+				m.window = xdnd_source_window;
+				m.message_type = xdnd_finished;
+				m.format=32;
+				m.data.l[0] = x11_window;
+				m.data.l[1] = 1;
+				m.data.l[2] = xdnd_action_copy; //We only ever copy.
+
+				XSendEvent(x11_display, xdnd_source_window, False, NoEventMask, (XEvent*)&m);
+			}
+			break;
 
 		case ClientMessage:
 
 			if ((unsigned int)event.xclient.data.l[0]==(unsigned int)wm_delete)
 				main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
+
+			else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) {
+
+				//File(s) have been dragged over the window, check for supported target (text/uri-list)
+				xdnd_version = ( event.xclient.data.l[1] >> 24);
+				Window source = event.xclient.data.l[0];
+				bool more_than_3 = event.xclient.data.l[1] & 1;
+				if (more_than_3) {
+					Property p = read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False));
+					requested = pick_target_from_list(x11_display, (Atom*)p.data, p.nitems);
+				}
+				else
+					requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2],event.xclient.data.l[3], event.xclient.data.l[4]);
+			}
+			else if ((unsigned int)event.xclient.message_type == (unsigned int )xdnd_position) {
+
+				//xdnd position event, reply with an XDND status message
+				//just depending on type of data for now
+				XClientMessageEvent m;
+				memset(&m, sizeof(m), 0);
+				m.type = ClientMessage;
+				m.display = event.xclient.display;
+				m.window = event.xclient.data.l[0];
+				m.message_type = xdnd_status;
+				m.format=32;
+				m.data.l[0] = x11_window;
+				m.data.l[1] = (requested != None);
+				m.data.l[2] = 0; //empty rectangle
+				m.data.l[3] = 0;
+				m.data.l[4] = xdnd_action_copy;
+
+				XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent*)&m);
+				XFlush(x11_display);
+			}
+			else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_drop) {
+
+				if (requested != None) {
+					xdnd_source_window = event.xclient.data.l[0];
+					if(xdnd_version >= 1)
+						XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), x11_window, event.xclient.data.l[2]);
+					else
+						XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), x11_window, CurrentTime);
+				}
+				else {
+					//Reply that we're not interested.
+					XClientMessageEvent m;
+					memset(&m, sizeof(m), 0);
+					m.type = ClientMessage;
+					m.display = event.xclient.display;
+					m.window = event.xclient.data.l[0];
+					m.message_type = xdnd_finished;
+					m.format=32;
+					m.data.l[0] = x11_window;
+					m.data.l[1] = 0;
+					m.data.l[2] = None; //Failed.
+					XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent*)&m);
+				}
+			}
 			break;
 		default:
 			break;

+ 12 - 0
platform/x11/os_x11.h

@@ -70,6 +70,17 @@ typedef struct {
 class OS_X11 : public OS_Unix {
 
 	Atom wm_delete;
+	Atom xdnd_enter;
+	Atom xdnd_position;
+	Atom xdnd_status;
+	Atom xdnd_action_copy;
+	Atom xdnd_drop;
+	Atom xdnd_finished;
+	Atom xdnd_selection;
+	Atom requested;
+
+	int  xdnd_version;
+
 #if defined(OPENGL_ENABLED) || defined(LEGACYGL_ENABLED)
 	ContextGL_X11 *context_gl;
 #endif
@@ -78,6 +89,7 @@ class OS_X11 : public OS_Unix {
 	VideoMode current_videomode;
 	List<String> args;
 	Window x11_window;
+	Window xdnd_source_window;
 	MainLoop *main_loop;
 	::Display* x11_display;
 	char *xmbstring;