gdkdnd-x11.c 76.5 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 80
  gint start_x;                /* Where the drag started */
  gint start_y;
81
  guint16 last_x;              /* Coordinates from last event */
82
  guint16 last_y;
83 84 85
  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 */
86 87 88
  guint version;               /* Xdnd protocol version */

  GSList *window_caches;
89

90 91
  GdkWindow *drag_window;

92 93 94
  gint hot_x;
  gint hot_y;

95 96 97 98 99 100 101
  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 */
102 103
};

104 105 106 107 108
struct _GdkX11DragContextClass
{
  GdkDragContextClass parent_class;
};

109 110
/* Forward declarations */

111 112 113
static GdkWindowCache *gdk_window_cache_get   (GdkScreen      *screen);
static GdkWindowCache *gdk_window_cache_ref   (GdkWindowCache *cache);
static void            gdk_window_cache_unref (GdkWindowCache *cache);
114

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

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

138
static GList *contexts;
139
static GSList *window_caches;
140

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


154
G_DEFINE_TYPE (GdkX11DragContext, gdk_x11_drag_context, GDK_TYPE_DRAG_CONTEXT)
155

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

162 163
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
164 165 166 167 168
                                                     GdkWindow       *drag_window,
                                                     GdkScreen       *screen,
                                                     gint             x_root,
                                                     gint             y_root,
                                                     GdkDragProtocol *protocol);
169
static gboolean    gdk_x11_drag_context_drag_motion (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
170 171 172 173 174 175 176
                                                     GdkWindow       *dest_window,
                                                     GdkDragProtocol  protocol,
                                                     gint             x_root,
                                                     gint             y_root,
                                                     GdkDragAction    suggested_action,
                                                     GdkDragAction    possible_actions,
                                                     guint32          time);
177
static void        gdk_x11_drag_context_drag_status (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
178 179
                                                     GdkDragAction    action,
                                                     guint32          time_);
180
static void        gdk_x11_drag_context_drag_abort  (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
181
                                                     guint32          time_);
182
static void        gdk_x11_drag_context_drag_drop   (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
183
                                                     guint32          time_);
184
static void        gdk_x11_drag_context_drop_reply  (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
185 186
                                                     gboolean         accept,
                                                     guint32          time_);
187
static void        gdk_x11_drag_context_drop_finish (GdkDragContext  *context,
Matthias Clasen's avatar
Matthias Clasen committed
188 189
                                                     gboolean         success,
                                                     guint32          time_);
190 191
static gboolean    gdk_x11_drag_context_drop_status (GdkDragContext  *context);
static GdkAtom     gdk_x11_drag_context_get_selection (GdkDragContext  *context);
Matthias Clasen's avatar
Matthias Clasen committed
192
static GdkWindow * gdk_x11_drag_context_get_drag_window (GdkDragContext *context);
193 194 195
static void        gdk_x11_drag_context_set_hotspot (GdkDragContext  *context,
                                                     gint             hot_x,
                                                     gint             hot_y);
196

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

203
  object_class->finalize = gdk_x11_drag_context_finalize;
Matthias Clasen's avatar
Matthias Clasen committed
204

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

218
static void
219
gdk_x11_drag_context_finalize (GObject *object)
220
{
221
  GdkDragContext *context = GDK_DRAG_CONTEXT (object);
222
  GdkX11DragContext *x11_context = GDK_X11_DRAG_CONTEXT (object);
223 224

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

230 231
  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
232

233
  contexts = g_list_remove (contexts, context);
234

235
  G_OBJECT_CLASS (gdk_x11_drag_context_parent_class)->finalize (object);
236
}
237

238 239
/* Drag Contexts */

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

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

256
      if ((context->source_window && gdk_window_get_display (context->source_window) != display) ||
257
          (context->dest_window && gdk_window_get_display (context->dest_window) != display))
Matthias Clasen's avatar
Matthias Clasen committed
258
        continue;
259

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

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

273 274 275
  return NULL;
}

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

      _gdk_x11_precache_atoms (GDK_WINDOW_DISPLAY (context->source_window),
289 290
                               (const gchar **)targets->pdata,
                               targets->len);
291 292

      for (i =0; i < targets->len; i++)
293
        g_free (targets->pdata[i]);
294 295 296 297 298

      g_ptr_array_free (targets, TRUE);
    }
}

299 300
/* Utility functions */

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

  if (child->shape_selected && display)
    {
310
      GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
311 312 313 314 315 316 317

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

  g_free (child);
}

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

  child->xid = xid;
  child->x = x;
  child->y = y;
  child->width = width;
  child->height = height;
  child->mapped = mapped;
335 336 337
  child->shape_selected = FALSE;
  child->shape_valid = FALSE;
  child->shape = NULL;
338 339

  cache->children = g_list_prepend (cache->children, child);
340 341
  g_hash_table_insert (cache->child_hash, GUINT_TO_POINTER (xid),
                       cache->children);
342 343
}

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

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

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

      return GDK_FILTER_REMOVE;
    }

  return GDK_FILTER_CONTINUE;
}

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

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

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

512 513 514 515
  GdkWindowCache *result = g_new (GdkWindowCache, 1);

  result->children = NULL;
  result->child_hash = g_hash_table_new (g_direct_hash, NULL);
516
  result->screen = screen;
517
  result->ref_count = 1;
518

519
  XGetWindowAttributes (xdisplay, GDK_WINDOW_XID (root_window), &xwa);
520
  result->old_event_mask = xwa.your_event_mask;
521

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

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

545
  XSelectInput (xdisplay, GDK_WINDOW_XID (root_window),
546
                result->old_event_mask | SubstructureNotifyMask);
547
  gdk_window_add_filter (root_window, gdk_window_cache_filter, result);
548
  gdk_window_add_filter (NULL, gdk_window_cache_shape_filter, result);
549

550
  if (!_gdk_x11_get_window_child_info (gdk_screen_get_display (screen),
551 552 553
                                       GDK_WINDOW_XID (root_window),
                                       FALSE, NULL,
                                       &children, &nchildren))
554
    return result;
555

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

563
  g_free (children);
564

565
#ifdef HAVE_XCOMPOSITE
566 567 568 569 570 571 572 573 574
  /*
   * 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))
    {
575
      cow = XCompositeGetOverlayWindow (xdisplay, GDK_WINDOW_XID (root_window));
576 577 578 579
      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);
580
      XCompositeReleaseOverlayWindow (xdisplay, GDK_WINDOW_XID (root_window));
581
    }
582
#endif
583

584 585 586 587 588 589
  return result;
}

static void
gdk_window_cache_destroy (GdkWindowCache *cache)
{
590
  GdkWindow *root_window = gdk_screen_get_root_window (cache->screen);
591
  GdkDisplay *display;
592 593

  XSelectInput (GDK_WINDOW_XDISPLAY (root_window),
594 595
                GDK_WINDOW_XID (root_window),
                cache->old_event_mask);
596
  gdk_window_remove_filter (root_window, gdk_window_cache_filter, cache);
597
  gdk_window_remove_filter (NULL, gdk_window_cache_shape_filter, cache);
598

599
  display = gdk_screen_get_display (cache->screen);
600

601 602 603
  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);
604

605 606
  g_list_free (cache->children);
  g_hash_table_destroy (cache->child_hash);
607 608

  g_free (cache);
609 610
}

611 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
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);
    }

646
  cache = gdk_window_cache_new (screen);
647

648
  window_caches = g_slist_prepend (window_caches, cache);
649

650
  return cache;
651 652
}

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

663 664 665 666 667 668 669
      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;
670

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

681 682 683 684 685 686 687 688 689 690
      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
691

692
      child->shape_valid = TRUE;
693 694
    }

695 696
  return child->shape == NULL ||
         cairo_region_contains_point (child->shape, x_pos, y_pos);
697 698
}

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

713 714 715 716
  if (!_gdk_x11_get_window_child_info (display, win, TRUE,
                                       is_toplevel? &has_wm_state : NULL,
                                       &children, &nchildren))
    return None;
717

718 719 720
  if (has_wm_state)
    {
      g_free (children);
721

722 723
      return win;
    }
724

725 726 727
  for (i = nchildren - 1; (i >= 0) && !found_child; i--)
    {
      GdkChildInfoX11 *cur_child = &children[i];
728

729 730 731 732 733 734 735 736 737 738
      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;
        }
    }
739

740
  g_free (children);
741

742 743 744 745 746 747 748 749 750
  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;
751
}
752

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

763
  display = gdk_screen_get_display (cache->screen);
764

765
  gdk_x11_display_error_trap_push (display);
766

767
  tmp_list = cache->children;
768

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

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

797
  gdk_x11_display_error_trap_pop_ignored (display);
798

799 800 801 802 803
  if (retval)
    return retval;
  else
    return GDK_WINDOW_XID (gdk_screen_get_root_window (cache->screen));
}
804

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

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

/* Utility functions */

static struct {
826
  const gchar *name;
827
  GdkAtom atom;
828 829
  GdkDragAction action;
} xdnd_actions_table[] = {
830 831 832 833 834
    { "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 },
835 836
  };

837
static const gint xdnd_n_actions = G_N_ELEMENTS (xdnd_actions_table);
838 839 840 841 842 843
static gboolean xdnd_actions_initialized = FALSE;

static void
xdnd_initialize_actions (void)
{
  gint i;
844

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

static GdkDragAction
851
xdnd_action_from_atom (GdkDisplay *display,
852
                       Atom        xatom)
853
{
854
  GdkAtom atom;
855 856
  gint i;

857 858 859 860 861
  if (xatom == None)
    return 0;

  atom = gdk_x11_xatom_to_atom_for_display (display, xatom);

862 863 864
  if (!xdnd_actions_initialized)
    xdnd_initialize_actions();

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

  return 0;
}

872
static Atom
873
xdnd_action_to_atom (GdkDisplay    *display,
874
                     GdkDragAction  action)
875 876 877 878 879 880
{
  gint i;

  if (!xdnd_actions_initialized)
    xdnd_initialize_actions();

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

885
  return None;
886 887 888 889
}

/* Source side */

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

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

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

910
  display = gdk_window_get_display (event->any.window);
911
  context = gdk_drag_context_find (display, TRUE, xevent->xclient.window, dest_window);
912

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

919 920 921
      event->dnd.send_event = FALSE;
      event->dnd.type = GDK_DRAG_STATUS;
      event->dnd.context = context;
922
      gdk_event_set_device (event, gdk_drag_context_get_device (context));
923
      g_object_ref (context);
924 925 926

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

933
      context->action = xdnd_action_from_atom (display, action);
934 935 936 937 938 939 940

      return GDK_FILTER_TRANSLATE;
    }

  return GDK_FILTER_REMOVE;
}

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

  if (!event->any.window ||
      gdk_window_get_window_type (event->any.window) == GDK_WINDOW_FOREIGN)
954 955 956 957
    return GDK_FILTER_CONTINUE;                 /* Not for us */

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

959
  display = gdk_window_get_display (event->any.window);
960
  context = gdk_drag_context_find (display, TRUE, xevent->xclient.window, dest_window);
961

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

968 969
      event->dnd.type = GDK_DROP_FINISHED;
      event->dnd.context = context;
970
      gdk_event_set_device (event, gdk_drag_context_get_device (context));
971
      g_object_ref (context);
972

973 974
      event->dnd.time = GDK_CURRENT_TIME; /* FIXME? */

975 976 977 978 979 980 981
      return GDK_FILTER_TRANSLATE;
    }

  return GDK_FILTER_REMOVE;
}

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

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

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

1006 1007
  g_free (atomlist);

Matthias Clasen's avatar
Matthias Clasen committed
1008
  context_x11->xdnd_targets_set = 1;
1009 1010
}

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

  if (!xdnd_actions_initialized)
    xdnd_initialize_actions();
1023

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

1035
  atomlist = g_new (Atom, n_atoms);
1036 1037 1038

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