gtkplug.c 39.7 KB
Newer Older
<
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
2 3 4
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * 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 Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.Free
16 17 18 19
 */

/* By Owen Taylor <otaylor@gtk.org>              98/4/4 */

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 "gtkdebug.h"
30
#include "gtkmain.h"
31
#include "gtkmarshalers.h"
32
#include "gtkplug.h"
33
#include "gtkintl.h"
34
#include "gtkprivate.h"
35
#include "gtksocketprivate.h"
Emmanuele Bassi's avatar
Emmanuele Bassi committed
36
#include "gtkwidgetprivate.h"
37
#include "gtkwindowprivate.h"
38
#include "gtkxembed.h"
39

40
#include <gdk/gdkx.h>
41

42 43 44 45
/**
 * SECTION:gtkplug
 * @Short_description: Toplevel for embedding into other processes
 * @Title: GtkPlug
46
 * @include: gtk/gtkx.h
47 48
 * @See_also: #GtkSocket
 *
49 50 51 52 53 54 55
 * Together with #GtkSocket, #GtkPlug provides the ability to embed
 * widgets from one process into another process in a fashion that is
 * transparent to the user. One process creates a #GtkSocket widget
 * and passes the ID of that widget's window to the other process,
 * which then creates a #GtkPlug with that window ID. Any widgets
 * contained in the #GtkPlug then will appear inside the first
 * application's window.
56
 *
57 58
 * The communication between a #GtkSocket and a #GtkPlug follows the
 * <ulink url="http://www.freedesktop.org/Standards/xembed-spec">XEmbed</ulink>
59 60 61 62
 * protocol. This protocol has also been implemented in other toolkits,
 * e.g. <application>Qt</application>, allowing the same level of
 * integration when embedding a <application>Qt</application> widget
 * in GTK+ or vice versa.
63
 *
64
 * <note>
65 66 67 68 69
 * The #GtkPlug and #GtkSocket widgets are only available when GTK+
 * is compiled for the X11 platform and %GDK_WINDOWING_X11 is defined.
 * They can only be used on a #GdkX11Display. To use #GtkPlug and
 * #GtkSocket, you need to include the <filename>gtk/gtkx.h</filename>
 * header.
70 71 72
 * </note>
 */

73 74 75 76 77 78 79 80 81 82 83 84
struct _GtkPlugPrivate
{
  GtkWidget      *modality_window;
  GtkWindowGroup *modality_group;

  GdkWindow      *socket_window;

  GHashTable     *grabbed_keys;

  guint  same_app : 1;
};

85 86 87 88
static void            gtk_plug_get_property          (GObject     *object,
						       guint        prop_id,
						       GValue      *value,
						       GParamSpec  *pspec);
89
static void            gtk_plug_finalize              (GObject          *object);
90 91
static void            gtk_plug_realize               (GtkWidget        *widget);
static void            gtk_plug_unrealize             (GtkWidget        *widget);
92 93 94 95 96 97
static void            gtk_plug_show                  (GtkWidget        *widget);
static void            gtk_plug_hide                  (GtkWidget        *widget);
static void            gtk_plug_map                   (GtkWidget        *widget);
static void            gtk_plug_unmap                 (GtkWidget        *widget);
static void            gtk_plug_size_allocate         (GtkWidget        *widget,
						       GtkAllocation    *allocation);
98 99
static gboolean        gtk_plug_key_press_event       (GtkWidget        *widget,
						       GdkEventKey      *event);
100 101
static gboolean        gtk_plug_focus_event           (GtkWidget        *widget,
						       GdkEventFocus    *event);
102 103
static void            gtk_plug_set_focus             (GtkWindow        *window,
						       GtkWidget        *focus);
104
static gboolean        gtk_plug_focus                 (GtkWidget        *widget,
105
						       GtkDirectionType  direction);
106
static void            gtk_plug_check_resize          (GtkContainer     *container);
107
static void            gtk_plug_keys_changed          (GtkWindow        *window);
108

109 110 111
static void            xembed_set_info                (GdkWindow        *window,
				                       unsigned long     flags);

112
static GtkBinClass *bin_class = NULL;
113

114 115 116 117 118 119
typedef struct
{
  guint			 accelerator_key;
  GdkModifierType	 accelerator_mods;
} GrabbedKey;

120 121 122
enum {
  PROP_0,
  PROP_EMBEDDED,
123
  PROP_SOCKET_WINDOW
124 125
};

126 127 128 129 130 131 132
enum {
  EMBEDDED,
  LAST_SIGNAL
}; 

static guint plug_signals[LAST_SIGNAL] = { 0 };

Matthias Clasen's avatar
Matthias Clasen committed
133
G_DEFINE_TYPE (GtkPlug, gtk_plug, GTK_TYPE_WINDOW)
134

135 136 137 138 139 140 141
static void
gtk_plug_get_property (GObject    *object,
		       guint       prop_id,
		       GValue     *value,
		       GParamSpec *pspec)
{
  GtkPlug *plug = GTK_PLUG (object);
142
  GtkPlugPrivate *priv = plug->priv;
143 144 145 146

  switch (prop_id)
    {
    case PROP_EMBEDDED:
147
      g_value_set_boolean (value, priv->socket_window != NULL);
148
      break;
149
    case PROP_SOCKET_WINDOW:
150
      g_value_set_object (value, priv->socket_window);
151
      break;
152 153 154 155 156 157
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

158 159 160
static void
gtk_plug_class_init (GtkPlugClass *class)
{
161
  GObjectClass *gobject_class = (GObjectClass *)class;
162 163
  GtkWidgetClass *widget_class = (GtkWidgetClass *)class;
  GtkWindowClass *window_class = (GtkWindowClass *)class;
164
  GtkContainerClass *container_class = (GtkContainerClass *)class;
165

Manish Singh's avatar
Manish Singh committed
166
  bin_class = g_type_class_peek (GTK_TYPE_BIN);
167

168
  gobject_class->get_property = gtk_plug_get_property;
169 170
  gobject_class->finalize = gtk_plug_finalize;
  
171
  widget_class->realize = gtk_plug_realize;
172
  widget_class->unrealize = gtk_plug_unrealize;
173
  widget_class->key_press_event = gtk_plug_key_press_event;
174 175
  widget_class->focus_in_event = gtk_plug_focus_event;
  widget_class->focus_out_event = gtk_plug_focus_event;
176

177 178 179 180 181 182 183 184
  widget_class->show = gtk_plug_show;
  widget_class->hide = gtk_plug_hide;
  widget_class->map = gtk_plug_map;
  widget_class->unmap = gtk_plug_unmap;
  widget_class->size_allocate = gtk_plug_size_allocate;

  widget_class->focus = gtk_plug_focus;

185 186
  gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_PANEL);

187
  container_class->check_resize = gtk_plug_check_resize;
188 189

  window_class->set_focus = gtk_plug_set_focus;
190
  window_class->keys_changed = gtk_plug_keys_changed;
191

192 193 194 195 196 197 198 199 200 201 202
  /**
   * GtkPlug:embedded:
   *
   * %TRUE if the plug is embedded in a socket.
   *
   * Since: 2.12
   */
  g_object_class_install_property (gobject_class,
				   PROP_EMBEDDED,
				   g_param_spec_boolean ("embedded",
							 P_("Embedded"),
203
							 P_("Whether the plug is embedded"),
204 205 206
							 FALSE,
							 GTK_PARAM_READABLE));

207 208 209 210 211
  /**
   * GtkPlug:socket-window:
   *
   * The window of the socket the plug is embedded in.
   *
212
   * Since: 2.14
213 214 215
   */
  g_object_class_install_property (gobject_class,
				   PROP_SOCKET_WINDOW,
216
				   g_param_spec_object ("socket-window",
217 218 219 220 221
							P_("Socket Window"),
							P_("The window of the socket the plug is embedded in"),
							GDK_TYPE_WINDOW,
							GTK_PARAM_READABLE));

Matthias Clasen's avatar
Matthias Clasen committed
222 223 224 225
  /**
   * GtkPlug::embedded:
   * @plug: the object on which the signal was emitted
   *
226
   * Gets emitted when the plug becomes embedded in a socket.
Matthias Clasen's avatar
Matthias Clasen committed
227
   */ 
228
  plug_signals[EMBEDDED] =
229
    g_signal_new (I_("embedded"),
230 231 232 233
		  G_OBJECT_CLASS_TYPE (class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkPlugClass, embedded),
		  NULL, NULL,
234
		  _gtk_marshal_VOID__VOID,
Manish Singh's avatar
Manish Singh committed
235
		  G_TYPE_NONE, 0);
236 237

  g_type_class_add_private (class, sizeof (GtkPlugPrivate));
238 239 240 241 242
}

static void
gtk_plug_init (GtkPlug *plug)
{
243 244 245
  plug->priv = G_TYPE_INSTANCE_GET_PRIVATE (plug,
                                            GTK_TYPE_PLUG,
                                            GtkPlugPrivate);
246
}
247

248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
/**
 * gtk_plug_handle_modality_on:
 * @plug: a #GtkPlug
 *
 * Called from the GtkPlug backend when the corresponding socket has
 * told the plug that it modality has toggled on.
 */
static void
gtk_plug_handle_modality_on (GtkPlug *plug)
{
  GtkPlugPrivate *priv = plug->priv;

  if (!priv->modality_window)
    {
      priv->modality_window = gtk_window_new (GTK_WINDOW_POPUP);
      gtk_window_set_screen (GTK_WINDOW (priv->modality_window),
			     gtk_widget_get_screen (GTK_WIDGET (plug)));
      gtk_widget_realize (priv->modality_window);
      gtk_window_group_add_window (priv->modality_group, GTK_WINDOW (priv->modality_window));
      gtk_grab_add (priv->modality_window);
    }
}

/**
 * gtk_plug_handle_modality_off:
 * @plug: a #GtkPlug
 *
 * Called from the GtkPlug backend when the corresponding socket has
 * told the plug that it modality has toggled off.
 */
static void
gtk_plug_handle_modality_off (GtkPlug *plug)
{
  GtkPlugPrivate *priv = plug->priv;

  if (priv->modality_window)
    {
      gtk_widget_destroy (priv->modality_window);
      priv->modality_window = NULL;
    }
}

290 291 292 293
static void
gtk_plug_set_is_child (GtkPlug  *plug,
		       gboolean  is_child)
{
294
  GtkPlugPrivate *priv = plug->priv;
295 296 297 298
  GtkWidget *widget = GTK_WIDGET (plug);

  g_assert (!gtk_widget_get_parent (widget));

299 300
  if (is_child)
    {
301
      if (priv->modality_window)
302
	gtk_plug_handle_modality_off (plug);
303

304
      if (priv->modality_group)
305
	{
306 307 308
	  gtk_window_group_remove_window (priv->modality_group, GTK_WINDOW (plug));
	  g_object_unref (priv->modality_group);
	  priv->modality_group = NULL;
309 310
	}
      
311 312 313 314 315
      /* As a toplevel, the MAPPED flag doesn't correspond
       * to whether the widget->window is mapped; we unmap
       * here, but don't bother remapping -- we will get mapped
       * by gtk_widget_set_parent ().
       */
316 317 318
      if (gtk_widget_get_mapped (widget))
	gtk_widget_unmap (widget);

319
      _gtk_window_set_is_toplevel (GTK_WINDOW (plug), FALSE);
320
      gtk_container_set_resize_mode (GTK_CONTAINER (plug), GTK_RESIZE_PARENT);
321

322
      _gtk_widget_propagate_hierarchy_changed (widget, widget);
323 324 325
    }
  else
    {
326
      if (gtk_window_get_focus (GTK_WINDOW (plug)))
327
	gtk_window_set_focus (GTK_WINDOW (plug), NULL);
328
      if (gtk_window_get_default_widget (GTK_WINDOW (plug)))
329
	gtk_window_set_default (GTK_WINDOW (plug), NULL);
330 331 332 333

      priv->modality_group = gtk_window_group_new ();
      gtk_window_group_add_window (priv->modality_group, GTK_WINDOW (plug));

334
      _gtk_window_set_is_toplevel (GTK_WINDOW (plug), TRUE);
335 336 337
      gtk_container_set_resize_mode (GTK_CONTAINER (plug), GTK_RESIZE_QUEUE);

      _gtk_widget_propagate_hierarchy_changed (GTK_WIDGET (plug), NULL);
338 339 340
    }
}

341 342 343 344 345 346 347 348 349 350
/**
 * gtk_plug_get_id:
 * @plug: a #GtkPlug.
 * 
 * Gets the window ID of a #GtkPlug widget, which can then
 * be used to embed this window inside another window, for
 * instance with gtk_socket_add_id().
 * 
 * Return value: the window ID for the plug
 **/
351
Window
352 353 354 355
gtk_plug_get_id (GtkPlug *plug)
{
  g_return_val_if_fail (GTK_IS_PLUG (plug), 0);

356
  if (!gtk_widget_get_realized (GTK_WIDGET (plug)))
357 358
    gtk_widget_realize (GTK_WIDGET (plug));

359
  return GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (plug)));
360 361
}

Tim Janik's avatar
Tim Janik committed
362 363 364 365 366
/**
 * gtk_plug_get_embedded:
 * @plug: a #GtkPlug
 *
 * Determines whether the plug is embedded in a socket.
367
 *
Tim Janik's avatar
Tim Janik committed
368
 * Return value: %TRUE if the plug is embedded in a socket
369 370
 *
 * Since: 2.14
Tim Janik's avatar
Tim Janik committed
371 372 373 374
 **/
gboolean
gtk_plug_get_embedded (GtkPlug *plug)
{
375
  g_return_val_if_fail (GTK_IS_PLUG (plug), FALSE);
Tim Janik's avatar
Tim Janik committed
376

377
  return plug->priv->socket_window != NULL;
Tim Janik's avatar
Tim Janik committed
378 379
}

380 381 382 383 384 385
/**
 * gtk_plug_get_socket_window:
 * @plug: a #GtkPlug
 *
 * Retrieves the socket the plug is embedded in.
 *
386
 * Return value: (transfer none): the window of the socket, or %NULL
387 388
 *
 * Since: 2.14
389 390 391 392 393 394
 **/
GdkWindow *
gtk_plug_get_socket_window (GtkPlug *plug)
{
  g_return_val_if_fail (GTK_IS_PLUG (plug), NULL);

395
  return plug->priv->socket_window;
396 397
}

398 399 400
/**
 * _gtk_plug_add_to_socket:
 * @plug: a #GtkPlug
401
 * @socket_: a #GtkSocket
402
 * 
403
 * Adds a plug to a socket within the same application.
404
 **/
405
void
406
_gtk_plug_add_to_socket (GtkPlug   *plug,
Matthias Clasen's avatar
Matthias Clasen committed
407
			 GtkSocket *socket_)
408
{
409
  GtkPlugPrivate *priv;
410
  GtkWidget *widget;
411 412
  
  g_return_if_fail (GTK_IS_PLUG (plug));
Matthias Clasen's avatar
Matthias Clasen committed
413
  g_return_if_fail (GTK_IS_SOCKET (socket_));
414
  g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (socket_)));
415

416
  priv = plug->priv;
417 418
  widget = GTK_WIDGET (plug);

419
  gtk_plug_set_is_child (plug, TRUE);
420
  priv->same_app = TRUE;
421 422
  socket_->priv->same_app = TRUE;
  socket_->priv->plug_widget = widget;
423

424 425
  priv->socket_window = gtk_widget_get_window (GTK_WIDGET (socket_));
  g_object_ref (priv->socket_window);
426 427
  g_signal_emit (plug, plug_signals[EMBEDDED], 0);
  g_object_notify (G_OBJECT (plug), "embedded");
428

429
  if (gtk_widget_get_realized (widget))
430
    {
431 432 433
      GdkWindow *window;

      window = gtk_widget_get_window (widget);
434 435 436
      gdk_window_reparent (window, priv->socket_window,
                           -gdk_window_get_width (window),
                           -gdk_window_get_height (window));
437
    }
438

Matthias Clasen's avatar
Matthias Clasen committed
439
  gtk_widget_set_parent (widget, GTK_WIDGET (socket_));
440

441
  g_signal_emit_by_name (socket_, "plug-added");
442 443
}

444 445
/*
 * gtk_plug_send_delete_event:
Matthias Clasen's avatar
Matthias Clasen committed
446
 * @widget: a #GtkWidget
447 448 449 450 451
 *
 * Send a GDK_DELETE event to the @widget and destroy it if
 * necessary. Internal GTK function, called from this file or the
 * backend-specific GtkPlug implementation.
 */
452 453
static void
gtk_plug_send_delete_event (GtkWidget *widget)
454 455
{
  GdkEvent *event = gdk_event_new (GDK_DELETE);
456

457
  event->any.window = g_object_ref (gtk_widget_get_window (widget));
458 459
  event->any.send_event = FALSE;

460 461
  g_object_ref (widget);

462 463
  if (!gtk_widget_event (widget, event))
    gtk_widget_destroy (widget);
464 465

  g_object_unref (widget);
466 467 468 469

  gdk_event_free (event);
}

470
/**
471
 * _gtk_plug_remove_from_socket:
472
 * @plug: a #GtkPlug
Matthias Clasen's avatar
Matthias Clasen committed
473
 * @socket_: a #GtkSocket
474
 * 
475
 * Removes a plug from a socket within the same application.
476 477 478
 **/
void
_gtk_plug_remove_from_socket (GtkPlug   *plug,
Matthias Clasen's avatar
Matthias Clasen committed
479
			      GtkSocket *socket_)
480
{
481
  GtkPlugPrivate *priv;
482
  GtkWidget *widget;
483
  GdkWindow *window;
484 485 486 487
  gboolean result;
  gboolean widget_was_visible;

  g_return_if_fail (GTK_IS_PLUG (plug));
Matthias Clasen's avatar
Matthias Clasen committed
488
  g_return_if_fail (GTK_IS_SOCKET (socket_));
489
  g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (plug)));
490

491
  priv = plug->priv;
492 493
  widget = GTK_WIDGET (plug);

494
  if (_gtk_widget_get_in_reparent (widget))
495 496
    return;

497
  g_object_ref (plug);
Matthias Clasen's avatar
Matthias Clasen committed
498
  g_object_ref (socket_);
499

500
  widget_was_visible = gtk_widget_get_visible (widget);
501 502 503
  window = gtk_widget_get_window (widget);

  gdk_window_hide (window);
504
  _gtk_widget_set_in_reparent (widget, TRUE);
505
  gdk_window_reparent (window,
506 507
		       gtk_widget_get_root_window (widget),
		       0, 0);
508
  gtk_widget_unparent (GTK_WIDGET (plug));
509
  _gtk_widget_set_in_reparent (widget, FALSE);
510
  
511 512
  socket_->priv->plug_widget = NULL;
  if (socket_->priv->plug_window != NULL)
513
    {
514 515
      g_object_unref (socket_->priv->plug_window);
      socket_->priv->plug_window = NULL;
516
    }
517
  
518
  socket_->priv->same_app = FALSE;
519

520 521
  priv->same_app = FALSE;
  if (priv->socket_window != NULL)
522
    {
523 524
      g_object_unref (priv->socket_window);
      priv->socket_window = NULL;
525
    }
526
  gtk_plug_set_is_child (plug, FALSE);
527

528
  g_signal_emit_by_name (socket_, "plug-removed", &result);
529
  if (!result)
Matthias Clasen's avatar
Matthias Clasen committed
530
    gtk_widget_destroy (GTK_WIDGET (socket_));
531

532
  if (window)
533
    gtk_plug_send_delete_event (widget);
534

535 536
  g_object_unref (plug);

537
  if (widget_was_visible && gtk_widget_get_visible (GTK_WIDGET (socket_)))
Matthias Clasen's avatar
Matthias Clasen committed
538
    gtk_widget_queue_resize (GTK_WIDGET (socket_));
539

Matthias Clasen's avatar
Matthias Clasen committed
540
  g_object_unref (socket_);
541 542
}

543 544 545 546 547
/**
 * gtk_plug_construct:
 * @plug: a #GtkPlug.
 * @socket_id: the XID of the socket's window.
 *
548
 * Finish the initialization of @plug for a given #GtkSocket identified by
549 550
 * @socket_id. This function will generally only be used by classes deriving from #GtkPlug.
 **/
551
void
552 553
gtk_plug_construct (GtkPlug *plug,
		    Window   socket_id)
554
{
Owen Taylor's avatar
Owen Taylor committed
555
  gtk_plug_construct_for_display (plug, gdk_display_get_default (), socket_id);
556 557 558 559
}

/**
 * gtk_plug_construct_for_display:
560
 * @plug: a #GtkPlug.
561 562 563 564
 * @display: the #GdkDisplay associated with @socket_id's 
 *	     #GtkSocket.
 * @socket_id: the XID of the socket's window.
 *
565
 * Finish the initialization of @plug for a given #GtkSocket identified by
566 567
 * @socket_id which is currently displayed on @display.
 * This function will generally only be used by classes deriving from #GtkPlug.
568 569
 *
 * Since: 2.2
570 571
 **/
void
572 573 574
gtk_plug_construct_for_display (GtkPlug    *plug,
				GdkDisplay *display,
				Window      socket_id)
575
{
576 577 578 579 580 581 582
  GtkPlugPrivate *priv;

  g_return_if_fail (GTK_IS_PLUG (plug));
  g_return_if_fail (GDK_IS_DISPLAY (display));

  priv = plug->priv;

583
  if (socket_id)
584
    {
585 586
      gpointer user_data = NULL;

587
      if (GDK_IS_X11_DISPLAY (display))
588 589 590 591
        priv->socket_window = gdk_x11_window_lookup_for_display (display, socket_id);
      else
        priv->socket_window = NULL;

592
      if (priv->socket_window)
593
	{
594
	  gdk_window_get_user_data (priv->socket_window, &user_data);
595 596

	  if (user_data)
597
	    {
598 599 600 601 602
	      if (GTK_IS_SOCKET (user_data))
		_gtk_plug_add_to_socket (plug, user_data);
	      else
		{
		  g_warning (G_STRLOC "Can't create GtkPlug as child of non-GtkSocket");
603
		  priv->socket_window = NULL;
604
		}
605
	    }
606
	  else
607
	    g_object_ref (priv->socket_window);
608
	}
609
      else if (GDK_IS_X11_DISPLAY (display))
610
        priv->socket_window = gdk_x11_window_foreign_new_for_display (display, socket_id);
611

612
      if (priv->socket_window) {
Manish Singh's avatar
Manish Singh committed
613
	g_signal_emit (plug, plug_signals[EMBEDDED], 0);
614 615 616

        g_object_notify (G_OBJECT (plug), "embedded");
      }
617 618
    }
}
619

620 621 622 623 624 625 626 627 628 629
/**
 * gtk_plug_new:
 * @socket_id:  the window ID of the socket, or 0.
 * 
 * Creates a new plug widget inside the #GtkSocket identified
 * by @socket_id. If @socket_id is 0, the plug is left "unplugged" and
 * can later be plugged into a #GtkSocket by  gtk_socket_add_id().
 * 
 * Return value: the new #GtkPlug widget.
 **/
630
GtkWidget*
631
gtk_plug_new (Window socket_id)
632
{
Owen Taylor's avatar
Owen Taylor committed
633
  return gtk_plug_new_for_display (gdk_display_get_default (), socket_id);
634 635 636 637
}

/**
 * gtk_plug_new_for_display:
638
 * @display: the #GdkDisplay on which @socket_id is displayed
639 640
 * @socket_id: the XID of the socket's window.
 * 
641
 * Create a new plug widget inside the #GtkSocket identified by socket_id.
642 643
 *
 * Return value: the new #GtkPlug widget.
644 645
 *
 * Since: 2.2
646 647
 */
GtkWidget*
648 649
gtk_plug_new_for_display (GdkDisplay *display,
			  Window      socket_id)
650 651 652
{
  GtkPlug *plug;

653 654
  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

Manish Singh's avatar
Manish Singh committed
655
  plug = g_object_new (GTK_TYPE_PLUG, NULL);
656
  gtk_plug_construct_for_display (plug, display, socket_id);
657 658 659
  return GTK_WIDGET (plug);
}

660 661 662 663
static void
gtk_plug_finalize (GObject *object)
{
  GtkPlug *plug = GTK_PLUG (object);
664
  GtkPlugPrivate *priv = plug->priv;
665

666
  if (priv->grabbed_keys)
667 668
    g_hash_table_destroy (priv->grabbed_keys);

Matthias Clasen's avatar
Matthias Clasen committed
669
  G_OBJECT_CLASS (gtk_plug_parent_class)->finalize (object);
670 671
}

672 673 674
static void
gtk_plug_unrealize (GtkWidget *widget)
{
675
  GtkPlug *plug = GTK_PLUG (widget);
676
  GtkPlugPrivate *priv = plug->priv;
677

678
  if (priv->socket_window != NULL)
679
    {
680 681
      g_object_unref (priv->socket_window);
      priv->socket_window = NULL;
682 683

      g_object_notify (G_OBJECT (widget), "embedded");
684 685
    }

686
  if (!priv->same_app)
687
    {
688
      if (priv->modality_window)
689
	gtk_plug_handle_modality_off (plug);
690

691 692
      gtk_window_group_remove_window (priv->modality_group, GTK_WINDOW (plug));
      g_object_unref (priv->modality_group);
693
    }
694

695
  GTK_WIDGET_CLASS (gtk_plug_parent_class)->unrealize (widget);
696 697
}

698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 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 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
static void
xembed_set_info (GdkWindow     *window,
		 unsigned long  flags)
{
  GdkDisplay *display = gdk_window_get_display (window);
  unsigned long buffer[2];

  Atom xembed_info_atom = gdk_x11_get_xatom_by_name_for_display (display, "_XEMBED_INFO");

  buffer[0] = GTK_XEMBED_PROTOCOL_VERSION;
  buffer[1] = flags;

  XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
		   GDK_WINDOW_XID (window),
		   xembed_info_atom, xembed_info_atom, 32,
		   PropModeReplace,
		   (unsigned char *)buffer, 2);
}

/**
 * gtk_plug_focus_first_last:
 * @plug: a #GtkPlug
 * @direction: a direction
 *
 * Called from the GtkPlug backend when the corresponding socket has
 * told the plug that it has received the focus.
 */
static void
gtk_plug_focus_first_last (GtkPlug          *plug,
			   GtkDirectionType  direction)
{
  GtkWindow *window = GTK_WINDOW (plug);
  GtkWidget *focus_widget;
  GtkWidget *parent;

  focus_widget = gtk_window_get_focus (window);
  if (focus_widget)
    {
      parent = gtk_widget_get_parent (focus_widget);
      while (parent)
	{
	  gtk_container_set_focus_child (GTK_CONTAINER (parent), NULL);
	  parent = gtk_widget_get_parent (parent);
	}
      
      gtk_window_set_focus (GTK_WINDOW (plug), NULL);
    }

  gtk_widget_child_focus (GTK_WIDGET (plug), direction);
}

static void
handle_xembed_message (GtkPlug           *plug,
		       XEmbedMessageType  message,
		       glong              detail,
		       glong              data1,
		       glong              data2,
		       guint32            time)
{
  GtkWindow *window = GTK_WINDOW (plug);

  GTK_NOTE (PLUGSOCKET,
	    g_message ("GtkPlug: %s received", _gtk_xembed_message_name (message)));
  
  switch (message)
    {
    case XEMBED_EMBEDDED_NOTIFY:
      break;
    case XEMBED_WINDOW_ACTIVATE:
      _gtk_window_set_is_active (window, TRUE);
      break;
    case XEMBED_WINDOW_DEACTIVATE:
      _gtk_window_set_is_active (window, FALSE);
      break;
      
    case XEMBED_MODALITY_ON:
      gtk_plug_handle_modality_on (plug);
      break;
    case XEMBED_MODALITY_OFF:
      gtk_plug_handle_modality_off (plug);
      break;

    case XEMBED_FOCUS_IN:
      _gtk_window_set_has_toplevel_focus (window, TRUE);
      switch (detail)
	{
	case XEMBED_FOCUS_FIRST:
	  gtk_plug_focus_first_last (plug, GTK_DIR_TAB_FORWARD);
	  break;
	case XEMBED_FOCUS_LAST:
	  gtk_plug_focus_first_last (plug, GTK_DIR_TAB_BACKWARD);
	  break;
	case XEMBED_FOCUS_CURRENT:
	  break;
	}
      break;

    case XEMBED_FOCUS_OUT:
      _gtk_window_set_has_toplevel_focus (window, FALSE);
      break;
      
    case XEMBED_GRAB_KEY:
    case XEMBED_UNGRAB_KEY:
    case XEMBED_GTK_GRAB_KEY:
    case XEMBED_GTK_UNGRAB_KEY:
    case XEMBED_REQUEST_FOCUS:
    case XEMBED_FOCUS_NEXT:
    case XEMBED_FOCUS_PREV:
      g_warning ("GtkPlug: Invalid _XEMBED message %s received", _gtk_xembed_message_name (message));
      break;
      
    default:
      GTK_NOTE(PLUGSOCKET,
	       g_message ("GtkPlug: Ignoring unknown _XEMBED message of type %d", message));
      break;
    }
}
static GdkFilterReturn
gtk_plug_filter_func (GdkXEvent *gdk_xevent,
		      GdkEvent  *event,
		      gpointer   data)
{
  GdkScreen *screen = gdk_window_get_screen (event->any.window);
  GdkDisplay *display = gdk_screen_get_display (screen);
  GtkPlug *plug = GTK_PLUG (data);
  GtkPlugPrivate *priv = plug->priv;
  XEvent *xevent = (XEvent *)gdk_xevent;
  GHashTableIter iter;
  gpointer key;
  GdkFilterReturn return_val;

  return_val = GDK_FILTER_CONTINUE;

  switch (xevent->type)
    {
    case ClientMessage:
      if (xevent->xclient.message_type == gdk_x11_get_xatom_by_name_for_display (display, "_XEMBED"))
	{
	  _gtk_xembed_push_message (xevent);
	  handle_xembed_message (plug,
				 xevent->xclient.data.l[1],
				 xevent->xclient.data.l[2],
				 xevent->xclient.data.l[3],
				 xevent->xclient.data.l[4],
				 xevent->xclient.data.l[0]);
	  _gtk_xembed_pop_message ();

	  return_val = GDK_FILTER_REMOVE;
	}
      else if (xevent->xclient.message_type == gdk_x11_get_xatom_by_name_for_display (display, "WM_DELETE_WINDOW"))
	{
	  /* We filter these out because we take being reparented back to the
	   * root window as the reliable end of the embedding protocol
	   */

	  return_val = GDK_FILTER_REMOVE;
	}
      break;
    case ReparentNotify:
      {
	XReparentEvent *xre = &xevent->xreparent;
        gboolean was_embedded = priv->socket_window != NULL;

	GTK_NOTE (PLUGSOCKET, g_message("GtkPlug: ReparentNotify received"));

	return_val = GDK_FILTER_REMOVE;
	
	g_object_ref (plug);
	
	if (was_embedded)
	  {
	    /* End of embedding protocol for previous socket */
	    
	    GTK_NOTE (PLUGSOCKET, g_message ("GtkPlug: end of embedding"));
	    /* FIXME: race if we remove from another socket and
	     * then add to a local window before we get notification
	     * Probably need check in _gtk_plug_add_to_socket
	     */

            if (xre->parent != GDK_WINDOW_XID (priv->socket_window))
	      {
		GtkWidget *widget = GTK_WIDGET (plug);

		g_object_unref (priv->socket_window);
		priv->socket_window = NULL;

		/* Emit a delete window, as if the user attempted
		 * to close the toplevel. Simple as to how we
		 * handle WM_DELETE_WINDOW, if it isn't handled
		 * we destroy the widget. BUt only do this if
		 * we are being reparented to the root window.
		 * Moving from one embedder to another should
		 * be invisible to the app.
		 */

		if (xre->parent == GDK_WINDOW_XID (gdk_screen_get_root_window (screen)))
		  {
		    GTK_NOTE (PLUGSOCKET, g_message ("GtkPlug: calling gtk_plug_send_delete_event()"));
896
		    gtk_plug_send_delete_event (widget);
897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932

		    g_object_notify (G_OBJECT (plug), "embedded");
		  }
	      }
	    else
	      goto done;
	  }

	if (xre->parent != GDK_WINDOW_XID (gdk_screen_get_root_window (screen)))
	  {
	    /* Start of embedding protocol */

	    GTK_NOTE (PLUGSOCKET, g_message ("GtkPlug: start of embedding"));

            priv->socket_window = gdk_x11_window_lookup_for_display (display, xre->parent);
            if (priv->socket_window)
	      {
		gpointer user_data = NULL;
		gdk_window_get_user_data (priv->socket_window, &user_data);

		if (user_data)
		  {
		    g_warning (G_STRLOC "Plug reparented unexpectedly into window in the same process");
                    priv->socket_window = NULL;
		    break; /* FIXME: shouldn't this unref the plug? i.e. "goto done;" instead */
		  }

		g_object_ref (priv->socket_window);
	      }
	    else
	      {
		priv->socket_window = gdk_x11_window_foreign_new_for_display (display, xre->parent);
		if (!priv->socket_window) /* Already gone */
		  break; /* FIXME: shouldn't this unref the plug? i.e. "goto done;" instead */
	      }

933
            if (priv->grabbed_keys)
934
              {
935 936 937 938 939 940 941 942 943
                g_hash_table_iter_init (&iter, priv->grabbed_keys);
                while (g_hash_table_iter_next (&iter, &key, NULL))
                  {
                    GrabbedKey *grabbed_key = key;

                    _gtk_xembed_send_message (priv->socket_window, XEMBED_GTK_GRAB_KEY, 0,
                                              grabbed_key->accelerator_key,
                                              grabbed_key->accelerator_mods);
                  }
944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982
              }

	    if (!was_embedded)
	      g_signal_emit_by_name (plug, "embedded");

	    g_object_notify (G_OBJECT (plug), "embedded");
	  }

      done:
	g_object_unref (plug);
	
	break;
      }
    case KeyPress:
    case KeyRelease:
      {
        GdkModifierType state, consumed;
        GdkDeviceManager *device_manager;
        GdkDevice *pointer, *keyboard;
        GdkKeymap *keymap;

        if (xevent->type == KeyPress)
          event->key.type = GDK_KEY_PRESS;
        else
          event->key.type = GDK_KEY_RELEASE;

        event->key.window = gdk_x11_window_lookup_for_display (display, xevent->xany.window);
        event->key.send_event = TRUE;
        event->key.time = xevent->xkey.time;
        event->key.state = (GdkModifierType) xevent->xkey.state;
        event->key.hardware_keycode = xevent->xkey.keycode;
        event->key.keyval = GDK_KEY_VoidSymbol;

        device_manager = gdk_display_get_device_manager (display);
        pointer = gdk_device_manager_get_client_pointer (device_manager);
        keyboard = gdk_device_get_associated_device (pointer);
        gdk_event_set_device (event, keyboard);

        keymap = gdk_keymap_get_for_display (display);
983 984 985 986

        event->key.group = gdk_x11_keymap_get_group_for_state (keymap, xevent->xkey.state);
        event->key.is_modifier = gdk_x11_keymap_key_is_modifier (keymap, event->key.hardware_keycode);

987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006
        gdk_keymap_translate_keyboard_state (keymap,
                                             event->key.hardware_keycode,
                                             event->key.state,
                                             event->key.group,
                                             &event->key.keyval,
                                             NULL, NULL, &consumed);

        state = event->key.state & ~consumed;
        gdk_keymap_add_virtual_modifiers (keymap, &state);
        event->key.state |= state;

        event->key.length = 0;
        event->key.string = g_strdup ("");

        return_val = GDK_FILTER_TRANSLATE;
      }
    }

  return return_val;
}
1007 1008 1009
static void
gtk_plug_realize (GtkWidget *widget)
{
1010
  GtkAllocation allocation;
1011
  GtkPlug *plug = GTK_PLUG (widget);
1012
  GtkPlugPrivate *priv = plug->priv;
1013 1014
  GtkWindow *window = GTK_WINDOW (widget);
  GdkWindow *gdk_window;
1015
  GdkWindowAttr attributes;
1016 1017
  const gchar *title;
  gchar *wmclass_name, *wmclass_class;
1018 1019
  gint attributes_mask;

1020
  gtk_widget_set_realized (widget, TRUE);
1021

1022 1023
  title = gtk_window_get_title (window);
  _gtk_window_get_wmclass (window, &wmclass_name, &wmclass_class);
1024 1025
  gtk_widget_get_allocation (widget, &allocation);

1026
  attributes.window_type = GDK_WINDOW_CHILD;	/* XXX GDK_WINDOW_PLUG ? */
1027 1028 1029
  attributes.title = (gchar *) title;
  attributes.wmclass_name = wmclass_name;
  attributes.wmclass_class = wmclass_class;
1030 1031
  attributes.width = allocation.width;
  attributes.height = allocation.height;
1032 1033 1034 1035 1036 1037 1038 1039
  attributes.wclass = GDK_INPUT_OUTPUT;

  /* this isn't right - we should match our parent's visual/colormap.
   * though that will require handling "foreign" colormaps */
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.event_mask = gtk_widget_get_events (widget);
  attributes.event_mask |= (GDK_EXPOSURE_MASK |
			    GDK_KEY_PRESS_MASK |
1040
			    GDK_KEY_RELEASE_MASK |
1041 1042 1043 1044
			    GDK_ENTER_NOTIFY_MASK |
			    GDK_LEAVE_NOTIFY_MASK |
			    GDK_STRUCTURE_MASK);

1045
  attributes_mask = GDK_WA_VISUAL;
1046
  attributes_mask |= (title ? GDK_WA_TITLE : 0);
1047
  attributes_mask |= (wmclass_name ? GDK_WA_WMCLASS : 0);
1048

1049
  if (gtk_widget_is_toplevel (widget))
1050
    {
1051 1052
      attributes.window_type = GDK_WINDOW_TOPLEVEL;

1053
      gdk_error_trap_push ();
1054 1055
      if (priv->socket_window)
        gdk_window = gdk_window_new (priv->socket_window,
1056
                                     &attributes, attributes_mask);
1057
      else /* If it's a passive plug, we use the root window */
1058 1059 1060
        gdk_window = gdk_window_new (gtk_widget_get_root_window (widget),
                                     &attributes, attributes_mask);
      gtk_widget_set_window (widget, gdk_window);
1061 1062

      gdk_display_sync (gtk_widget_get_display (widget));
1063 1064 1065
      if (gdk_error_trap_pop ()) /* Uh-oh */
	{
	  gdk_error_trap_push ();
1066
	  gdk_window_destroy (gdk_window);
1067
	  gdk_error_trap_pop_ignored ();
1068 1069 1070
	  gdk_window = gdk_window_new (gtk_widget_get_root_window (widget),
                                   &attributes, attributes_mask);
          gtk_widget_set_window (widget, gdk_window);
1071
	}
1072 1073

      gdk_window_add_filter (gdk_window,
1074
			     gtk_plug_filter_func,
1075
			     widget);
1076

1077 1078 1079
      priv->modality_group = gtk_window_group_new ();
      gtk_window_group_add_window (priv->modality_group, window);

1080
      xembed_set_info (gtk_widget_get_window (GTK_WIDGET (plug)), 0);
1081
    }
1082
  else
1083 1084 1085 1086 1087 1088
    {
      gdk_window = gdk_window_new (gtk_widget_get_parent_window (widget),
                                   &attributes, attributes_mask);
      gtk_widget_set_window (widget, gdk_window);
    }

1089
  gtk_widget_register_window (widget, gdk_window);
1090

1091 1092
  gtk_style_context_set_background (gtk_widget_get_style_context (widget),
                                    gdk_window);
1093 1094 1095 1096 1097
}

static void
gtk_plug_show (GtkWidget *widget)
{
1098
  if (gtk_widget_is_toplevel (widget))
Matthias Clasen's avatar
Matthias Clasen committed
1099
    GTK_WIDGET_CLASS (gtk_plug_parent_class)->show (widget);
1100 1101 1102 1103 1104 1105 1106
  else
    GTK_WIDGET_CLASS (bin_class)->show (widget);
}

static void
gtk_plug_hide (GtkWidget *widget)
{
1107
  if (gtk_widget_is_toplevel (widget))
Matthias Clasen's avatar
Matthias Clasen committed
1108
    GTK_WIDGET_CLASS (gtk_plug_parent_class)->hide (widget);
1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120
  else
    GTK_WIDGET_CLASS (bin_class)->hide (widget);
}

/* From gdkinternals.h */
void gdk_synthesize_window_state (GdkWindow     *window,
                                  GdkWindowState unset_flags,
                                  GdkWindowState set_flags);

static void
gtk_plug_map (GtkWidget *widget)
{
1121
  if (gtk_widget_is_toplevel (widget))
1122 1123
    {
      GtkBin *bin = GTK_BIN (widget);
1124
      GtkPlug *plug = GTK_PLUG (widget);
Javier Jardón's avatar
Javier Jardón committed
1125
      GtkWidget *child;
1126

1127
      gtk_widget_set_mapped (widget, TRUE);
1128

Javier Jardón's avatar
Javier Jardón committed
1129
      child = gtk_bin_get_child (bin);
1130 1131
      if (child != NULL &&
          gtk_widget_get_visible (child) &&
1132 1133
          !gtk_widget_get_mapped (child))
        gtk_widget_map (child);
1134

1135
      xembed_set_info (gtk_widget_get_window (GTK_WIDGET (plug)), XEMBED_MAPPED);
1136 1137

      gdk_synthesize_window_state (gtk_widget_get_window (widget),
1138 1139 1140 1141 1142 1143 1144 1145 1146 1147
				   GDK_WINDOW_STATE_WITHDRAWN,
				   0);
    }
  else
    GTK_WIDGET_CLASS (bin_class)->map (widget);
}

static void
gtk_plug_unmap (GtkWidget *widget)
{
1148
  if (gtk_widget_is_toplevel (widget))
1149
    {
1150
      GtkPlug *plug = GTK_PLUG (widget);
1151
      GdkWindow *window;
1152
      GtkWidget *child;
1153 1154

      window = gtk_widget_get_window (widget);
1155

1156
      gtk_widget_set_mapped (widget, FALSE);
1157

1158
      gdk_window_hide (window);
1159

1160 1161 1162 1163
      child = gtk_bin_get_child (GTK_BIN (widget));
      if (child != NULL)
        gtk_widget_unmap (child);

1164
      xembed_set_info (gtk_widget_get_window (GTK_WIDGET (plug)), 0);
1165 1166

      gdk_synthesize_window_state (window,
1167 1168 1169 1170 1171 1172
				   0,
				   GDK_WINDOW_STATE_WITHDRAWN);
    }
  else
    GTK_WIDGET_CLASS (bin_class)->unmap (widget);
}
1173

1174 1175 1176 1177
static void
gtk_plug_size_allocate (GtkWidget     *widget,
			GtkAllocation *allocation)
{
Javier Jardón's avatar
Javier Jardón committed
1178 1179
  GtkWidget *child;

1180
  if (gtk_widget_is_toplevel (widget))
Matthias Clasen's avatar
Matthias Clasen committed
1181
    GTK_WIDGET_CLASS (gtk_plug_parent_class)->size_allocate (widget, allocation);
1182 1183 1184 1185
  else
    {
      GtkBin *bin = GTK_BIN (widget);

1186
      gtk_widget_set_allocation (widget, allocation);
1187

1188
      if (gtk_widget_get_realized (widget))
1189
        gdk_window_move_resize (gtk_widget_get_window (widget),
1190 1191
				allocation->x, allocation->y,
				allocation->width, allocation->height);
1192

Javier Jardón's avatar
Javier Jardón committed
1193
      child = gtk_bin_get_child (bin);
1194 1195

      if (child != NULL && gtk_widget_get_visible (child))
1196 1197 1198
	{
	  GtkAllocation child_allocation;
	  
1199
	  child_allocation.x = child_allocation.y = gtk_container_get_border_width (GTK_CONTAINER (widget));
1200 1201 1202 1203 1204
	  child_allocation.width =
	    MAX (1, (gint)allocation->width - child_allocation.x * 2);
	  child_allocation.height =
	    MAX (1, (gint)allocation->height - child_allocation.y * 2);
	  
Javier Jardón's avatar
Javier Jardón committed
1205
	  gtk_widget_size_allocate (child, &child_allocation);
1206 1207 1208
	}
      
    }
1209 1210
}

1211
static gboolean
1212 1213 1214
gtk_plug_key_press_event (GtkWidget   *widget,
			  GdkEventKey *event)
{
1215
  if (gtk_widget_is_toplevel (widget))
Matthias Clasen's avatar
Matthias Clasen committed
1216
    return GTK_WIDGET_CLASS (gtk_plug_parent_class)->key_press_event (widget, event);
1217
  else
1218
    return FALSE;
1219 1220
}

1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231
static gboolean
gtk_plug_focus_event (GtkWidget      *widget,
		      GdkEventFocus  *event)
{
  /* We eat focus-in events and focus-out events, since they
   * can be generated by something like a keyboard grab on
   * a child of the plug.
   */
  return FALSE;
}

1232 1233 1234
static void
gtk_plug_set_focus (GtkWindow *window,
		    GtkWidget *focus)
1235
{
1236
  GtkPlug *plug = GTK_PLUG (window);
1237
  GtkPlugPrivate *priv = plug->priv;
1238

Matthias Clasen's avatar
Matthias Clasen committed
1239
  GTK_WINDOW_CLASS (gtk_plug_parent_class)->set_focus (window, focus);
1240 1241
  
  /* Ask for focus from embedder
1242
   */
1243

1244
  if (focus && !gtk_window_has_toplevel_focus (window))
1245 1246
    _gtk_xembed_send_message (priv->socket_window,
                              XEMBED_REQUEST_FOCUS, 0, 0, 0);
1247 1248
}

1249 1250 1251 1252 1253 1254 1255 1256 1257
static guint
grabbed_key_hash (gconstpointer a)
{
  const GrabbedKey *key = a;
  guint h;
  
  h = key->accelerator_key << 16;
  h ^= key->accelerator_key >> 16;
  h ^= key->accelerator_mods;
1258

1259 1260
  return h;
}
1261

1262 1263 1264 1265 1266
static gboolean
grabbed_key_equal (gconstpointer a, gconstpointer b)
{
  const GrabbedKey *keya = a;
  const GrabbedKey *keyb = b;
1267

1268 1269 1270
  return (keya->accelerator_key == keyb->accelerator_key &&
	  keya->accelerator_mods == keyb->accelerator_mods);
}
1271

1272
static void
1273
add_grabbed_key (gpointer key, gpointer val, gpointer data)
1274 1275 1276
{
  GrabbedKey *grabbed_key = key;
  GtkPlug *plug = data;
1277
  GtkPlugPrivate *priv = plug->priv;
1278

1279 1280
  if (!priv->grabbed_keys ||
      !g_hash_table_lookup (priv->grabbed_keys, grabbed_key))
1281
    {
1282 1283 1284
      _gtk_xembed_send_message (priv->socket_window, XEMBED_GTK_GRAB_KEY, 0,
                                grabbed_key->accelerator_key,
                                grabbed_key->accelerator_mods);
1285
    }
1286
}
1287

1288 1289
static void
remove_grabbed_key (gpointer key, gpointer val, gpointer data)
1290 1291 1292
{
  GrabbedKey *grabbed_key = key;
  GtkPlug *plug = data;
1293
  GtkPlugPrivate *priv = plug->priv;
1294

1295 1296
  if (!priv->grabbed_keys ||
      !g_hash_table_lookup (priv->grabbed_keys, grabbed_key))
1297
    {
1298 1299 1300
      _gtk_xembed_send_message (priv->socket_window, XEMBED_GTK_UNGRAB_KEY, 0,
                                grabbed_key->accelerator_key,
                                grabbed_key->accelerator_mods);
1301
    }
1302 1303 1304
}

static void
1305 1306 1307 1308 1309
keys_foreach (GtkWindow      *window,
	      guint           keyval,
	      GdkModifierType modifiers,
	      gboolean        is_mnemonic,
	      gpointer        data)
1310
{
1311
  GHashTable *new_grabbed_keys = data;
1312
  GrabbedKey *key = g_slice_new (GrabbedKey);
1313 1314 1315 1316 1317

  key->accelerator_key = keyval;
  key->accelerator_mods = modifiers;
  
  g_hash_table_replace (new_grabbed_keys, key, key);
1318
}
1319

1320 1321 1322 1323 1324 1325
static void
grabbed_key_free (gpointer data)
{
  g_slice_free (GrabbedKey, data);
}

1326
static void
1327
gtk_plug_keys_changed (GtkWindow *window)
1328 1329 1330
{
  GHashTable *new_grabbed_keys, *old_grabbed_keys;
  GtkPlug *plug = GTK_PLUG (window);
1331
  GtkPlugPrivate *priv = plug->priv;
1332

1333
  new_grabbed_keys = g_hash_table_new_full (grabbed_key_hash, grabbed_key_equal, (GDestroyNotify)grabbed_key_free, NULL);
1334
  _gtk_window_keys_foreach (window, keys_foreach, new_grabbed_keys);
1335

1336
  if (priv->socket_window)
1337
    g_hash_table_foreach (new_grabbed_keys, add_grabbed_key, plug);
1338

1339 1340
  old_grabbed_keys = priv->grabbed_keys;
  priv->grabbed_keys = new_grabbed_keys;
1341 1342 1343

  if (old_grabbed_keys)
    {
1344
      if (priv->socket_window)
1345
        g_hash_table_foreach (old_grabbed_keys, remove_grabbed_key, plug);