gdkdnd-x11.c 117 KB
Newer Older
1
/* GDK - The GIMP Drawing Kit
2
 * Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald
3 4
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6 7 8 9 10 11
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15 16 17 18
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
19 20

/*
21
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22 23 24 25 26
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
 */

27
#include "config.h"
28

29
#include "gdkx11dnd.h"
30
#include "gdkdndprivate.h"
Owen Taylor's avatar
Owen Taylor committed
31

32
#include "gdkmain.h"
Matthias Clasen's avatar
Matthias Clasen committed
33
#include "gdkinternals.h"
34
#include "gdkasync.h"
Owen Taylor's avatar
Owen Taylor committed
35
#include "gdkproperty.h"
36
#include "gdkprivate-x11.h"
37 38
#include "gdkscreen-x11.h"
#include "gdkdisplay-x11.h"
39

40 41 42 43 44 45 46 47
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>
#include <X11/extensions/Xcomposite.h>

#include <string.h>

48 49 50 51 52 53 54 55 56 57 58
typedef enum {
  GDK_DRAG_STATUS_DRAG,
  GDK_DRAG_STATUS_MOTION_WAIT,
  GDK_DRAG_STATUS_ACTION_WAIT,
  GDK_DRAG_STATUS_DROP
} GtkDragStatus;

typedef struct {
  guint32 xid;
  gint x, y, width, height;
  gboolean mapped;
59 60
  gboolean shape_selected;
  gboolean shape_valid;
61
  cairo_region_t *shape;
62 63 64 65 66 67
} GdkCacheChild;

typedef struct {
  GList *children;
  GHashTable *child_hash;
  guint old_event_mask;
68
  GdkScreen *screen;
69 70
} GdkWindowCache;

Matthias Clasen's avatar
Matthias Clasen committed
71

72 73
struct _GdkX11DragContext
{
74 75
  GdkDragContext context;

76
  Atom motif_selection;
77 78
  guint   ref_count;

79
  guint16 last_x;              /* Coordinates from last event */
80
  guint16 last_y;
81 82 83 84 85 86 87 88 89 90 91 92 93 94
  GdkDragAction old_action;    /* The last action we sent to the source */
  GdkDragAction old_actions;   /* The last actions we sent to the source */
  GdkDragAction xdnd_actions;  /* What is currently set in XdndActionList */

  Window dest_xid;             /* The last window we looked up */
  Window drop_xid;             /* The (non-proxied) window that is receiving drops */
  guint xdnd_targets_set  : 1; /* Whether we've already set XdndTypeList */
  guint xdnd_actions_set  : 1; /* Whether we've already set XdndActionList */
  guint xdnd_have_actions : 1; /* Whether an XdndActionList was provided */
  guint motif_targets_set : 1; /* Whether we've already set motif initiator info */
  guint drag_status       : 4; /* current status of drag */

  guint drop_failed       : 1; /* Whether the drop was unsuccessful */
  guint version;               /* Xdnd protocol version */
95

96
  GSList *window_caches;
97 98
};

99 100 101 102 103
struct _GdkX11DragContextClass
{
  GdkDragContextClass parent_class;
};

104 105 106 107
/* Forward declarations */

static void gdk_window_cache_destroy (GdkWindowCache *cache);

108 109
static void motif_read_target_table (GdkDisplay *display);

110
static GdkFilterReturn motif_dnd_filter (GdkXEvent *xev,
111 112
                                         GdkEvent  *event,
                                         gpointer   data);
113

114
static GdkFilterReturn xdnd_enter_filter    (GdkXEvent *xev,
115 116
                                             GdkEvent  *event,
                                             gpointer   data);
117
static GdkFilterReturn xdnd_leave_filter    (GdkXEvent *xev,
118 119
                                             GdkEvent  *event,
                                             gpointer   data);
120
static GdkFilterReturn xdnd_position_filter (GdkXEvent *xev,
121 122
                                             GdkEvent  *event,
                                             gpointer   data);
123
static GdkFilterReturn xdnd_status_filter   (GdkXEvent *xev,
124 125
                                             GdkEvent  *event,
                                             gpointer   data);
126
static GdkFilterReturn xdnd_finished_filter (GdkXEvent *xev,
127 128
                                             GdkEvent  *event,
                                             gpointer   data);
129
static GdkFilterReturn xdnd_drop_filter     (GdkXEvent *xev,
130 131
                                             GdkEvent  *event,
                                             gpointer   data);
132

133
static void   xdnd_manage_source_filter (GdkDragContext *context,
134 135
                                         GdkWindow      *window,
                                         gboolean        add_filter);
136

137 138
static GList *contexts;

Matthias Clasen's avatar
Matthias Clasen committed
139
static const struct {
140 141
  const char *atom_name;
  GdkFilterFunc func;
Matthias Clasen's avatar
Matthias Clasen committed
142
} xdnd_filters[] = {
143 144 145 146 147 148 149
  { "XdndEnter",    xdnd_enter_filter },
  { "XdndLeave",    xdnd_leave_filter },
  { "XdndPosition", xdnd_position_filter },
  { "XdndStatus",   xdnd_status_filter },
  { "XdndFinished", xdnd_finished_filter },
  { "XdndDrop",     xdnd_drop_filter },
};
150 151


152
G_DEFINE_TYPE (GdkX11DragContext, gdk_x11_drag_context, GDK_TYPE_DRAG_CONTEXT)
153

Matthias Clasen's avatar
Matthias Clasen committed
154
static void
155
gdk_x11_drag_context_init (GdkX11DragContext *context)
Matthias Clasen's avatar
Matthias Clasen committed
156 157 158 159
{
  contexts = g_list_prepend (contexts, context);
}

160 161
static void        gdk_x11_drag_context_finalize (GObject *object);
static GdkWindow * gdk_x11_drag_context_find_window (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
162 163 164 165 166
                                                     GdkWindow       *drag_window,
                                                     GdkScreen       *screen,
                                                     gint             x_root,
                                                     gint             y_root,
                                                     GdkDragProtocol *protocol);
167
static gboolean    gdk_x11_drag_context_drag_motion (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
168 169 170 171 172 173 174
                                                     GdkWindow       *dest_window,
                                                     GdkDragProtocol  protocol,
                                                     gint             x_root,
                                                     gint             y_root,
                                                     GdkDragAction    suggested_action,
                                                     GdkDragAction    possible_actions,
                                                     guint32          time);
175
static void        gdk_x11_drag_context_drag_status (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
176 177
                                                     GdkDragAction    action,
                                                     guint32          time_);
178
static void        gdk_x11_drag_context_drag_abort  (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
179
                                                     guint32          time_);
180
static void        gdk_x11_drag_context_drag_drop   (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
181
                                                     guint32          time_);
182
static void        gdk_x11_drag_context_drop_reply  (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
183 184
                                                     gboolean         accept,
                                                     guint32          time_);
185
static void        gdk_x11_drag_context_drop_finish (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
186 187
                                                     gboolean         success,
                                                     guint32          time_);
188 189
static gboolean    gdk_x11_drag_context_drop_status (GdkDragContext  *context);
static GdkAtom     gdk_x11_drag_context_get_selection (GdkDragContext  *context);
190

191
static void
192
gdk_x11_drag_context_class_init (GdkX11DragContextClass *klass)
193
{
194
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
Matthias Clasen's avatar
Matthias Clasen committed
195
  GdkDragContextClass *context_class = GDK_DRAG_CONTEXT_CLASS (klass);
196

197
  object_class->finalize = gdk_x11_drag_context_finalize;
Matthias Clasen's avatar
Matthias Clasen committed
198

199 200 201 202 203 204 205 206 207
  context_class->find_window = gdk_x11_drag_context_find_window;
  context_class->drag_status = gdk_x11_drag_context_drag_status;
  context_class->drag_motion = gdk_x11_drag_context_drag_motion;
  context_class->drag_abort = gdk_x11_drag_context_drag_abort;
  context_class->drag_drop = gdk_x11_drag_context_drag_drop;
  context_class->drop_reply = gdk_x11_drag_context_drop_reply;
  context_class->drop_finish = gdk_x11_drag_context_drop_finish;
  context_class->drop_status = gdk_x11_drag_context_drop_status;
  context_class->get_selection = gdk_x11_drag_context_get_selection;
208 209
}

210
static void
211
gdk_x11_drag_context_finalize (GObject *object)
212
{
213
  GdkX11DragContext *context_x11 = GDK_X11_DRAG_CONTEXT (object);
214 215 216
  GdkDragContext *context = GDK_DRAG_CONTEXT (object);

  if (context->source_window)
217
    {
Matthias Clasen's avatar
Matthias Clasen committed
218
      if ((context->protocol == GDK_DRAG_PROTO_XDND) && !context->is_source)
219 220
        xdnd_manage_source_filter (context, context->source_window, FALSE);
    }
221

Matthias Clasen's avatar
Matthias Clasen committed
222 223
  g_slist_free_full (context_x11->window_caches, (GDestroyNotify)gdk_window_cache_destroy);

224
  contexts = g_list_remove (contexts, context);
225

226
  G_OBJECT_CLASS (gdk_x11_drag_context_parent_class)->finalize (object);
227
}
228

229 230
/* Drag Contexts */

231
static GdkDragContext *
232
gdk_drag_context_find (GdkDisplay *display,
233 234 235
                       gboolean    is_source,
                       Window      source_xid,
                       Window      dest_xid)
236 237
{
  GList *tmp_list = contexts;
Jeff Garzik's avatar
Jeff Garzik committed
238
  GdkDragContext *context;
239
  GdkX11DragContext *context_x11;
240
  Window context_dest_xid;
241 242 243

  while (tmp_list)
    {
Jeff Garzik's avatar
Jeff Garzik committed
244
      context = (GdkDragContext *)tmp_list->data;
245
      context_x11 = (GdkX11DragContext *)context;
246

247
      if ((context->source_window && gdk_window_get_display (context->source_window) != display) ||
248
          (context->dest_window && gdk_window_get_display (context->dest_window) != display))
Matthias Clasen's avatar
Matthias Clasen committed
249
        continue;
250

Matthias Clasen's avatar
Matthias Clasen committed
251 252 253 254 255
      context_dest_xid = context->dest_window
                            ? (context_x11->drop_xid
                                  ? context_x11->drop_xid
                                  : GDK_WINDOW_XID (context->dest_window))
                            : None;
256 257

      if ((!context->is_source == !is_source) &&
Matthias Clasen's avatar
Matthias Clasen committed
258 259 260 261 262
          ((source_xid == None) || (context->source_window &&
            (GDK_WINDOW_XID (context->source_window) == source_xid))) &&
          ((dest_xid == None) || (context_dest_xid == dest_xid)))
        return context;

263 264
      tmp_list = tmp_list->next;
    }
Matthias Clasen's avatar
Matthias Clasen committed
265

266 267 268
  return NULL;
}

269 270 271 272 273 274 275 276 277 278
static void
precache_target_list (GdkDragContext *context)
{
  if (context->targets)
    {
      GPtrArray *targets = g_ptr_array_new ();
      GList *tmp_list;
      int i;

      for (tmp_list = context->targets; tmp_list; tmp_list = tmp_list->next)
279
        g_ptr_array_add (targets, gdk_atom_name (GDK_POINTER_TO_ATOM (tmp_list->data)));
280 281

      _gdk_x11_precache_atoms (GDK_WINDOW_DISPLAY (context->source_window),
282 283
                               (const gchar **)targets->pdata,
                               targets->len);
284 285

      for (i =0; i < targets->len; i++)
286
        g_free (targets->pdata[i]);
287 288 289 290 291

      g_ptr_array_free (targets, TRUE);
    }
}

292 293
/* Utility functions */

294 295 296 297 298
static void
free_cache_child (GdkCacheChild *child,
                  GdkDisplay    *display)
{
  if (child->shape)
Benjamin Otte's avatar
Benjamin Otte committed
299
    cairo_region_destroy (child->shape);
300 301 302

  if (child->shape_selected && display)
    {
303
      GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
304 305 306 307 308 309 310

      XShapeSelectInput (display_x11->xdisplay, child->xid, 0);
    }

  g_free (child);
}

311 312
static void
gdk_window_cache_add (GdkWindowCache *cache,
313 314 315 316 317 318
                      guint32         xid,
                      gint            x,
                      gint            y,
                      gint            width,
                      gint            height,
                      gboolean        mapped)
319 320 321 322 323 324 325 326 327
{
  GdkCacheChild *child = g_new (GdkCacheChild, 1);

  child->xid = xid;
  child->x = x;
  child->y = y;
  child->width = width;
  child->height = height;
  child->mapped = mapped;
328 329 330
  child->shape_selected = FALSE;
  child->shape_valid = FALSE;
  child->shape = NULL;
331 332

  cache->children = g_list_prepend (cache->children, child);
333 334
  g_hash_table_insert (cache->child_hash, GUINT_TO_POINTER (xid),
                       cache->children);
335 336
}

337 338 339 340 341 342 343 344
static GdkFilterReturn
gdk_window_cache_shape_filter (GdkXEvent *xev,
                               GdkEvent  *event,
                               gpointer   data)
{
  XEvent *xevent = (XEvent *)xev;
  GdkWindowCache *cache = data;

345
  GdkX11Display *display = GDK_X11_DISPLAY (gdk_screen_get_display (cache->screen));
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360

  if (display->have_shapes &&
      xevent->type == display->shape_event_base + ShapeNotify)
    {
      XShapeEvent *xse = (XShapeEvent*)xevent;
      GList *node;

      node = g_hash_table_lookup (cache->child_hash,
                                  GUINT_TO_POINTER (xse->window));
      if (node)
        {
          GdkCacheChild *child = node->data;
          child->shape_valid = FALSE;
          if (child->shape)
            {
Benjamin Otte's avatar
Benjamin Otte committed
361
              cairo_region_destroy (child->shape);
362 363 364 365 366 367 368 369 370 371
              child->shape = NULL;
            }
        }

      return GDK_FILTER_REMOVE;
    }

  return GDK_FILTER_CONTINUE;
}

372 373
static GdkFilterReturn
gdk_window_cache_filter (GdkXEvent *xev,
374 375
                         GdkEvent  *event,
                         gpointer   data)
376 377 378 379 380 381 382 383 384 385
{
  XEvent *xevent = (XEvent *)xev;
  GdkWindowCache *cache = data;

  switch (xevent->type)
    {
    case CirculateNotify:
      break;
    case ConfigureNotify:
      {
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
        XConfigureEvent *xce = &xevent->xconfigure;
        GList *node;

        node = g_hash_table_lookup (cache->child_hash,
                                    GUINT_TO_POINTER (xce->window));
        if (node)
          {
            GdkCacheChild *child = node->data;
            child->x = xce->x;
            child->y = xce->y;
            child->width = xce->width;
            child->height = xce->height;
            if (xce->above == None && (node->next))
              {
                GList *last = g_list_last (cache->children);
                cache->children = g_list_remove_link (cache->children, node);
                last->next = node;
                node->next = NULL;
                node->prev = last;
              }
            else
              {
                GList *above_node = g_hash_table_lookup (cache->child_hash,
                                                         GUINT_TO_POINTER (xce->above));
                if (above_node && node->next != above_node)
                  {
                    /* Put the window above (before in the list) above_node */
                    cache->children = g_list_remove_link (cache->children, node);
                    node->prev = above_node->prev;
                    if (node->prev)
                      node->prev->next = node;
                    else
                      cache->children = node;
                    node->next = above_node;
                    above_node->prev = node;
                  }
              }
          }
        break;
425 426 427
      }
    case CreateNotify:
      {
428 429 430 431 432 433 434 435
        XCreateWindowEvent *xcwe = &xevent->xcreatewindow;

        if (!g_hash_table_lookup (cache->child_hash,
                                  GUINT_TO_POINTER (xcwe->window)))
          gdk_window_cache_add (cache, xcwe->window,
                                xcwe->x, xcwe->y, xcwe->width, xcwe->height,
                                FALSE);
        break;
436 437 438
      }
    case DestroyNotify:
      {
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
        XDestroyWindowEvent *xdwe = &xevent->xdestroywindow;
        GList *node;

        node = g_hash_table_lookup (cache->child_hash,
                                    GUINT_TO_POINTER (xdwe->window));
        if (node)
          {
            GdkCacheChild *child = node->data;

            g_hash_table_remove (cache->child_hash,
                                 GUINT_TO_POINTER (xdwe->window));
            cache->children = g_list_remove_link (cache->children, node);
            /* window is destroyed, no need to disable ShapeNotify */
            free_cache_child (child, NULL);
            g_list_free_1 (node);
          }
        break;
456 457 458
      }
    case MapNotify:
      {
459 460 461 462 463 464 465 466 467 468 469
        XMapEvent *xme = &xevent->xmap;
        GList *node;

        node = g_hash_table_lookup (cache->child_hash,
                                    GUINT_TO_POINTER (xme->window));
        if (node)
          {
            GdkCacheChild *child = node->data;
            child->mapped = TRUE;
          }
        break;
470 471 472 473 474
      }
    case ReparentNotify:
      break;
    case UnmapNotify:
      {
475 476 477 478 479 480 481 482 483 484 485
        XMapEvent *xume = &xevent->xmap;
        GList *node;

        node = g_hash_table_lookup (cache->child_hash,
                                    GUINT_TO_POINTER (xume->window));
        if (node)
          {
            GdkCacheChild *child = node->data;
            child->mapped = FALSE;
          }
        break;
486 487 488 489 490 491 492 493
      }
    default:
      return GDK_FILTER_CONTINUE;
    }
  return GDK_FILTER_REMOVE;
}

static GdkWindowCache *
494
gdk_window_cache_new (GdkScreen *screen)
495 496
{
  XWindowAttributes xwa;
497 498
  Display *xdisplay = GDK_SCREEN_XDISPLAY (screen);
  GdkWindow *root_window = gdk_screen_get_root_window (screen);
499 500
  GdkChildInfoX11 *children;
  guint nchildren, i;
501
  Window cow;
502

503 504 505 506
  GdkWindowCache *result = g_new (GdkWindowCache, 1);

  result->children = NULL;
  result->child_hash = g_hash_table_new (g_direct_hash, NULL);
507
  result->screen = screen;
508

509
  XGetWindowAttributes (xdisplay, GDK_WINDOW_XID (root_window), &xwa);
510
  result->old_event_mask = xwa.your_event_mask;
511

512
  if (G_UNLIKELY (!GDK_X11_DISPLAY (GDK_X11_SCREEN (screen)->display)->trusted_client))
513 514 515 516
    {
      GList *toplevel_windows, *list;
      GdkWindow *window;
      gint x, y, width, height;
517

518
      toplevel_windows = gdk_screen_get_toplevel_windows (screen);
519 520 521 522 523 524 525 526
      for (list = toplevel_windows; list; list = list->next)
        {
          window = GDK_WINDOW (list->data);
          gdk_window_get_geometry (window, &x, &y, &width, &height);
          gdk_window_cache_add (result, GDK_WINDOW_XID (window),
                                x, y, width, height,
                                gdk_window_is_visible (window));
        }
527 528 529 530
      g_list_free (toplevel_windows);
      return result;
    }

531
  XSelectInput (xdisplay, GDK_WINDOW_XID (root_window),
532
                result->old_event_mask | SubstructureNotifyMask);
533
  gdk_window_add_filter (root_window, gdk_window_cache_filter, result);
534
  gdk_window_add_filter (NULL, gdk_window_cache_shape_filter, result);
535

536
  if (!_gdk_x11_get_window_child_info (gdk_screen_get_display (screen),
537 538 539
                                       GDK_WINDOW_XID (root_window),
                                       FALSE, NULL,
                                       &children, &nchildren))
540
    return result;
541

542 543
  for (i = 0; i < nchildren ; i++)
    {
544
      gdk_window_cache_add (result, children[i].window,
545 546
                            children[i].x, children[i].y, children[i].width, children[i].height,
                            children[i].is_mapped);
547 548
    }

549
  g_free (children);
550

551
#ifdef HAVE_XCOMPOSITE
552 553 554 555 556 557 558 559 560
  /*
   * Add the composite overlay window to the cache, as this can be a reasonable
   * Xdnd proxy as well.
   * This is only done when the screen is composited in order to avoid mapping
   * the COW. We assume that the CM is using the COW (which is true for pretty
   * much any CM currently in use).
   */
  if (gdk_screen_is_composited (screen))
    {
561
      cow = XCompositeGetOverlayWindow (xdisplay, GDK_WINDOW_XID (root_window));
562
      gdk_window_cache_add (result, cow, 0, 0, gdk_screen_get_width (screen), gdk_screen_get_height (screen), TRUE);
563
      XCompositeReleaseOverlayWindow (xdisplay, GDK_WINDOW_XID (root_window));
564
    }
565
#endif
566

567 568 569 570 571 572
  return result;
}

static void
gdk_window_cache_destroy (GdkWindowCache *cache)
{
573
  GdkWindow *root_window = gdk_screen_get_root_window (cache->screen);
574
  GdkDisplay *display;
575 576

  XSelectInput (GDK_WINDOW_XDISPLAY (root_window),
577 578
                GDK_WINDOW_XID (root_window),
                cache->old_event_mask);
579
  gdk_window_remove_filter (root_window, gdk_window_cache_filter, cache);
580
  gdk_window_remove_filter (NULL, gdk_window_cache_shape_filter, cache);
581

582
  display = gdk_screen_get_display (cache->screen);
583

584 585 586
  gdk_x11_display_error_trap_push (display);
  g_list_foreach (cache->children, (GFunc)free_cache_child, display);
  gdk_x11_display_error_trap_pop_ignored (display);
587

588 589
  g_list_free (cache->children);
  g_hash_table_destroy (cache->child_hash);
590 591

  g_free (cache);
592 593
}

594 595 596 597 598 599 600 601
static gboolean
is_pointer_within_shape (GdkDisplay    *display,
                         GdkCacheChild *child,
                         gint           x_pos,
                         gint           y_pos)
{
  if (!child->shape_selected)
    {
602
      GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
603 604 605 606 607 608

      XShapeSelectInput (display_x11->xdisplay, child->xid, ShapeNotifyMask);
      child->shape_selected = TRUE;
    }
  if (!child->shape_valid)
    {
609
      GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
610
      cairo_region_t *input_shape;
611

612 613
      child->shape = _gdk_x11_xwindow_get_shape (display_x11->xdisplay,
                                                 child->xid, ShapeBounding);
614
#ifdef ShapeInput
615 616
      input_shape = _gdk_x11_xwindow_get_shape (display_x11->xdisplay,
                                                child->xid, ShapeInput);
617 618
      if (child->shape && input_shape)
        {
Benjamin Otte's avatar
Benjamin Otte committed
619 620
          cairo_region_intersect (child->shape, input_shape);
          cairo_region_destroy (input_shape);
621 622 623 624 625 626 627 628 629 630 631
        }
      else if (input_shape)
        {
          child->shape = input_shape;
        }
#endif

      child->shape_valid = TRUE;
    }

  return child->shape == NULL ||
Benjamin Otte's avatar
Benjamin Otte committed
632
         cairo_region_contains_point (child->shape, x_pos, y_pos);
633 634
}

635 636
static Window
get_client_window_at_coords_recurse (GdkDisplay *display,
637 638 639 640
                                     Window      win,
                                     gboolean    is_toplevel,
                                     gint        x,
                                     gint        y)
641 642 643 644 645
{
  GdkChildInfoX11 *children;
  unsigned int nchildren;
  int i;
  gboolean found_child = FALSE;
646
  GdkChildInfoX11 child = { 0, };
647
  gboolean has_wm_state = FALSE;
648 649

  if (!_gdk_x11_get_window_child_info (display, win, TRUE,
650 651
                                       is_toplevel? &has_wm_state : NULL,
                                       &children, &nchildren))
652
    return None;
653

654
  if (has_wm_state)
655 656 657 658 659
    {
      g_free (children);

      return win;
    }
660

661
  for (i = nchildren - 1; (i >= 0) && !found_child; i--)
662
    {
663
      GdkChildInfoX11 *cur_child = &children[i];
664

665
      if ((cur_child->is_mapped) && (cur_child->window_class == InputOutput) &&
666 667 668 669 670 671 672 673 674 675
          (x >= cur_child->x) && (x < cur_child->x + cur_child->width) &&
          (y >= cur_child->y) && (y < cur_child->y + cur_child->height))
        {
          x -= cur_child->x;
          y -= cur_child->y;
          child = *cur_child;
          found_child = TRUE;
        }
    }

676
  g_free (children);
677

678 679 680
  if (found_child)
    {
      if (child.has_wm_state)
681
        return child.window;
682
      else
683
        return get_client_window_at_coords_recurse (display, child.window, FALSE, x, y);
684
    }
685 686 687 688
  else
    return None;
}

689
static Window
690
get_client_window_at_coords (GdkWindowCache *cache,
691 692 693
                             Window          ignore,
                             gint            x_root,
                             gint            y_root)
694 695 696
{
  GList *tmp_list;
  Window retval = None;
697 698 699 700 701
  GdkDisplay *display;

  display = gdk_screen_get_display (cache->screen);

  gdk_x11_display_error_trap_push (display);
702 703 704 705 706 707 708 709

  tmp_list = cache->children;

  while (tmp_list && !retval)
    {
      GdkCacheChild *child = tmp_list->data;

      if ((child->xid != ignore) && (child->mapped))
710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729
        {
          if ((x_root >= child->x) && (x_root < child->x + child->width) &&
              (y_root >= child->y) && (y_root < child->y + child->height))
            {
              if (!is_pointer_within_shape (display, child,
                                            x_root - child->x,
                                            y_root - child->y))
                {
                  tmp_list = tmp_list->next;
                  continue;
                }

              retval = get_client_window_at_coords_recurse (display,
                  child->xid, TRUE,
                  x_root - child->x,
                  y_root - child->y);
              if (!retval)
                retval = child->xid;
            }
        }
730 731 732
      tmp_list = tmp_list->next;
    }

733
  gdk_x11_display_error_trap_pop_ignored (display);
734

735 736 737
  if (retval)
    return retval;
  else
738
    return GDK_WINDOW_XID (gdk_screen_get_root_window (cache->screen));
739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802
}

/*************************************************************
 ***************************** MOTIF *************************
 *************************************************************/

/* values used in the message type for Motif DND */
enum {
    XmTOP_LEVEL_ENTER,
    XmTOP_LEVEL_LEAVE,
    XmDRAG_MOTION,
    XmDROP_SITE_ENTER,
    XmDROP_SITE_LEAVE,
    XmDROP_START,
    XmDROP_FINISH,
    XmDRAG_DROP_FINISH,
    XmOPERATION_CHANGED
};

/* Values used to specify type of protocol to use */
enum {
    XmDRAG_NONE,
    XmDRAG_DROP_ONLY,
    XmDRAG_PREFER_PREREGISTER,
    XmDRAG_PREREGISTER,
    XmDRAG_PREFER_DYNAMIC,
    XmDRAG_DYNAMIC,
    XmDRAG_PREFER_RECEIVER
};

/* Operation codes */
enum {
  XmDROP_NOOP,
  XmDROP_MOVE = 0x01,
  XmDROP_COPY = 0x02,
  XmDROP_LINK = 0x04
};

/* Drop site status */
enum {
  XmNO_DROP_SITE = 0x01,
  XmDROP_SITE_INVALID = 0x02,
  XmDROP_SITE_VALID = 0x03
};

/* completion status */
enum {
  XmDROP,
  XmDROP_HELP,
  XmDROP_CANCEL,
  XmDROP_INTERRUPT
};

/* Byte swapping routines. The motif specification leaves it
 * up to us to save a few bytes in the client messages
 */
static gchar local_byte_order = '\0';

#ifdef G_ENABLE_DEBUG
static void
print_target_list (GList *targets)
{
  while (targets)
    {
803
      gchar *name = gdk_atom_name (GDK_POINTER_TO_ATOM (targets->data));
804 805 806 807 808 809 810 811 812 813 814 815 816 817 818
      g_message ("\t%s", name);
      g_free (name);
      targets = targets->next;
    }
}
#endif /* G_ENABLE_DEBUG */

static void
init_byte_order (void)
{
  guint32 myint = 0x01020304;
  local_byte_order = (*(gchar *)&myint == 1) ? 'B' : 'l';
}

static guint16
819 820
card16_to_host (guint16 x, gchar byte_order)
{
821 822 823 824 825 826 827
  if (byte_order == local_byte_order)
    return x;
  else
    return (x << 8) | (x >> 8);
}

static guint32
828 829
card32_to_host (guint32 x, gchar byte_order)
{
830 831 832 833 834 835
  if (byte_order == local_byte_order)
    return x;
  else
    return (x << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | (x >> 24);
}

836 837 838
/* Motif packs together fields of varying length into the
 * client message. We can't rely on accessing these
 * through data.s[], data.l[], etc, because on some architectures
839
 * (i.e., Alpha) these won't be valid for format == 8.
840 841 842 843 844 845 846 847 848 849 850 851 852 853
 */

#define MOTIF_XCLIENT_BYTE(xevent,i) \
  (xevent)->xclient.data.b[i]
#define MOTIF_XCLIENT_SHORT(xevent,i) \
  ((gint16 *)&((xevent)->xclient.data.b[0]))[i]
#define MOTIF_XCLIENT_LONG(xevent,i) \
  ((gint32 *)&((xevent)->xclient.data.b[0]))[i]

#define MOTIF_UNPACK_BYTE(xevent,i) MOTIF_XCLIENT_BYTE(xevent,i)
#define MOTIF_UNPACK_SHORT(xevent,i) \
  card16_to_host (MOTIF_XCLIENT_SHORT(xevent,i), MOTIF_XCLIENT_BYTE(xevent, 1))
#define MOTIF_UNPACK_LONG(xevent,i) \
  card32_to_host (MOTIF_XCLIENT_LONG(xevent,i), MOTIF_XCLIENT_BYTE(xevent, 1))
854 855 856 857

/***** Dest side ***********/

/* Property placed on source windows */
858 859
typedef struct _MotifDragInitiatorInfo
{
860 861 862 863 864 865 866
  guint8 byte_order;
  guint8 protocol_version;
  guint16 targets_index;
  guint32 selection_atom;
} MotifDragInitiatorInfo;

/* Header for target table on the drag window */
867 868
typedef struct _MotifTargetTableHeader
{
869 870 871 872 873 874 875
  guchar byte_order;
  guchar protocol_version;
  guint16 n_lists;
  guint32 total_size;
} MotifTargetTableHeader;

/* Property placed on target windows */
876 877
typedef struct _MotifDragReceiverInfo
{
878 879 880 881 882 883 884 885 886 887 888 889
  guint8 byte_order;
  guint8 protocol_version;
  guint8 protocol_style;
  guint8 pad;
  guint32 proxy_window;
  guint16 num_drop_sites;
  guint16 padding;
  guint32 total_size;
} MotifDragReceiverInfo;

/* Target table handling */

890
static GdkFilterReturn
891
motif_drag_window_filter (GdkXEvent *xevent,
892 893
                          GdkEvent  *event,
                          gpointer data)
894 895
{
  XEvent *xev = (XEvent *)xevent;
896
  GdkDisplay *display = GDK_WINDOW_DISPLAY (event->any.window);
897
  GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
898 899 900 901

  switch (xev->xany.type)
    {
    case DestroyNotify:
902 903
      display_x11->motif_drag_window = None;
      display_x11->motif_drag_gdk_window = NULL;
904 905
      break;
    case PropertyNotify:
906
      if (display_x11->motif_target_lists &&
907 908
          (xev->xproperty.atom == gdk_x11_get_xatom_by_name_for_display (display, "_MOTIF_DRAG_TARGETS")))
        motif_read_target_table (display);
909 910 911 912 913 914
      break;
    }
  return GDK_FILTER_REMOVE;
}

static Window
915
motif_lookup_drag_window (GdkDisplay *display,
916
                          Display    *lookup_xdisplay)
917 918 919
{
  Window retval = None;
  gulong bytes_after, nitems;
920
  Atom type;
921 922 923
  gint format;
  guchar *data;

924
  XGetWindowProperty (lookup_xdisplay, RootWindow (lookup_xdisplay, 0),
925 926 927 928 929
                      gdk_x11_get_xatom_by_name_for_display (display, "_MOTIF_DRAG_WINDOW"),
                      0, 1, FALSE,
                      XA_WINDOW, &type, &format, &nitems, &bytes_after,
                      &data);

930
  if ((format == 32) && (nitems == 1) && (bytes_after == 0))
931 932
    {
      retval = *(Window *)data;
933 934
      GDK_NOTE (DND,
                g_message ("Found drag window %#lx\n", GDK_X11_DISPLAY (display)->motif_drag_window));
935 936 937 938 939 940 941 942 943 944 945
    }

  if (type != None)
    XFree (data);

  return retval;
}

/* Finds the window where global Motif drag information is stored.
 * If it doesn't exist and 'create' is TRUE, create one.
 */
946
static Window
947
motif_find_drag_window (GdkDisplay *display,
948
                        gboolean    create)
949
{
950
  GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
951

952
  if (!display_x11->motif_drag_window)
953
    {
954 955
      Atom motif_drag_window_atom = gdk_x11_get_xatom_by_name_for_display (display, "_MOTIF_DRAG_WINDOW");
      display_x11->motif_drag_window = motif_lookup_drag_window (display, display_x11->xdisplay);
956

957
      if (!display_x11->motif_drag_window && create)
958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992
        {
          /* Create a persistant window. (Copied from LessTif) */
          Display *persistant_xdisplay;
          XSetWindowAttributes attr;
          persistant_xdisplay = XOpenDisplay (gdk_display_get_name (display));
          XSetCloseDownMode (persistant_xdisplay, RetainPermanent);

          XGrabServer (persistant_xdisplay);

          display_x11->motif_drag_window = motif_lookup_drag_window (display, persistant_xdisplay);

          if (!display_x11->motif_drag_window)
            {
              attr.override_redirect = True;
              attr.event_mask = PropertyChangeMask;

               display_x11->motif_drag_window =
                XCreateWindow (persistant_xdisplay,
                               RootWindow (persistant_xdisplay, 0),
                              -100, -100, 10, 10, 0, 0,
                              InputOnly, (Visual *)CopyFromParent,
                              (CWOverrideRedirect | CWEventMask), &attr);

              GDK_NOTE (DND,
                        g_message ("Created drag window %#lx\n", display_x11->motif_drag_window));

              XChangeProperty (persistant_xdisplay,
                               RootWindow (persistant_xdisplay, 0),
                               motif_drag_window_atom, XA_WINDOW,
                               32, PropModeReplace,
                               (guchar *)&motif_drag_window_atom, 1);
            }
          XUngrabServer (persistant_xdisplay);
          XCloseDisplay (persistant_xdisplay);
        }
993 994 995 996

      /* There is a miniscule race condition here if the drag window
       * gets destroyed exactly now.
       */
997
      if (display_x11->motif_drag_window)
998 999 1000 1001 1002 1003 1004
        {
          display_x11->motif_drag_gdk_window =
            gdk_x11_window_foreign_new_for_display (display, display_x11->motif_drag_window);
          gdk_window_add_filter (display_x11->motif_drag_gdk_window,
                                 motif_drag_window_filter,
                                 NULL);
        }
1005 1006
    }

1007
  return display_x11->motif_drag_window;
1008 1009
}

1010
static void
1011
motif_read_target_table (GdkDisplay *display)
1012
{
1013
  GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
1014
  gulong bytes_after, nitems;
1015
  Atom type;
1016 1017
  gint format;
  gint i, j;
1018

1019
  Atom motif_drag_targets_atom = gdk_x11_get_xatom_by_name_for_display (display, "_MOTIF_DRAG_TARGETS");
1020

1021
  if (display_x11->motif_target_lists)
1022
    {
1023
      for (i=0; i<display_x11->motif_n_target_lists; i++)
1024 1025
        g_list_free (display_x11->motif_target_lists[i]);

1026 1027 1028
      g_free (display_x11->motif_target_lists);
      display_x11->motif_target_lists = NULL;
      display_x11->motif_n_target_lists = 0;
1029 1030
    }

1031
  if (motif_find_drag_window (display, FALSE))
1032
    {
1033
      guchar *data;
1034 1035 1036 1037 1038
      MotifTargetTableHeader *header = NULL;
      guchar *target_bytes = NULL;
      guchar *p;
      gboolean success = FALSE;

1039 1040 1041 1042 1043 1044 1045 1046
      gdk_x11_display_error_trap_push (display);
      XGetWindowProperty (display_x11->xdisplay,
                          display_x11->motif_drag_window,
                          motif_drag_targets_atom,
                          0, (sizeof(MotifTargetTableHeader)+3)/4, FALSE,
                          motif_drag_targets_atom,
                          &type, &format, &nitems, &bytes_after,
                          &data);
1047

1048 1049 1050
      if (gdk_x11_display_error_trap_pop (display) ||
          (format != 8) || (nitems < sizeof (MotifTargetTableHeader)))
        goto error;
1051

1052 1053
      header = (MotifTargetTableHeader *)data;

1054 1055 1056
      header->n_lists = card16_to_host (header->n_lists, header->byte_order);
      header->total_size = card32_to_host (header->total_size, header->byte_order);

1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
      gdk_x11_display_error_trap_push (display);
      XGetWindowProperty (display_x11->xdisplay,
                          display_x11->motif_drag_window,
                          motif_drag_targets_atom,
                          (sizeof(MotifTargetTableHeader)+3)/4,
                          (header->total_size + 3)/4 - (sizeof(MotifTargetTableHeader) + 3)/4,
                          FALSE,
                          motif_drag_targets_atom, &type, &format, &nitems,
                          &bytes_after, &target_bytes);

      if (gdk_x11_display_error_trap_pop (display) ||
          (format != 8) || (bytes_after != 0) ||
          (nitems != header->total_size - sizeof(MotifTargetTableHeader)))
          goto error;
1071

1072 1073
      display_x11->motif_n_target_lists = header->n_lists;
      display_x11->motif_target_lists = g_new0 (GList *, display_x11->motif_n_target_lists);
1074 1075 1076

      p = target_bytes;
      for (i=0; i<header->n_lists; i++)
1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103
        {
          gint n_targets;
          guint32 *targets;

          if (p + sizeof(guint16) - target_bytes > nitems)
            goto error;

          n_targets = card16_to_host (*(gushort *)p, header->byte_order);

          /* We need to make a copy of the targets, since it may
           * be unaligned
           */
          targets = g_new (guint32, n_targets);
          memcpy (targets, p + sizeof(guint16), sizeof(guint32) * n_targets);

          p +=  sizeof(guint16) + n_targets * sizeof(guint32);
          if (p - target_bytes > nitems)
            goto error;

          for (j=0; j<n_targets; j++)
            display_x11->motif_target_lists[i] =
              g_list_prepend (display_x11->motif_target_lists[i],
                              GUINT_TO_POINTER (card32_to_host (targets[j],
                                                                header->byte_order)));
          g_free (targets);
          display_x11->motif_target_lists[i] = g_list_reverse (display_x11->motif_target_lists[i]);
        }
1104 1105

      success = TRUE;
1106

1107 1108
    error:
      if (header)
1109 1110
        XFree (header);

1111
      if (target_bytes)
1112
        XFree (target_bytes);
1113 1114

      if (!success)
1115 1116 1117 1118 1119 1120 1121 1122 1123
        {
          if (display_x11->motif_target_lists)
            {
              g_free (display_x11->motif_target_lists);
              display_x11->motif_target_lists = NULL;
              display_x11->motif_n_target_lists = 0;
            }
          g_warning ("Error reading Motif target table\n");
        }
1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134
    }
}

static gint
targets_sort_func (gconstpointer a, gconstpointer b)
{
  return (GPOINTER_TO_UINT (a) < GPOINTER_TO_UINT (b)) ?
    -1 : ((GPOINTER_TO_UINT (a) > GPOINTER_TO_UINT (b)) ? 1 : 0);
}

/* Check if given (sorted) list is in the targets table */
1135
static gint
1136
motif_target_table_check (GdkDisplay *display,
1137
                          GList      *sorted)
1138
{
1139
  GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
1140 1141 1142
  GList *tmp_list1, *tmp_list2;
  gint i;

1143
  for (i=0; i<display_x11->motif_n_target_lists; i++)
1144
    {
1145
      tmp_list1 = display_x11->motif_target_lists[i];
1146
      tmp_list2 = sorted;
1147

1148
      while (tmp_list1 && tmp_list2)
1149 1150 1151
        {
          if (tmp_list1->data != tmp_list2->data)
            break;
1152

1153 1154 1155 1156 1157
          tmp_list1 = tmp_list1->next;
          tmp_list2 = tmp_list2->next;
        }
      if (!tmp_list1 && !tmp_list2)     /* Found it */
        return i;
1158 1159 1160 1161 1162 1163
    }

  return -1;
}

static gint
1164
motif_add_to_target_table (GdkDisplay *display,
1165
                           GList      *targets) /* targets is list of GdkAtom */
1166
{
1167
  GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
1168 1169 1170 1171
  GList *sorted = NULL;
  gint index = -1;
  gint i;
  GList *tmp_list;
1172

1173 1174 1175
  /* make a sorted copy of the list */
  while (targets)
    {
1176
      Atom xatom = gdk_x11_atom_to_xatom_for_display (display, GDK_POINTER_TO_ATOM (targets->data));
1177
      sorted = g_list_insert_sorted (sorted, GUINT_TO_POINTER (xatom), targets_sort_func);
1178 1179 1180
      targets = targets->next;
    }

1181
  /* First check if it is there already */
1182

1183 1184
  if (display_x11->motif_target_lists)
    index = motif_target_table_check (display, sorted);
1185 1186 1187 1188 1189 1190 1191 1192 1193

  /* We need to grab the server while doing this, to ensure
   * atomiticity. Ugh
   */

  if (index < 0)
    {
      /* We need to make sure that it exists _before_ we grab the
       * server, since we can't open a new connection after we
1194
       * grab the server.
1195
       */
1196
      motif_find_drag_window (display, TRUE);
1197

1198 1199
      gdk_x11_display_grab (display);
      motif_read_target_table (display);
1200

1201
      /* Check again, in case it was added in the meantime */
1202
      if (display_x11->motif_target_lists)
1203
        index = motif_target_table_check (display, sorted);
1204 1205

      if (index < 0)
1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233