gdkdnd-x11.c 76.2 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
Javier Jardon's avatar
Javier Jardon committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16
 */
17 18

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

25
#include "config.h"
26

27
#include "gdkx11dnd.h"
28
#include "gdkdndprivate.h"
Owen Taylor's avatar
Owen Taylor committed
29

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

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

#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
  gint ref_count;
70 71
} GdkWindowCache;

Matthias Clasen's avatar
Matthias Clasen committed
72

73 74
struct _GdkX11DragContext
{
75 76 77 78
  GdkDragContext context;

  guint   ref_count;

79
  guint16 last_x;              /* Coordinates from last event */
80
  guint16 last_y;
81 82 83
  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 */
84 85 86
  guint version;               /* Xdnd protocol version */

  GSList *window_caches;
87

88 89
  GdkWindow *drag_window;

90 91 92
  gint hot_x;
  gint hot_y;

93 94 95 96 97 98 99
  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 drag_status       : 4; /* current status of drag */
  guint drop_failed       : 1; /* Whether the drop was unsuccessful */
100 101
};

102 103 104 105 106
struct _GdkX11DragContextClass
{
  GdkDragContextClass parent_class;
};

107 108
/* Forward declarations */

109 110 111
static GdkWindowCache *gdk_window_cache_get   (GdkScreen      *screen);
static GdkWindowCache *gdk_window_cache_ref   (GdkWindowCache *cache);
static void            gdk_window_cache_unref (GdkWindowCache *cache);
112

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

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

136
static GList *contexts;
137
static GSList *window_caches;
138

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
  { "XdndEnter",                    xdnd_enter_filter },
  { "XdndLeave",                    xdnd_leave_filter },
  { "XdndPosition",                 xdnd_position_filter },
  { "XdndStatus",                   xdnd_status_filter },
  { "XdndFinished",                 xdnd_finished_filter },
  { "XdndDrop",                     xdnd_drop_filter },
149
};
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 192
static void        gdk_x11_drag_context_set_hotspot (GdkDragContext  *context,
                                                     gint             hot_x,
                                                     gint             hot_y);
193

194 195 196 197 198 199
static GdkWindow *
gdk_x11_drag_context_get_drag_window (GdkDragContext *context)
{
  return GDK_X11_DRAG_CONTEXT (context)->drag_window;
}

200
static void
201
gdk_x11_drag_context_class_init (GdkX11DragContextClass *klass)
202
{
203
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
Matthias Clasen's avatar
Matthias Clasen committed
204
  GdkDragContextClass *context_class = GDK_DRAG_CONTEXT_CLASS (klass);
205

206
  object_class->finalize = gdk_x11_drag_context_finalize;
Matthias Clasen's avatar
Matthias Clasen committed
207

208 209 210 211 212 213 214 215 216
  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;
217
  context_class->get_drag_window = gdk_x11_drag_context_get_drag_window;
218
  context_class->set_hotspot = gdk_x11_drag_context_set_hotspot;
219 220
}

221
static void
222
gdk_x11_drag_context_finalize (GObject *object)
223
{
224
  GdkDragContext *context = GDK_DRAG_CONTEXT (object);
225
  GdkX11DragContext *x11_context = GDK_X11_DRAG_CONTEXT (object);
226 227

  if (context->source_window)
228
    {
Matthias Clasen's avatar
Matthias Clasen committed
229
      if ((context->protocol == GDK_DRAG_PROTO_XDND) && !context->is_source)
230 231
        xdnd_manage_source_filter (context, context->source_window, FALSE);
    }
232

233 234
  g_slist_free_full (x11_context->window_caches, (GDestroyNotify)gdk_window_cache_unref);
  x11_context->window_caches = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
235

236
  contexts = g_list_remove (contexts, context);
237

238
  G_OBJECT_CLASS (gdk_x11_drag_context_parent_class)->finalize (object);
239
}
240

241 242
/* Drag Contexts */

243
static GdkDragContext *
244
gdk_drag_context_find (GdkDisplay *display,
245 246 247
                       gboolean    is_source,
                       Window      source_xid,
                       Window      dest_xid)
248
{
249
  GList *tmp_list;
Jeff Garzik's avatar
Jeff Garzik committed
250
  GdkDragContext *context;
251
  GdkX11DragContext *context_x11;
252
  Window context_dest_xid;
253

254
  for (tmp_list = contexts; tmp_list; tmp_list = tmp_list->next)
255
    {
Jeff Garzik's avatar
Jeff Garzik committed
256
      context = (GdkDragContext *)tmp_list->data;
257
      context_x11 = (GdkX11DragContext *)context;
258

259
      if ((context->source_window && gdk_window_get_display (context->source_window) != display) ||
260
          (context->dest_window && gdk_window_get_display (context->dest_window) != display))
Matthias Clasen's avatar
Matthias Clasen committed
261
        continue;
262

Matthias Clasen's avatar
Matthias Clasen committed
263 264 265 266 267
      context_dest_xid = context->dest_window
                            ? (context_x11->drop_xid
                                  ? context_x11->drop_xid
                                  : GDK_WINDOW_XID (context->dest_window))
                            : None;
268 269

      if ((!context->is_source == !is_source) &&
Matthias Clasen's avatar
Matthias Clasen committed
270 271 272 273
          ((source_xid == None) || (context->source_window &&
            (GDK_WINDOW_XID (context->source_window) == source_xid))) &&
          ((dest_xid == None) || (context_dest_xid == dest_xid)))
        return context;
274
    }
Matthias Clasen's avatar
Matthias Clasen committed
275

276 277 278
  return NULL;
}

279 280 281 282 283 284 285 286 287 288
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)
289
        g_ptr_array_add (targets, gdk_atom_name (GDK_POINTER_TO_ATOM (tmp_list->data)));
290 291

      _gdk_x11_precache_atoms (GDK_WINDOW_DISPLAY (context->source_window),
292 293
                               (const gchar **)targets->pdata,
                               targets->len);
294 295

      for (i =0; i < targets->len; i++)
296
        g_free (targets->pdata[i]);
297 298 299 300 301

      g_ptr_array_free (targets, TRUE);
    }
}

302 303
/* Utility functions */

304 305 306 307 308
static void
free_cache_child (GdkCacheChild *child,
                  GdkDisplay    *display)
{
  if (child->shape)
Benjamin Otte's avatar
Benjamin Otte committed
309
    cairo_region_destroy (child->shape);
310 311 312

  if (child->shape_selected && display)
    {
313
      GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
314 315 316 317 318 319 320

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

  g_free (child);
}

321 322
static void
gdk_window_cache_add (GdkWindowCache *cache,
323 324 325 326 327 328
                      guint32         xid,
                      gint            x,
                      gint            y,
                      gint            width,
                      gint            height,
                      gboolean        mapped)
329 330 331 332 333 334 335 336 337
{
  GdkCacheChild *child = g_new (GdkCacheChild, 1);

  child->xid = xid;
  child->x = x;
  child->y = y;
  child->width = width;
  child->height = height;
  child->mapped = mapped;
338 339 340
  child->shape_selected = FALSE;
  child->shape_valid = FALSE;
  child->shape = NULL;
341 342

  cache->children = g_list_prepend (cache->children, child);
343 344
  g_hash_table_insert (cache->child_hash, GUINT_TO_POINTER (xid),
                       cache->children);
345 346
}

347 348 349 350 351 352 353 354
static GdkFilterReturn
gdk_window_cache_shape_filter (GdkXEvent *xev,
                               GdkEvent  *event,
                               gpointer   data)
{
  XEvent *xevent = (XEvent *)xev;
  GdkWindowCache *cache = data;

355
  GdkX11Display *display = GDK_X11_DISPLAY (gdk_screen_get_display (cache->screen));
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370

  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
371
              cairo_region_destroy (child->shape);
372 373 374 375 376 377 378 379 380 381
              child->shape = NULL;
            }
        }

      return GDK_FILTER_REMOVE;
    }

  return GDK_FILTER_CONTINUE;
}

382 383
static GdkFilterReturn
gdk_window_cache_filter (GdkXEvent *xev,
384 385
                         GdkEvent  *event,
                         gpointer   data)
386 387 388 389 390 391 392 393 394 395
{
  XEvent *xevent = (XEvent *)xev;
  GdkWindowCache *cache = data;

  switch (xevent->type)
    {
    case CirculateNotify:
      break;
    case ConfigureNotify:
      {
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 425 426 427 428 429 430 431 432 433 434
        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;
435 436 437
      }
    case CreateNotify:
      {
438 439 440 441 442 443 444 445
        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;
446 447 448
      }
    case DestroyNotify:
      {
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
        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;
466 467 468
      }
    case MapNotify:
      {
469 470 471 472 473 474 475 476 477 478 479
        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;
480 481 482 483 484
      }
    case ReparentNotify:
      break;
    case UnmapNotify:
      {
485 486 487 488 489 490 491 492 493 494 495
        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;
496 497 498 499 500 501 502 503
      }
    default:
      return GDK_FILTER_CONTINUE;
    }
  return GDK_FILTER_REMOVE;
}

static GdkWindowCache *
504
gdk_window_cache_new (GdkScreen *screen)
505 506
{
  XWindowAttributes xwa;
507 508
  Display *xdisplay = GDK_SCREEN_XDISPLAY (screen);
  GdkWindow *root_window = gdk_screen_get_root_window (screen);
509 510
  GdkChildInfoX11 *children;
  guint nchildren, i;
511
#ifdef HAVE_XCOMPOSITE
512
  Window cow;
513
#endif
514

515 516 517 518
  GdkWindowCache *result = g_new (GdkWindowCache, 1);

  result->children = NULL;
  result->child_hash = g_hash_table_new (g_direct_hash, NULL);
519
  result->screen = screen;
520
  result->ref_count = 1;
521

522
  XGetWindowAttributes (xdisplay, GDK_WINDOW_XID (root_window), &xwa);
523
  result->old_event_mask = xwa.your_event_mask;
524

525
  if (G_UNLIKELY (!GDK_X11_DISPLAY (GDK_X11_SCREEN (screen)->display)->trusted_client))
526 527 528
    {
      GList *toplevel_windows, *list;
      GdkWindow *window;
529
      GdkWindowImplX11 *impl;
530
      gint x, y, width, height;
531

532
      toplevel_windows = gdk_screen_get_toplevel_windows (screen);
533 534 535
      for (list = toplevel_windows; list; list = list->next)
        {
          window = GDK_WINDOW (list->data);
536
	  impl = GDK_WINDOW_IMPL_X11 (window->impl);
537 538
          gdk_window_get_geometry (window, &x, &y, &width, &height);
          gdk_window_cache_add (result, GDK_WINDOW_XID (window),
539 540 541
                                x * impl->window_scale, y * impl->window_scale, 
				width * impl->window_scale, 
				height * impl->window_scale,
542 543
                                gdk_window_is_visible (window));
        }
544 545 546 547
      g_list_free (toplevel_windows);
      return result;
    }

548
  XSelectInput (xdisplay, GDK_WINDOW_XID (root_window),
549
                result->old_event_mask | SubstructureNotifyMask);
550
  gdk_window_add_filter (root_window, gdk_window_cache_filter, result);
551
  gdk_window_add_filter (NULL, gdk_window_cache_shape_filter, result);
552

553
  if (!_gdk_x11_get_window_child_info (gdk_screen_get_display (screen),
554 555 556
                                       GDK_WINDOW_XID (root_window),
                                       FALSE, NULL,
                                       &children, &nchildren))
557
    return result;
558

559 560
  for (i = 0; i < nchildren ; i++)
    {
561
      gdk_window_cache_add (result, children[i].window,
562 563
                            children[i].x, children[i].y, children[i].width, children[i].height,
                            children[i].is_mapped);
564 565
    }

566
  g_free (children);
567

568
#ifdef HAVE_XCOMPOSITE
569 570 571 572 573 574 575 576 577
  /*
   * 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))
    {
578
      cow = XCompositeGetOverlayWindow (xdisplay, GDK_WINDOW_XID (root_window));
579 580 581 582
      gdk_window_cache_add (result, cow, 0, 0, 
			    gdk_screen_get_width (screen) * GDK_X11_SCREEN(screen)->window_scale, 
			    gdk_screen_get_height (screen) * GDK_X11_SCREEN(screen)->window_scale, 
			    TRUE);
583
      XCompositeReleaseOverlayWindow (xdisplay, GDK_WINDOW_XID (root_window));
584
    }
585
#endif
586

587 588 589 590 591 592
  return result;
}

static void
gdk_window_cache_destroy (GdkWindowCache *cache)
{
593
  GdkWindow *root_window = gdk_screen_get_root_window (cache->screen);
594
  GdkDisplay *display;
595 596

  XSelectInput (GDK_WINDOW_XDISPLAY (root_window),
597 598
                GDK_WINDOW_XID (root_window),
                cache->old_event_mask);
599
  gdk_window_remove_filter (root_window, gdk_window_cache_filter, cache);
600
  gdk_window_remove_filter (NULL, gdk_window_cache_shape_filter, cache);
601

602
  display = gdk_screen_get_display (cache->screen);
603

604 605 606
  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);
607

608 609
  g_list_free (cache->children);
  g_hash_table_destroy (cache->child_hash);
610 611

  g_free (cache);
612 613
}

614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
static GdkWindowCache *
gdk_window_cache_ref (GdkWindowCache *cache)
{
  cache->ref_count += 1;

  return cache;
}

static void
gdk_window_cache_unref (GdkWindowCache *cache)
{
  g_assert (cache->ref_count > 0);

  cache->ref_count -= 1;

  if (cache->ref_count == 0)
    {
      window_caches = g_slist_remove (window_caches, cache);
      gdk_window_cache_destroy (cache);
    }
}

GdkWindowCache *
gdk_window_cache_get (GdkScreen *screen)
{
  GSList *list;
  GdkWindowCache *cache;

  for (list = window_caches; list; list = list->next)
    {
      cache = list->data;
      if (cache->screen == screen)
        return gdk_window_cache_ref (cache);
    }

649
  cache = gdk_window_cache_new (screen);
650

651
  window_caches = g_slist_prepend (window_caches, cache);
652

653
  return cache;
654 655
}

656 657 658 659 660
static gboolean
is_pointer_within_shape (GdkDisplay    *display,
                         GdkCacheChild *child,
                         gint           x_pos,
                         gint           y_pos)
661
{
662
  if (!child->shape_selected)
663
    {
664
      GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
665

666 667 668 669 670 671 672
      XShapeSelectInput (display_x11->xdisplay, child->xid, ShapeNotifyMask);
      child->shape_selected = TRUE;
    }
  if (!child->shape_valid)
    {
      GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
      cairo_region_t *input_shape;
673

674 675 676
      child->shape = NULL;
      if (gdk_display_supports_shapes (display))
        child->shape = _gdk_x11_xwindow_get_shape (display_x11->xdisplay,
677
                                                   child->xid, 1,  ShapeBounding);
678 679 680 681
#ifdef ShapeInput
      input_shape = NULL;
      if (gdk_display_supports_input_shapes (display))
        input_shape = _gdk_x11_xwindow_get_shape (display_x11->xdisplay,
682
                                                  child->xid, 1, ShapeInput);
683

684 685 686 687 688 689 690 691 692 693
      if (child->shape && input_shape)
        {
          cairo_region_intersect (child->shape, input_shape);
          cairo_region_destroy (input_shape);
        }
      else if (input_shape)
        {
          child->shape = input_shape;
        }
#endif
694

695
      child->shape_valid = TRUE;
696 697
    }

698 699
  return child->shape == NULL ||
         cairo_region_contains_point (child->shape, x_pos, y_pos);
700 701
}

702 703 704 705 706 707
static Window
get_client_window_at_coords_recurse (GdkDisplay *display,
                                     Window      win,
                                     gboolean    is_toplevel,
                                     gint        x,
                                     gint        y)
708
{
709 710 711 712 713 714
  GdkChildInfoX11 *children;
  unsigned int nchildren;
  int i;
  gboolean found_child = FALSE;
  GdkChildInfoX11 child = { 0, };
  gboolean has_wm_state = FALSE;
715

716 717 718 719
  if (!_gdk_x11_get_window_child_info (display, win, TRUE,
                                       is_toplevel? &has_wm_state : NULL,
                                       &children, &nchildren))
    return None;
720

721 722 723
  if (has_wm_state)
    {
      g_free (children);
724

725 726
      return win;
    }
727

728 729 730
  for (i = nchildren - 1; (i >= 0) && !found_child; i--)
    {
      GdkChildInfoX11 *cur_child = &children[i];
731

732 733 734 735 736 737 738 739 740 741
      if ((cur_child->is_mapped) && (cur_child->window_class == InputOutput) &&
          (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;
        }
    }
742

743
  g_free (children);
744

745 746 747 748 749 750 751 752 753
  if (found_child)
    {
      if (child.has_wm_state)
        return child.window;
      else
        return get_client_window_at_coords_recurse (display, child.window, FALSE, x, y);
    }
  else
    return None;
754
}
755

756 757 758 759 760
static Window
get_client_window_at_coords (GdkWindowCache *cache,
                             Window          ignore,
                             gint            x_root,
                             gint            y_root)
761
{
762 763
  GList *tmp_list;
  Window retval = None;
764
  GdkDisplay *display;
765

766
  display = gdk_screen_get_display (cache->screen);
767

768
  gdk_x11_display_error_trap_push (display);
769

770
  tmp_list = cache->children;
771

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

776
      if ((child->xid != ignore) && (child->mapped))
777
        {
778 779
          if ((x_root >= child->x) && (x_root < child->x + child->width) &&
              (y_root >= child->y) && (y_root < child->y + child->height))
780
            {
781 782 783 784 785 786 787 788 789 790 791 792 793 794
              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;
795 796
            }
        }
797
      tmp_list = tmp_list->next;
798
    }
799

800
  gdk_x11_display_error_trap_pop_ignored (display);
801

802 803 804 805 806
  if (retval)
    return retval;
  else
    return GDK_WINDOW_XID (gdk_screen_get_root_window (cache->screen));
}
807

808 809 810 811 812
#ifdef G_ENABLE_DEBUG
static void
print_target_list (GList *targets)
{
  while (targets)
813
    {
814 815 816 817
      gchar *name = gdk_atom_name (GDK_POINTER_TO_ATOM (targets->data));
      g_message ("\t%s", name);
      g_free (name);
      targets = targets->next;
818 819
    }
}
820
#endif /* G_ENABLE_DEBUG */
821 822 823 824 825 826 827 828

/*************************************************************
 ***************************** XDND **************************
 *************************************************************/

/* Utility functions */

static struct {
829
  const gchar *name;
830
  GdkAtom atom;
831 832
  GdkDragAction action;
} xdnd_actions_table[] = {
833 834 835 836 837
    { "XdndActionCopy",    None, GDK_ACTION_COPY },
    { "XdndActionMove",    None, GDK_ACTION_MOVE },
    { "XdndActionLink",    None, GDK_ACTION_LINK },
    { "XdndActionAsk",     None, GDK_ACTION_ASK  },
    { "XdndActionPrivate", None, GDK_ACTION_COPY },
838 839
  };

840
static const gint xdnd_n_actions = G_N_ELEMENTS (xdnd_actions_table);
841 842 843 844 845 846
static gboolean xdnd_actions_initialized = FALSE;

static void
xdnd_initialize_actions (void)
{
  gint i;
847

848
  xdnd_actions_initialized = TRUE;
Matthias Clasen's avatar
Matthias Clasen committed
849
  for (i = 0; i < xdnd_n_actions; i++)
Matthias Clasen's avatar
Matthias Clasen committed
850
    xdnd_actions_table[i].atom = gdk_atom_intern_static_string (xdnd_actions_table[i].name);
851 852 853
}

static GdkDragAction
854
xdnd_action_from_atom (GdkDisplay *display,
855
                       Atom        xatom)
856
{
857
  GdkAtom atom;
858 859
  gint i;

860 861 862 863 864
  if (xatom == None)
    return 0;

  atom = gdk_x11_xatom_to_atom_for_display (display, xatom);

865 866 867
  if (!xdnd_actions_initialized)
    xdnd_initialize_actions();

Matthias Clasen's avatar
Matthias Clasen committed
868
  for (i = 0; i < xdnd_n_actions; i++)
869 870 871 872 873 874
    if (atom == xdnd_actions_table[i].atom)
      return xdnd_actions_table[i].action;

  return 0;
}

875
static Atom
876
xdnd_action_to_atom (GdkDisplay    *display,
877
                     GdkDragAction  action)
878 879 880 881 882 883
{
  gint i;

  if (!xdnd_actions_initialized)
    xdnd_initialize_actions();

Matthias Clasen's avatar
Matthias Clasen committed
884
  for (i = 0; i < xdnd_n_actions; i++)
885
    if (action == xdnd_actions_table[i].action)
886
      return gdk_x11_atom_to_xatom_for_display (display, xdnd_actions_table[i].atom);
887

888
  return None;
889 890 891 892
}

/* Source side */

893
static GdkFilterReturn
894
xdnd_status_filter (GdkXEvent *xev,
895 896
                    GdkEvent  *event,
                    gpointer   data)
897
{
898
  GdkDisplay *display;
899 900 901
  XEvent *xevent = (XEvent *)xev;
  guint32 dest_window = xevent->xclient.data.l[0];
  guint32 flags = xevent->xclient.data.l[1];
902
  Atom action = xevent->xclient.data.l[4];
903
  GdkDragContext *context;
904 905 906

  if (!event->any.window ||
      gdk_window_get_window_type (event->any.window) == GDK_WINDOW_FOREIGN)
907 908 909 910 911
    return GDK_FILTER_CONTINUE;                 /* Not for us */

  GDK_NOTE (DND,
            g_message ("XdndStatus: dest_window: %#x  action: %ld",
                       dest_window, action));
912

913
  display = gdk_window_get_display (event->any.window);
914
  context = gdk_drag_context_find (display, TRUE, xevent->xclient.window, dest_window);
915

916 917
  if (context)
    {
918
      GdkX11DragContext *context_x11 = GDK_X11_DRAG_CONTEXT (context);
Matthias Clasen's avatar
Matthias Clasen committed
919 920
      if (context_x11->drag_status == GDK_DRAG_STATUS_MOTION_WAIT)
        context_x11->drag_status = GDK_DRAG_STATUS_DRAG;
921

922 923 924
      event->dnd.send_event = FALSE;
      event->dnd.type = GDK_DRAG_STATUS;
      event->dnd.context = context;
925
      gdk_event_set_device (event, gdk_drag_context_get_device (context));
926
      g_object_ref (context);
927 928 929

      event->dnd.time = GDK_CURRENT_TIME; /* FIXME? */
      if (!(action != 0) != !(flags & 1))
930 931 932 933 934
        {
          GDK_NOTE (DND,
                    g_warning ("Received status event with flags not corresponding to action!\n"));
          action = 0;
        }
935

936
      context->action = xdnd_action_from_atom (display, action);
937 938 939 940 941 942 943

      return GDK_FILTER_TRANSLATE;
    }

  return GDK_FILTER_REMOVE;
}

944
static GdkFilterReturn
945
xdnd_finished_filter (GdkXEvent *xev,
946 947
                      GdkEvent  *event,
                      gpointer   data)
948
{
949
  GdkDisplay *display;
950 951 952
  XEvent *xevent = (XEvent *)xev;
  guint32 dest_window = xevent->xclient.data.l[0];
  GdkDragContext *context;
953
  GdkX11DragContext *context_x11;
954 955 956

  if (!event->any.window ||
      gdk_window_get_window_type (event->any.window) == GDK_WINDOW_FOREIGN)
957 958 959 960
    return GDK_FILTER_CONTINUE;                 /* Not for us */

  GDK_NOTE (DND,
            g_message ("XdndFinished: dest_window: %#x", dest_window));
961

962
  display = gdk_window_get_display (event->any.window);
963
  context = gdk_drag_context_find (display, TRUE, xevent->xclient.window, dest_window);
964

965 966
  if (context)
    {
967
      context_x11 = GDK_X11_DRAG_CONTEXT (context);
Matthias Clasen's avatar
Matthias Clasen committed
968
      if (context_x11->version == 5)
969
        context_x11->drop_failed = xevent->xclient.data.l[1] == 0;
Matthias Clasen's avatar
Matthias Clasen committed
970

971 972
      event->dnd.type = GDK_DROP_FINISHED;
      event->dnd.context = context;
973
      gdk_event_set_device (event, gdk_drag_context_get_device (context));
974
      g_object_ref (context);
975

976 977
      event->dnd.time = GDK_CURRENT_TIME; /* FIXME? */

978 979 980 981 982 983 984
      return GDK_FILTER_TRANSLATE;
    }

  return GDK_FILTER_REMOVE;
}

static void
985
xdnd_set_targets (GdkX11DragContext *context_x11)
986
{
Matthias Clasen's avatar
Matthias Clasen committed
987
  GdkDragContext *context = GDK_DRAG_CONTEXT (context_x11);
988
  Atom *atomlist;
989 990
  GList *tmp_list = context->targets;
  gint i;
991
  gint n_atoms = g_list_length (context->targets);
992
  GdkDisplay *display = GDK_WINDOW_DISPLAY (context->source_window);
993

994
  atomlist = g_new (Atom, n_atoms);
995 996 997
  i = 0;
  while (tmp_list)
    {
998
      atomlist[i] = gdk_x11_atom_to_xatom_for_display (display, GDK_POINTER_TO_ATOM (tmp_list->data));
999 1000 1001 1002
      tmp_list = tmp_list->next;
      i++;
    }

1003
  XChangeProperty (GDK_WINDOW_XDISPLAY (context->source_window),
1004 1005 1006 1007
                   GDK_WINDOW_XID (context->source_window),
                   gdk_x11_get_xatom_by_name_for_display (display, "XdndTypeList"),
                   XA_ATOM, 32, PropModeReplace,
                   (guchar *)atomlist, n_atoms);
1008

1009 1010
  g_free (atomlist);

Matthias Clasen's avatar
Matthias Clasen committed
1011
  context_x11->xdnd_targets_set = 1;
1012 1013
}

1014
static void
1015
xdnd_set_actions (GdkX11DragContext *context_x11)
1016
{
Matthias Clasen's avatar
Matthias Clasen committed
1017
  GdkDragContext *context = GDK_DRAG_CONTEXT (context_x11);
1018
  Atom *atomlist;
1019 1020 1021
  gint i;
  gint n_atoms;
  guint actions;
1022
  GdkDisplay *display = GDK_WINDOW_DISPLAY (context->source_window);
1023 1024 1025

  if (!xdnd_actions_initialized)
    xdnd_initialize_actions();
1026

1027 1028
  actions = context->actions;
  n_atoms = 0;
Matthias Clasen's avatar
Matthias Clasen committed
1029
  for (i = 0; i < xdnd_n_actions; i++)
1030 1031
    {
      if (actions & xdnd_actions_table[i].action)
1032 1033 1034 1035
        {
          actions &= ~xdnd_actions_table[i].action;
          n_atoms++;
        }
1036 1037
    }

1038
  atomlist = g_new (Atom, n_atoms);
1039 1040 1041

  actions = context->actions;
  n_atoms = 0;
Matthias Clasen's avatar
Matthias Clasen committed
1042
  for (i = 0; i < xdnd_n_actions; i++)
1043 1044
    {
      if (actions & xdnd_actions_table[i].action)
1045 1046 1047 1048 1049
        {
          actions &= ~xdnd_actions_table[i].action;
          atomlist[n_atoms] = gdk_x11_atom_to_xatom_for_display (display, xdnd_actions_table[i].atom);
          n_atoms++;
        }