Big changes to placement across multiple monitors.

Add a Primary option for which monitor to place new windows on.  Make "Active" the default instead of "Any", which is just totally crazy.

When a window is being placed in the FOREGROUND, use a monitor chosen in
  the following order:
  1. same monitor as parent
  2. primary monitor if placement=PRIMARY
     active monitor if placement=ACTIVE
     pointer monitor if placement=MOUSE
  3. primary monitor
  4. other monitors where the window has group members on the same desktop
  5. other monitors where the window has group members on other desktops
  6. other monitors

When a window is being placed in the BACKGROUND, use a monitor chosen in the
  following order:
  1. same monitor as parent
  2. other monitors where the window has group members on the same desktop
   2a. primary monitor in this set
   2b. other monitors in this set
  3. other monitors where the window has group members on other desktops
   3a. primary monitor in this set
   3b. other monitors in this set
  4. other monitors
   4a. primary monitor in this set
   4b. other monitors in this set

Decide to focus the new window before placing it, so we know if it will be
  placed in the foreground or background.

Always choose a single monitor, then place on it, rather than possibly moving
  to a "backup" monitor.  Unpredictable monitor placement is horrible.
This commit is contained in:
Dana Jansens 2010-10-14 19:40:41 -04:00
parent 76ae19924d
commit 95535e8623
5 changed files with 338 additions and 280 deletions

View file

@ -36,10 +36,10 @@
<center>yes</center>
<!-- whether to place windows in the center of the free area found or
the top left corner -->
<monitor>Active</monitor>
<monitor>Primary</monitor>
<!-- with Smart placement on a multi-monitor system, try to place new windows
on: 'Any' - any monitor, 'Mouse' - where the mouse is, 'Active' - where
the active window is -->
the active window is, 'Primary' - only on the primary monitor -->
<primaryMonitor>1</primaryMonitor>
<!-- The monitor where Openbox should place popup dialogs such as the
focus cycling popup, or the desktop switch popup. It can be an index

View file

@ -192,7 +192,8 @@ void client_manage(Window window, ObPrompt *prompt)
{
ObClient *self;
XSetWindowAttributes attrib_set;
gboolean activate = FALSE;
gboolean try_activate = FALSE;
gboolean do_activate;
ObAppSettings *settings;
gboolean transient = FALSE;
Rect place;
@ -284,7 +285,7 @@ void client_manage(Window window, ObPrompt *prompt)
FALSE, FALSE, TRUE, TRUE, FALSE, FALSE,
settings->focus == 1))
{
activate = TRUE;
try_activate = TRUE;
}
/* remove the client's border */
@ -298,6 +299,14 @@ void client_manage(Window window, ObPrompt *prompt)
/* where the frame was placed is where the window was originally */
place = self->area;
ob_debug("Going to try activate new window? %s",
try_activate ? "yes" : "no");
if (try_activate)
do_activate = client_can_steal_focus(self, settings->focus,
event_time(), launch_time);
else
do_activate = FALSE;
/* figure out placement for the window if the window is new */
if (ob_state() == OB_STATE_RUNNING) {
ob_debug("Positioned: %s @ %d %d",
@ -316,7 +325,8 @@ void client_manage(Window window, ObPrompt *prompt)
"program + user specified" :
"BADNESS !?")))), place.width, place.height);
obplaced = place_client(self, &place.x, &place.y, settings);
obplaced = place_client(self, do_activate, &place.x, &place.y,
settings);
/* watch for buggy apps that ask to be placed at (0,0) when there is
a strut there */
@ -424,13 +434,30 @@ void client_manage(Window window, ObPrompt *prompt)
client_apply_startup_state(self, place.x, place.y,
place.width, place.height);
ob_debug_type(OB_DEBUG_FOCUS, "Going to try activate new window? %s",
activate ? "yes" : "no");
if (activate) {
activate = client_can_steal_focus(self, settings->focus,
event_time(), launch_time);
/* grab mouse bindings before showing the window */
mouse_grab_for_client(self, TRUE);
if (!activate) {
/* this has to happen before we try focus the window, but we want it to
happen after the client's stacking has been determined or it looks bad
*/
{
gulong ignore_start;
if (!config_focus_under_mouse)
ignore_start = event_start_ignore_all_enters();
client_show(self);
if (!config_focus_under_mouse)
event_end_ignore_all_enters(ignore_start);
}
/* activate/hilight/raise the window */
if (try_activate) {
if (do_activate) {
gboolean stacked = client_restore_session_stacking(self);
client_present(self, FALSE, !stacked, TRUE);
}
else {
/* if the client isn't stealing focus, then hilite it so the user
knows it is there, but don't do this if we're restoring from a
session */
@ -450,27 +477,6 @@ void client_manage(Window window, ObPrompt *prompt)
stacking_raise(CLIENT_AS_WINDOW(self));
}
mouse_grab_for_client(self, TRUE);
/* this has to happen before we try focus the window, but we want it to
happen after the client's stacking has been determined or it looks bad
*/
{
gulong ignore_start;
if (!config_focus_under_mouse)
ignore_start = event_start_ignore_all_enters();
client_show(self);
if (!config_focus_under_mouse)
event_end_ignore_all_enters(ignore_start);
}
if (activate) {
gboolean stacked = client_restore_session_stacking(self);
client_present(self, FALSE, !stacked, TRUE);
}
/* add to client list/map */
client_list = g_list_append(client_list, self);
window_add(&self->window, CLIENT_AS_WINDOW(self));
@ -693,19 +699,15 @@ static gboolean client_can_steal_focus(ObClient *self,
{
gboolean steal;
gboolean relative_focused;
gboolean parent_focused;
steal = TRUE;
parent_focused = (focus_client != NULL &&
client_search_focus_parent(self));
relative_focused = (focus_client != NULL &&
(client_search_focus_tree_full(self) != NULL ||
client_search_focus_group_full(self) != NULL));
/* This is focus stealing prevention */
ob_debug_type(OB_DEBUG_FOCUS,
"Want to focus window 0x%x at time %u "
ob_debug("Want to focus window 0x%x at time %u "
"launched at %u (last user interaction time %u)",
self->window, steal_time, launch_time,
event_last_user_time);
@ -721,15 +723,13 @@ static gboolean client_can_steal_focus(ObClient *self,
!event_time_after(launch_time, screen_desktop_user_time))))
{
steal = FALSE;
ob_debug_type(OB_DEBUG_FOCUS,
"Not focusing the window because its on another "
"desktop\n");
ob_debug("Not focusing the window because its on another desktop\n");
}
/* If something is focused... */
else if (focus_client) {
/* If the user is working in another window right now, then don't
steal focus */
if (!parent_focused &&
if (!relative_focused &&
event_last_user_time &&
(!launch_time ||
(event_time_after(event_last_user_time, launch_time) &&
@ -738,17 +738,14 @@ static gboolean client_can_steal_focus(ObClient *self,
steal_time - OB_EVENT_USER_TIME_DELAY))
{
steal = FALSE;
ob_debug_type(OB_DEBUG_FOCUS,
"Not focusing the window because the user is "
"working in another window that is not "
"its parent");
ob_debug("Not focusing the window because the user is "
"working in another window that is not its relative");
}
/* If the new window is a transient (and its relatives aren't
focused) */
else if (client_has_parent(self) && !relative_focused) {
steal = FALSE;
ob_debug_type(OB_DEBUG_FOCUS,
"Not focusing the window because it is a "
ob_debug("Not focusing the window because it is a "
"transient, and its relatives aren't focused");
}
/* Don't steal focus from globally active clients.
@ -758,16 +755,14 @@ static gboolean client_can_steal_focus(ObClient *self,
focus_client->focus_notify))
{
steal = FALSE;
ob_debug_type(OB_DEBUG_FOCUS,
"Not focusing the window because a globally "
ob_debug("Not focusing the window because a globally "
"active client has focus");
}
/* Don't move focus if it's not going to go to this window
anyway */
else if (client_focus_target(self) != self) {
steal = FALSE;
ob_debug_type(OB_DEBUG_FOCUS,
"Not focusing the window because another window "
ob_debug("Not focusing the window because another window "
"would get the focus anyway");
}
/* Don't move focus if the window is not visible on the current
@ -777,15 +772,13 @@ static gboolean client_can_steal_focus(ObClient *self,
!relative_focused)
{
steal = FALSE;
ob_debug_type(OB_DEBUG_FOCUS,
"Not focusing the window because it is on "
ob_debug("Not focusing the window because it is on "
"another desktop and no relatives are focused ");
}
}
if (!steal)
ob_debug_type(OB_DEBUG_FOCUS,
"Focus stealing prevention activated for %s at "
ob_debug("Focus stealing prevention activated for %s at "
"time %u (last user interaction time %u)",
self->title, steal_time, event_last_user_time);
return steal;

View file

@ -582,6 +582,8 @@ static void parse_placement(xmlNodePtr node, gpointer d)
config_place_monitor = OB_PLACE_MONITOR_ACTIVE;
else if (obt_xml_node_contains(n, "mouse"))
config_place_monitor = OB_PLACE_MONITOR_MOUSE;
else if (obt_xml_node_contains(n, "any"))
config_place_monitor = OB_PLACE_MONITOR_ANY;
}
if ((n = obt_xml_find_node(node, "primaryMonitor"))) {
config_primary_monitor_index = obt_xml_node_int(n);
@ -1003,7 +1005,7 @@ void config_startup(ObtXmlInst *i)
config_place_policy = OB_PLACE_POLICY_SMART;
config_place_center = TRUE;
config_place_monitor = OB_PLACE_MONITOR_ANY;
config_place_monitor = OB_PLACE_MONITOR_PRIMARY;
config_primary_monitor_index = 1;
config_primary_monitor = OB_PLACE_MONITOR_ACTIVE;

View file

@ -28,129 +28,207 @@
extern ObDock *dock;
static void add_choice(guint *choice, guint mychoice)
{
guint i;
for (i = 0; i < screen_num_monitors; ++i) {
if (choice[i] == mychoice)
return;
else if (choice[i] == screen_num_monitors) {
choice[i] = mychoice;
return;
}
}
}
static Rect *pick_pointer_head(ObClient *c)
{
return screen_area(c->desktop, screen_monitor_pointer(), NULL);
}
/*! Pick a monitor to place a window on. */
static Rect **pick_head(ObClient *c)
/* use the following priority lists for pick_head()
When a window is being placed in the FOREGROUND, use a monitor chosen in
the following order:
1. same monitor as parent
2. primary monitor if placement=PRIMARY
active monitor if placement=ACTIVE
pointer monitor if placement=MOUSE
3. primary monitor
4. other monitors where the window has group members on the same desktop
5. other monitors where the window has group members on other desktops
6. other monitors
When a window is being placed in the BACKGROUND, use a monitor chosen in the
following order:
1. same monitor as parent
2. other monitors where the window has group members on the same desktop
2a. primary monitor in this set
2b. other monitors in this set
3. other monitors where the window has group members on other desktops
3a. primary monitor in this set
3b. other monitors in this set
4. other monitors
4a. primary monitor in this set
4b. other monitors in this set
*/
/*! One for each possible head, used to sort them in order of precedence. */
typedef struct {
guint monitor;
guint flags;
} ObPlaceHead;
/*! Flags for ObPlaceHead */
enum {
HEAD_PARENT = 1 << 0, /* parent's monitor */
HEAD_PLACED = 1 << 1, /* chosen monitor by placement */
HEAD_PRIMARY = 1 << 2, /* primary monitor */
HEAD_GROUP_DESK = 1 << 3, /* has a group member on the same desktop */
HEAD_GROUP = 1 << 4, /* has a group member on another desktop */
};
gint cmp_foreground(const void *a, const void *b)
{
Rect **area;
guint *choice;
guint i;
gint px, py;
ObClient *p;
const ObPlaceHead *h1 = a;
const ObPlaceHead *h2 = b;
gint i = 0;
area = g_new(Rect*, screen_num_monitors);
choice = g_new(guint, screen_num_monitors);
for (i = 0; i < screen_num_monitors; ++i)
choice[i] = screen_num_monitors; /* make them all invalid to start */
if (h1->monitor == h2->monitor) return 0;
/* try direct parent first */
if ((p = client_direct_parent(c))) {
add_choice(choice, client_monitor(p));
ob_debug("placement adding choice %d for parent",
client_monitor(p));
if (h1->flags & HEAD_PARENT) --i;
if (h2->flags & HEAD_PARENT) ++i;
if (i) return i;
if (h1->flags & HEAD_PLACED) --i;
if (h2->flags & HEAD_PLACED) ++i;
if (i) return i;
if (h1->flags & HEAD_PRIMARY) --i;
if (h2->flags & HEAD_PRIMARY) ++i;
if (i) return i;
if (h1->flags & HEAD_GROUP_DESK) --i;
if (h2->flags & HEAD_GROUP_DESK) ++i;
if (i) return i;
if (h1->flags & HEAD_GROUP) --i;
if (h2->flags & HEAD_GROUP) ++i;
if (i) return i;
return h1->monitor - h2->monitor;
}
gint cmp_background(const void *a, const void *b)
{
const ObPlaceHead *h1 = a;
const ObPlaceHead *h2 = b;
gint i = 0;
if (h1->monitor == h2->monitor) return 0;
if (h1->flags & HEAD_PARENT) --i;
if (h2->flags & HEAD_PARENT) ++i;
if (i) return i;
if (h1->flags & HEAD_GROUP_DESK || h2->flags & HEAD_GROUP_DESK) {
if (h1->flags & HEAD_GROUP_DESK) --i;
if (h2->flags & HEAD_GROUP_DESK) ++i;
if (i) return i;
if (h1->flags & HEAD_PRIMARY) --i;
if (h2->flags & HEAD_PRIMARY) ++i;
if (i) return i;
}
/* more than one window in its group (more than just this window) */
if (client_has_group_siblings(c)) {
if (h1->flags & HEAD_GROUP || h2->flags & HEAD_GROUP) {
if (h1->flags & HEAD_GROUP) --i;
if (h2->flags & HEAD_GROUP) ++i;
if (i) return i;
if (h1->flags & HEAD_PRIMARY) --i;
if (h2->flags & HEAD_PRIMARY) ++i;
if (i) return i;
}
if (h1->flags & HEAD_PRIMARY) --i;
if (h2->flags & HEAD_PRIMARY) ++i;
if (i) return i;
return h1->monitor - h2->monitor;
}
/*! Pick a monitor to place a window on. */
static Rect *pick_head(ObClient *c, gboolean foreground)
{
Rect *area;
ObPlaceHead *choice;
guint i;
ObClient *p;
GSList *it;
/* try on the client's desktop */
for (it = c->group->members; it; it = g_slist_next(it)) {
ObClient *itc = it->data;
if (itc != c &&
(itc->desktop == c->desktop ||
itc->desktop == DESKTOP_ALL || c->desktop == DESKTOP_ALL))
{
add_choice(choice, client_monitor(it->data));
ob_debug("placement adding choice %d for group sibling",
client_monitor(it->data));
}
choice = g_new(ObPlaceHead, screen_num_monitors);
for (i = 0; i < screen_num_monitors; ++i) {
choice[i].monitor = i;
choice[i].flags = 0;
}
/* try on all desktops */
/* find monitors with group members */
for (it = c->group->members; it; it = g_slist_next(it)) {
ObClient *itc = it->data;
if (itc != c) {
add_choice(choice, client_monitor(it->data));
ob_debug("placement adding choice %d for group sibling on "
"another desktop", client_monitor(it->data));
guint m = client_monitor(itc);
if (m < screen_num_monitors) {
if (screen_compare_desktops(itc->desktop, c->desktop))
choice[m].flags |= HEAD_GROUP_DESK;
else
choice[m].flags |= HEAD_GROUP;
}
}
}
/* skip this if placing by the mouse position */
if (focus_client && client_normal(focus_client) &&
config_place_monitor != OB_PLACE_MONITOR_MOUSE)
i = screen_monitor_primary(FALSE);
if (i < screen_num_monitors) {
choice[i].flags |= HEAD_PRIMARY;
if (config_place_monitor == OB_PLACE_MONITOR_PRIMARY)
choice[i].flags |= HEAD_PLACED;
}
/* direct parent takes highest precedence */
if ((p = client_direct_parent(c))) {
i = client_monitor(p);
if (i < screen_num_monitors)
choice[i].flags |= HEAD_PARENT;
}
qsort(choice, screen_num_monitors, sizeof(ObPlaceHead),
foreground ? cmp_foreground : cmp_background);
/* save the areas of the monitors in order of their being chosen */
for (i = 0; i < screen_num_monitors; ++i)
{
add_choice(choice, client_monitor(focus_client));
ob_debug("placement adding choice %d for normal focused window",
client_monitor(focus_client));
ob_debug("placement choice %d is monitor %d", i, choice[i].monitor);
if (choice[i].flags & HEAD_PARENT)
ob_debug(" - parent on monitor");
if (choice[i].flags & HEAD_PLACED)
ob_debug(" - placement choice");
if (choice[i].flags & HEAD_PRIMARY)
ob_debug(" - primary monitor");
if (choice[i].flags & HEAD_GROUP_DESK)
ob_debug(" - group on same desktop");
if (choice[i].flags & HEAD_GROUP)
ob_debug(" - group on other desktop");
}
screen_pointer_pos(&px, &py);
for (i = 0; i < screen_num_monitors; i++) {
const Rect *monitor = screen_physical_area_monitor(i);
gboolean contain = RECT_CONTAINS(*monitor, px, py);
if (contain) {
add_choice(choice, i);
ob_debug("placement adding choice %d for mouse pointer", i);
break;
}
}
/* add any leftover choices */
for (i = 0; i < screen_num_monitors; ++i)
add_choice(choice, i);
for (i = 0; i < screen_num_monitors; ++i)
area[i] = screen_area(c->desktop, choice[i], NULL);
area = screen_area(c->desktop, choice[0].monitor, NULL);
g_free(choice);
/* return the area for the chosen monitor */
return area;
}
static gboolean place_random(ObClient *client, gint *x, gint *y)
static gboolean place_random(ObClient *client, Rect *area, gint *x, gint *y)
{
gint l, r, t, b;
Rect **areas;
guint i;
areas = pick_head(client);
i = (config_place_monitor != OB_PLACE_MONITOR_ANY) ?
0 : g_random_int_range(0, screen_num_monitors);
ob_debug("placing randomly");
l = areas[i]->x;
t = areas[i]->y;
r = areas[i]->x + areas[i]->width - client->frame->area.width;
b = areas[i]->y + areas[i]->height - client->frame->area.height;
l = area->x;
t = area->y;
r = area->x + area->width - client->frame->area.width;
b = area->y + area->height - client->frame->area.height;
if (r > l) *x = g_random_int_range(l, r + 1);
else *x = areas[i]->x;
else *x = area->x;
if (b > t) *y = g_random_int_range(t, b + 1);
else *y = areas[i]->y;
for (i = 0; i < screen_num_monitors; ++i)
g_slice_free(Rect, areas[i]);
g_free(areas);
else *y = area->y;
return TRUE;
}
@ -228,31 +306,25 @@ enum {
IGNORE_END = 7
};
static gboolean place_nooverlap(ObClient *c, gint *x, gint *y)
static gboolean place_nooverlap(ObClient *c, Rect *area, gint *x, gint *y)
{
Rect **areas;
gint ignore;
gboolean ret;
gint maxsize;
GSList *spaces = NULL, *sit, *maxit;
guint i;
areas = pick_head(c);
ob_debug("placing nonoverlap");
ret = FALSE;
maxsize = 0;
maxit = NULL;
/* try ignoring different things to find empty space */
for (ignore = 0; ignore < IGNORE_END && !ret; ignore++) {
/* try all monitors in order of preference, but only the first one
if config_place_monitor is MOUSE or ACTIVE */
for (i = 0; (i < (config_place_monitor != OB_PLACE_MONITOR_ANY ?
1 : screen_num_monitors) && !ret); ++i)
{
GList *it;
/* add the whole monitor */
spaces = area_add(spaces, areas[i]);
spaces = area_add(spaces, area);
/* go thru all the windows */
for (it = client_list; it; it = g_list_next(it)) {
@ -335,11 +407,7 @@ static gboolean place_nooverlap(ObClient *c, gint *x, gint *y)
spaces = g_slist_delete_link(spaces, spaces);
}
}
}
for (i = 0; i < screen_num_monitors; ++i)
g_slice_free(Rect, areas[i]);
g_free(areas);
return ret;
}
@ -349,6 +417,8 @@ static gboolean place_under_mouse(ObClient *client, gint *x, gint *y)
gint px, py;
Rect *area;
ob_debug("placing under mouse");
if (!screen_pointer_pos(&px, &py))
return FALSE;
area = pick_pointer_head(client);
@ -376,27 +446,17 @@ static gboolean place_per_app_setting(ObClient *client, gint *x, gint *y,
if (!settings || (settings && !settings->pos_given))
return FALSE;
ob_debug("placing by per-app settings");
/* Find which head the pointer is on */
if (settings->monitor == 0)
/* this can return NULL */
screen = pick_pointer_head(client);
else if (settings->monitor > 0 &&
(guint)settings->monitor <= screen_num_monitors)
screen = screen_area(client->desktop, (guint)settings->monitor - 1,
NULL);
/* if we have't found a screen yet.. */
if (!screen) {
Rect **areas;
guint i;
areas = pick_head(client);
screen = areas[0];
/* don't free the first one, it's being set as "screen" */
for (i = 1; i < screen_num_monitors; ++i)
g_slice_free(Rect, areas[i]);
g_free(areas);
else {
guint m = settings->monitor;
if (m < 1 || m > screen_num_monitors)
m = screen_monitor_primary(TRUE) + 1;
screen = screen_area(client->desktop, m - 1, NULL);
}
if (settings->position.x.center)
@ -423,12 +483,16 @@ static gboolean place_per_app_setting(ObClient *client, gint *x, gint *y,
return TRUE;
}
static gboolean place_transient_splash(ObClient *client, gint *x, gint *y)
static gboolean place_transient_splash(ObClient *client, Rect *area,
gint *x, gint *y)
{
if (client->type == OB_CLIENT_TYPE_DIALOG) {
GSList *it;
gboolean first = TRUE;
gint l, r, t, b;
ob_debug("placing dialog");
for (it = client->parents; it; it = g_slist_next(it)) {
ObClient *m = it->data;
if (!m->iconic) {
@ -456,17 +520,10 @@ static gboolean place_transient_splash(ObClient *client, gint *x, gint *y)
if (client->type == OB_CLIENT_TYPE_DIALOG ||
client->type == OB_CLIENT_TYPE_SPLASH)
{
Rect **areas;
guint i;
ob_debug("placing dialog or splash");
areas = pick_head(client);
*x = (areas[0]->width - client->frame->area.width) / 2 + areas[0]->x;
*y = (areas[0]->height - client->frame->area.height) / 2 + areas[0]->y;
for (i = 0; i < screen_num_monitors; ++i)
g_slice_free(Rect, areas[i]);
g_free(areas);
*x = (area->width - client->frame->area.width) / 2 + area->x;
*y = (area->height - client->frame->area.height) / 2 + area->y;
return TRUE;
}
@ -475,9 +532,10 @@ static gboolean place_transient_splash(ObClient *client, gint *x, gint *y)
/*! Return TRUE if openbox chose the position for the window, and FALSE if
the application chose it */
gboolean place_client(ObClient *client, gint *x, gint *y,
gboolean place_client(ObClient *client, gboolean foreground, gint *x, gint *y,
ObAppSettings *settings)
{
Rect *area;
gboolean ret;
/* per-app settings override program specified position
@ -488,15 +546,19 @@ gboolean place_client(ObClient *client, gint *x, gint *y,
!(settings && settings->pos_given)))
return FALSE;
area = pick_head(client, foreground);
/* try a number of methods */
ret = place_per_app_setting(client, x, y, settings) ||
place_transient_splash(client, x, y) ||
place_transient_splash(client, area, x, y) ||
(config_place_policy == OB_PLACE_POLICY_MOUSE &&
place_under_mouse(client, x, y)) ||
place_nooverlap(client, x, y) ||
place_random(client, x, y);
place_nooverlap(client, area, x, y) ||
place_random(client, area, x, y);
g_assert(ret);
g_slice_free(Rect, area);
/* get where the client should be */
frame_frame_gravity(client->frame, x, y);
return TRUE;

View file

@ -35,10 +35,11 @@ typedef enum
{
OB_PLACE_MONITOR_ANY,
OB_PLACE_MONITOR_ACTIVE,
OB_PLACE_MONITOR_MOUSE
OB_PLACE_MONITOR_MOUSE,
OB_PLACE_MONITOR_PRIMARY
} ObPlaceMonitor;
gboolean place_client(struct _ObClient *client, gint *x, gint *y,
struct _ObAppSettings *settings);
gboolean place_client(struct _ObClient *client, gboolean foreground,
gint *x, gint *y, struct _ObAppSettings *settings);
#endif