Pick the monitor most relevant to a rectangle more cleverly.
authorDana Jansens <danakj@orodu.net>
Sun, 16 Oct 2011 03:04:21 +0000 (23:04 -0400)
committerDana Jansens <danakj@orodu.net>
Sun, 7 Oct 2012 01:56:56 +0000 (21:56 -0400)
When monitors overlap (this happens with cloning), we were choosing a monitor
to associate with a window, for maximization for example, somewhat arbitrarily.

Now we have a more clever algorithm that considers the configured primary
monitor first, and that does not prefer monitors based on their sizes, but only
how much of the window is in the monitor, excluding parts that were claimed
by another monitor already.

openbox/geom.h
openbox/screen.c

index 003b008..8ac0e55 100644 (file)
@@ -65,6 +65,8 @@ typedef struct _Rect {
 #define RECT_RIGHT(r) ((r).x + (r).width - 1)
 #define RECT_BOTTOM(r) ((r).y + (r).height - 1)
 
+#define RECT_AREA(r) ((r).width * (r).height)
+
 #define RECT_SET_POINT(r, nx, ny) \
     (r).x = (nx), (r).y = (ny)
 #define RECT_SET_SIZE(r, w, h) \
index ffe74a0..35366be 100644 (file)
@@ -1369,6 +1369,14 @@ static void get_xinerama_screens(Rect **xin_areas, guint *nxin)
         b = MAX(b, (*xin_areas)[i].y + (*xin_areas)[i].height - 1);
     }
     RECT_SET((*xin_areas)[*nxin], l, t, r - l + 1, b - t + 1);
+
+    for (i = 0; i < *nxin; ++i)
+        ob_debug("Monitor %d @ %d,%d %dx%d\n", i,
+                 (*xin_areas)[i].x, (*xin_areas)[i].y,
+                 (*xin_areas)[i].width, (*xin_areas)[i].height);
+    ob_debug("Full desktop @ %d,%d %dx%d\n",
+             (*xin_areas)[i].x, (*xin_areas)[i].y,
+             (*xin_areas)[i].width, (*xin_areas)[i].height);
 }
 
 void screen_update_areas(void)
@@ -1628,27 +1636,135 @@ Rect* screen_area(guint desktop, guint head, Rect *search)
     return a;
 }
 
+typedef struct {
+    Rect r;
+    gboolean subtract;
+} RectArithmetic;
+
 guint screen_find_monitor(const Rect *search)
 {
     guint i;
     guint most = screen_num_monitors;
-    guint mostv = 0;
+    guint mostpx = 0;
+    GSList *counted = NULL;
+
+    /* we want to count the number of pixels search has on each monitor, but not
+       double count.  so if a pixel is counted on monitor A then we should not
+       count it again on monitor B. in the end we want to return the monitor
+       that had the most pixels counted under this scheme.
+
+       this assumes that monitors earlier in the list are more desirable to be
+       considered the search area's monitor.  we try the configured primary
+       monitor first, so it gets the highest preference.
+
+       if we have counted an area A, then we want to subtract the intersection
+       of A with the area on future monitors.
+       but now consider if we count an area B that intersects A. we want to
+       subtract the area B from that counted on future monitors, but not
+       subtract the intersection of A and B twice! so we would add the
+       intersection of A and B back, to account for it being subtracted both
+       for A and B.
+
+       this is the idea behind the algorithm.  we always subtract the full area
+       for monitor M intersected with the search area. we'll call that AREA.
+       but then we go through the list |counted| and for each rectangle in
+       the list that is being subtracted from future monitors, we insert a
+       request to add back the intersection of the subtracted rect with AREA.
+       vice versa for a rect in |counted| that is getting added back.
+    */
 
-    for (i = 0; i < screen_num_monitors; ++i) {
-        const Rect *area = screen_physical_area_monitor(i);
-        if (RECT_INTERSECTS_RECT(*area, *search)) {
-            Rect r;
-            guint v;
+    if (config_primary_monitor_index < screen_num_monitors) {
+        const Rect *monitor;
+        Rect on_current_monitor;
+        glong area;
+
+        monitor = screen_physical_area_monitor(config_primary_monitor_index);
+
+        if (RECT_INTERSECTS_RECT(*monitor, *search)) {
+            RECT_SET_INTERSECTION(on_current_monitor, *monitor, *search);
+            area = RECT_AREA(on_current_monitor);
 
-            RECT_SET_INTERSECTION(r, *area, *search);
-            v = r.width * r.height;
+            if (area > mostpx) {
+                mostpx = area;
+                most = config_primary_monitor_index;
+            }
 
-            if (v > mostv) {
-                mostv = v;
-                most = i;
+            /* add the intersection rect on the current monitor to the
+               counted list. that's easy for the first one, we just mark it for
+               subtraction */
+            {
+                RectArithmetic *ra = g_slice_new(RectArithmetic);
+                ra->r = on_current_monitor;
+                ra->subtract = TRUE;
+                counted = g_slist_prepend(counted, ra);
             }
         }
     }
+
+    for (i = 0; i < screen_num_monitors; ++i) {
+        const Rect *monitor;
+        Rect on_current_monitor;
+        glong area;
+        GSList *it;
+
+        monitor = screen_physical_area_monitor(i);
+
+        if (i == config_primary_monitor_index)
+            continue;  /* already did this one */
+        if (!RECT_INTERSECTS_RECT(*monitor, *search))
+            continue;  /* nothing to see here */
+
+        RECT_SET_INTERSECTION(on_current_monitor, *monitor, *search);
+        area = RECT_AREA(on_current_monitor);
+
+        /* remove pixels we already counted on any previous monitors. */
+        for (it = counted; it; it = g_slist_next(it)) {
+            RectArithmetic *ra = it->data;
+            Rect intersection;
+
+            RECT_SET_INTERSECTION(intersection, ra->r, *search);
+            if (ra->subtract) area -= RECT_AREA(intersection);
+            else area += RECT_AREA(intersection);
+        }
+
+        if (area > mostpx) {
+            mostpx = area;
+            most = i;
+        }
+
+        /* add the intersection rect on the current monitor I to the counted
+           list.
+           but now we need to compensate for every rectangle R already in the
+           counted list, and add a new rect R' that is the intersection of
+           R and I, but with the reverse subtraction/addition operation.
+        */
+        for (it = counted; it; it = g_slist_next(it)) {
+            RectArithmetic *saved = it->data;
+
+            if (!RECT_INTERSECTS_RECT(saved->r, on_current_monitor))
+                continue;
+            /* we are going to subtract our rect from future monitors, but
+               part of it may already be being subtracted/added, so compensate
+               to not double add/subtract. */
+            RectArithmetic *reverse = g_slice_new(RectArithmetic);
+            RECT_SET_INTERSECTION(reverse->r, saved->r, on_current_monitor);
+            reverse->subtract = !saved->subtract;
+            /* prepend so we can continue thru the list uninterupted */
+            counted = g_slist_prepend(counted, reverse);
+        }
+        {
+            RectArithmetic *ra = g_slice_new(RectArithmetic);
+            ra->r = on_current_monitor;
+            ra->subtract = TRUE;
+            counted = g_slist_prepend(counted, ra);
+        }
+    }
+
+    while (counted) {
+        g_slice_free(RectArithmetic, counted->data);
+        counted = g_slist_delete_link(counted, counted);
+    }
+
     return most < screen_num_monitors ? most : screen_monitor_primary(FALSE);
 }