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 int dnd_sent_request;
static LauncherIcon *dnd_launcher_icon;
static gboolean dnd_debug = FALSE;
gboolean debug_dnd = FALSE;
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()
{
dnd_source_window = 0;
@ -54,7 +167,7 @@ void handle_dnd_enter(XClientMessageEvent *e)
dnd_source_window = e->data.l[0];
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,
"DnD %s:%d: DnDEnter. Supports > 3 types = %s\n",
@ -83,15 +196,15 @@ void handle_dnd_enter(XClientMessageEvent *e)
if (more_than_3) {
// Fetch the list of possible conversions
// Notice the similarity to TARGETS with paste.
Property p = read_property(server.display, dnd_source_window, server.atom.XdndTypeList);
dnd_atom = pick_target_from_targets(server.display, p);
Property p = dnd_read_property(server.display, dnd_source_window, server.atom.XdndTypeList);
dnd_atom = dnd_pick_target_from_targets(server.display, p);
XFree(p.data);
} else {
// 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,
"tint2: DnD %s:%d: Requested type = %s\n",
__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[3] = (1 << 16) | 1; // Rectangle w,h for which no more XdndPosition events
if (accept) {
se.data.l[4] = dnd_version >= 2 ? e->data.l[4] : server.atom.XdndActionCopy;
se.data.l[4] = server.atom.XdndActionCopy;
} else {
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);
}
@ -149,14 +269,14 @@ void handle_dnd_drop(XClientMessageEvent *e)
if (dnd_version >= 1) {
XConvertSelection(server.display,
server.atom.XdndSelection,
XA_STRING,
dnd_atom,
dnd_selection,
dnd_target_window,
e->data.l[2]);
} else {
XConvertSelection(server.display,
server.atom.XdndSelection,
XA_STRING,
dnd_atom,
dnd_selection,
dnd_target_window,
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)
{
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,
"DnD %s:%d: Selection atom = %s\n",
@ -190,7 +327,7 @@ void handle_dnd_selection_notify(XSelectionEvent *e)
__LINE__,
GetAtomName(server.display, e->selection));
fprintf(stderr,
"tint2: DnD %s:%d: Target atom = %s\n",
"tint2: DnD %s:%d: Target atom = %s\n",
__FILE__,
__LINE__,
GetAtomName(server.display, target));
@ -201,116 +338,102 @@ void handle_dnd_selection_notify(XSelectionEvent *e)
GetAtomName(server.display, e->property));
}
if (e->property != None && dnd_launcher_icon) {
Property prop = read_property(server.display, dnd_target_window, dnd_selection);
if (dnd_launcher_icon) {
Property prop = dnd_read_property(server.display, dnd_target_window, dnd_selection);
// If we're being given a list of targets (possible conversions)
if (target == server.atom.TARGETS && !dnd_sent_request) {
dnd_sent_request = 1;
dnd_atom = pick_target_from_targets(server.display, prop);
if (prop.data) {
// If we're being given a list of targets (possible conversions)
if (target == server.atom.TARGETS && !dnd_sent_request) {
dnd_sent_request = 1;
dnd_atom = dnd_pick_target_from_targets(server.display, prop);
if (dnd_atom == None) {
if (dnd_debug)
fprintf(stderr, "tint2: No matching datatypes.\n");
} else {
// Request the data type we are able to select
if (dnd_debug)
fprintf(stderr, "tint2: Now requsting type %s", GetAtomName(server.display, dnd_atom));
XConvertSelection(server.display,
dnd_selection,
dnd_atom,
dnd_selection,
dnd_target_window,
CurrentTime);
}
} else if (target == dnd_atom) {
// Dump the binary data
if (dnd_debug) {
fprintf(stderr, "tint2: DnD %s:%d: Data begins:\n", __FILE__, __LINE__);
fprintf(stderr, "tint2: --------\n");
for (int i = 0; i < prop.nitems * prop.format / 8; i++)
fprintf(stderr, "tint2: %c", ((char *)prop.data)[i]);
fprintf(stderr, "tint2: --------\n");
}
// TODO replace this with exec*(...)
int cmd_length = 0;
cmd_length += 1; // (
cmd_length += strlen(dnd_launcher_icon->cmd) + 1; // exec + space
cmd_length += 1; // open double quotes
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) {
cmd_length += 3; // close double quotes, space, open double quotes
}
} else if (c == '\r') {
// Nothing to do
if (dnd_atom == None) {
if (debug_dnd)
fprintf(stderr, "tint2: No matching datatypes.\n");
} else {
cmd_length += 1; // 1 character
if (c == '`' || c == '$' || c == '\\') {
cmd_length += 1; // escape with one backslash
}
// Request the data type we are able to select
if (debug_dnd)
fprintf(stderr, "tint2: Now requsting type %s", GetAtomName(server.display, dnd_atom));
XConvertSelection(server.display,
dnd_selection,
dnd_atom,
dnd_selection,
dnd_target_window,
CurrentTime);
}
}
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, "\" \"");
}
} else if (c == '\r') {
// Nothing to do
} else {
if (c == '`' || c == '$' || c == '\\') {
strcat(cmd, "\\");
}
char sc[2];
sc[0] = c;
sc[1] = '\0';
strcat(cmd, sc);
} else if (target == dnd_atom) {
// Dump the binary data
if (debug_dnd) {
fprintf(stderr, "tint2: DnD %s:%d: Received data:\n", __FILE__, __LINE__);
fprintf(stderr, "tint2: --------\n");
for (int i = 0; i < prop.nitems * prop.format / 8; i++)
fprintf(stderr, "%c", ((char *)prop.data)[i]);
fprintf(stderr, "tint2: --------\n");
}
}
strcat(cmd, "\"");
strcat(cmd, "&)");
if (dnd_debug)
fprintf(stderr, "tint2: DnD %s:%d: Running command: %s\n", __FILE__, __LINE__, cmd);
tint_exec(cmd,
NULL,
NULL,
e->time,
NULL,
0,
0,
dnd_launcher_icon->start_in_terminal,
dnd_launcher_icon->startup_notification);
free(cmd);
// Reply OK.
XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = server.display;
m.window = dnd_source_window;
m.message_type = server.atom.XdndFinished;
m.format = 32;
m.data.l[0] = dnd_target_window;
m.data.l[1] = 1;
m.data.l[2] = server.atom.XdndActionCopy; // We only ever copy.
XSendEvent(server.display, dnd_source_window, False, NoEventMask, (XEvent *)&m);
XSync(server.display, False);
// 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);
const char *atom_name = GetAtomName(server.display, prop.type);
if (strcasecmp(atom_name, "STRING") == 0 ||
strcasecmp(atom_name, "text/uri-list") == 0) {
GString *url = g_string_new("");
GString *prev_url = g_string_new("");
for (int i = 0; i < prop.nitems * prop.format / 8; i++) {
char c = ((char *)prop.data)[i];
if (c == '\n') {
// Many programs cannot handle this prefix
tint2_g_string_replace(url, "file://", "");
// Some programs put duplicates in the list, we remove them
if (strcmp(url->str, prev_url->str) != 0) {
g_string_append(cmd, " \"");
g_string_append(cmd, url->str);
g_string_append(cmd, "\"");
}
g_string_assign(prev_url, url->str);
g_string_assign(url, "");
} else if (c == '\r') {
// Nothing to do
} else {
if (c == '`' || c == '$' || c == '\\') {
g_string_append(url, "\\");
}
g_string_append_c(url, c);
}
}
g_string_free(url, TRUE);
g_string_free(prev_url, TRUE);
}
if (debug_dnd)
fprintf(stderr, "tint2: DnD %s:%d: Running command: %s\n", __FILE__, __LINE__, cmd->str);
tint_exec(cmd->str,
NULL,
NULL,
e->time,
NULL,
0,
0,
dnd_launcher_icon->start_in_terminal,
dnd_launcher_icon->startup_notification);
g_string_free(cmd, TRUE);
// Reply OK.
XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = server.display;
m.window = dnd_source_window;
m.message_type = server.atom.XdndFinished;
m.format = 32;
m.data.l[0] = dnd_target_window;
m.data.l[1] = 1;
m.data.l[2] = server.atom.XdndActionCopy; // We only ever copy.
XSendEvent(server.display, dnd_source_window, False, NoEventMask, (XEvent *)&m);
XSync(server.display, False);
}
XFree(prop.data);
}
XFree(prop.data);
}
}

View file

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

View file

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

View file

@ -131,110 +131,6 @@ const char *GetAtomName(Display *disp, Atom 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()
{
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.
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 {
int x;
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[1] = (char*)"-e";
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;
wordexp(command, &words, WRDE_DOOFFS | WRDE_SHOWERR);
words.we_wordv[0] = (char*)"sh";
words.we_wordv[1] = (char*)"-c";
execvp("sh", words.we_wordv);
execlp("sh", "sh", "-c", command, NULL);
fprintf(stderr, "tint2: Failed to execute %s\n", command);
#if HAVE_SN
if (startup_notifications && startup_notification && time) {