| 1 | /* evilwm - Minimalist Window Manager for X |
|---|
| 2 | * Copyright (C) 1999-2006 Ciaran Anscomb <evilwm@6809.org.uk> |
|---|
| 3 | * see README for license and other details. */ |
|---|
| 4 | |
|---|
| 5 | #include <stdlib.h> |
|---|
| 6 | #include <string.h> |
|---|
| 7 | #include <stdio.h> |
|---|
| 8 | #include "evilwm.h" |
|---|
| 9 | #include "log.h" |
|---|
| 10 | |
|---|
| 11 | #define MAXIMUM_PROPERTY_LENGTH 4096 |
|---|
| 12 | |
|---|
| 13 | static void init_geometry(Client *c); |
|---|
| 14 | static void reparent(Client *c); |
|---|
| 15 | static void *get_property(Window w, Atom property, Atom req_type, |
|---|
| 16 | unsigned long *nitems_return); |
|---|
| 17 | #ifdef XDEBUG |
|---|
| 18 | static const char *map_state_string(int map_state); |
|---|
| 19 | static const char *gravity_string(int gravity); |
|---|
| 20 | static void debug_wm_normal_hints(XSizeHints *size); |
|---|
| 21 | #else |
|---|
| 22 | # define debug_wm_normal_hints(s) |
|---|
| 23 | #endif |
|---|
| 24 | |
|---|
| 25 | void make_new_client(Window w, ScreenInfo *s) { |
|---|
| 26 | Client *c; |
|---|
| 27 | char *name; |
|---|
| 28 | XClassHint *class; |
|---|
| 29 | |
|---|
| 30 | XGrabServer(dpy); |
|---|
| 31 | |
|---|
| 32 | /* First a bit of interaction with the error handler due to X's |
|---|
| 33 | * tendency to batch event notifications. We set a global variable to |
|---|
| 34 | * the id of the window we're initialising then do simple X call on |
|---|
| 35 | * that window. If an error is raised by this (and nothing else should |
|---|
| 36 | * do so as we've grabbed the server), the error handler resets the |
|---|
| 37 | * variable indicating the window has already disappeared, so we stop |
|---|
| 38 | * trying to manage it. */ |
|---|
| 39 | initialising = w; |
|---|
| 40 | XFetchName(dpy, w, &name); |
|---|
| 41 | XSync(dpy, False); |
|---|
| 42 | /* If 'initialising' is now set to None, that means doing the |
|---|
| 43 | * XFetchName raised BadWindow - the window has been removed before |
|---|
| 44 | * we got a chance to grab the server. */ |
|---|
| 45 | if (initialising == None) { |
|---|
| 46 | LOG_DEBUG("make_new_client() : XError occurred for initialising window - aborting...\n"); |
|---|
| 47 | XUngrabServer(dpy); |
|---|
| 48 | return; |
|---|
| 49 | } |
|---|
| 50 | initialising = None; |
|---|
| 51 | LOG_DEBUG("make_new_client(): %s\n", name ? name : "Untitled"); |
|---|
| 52 | if (name) |
|---|
| 53 | XFree(name); |
|---|
| 54 | |
|---|
| 55 | c = malloc(sizeof(Client)); |
|---|
| 56 | /* Don't crash the window manager, just fail the operation. */ |
|---|
| 57 | if (!c) { |
|---|
| 58 | LOG_ERROR("out of memory in new_client; limping onward\n"); |
|---|
| 59 | return; |
|---|
| 60 | } |
|---|
| 61 | c->next = head_client; |
|---|
| 62 | c->dict = PyDict_New(); |
|---|
| 63 | head_client = c; |
|---|
| 64 | |
|---|
| 65 | c->screen = s; |
|---|
| 66 | c->window = w; |
|---|
| 67 | c->ignore_unmap = 0; |
|---|
| 68 | c->remove = 0; |
|---|
| 69 | |
|---|
| 70 | /* Ungrab the X server as soon as possible. Now that the client is |
|---|
| 71 | * malloc()ed and attached to the list, it is safe for any subsequent |
|---|
| 72 | * X calls to raise an X error and thus flag it for removal. */ |
|---|
| 73 | XUngrabServer(dpy); |
|---|
| 74 | |
|---|
| 75 | c->border = opt_bw; |
|---|
| 76 | |
|---|
| 77 | init_geometry(c); |
|---|
| 78 | |
|---|
| 79 | #ifdef DEBUG |
|---|
| 80 | { |
|---|
| 81 | Client *p; |
|---|
| 82 | int i = 0; |
|---|
| 83 | for (p = head_client; p; p = p->next) |
|---|
| 84 | i++; |
|---|
| 85 | LOG_DEBUG("make_new_client() : new window %dx%d+%d+%d, wincount=%d\n", c->width, c->height, c->x, c->y, i); |
|---|
| 86 | } |
|---|
| 87 | #endif |
|---|
| 88 | |
|---|
| 89 | #ifdef COLOURMAP |
|---|
| 90 | XSelectInput(dpy, c->window, ColormapChangeMask | EnterWindowMask | PropertyChangeMask); |
|---|
| 91 | #else |
|---|
| 92 | XSelectInput(dpy, c->window, EnterWindowMask | PropertyChangeMask); |
|---|
| 93 | #endif |
|---|
| 94 | |
|---|
| 95 | reparent(c); |
|---|
| 96 | |
|---|
| 97 | #ifdef SHAPE |
|---|
| 98 | if (have_shape) { |
|---|
| 99 | XShapeSelectInput(dpy, c->window, ShapeNotifyMask); |
|---|
| 100 | set_shape(c); |
|---|
| 101 | } |
|---|
| 102 | #endif |
|---|
| 103 | |
|---|
| 104 | /* Read instance/class information for client and check against list |
|---|
| 105 | * built with -app options */ |
|---|
| 106 | class = XAllocClassHint(); |
|---|
| 107 | if (class) { |
|---|
| 108 | Application *a = head_app; |
|---|
| 109 | XGetClassHint(dpy, w, class); |
|---|
| 110 | while (a) { |
|---|
| 111 | if ((!a->res_name || (class->res_name && !strcmp(class->res_name, a->res_name))) |
|---|
| 112 | && (!a->res_class || (class->res_class && !strcmp(class->res_class, a->res_class)))) { |
|---|
| 113 | if (a->geometry_mask & WidthValue) |
|---|
| 114 | c->width = a->width * c->width_inc; |
|---|
| 115 | if (a->geometry_mask & HeightValue) |
|---|
| 116 | c->height = a->height * c->height_inc; |
|---|
| 117 | if (a->geometry_mask & XValue) { |
|---|
| 118 | if (a->geometry_mask & XNegative) |
|---|
| 119 | c->x = a->x + DisplayWidth(dpy, s->screen)-c->width-c->border; |
|---|
| 120 | else |
|---|
| 121 | c->x = a->x + c->border; |
|---|
| 122 | } |
|---|
| 123 | if (a->geometry_mask & YValue) { |
|---|
| 124 | if (a->geometry_mask & YNegative) |
|---|
| 125 | c->y = a->y + DisplayHeight(dpy, s->screen)-c->height-c->border; |
|---|
| 126 | else |
|---|
| 127 | c->y = a->y + c->border; |
|---|
| 128 | } |
|---|
| 129 | moveresize(c); |
|---|
| 130 | #ifdef VWM |
|---|
| 131 | if (a->vdesk != -1) c->vdesk = a->vdesk; |
|---|
| 132 | c->sticky = a->sticky; |
|---|
| 133 | #endif |
|---|
| 134 | } |
|---|
| 135 | a = a->next; |
|---|
| 136 | } |
|---|
| 137 | XFree(class->res_name); |
|---|
| 138 | XFree(class->res_class); |
|---|
| 139 | XFree(class); |
|---|
| 140 | } |
|---|
| 141 | |
|---|
| 142 | /* Only map the window frame (and thus the window) if it's supposed |
|---|
| 143 | * to be visible on this virtual desktop. */ |
|---|
| 144 | #ifdef VWM |
|---|
| 145 | if (s->vdesk == c->vdesk) |
|---|
| 146 | #endif |
|---|
| 147 | { |
|---|
| 148 | unhide(c, RAISE); |
|---|
| 149 | #ifndef MOUSE |
|---|
| 150 | select_client(c); |
|---|
| 151 | setmouse(c->window, c->width + c->border - 1, |
|---|
| 152 | c->height + c->border - 1); |
|---|
| 153 | discard_enter_events(); |
|---|
| 154 | #endif |
|---|
| 155 | } |
|---|
| 156 | #ifdef VWM |
|---|
| 157 | else { |
|---|
| 158 | set_wm_state(c, IconicState); |
|---|
| 159 | } |
|---|
| 160 | update_net_wm_desktop(c); |
|---|
| 161 | #endif |
|---|
| 162 | } |
|---|
| 163 | |
|---|
| 164 | /* Calls XGetWindowAttributes, XGetWMHints and XGetWMNormalHints to determine |
|---|
| 165 | * window's initial geometry. |
|---|
| 166 | * |
|---|
| 167 | * XGetWindowAttributes |
|---|
| 168 | */ |
|---|
| 169 | static void init_geometry(Client *c) { |
|---|
| 170 | long size_flags; |
|---|
| 171 | XWindowAttributes attr; |
|---|
| 172 | unsigned long nitems; |
|---|
| 173 | PropMwmHints *mprop; |
|---|
| 174 | #ifdef VWM |
|---|
| 175 | unsigned long i; |
|---|
| 176 | unsigned long *lprop; |
|---|
| 177 | Atom *aprop; |
|---|
| 178 | #endif |
|---|
| 179 | |
|---|
| 180 | if ( (mprop = get_property(c->window, mwm_hints, mwm_hints, &nitems)) ) { |
|---|
| 181 | if (nitems >= PROP_MWM_HINTS_ELEMENTS |
|---|
| 182 | && (mprop->flags & MWM_HINTS_DECORATIONS) |
|---|
| 183 | && !(mprop->decorations & MWM_DECOR_ALL) |
|---|
| 184 | && !(mprop->decorations & MWM_DECOR_BORDER)) { |
|---|
| 185 | c->border = 0; |
|---|
| 186 | } |
|---|
| 187 | XFree(mprop); |
|---|
| 188 | } |
|---|
| 189 | |
|---|
| 190 | #ifdef VWM |
|---|
| 191 | c->vdesk = c->screen->vdesk; |
|---|
| 192 | if ( (lprop = get_property(c->window, xa_net_wm_desktop, XA_CARDINAL, &nitems)) ) { |
|---|
| 193 | if (nitems && valid_vdesk(lprop[0])) |
|---|
| 194 | c->vdesk = lprop[0]; |
|---|
| 195 | XFree(lprop); |
|---|
| 196 | } |
|---|
| 197 | remove_sticky(c); |
|---|
| 198 | if ( (aprop = get_property(c->window, xa_net_wm_state, XA_ATOM, &nitems)) ) { |
|---|
| 199 | for (i = 0; i < nitems; i++) { |
|---|
| 200 | if (aprop[i] == xa_net_wm_state_sticky) |
|---|
| 201 | add_sticky(c); |
|---|
| 202 | } |
|---|
| 203 | XFree(aprop); |
|---|
| 204 | } |
|---|
| 205 | #endif |
|---|
| 206 | |
|---|
| 207 | /* Get current window attributes */ |
|---|
| 208 | LOG_XDEBUG("XGetWindowAttributes()\n"); |
|---|
| 209 | XGetWindowAttributes(dpy, c->window, &attr); |
|---|
| 210 | LOG_XDEBUG("\t(%s) %dx%d+%d+%d, bw = %d\n", map_state_string(attr.map_state), attr.width, attr.height, attr.x, attr.y, attr.border_width); |
|---|
| 211 | c->old_border = attr.border_width; |
|---|
| 212 | c->oldw = c->oldh = 0; |
|---|
| 213 | #ifdef COLOURMAP |
|---|
| 214 | c->cmap = attr.colormap; |
|---|
| 215 | #endif |
|---|
| 216 | |
|---|
| 217 | size_flags = get_wm_normal_hints(c); |
|---|
| 218 | |
|---|
| 219 | if ((attr.width >= c->min_width) && (attr.height >= c->min_height)) { |
|---|
| 220 | /* if (attr.map_state == IsViewable || (size_flags & (PSize | USSize))) { */ |
|---|
| 221 | c->width = attr.width; |
|---|
| 222 | c->height = attr.height; |
|---|
| 223 | } else { |
|---|
| 224 | c->width = c->min_width; |
|---|
| 225 | c->height = c->min_height; |
|---|
| 226 | send_config(c); |
|---|
| 227 | } |
|---|
| 228 | if ((attr.map_state == IsViewable) |
|---|
| 229 | || (size_flags & (/*PPosition |*/ USPosition))) { |
|---|
| 230 | c->x = attr.x; |
|---|
| 231 | c->y = attr.y; |
|---|
| 232 | } else { |
|---|
| 233 | #ifdef MOUSE |
|---|
| 234 | int xmax = DisplayWidth(dpy, c->screen->screen); |
|---|
| 235 | int ymax = DisplayHeight(dpy, c->screen->screen); |
|---|
| 236 | int x, y; |
|---|
| 237 | get_mouse_position(&x, &y, c->screen->root); |
|---|
| 238 | c->x = (x * (xmax - c->border - c->width)) / xmax; |
|---|
| 239 | c->y = (y * (ymax - c->border - c->height)) / ymax; |
|---|
| 240 | #else |
|---|
| 241 | c->x = c->y = c->border; |
|---|
| 242 | #endif |
|---|
| 243 | send_config(c); |
|---|
| 244 | } |
|---|
| 245 | |
|---|
| 246 | LOG_DEBUG("\twindow started as %dx%d +%d+%d\n", c->width, c->height, c->x, c->y); |
|---|
| 247 | if (attr.map_state == IsViewable) { |
|---|
| 248 | /* The reparent that is to come would trigger an unmap event */ |
|---|
| 249 | c->ignore_unmap++; |
|---|
| 250 | } |
|---|
| 251 | gravitate(c); |
|---|
| 252 | run_geometry_hook(c); |
|---|
| 253 | } |
|---|
| 254 | |
|---|
| 255 | static void reparent(Client *c) { |
|---|
| 256 | XSetWindowAttributes p_attr; |
|---|
| 257 | |
|---|
| 258 | p_attr.border_pixel = c->screen->bg.pixel; |
|---|
| 259 | p_attr.override_redirect = True; |
|---|
| 260 | p_attr.event_mask = ChildMask | ButtonPressMask | EnterWindowMask; |
|---|
| 261 | c->parent = XCreateWindow(dpy, c->screen->root, c->x-c->border, c->y-c->border, |
|---|
| 262 | c->width, c->height, c->border, |
|---|
| 263 | DefaultDepth(dpy, c->screen->screen), CopyFromParent, |
|---|
| 264 | DefaultVisual(dpy, c->screen->screen), |
|---|
| 265 | CWOverrideRedirect | CWBorderPixel | CWEventMask, &p_attr); |
|---|
| 266 | |
|---|
| 267 | XAddToSaveSet(dpy, c->window); |
|---|
| 268 | XSetWindowBorderWidth(dpy, c->window, 0); |
|---|
| 269 | XReparentWindow(dpy, c->window, c->parent, 0, 0); |
|---|
| 270 | XMapWindow(dpy, c->window); |
|---|
| 271 | #ifdef MOUSE |
|---|
| 272 | grab_button(c->parent, grabmask2, AnyButton); |
|---|
| 273 | #endif |
|---|
| 274 | } |
|---|
| 275 | |
|---|
| 276 | /* Get WM_NORMAL_HINTS property */ |
|---|
| 277 | long get_wm_normal_hints(Client *c) { |
|---|
| 278 | XSizeHints *size; |
|---|
| 279 | long flags; |
|---|
| 280 | long dummy; |
|---|
| 281 | size = XAllocSizeHints(); |
|---|
| 282 | LOG_XDEBUG("XGetWMNormalHints()\n"); |
|---|
| 283 | XGetWMNormalHints(dpy, c->window, size, &dummy); |
|---|
| 284 | debug_wm_normal_hints(size); |
|---|
| 285 | flags = size->flags; |
|---|
| 286 | if (flags & PMinSize) { |
|---|
| 287 | c->min_width = size->min_width; |
|---|
| 288 | c->min_height = size->min_height; |
|---|
| 289 | } else { |
|---|
| 290 | c->min_width = c->min_height = 0; |
|---|
| 291 | } |
|---|
| 292 | if (flags & PMaxSize) { |
|---|
| 293 | c->max_width = size->max_width; |
|---|
| 294 | c->max_height = size->max_height; |
|---|
| 295 | } else { |
|---|
| 296 | c->max_width = c->max_height = 0; |
|---|
| 297 | } |
|---|
| 298 | if (flags & PBaseSize) { |
|---|
| 299 | c->base_width = size->base_width; |
|---|
| 300 | c->base_height = size->base_height; |
|---|
| 301 | } else { |
|---|
| 302 | c->base_width = c->min_width; |
|---|
| 303 | c->base_height = c->min_height; |
|---|
| 304 | } |
|---|
| 305 | c->width_inc = c->height_inc = 1; |
|---|
| 306 | if (flags & PResizeInc) { |
|---|
| 307 | c->width_inc = size->width_inc ? size->width_inc : 1; |
|---|
| 308 | c->height_inc = size->height_inc ? size->height_inc : 1; |
|---|
| 309 | } |
|---|
| 310 | if (!(flags & PMinSize)) { |
|---|
| 311 | c->min_width = c->base_width + c->width_inc; |
|---|
| 312 | c->min_height = c->base_height + c->height_inc; |
|---|
| 313 | } |
|---|
| 314 | if (flags & PWinGravity) { |
|---|
| 315 | c->win_gravity = size->win_gravity; |
|---|
| 316 | } else { |
|---|
| 317 | c->win_gravity = NorthWestGravity; |
|---|
| 318 | } |
|---|
| 319 | XFree(size); |
|---|
| 320 | return flags; |
|---|
| 321 | } |
|---|
| 322 | |
|---|
| 323 | static void *get_property(Window w, Atom property, Atom req_type, |
|---|
| 324 | unsigned long *nitems_return) { |
|---|
| 325 | Atom actual_type; |
|---|
| 326 | int actual_format; |
|---|
| 327 | unsigned long bytes_after; |
|---|
| 328 | unsigned char *prop; |
|---|
| 329 | if (XGetWindowProperty(dpy, w, property, |
|---|
| 330 | 0L, MAXIMUM_PROPERTY_LENGTH / 4, False, |
|---|
| 331 | req_type, &actual_type, &actual_format, |
|---|
| 332 | nitems_return, &bytes_after, &prop) |
|---|
| 333 | == Success) { |
|---|
| 334 | if (actual_type == req_type) |
|---|
| 335 | return (void *)prop; |
|---|
| 336 | XFree(prop); |
|---|
| 337 | } |
|---|
| 338 | return NULL; |
|---|
| 339 | } |
|---|
| 340 | |
|---|
| 341 | #ifdef XDEBUG |
|---|
| 342 | static const char *map_state_string(int map_state) { |
|---|
| 343 | const char *map_states[4] = { |
|---|
| 344 | "IsUnmapped", |
|---|
| 345 | "IsUnviewable", |
|---|
| 346 | "IsViewable", |
|---|
| 347 | "Unknown" |
|---|
| 348 | }; |
|---|
| 349 | return ((unsigned int)map_state < 3) |
|---|
| 350 | ? map_states[map_state] |
|---|
| 351 | : map_states[3]; |
|---|
| 352 | } |
|---|
| 353 | |
|---|
| 354 | static const char *gravity_string(int gravity) { |
|---|
| 355 | const char *gravities[12] = { |
|---|
| 356 | "ForgetGravity", |
|---|
| 357 | "NorthWestGravity", |
|---|
| 358 | "NorthGravity", |
|---|
| 359 | "NorthEastGravity", |
|---|
| 360 | "WestGravity", |
|---|
| 361 | "CenterGravity", |
|---|
| 362 | "EastGravity", |
|---|
| 363 | "SouthWestGravity", |
|---|
| 364 | "SouthGravity", |
|---|
| 365 | "SouthEastGravity", |
|---|
| 366 | "StaticGravity", |
|---|
| 367 | "Unknown" |
|---|
| 368 | }; |
|---|
| 369 | return ((unsigned int)gravity < 11) ? gravities[gravity] : gravities[11]; |
|---|
| 370 | } |
|---|
| 371 | |
|---|
| 372 | static void debug_wm_normal_hints(XSizeHints *size) { |
|---|
| 373 | if (size->flags & 15) { |
|---|
| 374 | LOG_XDEBUG("\t"); |
|---|
| 375 | if (size->flags & USPosition) { |
|---|
| 376 | LOG_XDEBUG("USPosition "); |
|---|
| 377 | } |
|---|
| 378 | if (size->flags & USSize) { |
|---|
| 379 | LOG_XDEBUG("USSize "); |
|---|
| 380 | } |
|---|
| 381 | if (size->flags & PPosition) { |
|---|
| 382 | LOG_XDEBUG("PPosition "); |
|---|
| 383 | } |
|---|
| 384 | if (size->flags & PSize) { |
|---|
| 385 | LOG_XDEBUG("PSize"); |
|---|
| 386 | } |
|---|
| 387 | LOG_XDEBUG("\n"); |
|---|
| 388 | } |
|---|
| 389 | if (size->flags & PMinSize) { |
|---|
| 390 | LOG_XDEBUG("\tPMinSize: min_width = %d, min_height = %d\n", size->min_width, size->min_height); |
|---|
| 391 | } |
|---|
| 392 | if (size->flags & PMaxSize) { |
|---|
| 393 | LOG_XDEBUG("\tPMaxSize: max_width = %d, max_height = %d\n", size->max_width, size->max_height); |
|---|
| 394 | } |
|---|
| 395 | if (size->flags & PResizeInc) { |
|---|
| 396 | LOG_XDEBUG("\tPResizeInc: width_inc = %d, height_inc = %d\n", |
|---|
| 397 | size->width_inc, size->height_inc); |
|---|
| 398 | } |
|---|
| 399 | if (size->flags & PAspect) { |
|---|
| 400 | LOG_XDEBUG("\tPAspect: min_aspect = %d/%d, max_aspect = %d/%d\n", |
|---|
| 401 | size->min_aspect.x, size->min_aspect.y, |
|---|
| 402 | size->max_aspect.x, size->max_aspect.y); |
|---|
| 403 | } |
|---|
| 404 | if (size->flags & PBaseSize) { |
|---|
| 405 | LOG_XDEBUG("\tPBaseSize: base_width = %d, base_height = %d\n", |
|---|
| 406 | size->base_width, size->base_height); |
|---|
| 407 | } |
|---|
| 408 | if (size->flags & PWinGravity) { |
|---|
| 409 | LOG_XDEBUG("\tPWinGravity: %s\n", gravity_string(size->win_gravity)); |
|---|
| 410 | } |
|---|
| 411 | } |
|---|
| 412 | #endif |
|---|