A Place Where No Dreams Come True...

Current Project: A very simple X11 Drag and Drop interface using the XDND protocol.

What Does This Code Do...

Implements X11 XDND protocol to negotiate with an anonymous source window as a method for providing user input to the destination window. I exclusively use this method to pass filename info from a File-Manager to a file processing application. I have no idea what it might yield outside of this context.

What Doesn't This Code Do...

Uh! Well it doesn't do much so it must not do a lot of things it should. Leaving it open for improvement I guess.

//-----------------------------------------------------------------------------
// xdndfile.c
//
//  X11 drag/drop processor implementation.
//
// Copyright (c) 2013 - No Fun Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//-----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//
//-----------------------------------------------------------------------------

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>

#include "xdndfile.h"
#include "report.h"

//-----------------------------------------------------------------------------

struct xdndatom {
  Atom  atom;
  char *name;
};

static struct xdndatom xdndatoms[] = {
  { 0, "XdndAware"                },
  { 0, "XdndSelection"            },
  { 0, "XdndEnter"                },
  { 0, "XdndLeave"                },
  { 0, "XdndPosition"             },
  { 0, "XdndDrop"                 },
  { 0, "XdndFinished"             },
  { 0, "XdndStatus"               },
  { 0, "XdndActionCopy"           },
  { 0, "XdndActionMove"           },
  { 0, "XdndActionLink"           },
  { 0, "XdndActionAsk"            },
  { 0, "XdndActionPrivate"        },
  { 0, "XdndTypeList"             },
  { 0, "XdndActionList"           },
  { 0, "XdndActionDescription"    },
  { 0, "text/uri-list"            },
  { 0, "UTF8_STRING"              },
  { 0, "COMPOUND_TEXT"            },
  { 0, "TEXT"                     },
  { 0, "STRING"                   },
  { 0, "text/plain;charset=utf-8" },
  { 0, "text/plain"               }
};

//-----------------------------------------------------------------------------

static char ascii_to_hex(char chr)
{
  char rtn = 0;
  char c;

  c = chr - '0';
  if(c >= 0)
  {
    if(c > 9 && c >= 16)
    {
      c -= (16 - 9);
    }
    if(c < 16)
    {
      rtn = c;
    }
  }
  report(dbglevl_verbose, "ascii_to_hex() returned %X.\n", rtn);
  return rtn;
}

//-----------------------------------------------------------------------------

static void xdnd_unescape_uri(char *uri)
{
  int x;
  int i;
  char c;
  // In-place UTF8 -> ASCII conversion.
  for(x = 0, i = 0; uri[x]; x++)
  {
    c = uri[x];
    if(c == '\%')
    {
      x++;
      if(uri[x])
      {
	c = ascii_to_hex(uri[x]) << 4;
	x++;
	if(uri[x])
	{
	  c |= ascii_to_hex(uri[x]);
	}
      }
    }
    uri[i++] = c;
  }
  uri[i] = '\0';
}

//-----------------------------------------------------------------------------

static const char *xdnd_get_atom_name(Atom atom)
{
  int i;
  char *name = "Unregistered Xdnd Atom";
  for(i = 0; i < sizeof(xdndatoms) / sizeof(struct xdndatom); i++)
  {
    if(xdndatoms[i].atom == atom)
    {
      name = xdndatoms[i].name;
      break;
    }
  }
  return name;
}

//-----------------------------------------------------------------------------

void xdnd_set_dnd_aware(pAWIdnd dnd)
{
  XChangeProperty(dnd->display, dnd->window, dnd->XdndAware, XA_ATOM, 32,
		  PropModeReplace, (unsigned char*)&dnd->version, 1);
  // typelist[n] must be a NULL terminated array.
  int n = 0;
  while(dnd->TypeList[n])
  {
    n++;
  };
  if(n)
  {
    XChangeProperty(dnd->display, dnd->window, dnd->XdndAware, XA_ATOM, 32,
		    PropModeAppend, (unsigned char*)dnd->TypeList, n);
  }
}

//-----------------------------------------------------------------------------

void xdnd_set_dnd_unaware(pAWIdnd dnd)
{
  XDeleteProperty(dnd->display, dnd->window, dnd->XdndAware);
}

//-----------------------------------------------------------------------------

void xdnd_init(Display *display, Window window, pAWIdnd dnd)
{
  int i;

  dnd->display               = display;
  dnd->window                = window;
  for(i = 0; i < sizeof(xdndatoms) / sizeof(struct xdndatom); i++)
  {
    xdndatoms[i].atom = XInternAtom(dnd->display, xdndatoms[i].name, False);
  }
  // FIXME: Synchronization probem.
  dnd->version               = XDND_VERSION;
  dnd->XdndAware             = xdndatoms[0].atom;
  dnd->XdndSelection         = xdndatoms[1].atom;
  dnd->XdndEnter             = xdndatoms[2].atom;
  dnd->XdndLeave             = xdndatoms[3].atom;
  dnd->XdndPosition          = xdndatoms[4].atom;
  dnd->XdndDrop              = xdndatoms[5].atom;
  dnd->XdndFinished          = xdndatoms[6].atom;
  dnd->XdndStatus            = xdndatoms[7].atom;
  dnd->XdndActionCopy        = xdndatoms[8].atom;
  dnd->XdndActionMove        = xdndatoms[9].atom;
  dnd->XdndActionLink        = xdndatoms[10].atom;
  dnd->XdndActionAsk         = xdndatoms[11].atom;
  dnd->XdndActionPrivate     = xdndatoms[12].atom;
  dnd->XdndTypeList          = xdndatoms[13].atom;
  dnd->XdndActionList        = xdndatoms[14].atom;
  dnd->XdndActionDescription = xdndatoms[15].atom;
  dnd->text_uri_list         = xdndatoms[16].atom;
  dnd->utf8_string           = xdndatoms[17].atom;
  dnd->compound_text         = xdndatoms[18].atom;
  dnd->text                  = xdndatoms[19].atom;
  dnd->string                = xdndatoms[20].atom;
  dnd->text_plain_utf_8      = xdndatoms[21].atom;
  dnd->text_plain            = xdndatoms[22].atom;
  
  dnd->TypeList[0] = dnd->XdndEnter;
  dnd->TypeList[1] = dnd->XdndPosition;
  dnd->TypeList[2] = dnd->XdndSelection;
  dnd->TypeList[3] = dnd->XdndStatus;
  dnd->TypeList[4] = dnd->XdndDrop;
  dnd->TypeList[5] = dnd->XdndFinished;
  dnd->TypeList[6] = dnd->XdndLeave;
  dnd->TypeList[7] = 0;
  xdnd_set_dnd_aware(dnd);
  dnd->TypeList[0] = 0;

  for(i = 0; i < sizeof(xdndatoms) / sizeof(struct xdndatom); i++)
  {
    if(xdndatoms[i].atom)
    {
      report(dbglevl_debug, "Registered % 6li  : %s.\n", xdndatoms[i].atom, xdndatoms[i].name);
    }
  }
}

//-----------------------------------------------------------------------------

void xdnd_get_type_list(pAWIdnd dnd)
{
  unsigned i;
  Atom *a;
  Atom  type              = 0;
  int   format            = 0;
  unsigned long count     = 0;
  unsigned long remaining = 0;
  unsigned char *data     = NULL;

  dnd->TypeList[0] = 0;

  XGetWindowProperty(dnd->display, dnd->src_window, dnd->XdndTypeList,
		     0, 0x8000000L,
		     False,
		     XA_ATOM,
		     &type, &format, &count, &remaining, &data);

  report(dbglevl_debug, "XdndTypeList said  : type=%li format=%i count=%li remaining=%li data=%08lX.\n",
	 type, format, count, remaining, (long int)data);

  // FIXME: Logic.
  if(type != XA_ATOM || format != 32 || count == 0 || !data || count > 32)
  {
    if(data)
    {
      XFree(data);
    }
    report(dbglevl_ERROR, "error : XGetWindowProperty failed in xdnd_get_type_list - dnd->XdndTypeList = %ld", dnd->XdndTypeList);
    return;
  }
  a = (Atom*)data;
  report(dbglevl_debug, " reported atoms...\n");
  for(i = 0; i < count; i++)
  {
    report(dbglevl_debug, "% 18li : %s.\n", a[i], xdnd_get_atom_name(a[i]));
    dnd->TypeList[i] = a[i];
  }
  //report(dbglevl_debug, ".\n");
  dnd->TypeList[i] = 0;
  XFree (data);
  return;
}

//-----------------------------------------------------------------------------

void xdnd_convert_selection(pAWIdnd dnd, XEvent *pevent, Atom want_selection)
{
  report(dbglevl_debug, "Replied want       : %s.\n", xdnd_get_atom_name(want_selection));

  XConvertSelection(dnd->display,
		    dnd->XdndSelection,
		    want_selection,
		    XInternAtom(dnd->display, "PRIMARY", 0),
		    dnd->window,
		    pevent->xclient.data.l[2]);
}

//-----------------------------------------------------------------------------

void xdnd_get_selection(pAWIdnd dnd, XEvent *pevent, Atom want_selection, char **pdata)
{
  unsigned i;
  Atom type    = 0;
  int  format  = 0;
  unsigned long  count     = 0;
  unsigned long  remaining = 0;
  unsigned char *selection = NULL;

  char *data;
  *pdata = NULL;

  if(pevent->xselection.target == want_selection)
  {
    XGetWindowProperty(dnd->display,
		       dnd->window,
		       pevent->xselection.property,
		       0,
		       256, //sizeof(pdrop) / 4, // 32-bit quantities.
		       False,
		       want_selection,
		       &type,
		       &format,
		       &count,
		       &remaining,
		       &selection);

    report(dbglevl_debug, "Get XdndSelection...\n");
    report(dbglevl_debug, " window            : %08lX.\n", dnd->window);
    report(dbglevl_debug, " requesting type   : %s.\n", xdnd_get_atom_name(want_selection));
    report(dbglevl_debug, " suggested type    : %s.\n", xdnd_get_atom_name(type));
    report(dbglevl_debug, " format            : %i.\n", format);
    report(dbglevl_debug, " items             : %li.\n", count);
    report(dbglevl_debug, " remaining         : %li.\n", remaining);

    if(selection)
    {
      report(dbglevl_debug, "  data raw         : %02X %02X %02X %02X...%02X %02X %02X %02X.\n",
	     selection[0], selection[1], selection[2], selection[3],
	     selection[count - 4], selection[count - 3], selection[count - 2], selection[count - 1]);
      if((type == want_selection) && (format == 8) && count)
      {
	// Allocate for 'remaining' but ignore it... for now.
	data = malloc(count + remaining + 4);
	if(data)
	{
	  int nofst = 0;
	  report(dbglevl_debug, "  received data    : %s", selection);
	  // FIXME: Not complient.
	  if(strncmp((char*)selection, "file://", 7) == 0)
	  {
	    nofst = 7;
	  }
	  memcpy(data, &selection[nofst], count - nofst);
	  data[count - nofst] = 0;
	  i = 1;
	  while((data[(count - nofst) - i] == 0x0D) || (data[(count - nofst) - i] == 0x0A))
	  {
	    i++;
	  };
	  if(i < (count - nofst))
	  {
	    data[(count - nofst) - (i - 1)] = 0;
	  }
	  xdnd_unescape_uri(data);
	  report(dbglevl_debug, "\n  adjusted data    : %s\n", data);
	  *pdata = data;
	}
      }
      XFree(selection);
    }
    else
    {
      report(dbglevl_debug, "  data raw         : null-pointer.\n");
    }
  }
  return;
}

//-----------------------------------------------------------------------------

void xdnd_recv_enter(pAWIdnd dnd, XEvent *pevent)
{
  dnd->src_window  = pevent->xclient.data.l[0];
  dnd->src_version = pevent->xclient.data.l[1] >> 24;
  dnd->time = 0; // reset.

  report(dbglevl_debug, "Received XdndEnter...\n");
  report(dbglevl_debug, " source window XID : %08lX.\n", dnd->src_window);
  report(dbglevl_debug, " version           : %i.\n", dnd->src_version);
  report(dbglevl_debug, " time stamp        : %li.\n", dnd->time);
  report(dbglevl_debug, " requires TypeList : %li.\n", pevent->xclient.data.l[1] & 0x01);

  if(pevent->xclient.data.l[1] & 0x01)
  {
    xdnd_get_type_list(dnd);
  }
  else
  {
    dnd->TypeList[0] = pevent->xclient.data.l[2];
    dnd->TypeList[1] = pevent->xclient.data.l[3];
    dnd->TypeList[2] = pevent->xclient.data.l[4];
    dnd->TypeList[3] = 0;
  }
}

//-----------------------------------------------------------------------------

void xdnd_recv_position(pAWIdnd dnd, XEvent *pevent)
{
  report(dbglevl_debug, "Received XdndPosition...\n");
  report(dbglevl_debug, " source window XID : %08lX.\n", pevent->xclient.data.l[0]);
  report(dbglevl_debug, " root coordinates  : X=%li y=%li.\n",
	 pevent->xclient.data.l[2] >> 16, pevent->xclient.data.l[2] & 0xFFFF);
  report(dbglevl_debug, " time stamp        : %li.\n", pevent->xclient.data.l[3]);
  report(dbglevl_debug, " action suggest    : %s.\n", xdnd_get_atom_name((Atom)(pevent->xclient.data.l[4])));

  if(dnd->time == 0)
  {
    dnd->time = pevent->xclient.data.l[3];
  }
}

//-----------------------------------------------------------------------------

void xdnd_recv_leave(pAWIdnd dnd, XEvent *pevent)
{
  report(dbglevl_debug, "Received XdndLeave...\n");
  report(dbglevl_debug, " source window XID : %08lX.\n", pevent->xclient.data.l[0]);
}

//-----------------------------------------------------------------------------

void xdnd_recv_drop(pAWIdnd dnd, XEvent *pevent)
{
  dnd->src_window = pevent->xclient.data.l[0];

  report(dbglevl_debug, "Received XdndDrop...\n");
  report(dbglevl_debug, " source window XID : %08lX.\n", dnd->src_window);
  report(dbglevl_debug, " time stamp        : %li.\n", pevent->xclient.data.l[2]);
}

//-----------------------------------------------------------------------------

void xdnd_recv_status(pAWIdnd dnd, XEvent *pevent)
{
  report(dbglevl_debug, "Received XdndStatus...\n");
  report(dbglevl_debug, " source window XID : %08lX.\n", pevent->xclient.data.l[0]);
  report(dbglevl_debug, " will accept       : %s.\n", pevent->xclient.data.l[1] & 0x1 ? "yes" : "no");
  report(dbglevl_debug, " want Position     : %s.\n", pevent->xclient.data.l[1] & 0x2 ? "yes" : "no");
  report(dbglevl_debug, " root coordinates  : X=%li y=%li width=%li height=%li.\n",
	 pevent->xclient.data.l[2] >> 16, pevent->xclient.data.l[2] & 0xFFFF,
	 pevent->xclient.data.l[3] >> 16, pevent->xclient.data.l[3] & 0xFFFF);
  report(dbglevl_debug, " action            : %s.\n", xdnd_get_atom_name((Atom)(pevent->xclient.data.l[4])));
}

//-----------------------------------------------------------------------------

void xdnd_send_status(pAWIdnd dnd, int will_accept, int want_position,
		      unsigned x, unsigned y, unsigned w, unsigned h,
		      Atom action)
{
  XEvent xevent;

  memset(&xevent, 0, sizeof(xevent));

  xevent.xany.type            = ClientMessage;
  xevent.xany.display         = dnd->display;
  xevent.xclient.window       = dnd->src_window;
  xevent.xclient.message_type = dnd->XdndStatus;
  xevent.xclient.format       = 32;

  xevent.xclient.data.l[0] = dnd->window;
  if(will_accept)
  {
    xevent.xclient.data.l[1] |= 0x01;
    if(want_position)
    {
      xevent.xclient.data.l[1] |= 0x02;
    }
    xevent.xclient.data.l[2] = (x << 16) | (y & 0xFFFF);
    xevent.xclient.data.l[3] = (w << 16) | (h & 0xFFFF);
    xevent.xclient.data.l[4] = action;
  }
  report(dbglevl_debug, "Sent XdndStatus...\n");
  report(dbglevl_debug, " source window XID : %08lX.\n", xevent.xclient.data.l[0]);
  report(dbglevl_debug, " will accept       : %s.\n", xevent.xclient.data.l[1] & 0x1 ? "yes" : "no");
  report(dbglevl_debug, " want Position     : %s.\n", xevent.xclient.data.l[1] & 0x2 ? "yes" : "no");
  report(dbglevl_debug, " root coordinates  : X=%li y=%li width=%li height=%li.\n",
	 xevent.xclient.data.l[2] >> 16, xevent.xclient.data.l[2] & 0xFFFF,
	 xevent.xclient.data.l[3] >> 16, xevent.xclient.data.l[3] & 0xFFFF);
  report(dbglevl_debug, " want action       : %s.\n", xdnd_get_atom_name((Atom)(xevent.xclient.data.l[4])));

  XSendEvent(dnd->display, dnd->src_window, 0, 0, &xevent);
}

//-----------------------------------------------------------------------------

void xdnd_send_finished(pAWIdnd dnd, Atom action)
{
  XEvent xevent;

  memset(&xevent, 0, sizeof(xevent));

  xevent.xany.type            = ClientMessage;
  xevent.xany.display         = dnd->display;
  xevent.xclient.window       = dnd->src_window;
  xevent.xclient.message_type = dnd->XdndFinished;
  xevent.xclient.format       = 32;
  xevent.xclient.data.l[0]    = dnd->window;
  xevent.xclient.data.l[1]    = 1;
  xevent.xclient.data.l[2]    = action;

  XSendEvent(dnd->display, dnd->src_window, 0, 0, &xevent);

  report(dbglevl_debug, "Replied XdndFinished...\n");
  report(dbglevl_debug, " window            : %08lX.\n", dnd->src_window);
  report(dbglevl_debug, " Action            : %s.\n", xdnd_get_atom_name(action));
} 

//-----------------------------------------------------------------------------
// end: xdndfile.c
//

  

And The Companion Header...

//-----------------------------------------------------------------------------
// xdndfile.h
//
//  X11 drag/drop processor definition.
//
// Copyright (c) 2013 - No Fun Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//-----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//
//-----------------------------------------------------------------------------

#define XDND_VERSION 3

//-----------------------------------------------------------------------------

typedef struct {
  Display *display;
  Window window;
  Window src_window;
  int src_version;
  time_t time;
  Atom version;               //= XDND_VERSION;
  Atom XdndAware;             //= XInternAtom(dnd->display, "XdndAware", False);
  Atom XdndSelection;         //= XInternAtom(dnd->display, "XdndSelection", False);
  Atom XdndEnter;             //= XInternAtom(dnd->display, "XdndEnter", False);
  Atom XdndLeave;             //= XInternAtom(dnd->display, "XdndLeave", False);
  Atom XdndPosition;          //= XInternAtom(dnd->display, "XdndPosition", False);
  Atom XdndDrop;              //= XInternAtom(dnd->display, "XdndDrop", False);
  Atom XdndFinished;          //= XInternAtom(dnd->display, "XdndFinished", False);
  Atom XdndStatus;            //= XInternAtom(dnd->display, "XdndStatus", False);
  Atom XdndActionCopy;        //= XInternAtom(dnd->display, "XdndActionCopy", False);
  Atom XdndActionMove;        //= XInternAtom(dnd->display, "XdndActionMove", False);
  Atom XdndActionLink;        //= XInternAtom(dnd->display, "XdndActionLink", False);
  Atom XdndActionAsk;         //= XInternAtom(dnd->display, "XdndActionAsk", False);
  Atom XdndActionPrivate;     //= XInternAtom(dnd->display, "XdndActionPrivate",False);
  Atom XdndTypeList;          //= XInternAtom(dnd->display, "XdndTypeList", False);
  Atom XdndActionList;        //= XInternAtom(dnd->display, "XdndActionList", False);
  Atom XdndActionDescription; //= XInternAtom(dnd->display, "XdndActionDescription", False);
  Atom text_uri_list;         //= XInternAtom(dnd->display, "text/uri-list", False);
  Atom utf8_string;           //= XInternAtom(dnd->display, "UTF8-STRING", False);
  Atom compound_text;         //= XInternAtom(dnd->display, "COMPOUND_TEXT", False);
  Atom text;                  //= XInternAtom(dnd->display, "TEXT", False);
  Atom string;                //= XInternAtom(dnd->display, "STRING", False);
  Atom text_plain_utf_8;      //= XInternAtom(dnd->display, "text/plain;charset=utf-8", False);
  Atom text_plain;            //= XInternAtom(dnd->display, "text/plain", False);
  Atom TypeList[64];
} AWIdnd, *pAWIdnd;

//-----------------------------------------------------------------------------

extern void xdnd_init(Display*, Window, pAWIdnd);
extern void xdnd_set_dnd_aware(pAWIdnd);
extern void xdnd_set_dnd_unaware(pAWIdnd);
extern void xdnd_get_type_list(pAWIdnd);
extern void xdnd_convert_selection(pAWIdnd, XEvent*, Atom);
extern void xdnd_get_selection(pAWIdnd, XEvent*, Atom, char**);
extern void xdnd_recv_enter(pAWIdnd, XEvent*);
extern void xdnd_recv_position(pAWIdnd, XEvent*);
extern void xdnd_recv_leave(pAWIdnd, XEvent*);
extern void xdnd_recv_drop(pAWIdnd, XEvent*);
extern void xdnd_recv_status(pAWIdnd, XEvent*);
extern void xdnd_send_status(pAWIdnd, int, int, unsigned, unsigned, unsigned, unsigned, Atom);
extern void xdnd_send_finished(pAWIdnd, Atom);

//-----------------------------------------------------------------------------
// end: xdndfile.h
//

  

The Code Demonstrates The Following Programming Techniques...

Conclusion...

It is known that this code has many 'Freshman' mistakes in it. It is not claimed to be good code. Hopefully though; it demonstrates some of the less obvious interface mechanisms when communicating with both a Window-Manager and a Client program. A definition of how to use the code is also missing. Not purposely, an interface spec just can't be fit into my schedule. For now. Besides... This code is not really meant to be used. Although I use this code it is presented here purely for educational purposes.

6988