output: Support multiple adjacent outputs.

When querying for an adjacent output we now need an absolute position in
order to know which adjacent output that matches. (The position is
either the current mouse position or the center of the currently focused
container, depending on context.)

If two outputs have one edge each that at least partially align with
each other they now count as adjacent.

Seamless mouse is affected by this and now properly moves and positions
itself between outputs with "uneven" placement (as long as they have at
least some part of the edge adjacent to each other).

When focusing or moving a container in a specified direction the center
of the current focused container decides where to look for an adjacent
output. So if e.g. an output has two adjacent outputs to the right and a
"focus right" command is issued then it's the placement of the currently
focused container that decides which output actually gets focused.

Also, if an output has at least one output adjacent in some direction
but the entire edge is not covered (ie. it has "holes" with no outputs),
then the algorithm will choose the output that is closest to the
currently focused container (this does not apply to seamless mouse, the
pointer will just stop at the edge in that case).
This commit is contained in:
S. Christoffer Eliesen 2015-11-16 00:35:25 +01:00
parent 95c65ee33e
commit 236f26f62e
6 changed files with 169 additions and 51 deletions

View file

@ -63,7 +63,7 @@ struct sway_container {
/** /**
* The coordinates that this view appear at, relative to the output they * The coordinates that this view appear at, relative to the output they
* are located on. * are located on (output containers have absolute coordinates).
*/ */
double x, y; double x, y;

View file

@ -4,7 +4,16 @@
#include "container.h" #include "container.h"
#include "focus.h" #include "focus.h"
swayc_t *output_by_name(const char* name); // Position is absolute coordinates on the edge where the adjacent output
swayc_t *swayc_adjacent_output(swayc_t *output, enum movement_direction dir); // should be searched for.
swayc_t *output_by_name(const char* name, const struct wlc_point *abs_pos);
swayc_t *swayc_adjacent_output(swayc_t *output, enum movement_direction dir, const struct wlc_point *abs_pos, bool pick_closest);
// Place absolute coordinates for given container into given wlc_point.
void get_absolute_position(swayc_t *container, struct wlc_point *point);
// Place absolute coordinates for the center point of given container into
// given wlc_point.
void get_absolute_center_position(swayc_t *container, struct wlc_point *point);
#endif #endif

View file

@ -377,11 +377,13 @@ static struct cmd_results *cmd_focus(int argc, char **argv) {
struct cmd_results *error = NULL; struct cmd_results *error = NULL;
if (argc > 0 && strcasecmp(argv[0], "output") == 0) { if (argc > 0 && strcasecmp(argv[0], "output") == 0) {
swayc_t *output = NULL; swayc_t *output = NULL;
struct wlc_point abs_pos;
get_absolute_center_position(get_focused_container(&root_container), &abs_pos);
if ((error = checkarg(argc, "focus", EXPECTED_EQUAL_TO, 2))) { if ((error = checkarg(argc, "focus", EXPECTED_EQUAL_TO, 2))) {
return error; return error;
} else if (!(output = output_by_name(argv[1]))) { } else if (!(output = output_by_name(argv[1], &abs_pos))) {
return cmd_results_new(CMD_FAILURE, "focus output", return cmd_results_new(CMD_FAILURE, "focus output",
"Can't find output with name/at direction %s", argv[1]); "Can't find output with name/at direction '%s' @ (%i,%i)", argv[1], abs_pos.x, abs_pos.y);
} else if (!workspace_switch(swayc_active_workspace_for(output))) { } else if (!workspace_switch(swayc_active_workspace_for(output))) {
return cmd_results_new(CMD_FAILURE, "focus output", return cmd_results_new(CMD_FAILURE, "focus output",
"Switching to workspace on output '%s' was blocked", argv[1]); "Switching to workspace on output '%s' was blocked", argv[1]);
@ -591,11 +593,13 @@ static struct cmd_results *cmd_move(int argc, char **argv) {
} else if (strcasecmp(argv[1], "to") == 0 && strcasecmp(argv[2], "output") == 0) { } else if (strcasecmp(argv[1], "to") == 0 && strcasecmp(argv[2], "output") == 0) {
// move container to output x // move container to output x
swayc_t *output = NULL; swayc_t *output = NULL;
struct wlc_point abs_pos;
get_absolute_center_position(view, &abs_pos);
if (view->type != C_CONTAINER && view->type != C_VIEW) { if (view->type != C_CONTAINER && view->type != C_VIEW) {
return cmd_results_new(CMD_FAILURE, "move", "Can only move containers and views."); return cmd_results_new(CMD_FAILURE, "move", "Can only move containers and views.");
} else if (!(output = output_by_name(argv[3]))) { } else if (!(output = output_by_name(argv[3], &abs_pos))) {
return cmd_results_new(CMD_FAILURE, "move", return cmd_results_new(CMD_FAILURE, "move",
"Can't find output with name/direction '%s'", argv[3]); "Can't find output with name/direction '%s' @ (%i,%i)", argv[3], abs_pos.x, abs_pos.y);
} else { } else {
swayc_t *container = get_focused_container(output); swayc_t *container = get_focused_container(output);
if (container->is_floating) { if (container->is_floating) {
@ -610,13 +614,15 @@ static struct cmd_results *cmd_move(int argc, char **argv) {
} else if (strcasecmp(argv[0], "workspace") == 0) { } else if (strcasecmp(argv[0], "workspace") == 0) {
// move workspace (to output x) // move workspace (to output x)
swayc_t *output = NULL; swayc_t *output = NULL;
struct wlc_point abs_pos;
get_absolute_center_position(view, &abs_pos);
if ((error = checkarg(argc, "move workspace", EXPECTED_EQUAL_TO, 4))) { if ((error = checkarg(argc, "move workspace", EXPECTED_EQUAL_TO, 4))) {
return error; return error;
} else if (strcasecmp(argv[1], "to") != 0 || strcasecmp(argv[2], "output") != 0) { } else if (strcasecmp(argv[1], "to") != 0 || strcasecmp(argv[2], "output") != 0) {
return cmd_results_new(CMD_INVALID, "move", expected_syntax); return cmd_results_new(CMD_INVALID, "move", expected_syntax);
} else if (!(output = output_by_name(argv[3]))) { } else if (!(output = output_by_name(argv[3], &abs_pos))) {
return cmd_results_new(CMD_FAILURE, "move workspace", return cmd_results_new(CMD_FAILURE, "move workspace",
"Can't find output with name/at direction '%s'", argv[3]); "Can't find output with name/direction '%s' @ (%i,%i)", argv[3], abs_pos.x, abs_pos.y);
} }
if (view->type == C_WORKSPACE) { if (view->type == C_WORKSPACE) {
// This probably means we're moving an empty workspace, but // This probably means we're moving an empty workspace, but

View file

@ -305,28 +305,39 @@ static bool handle_pointer_motion(wlc_handle handle, uint32_t time, const struct
!pointer_state.left.held && !pointer_state.right.held && !pointer_state.scroll.held) { !pointer_state.left.held && !pointer_state.right.held && !pointer_state.scroll.held) {
swayc_t *output = swayc_active_output(), *adjacent = NULL; swayc_t *output = swayc_active_output(), *adjacent = NULL;
struct wlc_point abs_pos = *origin;
abs_pos.x += output->x;
abs_pos.y += output->y;
if (origin->x == 0) { // Left edge if (origin->x == 0) { // Left edge
if ((adjacent = swayc_adjacent_output(output, MOVE_LEFT))) { if ((adjacent = swayc_adjacent_output(output, MOVE_LEFT, &abs_pos, false))) {
if (workspace_switch(swayc_active_workspace_for(adjacent))) { if (workspace_switch(swayc_active_workspace_for(adjacent))) {
new_origin.x = adjacent->width; new_origin.x = adjacent->width;
// adjust for differently aligned outputs (well, this is
// only correct when the two outputs have the same
// resolution or the same dpi I guess, it should take
// physical attributes into account)
new_origin.y += (output->y - adjacent->y);
} }
} }
} else if ((double)origin->x == output->width) { // Right edge } else if ((double)origin->x == output->width) { // Right edge
if ((adjacent = swayc_adjacent_output(output, MOVE_RIGHT))) { if ((adjacent = swayc_adjacent_output(output, MOVE_RIGHT, &abs_pos, false))) {
if (workspace_switch(swayc_active_workspace_for(adjacent))) { if (workspace_switch(swayc_active_workspace_for(adjacent))) {
new_origin.x = 0; new_origin.x = 0;
new_origin.y += (output->y - adjacent->y);
} }
} }
} else if (origin->y == 0) { // Top edge } else if (origin->y == 0) { // Top edge
if ((adjacent = swayc_adjacent_output(output, MOVE_UP))) { if ((adjacent = swayc_adjacent_output(output, MOVE_UP, &abs_pos, false))) {
if (workspace_switch(swayc_active_workspace_for(adjacent))) { if (workspace_switch(swayc_active_workspace_for(adjacent))) {
new_origin.y = adjacent->height; new_origin.y = adjacent->height;
new_origin.x += (output->x - adjacent->x);
} }
} }
} else if ((double)origin->y == output->height) { // Bottom edge } else if ((double)origin->y == output->height) { // Bottom edge
if ((adjacent = swayc_adjacent_output(output, MOVE_DOWN))) { if ((adjacent = swayc_adjacent_output(output, MOVE_DOWN, &abs_pos, false))) {
if (workspace_switch(swayc_active_workspace_for(adjacent))) { if (workspace_switch(swayc_active_workspace_for(adjacent))) {
new_origin.y = 0; new_origin.y = 0;
new_origin.x += (output->x - adjacent->x);
} }
} }
} }

View file

@ -533,13 +533,17 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
return parent; return parent;
} }
} }
// If moving to an adjacent output we need a starting position (since this
// output might border to multiple outputs).
struct wlc_point abs_pos;
get_absolute_center_position(container, &abs_pos);
while (true) { while (true) {
// Test if we can even make a difference here // Test if we can even make a difference here
bool can_move = false; bool can_move = false;
int diff = 0; int diff = 0;
if (parent->type == C_ROOT) { if (parent->type == C_ROOT) {
sway_log(L_DEBUG, "Moving between outputs"); sway_log(L_DEBUG, "Moving between outputs");
return swayc_adjacent_output(container, dir); return swayc_adjacent_output(container, dir, &abs_pos, true);
} else { } else {
if (dir == MOVE_LEFT || dir == MOVE_RIGHT) { if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
if (parent->layout == L_HORIZ) { if (parent->layout == L_HORIZ) {

View file

@ -2,15 +2,15 @@
#include "output.h" #include "output.h"
#include "log.h" #include "log.h"
swayc_t *output_by_name(const char* name) { swayc_t *output_by_name(const char* name, const struct wlc_point *abs_pos) {
if (strcasecmp(name, "left") == 0) { if (strcasecmp(name, "left") == 0) {
return swayc_adjacent_output(NULL, MOVE_LEFT); return swayc_adjacent_output(NULL, MOVE_LEFT, abs_pos, true);
} else if (strcasecmp(name, "right") == 0) { } else if (strcasecmp(name, "right") == 0) {
return swayc_adjacent_output(NULL, MOVE_RIGHT); return swayc_adjacent_output(NULL, MOVE_RIGHT, abs_pos, true);
} else if (strcasecmp(name, "up") == 0) { } else if (strcasecmp(name, "up") == 0) {
return swayc_adjacent_output(NULL, MOVE_UP); return swayc_adjacent_output(NULL, MOVE_UP, abs_pos, true);
} else if (strcasecmp(name, "down") == 0) { } else if (strcasecmp(name, "down") == 0) {
return swayc_adjacent_output(NULL, MOVE_DOWN); return swayc_adjacent_output(NULL, MOVE_DOWN, abs_pos, true);
} else { } else {
for(int i = 0; i < root_container.children->length; ++i) { for(int i = 0; i < root_container.children->length; ++i) {
swayc_t *c = root_container.children->items[i]; swayc_t *c = root_container.children->items[i];
@ -22,65 +22,125 @@ swayc_t *output_by_name(const char* name) {
return NULL; return NULL;
} }
swayc_t *swayc_adjacent_output(swayc_t *output, enum movement_direction dir) { // Position is where on the edge (as absolute position) the adjacent output should be searched for.
// TODO: This implementation is naïve: We assume all outputs are swayc_t *swayc_adjacent_output(swayc_t *output, enum movement_direction dir,
// perfectly aligned (ie. only a single output per edge which covers const struct wlc_point *abs_pos, bool pick_closest) {
// the whole edge).
if (!output) { if (!output) {
output = swayc_active_output(); output = swayc_active_output();
} }
// In order to find adjacent outputs we need to test that the outputs are
// aligned on one axis (decided by the direction given) and that the given
// position is within the edge of the adjacent output. If no such output
// exists we pick the adjacent output within the edge that is closest to
// the given position, if any.
swayc_t *adjacent = NULL; swayc_t *adjacent = NULL;
char *dir_text = NULL;
switch(dir) { switch(dir) {
case MOVE_LEFT: case MOVE_LEFT:
case MOVE_RIGHT: ;
double delta_y = 0;
for(int i = 0; i < root_container.children->length; ++i) { for(int i = 0; i < root_container.children->length; ++i) {
swayc_t *c = root_container.children->items[i]; swayc_t *c = root_container.children->items[i];
if (c == output || c->type != C_OUTPUT) { if (c == output || c->type != C_OUTPUT) {
continue; continue;
} }
if (c->y == output->y && c->x + c->width == output->x) { bool x_aligned = dir == MOVE_LEFT ?
sway_log(L_DEBUG, "%s is left of current output %s", c->name, output->name); c->x + c->width == output->x :
c->x == output->x + output->width;
if (!x_aligned) {
continue;
}
if (abs_pos->y >= c->y && abs_pos->y <= c->y + c->height) {
delta_y = 0;
adjacent = c; adjacent = c;
break; break;
} else if (pick_closest) {
// track closest adjacent output
double top_y = c->y, bottom_y = c->y + c->height;
if (top_y >= output->y && top_y <= output->y + output->height) {
double delta = top_y - abs_pos->y;
if (delta < 0) delta = -delta;
if (delta < delta_y || !adjacent) {
delta_y = delta;
adjacent = c;
}
}
// we check both points and pick the closest
if (bottom_y >= output->y && bottom_y <= output->y + output->height) {
double delta = bottom_y - abs_pos->y;
if (delta < 0) delta = -delta;
if (delta < delta_y || !adjacent) {
delta_y = delta;
adjacent = c;
}
}
} }
} }
break; dir_text = dir == MOVE_LEFT ? "left of" : "right of";
case MOVE_RIGHT: if (adjacent && delta_y == 0) {
for(int i = 0; i < root_container.children->length; ++i) { sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (y-position %i)",
swayc_t *c = root_container.children->items[i]; adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y,
if (c == output || c->type != C_OUTPUT) { dir_text, output->name, abs_pos->y);
continue; } else if (adjacent) {
} // so we end up picking the closest adjacent output because
if (c->y == output->y && output->x + output->width == c->x) { // there is no directly adjacent to the given position
sway_log(L_DEBUG, "%s is right of current output %s", c->name, output->name); sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (y-position %i, delta: %.0f)",
adjacent = c; adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y,
break; dir_text, output->name, abs_pos->y, delta_y);
}
} }
break; break;
case MOVE_UP: case MOVE_UP:
case MOVE_DOWN: ;
double delta_x = 0;
for(int i = 0; i < root_container.children->length; ++i) { for(int i = 0; i < root_container.children->length; ++i) {
swayc_t *c = root_container.children->items[i]; swayc_t *c = root_container.children->items[i];
if (c == output || c->type != C_OUTPUT) { if (c == output || c->type != C_OUTPUT) {
continue; continue;
} }
if (output->x == c->x && c->y + c->height == output->y) { bool y_aligned = dir == MOVE_UP ?
sway_log(L_DEBUG, "%s is above current output %s", c->name, output->name); c->y + c->height == output->y :
c->y == output->y + output->height;
if (!y_aligned) {
continue;
}
if (abs_pos->x >= c->x && abs_pos->x <= c->x + c->width) {
delta_x = 0;
adjacent = c; adjacent = c;
break; break;
} else if (pick_closest) {
// track closest adjacent output
double left_x = c->x, right_x = c->x + c->width;
if (left_x >= output->x && left_x <= output->x + output->width) {
double delta = left_x - abs_pos->x;
if (delta < 0) delta = -delta;
if (delta < delta_x || !adjacent) {
delta_x = delta;
adjacent = c;
}
}
// we check both points and pick the closest
if (right_x >= output->x && right_x <= output->x + output->width) {
double delta = right_x - abs_pos->x;
if (delta < 0) delta = -delta;
if (delta < delta_x || !adjacent) {
delta_x = delta;
adjacent = c;
}
}
} }
} }
break; dir_text = dir == MOVE_UP ? "above" : "below";
case MOVE_DOWN: if (adjacent && delta_x == 0) {
for(int i = 0; i < root_container.children->length; ++i) { sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (x-position %i)",
swayc_t *c = root_container.children->items[i]; adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y,
if (c == output || c->type != C_OUTPUT) { dir_text, output->name, abs_pos->x);
continue; } else if (adjacent) {
} // so we end up picking the closest adjacent output because
if (output->x == c->x && output->y + output->height == c->y) { // there is no directly adjacent to the given position
sway_log(L_DEBUG, "%s is below current output %s", c->name, output->name); sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (x-position %i, delta: %.0f)",
adjacent = c; adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y,
break; dir_text, output->name, abs_pos->x, delta_x);
}
} }
break; break;
default: default:
@ -89,3 +149,31 @@ swayc_t *swayc_adjacent_output(swayc_t *output, enum movement_direction dir) {
} }
return adjacent; return adjacent;
} }
void get_absolute_position(swayc_t *container, struct wlc_point *point) {
if (!container || !point)
sway_abort("Need container and wlc_point (was %p, %p).", container, point);
if (container->type == C_OUTPUT) {
// Coordinates are already absolute.
point->x = container->x;
point->y = container->y;
} else {
swayc_t *output = swayc_parent_by_type(container, C_OUTPUT);
if (container->type == C_WORKSPACE) {
// Workspace coordinates are actually wrong/arbitrary, but should
// be same as output.
point->x = output->x;
point->y = output->y;
} else {
point->x = output->x + container->x;
point->y = output->y + container->y;
}
}
}
void get_absolute_center_position(swayc_t *container, struct wlc_point *point) {
get_absolute_position(container, point);
point->x += container->width/2;
point->y += container->height/2;
}