diff options
Diffstat (limited to 'mate-panel/panel-multiscreen.c')
-rw-r--r-- | mate-panel/panel-multiscreen.c | 784 |
1 files changed, 784 insertions, 0 deletions
diff --git a/mate-panel/panel-multiscreen.c b/mate-panel/panel-multiscreen.c new file mode 100644 index 00000000..eef3ed72 --- /dev/null +++ b/mate-panel/panel-multiscreen.c @@ -0,0 +1,784 @@ +/* + * panel-multiscreen.c: Multi-screen and Xinerama support for the panel. + * + * Copyright (C) 2001 George Lebl <[email protected]> + * 2002 Sun Microsystems Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Authors: George Lebl <[email protected]>, + * Mark McLoughlin <[email protected]> + */ + +#include <config.h> + +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/extensions/Xrandr.h> +#include <gdk/gdkx.h> + +#include "panel-multiscreen.h" + +#include <string.h> + +#if defined(HAVE_CRT_EXTERNS_H) && defined(HAVE__NSGETENVIRON) +#include <crt_externs.h> /* for _NSGetEnviron */ +#define environ (*_NSGetEnviron()) +#else +extern char **environ; +#endif + +static int screens = 0; +static int *monitors = NULL; +static GdkRectangle **geometries = NULL; +static gboolean initialized = FALSE; +static gboolean have_randr = FALSE; +static gboolean have_randr_1_3 = FALSE; +static guint reinit_id = 0; + +#ifdef HAVE_RANDR +static gboolean +_panel_multiscreen_output_should_be_first (Display *xdisplay, + RROutput output, + XRROutputInfo *info, + RROutput primary) +{ + if (primary) + return output == primary; + + if (have_randr_1_3) { + Atom connector_type_atom; + Atom actual_type; + int actual_format; + unsigned long nitems; + unsigned long bytes_after; + unsigned char *prop; + char *connector_type; + gboolean retval; + + connector_type_atom = XInternAtom (xdisplay, "ConnectorType", False); + + if (XRRGetOutputProperty (xdisplay, output, connector_type_atom, + 0, 100, False, False, None, + &actual_type, &actual_format, + &nitems, &bytes_after, &prop) == Success) { + if (actual_type == XA_ATOM && nitems == 1 && actual_format == 32) { + connector_type = XGetAtomName (xdisplay, prop[0]); + retval = g_strcmp0 (connector_type, "Panel") == 0; + XFree (connector_type); + return retval; + } + } + } + + /* Pre-1.3 fallback: + * "LVDS" is the oh-so-intuitive name that X gives to laptop LCDs. + * It can actually be LVDS0, LVDS-0, Lvds, etc. + */ + return (g_ascii_strncasecmp (info->name, "LVDS", strlen ("LVDS")) == 0); +} +#endif + +static gboolean +panel_multiscreen_get_randr_monitors_for_screen (GdkScreen *screen, + int *monitors_ret, + GdkRectangle **geometries_ret) +{ +#ifdef HAVE_RANDR + Display *xdisplay; + Window xroot; + XRRScreenResources *resources; + RROutput primary; + GArray *geometries; + int i; + gboolean driver_is_pre_randr_1_2; + + if (!have_randr) + return FALSE; + + /* GTK+ 2.14.x uses the Xinerama API, instead of RANDR, to get the + * monitor geometries. It does this to avoid calling + * XRRGetScreenResources(), which is slow as it re-detects all the + * monitors --- note that XRRGetScreenResourcesCurrent() had not been + * introduced yet. Using Xinerama in GTK+ has the bad side effect that + * gdk_screen_get_monitor_plug_name() will return NULL, as Xinerama + * does not provide that information, unlike RANDR. + * + * Here we need to identify the output names, so that we can put the + * built-in LCD in a laptop *before* all other outputs. This is so + * that mate-panel will normally prefer to appear on the "native" + * display rather than on an external monitor. + * + * To get the output names and geometries, we will not use + * gdk_screen_get_n_monitors() and friends, but rather we will call + * XRR*() directly. + * + * See https://bugzilla.novell.com/show_bug.cgi?id=479684 for this + * particular bug, and and + * http://bugzilla.gnome.org/show_bug.cgi?id=562944 for a more + * long-term solution. + */ + + xdisplay = GDK_SCREEN_XDISPLAY (screen); + xroot = GDK_WINDOW_XWINDOW (gdk_screen_get_root_window (screen)); + +#if (RANDR_MAJOR > 1 || (RANDR_MAJOR == 1 && RANDR_MINOR >= 3)) + if (have_randr_1_3) { + resources = XRRGetScreenResourcesCurrent (xdisplay, xroot); + if (resources->noutput == 0) { + /* This might happen if nothing tried to get randr + * resources from the server before, so we need an + * active probe. See comment #27 in + * https://bugzilla.gnome.org/show_bug.cgi?id=597101 */ + XRRFreeScreenResources (resources); + resources = XRRGetScreenResources (xdisplay, xroot); + } + } else + resources = XRRGetScreenResources (xdisplay, xroot); +#else + resources = XRRGetScreenResources (xdisplay, xroot); +#endif + + if (!resources) + return FALSE; + + primary = None; +#if (RANDR_MAJOR > 1 || (RANDR_MAJOR == 1 && RANDR_MINOR >= 3)) + if (have_randr_1_3) + primary = XRRGetOutputPrimary (xdisplay, xroot); +#endif + + geometries = g_array_sized_new (FALSE, FALSE, + sizeof (GdkRectangle), + resources->noutput); + + driver_is_pre_randr_1_2 = FALSE; + + for (i = 0; i < resources->noutput; i++) { + XRROutputInfo *output; + + output = XRRGetOutputInfo (xdisplay, resources, + resources->outputs[i]); + + /* Drivers before RANDR 1.2 return "default" for the output + * name */ + if (g_strcmp0 (output->name, "default") == 0) + driver_is_pre_randr_1_2 = TRUE; + + if (output->connection != RR_Disconnected && + output->crtc != 0) { + XRRCrtcInfo *crtc; + GdkRectangle rect; + + crtc = XRRGetCrtcInfo (xdisplay, resources, + output->crtc); + + rect.x = crtc->x; + rect.y = crtc->y; + rect.width = crtc->width; + rect.height = crtc->height; + + XRRFreeCrtcInfo (crtc); + + if (_panel_multiscreen_output_should_be_first (xdisplay, + resources->outputs[i], + output, primary)) + g_array_prepend_vals (geometries, &rect, 1); + else + g_array_append_vals (geometries, &rect, 1); + } + + XRRFreeOutputInfo (output); + } + + XRRFreeScreenResources (resources); + + if (driver_is_pre_randr_1_2) { + /* Drivers before RANDR 1.2 don't provide useful info about + * outputs */ + g_array_free (geometries, TRUE); + return FALSE; + } + + if (geometries->len == 0) { + /* This can happen in at least one case: + * https://bugzilla.novell.com/show_bug.cgi?id=543876 where all + * monitors appear disconnected (possibly because the screen + * is behing a KVM switch) -- see comment #8. + * There might be other cases too, so we stay on the safe side. + */ + g_array_free (geometries, TRUE); + return FALSE; + } + + *monitors_ret = geometries->len; + *geometries_ret = (GdkRectangle *) g_array_free (geometries, FALSE); + + return TRUE; +#else + return FALSE; +#endif +} + +static void +panel_multiscreen_get_gdk_monitors_for_screen (GdkScreen *screen, + int *monitors_ret, + GdkRectangle **geometries_ret) +{ + int num_monitors; + GdkRectangle *geometries; + int i; + + num_monitors = gdk_screen_get_n_monitors (screen); + geometries = g_new (GdkRectangle, num_monitors); + + for (i = 0; i < num_monitors; i++) + gdk_screen_get_monitor_geometry (screen, i, &(geometries[i])); + + *monitors_ret = num_monitors; + *geometries_ret = geometries; +} + +static void +panel_multiscreen_get_raw_monitors_for_screen (GdkScreen *screen, + int *monitors_ret, + GdkRectangle **geometries_ret) +{ + gboolean res; + + *monitors_ret = 0; + *geometries_ret = NULL; + + res = panel_multiscreen_get_randr_monitors_for_screen (screen, + monitors_ret, + geometries_ret); + if (res && *monitors_ret > 0) + return; + + panel_multiscreen_get_gdk_monitors_for_screen (screen, + monitors_ret, + geometries_ret); +} + +static inline gboolean +rectangle_overlaps (GdkRectangle *a, + GdkRectangle *b) +{ + return gdk_rectangle_intersect (a, b, NULL); +} + +static long +pixels_in_rectangle (GdkRectangle *r) +{ + return (long) (r->width * r->height); +} + +static void +panel_multiscreen_compress_overlapping_monitors (int *num_monitors_inout, + GdkRectangle **geometries_inout) +{ + int num_monitors; + GdkRectangle *geometries; + int i; + + num_monitors = *num_monitors_inout; + geometries = *geometries_inout; + + /* http://bugzilla.gnome.org/show_bug.cgi?id=530969 + * https://bugzilla.novell.com/show_bug.cgi?id=310208 + * and many other such bugs... + * + * RANDR sometimes gives us monitors that overlap (i.e. outputs whose + * bounding rectangles overlap). This is sometimes right and sometimes + * wrong: + * + * * Right - two 1024x768 outputs at the same offset (0, 0) that show + * the same thing. Think "laptop plus projector with the same + * resolution". + * + * * Wrong - one 1280x1024 output ("laptop internal LCD") and another + * 1024x768 output ("external monitor"), both at offset (0, 0). + * There is no way for the monitor with the small resolution to + * show the complete image from the laptop's LCD, unless one uses + * panning (but nobody wants panning, right!?). + * + * With overlapping monitors, we may end up placing the panel with + * respect to the "wrong" one. This is always wrong, as the panel + * appears "in the middle of the screen" of the monitor with the + * smaller resolution, instead of at the edge. + * + * Our strategy is to find the subsets of overlapping monitors, and + * "compress" each such set to being like if there were a single + * monitor with the biggest resolution of each of that set's monitors. + * Say we have four monitors + * + * A, B, C, D + * + * where B and D overlap. In that case, we'll generate a new list that + * looks like + * + * A, MAX(B, D), C + * + * with three monitors. + * + * NOTE FOR THE FUTURE: We could avoid most of this mess if we had a + * concept of a "primary monitor". Also, we could look at each + * output's name or properties to see if it is the built-in LCD in a + * laptop. However, with GTK+ 2.14.x we don't get output names, since + * it gets the list outputs from Xinerama, not RANDR (and Xinerama + * doesn't provide output names). + */ + + for (i = 0; i < num_monitors; i++) { + long max_pixels; + int j; + + max_pixels = pixels_in_rectangle (&geometries[i]); + + j = i + 1; + + while (j < num_monitors) { + if (rectangle_overlaps (&geometries[i], + &geometries[j])) { + long pixels; + + pixels = pixels_in_rectangle (&geometries[j]); + if (pixels > max_pixels) { + max_pixels = pixels; + /* keep the maximum */ + geometries[i] = geometries[j]; + } + + /* Shift the remaining monitors to the left */ + if (num_monitors - j - 1 > 0) + memmove (&geometries[j], + &geometries[j + 1], + sizeof (geometries[0]) * (num_monitors - j - 1)); + + num_monitors--; + g_assert (num_monitors > 0); + } else + j++; + } + } + + *num_monitors_inout = num_monitors; + *geometries_inout = geometries; +} + +static void +panel_multiscreen_get_monitors_for_screen (GdkScreen *screen, + int *monitors_ret, + GdkRectangle **geometries_ret) +{ + panel_multiscreen_get_raw_monitors_for_screen (screen, + monitors_ret, + geometries_ret); + panel_multiscreen_compress_overlapping_monitors (monitors_ret, + geometries_ret); +} + +static gboolean +panel_multiscreen_reinit_idle (gpointer data) +{ + panel_multiscreen_reinit (); + reinit_id = 0; + + return FALSE; +} + +static void +panel_multiscreen_queue_reinit (void) +{ + if (reinit_id) + return; + + reinit_id = g_idle_add (panel_multiscreen_reinit_idle, NULL); +} + +static void +panel_multiscreen_init_randr (GdkDisplay *display) +{ +#ifdef HAVE_RANDR + Display *xdisplay; + int event_base, error_base; +#endif + + have_randr = FALSE; + have_randr_1_3 = FALSE; + +#ifdef HAVE_RANDR + xdisplay = GDK_DISPLAY_XDISPLAY (display); + + /* We don't remember the event/error bases, as we expect to get "screen + * changed" events from GdkScreen instead. + */ + + if (XRRQueryExtension (xdisplay, &event_base, &error_base)) { + int major, minor; + + XRRQueryVersion (xdisplay, &major, &minor); + if ((major == 1 && minor >= 2) || major > 1) + have_randr = TRUE; + + if ((major == 1 && minor >= 3) || major > 1) + have_randr_1_3 = TRUE; + } +#endif +} + +void +panel_multiscreen_init (void) +{ + GdkDisplay *display; + int i; + + if (initialized) + return; + + display = gdk_display_get_default (); + screens = gdk_display_get_n_screens (display); + + panel_multiscreen_init_randr (display); + + monitors = g_new0 (int, screens); + geometries = g_new0 (GdkRectangle *, screens); + + for (i = 0; i < screens; i++) { + GdkScreen *screen; + + screen = gdk_display_get_screen (display, i); + + /* We connect to both signals to be on the safe side, but in + * theory, it should be enough to only connect to + * monitors-changed. Since we'll likely get two signals, we do + * the real callback in the idle loop. */ + g_signal_connect (screen, "size-changed", + G_CALLBACK (panel_multiscreen_queue_reinit), NULL); + g_signal_connect (screen, "monitors-changed", + G_CALLBACK (panel_multiscreen_queue_reinit), NULL); + + panel_multiscreen_get_monitors_for_screen (screen, + &(monitors[i]), + &(geometries[i])); + } + + initialized = TRUE; +} + +void +panel_multiscreen_reinit (void) +{ + GdkDisplay *display; + GList *toplevels, *l; + int new_screens; + int i; + + if (monitors) + g_free (monitors); + + if (geometries) { + int j; + + for (j = 0; j < screens; j++) + g_free (geometries[j]); + g_free (geometries); + } + + display = gdk_display_get_default (); + /* Don't use the screens variable since in the future, we might + * want to call this function when a screen appears/disappears. */ + new_screens = gdk_display_get_n_screens (display); + + for (i = 0; i < new_screens; i++) { + GdkScreen *screen; + + screen = gdk_display_get_screen (display, i); + g_signal_handlers_disconnect_by_func (screen, + panel_multiscreen_queue_reinit, + NULL); + } + + initialized = FALSE; + panel_multiscreen_init (); + + toplevels = gtk_window_list_toplevels (); + + for (l = toplevels; l; l = l->next) + gtk_widget_queue_resize (l->data); + + g_list_free (toplevels); +} + +int +panel_multiscreen_screens (void) +{ + return screens; +} + +int +panel_multiscreen_monitors (GdkScreen *screen) +{ + int n_screen; + + n_screen = gdk_screen_get_number (screen); + + g_return_val_if_fail (n_screen >= 0 && n_screen < screens, 1); + + return monitors [n_screen]; +} + +int +panel_multiscreen_x (GdkScreen *screen, + int monitor) +{ + int n_screen; + + n_screen = gdk_screen_get_number (screen); + + g_return_val_if_fail (n_screen >= 0 && n_screen < screens, 0); + g_return_val_if_fail (monitor >= 0 && monitor < monitors [n_screen], 0); + + return geometries [n_screen][monitor].x; +} + +int +panel_multiscreen_y (GdkScreen *screen, + int monitor) +{ + int n_screen; + + n_screen = gdk_screen_get_number (screen); + + g_return_val_if_fail (n_screen >= 0 && n_screen < screens, 0); + g_return_val_if_fail (monitor >= 0 && monitor < monitors [n_screen], 0); + + return geometries [n_screen][monitor].y; +} + +int +panel_multiscreen_width (GdkScreen *screen, + int monitor) +{ + int n_screen; + + n_screen = gdk_screen_get_number (screen); + + g_return_val_if_fail (n_screen >= 0 && n_screen < screens, 0); + g_return_val_if_fail (monitor >= 0 && monitor < monitors [n_screen], 0); + + return geometries [n_screen][monitor].width; +} + +int +panel_multiscreen_height (GdkScreen *screen, + int monitor) +{ + int n_screen; + + n_screen = gdk_screen_get_number (screen); + + g_return_val_if_fail (n_screen >= 0 && n_screen < screens, 0); + g_return_val_if_fail (monitor >= 0 && monitor < monitors [n_screen], 0); + + return geometries [n_screen][monitor].height; +} + +int +panel_multiscreen_locate_widget_monitor (GtkWidget *widget) +{ + GtkWidget *toplevel; + int retval = -1; + + toplevel = gtk_widget_get_toplevel (widget); + if (!toplevel) + return -1; + + g_object_get (toplevel, "monitor", &retval, NULL); + + return retval; +} + +static int +axis_distance (int p, int axis_start, int axis_size) +{ + if (p >= axis_start && p < axis_start + axis_size) + return 0; + else if (p < axis_start) + return (axis_start - p); + else + return (p - (axis_start + axis_size - 1)); +} + +/* The panel can't use gdk_screen_get_monitor_at_point() since it has its own + * view of which monitors are present. Look at get_monitors_for_screen() above + * to see why. */ +int +panel_multiscreen_get_monitor_at_point (GdkScreen *screen, + int x, + int y) +{ + int n_screen; + int i; + int n_monitors; + GdkRectangle *geoms; + int min_dist_squared; + int closest_monitor; + + /* not -1 as callers expect a real monitor */ + g_return_val_if_fail (GDK_IS_SCREEN (screen), 0); + + n_screen = gdk_screen_get_number (screen); + + n_monitors = monitors[n_screen]; + geoms = geometries[n_screen]; + + min_dist_squared = G_MAXINT32; + closest_monitor = 0; + + for (i = 0; i < n_monitors; i++) { + int dist_x, dist_y; + int dist_squared; + + dist_x = axis_distance (x, geoms[i].x, geoms[i].width); + dist_y = axis_distance (y, geoms[i].y, geoms[i].height); + + if (dist_x == 0 && dist_y == 0) + return i; + + dist_squared = dist_x * dist_x + dist_y * dist_y; + + if (dist_squared < min_dist_squared) { + min_dist_squared = dist_squared; + closest_monitor = i; + } + } + + return closest_monitor; +} + +typedef struct { + int x0; + int y0; + int x1; + int y1; +} MonitorBounds; + +static inline void +get_monitor_bounds (int n_screen, + int n_monitor, + MonitorBounds *bounds) +{ + g_assert (n_screen >= 0 && n_screen < screens); + g_assert (n_monitor >= 0 || n_monitor < monitors [n_screen]); + g_assert (bounds != NULL); + + bounds->x0 = geometries [n_screen][n_monitor].x; + bounds->y0 = geometries [n_screen][n_monitor].y; + bounds->x1 = bounds->x0 + geometries [n_screen][n_monitor].width; + bounds->y1 = bounds->y0 + geometries [n_screen][n_monitor].height; +} + +/* determines whether a given monitor is along the visible + * edge of the logical screen. + */ +void +panel_multiscreen_is_at_visible_extreme (GdkScreen *screen, + int n_monitor, + gboolean *leftmost, + gboolean *rightmost, + gboolean *topmost, + gboolean *bottommost) +{ + MonitorBounds monitor; + int n_screen, i; + + n_screen = gdk_screen_get_number (screen); + + *leftmost = TRUE; + *rightmost = TRUE; + *topmost = TRUE; + *bottommost = TRUE; + + g_return_if_fail (n_screen >= 0 && n_screen < screens); + g_return_if_fail (n_monitor >= 0 && n_monitor < monitors [n_screen]); + + get_monitor_bounds (n_screen, n_monitor, &monitor); + + /* go through each monitor and try to find one either right, + * below, above, or left of the specified monitor + */ + + for (i = 0; i < monitors [n_screen]; i++) { + MonitorBounds iter; + + if (i == n_monitor) continue; + + get_monitor_bounds (n_screen, i, &iter); + + if ((iter.y0 >= monitor.y0 && iter.y0 < monitor.y1) || + (iter.y1 > monitor.y0 && iter.y1 <= monitor.y1)) { + if (iter.x0 < monitor.x0) + *leftmost = FALSE; + if (iter.x1 > monitor.x1) + *rightmost = FALSE; + } + + if ((iter.x0 >= monitor.x0 && iter.x0 < monitor.x1) || + (iter.x1 > monitor.x0 && iter.x1 <= monitor.x1)) { + if (iter.y0 < monitor.y0) + *topmost = FALSE; + if (iter.y1 > monitor.y1) + *bottommost = FALSE; + } + } +} + +char ** +panel_make_environment_for_screen (GdkScreen *screen, + char **envp) +{ + char **retval = NULL; + char *display_name; + int display_index = -1; + int i, env_len; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + + if (envp == NULL) + envp = environ; + + for (env_len = 0; envp[env_len]; env_len++) + if (strncmp (envp[env_len], "DISPLAY", strlen ("DISPLAY")) == 0) + display_index = env_len; + + retval = g_new (char *, env_len + 1); + retval[env_len] = NULL; + + display_name = gdk_screen_make_display_name (screen); + + for (i = 0; i < env_len; i++) + if (i == display_index) + retval[i] = g_strconcat ("DISPLAY=", display_name, NULL); + else + retval[i] = g_strdup (envp[i]); + + g_assert (i == env_len); + + g_free (display_name); + + return retval; +} |