/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* By Owen Taylor 98/4/4 */ /* * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS * 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/. */ #include #include #include "gdk/gdkkeysyms.h" #include "gtkalias.h" #include "gtkmain.h" #include "gtkmarshalers.h" #include "gtkwindow.h" #include "gtkplug.h" #include "gtkprivate.h" #include "gtksocket.h" #include "gtksocketprivate.h" #include "gtkdnd.h" #include "x11/gdkx.h" #ifdef HAVE_XFIXES #include #endif #include "gtkxembed.h" #include "gtkalias.h" static gboolean xembed_get_info (GdkWindow *gdk_window, unsigned long *version, unsigned long *flags); /* From Tk */ #define EMBEDDED_APP_WANTS_FOCUS NotifyNormal+20 GdkNativeWindow _gtk_socket_windowing_get_id (GtkSocket *socket) { return GDK_WINDOW_XWINDOW (GTK_WIDGET (socket)->window); } void _gtk_socket_windowing_realize_window (GtkSocket *socket) { GdkWindow *window = GTK_WIDGET (socket)->window; XWindowAttributes xattrs; XGetWindowAttributes (GDK_WINDOW_XDISPLAY (window), GDK_WINDOW_XWINDOW (window), &xattrs); XSelectInput (GDK_WINDOW_XDISPLAY (window), GDK_WINDOW_XWINDOW (window), xattrs.your_event_mask | SubstructureNotifyMask | SubstructureRedirectMask); } void _gtk_socket_windowing_end_embedding_toplevel (GtkSocket *socket) { gtk_window_remove_embedded_xid (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (socket))), GDK_WINDOW_XWINDOW (socket->plug_window)); } void _gtk_socket_windowing_size_request (GtkSocket *socket) { XSizeHints hints; long supplied; gdk_error_trap_push (); socket->request_width = 1; socket->request_height = 1; if (XGetWMNormalHints (GDK_WINDOW_XDISPLAY (socket->plug_window), GDK_WINDOW_XWINDOW (socket->plug_window), &hints, &supplied)) { if (hints.flags & PMinSize) { socket->request_width = hints.min_width; socket->request_height = hints.min_height; } else if (hints.flags & PBaseSize) { socket->request_width = hints.base_width; socket->request_height = hints.base_height; } } socket->have_size = TRUE; gdk_error_trap_pop (); } void _gtk_socket_windowing_send_key_event (GtkSocket *socket, GdkEvent *gdk_event, gboolean mask_key_presses) { XEvent xevent; GdkScreen *screen = gdk_drawable_get_screen (socket->plug_window); xevent.xkey.type = (gdk_event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease; xevent.xkey.window = GDK_WINDOW_XWINDOW (socket->plug_window); xevent.xkey.root = GDK_WINDOW_XWINDOW (gdk_screen_get_root_window (screen)); xevent.xkey.subwindow = None; xevent.xkey.time = gdk_event->key.time; xevent.xkey.x = 0; xevent.xkey.y = 0; xevent.xkey.x_root = 0; xevent.xkey.y_root = 0; xevent.xkey.state = gdk_event->key.state; xevent.xkey.keycode = gdk_event->key.hardware_keycode; xevent.xkey.same_screen = True;/* FIXME ? */ gdk_error_trap_push (); XSendEvent (GDK_WINDOW_XDISPLAY (socket->plug_window), GDK_WINDOW_XWINDOW (socket->plug_window), False, (mask_key_presses ? KeyPressMask : NoEventMask), &xevent); gdk_display_sync (gdk_screen_get_display (screen)); gdk_error_trap_pop (); } void _gtk_socket_windowing_focus_change (GtkSocket *socket, gboolean focus_in) { if (focus_in) _gtk_xembed_send_focus_message (socket->plug_window, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT); else _gtk_xembed_send_message (socket->plug_window, XEMBED_FOCUS_OUT, 0, 0, 0); } void _gtk_socket_windowing_update_active (GtkSocket *socket, gboolean active) { _gtk_xembed_send_message (socket->plug_window, active ? XEMBED_WINDOW_ACTIVATE : XEMBED_WINDOW_DEACTIVATE, 0, 0, 0); } void _gtk_socket_windowing_update_modality (GtkSocket *socket, gboolean modality) { _gtk_xembed_send_message (socket->plug_window, modality ? XEMBED_MODALITY_ON : XEMBED_MODALITY_OFF, 0, 0, 0); } void _gtk_socket_windowing_focus (GtkSocket *socket, GtkDirectionType direction) { gint detail = -1; switch (direction) { case GTK_DIR_UP: case GTK_DIR_LEFT: case GTK_DIR_TAB_BACKWARD: detail = XEMBED_FOCUS_LAST; break; case GTK_DIR_DOWN: case GTK_DIR_RIGHT: case GTK_DIR_TAB_FORWARD: detail = XEMBED_FOCUS_FIRST; break; } _gtk_xembed_send_focus_message (socket->plug_window, XEMBED_FOCUS_IN, detail); } void _gtk_socket_windowing_send_configure_event (GtkSocket *socket) { XEvent event; gint x, y; g_return_if_fail (socket->plug_window != NULL); event.xconfigure.type = ConfigureNotify; event.xconfigure.event = GDK_WINDOW_XWINDOW (socket->plug_window); event.xconfigure.window = GDK_WINDOW_XWINDOW (socket->plug_window); /* The ICCCM says that synthetic events should have root relative * coordinates. We still aren't really ICCCM compliant, since * we don't send events when the real toplevel is moved. */ gdk_error_trap_push (); gdk_window_get_origin (socket->plug_window, &x, &y); gdk_error_trap_pop (); event.xconfigure.x = x; event.xconfigure.y = y; event.xconfigure.width = GTK_WIDGET(socket)->allocation.width; event.xconfigure.height = GTK_WIDGET(socket)->allocation.height; event.xconfigure.border_width = 0; event.xconfigure.above = None; event.xconfigure.override_redirect = False; gdk_error_trap_push (); XSendEvent (GDK_WINDOW_XDISPLAY (socket->plug_window), GDK_WINDOW_XWINDOW (socket->plug_window), False, NoEventMask, &event); gdk_display_sync (gtk_widget_get_display (GTK_WIDGET (socket))); gdk_error_trap_pop (); } void _gtk_socket_windowing_select_plug_window_input (GtkSocket *socket) { XSelectInput (GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (socket))), GDK_WINDOW_XWINDOW (socket->plug_window), StructureNotifyMask | PropertyChangeMask); } void _gtk_socket_windowing_embed_get_info (GtkSocket *socket) { unsigned long version; unsigned long flags; socket->xembed_version = -1; if (xembed_get_info (socket->plug_window, &version, &flags)) { socket->xembed_version = MIN (GTK_XEMBED_PROTOCOL_VERSION, version); socket->is_mapped = (flags & XEMBED_MAPPED) != 0; } else { /* FIXME, we should probably actually check the state before we started */ socket->is_mapped = TRUE; } } void _gtk_socket_windowing_embed_notify (GtkSocket *socket) { #ifdef HAVE_XFIXES GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (socket); XFixesChangeSaveSet (GDK_DISPLAY_XDISPLAY (display)), GDK_WINDOW_XWINDOW (socket->plug_window), SetModeInsert, SaveSetRoot, SaveSetUnmap); #endif _gtk_xembed_send_message (socket->plug_window, XEMBED_EMBEDDED_NOTIFY, 0, GDK_WINDOW_XWINDOW (GTK_WIDGET (socket)->window), socket->xembed_version); } static gboolean xembed_get_info (GdkWindow *window, unsigned long *version, unsigned long *flags) { GdkDisplay *display = gdk_drawable_get_display (window); Atom xembed_info_atom = gdk_x11_get_xatom_by_name_for_display (display, "_XEMBED_INFO"); Atom type; int format; unsigned long nitems, bytes_after; unsigned char *data; unsigned long *data_long; int status; gdk_error_trap_push(); status = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XWINDOW (window), xembed_info_atom, 0, 2, False, xembed_info_atom, &type, &format, &nitems, &bytes_after, &data); gdk_error_trap_pop(); if (status != Success) return FALSE; /* Window vanished? */ if (type == None) /* No info property */ return FALSE; if (type != xembed_info_atom) { g_warning ("_XEMBED_INFO property has wrong type\n"); return FALSE; } if (nitems < 2) { g_warning ("_XEMBED_INFO too short\n"); XFree (data); return FALSE; } data_long = (unsigned long *)data; if (version) *version = data_long[0]; if (flags) *flags = data_long[1] & XEMBED_MAPPED; XFree (data); return TRUE; } gboolean _gtk_socket_windowing_embed_get_focus_wrapped (void) { return _gtk_xembed_get_focus_wrapped (); } void _gtk_socket_windowing_embed_set_focus_wrapped (void) { _gtk_xembed_set_focus_wrapped (); } static void handle_xembed_message (GtkSocket *socket, XEmbedMessageType message, glong detail, glong data1, glong data2, guint32 time) { GTK_NOTE (PLUGSOCKET, g_message ("GtkSocket: %s received", _gtk_xembed_message_name (message))); switch (message) { case XEMBED_EMBEDDED_NOTIFY: case XEMBED_WINDOW_ACTIVATE: case XEMBED_WINDOW_DEACTIVATE: case XEMBED_MODALITY_ON: case XEMBED_MODALITY_OFF: case XEMBED_FOCUS_IN: case XEMBED_FOCUS_OUT: g_warning ("GtkSocket: Invalid _XEMBED message %s received", _gtk_xembed_message_name (message)); break; case XEMBED_REQUEST_FOCUS: _gtk_socket_claim_focus (socket, TRUE); break; case XEMBED_FOCUS_NEXT: case XEMBED_FOCUS_PREV: _gtk_socket_advance_toplevel_focus (socket, (message == XEMBED_FOCUS_NEXT ? GTK_DIR_TAB_FORWARD : GTK_DIR_TAB_BACKWARD)); break; case XEMBED_GTK_GRAB_KEY: _gtk_socket_add_grabbed_key (socket, data1, data2); break; case XEMBED_GTK_UNGRAB_KEY: _gtk_socket_remove_grabbed_key (socket, data1, data2); break; case XEMBED_GRAB_KEY: case XEMBED_UNGRAB_KEY: break; default: GTK_NOTE (PLUGSOCKET, g_message ("GtkSocket: Ignoring unknown _XEMBED message of type %d", message)); break; } } GdkFilterReturn _gtk_socket_windowing_filter_func (GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data) { GtkSocket *socket; GtkWidget *widget; GdkDisplay *display; XEvent *xevent; GdkFilterReturn return_val; socket = GTK_SOCKET (data); return_val = GDK_FILTER_CONTINUE; if (socket->plug_widget) return return_val; widget = GTK_WIDGET (socket); xevent = (XEvent *)gdk_xevent; display = gtk_widget_get_display (widget); 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 (socket, 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; } break; case CreateNotify: { XCreateWindowEvent *xcwe = &xevent->xcreatewindow; if (!socket->plug_window) { _gtk_socket_add_window (socket, xcwe->window, FALSE); if (socket->plug_window) { GTK_NOTE (PLUGSOCKET, g_message ("GtkSocket - window created")); } } return_val = GDK_FILTER_REMOVE; break; } case ConfigureRequest: { XConfigureRequestEvent *xcre = &xevent->xconfigurerequest; if (!socket->plug_window) _gtk_socket_add_window (socket, xcre->window, FALSE); if (socket->plug_window) { GtkSocketPrivate *private = _gtk_socket_get_private (socket); if (xcre->value_mask & (CWWidth | CWHeight)) { GTK_NOTE (PLUGSOCKET, g_message ("GtkSocket - configure request: %d %d", socket->request_width, socket->request_height)); private->resize_count++; gtk_widget_queue_resize (widget); } else if (xcre->value_mask & (CWX | CWY)) { _gtk_socket_windowing_send_configure_event (socket); } /* Ignore stacking requests. */ return_val = GDK_FILTER_REMOVE; } break; } case DestroyNotify: { XDestroyWindowEvent *xdwe = &xevent->xdestroywindow; /* Note that we get destroy notifies both from SubstructureNotify on * our window and StructureNotify on socket->plug_window */ if (socket->plug_window && (xdwe->window == GDK_WINDOW_XWINDOW (socket->plug_window))) { gboolean result; GTK_NOTE (PLUGSOCKET, g_message ("GtkSocket - destroy notify")); gdk_window_destroy_notify (socket->plug_window); _gtk_socket_end_embedding (socket); g_object_ref (widget); g_signal_emit_by_name (widget, "plug_removed", &result); if (!result) gtk_widget_destroy (widget); g_object_unref (widget); return_val = GDK_FILTER_REMOVE; } break; } case FocusIn: if (xevent->xfocus.mode == EMBEDDED_APP_WANTS_FOCUS) { _gtk_socket_claim_focus (socket, TRUE); } return_val = GDK_FILTER_REMOVE; break; case FocusOut: return_val = GDK_FILTER_REMOVE; break; case MapRequest: if (!socket->plug_window) { _gtk_socket_add_window (socket, xevent->xmaprequest.window, FALSE); } if (socket->plug_window) { GTK_NOTE (PLUGSOCKET, g_message ("GtkSocket - Map Request")); _gtk_socket_handle_map_request (socket); return_val = GDK_FILTER_REMOVE; } break; case PropertyNotify: if (socket->plug_window && xevent->xproperty.window == GDK_WINDOW_XWINDOW (socket->plug_window)) { GdkDragProtocol protocol; if (xevent->xproperty.atom == gdk_x11_get_xatom_by_name_for_display (display, "WM_NORMAL_HINTS")) { GTK_NOTE (PLUGSOCKET, g_message ("GtkSocket - received PropertyNotify for plug's WM_NORMAL_HINTS")); socket->have_size = FALSE; gtk_widget_queue_resize (widget); return_val = GDK_FILTER_REMOVE; } else if ((xevent->xproperty.atom == gdk_x11_get_xatom_by_name_for_display (display, "XdndAware")) || (xevent->xproperty.atom == gdk_x11_get_xatom_by_name_for_display (display, "_MOTIF_DRAG_RECEIVER_INFO"))) { gdk_error_trap_push (); if (gdk_drag_get_protocol_for_display (display, xevent->xproperty.window, &protocol)) gtk_drag_dest_set_proxy (GTK_WIDGET (socket), socket->plug_window, protocol, TRUE); gdk_display_sync (display); gdk_error_trap_pop (); return_val = GDK_FILTER_REMOVE; } else if (xevent->xproperty.atom == gdk_x11_get_xatom_by_name_for_display (display, "_XEMBED_INFO")) { unsigned long flags; if (xembed_get_info (socket->plug_window, NULL, &flags)) { gboolean was_mapped = socket->is_mapped; gboolean is_mapped = (flags & XEMBED_MAPPED) != 0; if (was_mapped != is_mapped) { if (is_mapped) _gtk_socket_handle_map_request (socket); else { gdk_error_trap_push (); gdk_window_show (socket->plug_window); gdk_flush (); gdk_error_trap_pop (); _gtk_socket_unmap_notify (socket); } } } return_val = GDK_FILTER_REMOVE; } } break; case ReparentNotify: { XReparentEvent *xre = &xevent->xreparent; GTK_NOTE (PLUGSOCKET, g_message ("GtkPlug: ReparentNotify received\n")); if (!socket->plug_window && xre->parent == GDK_WINDOW_XWINDOW (widget->window)) { _gtk_socket_add_window (socket, xre->window, FALSE); if (socket->plug_window) { GTK_NOTE (PLUGSOCKET, g_message ("GtkSocket - window reparented")); } return_val = GDK_FILTER_REMOVE; } break; } case UnmapNotify: if (socket->plug_window && xevent->xunmap.window == GDK_WINDOW_XWINDOW (socket->plug_window)) { GTK_NOTE (PLUGSOCKET, g_message ("GtkSocket - Unmap notify")); _gtk_socket_unmap_notify (socket); return_val = GDK_FILTER_REMOVE; } break; } return return_val; }