Launcher: Fix drag and drop

This commit is contained in:
o9000 2017-09-02 12:39:40 +02:00
parent 14c3824632
commit 498b665c8a
6 changed files with 243 additions and 241 deletions

View file

@ -31,10 +31,123 @@ static Atom dnd_selection;
static Atom dnd_atom; static Atom dnd_atom;
static int dnd_sent_request; static int dnd_sent_request;
static LauncherIcon *dnd_launcher_icon; static LauncherIcon *dnd_launcher_icon;
static gboolean dnd_debug = FALSE; gboolean debug_dnd = FALSE;
gboolean hidden_panel_shown_for_dnd; gboolean hidden_panel_shown_for_dnd;
// This fetches all the data from a property
struct Property dnd_read_property(Display *disp, Window w, Atom 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(disp,
w,
property,
0,
read_bytes,
False,
AnyPropertyType,
&actual_type,
&actual_format,
&nitems,
&bytes_after,
&ret);
read_bytes *= 2;
} while (bytes_after != 0);
if (debug_dnd)
fprintf(stderr, "tint2: DnD %s:%d: Property:\n", __FILE__, __LINE__);
fprintf(stderr, "tint2: DnD %s:%d: Actual type: %s\n", __FILE__, __LINE__, GetAtomName(disp, actual_type));
fprintf(stderr, "tint2: DnD %s:%d: Actual format: %d\n", __FILE__, __LINE__, actual_format);
fprintf(stderr, "tint2: DnD %s:%d: Number of items: %lu\n", __FILE__, __LINE__, nitems);
Property p;
p.data = ret;
p.format = actual_format;
p.nitems = nitems;
p.type = actual_type;
return p;
}
// This function takes a list of targets which can be converted to (atom_list, nitems)
// and a list of acceptable targets with prioritees (datatypes). It returns the highest
// entry in datatypes which is also in atom_list: ie it finds the best match.
Atom dnd_pick_target_from_list(Display *disp, Atom *atom_list, int nitems)
{
Atom to_be_requested = None;
int i;
for (i = 0; i < nitems; i++) {
const char *atom_name = GetAtomName(disp, atom_list[i]);
fprintf(stderr, "tint2: DnD %s:%d: Type %d = %s\n", __FILE__, __LINE__, i, atom_name);
// See if this data type is allowed and of higher priority (closer to zero)
// than the present one.
if (strcasecmp(atom_name, "STRING") == 0) {
to_be_requested = atom_list[i];
} else if (strcasecmp(atom_name, "text/uri-list") == 0 && !to_be_requested) {
to_be_requested = atom_list[i];
}
}
fprintf(stderr,
"tint2: DnD %s:%d: Accepting: Type %s\n",
__FILE__,
__LINE__,
GetAtomName(server.display, to_be_requested));
return to_be_requested;
}
// Finds the best target given up to three atoms provided (any can be None).
// Useful for part of the Xdnd protocol.
Atom dnd_pick_target_from_atoms(Display *disp, Atom t1, Atom t2, Atom t3)
{
Atom atoms[3];
int n = 0;
if (t1 != None)
atoms[n++] = t1;
if (t2 != None)
atoms[n++] = t2;
if (t3 != None)
atoms[n++] = t3;
return dnd_pick_target_from_list(disp, atoms, n);
}
// Finds the best target given a local copy of a property.
Atom dnd_pick_target_from_targets(Display *disp, Property p)
{
// The list of targets is a list of atoms, so it should have type XA_ATOM
// but it may have the type TARGETS instead.
if ((p.type != XA_ATOM && p.type != server.atom.TARGETS) || p.format != 32) {
// This would be really broken. Targets have to be an atom list
// and applications should support this. Nevertheless, some
// seem broken (MATLAB 7, for instance), so ask for STRING
// next instead as the lowest common denominator
return XA_STRING;
} else {
Atom *atom_list = (Atom *)p.data;
return dnd_pick_target_from_list(disp, atom_list, p.nitems);
}
}
void dnd_init() void dnd_init()
{ {
dnd_source_window = 0; dnd_source_window = 0;
@ -54,7 +167,7 @@ void handle_dnd_enter(XClientMessageEvent *e)
dnd_source_window = e->data.l[0]; dnd_source_window = e->data.l[0];
dnd_version = (e->data.l[1] >> 24); dnd_version = (e->data.l[1] >> 24);
if (dnd_debug) { if (debug_dnd) {
fprintf(stderr, "tint2: DnD %s:%d: DnDEnter\n", __FILE__, __LINE__); fprintf(stderr, "tint2: DnD %s:%d: DnDEnter\n", __FILE__, __LINE__);
fprintf(stderr, fprintf(stderr,
"DnD %s:%d: DnDEnter. Supports > 3 types = %s\n", "DnD %s:%d: DnDEnter. Supports > 3 types = %s\n",
@ -83,15 +196,15 @@ void handle_dnd_enter(XClientMessageEvent *e)
if (more_than_3) { if (more_than_3) {
// Fetch the list of possible conversions // Fetch the list of possible conversions
// Notice the similarity to TARGETS with paste. // Notice the similarity to TARGETS with paste.
Property p = read_property(server.display, dnd_source_window, server.atom.XdndTypeList); Property p = dnd_read_property(server.display, dnd_source_window, server.atom.XdndTypeList);
dnd_atom = pick_target_from_targets(server.display, p); dnd_atom = dnd_pick_target_from_targets(server.display, p);
XFree(p.data); XFree(p.data);
} else { } else {
// Use the available list // Use the available list
dnd_atom = pick_target_from_atoms(server.display, e->data.l[2], e->data.l[3], e->data.l[4]); dnd_atom = dnd_pick_target_from_atoms(server.display, e->data.l[2], e->data.l[3], e->data.l[4]);
} }
if (dnd_debug) if (debug_dnd)
fprintf(stderr, fprintf(stderr,
"tint2: DnD %s:%d: Requested type = %s\n", "tint2: DnD %s:%d: Requested type = %s\n",
__FILE__, __FILE__,
@ -135,11 +248,18 @@ void handle_dnd_position(XClientMessageEvent *e)
se.data.l[2] = 0; // Rectangle x,y for which no more XdndPosition events se.data.l[2] = 0; // Rectangle x,y for which no more XdndPosition events
se.data.l[3] = (1 << 16) | 1; // Rectangle w,h for which no more XdndPosition events se.data.l[3] = (1 << 16) | 1; // Rectangle w,h for which no more XdndPosition events
if (accept) { if (accept) {
se.data.l[4] = dnd_version >= 2 ? e->data.l[4] : server.atom.XdndActionCopy; se.data.l[4] = server.atom.XdndActionCopy;
} else { } else {
se.data.l[4] = None; // None = drop will not be accepted se.data.l[4] = None; // None = drop will not be accepted
} }
if (debug_dnd)
fprintf(stderr,
"tint2: DnD %s:%d: Accepted: %s\n",
__FILE__,
__LINE__,
accept ? GetAtomName(server.display, (Atom)se.data.l[4]) : "no");
XSendEvent(server.display, e->data.l[0], False, NoEventMask, (XEvent *)&se); XSendEvent(server.display, e->data.l[0], False, NoEventMask, (XEvent *)&se);
} }
@ -149,14 +269,14 @@ void handle_dnd_drop(XClientMessageEvent *e)
if (dnd_version >= 1) { if (dnd_version >= 1) {
XConvertSelection(server.display, XConvertSelection(server.display,
server.atom.XdndSelection, server.atom.XdndSelection,
XA_STRING, dnd_atom,
dnd_selection, dnd_selection,
dnd_target_window, dnd_target_window,
e->data.l[2]); e->data.l[2]);
} else { } else {
XConvertSelection(server.display, XConvertSelection(server.display,
server.atom.XdndSelection, server.atom.XdndSelection,
XA_STRING, dnd_atom,
dnd_selection, dnd_selection,
dnd_target_window, dnd_target_window,
CurrentTime); CurrentTime);
@ -178,11 +298,28 @@ void handle_dnd_drop(XClientMessageEvent *e)
} }
} }
GString *tint2_g_string_replace(GString *s, const char *from, const char *to)
{
GString *result = g_string_new("");
for (char *p = s->str; *p;) {
if (strstr(p, from) == p) {
g_string_append(result, to);
p += strlen(from);
} else {
g_string_append_c(result, *p);
p += 1;
}
}
g_string_assign(s, result->str);
g_string_free(result, TRUE);
return s;
}
void handle_dnd_selection_notify(XSelectionEvent *e) void handle_dnd_selection_notify(XSelectionEvent *e)
{ {
Atom target = e->target; Atom target = e->target;
if (dnd_debug) { if (debug_dnd) {
fprintf(stderr, "tint2: DnD %s:%d: A selection notify has arrived!\n", __FILE__, __LINE__); fprintf(stderr, "tint2: DnD %s:%d: A selection notify has arrived!\n", __FILE__, __LINE__);
fprintf(stderr, fprintf(stderr,
"DnD %s:%d: Selection atom = %s\n", "DnD %s:%d: Selection atom = %s\n",
@ -201,20 +338,21 @@ void handle_dnd_selection_notify(XSelectionEvent *e)
GetAtomName(server.display, e->property)); GetAtomName(server.display, e->property));
} }
if (e->property != None && dnd_launcher_icon) { if (dnd_launcher_icon) {
Property prop = read_property(server.display, dnd_target_window, dnd_selection); Property prop = dnd_read_property(server.display, dnd_target_window, dnd_selection);
if (prop.data) {
// If we're being given a list of targets (possible conversions) // If we're being given a list of targets (possible conversions)
if (target == server.atom.TARGETS && !dnd_sent_request) { if (target == server.atom.TARGETS && !dnd_sent_request) {
dnd_sent_request = 1; dnd_sent_request = 1;
dnd_atom = pick_target_from_targets(server.display, prop); dnd_atom = dnd_pick_target_from_targets(server.display, prop);
if (dnd_atom == None) { if (dnd_atom == None) {
if (dnd_debug) if (debug_dnd)
fprintf(stderr, "tint2: No matching datatypes.\n"); fprintf(stderr, "tint2: No matching datatypes.\n");
} else { } else {
// Request the data type we are able to select // Request the data type we are able to select
if (dnd_debug) if (debug_dnd)
fprintf(stderr, "tint2: Now requsting type %s", GetAtomName(server.display, dnd_atom)); fprintf(stderr, "tint2: Now requsting type %s", GetAtomName(server.display, dnd_atom));
XConvertSelection(server.display, XConvertSelection(server.display,
dnd_selection, dnd_selection,
@ -225,67 +363,51 @@ void handle_dnd_selection_notify(XSelectionEvent *e)
} }
} else if (target == dnd_atom) { } else if (target == dnd_atom) {
// Dump the binary data // Dump the binary data
if (dnd_debug) { if (debug_dnd) {
fprintf(stderr, "tint2: DnD %s:%d: Data begins:\n", __FILE__, __LINE__); fprintf(stderr, "tint2: DnD %s:%d: Received data:\n", __FILE__, __LINE__);
fprintf(stderr, "tint2: --------\n"); fprintf(stderr, "tint2: --------\n");
for (int i = 0; i < prop.nitems * prop.format / 8; i++) for (int i = 0; i < prop.nitems * prop.format / 8; i++)
fprintf(stderr, "tint2: %c", ((char *)prop.data)[i]); fprintf(stderr, "%c", ((char *)prop.data)[i]);
fprintf(stderr, "tint2: --------\n"); fprintf(stderr, "tint2: --------\n");
} }
// TODO replace this with exec*(...) // TODO: support %r nd %F
// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables
GString *cmd = g_string_new(dnd_launcher_icon->cmd);
int cmd_length = 0; const char *atom_name = GetAtomName(server.display, prop.type);
cmd_length += 1; // ( if (strcasecmp(atom_name, "STRING") == 0 ||
cmd_length += strlen(dnd_launcher_icon->cmd) + 1; // exec + space strcasecmp(atom_name, "text/uri-list") == 0) {
cmd_length += 1; // open double quotes GString *url = g_string_new("");
GString *prev_url = g_string_new("");
for (int i = 0; i < prop.nitems * prop.format / 8; i++) { for (int i = 0; i < prop.nitems * prop.format / 8; i++) {
char c = ((char *)prop.data)[i]; char c = ((char *)prop.data)[i];
if (c == '\n') { if (c == '\n') {
if (i < prop.nitems * prop.format / 8 - 1) { // Many programs cannot handle this prefix
cmd_length += 3; // close double quotes, space, open double quotes tint2_g_string_replace(url, "file://", "");
} // Some programs put duplicates in the list, we remove them
} else if (c == '\r') { if (strcmp(url->str, prev_url->str) != 0) {
// Nothing to do g_string_append(cmd, " \"");
} else { g_string_append(cmd, url->str);
cmd_length += 1; // 1 character g_string_append(cmd, "\"");
if (c == '`' || c == '$' || c == '\\') {
cmd_length += 1; // escape with one backslash
}
}
}
cmd_length += 1; // close double quotes
cmd_length += 2; // &)
cmd_length += 1; // terminator
char *cmd = (char *)calloc(cmd_length, 1);
cmd[0] = '\0';
strcat(cmd, "(");
strcat(cmd, dnd_launcher_icon->cmd);
strcat(cmd, " \"");
for (int i = 0; i < prop.nitems * prop.format / 8; i++) {
char c = ((char *)prop.data)[i];
if (c == '\n') {
if (i < prop.nitems * prop.format / 8 - 1) {
strcat(cmd, "\" \"");
} }
g_string_assign(prev_url, url->str);
g_string_assign(url, "");
} else if (c == '\r') { } else if (c == '\r') {
// Nothing to do // Nothing to do
} else { } else {
if (c == '`' || c == '$' || c == '\\') { if (c == '`' || c == '$' || c == '\\') {
strcat(cmd, "\\"); g_string_append(url, "\\");
} }
char sc[2]; g_string_append_c(url, c);
sc[0] = c;
sc[1] = '\0';
strcat(cmd, sc);
} }
} }
strcat(cmd, "\""); g_string_free(url, TRUE);
strcat(cmd, "&)"); g_string_free(prev_url, TRUE);
if (dnd_debug) }
fprintf(stderr, "tint2: DnD %s:%d: Running command: %s\n", __FILE__, __LINE__, cmd); if (debug_dnd)
tint_exec(cmd, fprintf(stderr, "tint2: DnD %s:%d: Running command: %s\n", __FILE__, __LINE__, cmd->str);
tint_exec(cmd->str,
NULL, NULL,
NULL, NULL,
e->time, e->time,
@ -294,7 +416,7 @@ void handle_dnd_selection_notify(XSelectionEvent *e)
0, 0,
dnd_launcher_icon->start_in_terminal, dnd_launcher_icon->start_in_terminal,
dnd_launcher_icon->startup_notification); dnd_launcher_icon->startup_notification);
free(cmd); g_string_free(cmd, TRUE);
// Reply OK. // Reply OK.
XClientMessageEvent m; XClientMessageEvent m;
@ -313,4 +435,5 @@ void handle_dnd_selection_notify(XSelectionEvent *e)
XFree(prop.data); XFree(prop.data);
} }
}
} }

View file

@ -10,6 +10,7 @@
#include <glib.h> #include <glib.h>
extern gboolean hidden_panel_shown_for_dnd; extern gboolean hidden_panel_shown_for_dnd;
extern gboolean debug_dnd;
void dnd_init(); void dnd_init();

View file

@ -7,6 +7,7 @@
#include <unistd.h> #include <unistd.h>
#include "config.h" #include "config.h"
#include "drag_and_drop.h"
#include "fps_distribution.h" #include "fps_distribution.h"
#include "panel.h" #include "panel.h"
#include "server.h" #include "server.h"
@ -86,6 +87,7 @@ void handle_env_vars()
debug_icons = getenv("DEBUG_ICONS") != NULL; debug_icons = getenv("DEBUG_ICONS") != NULL;
debug_fps = getenv("DEBUG_FPS") != NULL; debug_fps = getenv("DEBUG_FPS") != NULL;
debug_frames = getenv("DEBUG_FRAMES") != NULL; debug_frames = getenv("DEBUG_FRAMES") != NULL;
debug_dnd = getenv("DEBUG_DND") != NULL;
if (debug_fps) { if (debug_fps) {
init_fps_distribution(); init_fps_distribution();
char *s = getenv("TRACING_FPS_THRESHOLD"); char *s = getenv("TRACING_FPS_THRESHOLD");

View file

@ -131,110 +131,6 @@ const char *GetAtomName(Display *disp, Atom a)
return XGetAtomName(disp, a); return XGetAtomName(disp, a);
} }
// This fetches all the data from a property
struct Property read_property(Display *disp, Window w, Atom 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(disp,
w,
property,
0,
read_bytes,
False,
AnyPropertyType,
&actual_type,
&actual_format,
&nitems,
&bytes_after,
&ret);
read_bytes *= 2;
} while (bytes_after != 0);
fprintf(stderr, "tint2: DnD %s:%d: Property:\n", __FILE__, __LINE__);
fprintf(stderr, "tint2: DnD %s:%d: Actual type: %s\n", __FILE__, __LINE__, GetAtomName(disp, actual_type));
fprintf(stderr, "tint2: DnD %s:%d: Actual format: %d\n", __FILE__, __LINE__, actual_format);
fprintf(stderr, "tint2: DnD %s:%d: Number of items: %lu\n", __FILE__, __LINE__, nitems);
Property p;
p.data = ret;
p.format = actual_format;
p.nitems = nitems;
p.type = actual_type;
return p;
}
// This function takes a list of targets which can be converted to (atom_list, nitems)
// and a list of acceptable targets with prioritees (datatypes). It returns the highest
// entry in datatypes which is also in atom_list: ie it finds the best match.
Atom pick_target_from_list(Display *disp, Atom *atom_list, int nitems)
{
Atom to_be_requested = None;
int i;
for (i = 0; i < nitems; i++) {
const char *atom_name = GetAtomName(disp, atom_list[i]);
fprintf(stderr, "tint2: DnD %s:%d: Type %d = %s\n", __FILE__, __LINE__, i, atom_name);
// See if this data type is allowed and of higher priority (closer to zero)
// than the present one.
if (strcmp(atom_name, "STRING") == 0) {
to_be_requested = atom_list[i];
}
}
return to_be_requested;
}
// Finds the best target given up to three atoms provided (any can be None).
// Useful for part of the Xdnd protocol.
Atom pick_target_from_atoms(Display *disp, Atom t1, Atom t2, Atom t3)
{
Atom atoms[3];
int n = 0;
if (t1 != None)
atoms[n++] = t1;
if (t2 != None)
atoms[n++] = t2;
if (t3 != None)
atoms[n++] = t3;
return pick_target_from_list(disp, atoms, n);
}
// Finds the best target given a local copy of a property.
Atom pick_target_from_targets(Display *disp, Property p)
{
// The list of targets is a list of atoms, so it should have type XA_ATOM
// but it may have the type TARGETS instead.
if ((p.type != XA_ATOM && p.type != server.atom.TARGETS) || p.format != 32) {
// This would be really broken. Targets have to be an atom list
// and applications should support this. Nevertheless, some
// seem broken (MATLAB 7, for instance), so ask for STRING
// next instead as the lowest common denominator
return XA_STRING;
} else {
Atom *atom_list = (Atom *)p.data;
return pick_target_from_list(disp, atom_list, p.nitems);
}
}
void cleanup_server() void cleanup_server()
{ {
if (server.colormap) if (server.colormap)

View file

@ -102,21 +102,6 @@ typedef struct Property {
// Returns the name of an Atom as string. Do not free the string. // Returns the name of an Atom as string. Do not free the string.
const char *GetAtomName(Display *disp, Atom a); const char *GetAtomName(Display *disp, Atom a);
// This function takes a list of targets which can be converted to (atom_list, nitems)
// and a list of acceptable targets with prioritees (datatypes). It returns the highest
// entry in datatypes which is also in atom_list: ie it finds the best match.
Atom pick_target_from_list(Display *disp, Atom *atom_list, int nitems);
// Finds the best target given up to three atoms provided (any can be None).
// Useful for part of the Xdnd protocol.
Atom pick_target_from_atoms(Display *disp, Atom t1, Atom t2, Atom t3);
// Finds the best target given a local copy of a property.
Atom pick_target_from_targets(Display *disp, Property p);
// This fetches all the data from a property
struct Property read_property(Display *disp, Window w, Atom property);
typedef struct Monitor { typedef struct Monitor {
int x; int x;
int y; int y;

View file

@ -404,14 +404,9 @@ pid_t tint_exec(const char *command,
words.we_wordv[0] = (char*)"x-terminal-emulator"; words.we_wordv[0] = (char*)"x-terminal-emulator";
words.we_wordv[1] = (char*)"-e"; words.we_wordv[1] = (char*)"-e";
execvp("x-terminal-emulator", words.we_wordv); execvp("x-terminal-emulator", words.we_wordv);
}
fprintf(stderr, "tint2: could not execute command in x-terminal-emulator: %s, executting in shell\n", command); fprintf(stderr, "tint2: could not execute command in x-terminal-emulator: %s, executting in shell\n", command);
wordexp_t words; }
words.we_offs = 2; execlp("sh", "sh", "-c", command, NULL);
wordexp(command, &words, WRDE_DOOFFS | WRDE_SHOWERR);
words.we_wordv[0] = (char*)"sh";
words.we_wordv[1] = (char*)"-c";
execvp("sh", words.we_wordv);
fprintf(stderr, "tint2: Failed to execute %s\n", command); fprintf(stderr, "tint2: Failed to execute %s\n", command);
#if HAVE_SN #if HAVE_SN
if (startup_notifications && startup_notification && time) { if (startup_notifications && startup_notification && time) {