From dac6a76ef2282c57aa5deb72481b9e09e603a0b6 Mon Sep 17 00:00:00 2001 From: Olivier Fourdan Date: Mon, 21 Jan 2013 11:52:32 +0100 Subject: [PATCH] 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 b5767b58f4..65576f7dfd 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 d807a17005..f4a15b8b2b 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 70a6ba0303..e86cf7d2c8 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 @@ -3625,6 +3633,99 @@ gdk_x11_window_unmaximize (GdkWindow *window) 0); } +static void +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)) + { + 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) { @@ -3633,10 +3734,16 @@ gdk_x11_window_fullscreen (GdkWindow *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); - + { + 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;