[gtk+] x11: implement gdk_window_apply_fullscreen_mode()



commit dac6a76ef2282c57aa5deb72481b9e09e603a0b6
Author: Olivier Fourdan <ofourdan redhat com>
Date:   Mon Jan 21 11:52:32 2013 +0100

    x11: implement gdk_window_apply_fullscreen_mode()
    
    for the X11 backend using the EWMH mechanism
    _NET_WM_FULLSCREEN_MONITORS.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=691856

 gdk/x11/gdkscreen-x11.c |  167 +++++++++++++++++++++++++++++++++++++++++++++++
 gdk/x11/gdkscreen-x11.h |   10 +++
 gdk/x11/gdkwindow-x11.c |  116 +++++++++++++++++++++++++++++++-
 3 files changed, 289 insertions(+), 4 deletions(-)
---
diff --git a/gdk/x11/gdkscreen-x11.c b/gdk/x11/gdkscreen-x11.c
index b5767b5..65576f7 100644
--- a/gdk/x11/gdkscreen-x11.c
+++ b/gdk/x11/gdkscreen-x11.c
@@ -779,12 +779,179 @@ init_xfree_xinerama (GdkScreen *screen)
   return FALSE;
 }
 
+static gboolean
+init_solaris_xinerama_indices (GdkX11Screen *x11_screen)
+{
+#ifdef HAVE_SOLARIS_XINERAMA
+  XRectangle    x_monitors[MAXFRAMEBUFFERS];
+  unsigned char hints[16];
+  gint          result;
+  gint          monitor_num;
+  gint          x_n_monitors;
+  gint          i;
+
+  if (!XineramaGetState (x11_screen->xdisplay, x11_screen->screen_num))
+    return FALSE;
+
+  result = XineramaGetInfo (x11_screen->xdisplay, x11_screen->screen_num,
+                            x_monitors, hints, &x_n_monitors);
+
+  if (result == 0)
+    return FALSE;
+
+
+  for (monitor_num = 0; monitor_num < x11_screen->n_monitors; ++monitor_num)
+    {
+      for (i = 0; i < x_n_monitors; ++i)
+        {
+          if (x11_screen->monitors[monitor_num].geometry.x == x_monitors[i].x &&
+	      x11_screen->monitors[monitor_num].geometry.y == x_monitors[i].y &&
+	      x11_screen->monitors[monitor_num].geometry.width == x_monitors[i].width &&
+	      x11_screen->monitors[monitor_num].geometry.height == x_monitors[i].height)
+	    {
+	      g_hash_table_insert (x11_screen->xinerama_matches,
+				   GINT_TO_POINTER (monitor_num),
+				   GINT_TO_POINTER (i));
+	    }
+        }
+    }
+  return TRUE;
+#endif /* HAVE_SOLARIS_XINERAMA */
+
+  return FALSE;
+}
+
+static gboolean
+init_xfree_xinerama_indices (GdkX11Screen *x11_screen)
+{
+#ifdef HAVE_XFREE_XINERAMA
+  XineramaScreenInfo *x_monitors;
+  gint                monitor_num;
+  gint                x_n_monitors;
+  gint                i;
+
+  if (!XineramaIsActive (x11_screen->xdisplay))
+    return FALSE;
+
+  x_monitors = XineramaQueryScreens (x11_screen->xdisplay, &x_n_monitors);
+  if (x_n_monitors <= 0 || x_monitors == NULL)
+    {
+      if (x_monitors)
+	XFree (x_monitors);
+
+      return FALSE;
+    }
+
+  for (monitor_num = 0; monitor_num < x11_screen->n_monitors; ++monitor_num)
+    {
+      for (i = 0; i < x_n_monitors; ++i)
+        {
+          if (x11_screen->monitors[monitor_num].geometry.x == x_monitors[i].x_org &&
+	      x11_screen->monitors[monitor_num].geometry.y == x_monitors[i].y_org &&
+	      x11_screen->monitors[monitor_num].geometry.width == x_monitors[i].width &&
+	      x11_screen->monitors[monitor_num].geometry.height == x_monitors[i].height)
+	    {
+	      g_hash_table_insert (x11_screen->xinerama_matches,
+				   GINT_TO_POINTER (monitor_num),
+				   GINT_TO_POINTER (i));
+	    }
+        }
+    }
+  XFree (x_monitors);
+  return TRUE;
+#endif /* HAVE_XFREE_XINERAMA */
+
+  return FALSE;
+}
+
+static void
+init_xinerama_indices (GdkX11Screen *x11_screen)
+{
+  int opcode, firstevent, firsterror;
+
+  x11_screen->xinerama_matches = g_hash_table_new (g_direct_hash, g_direct_equal);
+  if (XQueryExtension (x11_screen->xdisplay, "XINERAMA",
+		       &opcode, &firstevent, &firsterror))
+    {
+      x11_screen->xinerama_matches = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+      /* Solaris Xinerama first, then XFree/Xorg Xinerama
+       * to match the order in init_multihead()
+       */
+      if (init_solaris_xinerama_indices (x11_screen) == FALSE)
+        init_xfree_xinerama_indices (x11_screen);
+    }
+}
+
+gint
+_gdk_x11_screen_get_xinerama_index (GdkScreen *screen,
+				    gint       monitor_num)
+{
+  GdkX11Screen *x11_screen = GDK_X11_SCREEN (screen);
+  gpointer val;
+
+  g_return_val_if_fail (monitor_num < x11_screen->n_monitors, -1);
+
+  if (x11_screen->xinerama_matches == NULL)
+    init_xinerama_indices (x11_screen);
+
+  if (g_hash_table_lookup_extended (x11_screen->xinerama_matches, GINT_TO_POINTER (monitor_num), NULL, &val))
+    return (GPOINTER_TO_INT(val));
+
+  return -1;
+}
+
+void
+_gdk_x11_screen_get_edge_monitors (GdkScreen *screen,
+                                   gint      *top,
+                                   gint      *bottom,
+                                   gint      *left,
+                                   gint      *right)
+{
+  GdkX11Screen *x11_screen = GDK_X11_SCREEN (screen);
+  gint          top_most_pos = HeightOfScreen (GDK_X11_SCREEN (screen)->xscreen);
+  gint          left_most_pos = WidthOfScreen (GDK_X11_SCREEN (screen)->xscreen);
+  gint          bottom_most_pos = 0;
+  gint          right_most_pos = 0;
+  gint          monitor_num;
+
+  for (monitor_num = 0; monitor_num < x11_screen->n_monitors; monitor_num++)
+    {
+      gint monitor_x = x11_screen->monitors[monitor_num].geometry.x;
+      gint monitor_y = x11_screen->monitors[monitor_num].geometry.y;
+      gint monitor_max_x = monitor_x + x11_screen->monitors[monitor_num].geometry.width;
+      gint monitor_max_y = monitor_y + x11_screen->monitors[monitor_num].geometry.height;
+
+      if (left && left_most_pos > monitor_x)
+	{
+	  left_most_pos = monitor_x;
+	  *left = monitor_num;
+	}
+      if (right && right_most_pos < monitor_max_x)
+	{
+	  right_most_pos = monitor_max_x;
+	  *right = monitor_num;
+	}
+      if (top && top_most_pos > monitor_y)
+	{
+	  top_most_pos = monitor_y;
+	  *top = monitor_num;
+	}
+      if (bottom && bottom_most_pos < monitor_max_y)
+	{
+	  bottom_most_pos = monitor_max_y;
+	  *bottom = monitor_num;
+	}
+    }
+}
+
 static void
 deinit_multihead (GdkScreen *screen)
 {
   GdkX11Screen *x11_screen = GDK_X11_SCREEN (screen);
 
   free_monitors (x11_screen->monitors, x11_screen->n_monitors);
+  g_clear_pointer (&x11_screen->xinerama_matches, g_hash_table_destroy);
 
   x11_screen->n_monitors = 0;
   x11_screen->monitors = NULL;
diff --git a/gdk/x11/gdkscreen-x11.h b/gdk/x11/gdkscreen-x11.h
index d807a17..f4a15b8 100644
--- a/gdk/x11/gdkscreen-x11.h
+++ b/gdk/x11/gdkscreen-x11.h
@@ -91,6 +91,9 @@ struct _GdkX11Screen
 
   /* cache for window->translate vfunc */
   GC subwindow_gcs[32];
+
+  /* cache for Xinerama monitor indices */
+  GHashTable *xinerama_matches;
 };
 
 struct _GdkX11ScreenClass
@@ -110,6 +113,13 @@ void _gdk_x11_screen_size_changed           (GdkScreen *screen,
 					     XEvent    *event);
 void _gdk_x11_screen_process_owner_change   (GdkScreen *screen,
 					     XEvent    *event);
+gint _gdk_x11_screen_get_xinerama_index     (GdkScreen *screen,
+					     gint       monitor_num);
+void _gdk_x11_screen_get_edge_monitors      (GdkScreen *screen,
+					     gint      *top,
+					     gint      *bottom,
+					     gint      *left,
+					     gint      *right);
 
 G_END_DECLS
 
diff --git a/gdk/x11/gdkwindow-x11.c b/gdk/x11/gdkwindow-x11.c
index 70a6ba0..e86cf7d 100644
--- a/gdk/x11/gdkwindow-x11.c
+++ b/gdk/x11/gdkwindow-x11.c
@@ -97,6 +97,7 @@ const int _gdk_x11_event_mask_table[21] =
 const gint _gdk_x11_event_mask_table_size = G_N_ELEMENTS (_gdk_x11_event_mask_table);
 
 /* Forward declarations */
+static void     gdk_x11_window_apply_fullscreen_mode (GdkWindow  *window);
 static void     gdk_window_set_static_win_gravity (GdkWindow  *window,
 						   gboolean    on);
 static gboolean gdk_window_icon_name_set          (GdkWindow  *window);
@@ -1354,6 +1355,13 @@ gdk_window_x11_show (GdkWindow *window, gboolean already_mapped)
   
   if (unset_bg)
     _gdk_x11_window_tmp_reset_bg (window, TRUE);
+
+  /* Fullscreen on current monitor is the default, no need to apply this mode
+   * when mapping a window. This also ensures that the default behavior remains
+   * consistent with pre-fullscreen mode implementation.
+   */
+  if (window->fullscreen_mode != GDK_FULLSCREEN_ON_CURRENT_MONITOR)
+    gdk_x11_window_apply_fullscreen_mode (window);
 }
 
 static void
@@ -3626,17 +3634,116 @@ gdk_x11_window_unmaximize (GdkWindow *window)
 }
 
 static void
-gdk_x11_window_fullscreen (GdkWindow *window)
+gdk_x11_window_apply_fullscreen_mode (GdkWindow *window)
 {
   if (GDK_WINDOW_DESTROYED (window) ||
       !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window))
     return;
 
+  /* _NET_WM_FULLSCREEN_MONITORS gives an indication to the window manager as
+   * to which monitors so span across when the window is fullscreen, but it's
+   * not a state in itself so this would have no effect if the window is not
+   * mapped.
+   */
+
   if (GDK_WINDOW_IS_MAPPED (window))
-    gdk_wmspec_change_state (TRUE, window,
-			     gdk_atom_intern_static_string ("_NET_WM_STATE_FULLSCREEN"),
-                             GDK_NONE);
+    {
+      XClientMessageEvent xclient;
+      gint                gdk_monitors[4];
+      gint                i;
+
+      memset (&xclient, 0, sizeof (xclient));
+      xclient.type = ClientMessage;
+      xclient.window = GDK_WINDOW_XID (window);
+      xclient.display = GDK_WINDOW_XDISPLAY (window);
+      xclient.format = 32;
+
+      switch (window->fullscreen_mode)
+	{
+	case GDK_FULLSCREEN_ON_CURRENT_MONITOR:
+
+	  /* FIXME: This is not part of the EWMH spec!
+	   *
+	   * There is no documented mechanism to remove the property
+	   * _NET_WM_FULLSCREEN_MONITORS once set, so we use use a set of
+	   * invalid, largest possible value.
+	   *
+	   * When given values larger than actual possible monitor values, most
+	   * window managers who support the _NET_WM_FULLSCREEN_MONITORS spec
+	   * will simply unset _NET_WM_FULLSCREEN_MONITORS and revert to their
+	   * default behavior.
+	   *
+	   * Successfully tested on mutter/metacity, kwin, compiz and xfwm4.
+	   *
+	   * Note, this (non documented) mechanism is unlikely to be an issue
+	   * as it's used only for transitionning back from "all monitors" to
+	   * "current monitor" mode.
+	   *
+	   * Applications who don't change the default mode won't trigger this
+	   * mechanism.
+	   */
+	  for (i = 0; i < 4; ++i)
+	    xclient.data.l[i] = G_MAXLONG;
+
+	  break;
+
+	case GDK_FULLSCREEN_ON_ALL_MONITORS:
+
+	  _gdk_x11_screen_get_edge_monitors (GDK_WINDOW_SCREEN (window),
+					     &gdk_monitors[0],
+					     &gdk_monitors[1],
+					     &gdk_monitors[2],
+					     &gdk_monitors[3]);
+	  /* Translate all 4 monitors from the GDK set into XINERAMA indices */
+	  for (i = 0; i < 4; ++i)
+	    {
+	      xclient.data.l[i] = _gdk_x11_screen_get_xinerama_index (GDK_WINDOW_SCREEN (window),
+								      gdk_monitors[i]);
+	      /* Sanity check, if XINERAMA is not available, we could have invalid
+	       * negative values for the XINERAMA indices.
+	       */
+	      if (xclient.data.l[i] < 0)
+		{
+		  g_warning ("gdk_x11_window_apply_fullscreen_mode: Invalid XINERAMA monitor index");
+		  return;
+		}
+	    }
+	  break;
+
+	default:
+	  g_warning ("gdk_x11_window_apply_fullscreen_mode: Unhandled fullscreen mode %d",
+		     window->fullscreen_mode);
+	  return;
+	}
+
+      /* Send fullscreen monitors client message */
+      xclient.data.l[4] = 1; /* source indication */
+      xclient.message_type = gdk_x11_get_xatom_by_name_for_display (GDK_WINDOW_DISPLAY (window),
+								    "_NET_WM_FULLSCREEN_MONITORS");
+      XSendEvent (GDK_WINDOW_XDISPLAY (window), GDK_WINDOW_XROOTWIN (window), False,
+                  SubstructureRedirectMask | SubstructureNotifyMask,
+                  (XEvent *)&xclient);
+    }
+}
+
+static void
+gdk_x11_window_fullscreen (GdkWindow *window)
+{
+  if (GDK_WINDOW_DESTROYED (window) ||
+      !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window))
+    return;
 
+  if (GDK_WINDOW_IS_MAPPED (window))
+    {
+      gdk_wmspec_change_state (TRUE, window,
+			       gdk_atom_intern_static_string ("_NET_WM_STATE_FULLSCREEN"),
+                               GDK_NONE);
+      /* Actual XRandR layout may have change since we computed the fullscreen
+       * monitors in GDK_FULLSCREEN_ON_ALL_MONITORS mode.
+       */
+      if (window->fullscreen_mode == GDK_FULLSCREEN_ON_ALL_MONITORS)
+	gdk_x11_window_apply_fullscreen_mode (window);
+    }
   else
     gdk_synthesize_window_state (window,
                                  0,
@@ -5022,6 +5129,7 @@ gdk_window_impl_x11_class_init (GdkWindowImplX11Class *klass)
   impl_class->maximize = gdk_x11_window_maximize;
   impl_class->unmaximize = gdk_x11_window_unmaximize;
   impl_class->fullscreen = gdk_x11_window_fullscreen;
+  impl_class->apply_fullscreen_mode = gdk_x11_window_apply_fullscreen_mode;
   impl_class->unfullscreen = gdk_x11_window_unfullscreen;
   impl_class->set_keep_above = gdk_x11_window_set_keep_above;
   impl_class->set_keep_below = gdk_x11_window_set_keep_below;



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