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