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
This commit is contained in:
Olivier Fourdan 2013-01-21 11:52:32 +01:00
parent 54dc823d67
commit dac6a76ef2
3 changed files with 289 additions and 4 deletions

View File

@ -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;

View File

@ -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

View File

@ -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;