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.
This commit is contained in:
Dana Jansens 2011-10-15 23:04:21 -04:00
parent dc69fe7ced
commit d68116c9c9
2 changed files with 129 additions and 11 deletions

View 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) \

View 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;
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;
/* 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.
RECT_SET_INTERSECTION(r, *area, *search);
v = r.width * r.height;
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 (v > mostv) {
mostv = v;
most = i;
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.
*/
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);
if (area > mostpx) {
mostpx = area;
most = config_primary_monitor_index;
}
/* 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);
}