From 3b50615a7194b984bfdfc55b42a32f083351bc86 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 3 Mar 2009 23:51:02 +0100 Subject: [PATCH] Implement Xinerama screen changes --- include/xinerama.h | 5 +- src/commands.c | 12 ++-- src/handlers.c | 11 ++- src/layout.c | 2 +- src/mainx.c | 5 +- src/xinerama.c | 167 ++++++++++++++++++++++++++++++++++++--------- 6 files changed, 160 insertions(+), 42 deletions(-) diff --git a/include/xinerama.h b/include/xinerama.h index 99ee0161..f0e2d403 100644 --- a/include/xinerama.h +++ b/include/xinerama.h @@ -14,10 +14,11 @@ #define _XINERAMA_H TAILQ_HEAD(screens_head, Screen); -extern struct screens_head virtual_screens; +extern struct screens_head *virtual_screens; void initialize_xinerama(xcb_connection_t *conn); -i3Screen *get_screen_at(int x, int y); +void xinerama_requery_screens(xcb_connection_t *conn); +i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist); i3Screen *get_screen_containing(int x, int y); #endif diff --git a/src/commands.c b/src/commands.c index e9be6a77..bc5800d4 100644 --- a/src/commands.c +++ b/src/commands.c @@ -29,19 +29,19 @@ static bool focus_window_in_container(xcb_connection_t *connection, Container *c if (container->currently_focused == NULL) return false; - Client *candidad = NULL; + Client *candidate = NULL; if (direction == D_UP) - candidad = CIRCLEQ_PREV(container->currently_focused, clients); + candidate = CIRCLEQ_PREV(container->currently_focused, clients); else if (direction == D_DOWN) - candidad = CIRCLEQ_NEXT(container->currently_focused, clients); + candidate = CIRCLEQ_NEXT(container->currently_focused, clients); else printf("Direction not implemented!\n"); /* If we don’t have anything to select, we’re done */ - if (candidad == CIRCLEQ_END(&(container->clients))) + if (candidate == CIRCLEQ_END(&(container->clients))) return false; /* Set focus if we could successfully move */ - set_focus(connection, candidad); + set_focus(connection, candidate); return true; } @@ -96,7 +96,7 @@ static bool move_current_window_in_container(xcb_connection_t *connection, Clien assert(client->container != NULL); Client *other = (direction == D_UP ? CIRCLEQ_PREV(client, clients) : - CIRCLEQ_NEXT(client, clients)); + CIRCLEQ_NEXT(client, clients)); if (other == CIRCLEQ_END(&(client->container->clients))) return false; diff --git a/src/handlers.c b/src/handlers.c index e644554b..3a0fe2c7 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -322,9 +322,18 @@ int handle_map_notify_event(void *prophs, xcb_connection_t *conn, xcb_map_notify * */ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) { + xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; + printf("handle_configure_event\n"); - ignore_notify_event = event->sequence; printf("event->x = %d, ->y = %d, ->width = %d, ->height = %d\n", event->x, event->y, event->width, event->height); + if (event->event == root) { + printf("reconfigure of the root window, need to xinerama\n"); + xinerama_requery_screens(conn); + return 1; + } + + ignore_notify_event = event->sequence; + Client *client = table_get(byChild, event->window); if (client == NULL) { printf("client not managed, ignoring\n"); diff --git a/src/layout.c b/src/layout.c index 7e3b6a74..6596e657 100644 --- a/src/layout.c +++ b/src/layout.c @@ -342,7 +342,7 @@ static void render_bars(xcb_connection_t *connection, Workspace *r_ws, int width void render_layout(xcb_connection_t *connection) { i3Screen *screen; - TAILQ_FOREACH(screen, &virtual_screens, screens) { + TAILQ_FOREACH(screen, virtual_screens, screens) { /* r_ws (rendering workspace) is just a shortcut to the Workspace being currently rendered */ Workspace *r_ws = &(workspaces[screen->current_workspace]); diff --git a/src/mainx.c b/src/mainx.c index ce5b788e..f67b455f 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -390,6 +390,9 @@ int main(int argc, char *argv[], char *env[]) { uint32_t mask = XCB_CW_EVENT_MASK; uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video + projector), the root window gets a + ConfigureNotify */ XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW }; xcb_change_window_attributes(c, root, mask, values); @@ -456,7 +459,7 @@ int main(int argc, char *argv[], char *env[]) { i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y); if (screen == NULL) { - printf("ERROR: No such screen\n"); + printf("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y); return 0; } if (screen->current_workspace != 0) { diff --git a/src/xinerama.c b/src/xinerama.c index 56e6f526..8e7f7918 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -21,18 +22,21 @@ #include "table.h" #include "util.h" #include "xinerama.h" +#include "layout.h" /* This TAILQ of i3Screens stores the virtual screens, used for handling overlapping screens * (xrandr --same-as) */ -struct screens_head virtual_screens = TAILQ_HEAD_INITIALIZER(virtual_screens); +struct screens_head *virtual_screens; + +static bool xinerama_enabled = true; /* * Looks in virtual_screens for the i3Screen whose start coordinates are x, y * */ -i3Screen *get_screen_at(int x, int y) { +i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist) { i3Screen *screen; - TAILQ_FOREACH(screen, &virtual_screens, screens) + TAILQ_FOREACH(screen, screenlist, screens) if (screen->rect.x == x && screen->rect.y == y) return screen; @@ -45,10 +49,13 @@ i3Screen *get_screen_at(int x, int y) { */ i3Screen *get_screen_containing(int x, int y) { i3Screen *screen; - TAILQ_FOREACH(screen, &virtual_screens, screens) - if (x > screen->rect.x && x < (screen->rect.x + screen->rect.width) && - y > screen->rect.y && y < (screen->rect.y + screen->rect.height)) + TAILQ_FOREACH(screen, virtual_screens, screens) { + printf("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", + x, y, screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); + if (x >= screen->rect.x && x < (screen->rect.x + screen->rect.width) && + y >= screen->rect.y && y < (screen->rect.y + screen->rect.height)) return screen; + } return NULL; } @@ -67,41 +74,31 @@ static void disable_xinerama(xcb_connection_t *connection) { s->rect.width = root_screen->width_in_pixels; s->rect.height = root_screen->height_in_pixels; - TAILQ_INSERT_TAIL(&virtual_screens, s, screens); + TAILQ_INSERT_TAIL(virtual_screens, s, screens); + + xinerama_enabled = false; } /* - * We have just established a connection to the X server and need the initial Xinerama - * information to setup workspaces for each screen. + * Gets the Xinerama screens and converts them to virtual i3Screens (only one screen for two + * Xinerama screen which are configured in clone mode) in the given screenlist * */ -void initialize_xinerama(xcb_connection_t *conn) { +static void query_screens(xcb_connection_t *connection, struct screens_head *screenlist) { xcb_xinerama_query_screens_reply_t *reply; xcb_xinerama_screen_info_t *screen_info; - if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) { - printf("Xinerama extension not found, disabling.\n"); - disable_xinerama(conn); - return; - } - - if (!xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL)->state) { - printf("Xinerama is not active (in your X-Server), disabling.\n"); - disable_xinerama(conn); - return; - } - - reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); + reply = xcb_xinerama_query_screens_reply(connection, xcb_xinerama_query_screens_unchecked(connection), NULL); if (!reply) { - printf("Couldn’t get active Xinerama screens\n"); + printf("Couldn't get Xinerama screens\n"); return; } screen_info = xcb_xinerama_query_screens_screen_info(reply); - num_screens = xcb_xinerama_query_screens_screen_info_length(reply); + int screens = xcb_xinerama_query_screens_screen_info_length(reply); + num_screens = 0; - /* Just go through each workspace and associate as many screens as we can. */ - for (int screen = 0; screen < num_screens; screen++) { - i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org); + for (int screen = 0; screen < screens; screen++) { + i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist); if (s!= NULL) { /* This screen already exists. We use the littlest screen so that the user can always see the complete workspace */ @@ -113,7 +110,11 @@ void initialize_xinerama(xcb_connection_t *conn) { s->rect.y = screen_info[screen].y_org; s->rect.width = screen_info[screen].width; s->rect.height = screen_info[screen].height; - TAILQ_INSERT_TAIL(&virtual_screens, s, screens); + /* We always treat the screen at 0x0 as the primary screen */ + if (s->rect.x == 0 && s->rect.y == 0) + TAILQ_INSERT_HEAD(screenlist, s, screens); + else TAILQ_INSERT_TAIL(screenlist, s, screens); + num_screens++; } printf("found Xinerama screen: %d x %d at %d x %d\n", @@ -121,9 +122,36 @@ void initialize_xinerama(xcb_connection_t *conn) { screen_info[screen].x_org, screen_info[screen].y_org); } + free(reply); +} + +/* + * We have just established a connection to the X server and need the initial Xinerama + * information to setup workspaces for each screen. + * + */ +void initialize_xinerama(xcb_connection_t *connection) { + virtual_screens = scalloc(sizeof(struct screens_head)); + TAILQ_INIT(virtual_screens); + + if (!xcb_get_extension_data(connection, &xcb_xinerama_id)->present) { + printf("Xinerama extension not found, disabling.\n"); + disable_xinerama(connection); + return; + } + + if (!xcb_xinerama_is_active_reply(connection, xcb_xinerama_is_active(connection), NULL)->state) { + printf("Xinerama is not active (in your X-Server), disabling.\n"); + disable_xinerama(connection); + return; + } + + query_screens(connection, virtual_screens); + i3Screen *s; num_screens = 0; - TAILQ_FOREACH(s, &virtual_screens, screens) { + /* Just go through each workspace and associate as many screens as we can. */ + TAILQ_FOREACH(s, virtual_screens, screens) { s->num = num_screens; s->current_workspace = num_screens; workspaces[num_screens].screen = s; @@ -131,6 +159,83 @@ void initialize_xinerama(xcb_connection_t *conn) { printf("that is virtual screen at %d x %d with %d x %d\n", s->rect.x, s->rect.y, s->rect.width, s->rect.height); } +} - free(reply); +/* + * This is called when the rootwindow receives a configure_notify event and therefore the + * number/position of the Xinerama screens could have changed. + * + */ +void xinerama_requery_screens(xcb_connection_t *connection) { + /* POSSIBLE PROBLEM: Is the order of the Xinerama screens always constant? That is, can + it change when I move the --right-of video projector to --left-of? */ + + if (!xinerama_enabled) { + printf("Xinerama is disabled\n"); + return; + } + + /* We use a separate copy to diff with the previous set of screens */ + struct screens_head *new_screens = scalloc(sizeof(struct screens_head)); + TAILQ_INIT(new_screens); + + query_screens(connection, new_screens); + + i3Screen *first = TAILQ_FIRST(new_screens), + *screen; + int screen_count = 0; + TAILQ_FOREACH(screen, new_screens, screens) { + screen->num = screen_count; + screen->current_workspace = -1; + for (int c = 0; c < 10; c++) + if ((workspaces[c].screen != NULL) && + (workspaces[c].screen->num == screen_count)) { + printf("Found a matching screen\n"); + /* Try to use the same workspace, if it’s available */ + if (workspaces[c].screen->current_workspace) + screen->current_workspace = workspaces[c].screen->current_workspace; + + if (screen->current_workspace == -1) + screen->current_workspace = c; + + /* Update the dimensions */ + memcpy(&(workspaces[c].rect), &(screen->rect), sizeof(Rect)); + workspaces[c].screen = screen; + } + if (screen->current_workspace == -1) { + /* Create a new workspace for this screen, it’s new */ + for (int c = 0; c < 10; c++) + if (workspaces[c].screen == NULL) { + printf("fix: initializing new workspace, setting num to %d\n", c); + workspaces[c].screen = screen; + screen->current_workspace = c; + /* Copy the dimensions from the virtual screen */ + memcpy(&(workspaces[c].rect), &(screen->rect), sizeof(Rect)); + break; + } + } + screen_count++; + } + + /* Check for workspaces which are out of bounds */ + for (int c = 0; c < 10; c++) + if ((workspaces[c].screen != NULL) && + (workspaces[c].screen->num >= num_screens)) { + printf("Workspace %d's screen out of bounds, assigning to first screen\n", c+1); + workspaces[c].screen = first; + memcpy(&(workspaces[c].rect), &(first->rect), sizeof(Rect)); + } + + /* Free the old list */ + TAILQ_FOREACH(screen, virtual_screens, screens) { + TAILQ_REMOVE(virtual_screens, screen, screens); + free(screen); + } + free(virtual_screens); + + virtual_screens = new_screens; + + printf("Current workspace is now: %d\n", first->current_workspace); + + render_layout(connection); } -- 2.39.5