From d68116c9c9b2c9a9925d872141eff8e301ef3bb3 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sat, 15 Oct 2011 23:04:21 -0400 Subject: [PATCH] Pick the monitor most relevant to a rectangle more cleverly. 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 | 2 + openbox/screen.c | 138 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 129 insertions(+), 11 deletions(-) diff --git a/openbox/geom.h b/openbox/geom.h index 003b0081..8ac0e550 100644 --- a/openbox/geom.h +++ b/openbox/geom.h @@ -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) \ diff --git a/openbox/screen.c b/openbox/screen.c index ffe74a07..35366beb 100644 --- a/openbox/screen.c +++ b/openbox/screen.c @@ -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); } -- 2.44.0