[mutter/wip/carlosg/frames-client: 3/5] frames: Add new X11 frames client




commit 938ee9df8831f7dba6f57a83007b74a83b7a9a89
Author: Carlos Garnacho <carlosg gnome org>
Date:   Tue Dec 21 21:11:05 2021 +0100

    frames: Add new X11 frames client
    
    This separate X11 client takes care of creating frames for client
    windows, Mutter will use this client to delegate window frame
    rendering and event handling.
    
    This client is itself a GTK4 application, using real GtkWindows to
    implement window frames, the client window is expected to be
    reparented by the WM (still Mutter) into the GtkWindow X11 Window,
    and picked up from there.
    
    In order to make things behave, the frames need to forward some
    messaging to client windows (e.g. WM_DELETE_WINDOW when the X button
    is pressed), and some things it needs to read and synchronize with
    the client (e.g. window titles).
    
    Other than that, this is still a CSD GTK4 window, so window drags
    and resizes, and window context menus are forwarded for the WM to
    handle.

 meson.build            |   2 +
 src/frames/main.c      | 382 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/frames/meson.build |  10 ++
 src/meson.build        |   1 +
 4 files changed, 395 insertions(+)
---
diff --git a/meson.build b/meson.build
index c9e57c8126..d27fd7fbf9 100644
--- a/meson.build
+++ b/meson.build
@@ -18,6 +18,7 @@ glib_req = '>= 2.69.0'
 gi_req = '>= 0.9.5'
 graphene_req = '>= 1.10.2'
 gtk3_req = '>= 3.19.8'
+gtk4_req = '>= 4.0.0'
 gdk_pixbuf_req = '>= 2.0'
 uprof_req = '>= 0.3'
 pango_req = '>= 1.46.0'
@@ -91,6 +92,7 @@ m_dep = cc.find_library('m', required: true)
 x11_dep = dependency('x11')
 graphene_dep = dependency('graphene-gobject-1.0', version: graphene_req)
 gtk3_dep = dependency('gtk+-3.0', version: gtk3_req)
+gtk4_dep = dependency('gtk4', version: gtk4_req)
 gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0')
 pango_dep = dependency('pango', version: pango_req)
 cairo_dep = dependency('cairo', version: cairo_req)
diff --git a/src/frames/main.c b/src/frames/main.c
new file mode 100644
index 0000000000..b241b8596f
--- /dev/null
+++ b/src/frames/main.c
@@ -0,0 +1,382 @@
+#include <gdk/x11/gdkx.h>
+#include <gtk/gtk.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+
+static GHashTable *frames = NULL;
+static GHashTable *client_windows = NULL;
+
+typedef struct _MetaFrameContent
+{
+  GtkWidget parent_instance;
+  Window content;
+  cairo_rectangle_int_t rect;
+  int width, height;
+  int frame_height;
+} MetaFrameContent;
+
+typedef struct {
+    unsigned long flags;
+    unsigned long functions;
+    unsigned long decorations;
+    long input_mode;
+    unsigned long status;
+} MotifWmHints;
+
+#define MWM_FUNC_MINIMIZE (1L << 3)
+#define MWM_FUNC_MAXIMIZE (1L << 4)
+#define MWM_FUNC_CLOSE    (1L << 5)
+
+G_DECLARE_FINAL_TYPE (MetaFrameContent, meta_frame_content,
+                      META, FRAME_CONTENT, GtkWidget)
+
+G_DEFINE_TYPE (MetaFrameContent, meta_frame_content, GTK_TYPE_WIDGET)
+
+#define META_TYPE_FRAME_CONTENT (meta_frame_content_get_type ())
+
+static void
+meta_frame_content_update_frame_height (MetaFrameContent *content,
+                                        int               height)
+{
+  GdkDisplay *display;
+  GtkWindow *window;
+  GdkSurface *surface;
+  Window xframe;
+
+  if (content->frame_height == height)
+    return;
+
+  content->frame_height = height;
+
+  display = gtk_widget_get_display (GTK_WIDGET (content));
+  window = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (content)));
+  surface = gtk_native_get_surface (GTK_NATIVE (window));
+  xframe = gdk_x11_surface_get_xid (surface);
+
+  XChangeProperty (gdk_x11_display_get_xdisplay (display),
+                   xframe,
+                   gdk_x11_get_xatom_by_name_for_display (display, "_MUTTER_FRAME_HEIGHT"),
+                   XA_CARDINAL,
+                   32,
+                   PropModeReplace,
+                   (guchar *) &content->frame_height, 1);
+}
+
+static void
+meta_frame_content_measure (GtkWidget      *widget,
+                            GtkOrientation  orientation,
+                            int             for_size,
+                            int            *minimum,
+                            int            *natural,
+                            int            *minimum_baseline,
+                            int            *natural_baseline)
+{
+  MetaFrameContent *content = META_FRAME_CONTENT (widget);
+  GdkDisplay *display = gtk_widget_get_display (widget);
+  XWindowAttributes attrs;
+
+  if (content->width < 0 || content->height < 0)
+    {
+      XGetWindowAttributes (gdk_x11_display_get_xdisplay (display),
+                            content->content, &attrs);
+      content->width = attrs.width;
+      content->height = attrs.height;
+    }
+
+  *minimum_baseline = *natural_baseline = -1;
+  *minimum = 1;
+  *natural = (orientation == GTK_ORIENTATION_VERTICAL) ?
+    content->height : content->width;
+}
+
+static void
+meta_frame_content_size_allocate (GtkWidget *widget,
+                                  int        width,
+                                  int        height,
+                                  int        baseline)
+{
+  MetaFrameContent *content = META_FRAME_CONTENT (widget);
+  GtkWindow *window = GTK_WINDOW (gtk_widget_get_root (widget));
+  GdkDisplay *display = gtk_widget_get_display (widget);
+  double x = 0, y = 0, sx, sy, scale;
+
+  scale = gdk_surface_get_scale_factor (gtk_native_get_surface (GTK_NATIVE (window)));
+  gtk_widget_translate_coordinates (widget,
+                                    GTK_WIDGET (window),
+                                    x, y,
+                                    &x, &y);
+
+  meta_frame_content_update_frame_height (content, y);
+
+  gtk_native_get_surface_transform (GTK_NATIVE (window),
+                                    &sx, &sy);
+  x += sx;
+  y += sy;
+
+  if (content->rect.x != x || content->rect.y != y ||
+      content->rect.width != width || content->rect.height != height)
+    {
+      XMoveResizeWindow (gdk_x11_display_get_xdisplay (display),
+                         content->content,
+                         x * scale,
+                         y * scale,
+                         width * scale,
+                         height * scale);
+      content->rect = (cairo_rectangle_int_t) { x, y, width, height };
+    }
+}
+
+static void
+meta_frame_content_class_init (MetaFrameContentClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  widget_class->measure = meta_frame_content_measure;
+  widget_class->size_allocate = meta_frame_content_size_allocate;
+}
+
+static void
+meta_frame_content_init (MetaFrameContent *content)
+{
+  content->width = content->height = -1;
+  content->frame_height = -1;
+}
+
+static GtkWidget *
+meta_frame_content_new (Display *xdisplay,
+                        Window   window)
+{
+  MetaFrameContent *content;
+
+  content = g_object_new (META_TYPE_FRAME_CONTENT, NULL);
+  content->content = window;
+
+  XSelectInput (xdisplay, window,
+                PropertyChangeMask | StructureNotifyMask);
+
+  return GTK_WIDGET (content);
+}
+
+static gboolean
+on_frame_close_request (GtkWindow *window,
+                        gpointer   user_data)
+{
+  MetaFrameContent *content = user_data;
+  XClientMessageEvent ev;
+  GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (window));
+
+  ev.type = ClientMessage;
+  ev.window = content->content;
+  ev.message_type =
+    gdk_x11_get_xatom_by_name_for_display (display, "WM_PROTOCOLS");
+  ev.format = 32;
+  ev.data.l[0] =
+    gdk_x11_get_xatom_by_name_for_display (display, "WM_DELETE_WINDOW");
+  ev.data.l[1] = 0; /* FIXME: missing timestamp */
+
+  gdk_x11_display_error_trap_push (display);
+  XSendEvent (gdk_x11_display_get_xdisplay (display),
+              content->content, False, 0, (XEvent*) &ev);
+  gdk_x11_display_error_trap_pop_ignored (display);
+
+  return TRUE;
+}
+
+static gboolean
+on_xevent (GdkDisplay *display,
+           XEvent     *xevent,
+           gpointer    user_data)
+{
+  GtkWindow *frame;
+
+  if (xevent->type == ClientMessage &&
+      xevent->xclient.message_type ==
+      gdk_x11_get_xatom_by_name_for_display (display, "_MUTTER_FRAME"))
+    {
+      Window client_window = xevent->xclient.data.l[0];
+
+      /* Double check it's not a request for a frame of our own. */
+      if (!g_hash_table_contains (frames, GUINT_TO_POINTER (client_window)))
+        {
+          GtkWidget *header, *content;
+          GdkSurface *surface;
+          unsigned long data[1];
+          Window xframe;
+
+          /* Create a frame window */
+          XGrabServer (gdk_x11_display_get_xdisplay (display));
+
+          frame = GTK_WINDOW (gtk_window_new ());
+
+          header = gtk_header_bar_new ();
+          gtk_window_set_titlebar (GTK_WINDOW (frame), header);
+          content = meta_frame_content_new (gdk_x11_display_get_xdisplay (display),
+                                            client_window);
+          gtk_window_set_child (frame, content);
+
+          g_signal_connect (frame, "close-request",
+                            G_CALLBACK (on_frame_close_request), content);
+
+          gtk_widget_show (GTK_WIDGET (frame));
+          surface = gtk_native_get_surface (GTK_NATIVE (frame));
+          gdk_x11_surface_set_frame_sync_enabled (surface, FALSE);
+          xframe = gdk_x11_surface_get_xid (surface);
+
+          data[0] = client_window;
+          XChangeProperty (gdk_x11_display_get_xdisplay (display),
+                           xframe,
+                           gdk_x11_get_xatom_by_name_for_display (display, "_MUTTER_FRAME_FOR"),
+                           XA_WINDOW,
+                           32,
+                           PropModeReplace,
+                           (guchar *) data, 1);
+
+          XUngrabServer (gdk_x11_display_get_xdisplay (display));
+
+          g_hash_table_insert (frames, GUINT_TO_POINTER (xframe), frame);
+          g_hash_table_insert (client_windows, GUINT_TO_POINTER (client_window), frame);
+        }
+    }
+  else if (xevent->type == DestroyNotify)
+    {
+      frame = g_hash_table_lookup (client_windows,
+                                   GUINT_TO_POINTER (xevent->xdestroywindow.window));
+
+      if (frame)
+        {
+          GdkSurface *surface;
+          Window xframe;
+
+          surface = gtk_native_get_surface (GTK_NATIVE (frame));
+          xframe = gdk_x11_surface_get_xid (surface);
+
+          g_hash_table_remove (client_windows,
+                               GUINT_TO_POINTER (xevent->xdestroywindow.window));
+          g_hash_table_remove (frames, GUINT_TO_POINTER (xframe));
+        }
+    }
+  else if (xevent->type == PropertyNotify &&
+           xevent->xproperty.atom ==
+           gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_NAME"))
+    {
+      frame = g_hash_table_lookup (client_windows,
+                                   GUINT_TO_POINTER (xevent->xproperty.window));
+      if (frame)
+        {
+          char *title = NULL;
+
+          if (xevent->xproperty.state == PropertyNewValue)
+            {
+              int format;
+              Atom type;
+              unsigned long nitems, bytes_after;
+
+              XGetWindowProperty (gdk_x11_display_get_xdisplay (display),
+                                  xevent->xproperty.window,
+                                  xevent->xproperty.atom,
+                                  0, G_MAXLONG, False,
+                                  gdk_x11_get_xatom_by_name_for_display (display,
+                                                                         "UTF8_STRING"),
+                                  &type, &format,
+                                  &nitems, &bytes_after,
+                                  (unsigned char **) &title);
+            }
+
+          gtk_window_set_title (frame, title);
+          g_free (title);
+        }
+    }
+  else if (xevent->type == PropertyNotify &&
+           xevent->xproperty.atom ==
+           gdk_x11_get_xatom_by_name_for_display (display, "_MOTIF_WM_HINTS"))
+    {
+      frame = g_hash_table_lookup (client_windows,
+                                   GUINT_TO_POINTER (xevent->xproperty.window));
+      if (frame)
+        {
+          MotifWmHints *mwm_hints = NULL;
+
+          if (xevent->xproperty.state == PropertyNewValue)
+            {
+              int format;
+              Atom type;
+              unsigned long nitems, bytes_after;
+
+              XGetWindowProperty (gdk_x11_display_get_xdisplay (display),
+                                  xevent->xproperty.window,
+                                  xevent->xproperty.atom,
+                                  0, sizeof (MotifWmHints) / sizeof (long),
+                                  False, AnyPropertyType,
+                                  &type, &format,
+                                  &nitems, &bytes_after,
+                                  (unsigned char **) &mwm_hints);
+            }
+
+          gtk_window_set_deletable (frame, (mwm_hints->functions & MWM_FUNC_CLOSE) == 0);
+          g_free (mwm_hints);
+        }
+    }
+  else if (xevent->type == ConfigureNotify)
+    {
+      frame = g_hash_table_lookup (client_windows,
+                                   GUINT_TO_POINTER (xevent->xconfigure.window));
+      if (frame)
+        {
+          MetaFrameContent *content;
+
+          content = META_FRAME_CONTENT (gtk_window_get_child (frame));
+
+          if (xevent->xconfigure.width !=
+              gtk_widget_get_allocated_width (GTK_WIDGET (content)) ||
+              xevent->xconfigure.height !=
+              gtk_widget_get_allocated_height (GTK_WIDGET (content)))
+            {
+              content->width = xevent->xconfigure.width;
+              content->height = xevent->xconfigure.height;
+              gtk_widget_queue_resize (GTK_WIDGET (content));
+            }
+        }
+    }
+
+  return GDK_EVENT_PROPAGATE;
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+  GdkDisplay *display;
+  GMainLoop *loop;
+  Display *xdisplay;
+  Window xroot;
+
+  /* This seems to be the renderer that works best with
+   * frame sync disabled.
+   */
+  g_setenv ("GSK_RENDERER", "cairo", TRUE);
+
+  gdk_set_allowed_backends ("x11");
+
+  gtk_init ();
+
+  frames = g_hash_table_new_full (NULL, NULL, NULL,
+                                  (GDestroyNotify) gtk_window_destroy);
+  client_windows = g_hash_table_new (NULL, NULL);
+
+  display = gdk_display_get_default ();
+
+  xdisplay = gdk_x11_display_get_xdisplay (display);
+  xroot = gdk_x11_display_get_xrootwindow (display);
+  XSelectInput (xdisplay, xroot,
+                KeyPressMask |
+                PropertyChangeMask);
+
+  g_signal_connect (display, "xevent",
+                    G_CALLBACK (on_xevent), NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (loop);
+  g_main_loop_unref (loop);
+
+  return 0;
+}
diff --git a/src/frames/meson.build b/src/frames/meson.build
new file mode 100644
index 0000000000..7639920e2b
--- /dev/null
+++ b/src/frames/meson.build
@@ -0,0 +1,10 @@
+frames = executable('mutter-x11-frames',
+  sources: ['main.c'],
+  dependencies: [
+    gtk4_dep,
+    x11_dep,
+    xext_dep,
+  ],
+  install: true,
+  install_dir: get_option('libexecdir'),
+)
diff --git a/src/meson.build b/src/meson.build
index 6e043d5e86..af1d4464d0 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1126,6 +1126,7 @@ pkg.generate(libmutter,
 )
 
 subdir('compositor/plugins')
+subdir('frames')
 
 if have_core_tests
   subdir('tests')


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]