File gnome-panel-bnc477845-sanitize-overlapping-monitors.diff of Package gnome-panel

bnc477845 - Panel appears in the middle of the screen when there are overlapping monitors
This is also bgo530969.

diff --git a/gnome-panel/panel-multiscreen.c b/gnome-panel/panel-multiscreen.c
index c71eb0f..06df009 100644
--- a/gnome-panel/panel-multiscreen.c
+++ b/gnome-panel/panel-multiscreen.c
@@ -41,6 +41,111 @@ static int           *monitors    = NULL;
 static GdkRectangle **geometries  = NULL;
 static gboolean	      initialized = FALSE;
 
+static gboolean
+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
+get_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]));
+
+	/* 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 (overlaps (&geometries[i], &geometries[j])) {
+				long pixels;
+
+				pixels = pixels_in_rectangle (&geometries[j]);
+				if (pixels > max_pixels) {
+					max_pixels = pixels;
+					geometries[i] = geometries[j]; /* keep the maximum */
+				}
+
+				/* 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++;
+		}
+	}
+
+	*monitors_ret = num_monitors;
+	*geometries_ret = geometries;
+}
+
 void
 panel_multiscreen_init (void)
 {
@@ -58,19 +163,13 @@ panel_multiscreen_init (void)
 
 	for (i = 0; i < screens; i++) {
 		GdkScreen *screen;
-		int        j;
 
 		screen = gdk_display_get_screen (display, i);
 
 		g_signal_connect (screen, "size-changed",
 				  G_CALLBACK (panel_multiscreen_reinit), NULL);
 
-		monitors   [i] = gdk_screen_get_n_monitors (screen);
-		geometries [i] = g_new0 (GdkRectangle, monitors [i]);
-
-		for (j = 0; j < monitors [i]; j++)
-			gdk_screen_get_monitor_geometry (
-				screen, j, &geometries [i][j]);
+		get_monitors_for_screen (screen, &(monitors[i]), &(geometries[i]));
 	}
 
 	initialized = TRUE;
@@ -209,6 +308,61 @@ panel_multiscreen_locate_widget_monitor (GtkWidget *widget)
 	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;
+
+	g_return_val_if_fail (GDK_IS_SCREEN (screen), 0); /* not -1 as callers expect a real monitor */
+
+	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;
diff --git a/gnome-panel/panel-multiscreen.h b/gnome-panel/panel-multiscreen.h
index ba5fe2c..dd31b89 100644
--- a/gnome-panel/panel-multiscreen.h
+++ b/gnome-panel/panel-multiscreen.h
@@ -43,6 +43,9 @@ int	panel_multiscreen_width                 (GdkScreen *screen,
 int	panel_multiscreen_height                (GdkScreen *screen,
 						 int        monitor);
 int	panel_multiscreen_locate_widget_monitor (GtkWidget *widget);
+int     panel_multiscreen_get_monitor_at_point  (GdkScreen *screen,
+						 int        x,
+						 int        y);
 void    panel_multiscreen_is_at_visible_extreme (GdkScreen *screen,
 						 int        monitor,
 						 gboolean  *leftmost,
diff --git a/gnome-panel/panel-toplevel.c b/gnome-panel/panel-toplevel.c
index 22694fe..5c9824b 100644
--- a/gnome-panel/panel-toplevel.c
+++ b/gnome-panel/panel-toplevel.c
@@ -615,7 +615,7 @@ panel_toplevel_calc_new_orientation (PanelToplevel *toplevel,
 
 	screen = gtk_window_get_screen (GTK_WINDOW (toplevel));
 
-	monitor = gdk_screen_get_monitor_at_point (screen, pointer_x, pointer_y);
+	monitor = panel_multiscreen_get_monitor_at_point (screen, pointer_x, pointer_y);
 
 	if (toplevel->priv->geometry.height < toplevel->priv->geometry.width)
 		vborder = hborder = (3 * toplevel->priv->geometry.height) >> 1;
@@ -727,7 +727,7 @@ panel_toplevel_move_to (PanelToplevel *toplevel,
 		 toplevel->priv->orientation & PANEL_HORIZONTAL_MASK)
 		new_orientation = PANEL_ORIENTATION_BOTTOM;
 
-	new_monitor = gdk_screen_get_monitor_at_point (screen, new_x, new_y);
+	new_monitor = panel_multiscreen_get_monitor_at_point (screen, new_x, new_y);
 
 	panel_toplevel_get_monitor_geometry (
 			toplevel, NULL, NULL, &monitor_width, &monitor_height);
@@ -2091,7 +2091,7 @@ panel_toplevel_update_expanded_position (PanelToplevel *toplevel)
 		break;
 	}
 
-	monitor = gdk_screen_get_monitor_at_point (screen, x, y);
+	monitor = panel_multiscreen_get_monitor_at_point (screen, x, y);
 
 	panel_toplevel_set_monitor (toplevel, monitor);
 
openSUSE Build Service is sponsored by