diff options
Diffstat (limited to 'src/window.c')
-rw-r--r-- | src/window.c | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/src/window.c b/src/window.c new file mode 100644 index 0000000..e28330a --- /dev/null +++ b/src/window.c @@ -0,0 +1,468 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + /* vwm "managed" windows (vwm_window_t) (which are built upon the "core" X windows (vwm_xwindow_t)) */ + +#include <X11/Xlib.h> +#include <stdlib.h> +#include <stdarg.h> + +#include "desktop.h" +#include "list.h" +#include "vwm.h" +#include "window.h" +#include "xwindow.h" + +/* unmap the specified window and set the unmapping-in-progress flag so we can discard vwm-generated UnmapNotify events */ +void vwm_win_unmap(vwm_t *vwm, vwm_window_t *vwin) +{ + if (!vwin->xwindow->mapped) { + VWM_TRACE("inhibited unmap of \"%s\", not mapped by client", vwin->xwindow->name); + return; + } + VWM_TRACE("Unmapping \"%s\"", vwin->xwindow->name); + vwin->unmapping = 1; + XUnmapWindow(vwm->display, vwin->xwindow->id); +} + + +/* map the specified window and set the mapping-in-progress flag so we can discard vwm-generated MapNotify events */ +void vwm_win_map(vwm_t *vwm, vwm_window_t *vwin) +{ + if (!vwin->xwindow->mapped) { + VWM_TRACE("inhibited map of \"%s\", not mapped by client", vwin->xwindow->name); + return; + } + VWM_TRACE("Mapping \"%s\"", vwin->xwindow->name); + vwin->mapping = 1; + XMapWindow(vwm->display, vwin->xwindow->id); +} + + +/* make the specified window the most recently used one */ +void vwm_win_mru(vwm_t *vwm, vwm_window_t *vwin) +{ + list_move(&vwin->windows_mru, &vwm->windows_mru); +} + + +/* look up the X window in the global managed windows list */ +vwm_window_t * vwm_win_lookup(vwm_t *vwm, Window win) +{ + vwm_window_t *tmp, *vwin = NULL; + + list_for_each_entry(tmp, &vwm->windows_mru, windows_mru) { + if (tmp->xwindow->id == win) { + vwin = tmp; + break; + } + } + + return vwin; +} + + +/* return the currently focused window (considers current context...), may return NULL */ +vwm_window_t * vwm_win_focused(vwm_t *vwm) +{ + vwm_window_t *vwin = NULL; + + switch (vwm->focused_context) { + case VWM_CONTEXT_SHELF: + vwin = vwm->focused_shelf; + break; + + case VWM_CONTEXT_DESKTOP: + if (vwm->focused_desktop) vwin = vwm->focused_desktop->focused_window; + break; + + default: + VWM_BUG("Unsupported context"); + break; + } + + return vwin; +} + + +/* "autoconfigure" windows (configuration shortcuts like fullscreen/halfscreen/quarterscreen) and restoring the window */ +void vwm_win_autoconf(vwm_t *vwm, vwm_window_t *vwin, vwm_screen_rel_t rel, vwm_win_autoconf_t conf, ...) +{ + const vwm_screen_t *scr; + va_list ap; + XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; + + /* remember the current configuration as the "client" configuration if it's not an autoconfigured one. */ + if (vwin->autoconfigured == VWM_WIN_AUTOCONF_NONE) vwin->client = vwin->xwindow->attrs; + + scr = vwm_screen_find(vwm, rel, vwin->xwindow); /* XXX FIXME: this becomes a bug when vwm_screen_find() uses non-xwin va_args */ + va_start(ap, conf); + switch (conf) { + case VWM_WIN_AUTOCONF_QUARTER: { + vwm_corner_t corner = va_arg(ap, vwm_corner_t); + changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); + switch (corner) { + case VWM_CORNER_TOP_LEFT: + changes.x = scr->x_org; + changes.y = scr->y_org; + break; + + case VWM_CORNER_TOP_RIGHT: + changes.x = scr->x_org + scr->width / 2; + changes.y = scr->y_org; + break; + + case VWM_CORNER_BOTTOM_RIGHT: + changes.x = scr->x_org + scr->width / 2; + changes.y = scr->y_org + scr->height / 2; + break; + + case VWM_CORNER_BOTTOM_LEFT: + changes.x = scr->x_org; + changes.y = scr->y_org + scr->height / 2; + break; + } + break; + } + + case VWM_WIN_AUTOCONF_HALF: { + vwm_side_t side = va_arg(ap, vwm_side_t); + switch (side) { + case VWM_SIDE_TOP: + changes.width = scr->width - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.x = scr->x_org; + changes.y = scr->y_org; + break; + + case VWM_SIDE_BOTTOM: + changes.width = scr->width - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.x = scr->x_org; + changes.y = scr->y_org + scr->height / 2; + break; + + case VWM_SIDE_LEFT: + changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height - (WINDOW_BORDER_WIDTH * 2); + changes.x = scr->x_org; + changes.y = scr->y_org; + break; + + case VWM_SIDE_RIGHT: + changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height - (WINDOW_BORDER_WIDTH * 2); + changes.x = scr->x_org + scr->width / 2; + changes.y = scr->y_org; + break; + } + break; + } + + case VWM_WIN_AUTOCONF_FULL: + changes.width = scr->width - WINDOW_BORDER_WIDTH * 2; + changes.height = scr->height - WINDOW_BORDER_WIDTH * 2; + changes.x = scr->x_org; + changes.y = scr->y_org; + break; + + case VWM_WIN_AUTOCONF_ALL: + changes.width = scr->width; + changes.height = scr->height; + changes.x = scr->x_org; + changes.y = scr->y_org; + changes.border_width = 0; + break; + + case VWM_WIN_AUTOCONF_NONE: /* restore window if autoconfigured */ + changes.width = vwin->client.width; + changes.height = vwin->client.height; + changes.x = vwin->client.x; + changes.y = vwin->client.y; + break; + } + va_end(ap); + + XConfigureWindow(vwm->display, vwin->xwindow->id, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &changes); + vwin->autoconfigured = conf; +} + + +/* focus a window */ +/* this updates window border color as needed and the X input focus if mapped */ +void vwm_win_focus(vwm_t *vwm, vwm_window_t *vwin) +{ + VWM_TRACE("focusing: %#x", (unsigned int)vwin->xwindow->id); + + if (vwm_xwin_is_mapped(vwm, vwin->xwindow)) { + /* if vwin is mapped give it the input focus */ + XSetInputFocus(vwm->display, vwin->xwindow->id, RevertToPointerRoot, CurrentTime); + } + + /* update the border color accordingly */ + if (vwin->shelved) { + /* set the border of the newly focused window to the shelved color */ + XSetWindowBorder(vwm->display, vwin->xwindow->id, vwin == vwm->console ? vwm->colors.shelved_console_border_color.pixel : vwm->colors.shelved_window_border_color.pixel); + /* fullscreen windows in the shelf when focused, since we don't intend to overlap there */ + vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_POINTER, VWM_WIN_AUTOCONF_FULL); /* XXX TODO: for now the shelf follows the pointer, it's simple. */ + } else { + if (vwin->desktop->focused_window) { + /* set the border of the previously focused window on the same desktop to the unfocused color */ + XSetWindowBorder(vwm->display, vwin->desktop->focused_window->xwindow->id, vwm->colors.unfocused_window_border_color.pixel); + } + + /* set the border of the newly focused window to the focused color */ + XSetWindowBorder(vwm->display, vwin->xwindow->id, vwm->colors.focused_window_border_color.pixel); + + /* persist this on a per-desktop basis so it can be restored on desktop switches */ + vwin->desktop->focused_window = vwin; + } +} + + +/* focus the next window on a virtual desktop relative to the supplied window, in the specified context, respecting screen boundaries according to fence. */ +vwm_window_t * vwm_win_focus_next(vwm_t *vwm, vwm_window_t *vwin, vwm_fence_t fence) +{ + const vwm_screen_t *scr = vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, vwin->xwindow), *next_scr = NULL; + vwm_window_t *next; + unsigned long visited_mask; + +_retry: + visited_mask = 0; + list_for_each_entry(next, &vwin->windows_mru, windows_mru) { + /* searching for the next mapped window in this context, using vwin->windows as the head */ + if (&next->windows_mru == &vwm->windows_mru) continue; /* XXX: skip the containerless head, we're leveraging the circular list implementation */ + + if ((vwin->shelved && next->shelved) || + ((!vwin->shelved && !next->shelved && next->desktop == vwin->desktop) && + (fence == VWM_FENCE_IGNORE || + ((fence == VWM_FENCE_RESPECT || fence == VWM_FENCE_TRY_RESPECT) && vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, next->xwindow) == scr) || + (fence == VWM_FENCE_VIOLATE && vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, next->xwindow) != scr) || + (fence == VWM_FENCE_MASKED_VIOLATE && (next_scr = vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, next->xwindow)) != scr && + !((1UL << next_scr->screen_number) & vwm->fence_mask)) + ))) break; + + if (fence == VWM_FENCE_MASKED_VIOLATE && next_scr && next_scr != scr) visited_mask |= (1UL << next_scr->screen_number); + } + + if (fence == VWM_FENCE_TRY_RESPECT && next == vwin) { + /* if we tried to respect the fence and failed to find a next, fallback to ignoring the fence and try again */ + fence = VWM_FENCE_IGNORE; + goto _retry; + } else if (fence == VWM_FENCE_MASKED_VIOLATE && next_scr) { + /* if we did a masked violate update the mask with the potentially new screen number */ + if (next == vwin && visited_mask) { + /* if we failed to find a next window on a different screen but had candidates we've exhausted screens and need to reset the mask then retry */ + VWM_TRACE("all candidate screens masked @ 0x%lx, resetting mask", vwm->fence_mask); + vwm->fence_mask = 0; + goto _retry; + } + vwm->fence_mask |= (1UL << next_scr->screen_number); + VWM_TRACE("VWM_FENCE_MASKED_VIOLATE fence_mask now: 0x%lx\n", vwm->fence_mask); + } + + if (vwin->shelved) { + if (next != vwm->focused_shelf) { + /* shelf switch, unmap the focused shelf and take it over */ + /* TODO FIXME: this makes assumptions about the shelf being focused calling unmap/map directly.. */ + vwm_win_unmap(vwm, vwm->focused_shelf); + + XFlush(vwm->display); + + vwm_win_map(vwm, next); + vwm->focused_shelf = next; + vwm_win_focus(vwm, next); + } + } else { + if (next != next->desktop->focused_window) { + /* focus the changed window */ + vwm_win_focus(vwm, next); + XRaiseWindow(vwm->display, next->xwindow->id); + } + } + + VWM_TRACE("vwin=%p xwin=%p name=\"%s\"", next, next->xwindow, next->xwindow->name); + + return next; +} + + +/* shelves a window, if the window is focused we focus the next one (if possible) */ +void vwm_win_shelve(vwm_t *vwm, vwm_window_t *vwin) +{ + /* already shelved, NOOP */ + if (vwin->shelved) return; + + /* shelving focused window, focus the next window */ + if (vwin == vwin->desktop->focused_window) { + vwm_win_mru(vwm, vwm_win_focus_next(vwm, vwin, VWM_FENCE_RESPECT)); + } + + if (vwin == vwin->desktop->focused_window) { + /* TODO: we can probably put this into vwm_win_focus_next() and have it always handled there... */ + vwin->desktop->focused_window = NULL; + } + + vwin->shelved = 1; + vwm_win_mru(vwm, vwin); + + /* newly shelved windows always become the focused shelf */ + vwm->focused_shelf = vwin; + + vwm_win_unmap(vwm, vwin); +} + + +/* helper for (idempotently) unfocusing a window, deals with context switching etc... */ +void vwm_win_unfocus(vwm_t *vwm, vwm_window_t *vwin) +{ + /* if we're the focused shelved window, cycle the focus to the next shelved window if possible, if there's no more shelf, switch to the desktop */ + /* TODO: there's probably some icky behaviors for focused windows unmapping/destroying in unfocused contexts, we probably jump contexts suddenly. */ + if (vwin == vwm->focused_shelf) { + VWM_TRACE("unfocusing focused shelf"); + vwm_win_focus_next(vwm, vwin, VWM_FENCE_IGNORE); + + if (vwin == vwm->focused_shelf) { + VWM_TRACE("shelf empty, leaving"); + /* no other shelved windows, exit the shelf context */ + vwm_context_focus(vwm, VWM_CONTEXT_DESKTOP); + vwm->focused_shelf = NULL; + } + } + + /* if we're the focused window cycle the focus to the next window on the desktop if possible */ + if (vwin->desktop->focused_window == vwin) { + VWM_TRACE("unfocusing focused window"); + vwm_win_focus_next(vwm, vwin, VWM_FENCE_TRY_RESPECT); + } + + if (vwin->desktop->focused_window == vwin) { + VWM_TRACE("desktop empty"); + vwin->desktop->focused_window = NULL; + } +} + + +/* demote an managed window to an unmanaged one */ +vwm_xwindow_t * vwm_win_unmanage(vwm_t *vwm, vwm_window_t *vwin) +{ + vwm_win_mru(vwm, vwin); /* bump vwin to the mru head before unfocusing so we always move focus to the current head on unmanage of the focused window */ + vwm_win_unfocus(vwm, vwin); + list_del(&vwin->windows_mru); + + if (vwin == vwm->console) vwm->console = NULL; + if (vwin == vwm->focused_origin) vwm->focused_origin = NULL; + + vwin->xwindow->managed = NULL; + + return vwin->xwindow; +} + + +/* promote an unmanaged window to a managed one */ +vwm_window_t * vwm_win_manage_xwin(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + vwm_window_t *focused, *vwin = NULL; + + if (xwin->managed) { + VWM_TRACE("suppressed re-management of xwin=%p", xwin); + goto _fail; + } + + if (!(vwin = (vwm_window_t *)malloc(sizeof(vwm_window_t)))) { + VWM_PERROR("Failed to allocate vwin"); + goto _fail; + } + + XUngrabButton(vwm->display, AnyButton, AnyModifier, xwin->id); + XGrabButton(vwm->display, AnyButton, WM_GRAB_MODIFIER, xwin->id, False, (PointerMotionMask | ButtonPressMask | ButtonReleaseMask), GrabModeAsync, GrabModeAsync, None, None); + XGrabKey(vwm->display, AnyKey, WM_GRAB_MODIFIER, xwin->id, False, GrabModeAsync, GrabModeAsync); + XSetWindowBorder(vwm->display, xwin->id, vwm->colors.unfocused_window_border_color.pixel); + + vwin->hints = XAllocSizeHints(); + if (!vwin->hints) { + VWM_PERROR("Failed to allocate WM hints"); + goto _fail; + } + XGetWMNormalHints(vwm->display, xwin->id, vwin->hints, &vwin->hints_supplied); + + xwin->managed = vwin; + vwin->xwindow = xwin; + + vwin->desktop = vwm->focused_desktop; + vwin->autoconfigured = VWM_WIN_AUTOCONF_NONE; + vwin->mapping = vwin->unmapping = vwin->configuring = 0; + vwin->shelved = (vwm->focused_context == VWM_CONTEXT_SHELF); /* if we're in the shelf when the window is created, the window is shelved */ + vwin->client = xwin->attrs; /* remember whatever the current attributes are */ + + VWM_TRACE("hints: flags=%lx x=%i y=%i w=%i h=%i minw=%i minh=%i maxw=%i maxh=%i winc=%i hinc=%i basew=%i baseh=%i grav=%x", + vwin->hints->flags, + vwin->hints->x, vwin->hints->y, + vwin->hints->width, vwin->hints->height, + vwin->hints->min_width, vwin->hints->min_height, + vwin->hints->max_width, vwin->hints->max_height, + vwin->hints->width_inc, vwin->hints->height_inc, + vwin->hints->base_width, vwin->hints->base_height, + vwin->hints->win_gravity); + + if ((vwin->hints_supplied & (USSize | PSize))) { + vwin->client.width = vwin->hints->base_width; + vwin->client.height = vwin->hints->base_height; + } + + /* put it on the global windows_mru list, if there's a focused window insert the new one after it */ + if (!list_empty(&vwm->windows_mru) && (focused = vwm_win_focused(vwm))) { + /* insert the vwin immediately after the focused window, so Mod1+Tab goes to the new window */ + list_add(&vwin->windows_mru, &focused->windows_mru); + } else { + list_add(&vwin->windows_mru, &vwm->windows_mru); + } + + /* always raise newly managed windows so we know about them. */ + XRaiseWindow(vwm->display, xwin->id); + + /* if the desktop has no focused window yet, automatically focus the newly managed one, provided we're on the desktop context */ + if (!vwm->focused_desktop->focused_window && vwm->focused_context == VWM_CONTEXT_DESKTOP) { + VWM_TRACE("Mapped new window \"%s\" is alone on desktop \"%s\", focusing", xwin->name, vwm->focused_desktop->name); + vwm_win_focus(vwm, vwin); + } + + return vwin; + +_fail: + if (vwin) { + if (vwin->hints) XFree(vwin->hints); + free(vwin); + } + return NULL; +} + + +/* migrate a window to a new desktop, focuses the destination desktop as well */ +void vwm_win_migrate(vwm_t *vwm, vwm_window_t *vwin, vwm_desktop_t *desktop) +{ + vwm_win_unfocus(vwm, vwin); /* go through the motions of unfocusing the window if it is focused */ + vwin->shelved = 0; /* ensure not shelved */ + vwin->desktop = desktop; /* assign the new desktop */ + vwm_desktop_focus(vwm, desktop); /* currently we always focus the new desktop in a migrate */ + + vwm_win_focus(vwm, vwin); /* focus the window so borders get updated */ + vwm_win_mru(vwm, vwin); /* TODO: is this right? shouldn't the Mod1 release be what's responsible for this? I migrate so infrequently it probably doesn't matter */ + + XRaiseWindow(vwm->display, vwin->xwindow->id); /* ensure the window is raised */ +} |