gdkdnd-x11.c 118 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
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>
44
#ifdef HAVE_XCOMPOSITE
45
#include <X11/extensions/Xcomposite.h>
46
#endif
47 48 49

#include <string.h>

50 51 52 53 54 55 56 57 58 59 60
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;
61 62
  gboolean shape_selected;
  gboolean shape_valid;
63
  cairo_region_t *shape;
64 65 66 67 68 69
} GdkCacheChild;

typedef struct {
  GList *children;
  GHashTable *child_hash;
  guint old_event_mask;
70
  GdkScreen *screen;
71
  gint ref_count;
72 73
} GdkWindowCache;

Matthias Clasen's avatar
Matthias Clasen committed
74

75 76
struct _GdkX11DragContext
{
77 78
  GdkDragContext context;

79
  Atom motif_selection;
80 81
  guint   ref_count;

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

  GSList *window_caches;
90 91 92 93 94 95 96 97 98

  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 */
99 100
};

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

106 107
/* Forward declarations */

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

112 113
static void motif_read_target_table (GdkDisplay *display);

114
static GdkFilterReturn motif_dnd_filter (GdkXEvent *xev,
115 116
                                         GdkEvent  *event,
                                         gpointer   data);
117

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

137
static void   xdnd_manage_source_filter (GdkDragContext *context,
138 139
                                         GdkWindow      *window,
                                         gboolean        add_filter);
140

141
static GList *contexts;
142
static GSList *window_caches;
143

Matthias Clasen's avatar
Matthias Clasen committed
144
static const struct {
145 146
  const char *atom_name;
  GdkFilterFunc func;
Matthias Clasen's avatar
Matthias Clasen committed
147
} xdnd_filters[] = {
148 149 150 151 152 153 154 155
  { "_MOTIF_DRAG_AND_DROP_MESSAGE", motif_dnd_filter },

  { "XdndEnter",                    xdnd_enter_filter },
  { "XdndLeave",                    xdnd_leave_filter },
  { "XdndPosition",                 xdnd_position_filter },
  { "XdndStatus",                   xdnd_status_filter },
  { "XdndFinished",                 xdnd_finished_filter },
  { "XdndDrop",                     xdnd_drop_filter },
156
};
157 158


159
G_DEFINE_TYPE (GdkX11DragContext, gdk_x11_drag_context, GDK_TYPE_DRAG_CONTEXT)
160

Matthias Clasen's avatar
Matthias Clasen committed
161
static void
162
gdk_x11_drag_context_init (GdkX11DragContext *context)
Matthias Clasen's avatar
Matthias Clasen committed
163 164 165 166
{
  contexts = g_list_prepend (contexts, context);
}

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

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

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

206 207 208 209 210 211 212 213 214
  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;
215 216
}

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

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

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

232
  contexts = g_list_remove (contexts, context);
233

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

237 238
/* Drag Contexts */

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

  while (tmp_list)
    {
Jeff Garzik's avatar
Jeff Garzik committed
252
      context = (GdkDragContext *)tmp_list->data;
253
      context_x11 = (GdkX11DragContext *)context;
254

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

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

      if ((!context->is_source == !is_source) &&
Matthias Clasen's avatar
Matthias Clasen committed
266 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 272
      tmp_list = tmp_list->next;
    }
Matthias Clasen's avatar
Matthias Clasen committed
273

274 275 276
  return NULL;
}

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

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

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

      g_ptr_array_free (targets, TRUE);
    }
}

300 301
/* Utility functions */

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

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

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

  g_free (child);
}

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

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

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

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

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

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

      return GDK_FILTER_REMOVE;
    }

  return GDK_FILTER_CONTINUE;
}

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

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

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

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

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

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

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

529
      toplevel_windows = gdk_screen_get_toplevel_windows (screen);
530 531 532 533 534 535 536 537
      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));
        }
538 539 540 541
      g_list_free (toplevel_windows);
      return result;
    }

542
  XSelectInput (xdisplay, GDK_WINDOW_XID (root_window),
543
                result->old_event_mask | SubstructureNotifyMask);
544
  gdk_window_add_filter (root_window, gdk_window_cache_filter, result);
545
  gdk_window_add_filter (NULL, gdk_window_cache_shape_filter, result);
546

547
  if (!_gdk_x11_get_window_child_info (gdk_screen_get_display (screen),
548 549 550
                                       GDK_WINDOW_XID (root_window),
                                       FALSE, NULL,
                                       &children, &nchildren))
551
    return result;
552

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

560
  g_free (children);
561

562
#ifdef HAVE_XCOMPOSITE
563 564 565 566 567 568 569 570 571
  /*
   * 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))
    {
572
      cow = XCompositeGetOverlayWindow (xdisplay, GDK_WINDOW_XID (root_window));
573
      gdk_window_cache_add (result, cow, 0, 0, gdk_screen_get_width (screen), gdk_screen_get_height (screen), TRUE);
574
      XCompositeReleaseOverlayWindow (xdisplay, GDK_WINDOW_XID (root_window));
575
    }
576
#endif
577

578 579 580 581 582 583
  return result;
}

static void
gdk_window_cache_destroy (GdkWindowCache *cache)
{
584
  GdkWindow *root_window = gdk_screen_get_root_window (cache->screen);
585
  GdkDisplay *display;
586 587

  XSelectInput (GDK_WINDOW_XDISPLAY (root_window),
588 589
                GDK_WINDOW_XID (root_window),
                cache->old_event_mask);
590
  gdk_window_remove_filter (root_window, gdk_window_cache_filter, cache);
591
  gdk_window_remove_filter (NULL, gdk_window_cache_shape_filter, cache);
592

593
  display = gdk_screen_get_display (cache->screen);
594

595 596 597
  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);
598

599 600
  g_list_free (cache->children);
  g_hash_table_destroy (cache->child_hash);
601 602

  g_free (cache);
603 604
}

605 606 607 608 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 646
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);
    }

  cache = gdk_window_cache_new (screen);

  window_caches = g_slist_prepend (window_caches, cache);

  return cache;
}

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

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

665 666 667 668
      child->shape = NULL;
      if (gdk_display_supports_shapes (display))
        child->shape = _gdk_x11_xwindow_get_shape (display_x11->xdisplay,
                                                   child->xid, ShapeBounding);
669
#ifdef ShapeInput
670 671 672 673 674
      input_shape = NULL;
      if (gdk_display_supports_input_shapes (display))
        input_shape = _gdk_x11_xwindow_get_shape (display_x11->xdisplay,
                                                  child->xid, ShapeInput);

675 676
      if (child->shape && input_shape)
        {
Benjamin Otte's avatar
Benjamin Otte committed
677 678
          cairo_region_intersect (child->shape, input_shape);
          cairo_region_destroy (input_shape);
679 680 681 682 683 684 685 686 687 688 689
        }
      else if (input_shape)
        {
          child->shape = input_shape;
        }
#endif

      child->shape_valid = TRUE;
    }

  return child->shape == NULL ||
Benjamin Otte's avatar
Benjamin Otte committed
690
         cairo_region_contains_point (child->shape, x_pos, y_pos);
691 692
}

693 694
static Window
get_client_window_at_coords_recurse (GdkDisplay *display,
695 696 697 698
                                     Window      win,
                                     gboolean    is_toplevel,
                                     gint        x,
                                     gint        y)
699 700 701 702 703
{
  GdkChildInfoX11 *children;
  unsigned int nchildren;
  int i;
  gboolean found_child = FALSE;
704
  GdkChildInfoX11 child = { 0, };
705
  gboolean has_wm_state = FALSE;
706 707

  if (!_gdk_x11_get_window_child_info (display, win, TRUE,
708 709
                                       is_toplevel? &has_wm_state : NULL,
                                       &children, &nchildren))
710
    return None;
711

712
  if (has_wm_state)
713 714 715 716 717
    {
      g_free (children);

      return win;
    }
718

719
  for (i = nchildren - 1; (i >= 0) && !found_child; i--)
720
    {
721
      GdkChildInfoX11 *cur_child = &children[i];
722

723
      if ((cur_child->is_mapped) && (cur_child->window_class == InputOutput) &&
724 725 726 727 728 729 730 731 732 733
          (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;
        }
    }

734
  g_free (children);
735

736 737 738
  if (found_child)
    {
      if (child.has_wm_state)
739
        return child.window;
740
      else
741
        return get_client_window_at_coords_recurse (display, child.window, FALSE, x, y);
742
    }
743 744 745 746
  else
    return None;
}

747
static Window
748
get_client_window_at_coords (GdkWindowCache *cache,
749 750 751
                             Window          ignore,
                             gint            x_root,
                             gint            y_root)
752 753 754
{
  GList *tmp_list;
  Window retval = None;
755 756 757 758 759
  GdkDisplay *display;

  display = gdk_screen_get_display (cache->screen);

  gdk_x11_display_error_trap_push (display);
760 761 762 763 764 765 766 767

  tmp_list = cache->children;

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

      if ((child->xid != ignore) && (child->mapped))
768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787
        {
          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;
            }
        }
788 789 790
      tmp_list = tmp_list->next;
    }

791
  gdk_x11_display_error_trap_pop_ignored (display);
792

793 794 795
  if (retval)
    return retval;
  else
796
    return GDK_WINDOW_XID (gdk_screen_get_root_window (cache->screen));
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
}

/*************************************************************
 ***************************** 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
 */
853 854 855 856 857
#if G_BYTE_ORDER == G_BIG_ENDIAN
static gchar local_byte_order = 'B';
#else
static gchar local_byte_order = 'l';
#endif
858 859 860 861 862 863 864

#ifdef G_ENABLE_DEBUG
static void
print_target_list (GList *targets)
{
  while (targets)
    {
865
      gchar *name = gdk_atom_name (GDK_POINTER_TO_ATOM (targets->data));
866 867 868 869 870 871 872 873
      g_message ("\t%s", name);
      g_free (name);
      targets = targets->next;
    }
}
#endif /* G_ENABLE_DEBUG */

static guint16
874 875
card16_to_host (guint16 x, gchar byte_order)
{
876 877 878 879 880 881 882
  if (byte_order == local_byte_order)
    return x;
  else
    return (x << 8) | (x >> 8);
}

static guint32
883 884
card32_to_host (guint32 x, gchar byte_order)
{
885 886 887 888 889 890
  if (byte_order == local_byte_order)
    return x;
  else
    return (x << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | (x >> 24);
}

891 892 893
/* 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
894
 * (i.e., Alpha) these won't be valid for format == 8.
895 896 897 898 899 900 901 902 903 904 905 906 907 908
 */

#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))
909 910 911 912

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

/* Property placed on source windows */
913 914
typedef struct _MotifDragInitiatorInfo
{
915 916 917 918 919 920 921
  guint8 byte_order;
  guint8 protocol_version;
  guint16 targets_index;
  guint32 selection_atom;
} MotifDragInitiatorInfo;

/* Header for target table on the drag window */
922 923
typedef struct _MotifTargetTableHeader
{
924 925 926 927 928 929 930
  guchar byte_order;
  guchar protocol_version;
  guint16 n_lists;
  guint32 total_size;
} MotifTargetTableHeader;

/* Property placed on target windows */
931 932
typedef struct _MotifDragReceiverInfo
{
933 934 935 936 937 938 939 940 941 942 943 944
  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 */

945
static GdkFilterReturn
946
motif_drag_window_filter (GdkXEvent *xevent,
947 948
                          GdkEvent  *event,
                          gpointer data)
949 950
{
  XEvent *xev = (XEvent *)xevent;
951
  GdkDisplay *display = GDK_WINDOW_DISPLAY (event->any.window);
952
  GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
953 954 955 956

  switch (xev->xany.type)
    {
    case DestroyNotify:
957 958
      display_x11->motif_drag_window = None;
      display_x11->motif_drag_gdk_window = NULL;
959 960
      break;
    case PropertyNotify:
961
      if (display_x11->motif_target_lists &&
962 963
          (xev->xproperty.atom == gdk_x11_get_xatom_by_name_for_display (display, "_MOTIF_DRAG_TARGETS")))
        motif_read_target_table (display);
964 965 966 967 968 969
      break;
    }
  return GDK_FILTER_REMOVE;
}

static Window
970
motif_lookup_drag_window (GdkDisplay *display,
971
                          Display    *lookup_xdisplay)
972 973 974
{
  Window retval = None;
  gulong bytes_after, nitems;
975
  Atom type;
976 977 978
  gint format;
  guchar *data;

979
  XGetWindowProperty (lookup_xdisplay, RootWindow (lookup_xdisplay, 0),
980 981 982 983 984
                      gdk_x11_get_xatom_by_name_for_display (display, "_MOTIF_DRAG_WINDOW"),
                      0, 1, FALSE,
                      XA_WINDOW, &type, &format, &nitems, &bytes_after,
                      &data);

985
  if ((format == 32) && (nitems == 1) && (bytes_after == 0))
986 987
    {
      retval = *(Window *)data;
988 989
      GDK_NOTE (DND,
                g_message ("Found drag window %#lx\n", GDK_X11_DISPLAY (display)->motif_drag_window));
990 991 992 993 994 995 996 997 998 999 1000
    }

  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.
 */
1001
static Window
1002
motif_find_drag_window (GdkDisplay *display,
1003
                        gboolean    create)
1004
{
1005
  GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
1006

1007
  if (!display_x11->motif_drag_window)
1008
    {
1009 1010
      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);
1011

1012
      if (!display_x11->motif_drag_window && create)
1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047
        {
          /* 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);
        }
1048 1049 1050 1051

      /* There is a miniscule race condition here if the drag window
       * gets destroyed exactly now.
       */
1052
      if (display_x11->motif_drag_window)
1053 1054 1055 1056 1057 1058 1059
        {
          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);
        }
1060 1061
    }

1062
  return display_x11->motif_drag_window;
1063 1064
}

1065
static void
1066
motif_read_target_table (GdkDisplay *display)
1067
{
1068
  GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
1069
  gulong bytes_after, nitems;
1070
  Atom type;
1071 1072
  gint format;
  gint i, j;
1073

1074
  Atom motif_drag_targets_atom = gdk_x11_get_xatom_by_name_for_display (display, "_MOTIF_DRAG_TARGETS");
1075

1076
  if (display_x11->motif_target_lists)
1077
    {
1078
      for (i=0; i<display_x11->motif_n_target_lists; i++)
1079 1080
        g_list_free (display_x11->motif_target_lists[i]);

1081 1082 1083
      g_free (display_x11->motif_target_lists);
      display_x11->motif_target_lists = NULL;
      display_x11->motif_n_target_lists = 0;
1084 1085
    }

1086
  if (motif_find_drag_window (display, FALSE))
1087
    {
1088
      guchar *data;
1089 1090 1091 1092 1093
      MotifTargetTableHeader *header = NULL;
      guchar *target_bytes = NULL;
      guchar *p;
      gboolean success = FALSE;

1094 1095 1096 1097 1098 1099 1100 1101
      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);
1102

1103 1104 1105
      if (gdk_x11_display_error_trap_pop (display) ||
          (format != 8) || (nitems < sizeof (MotifTargetTableHeader)))
        goto error;
1106

1107 1108
      header = (MotifTargetTableHeader *)data;

1109 1110 1111
      header->n_lists = card16_to_host (header->n_lists, header->byte_order);
      header->total_size = card32_to_host (header->total_size, header->byte_order);

1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125
      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;
1126

1127 1128
      display_x11->motif_n_target_lists = header->n_lists;
      display_x11->motif_target_lists = g_new0 (GList *, display_x11->motif_n_target_lists);
1129 1130 1131

      p = target_bytes;
      for (i=0; i<header->n_lists; i++)
1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158
        {
          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]);
        }
1159 1160

      success = TRUE;
1161

1162 1163
    error:
      if (header)
1164 1165
        XFree (header);

1166
      if (target_bytes)
1167
        XFree (target_bytes);
1168 1169

      if (!success)
1170 1171 1172 1173 1174 1175 1176 1177 1178
        {
          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");
        }
1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189
    }
}

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 */
1190
static gint
1191
motif_target_table_check (GdkDisplay *display,
1192
                          GList      *sorted)
1193
{
1194
  GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
1195 1196 1197
  GList *tmp_list1, *tmp_list2;
  gint i;

1198
  for (i=0; i<display_x11->motif_n_target_lists; i++)
1199
    {
1200
      tmp_list1 = display_x11->motif_target_lists[i];
1201
      tmp_list2 = sorted;
1202

1203
      while (tmp_list1 && tmp_list2)
1204 1205 1206
        {
          if (tmp_list1->data != tmp_list2->data)
            break;
1207

1208 1209 1210 1211 1212
          tmp_list1 = tmp_list1->next;
          tmp_list2 = tmp_list2->next;
        }
      if (!tmp_list1 && !tmp_list2)     /* Found it */
        return i;
1213 1214 1215 1216 1217 1218
    }

  return -1;
}

static gint
1219
motif_add_to_target_table (GdkDisplay *display,
1220
                           GList      *targets) /* targets is list of GdkAtom */
1221
{
1222
  GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
1223 1224 1225 1226
  GList *sorted = NULL;
  gint index = -1;
  gint i;
  GList *tmp_list;