summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-01-04 01:16:44 +0100
committerwm4 <wm4@nowhere>2014-01-04 01:27:28 +0100
commit6a1b12158d903c09b7889820451b07dc73ee8576 (patch)
treecc69c8e91cf32b8355e0a109a2994f0303330b9e
parent0702f642d576ee085f65bb1363b1b61f84150e51 (diff)
downloadmpv-6a1b12158d903c09b7889820451b07dc73ee8576.tar.bz2
mpv-6a1b12158d903c09b7889820451b07dc73ee8576.tar.xz
x11: implement dropping files on the VO window
Drag and drop is pretty complicated (just note how the number of Atoms in use almost doubles), so I'm not sure whether this works everywhere. This has been written by looking at the specification [1] or what looks like the specification, and some external example code [2]. (The latter one has no code license, but we didn't copy any code.) We completely ignore the "requirement" of the spec. that the filename "must" include username and hostname, e.g. "file://user@host/path/file". In theory, this is required because X is network transparent, but at this point the so called network transparency is a complete joke, and Konqueror for one didn't include hostnames in "file://" URIs. Tested with konqueror as drop source. [1] http://www.newplanetsoftware.com/xdnd/ [2] http://www.edwardrosten.com/code/dist/x_clipboard-1.1/paste.cc
-rw-r--r--video/out/x11_common.c181
-rw-r--r--video/out/x11_common.h17
2 files changed, 192 insertions, 6 deletions
diff --git a/video/out/x11_common.c b/video/out/x11_common.c
index 9b5d5cb929..2e8b0917ac 100644
--- a/video/out/x11_common.c
+++ b/video/out/x11_common.c
@@ -93,6 +93,8 @@
#define WIN_LAYER_ONTOP 6
#define WIN_LAYER_ABOVE_DOCK 10
+#define DND_VERSION 5
+
// ----- Motif header: -------
#define MWM_HINTS_FUNCTIONS (1L << 0)
@@ -342,17 +344,17 @@ static int net_wm_support_state_test(struct vo_x11_state *x11, Atom atom)
return 0;
}
-static int x11_get_property(struct vo_x11_state *x11, Atom type, Atom **args,
- unsigned long *nitems)
+static bool x11_get_property(struct vo_x11_state *x11, Window wnd, Atom type,
+ Atom **args, unsigned long *nitems)
{
int format;
unsigned long bytesafter;
return Success ==
- XGetWindowProperty(x11->display, x11->rootwin, type, 0, 16384, False,
+ XGetWindowProperty(x11->display, wnd, type, 0, 16384, False,
AnyPropertyType, &type, &format, nitems,
&bytesafter, (unsigned char **) args)
- && *nitems > 0;
+ && *nitems > 0 && bytesafter == 0;
}
static int vo_wm_detect(struct vo *vo)
@@ -362,12 +364,13 @@ static int vo_wm_detect(struct vo *vo)
int wm = 0;
unsigned long nitems;
Atom *args = NULL;
+ Window win = x11->rootwin;
if (vo->opts->WinID >= 0)
return 0;
// -- supports layers
- if (x11_get_property(x11, x11->XA_WIN_PROTOCOLS, &args, &nitems)) {
+ if (x11_get_property(x11, win, x11->XA_WIN_PROTOCOLS, &args, &nitems)) {
MP_VERBOSE(x11, "Detected wm supports layers.\n");
for (i = 0; i < nitems; i++) {
if (args[i] == x11->XA_WIN_LAYER)
@@ -376,7 +379,7 @@ static int vo_wm_detect(struct vo *vo)
XFree(args);
}
// --- netwm
- if (x11_get_property(x11, x11->XA_NET_SUPPORTED, &args, &nitems)) {
+ if (x11_get_property(x11, win, x11->XA_NET_SUPPORTED, &args, &nitems)) {
MP_VERBOSE(x11, "Detected wm supports NetWM.\n");
for (i = 0; i < nitems; i++)
wm |= net_wm_support_state_test(vo->x11, args[i]);
@@ -407,9 +410,21 @@ static void init_atoms(struct vo_x11_state *x11)
XA_INIT(WM_PROTOCOLS);
XA_INIT(WM_DELETE_WINDOW);
XA_INIT(UTF8_STRING);
+ XA_INIT(TARGETS);
+ XA_INIT(XdndAware);
+ XA_INIT(XdndEnter);
+ XA_INIT(XdndLeave);
+ XA_INIT(XdndPosition);
+ XA_INIT(XdndStatus);
+ XA_INIT(XdndActionCopy);
+ XA_INIT(XdndTypeList);
+ XA_INIT(XdndDrop);
+ XA_INIT(XdndSelection);
+ XA_INIT(XdndFinished);
char buf[50];
sprintf(buf, "_NET_WM_CM_S%d", x11->screen);
x11->XA_NET_WM_CM = XInternAtom(x11->display, buf, False);
+ x11->XA_uri_list = XInternAtom(x11->display, "text/uri-list", False);
}
static void vo_x11_update_screeninfo(struct vo *vo)
@@ -720,6 +735,155 @@ void vo_x11_uninit(struct vo *vo)
vo->x11 = NULL;
}
+// The data is in the form of the mimetype text/uri-list.
+static bool dnd_handle_drop_data(struct vo *vo, bstr data)
+{
+ void *tmp = talloc_new(NULL);
+ int num_files = 0;
+ char **files = NULL;
+ while (data.len) {
+ bstr line = bstr_getline(data, &data);
+ line = bstr_strip_linebreaks(line);
+ if (bstr_startswith0(line, "#"))
+ continue;
+ char *s = bstrto0(tmp, line);
+ MP_TARRAY_APPEND(tmp, files, num_files, s);
+ }
+ vo_drop_files(vo, num_files, files);
+ talloc_free(tmp);
+ return num_files > 0;
+}
+
+static void vo_x11_dnd_init_window(struct vo *vo)
+{
+ struct vo_x11_state *x11 = vo->x11;
+
+ Atom version = DND_VERSION;
+ XChangeProperty(x11->display, x11->window, x11->XAXdndAware, XA_ATOM,
+ 32, PropModeReplace, (unsigned char *)&version, 1);
+
+ x11->dnd_property = XInternAtom(x11->display, "mpv_dnd_selection", False);
+}
+
+static void dnd_select_format(struct vo_x11_state *x11, Atom *args, int items)
+{
+ for (int n = 0; n < items; n++) {
+ // There are other types; possibly not worth supporting.
+ if (args[n] == x11->XA_uri_list)
+ x11->dnd_requested_format = args[n];
+ }
+}
+
+static void dnd_reset(struct vo *vo)
+{
+ struct vo_x11_state *x11 = vo->x11;
+
+ x11->dnd_src_window = 0;
+ x11->dnd_requested_format = 0;
+}
+
+static void vo_x11_dnd_handle_message(struct vo *vo, XClientMessageEvent *ce)
+{
+ struct vo_x11_state *x11 = vo->x11;
+
+ if (!x11->window)
+ return;
+
+ if (ce->message_type == x11->XAXdndEnter) {
+ x11->dnd_requested_format = 0;
+
+ Window src = ce->data.l[0];
+ if (ce->data.l[1] & 1) {
+ unsigned long nitems = 0;
+ Atom *args = NULL;
+ if (x11_get_property(x11, src, x11->XAXdndTypeList, &args, &nitems)) {
+ dnd_select_format(x11, args, nitems);
+ XFree(args);
+ }
+ } else {
+ Atom args[3];
+ for (int n = 2; n <= 4; n++)
+ args[n - 2] = ce->data.l[n];
+ dnd_select_format(x11, args, 3);
+ }
+ } else if (ce->message_type == x11->XAXdndPosition) {
+ Window src = ce->data.l[0];
+ XEvent xev;
+
+ xev.xclient.type = ClientMessage;
+ xev.xclient.serial = 0;
+ xev.xclient.send_event = True;
+ xev.xclient.message_type = x11->XAXdndStatus;
+ xev.xclient.window = src;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = x11->window;
+ xev.xclient.data.l[1] = x11->dnd_requested_format ? 1 : 0;
+ xev.xclient.data.l[2] = 0;
+ xev.xclient.data.l[3] = 0;
+ xev.xclient.data.l[4] = x11->XAXdndActionCopy;
+
+ XSendEvent(x11->display, src, False, 0, &xev);
+ } else if (ce->message_type == x11->XAXdndDrop) {
+ x11->dnd_src_window = ce->data.l[0];
+ XConvertSelection(x11->display, x11->XAXdndSelection,
+ x11->dnd_requested_format, x11->dnd_property,
+ x11->window, ce->data.l[2]);
+ } else if (ce->message_type == x11->XAXdndLeave) {
+ dnd_reset(vo);
+ }
+}
+
+static void vo_x11_dnd_handle_selection(struct vo *vo, XSelectionEvent *se)
+{
+ struct vo_x11_state *x11 = vo->x11;
+
+ if (!x11->window || !x11->dnd_src_window)
+ return;
+
+ bool success = false;
+
+ if (se->selection == x11->XAXdndSelection &&
+ se->property == x11->dnd_property &&
+ se->target == x11->dnd_requested_format)
+ {
+ Atom type;
+ int format;
+ unsigned long nitems;
+ unsigned long bytes_left;
+ unsigned char *prop;
+ if (XGetWindowProperty(x11->display, x11->window, x11->dnd_property,
+ 0, 64 * 1024, False, x11->dnd_requested_format,
+ &type, &format, &nitems, &bytes_left, &prop)
+ == Success)
+ {
+ if (!bytes_left && type == x11->dnd_requested_format && format == 8)
+ {
+ // No idea if this is guaranteed to be \0-padded, so use bstr.
+ success = dnd_handle_drop_data(vo, (bstr){prop, nitems});
+ }
+ XFree(prop);
+ }
+ }
+
+ XEvent xev;
+
+ xev.xclient.type = ClientMessage;
+ xev.xclient.serial = 0;
+ xev.xclient.send_event = True;
+ xev.xclient.message_type = x11->XAXdndFinished;
+ xev.xclient.window = x11->dnd_src_window;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = x11->window;
+ xev.xclient.data.l[1] = success ? 1 : 0;
+ xev.xclient.data.l[2] = success ? x11->XAXdndActionCopy : 0;
+ xev.xclient.data.l[3] = 0;
+ xev.xclient.data.l[4] = 0;
+
+ XSendEvent(x11->display, x11->dnd_src_window, False, 0, &xev);
+
+ dnd_reset(vo);
+}
+
static void update_vo_size(struct vo *vo)
{
struct vo_x11_state *x11 = vo->x11;
@@ -838,6 +1002,10 @@ int vo_x11_check_events(struct vo *vo)
if (Event.xclient.message_type == x11->XAWM_PROTOCOLS &&
Event.xclient.data.l[0] == x11->XAWM_DELETE_WINDOW)
mp_input_put_key(vo->input_ctx, MP_KEY_CLOSE_WIN);
+ vo_x11_dnd_handle_message(vo, &Event.xclient);
+ break;
+ case SelectionNotify:
+ vo_x11_dnd_handle_selection(vo, &Event.xselection);
break;
default:
if (Event.type == x11->ShmCompletionEvent) {
@@ -1129,6 +1297,7 @@ static void vo_x11_create_window(struct vo *vo, XVisualInfo *vis, int x, int y,
vo_x11_set_wm_icon(x11);
vo_x11_update_window_title(vo);
+ vo_x11_dnd_init_window(vo);
}
static void vo_x11_map_window(struct vo *vo, int x, int y, int w, int h)
diff --git a/video/out/x11_common.h b/video/out/x11_common.h
index 2763009755..f65af8e79f 100644
--- a/video/out/x11_common.h
+++ b/video/out/x11_common.h
@@ -102,6 +102,11 @@ struct vo_x11_state {
/* Increment it before XShmPutImage */
int ShmCompletionWaitCount;
+ /* drag and drop */
+ Atom dnd_property;
+ Atom dnd_requested_format;
+ Window dnd_src_window;
+
Atom XA_NET_SUPPORTED;
Atom XA_NET_WM_STATE;
Atom XA_NET_WM_STATE_FULLSCREEN;
@@ -119,6 +124,18 @@ struct vo_x11_state {
Atom XAWM_DELETE_WINDOW;
Atom XAUTF8_STRING;
Atom XA_NET_WM_CM;
+ Atom XATARGETS;
+ Atom XAXdndAware;
+ Atom XAXdndEnter;
+ Atom XAXdndLeave;
+ Atom XAXdndPosition;
+ Atom XAXdndStatus;
+ Atom XAXdndActionCopy;
+ Atom XAXdndTypeList;
+ Atom XAXdndDrop;
+ Atom XAXdndSelection;
+ Atom XAXdndFinished;
+ Atom XA_uri_list;
};
int vo_x11_init(struct vo *vo);