/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* Copyright (C) 2022 Red Hat Inc. * * 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, see . * * Author: Carlos Garnacho * * Based on GTK+ code by Owen Taylor */ #include "config.h" #include "na-xembed.h" #include #include #include #include typedef struct _NaXembedPrivate NaXembedPrivate; struct _NaXembedPrivate { MetaX11Display *x11_display; Window socket_window; Window plug_window; int root_x; int root_y; int request_width; int request_height; int current_width; int current_height; int resize_count; int xembed_version; unsigned int event_func_id; guint resize_id; XVisualInfo *xvisual_info; Atom atom__XEMBED; Atom atom__XEMBED_INFO; Atom atom_WM_NORMAL_HINTS; gboolean have_size; gboolean need_map; gboolean is_mapped; gboolean has_alpha; }; /* XEMBED messages */ typedef enum _XembedMessageType XembedMessageType; enum _XembedMessageType { XEMBED_EMBEDDED_NOTIFY, XEMBED_WINDOW_ACTIVATE, XEMBED_WINDOW_DEACTIVATE, XEMBED_REQUEST_FOCUS, XEMBED_FOCUS_IN, XEMBED_FOCUS_OUT, XEMBED_FOCUS_NEXT, XEMBED_FOCUS_PREV, XEMBED_GRAB_KEY, XEMBED_UNGRAB_KEY, XEMBED_MODALITY_ON, XEMBED_MODALITY_OFF, }; /* Flags for _XEMBED_INFO */ #define XEMBED_MAPPED (1 << 0) #define XEMBED_PROTOCOL_VERSION 1 G_DEFINE_TYPE_WITH_PRIVATE (NaXembed, na_xembed, G_TYPE_OBJECT) enum { PLUG_ADDED, PLUG_REMOVED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0, }; enum { PROP_0, PROP_X11_DISPLAY, N_PROPS }; static GParamSpec *props[N_PROPS] = { 0, }; static void xembed_send_message (NaXembed *xembed, Window recipient, XembedMessageType message, glong detail, glong data1, glong data2) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display); XClientMessageEvent xclient; memset (&xclient, 0, sizeof (xclient)); xclient.window = recipient; xclient.type = ClientMessage; xclient.message_type = priv->atom__XEMBED; xclient.format = 32; xclient.data.l[0] = 0; /* Time */ xclient.data.l[1] = message; xclient.data.l[2] = detail; xclient.data.l[3] = data1; xclient.data.l[4] = data2; mtk_x11_error_trap_push (xdisplay); XSendEvent (xdisplay, recipient, False, NoEventMask, (XEvent*) &xclient); mtk_x11_error_trap_pop (xdisplay); } static void na_xembed_end_embedding (NaXembed *xembed) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); priv->plug_window = None; priv->current_width = 0; priv->current_height = 0; priv->resize_count = 0; g_clear_handle_id (&priv->resize_id, g_source_remove); } static void na_xembed_send_configure_event (NaXembed *xembed) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); XConfigureEvent xconfigure; Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display); memset (&xconfigure, 0, sizeof (xconfigure)); xconfigure.type = ConfigureNotify; xconfigure.event = priv->plug_window; xconfigure.window = priv->plug_window; xconfigure.x = priv->root_x; xconfigure.y = priv->root_y; xconfigure.width = priv->current_width; xconfigure.height = priv->current_height; xconfigure.border_width = 0; xconfigure.above = None; xconfigure.override_redirect = False; mtk_x11_error_trap_push (xdisplay); XSendEvent (xdisplay, priv->plug_window, False, NoEventMask, (XEvent*) &xconfigure); mtk_x11_error_trap_pop (xdisplay); } static void na_xembed_synchronize_size (NaXembed *xembed) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display); int x, y, width, height; x = priv->root_x; y = priv->root_y; width = priv->request_width; height = priv->request_height; XMoveResizeWindow (xdisplay, priv->socket_window, x, y, width, height); if (priv->plug_window) { mtk_x11_error_trap_push (xdisplay); if (width != priv->current_width || height != priv->current_height) { XMoveResizeWindow (xdisplay, priv->plug_window, 0, 0, width, height); if (priv->resize_count) priv->resize_count--; priv->current_width = width; priv->current_height = height; } if (priv->need_map) { XMapWindow (xdisplay, priv->plug_window); priv->need_map = FALSE; } while (priv->resize_count) { na_xembed_send_configure_event (xembed); priv->resize_count--; } mtk_x11_error_trap_pop (xdisplay); } } static gboolean synchronize_size_cb (gpointer user_data) { NaXembed *xembed = user_data; NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); na_xembed_synchronize_size (xembed); priv->resize_id = 0; return G_SOURCE_REMOVE; } static void na_xembed_resize (NaXembed *xembed) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display); XSizeHints hints; long supplied; g_clear_handle_id (&priv->resize_id, g_source_remove); mtk_x11_error_trap_push (xdisplay); priv->request_width = 1; priv->request_height = 1; if (XGetWMNormalHints (xdisplay, priv->plug_window, &hints, &supplied)) { if (hints.flags & PMinSize) { priv->request_width = MAX (hints.min_width, 1); priv->request_height = MAX (hints.min_height, 1); } else if (hints.flags & PBaseSize) { priv->request_width = MAX (hints.base_width, 1); priv->request_height = MAX (hints.base_height, 1); } } priv->have_size = TRUE; mtk_x11_error_trap_pop (xdisplay); priv->resize_id = g_idle_add (synchronize_size_cb, xembed); } static gboolean na_xembed_get_info (NaXembed *xembed, Window xwindow, unsigned long *version, unsigned long *flags) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); MetaX11Display *x11_display = priv->x11_display; Display *xdisplay = meta_x11_display_get_xdisplay (x11_display); Atom type; int format; unsigned long nitems, bytes_after; unsigned char *data; unsigned long *data_long; int status; mtk_x11_error_trap_push (xdisplay); status = XGetWindowProperty (xdisplay, xwindow, priv->atom__XEMBED_INFO, 0, 2, False, priv->atom__XEMBED_INFO, &type, &format, &nitems, &bytes_after, &data); mtk_x11_error_trap_pop (xdisplay); if (status != Success) return FALSE; if (type == None) return FALSE; if (type != priv->atom__XEMBED_INFO) { g_warning ("_XEMBED_INFO property has wrong type"); XFree (data); return FALSE; } if (nitems < 2) { g_warning ("_XEMBED_INFO too short"); 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; } static void na_xembed_add_window (NaXembed *xembed, Window xid, gboolean need_reparent) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display); XVisualInfo template = { 0, }; int n_xvisuals; unsigned long version; unsigned long flags; priv->plug_window = xid; mtk_x11_error_trap_push (xdisplay); XSelectInput (xdisplay, priv->plug_window, StructureNotifyMask | PropertyChangeMask); if (mtk_x11_error_trap_pop_with_return (xdisplay)) { priv->plug_window = None; return; } /* OK, we now will reliably get destroy notification on socket->plug_window */ mtk_x11_error_trap_push (xdisplay); if (need_reparent) { XSetWindowAttributes socket_attrs; XWindowAttributes plug_attrs; int result; result = XGetWindowAttributes (xdisplay, priv->plug_window, &plug_attrs); if (result == 0) { mtk_x11_error_trap_pop (xdisplay); priv->plug_window = None; return; } template.visualid = plug_attrs.visual->visualid; priv->xvisual_info = XGetVisualInfo (xdisplay, VisualIDMask, &template, &n_xvisuals); if (!priv->xvisual_info) { mtk_x11_error_trap_pop (xdisplay); priv->plug_window = None; return; } priv->has_alpha = (priv->xvisual_info->depth > __builtin_popcount (priv->xvisual_info->red_mask | priv->xvisual_info->green_mask | priv->xvisual_info->blue_mask)); socket_attrs.override_redirect = True; priv->socket_window = XCreateWindow (xdisplay, meta_x11_display_get_xroot (priv->x11_display), -1, -1, 1, 1, 0, priv->xvisual_info->depth, InputOutput, plug_attrs.visual, CWOverrideRedirect, &socket_attrs); XUnmapWindow (xdisplay, priv->plug_window); /* Shouldn't actually be necessary for XEMBED, but just in case */ XReparentWindow (xdisplay, priv->plug_window, priv->socket_window, 0, 0); } priv->have_size = FALSE; priv->xembed_version = -1; if (na_xembed_get_info (xembed, priv->plug_window, &version, &flags)) { priv->xembed_version = MIN (XEMBED_PROTOCOL_VERSION, version); priv->is_mapped = (flags & XEMBED_MAPPED) != 0; } else { /* FIXME, we should probably actually check the state before we started */ priv->is_mapped = TRUE; } priv->need_map = priv->is_mapped; mtk_x11_error_trap_pop (xdisplay); mtk_x11_error_trap_push (xdisplay); XFixesChangeSaveSet (xdisplay, priv->plug_window, SetModeInsert, SaveSetRoot, SaveSetUnmap); mtk_x11_error_trap_pop (xdisplay); xembed_send_message (xembed, priv->plug_window, XEMBED_EMBEDDED_NOTIFY, 0, priv->socket_window, priv->xembed_version); na_xembed_resize (xembed); g_signal_emit (xembed, signals[PLUG_ADDED], 0); XMapWindow (xdisplay, priv->socket_window); } static void na_xembed_handle_map_request (NaXembed *xembed) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); if (!priv->is_mapped) { priv->is_mapped = TRUE; priv->need_map = TRUE; na_xembed_resize (xembed); } } static void na_xembed_handle_unmap_notify (NaXembed *xembed) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); if (priv->is_mapped) { priv->is_mapped = FALSE; na_xembed_resize (xembed); } } static void xembed_filter_func (MetaX11Display *x11_display, XEvent *xevent, gpointer user_data) { NaXembed *xembed = user_data; NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display); if (priv->socket_window == None) return; if (xevent->xany.window != priv->socket_window && xevent->xany.window != priv->plug_window) return; switch (xevent->type) { case ClientMessage: /* We choose to ignore all _XEMBED ClientEvent messages * addressed to the socket. */ break; case CreateNotify: { XCreateWindowEvent *xcwe = &xevent->xcreatewindow; if (!priv->plug_window) na_xembed_add_window (xembed, xcwe->window, FALSE); break; } case ConfigureRequest: { XConfigureRequestEvent *xcre = &xevent->xconfigurerequest; if (!priv->plug_window) na_xembed_add_window (xembed, xcre->window, FALSE); if (priv->plug_window) { if (xcre->value_mask & (CWWidth | CWHeight)) { priv->resize_count++; na_xembed_resize (xembed); } else if (xcre->value_mask & (CWX | CWY)) { na_xembed_send_configure_event (xembed); } /* Ignore stacking requests. */ } 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 (priv->plug_window && (xdwe->window == priv->plug_window)) { g_object_ref (xembed); g_signal_emit (xembed, signals[PLUG_REMOVED], 0); na_xembed_end_embedding (xembed); g_object_unref (xembed); } break; } case MapRequest: if (!priv->plug_window) na_xembed_add_window (xembed, xevent->xmaprequest.window, FALSE); if (priv->plug_window == xevent->xmaprequest.window) na_xembed_handle_map_request (xembed); break; case PropertyNotify: if (priv->plug_window && xevent->xproperty.window == priv->plug_window) { if (xevent->xproperty.atom == priv->atom_WM_NORMAL_HINTS) { priv->have_size = FALSE; na_xembed_resize (xembed); } else if (xevent->xproperty.atom == priv->atom__XEMBED_INFO) { unsigned long flags; if (na_xembed_get_info (xembed, priv->plug_window, NULL, &flags)) { gboolean was_mapped = priv->is_mapped; gboolean is_mapped = (flags & XEMBED_MAPPED) != 0; if (was_mapped != is_mapped) { if (is_mapped) { na_xembed_handle_map_request (xembed); } else { mtk_x11_error_trap_push (xdisplay); XMapWindow (xdisplay, priv->plug_window); mtk_x11_error_trap_pop (xdisplay); na_xembed_handle_unmap_notify (xembed); } } } } } break; case ReparentNotify: { XReparentEvent *xre = &xevent->xreparent; if (priv->plug_window == None && xre->parent == priv->socket_window) { na_xembed_add_window (xembed, xre->window, FALSE); } else if (priv->plug_window != None && xre->window == priv->plug_window && xre->parent != priv->socket_window) { g_object_ref (xembed); g_signal_emit (xembed, signals[PLUG_REMOVED], 0); na_xembed_end_embedding (xembed); g_object_unref (xembed); } break; } case UnmapNotify: if (priv->plug_window != None && xevent->xunmap.window == priv->plug_window) na_xembed_handle_unmap_notify (xembed); break; } } static void na_xembed_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NaXembed *xembed = NA_XEMBED (object); NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); switch (prop_id) { case PROP_X11_DISPLAY: priv->x11_display = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void na_xembed_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NaXembed *xembed = NA_XEMBED (object); NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); switch (prop_id) { case PROP_X11_DISPLAY: g_value_set_object (value, priv->x11_display); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void na_xembed_finalize (GObject *object) { NaXembed *xembed = NA_XEMBED (object); NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); g_clear_pointer (&priv->xvisual_info, XFree); if (priv->x11_display && priv->event_func_id) meta_x11_display_remove_event_func (priv->x11_display, priv->event_func_id); if (priv->plug_window) na_xembed_end_embedding (xembed); G_OBJECT_CLASS (na_xembed_parent_class)->finalize (object); } static void na_xembed_constructed (GObject *object) { NaXembed *xembed = NA_XEMBED (object); NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); Display *xdisplay; G_OBJECT_CLASS (na_xembed_parent_class)->constructed (object); xdisplay = meta_x11_display_get_xdisplay (priv->x11_display); priv->event_func_id = meta_x11_display_add_event_func (priv->x11_display, xembed_filter_func, object, NULL); priv->atom__XEMBED = XInternAtom (xdisplay, "_XEMBED", False); priv->atom__XEMBED_INFO = XInternAtom (xdisplay, "_XEMBED_INFO", False); priv->atom_WM_NORMAL_HINTS = XInternAtom (xdisplay, "WM_NORMAL_HINTS", False); } static void na_xembed_class_init (NaXembedClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = na_xembed_set_property; object_class->get_property = na_xembed_get_property; object_class->finalize = na_xembed_finalize; object_class->constructed = na_xembed_constructed; signals[PLUG_ADDED] = g_signal_new ("plug-added", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NaXembedClass, plug_added), NULL, NULL, NULL, G_TYPE_NONE, 0); signals[PLUG_REMOVED] = g_signal_new ("plug-removed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NaXembedClass, plug_removed), NULL, NULL, NULL, G_TYPE_NONE, 0); props[PROP_X11_DISPLAY] = g_param_spec_object ("x11-display", "x11-display", "x11-display", META_TYPE_X11_DISPLAY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_properties (object_class, N_PROPS, props); } static void na_xembed_init (NaXembed *xembed) { } void na_xembed_add_id (NaXembed *xembed, Window window) { na_xembed_add_window (xembed, window, TRUE); } Window na_xembed_get_plug_window (NaXembed *xembed) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); return priv->plug_window; } Window na_xembed_get_socket_window (NaXembed *xembed) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); return priv->socket_window; } void na_xembed_set_root_position (NaXembed *xembed, int x, int y) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); if (priv->root_x == x && priv->root_y == y) return; priv->root_x = x; priv->root_y = y; if (priv->resize_id == 0) priv->resize_id = g_idle_add (synchronize_size_cb, xembed); } MetaX11Display * na_xembed_get_x11_display (NaXembed *xembed) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); return priv->x11_display; } void na_xembed_get_size (NaXembed *xembed, int *width, int *height) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); if (width) *width = priv->request_width; if (height) *height = priv->request_height; } static void get_pixel_details (unsigned long pixel_mask, int *shift, int *precision) { unsigned long m = 0; int s = 0; int p = 0; if (pixel_mask != 0) { m = pixel_mask; while (!(m & 0x1)) { s++; m >>= 1; } while (m & 0x1) { p++; m >>= 1; } } if (shift) *shift = s; if (precision) *precision = p; } void na_xembed_set_background_color (NaXembed *xembed, const ClutterColor *color) { NaXembedPrivate *priv = na_xembed_get_instance_private (xembed); XVisualInfo *xvisual_info; Display *xdisplay; unsigned long pixel; if (!priv->socket_window) return; if (!priv->xvisual_info) return; xvisual_info = priv->xvisual_info; if (priv->has_alpha) { pixel = 0; } else { double red, green, blue; int red_shift, red_prec, green_shift, green_prec, blue_shift, blue_prec; unsigned int padding; /* Shifting by >= width-of-type isn't defined in C */ if (xvisual_info->depth >= 32) padding = 0; else padding = ((~(uint32_t)0)) << xvisual_info->depth; red = (double) color->red / 255.0; green = (double) color->green / 255.0; blue = (double) color->blue / 255.0; get_pixel_details (xvisual_info->red_mask, &red_shift, &red_prec); get_pixel_details (xvisual_info->green_mask, &green_shift, &green_prec); get_pixel_details (xvisual_info->blue_mask, &blue_shift, &blue_prec); pixel = ~(xvisual_info->red_mask | xvisual_info->green_mask | xvisual_info->blue_mask | padding); pixel += (((int) (red * ((1 << red_prec) - 1))) << red_shift) + (((int) (green * ((1 << green_prec) - 1))) << green_shift) + (((int) (blue * ((1 << blue_prec) - 1))) << blue_shift); } xdisplay = meta_x11_display_get_xdisplay (priv->x11_display); XSetWindowBackground (xdisplay, priv->socket_window, pixel); XClearWindow (xdisplay, priv->socket_window); }