/* * Manage windows, such as Mapping them and making sure the proper key Grabs * have been put in place. * * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * 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, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ #include #include #include #include #include #include #include #include #include #include "sdorfehs.h" static char **unmanaged_window_list = NULL; static int num_unmanaged_windows = 0; void clear_unmanaged_list(void) { if (unmanaged_window_list) { int i; for (i = 0; i < num_unmanaged_windows; i++) free(unmanaged_window_list[i]); free(unmanaged_window_list); unmanaged_window_list = NULL; } num_unmanaged_windows = 0; } char * list_unmanaged_windows(void) { char *tmp = NULL; if (unmanaged_window_list) { struct sbuf *buf; int i; buf = sbuf_new(0); for (i = 0; i < num_unmanaged_windows; i++) { sbuf_concat(buf, unmanaged_window_list[i]); sbuf_concat(buf, "\n"); } sbuf_chop(buf); tmp = sbuf_free_struct(buf); } return tmp; } void add_unmanaged_window(char *name) { char **tmp; if (!name) return; tmp = xmalloc((num_unmanaged_windows + 1) * sizeof(char *)); if (unmanaged_window_list) { memcpy(tmp, unmanaged_window_list, num_unmanaged_windows * sizeof(char *)); free(unmanaged_window_list); } tmp[num_unmanaged_windows] = xstrdup(name); num_unmanaged_windows++; unmanaged_window_list = tmp; } void grab_top_level_keys(Window w) { rp_keymap *map = find_keymap(defaults.top_kmap); int i; if (map == NULL) { warnx("unable to find %s level keymap", defaults.top_kmap); return; } PRINT_INPUT_DEBUG(("grabbing top level key\n")); for (i = 0; i < map->actions_last; i++) { PRINT_INPUT_DEBUG(("%d\n", i)); grab_key(map->actions[i].key, map->actions[i].state, w); } } void ungrab_top_level_keys(Window w) { XUngrabKey(dpy, AnyKey, AnyModifier, w); } void ungrab_keys_all_wins(void) { rp_window *cur; /* Remove the grab on the current prefix key */ list_for_each_entry(cur, &rp_mapped_window, node) { ungrab_top_level_keys(cur->w); } } void grab_keys_all_wins(void) { rp_window *cur; /* Remove the grab on the current prefix key */ list_for_each_entry(cur, &rp_mapped_window, node) { grab_top_level_keys(cur->w); } } void update_normal_hints(rp_window *win) { long supplied; XGetWMNormalHints(dpy, win->w, win->hints, &supplied); /* Discard bogus hints */ if ((win->hints->flags & PAspect) && (win->hints->min_aspect.x < 1 || win->hints->min_aspect.y < 1 || win->hints->max_aspect.x < 1 || win->hints->max_aspect.y < 1)) win->hints->flags &= ~PAspect; if ((win->hints->flags & PMaxSize) && win->hints->max_width < 1) win->hints->flags &= ~PMaxSize; if ((win->hints->flags & PMinSize) && win->hints->min_width < 1) win->hints->flags &= ~PMinSize; if ((win->hints->flags & PResizeInc) && (win->hints->width_inc < 1 || win->hints->height_inc < 1)) win->hints->flags &= ~PResizeInc; /* Print debugging output for window hints. */ #ifdef DEBUG if (win->hints->flags & PMinSize) PRINT_DEBUG(("minx: %d miny: %d\n", win->hints->min_width, win->hints->min_height)); if (win->hints->flags & PMaxSize) PRINT_DEBUG(("maxx: %d maxy: %d\n", win->hints->max_width, win->hints->max_height)); if (win->hints->flags & PResizeInc) PRINT_DEBUG(("incx: %d incy: %d\n", win->hints->width_inc, win->hints->height_inc)); #endif } static char * get_wmname(Window w) { char *name = NULL; XTextProperty text_prop; Atom type = None; int ret = None, n; unsigned long nitems, bytes_after; int format; char **cl; unsigned char *val = NULL; /* * Try to use the window's _NET_WM_NAME ewmh property */ ret = XGetWindowProperty(dpy, w, _net_wm_name, 0, 40, False, xa_utf8_string, &type, &format, &nitems, &bytes_after, &val); /* We have a valid UTF-8 string */ if (ret == Success && type == xa_utf8_string && format == 8 && nitems > 0) { name = xstrdup((char *) val); XFree(val); PRINT_DEBUG(("Fetching window name using " "_NET_WM_NAME succeeded\n")); PRINT_DEBUG(("WM_NAME: %s\n", name)); return name; } /* Something went wrong for whatever reason */ if (ret == Success && val) XFree(val); PRINT_DEBUG(("Could not fetch window name using _NET_WM_NAME\n")); if (XGetWMName(dpy, w, &text_prop) == 0) { PRINT_DEBUG(("XGetWMName failed\n")); return NULL; } PRINT_DEBUG(("WM_NAME encoding: ")); if (text_prop.encoding == xa_string) PRINT_DEBUG(("STRING\n")); else if (text_prop.encoding == xa_compound_text) PRINT_DEBUG(("COMPOUND_TEXT\n")); else if (text_prop.encoding == xa_utf8_string) PRINT_DEBUG(("UTF8_STRING\n")); else PRINT_DEBUG(("unknown (%d)\n", (int) text_prop.encoding)); /* * It seems that most applications supporting UTF8_STRING and * _NET_WM_NAME don't bother making their WM_NAME available as * UTF8_STRING (but only as either STRING or COMPOUND_TEXT). Let's try * anyway. */ if (text_prop.encoding == xa_utf8_string) { ret = Xutf8TextPropertyToTextList(dpy, &text_prop, &cl, &n); PRINT_DEBUG(("Xutf8TextPropertyToTextList: %s\n", ret == Success ? "success" : "error")); } else { /* * XmbTextPropertyToTextList should be fine for all cases, even * UTF8_STRING encoded WM_NAME */ ret = XmbTextPropertyToTextList(dpy, &text_prop, &cl, &n); PRINT_DEBUG(("XmbTextPropertyToTextList: %s\n", ret == Success ? "success" : "error")); } if (ret == Success && cl && n > 0) { name = xstrdup(cl[0]); XFreeStringList(cl); } else if (text_prop.value) { /* Convertion failed, try to get the raw string */ name = xstrdup((char *) text_prop.value); XFree(text_prop.value); } if (name == NULL) { PRINT_DEBUG(("I can't get the WMName.\n")); } else { PRINT_DEBUG(("WM_NAME: '%s'\n", name)); } return name; } static XClassHint * get_class_hints(Window w) { XClassHint *class; class = XAllocClassHint(); if (class == NULL) errx(1, "not enough memory for WM_CLASS structure"); XGetClassHint(dpy, w, class); return class; } /* * Reget the WM_NAME property for the window and update its name. Return 1 if * the name changed. */ int update_window_name(rp_window *win) { char *newstr; int changed = 0; XClassHint *class; newstr = get_wmname(win->w); if (newstr != NULL) { changed = changed || win->wm_name == NULL || strcmp(newstr, win->wm_name); free(win->wm_name); win->wm_name = newstr; } class = get_class_hints(win->w); if (class->res_class != NULL && (win->res_class == NULL || strcmp(class->res_class, win->res_class))) { changed = 1; free(win->res_class); win->res_class = xstrdup(class->res_class); } if (class->res_name != NULL && (win->res_name == NULL || strcmp(class->res_name, win->res_name))) { changed = 1; free(win->res_name); win->res_name = xstrdup(class->res_name); } XFree(class->res_name); XFree(class->res_class); XFree(class); return changed; } /* * This function is used to determine if the window should be treated as a * transient. */ int window_is_transient(rp_window *win) { return win->transient #ifdef ASPECT_WINDOWS_ARE_TRANSIENTS || win->hints->flags & PAspect #endif #ifdef MAXSIZE_WINDOWS_ARE_TRANSIENTS || (win->hints->flags & PMaxSize && (win->hints->max_width < win->vscreen->screen->width || win->hints->max_height < win->vscreen->screen->height)) #endif ; } Atom get_net_wm_window_type(rp_window *win) { Atom type, window_type = None; int format; unsigned long nitems; unsigned long bytes_left; unsigned char *data; if (win == NULL) return None; if (XGetWindowProperty(dpy, win->w, _net_wm_window_type, 0, 1L, False, XA_ATOM, &type, &format, &nitems, &bytes_left, &data) == Success && nitems > 0) { window_type = *(Atom *)data; XFree(data); } return window_type; } int is_unmanaged_window_type(Window win) { Atom win_type; rp_window tmp; tmp.w = win; win_type = get_net_wm_window_type(&tmp); if (win_type == _net_wm_window_type_dock || win_type == _net_wm_window_type_splash || win_type == _net_wm_window_type_tooltip || win_type == _net_wm_window_type_utility) return 1; return 0; } void update_window_information(rp_window *win) { XWindowAttributes attr; update_window_name(win); /* Get the WM Hints */ update_normal_hints(win); /* Get the colormap */ XGetWindowAttributes(dpy, win->w, &attr); win->colormap = attr.colormap; win->x = attr.x; win->y = attr.y; win->width = attr.width; win->height = attr.height; win->border = attr.border_width; /* Transient status */ win->transient = XGetTransientForHint(dpy, win->w, &win->transient_for); if (get_net_wm_window_type(win) == _net_wm_window_type_dialog) win->transient = 1; PRINT_DEBUG(("update_window_information: x:%d y:%d width:%d height:%d " "transient:%d\n", win->x, win->y, win->width, win->height, win->transient)); update_window_gravity(win); } void unmanage(rp_window *w) { list_del(&w->node); vscreen_del_window(w->vscreen, w); remove_atom(rp_glob_screen.root, _net_client_list, XA_WINDOW, w->w); remove_atom(rp_glob_screen.root, _net_client_list_stacking, XA_WINDOW, w->w); free_window(w); } /* When starting up scan existing windows and start managing them. */ void scanwins(void) { rp_window *win; XWindowAttributes attr; unsigned int i, nwins; Window dw1, dw2, *wins; XQueryTree(dpy, rp_glob_screen.root, &dw1, &dw2, &wins, &nwins); PRINT_DEBUG(("windows: %d\n", nwins)); for (i = 0; i < nwins; i++) { rp_screen *screen; XGetWindowAttributes(dpy, wins[i], &attr); if (is_rp_window(wins[i]) || attr.override_redirect == True || unmanaged_window(wins[i])) continue; screen = find_screen_by_attr(attr); if (!screen) list_first(screen, &rp_screens, node); win = add_to_window_list(screen, wins[i]); PRINT_DEBUG(("map_state: %s\n", attr.map_state == IsViewable ? "IsViewable" : attr.map_state == IsUnviewable ? "IsUnviewable" : "IsUnmapped")); PRINT_DEBUG(("state: %s\n", get_state(win) == IconicState ? "Iconic" : get_state(win) == NormalState ? "Normal" : "Other")); /* Collect mapped and iconized windows. */ if (attr.map_state == IsViewable || (attr.map_state == IsUnmapped && get_state(win) == IconicState)) map_window(win); } XFree(wins); } int unmanaged_window(Window w) { char *wname; int i; if (!unmanaged_window_list) return 0; wname = get_wmname(w); if (!wname) return 0; for (i = 0; i < num_unmanaged_windows; i++) { if (!strcmp(unmanaged_window_list[i], wname)) { free(wname); return 1; } } free(wname); if (is_unmanaged_window_type(w)) return 1; return 0; } /* Set the state of the window. */ void set_state(rp_window *win, int state) { unsigned long data[2]; win->state = state; data[0] = (long) win->state; data[1] = (long) None; set_atom(win->w, wm_state, wm_state, data, 2); } /* Get the WM state of the window. */ long get_state(rp_window *win) { long state = WithdrawnState; Atom type; int format; unsigned long nitems; unsigned long bytes_left; unsigned char *data; if (win == NULL) return state; if (XGetWindowProperty(dpy, win->w, wm_state, 0L, 2L, False, wm_state, &type, &format, &nitems, &bytes_left, &data) == Success && nitems > 0) { state = *(long *)data; XFree(data); } return state; } void check_state(rp_window *win) { Atom state; unsigned long read, left; int i, fs = 0; for (i = 0, left = 1; left; i += read) { read = get_atom(win->w, _net_wm_state, XA_ATOM, i, &state, 1, &left); if (!read) break; if (state == _net_wm_state_fullscreen) { fs = 1; window_full_screen(win); } else { PRINT_DEBUG(("unhandled window state %ld (%s)\n", state, XGetAtomName(dpy, state))); } } if (win->full_screen && !fs) window_full_screen(NULL); } static void move_window(rp_window *win) { rp_frame *frame; int t, t2, gap; if (win->frame_number == EMPTY) { PRINT_DEBUG(("%s: window has no frame\n", __func__)); return; } frame = win_get_frame(win); if (win->full_screen) { win->x = win->vscreen->screen->left; win->y = win->vscreen->screen->top; return; } if (defaults.only_border == 0 && num_frames(win->vscreen) <= 1) gap = 0; else gap = defaults.gap; /* X coord. */ switch (win->gravity) { case NorthWestGravity: case WestGravity: case SouthWestGravity: win->x = frame->x + (frame_left_screen_edge(frame) ? 0 : gap); break; case NorthGravity: case CenterGravity: case SouthGravity: t = (frame_left_screen_edge(frame) ? 0 : gap); t2 = (frame_right_screen_edge(frame) ? 0 : gap); win->x = frame->x + t + ((frame->width - t - t2 - (win->width + win->border * 2)) / 2); break; case NorthEastGravity: case EastGravity: case SouthEastGravity: win->x = frame->x + frame->width - (win->width + win->border * 2) - (frame_right_screen_edge(frame) ? 0 : gap); break; } /* Y coord. */ switch (win->gravity) { case NorthEastGravity: case NorthGravity: case NorthWestGravity: win->y = frame->y + (frame_top_screen_edge(frame) ? 0 : gap); break; case EastGravity: case CenterGravity: case WestGravity: t = (frame_top_screen_edge(frame) ? 0 : gap); t2 = (frame_bottom_screen_edge(frame) ? 0 : gap); win->y = frame->y + t + ((frame->height - t - t2 - (win->height + win->border * 2)) / 2); break; case SouthEastGravity: case SouthGravity: case SouthWestGravity: win->y = frame->y + frame->height - (win->height + win->border * 2) - (frame_bottom_screen_edge(frame) ? 0 : gap); break; } if (win->x < frame->x) win->x = frame->x; if (win->y < frame->y) win->y = frame->y; } /* * set a good standard window's x,y,width,height fields to maximize the window. */ static void maximize_window(rp_window *win, int transient) { rp_frame *frame; int maxw, maxh; float gap; frame = win_get_frame(win); /* We can't maximize a window if it has no frame. */ if (frame == NULL) { PRINT_DEBUG(("%s: no frame\n", __func__)); return; } /* Set the window's border */ if ((defaults.only_border == 0 && num_frames(win->vscreen) <= 1) || win->full_screen) win->border = 0; else win->border = defaults.window_border_width; if (win->full_screen) { maxw = win->vscreen->screen->width; maxh = win->vscreen->screen->height; } else { if (transient) { maxw = win->width; maxh = win->height; } else { maxw = frame->width; maxh = frame->height; } if (win->hints->flags & PMaxSize) { if (maxw > win->hints->max_width) maxw = win->hints->max_width; if (maxh > win->hints->max_height) maxh = win->hints->max_height; } if (maxw > frame->width) maxw = frame->width; if (maxh > frame->height) maxh = frame->height; PRINT_DEBUG(("adjusted to frame, maxsize %d %d\n", maxw, maxh)); if (!transient && !(defaults.only_border == 0 && num_frames(win->vscreen) <= 1)) { int fw, fh; gap = (frame_right_screen_edge(frame) ? 0 : 1); gap += (frame_left_screen_edge(frame) ? 0 : 1); fw = frame->width - (gap * defaults.gap) - (win->border * 2); if (maxw > fw) maxw = fw; gap = (frame_top_screen_edge(frame) ? 0 : 1); gap += (frame_bottom_screen_edge(frame) ? 0 : 1); fh = frame->height - (gap * defaults.gap) - (win->border * 2); if (maxh > fh) maxh = fh; } } /* Honour the window's aspect ratio. */ PRINT_DEBUG(("aspect: %ld\n", win->hints->flags & PAspect)); if (!win->full_screen && (win->hints->flags & PAspect)) { float ratio = (float) maxw / maxh; float min_ratio = (float) win->hints->min_aspect.x / win->hints->min_aspect.y; float max_ratio = (float) win->hints->max_aspect.x / win->hints->max_aspect.y; PRINT_DEBUG(("ratio=%f min_ratio=%f max_ratio=%f\n", ratio, min_ratio, max_ratio)); if (ratio < min_ratio) { maxh = (int) (maxw / min_ratio); } else if (ratio > max_ratio) { maxw = (int) (maxh * max_ratio); } PRINT_DEBUG(("honored ratio, maxsize %d %d\n", maxw, maxh)); } /* * Make sure we maximize to the nearest Resize Increment specified by * the window */ if (!defaults.ignore_resize_hints && !win->full_screen && (win->hints->flags & PResizeInc)) { int amount; int delta; if (win->hints->width_inc) { amount = maxw - win->width; delta = amount % win->hints->width_inc; if (amount < 0 && delta) amount -= win->hints->width_inc; amount -= delta; maxw = amount + win->width; } if (win->hints->height_inc) { amount = maxh - win->height; delta = amount % win->hints->height_inc; if (amount < 0 && delta) amount -= win->hints->height_inc; amount -= delta; maxh = amount + win->height; } PRINT_DEBUG(("applied width_inc/height_inc, maxsize %d %d\n", maxw, maxh)); } PRINT_DEBUG(("final maxsize: %d %d\n", maxw, maxh)); win->width = maxw; win->height = maxh; } /* * Maximize the current window if data = 0, otherwise assume it is a pointer to * a window that should be maximized */ void maximize(rp_window *win) { if (!win) win = current_window(); if (!win) return; /* Handle maximizing transient windows differently. */ maximize_window(win, win->transient); /* Reposition the window. */ move_window(win); PRINT_DEBUG(("Resizing %s window '%s' to x:%d y:%d w:%d h:%d\n", win->transient ? "transient" : "normal", window_name(win), win->x, win->y, win->width, win->height)); /* Actually do the maximizing. */ XMoveResizeWindow(dpy, win->w, win->x, win->y, win->width, win->height); XSetWindowBorderWidth(dpy, win->w, win->border); XSync(dpy, False); } /* * Maximize the current window but don't treat transient windows differently. */ void force_maximize(rp_window *win) { if (!win) win = current_window(); if (!win) return; maximize_window(win, 0); /* Reposition the window. */ move_window(win); /* * This little dance is to force a maximize event. If the window is * already "maximized" X11 will optimize away the event since to * geometry changes were made. This initial resize solves the problem. */ if (!defaults.ignore_resize_hints && (win->hints->flags & PResizeInc)) { XMoveResizeWindow(dpy, win->w, win->vscreen->screen->left + win->x, win->vscreen->screen->top + win->y, win->width + win->hints->width_inc, win->height + win->hints->height_inc); } else { XResizeWindow(dpy, win->w, win->width + 1, win->height + 1); } XSync(dpy, False); /* Resize the window to its proper maximum size. */ XMoveResizeWindow(dpy, win->w, win->vscreen->screen->left + win->x, win->vscreen->screen->top + win->y, win->width, win->height); XSetWindowBorderWidth(dpy, win->w, win->border); XSync(dpy, False); } /* map the unmapped window win */ void map_window(rp_window *win) { rp_window *transfor; PRINT_DEBUG(("Mapping the unmapped window %s\n", window_name(win))); /* Fill in the necessary data about the window */ update_window_information(win); if (win->transient_for && (transfor = find_window(win->transient_for))) { PRINT_DEBUG(("map_window: transient_for %lu\n", win->transient_for)); win->vscreen = transfor->vscreen; win->intended_frame_number = transfor->frame_number; } win->number = numset_request(rp_window_numset); grab_top_level_keys(win->w); /* Put win in the mapped window list */ list_del(&win->node); insert_into_list(win, &rp_mapped_window); vscreen_map_window(win->vscreen, win); /* * The window has never been accessed since it was brought back from the * Withdrawn state. */ win->last_access = 0; /* It is now considered iconic and set_active_window can handle the * rest. */ set_state(win, IconicState); /* Depending on the rudeness level, actually map the window. */ if ((rp_honour_transient_map && win->transient) || (rp_honour_normal_map && !win->transient)) set_active_window(win); else show_rudeness_msg(win, 0); append_atom(rp_glob_screen.root, _net_client_list, XA_WINDOW, &win->w, 1); append_atom(rp_glob_screen.root, _net_client_list_stacking, XA_WINDOW, &win->w, 1); hook_run(&rp_new_window_hook); } void hide_window(rp_window *win) { if (win == NULL) return; /* An unmapped window is not inside a frame. */ win->frame_number = EMPTY; /* Ignore the unmap_notify event. */ XSelectInput(dpy, win->w, WIN_EVENTS & ~(StructureNotifyMask)); XUnmapWindow(dpy, win->w); XSelectInput(dpy, win->w, WIN_EVENTS); /* * Ensure that the window doesn't have the focused border color. This * is needed by remove_frame and possibly others. */ XSetWindowBorder(dpy, win->w, rp_glob_screen.bwcolor); set_state(win, IconicState); } void unhide_window(rp_window * win) { if (win == NULL) return; /* Always raise the window. */ XRaiseWindow(dpy, win->w); if (win->state != IconicState) return; XMapWindow(dpy, win->w); set_state(win, NormalState); } void unhide_all_windows(void) { struct list_head *tmp, *iter; rp_window *win; list_for_each_safe_entry(win, iter, tmp, &rp_mapped_window, node) unhide_window(win); } void withdraw_window(rp_window *win) { if (win == NULL) return; PRINT_DEBUG(("withdraw_window on '%s'\n", window_name(win))); if (win->full_screen) window_full_screen(NULL); /* * Give back the window number. the window will get another one, if it is * remapped. */ if (win->number == -1) warnx("attempting to withdraw '%s' with number -1!", window_name(win)); numset_release(rp_window_numset, win->number); win->number = -1; list_move_tail(&win->node, &rp_unmapped_window); /* Update the vscreens. */ vscreen_unmap_window(win->vscreen, win); ignore_badwindow++; XRemoveFromSaveSet(dpy, win->w); set_state(win, WithdrawnState); XSync(dpy, False); ignore_badwindow--; /* Call our hook */ hook_run(&rp_delete_window_hook); } /* Hide all other mapped windows except for win in win's frame. */ void hide_others(rp_window *win) { rp_frame *frame; rp_window *cur; if (win == NULL) return; frame = find_windows_frame(win); if (frame == NULL) return; list_for_each_entry(cur, &rp_mapped_window, node) { if (find_windows_frame(cur) || cur->state != NormalState || cur->frame_number != frame->number) continue; hide_window(cur); } } /* Hide any window displayed on the given screen */ void hide_vscreen_windows(rp_vscreen *v) { rp_window *cur; list_for_each_entry(cur, &rp_mapped_window, node) if (cur->vscreen == v) hide_window(cur); } void raise_utility_windows(void) { unsigned int i, nwins; Window dw1, dw2, *wins; XQueryTree(dpy, rp_glob_screen.root, &dw1, &dw2, &wins, &nwins); for (i = 0; i < nwins; i++) { if (!is_rp_window(wins[i]) && is_unmanaged_window_type(wins[i])) XRaiseWindow(dpy, wins[i]); } XFree(wins); }