556 lines
13 KiB
C
556 lines
13 KiB
C
/*
|
|
* Window management.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2019 Derek Stevens, 2005 Russ Cox, 1994-1996 David Hogan
|
|
* see README for licence details
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
#include <X11/X.h>
|
|
#include <X11/Xos.h>
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/Xatom.h>
|
|
#include <X11/extensions/shape.h>
|
|
#include "config.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
|
|
int isNew;
|
|
|
|
int manage(Client* c, int mapped) {
|
|
int fixsize, dohide, doreshape, state;
|
|
long msize;
|
|
XClassHint class;
|
|
XWMHints* hints;
|
|
XSetWindowAttributes attrs;
|
|
|
|
static XWindowAttributes ra;
|
|
XGetWindowAttributes(dpy, DefaultRootWindow(dpy), &ra);
|
|
|
|
trace("manage", c, 0);
|
|
XSelectInput(
|
|
dpy,
|
|
c->window,
|
|
ColormapChangeMask | EnterWindowMask | PropertyChangeMask |
|
|
FocusChangeMask | KeyPressMask);
|
|
|
|
/* Get loads of hints */
|
|
|
|
if (XGetClassHint(dpy, c->window, &class) != 0) { /* ``Success'' */
|
|
c->instance = class.res_name;
|
|
c->class = class.res_class;
|
|
c->is9term = 0;
|
|
if (isNew) {
|
|
c->is9term = strstr(c->class, "term") || strstr(c->class, "Term") ||
|
|
strstr(c->class, "urxvt") || strstr(c->class, "URxvt") ||
|
|
strstr(c->class, "onsole") || strstr(c->class, "Alacritty");
|
|
isNew = 0;
|
|
}
|
|
} else {
|
|
c->instance = 0;
|
|
c->class = 0;
|
|
c->is9term = 0;
|
|
}
|
|
c->iconname = getprop(c->window, XA_WM_ICON_NAME);
|
|
c->name = getprop(c->window, XA_WM_NAME);
|
|
setlabel(c);
|
|
|
|
hints = XGetWMHints(dpy, c->window);
|
|
if (
|
|
XGetWMNormalHints(dpy, c->window, &c->size, &msize) == 0 ||
|
|
c->size.flags == 0)
|
|
c->size.flags = PSize; /* not specified - punt */
|
|
|
|
getcmaps(c);
|
|
getproto(c);
|
|
gettrans(c);
|
|
if (c->is9term)
|
|
c->hold = getiprop(c->window, _rio_hold_mode);
|
|
|
|
/* Figure out what to do with the window from hints */
|
|
|
|
if (!getstate(c->window, &state))
|
|
state = hints ? hints->initial_state : NormalState;
|
|
dohide = (state == IconicState);
|
|
|
|
fixsize = 0;
|
|
if ((c->size.flags & (USSize | PSize)))
|
|
fixsize = 1;
|
|
if (
|
|
(c->size.flags & (PMinSize | PMaxSize)) == (PMinSize | PMaxSize) &&
|
|
c->size.min_width == c->size.max_width &&
|
|
c->size.min_height == c->size.max_height)
|
|
fixsize = 1;
|
|
doreshape = !mapped;
|
|
if (fixsize) {
|
|
if (c->size.flags & USPosition)
|
|
doreshape = 0;
|
|
if (dohide && (c->size.flags & PPosition))
|
|
doreshape = 0;
|
|
if (c->trans != None)
|
|
doreshape = 0;
|
|
}
|
|
if (c->is9term)
|
|
fixsize = 0;
|
|
if (c->size.flags & PBaseSize) {
|
|
c->min_dx = c->size.base_width;
|
|
c->min_dy = c->size.base_height;
|
|
} else if (c->size.flags & PMinSize) {
|
|
c->min_dx = c->size.min_width;
|
|
c->min_dy = c->size.min_height;
|
|
} else if (c->is9term) {
|
|
c->min_dx = 100;
|
|
c->min_dy = 50;
|
|
} else
|
|
c->min_dx = c->min_dy = 0;
|
|
|
|
if (hints)
|
|
XFree(hints);
|
|
|
|
/* Now do it!!! */
|
|
|
|
if (doreshape) {
|
|
if (0)
|
|
fprintf(
|
|
stderr,
|
|
"in doreshape is9term=%d fixsize=%d, x=%d, y=%d, min_dx=%d, min_dy=%d, "
|
|
"dx=%d, dy=%d\n",
|
|
c->is9term,
|
|
fixsize,
|
|
c->x,
|
|
c->y,
|
|
c->min_dx,
|
|
c->min_dy,
|
|
c->dx,
|
|
c->dy);
|
|
if (current && current->screen == c->screen)
|
|
cmapnofocus(c->screen);
|
|
if (!c->is9term && c->x == 0 && c->y == 0) {
|
|
static int nwin;
|
|
|
|
c->x = 20 * nwin + BORDER;
|
|
c->y = 20 * nwin + BORDER;
|
|
nwin++;
|
|
nwin %= 10;
|
|
}
|
|
|
|
if (c->is9term && !(fixsize ? drag(c, Button3) : sweep(c, Button3))) {
|
|
XKillClient(dpy, c->window);
|
|
rmclient(c);
|
|
if (current && current->screen == c->screen)
|
|
cmapfocus(current);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
attrs.border_pixel = c->screen->black;
|
|
attrs.background_pixel = c->screen->white;
|
|
attrs.colormap = c->screen->def_cmap;
|
|
c->parent = XCreateWindow(
|
|
dpy,
|
|
c->screen->root,
|
|
c->x - BORDER,
|
|
c->y - BORDER,
|
|
c->dx + 2 * BORDER,
|
|
c->dy + 2 * BORDER,
|
|
0,
|
|
c->screen->depth,
|
|
CopyFromParent,
|
|
c->screen->vis,
|
|
CWBackPixel | CWBorderPixel | CWColormap,
|
|
&attrs);
|
|
|
|
XSelectInput(
|
|
dpy,
|
|
c->parent,
|
|
SubstructureRedirectMask | SubstructureNotifyMask | ButtonPressMask |
|
|
PointerMotionMask | LeaveWindowMask | KeyPressMask);
|
|
if (mapped)
|
|
c->reparenting = 1;
|
|
if (doreshape && !fixsize)
|
|
XResizeWindow(dpy, c->window, c->dx, c->dy);
|
|
XSetWindowBorderWidth(dpy, c->window, 0);
|
|
|
|
/*
|
|
* To have something more than only a big white or black border
|
|
* XXX should replace this by a pattern in the white or black
|
|
* such that we can see the border also if all our
|
|
* windows are black and/or white
|
|
* (black (or white) border around black (or white) window
|
|
* is not very helpful.
|
|
*/
|
|
if (c->screen->depth <= 8) {
|
|
XSetWindowBorderWidth(dpy, c->parent, 1);
|
|
}
|
|
|
|
XReparentWindow(dpy, c->window, c->parent, BORDER, BORDER);
|
|
#ifdef SHAPE
|
|
if (shape) {
|
|
XShapeSelectInput(dpy, c->window, ShapeNotifyMask);
|
|
ignore_badwindow = 1; /* magic */
|
|
setshape(c);
|
|
ignore_badwindow = 0;
|
|
}
|
|
#endif
|
|
XAddToSaveSet(dpy, c->window);
|
|
if (dohide)
|
|
hide(c);
|
|
else {
|
|
XMapWindow(dpy, c->window);
|
|
XMapWindow(dpy, c->parent);
|
|
XUnmapWindow(dpy, c->screen->sweepwin);
|
|
#ifdef AUTOSTICK
|
|
if (!isautostick(c))
|
|
#endif
|
|
active(c);
|
|
/*else if(c->trans != None && current && current->window == c->trans)
|
|
active(c);*/
|
|
else
|
|
setactive(c, 0);
|
|
setstate(c, NormalState);
|
|
}
|
|
if (current && (current != c))
|
|
cmapfocus(current);
|
|
c->init = 1;
|
|
|
|
/* If we are in zoom mode, spawn the window maximized */
|
|
if (zoom) {
|
|
quickreshape(
|
|
c,
|
|
-BORDER,
|
|
-BORDER,
|
|
ra.width + 2 * BORDER,
|
|
ra.height + 2 * BORDER);
|
|
}
|
|
|
|
/* If the window is out of bounds of the screen, try to wrangle it */
|
|
|
|
/* If it's bigger than the screen, try to set it maximized */
|
|
if (c->dx >= ra.width || c->dy >= ra.width) {
|
|
if (c->dx >= ra.width)
|
|
quickreshape(
|
|
c,
|
|
-BORDER,
|
|
c->y - BORDER,
|
|
ra.width + 2 * BORDER,
|
|
c->dy + 2 * BORDER);
|
|
if (c->dy >= ra.height)
|
|
quickreshape(
|
|
c,
|
|
c->x - BORDER,
|
|
-BORDER,
|
|
c->dx + 2 * BORDER,
|
|
ra.height + 2 * BORDER);
|
|
|
|
/* and if it's got an edge out of bounds, nudge it into bounds */
|
|
} else {
|
|
if (c->x < BORDER) {
|
|
quickreshape(c, 0, c->y - BORDER, c->dx + 2 * BORDER, c->dy + 2 * BORDER);
|
|
}
|
|
if (c->y < BORDER) {
|
|
quickreshape(c, c->x - BORDER, 0, c->dx + 2 * BORDER, c->dy + 2 * BORDER);
|
|
}
|
|
if (c->x + c->dx + BORDER > ra.width) {
|
|
quickreshape(
|
|
c,
|
|
ra.width - (c->dx + 2 * BORDER),
|
|
c->y - BORDER,
|
|
c->dx + 2 * BORDER,
|
|
c->dy + 2 * BORDER);
|
|
}
|
|
if (c->y + c->dy + BORDER > ra.height) {
|
|
quickreshape(
|
|
c,
|
|
c->x - BORDER,
|
|
ra.height - (c->dy + 2 * BORDER),
|
|
c->dx + 2 * BORDER,
|
|
c->dy + 2 * BORDER);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we swept the window, let's send a resize event to the
|
|
* guy who just got resized. It's not clear whether the apps
|
|
* should notice their new size via other means. Try as I might,
|
|
* I can't find a way to have them notice during initdraw, so
|
|
* I solve the problem this way instead. -rsc
|
|
*/
|
|
if (c->is9term)
|
|
sendconfig(c);
|
|
return 1;
|
|
}
|
|
|
|
void scanwins(ScreenInfo* s) {
|
|
unsigned int i, nwins;
|
|
Client* c;
|
|
Window dw1, dw2, *wins;
|
|
XWindowAttributes attr;
|
|
|
|
XQueryTree(dpy, s->root, &dw1, &dw2, &wins, &nwins);
|
|
for (i = 0; i < nwins; i++) {
|
|
XGetWindowAttributes(dpy, wins[i], &attr);
|
|
if (attr.override_redirect || wins[i] == s->menuwin)
|
|
continue;
|
|
c = getclient(wins[i], 1);
|
|
if (c != 0 && c->window == wins[i] && !c->init) {
|
|
c->x = attr.x;
|
|
c->y = attr.y;
|
|
c->dx = attr.width;
|
|
c->dy = attr.height;
|
|
c->border = attr.border_width;
|
|
c->screen = s;
|
|
c->parent = s->root;
|
|
if (attr.map_state == IsViewable)
|
|
manage(c, 1);
|
|
}
|
|
}
|
|
XFree((void*)wins); /* cast is to shut stoopid compiler up */
|
|
}
|
|
|
|
void gettrans(Client* c) {
|
|
Window trans;
|
|
|
|
trans = None;
|
|
if (XGetTransientForHint(dpy, c->window, &trans) != 0)
|
|
c->trans = trans;
|
|
else
|
|
c->trans = None;
|
|
}
|
|
|
|
void withdraw(Client* c) {
|
|
XUnmapWindow(dpy, c->parent);
|
|
XReparentWindow(dpy, c->window, c->screen->root, c->x, c->y);
|
|
XRemoveFromSaveSet(dpy, c->window);
|
|
setstate(c, WithdrawnState);
|
|
|
|
/* flush any errors */
|
|
ignore_badwindow = 1;
|
|
XSync(dpy, False);
|
|
ignore_badwindow = 0;
|
|
}
|
|
|
|
static void installcmap(ScreenInfo* s, Colormap cmap) {
|
|
if (cmap == None)
|
|
XInstallColormap(dpy, s->def_cmap);
|
|
else
|
|
XInstallColormap(dpy, cmap);
|
|
}
|
|
|
|
void cmapfocus(Client* c) {
|
|
int i, found;
|
|
Client* cc;
|
|
|
|
if (c == 0)
|
|
return;
|
|
else if (c->ncmapwins != 0) {
|
|
found = 0;
|
|
for (i = c->ncmapwins - 1; i >= 0; i--) {
|
|
installcmap(c->screen, c->wmcmaps[i]);
|
|
if (c->cmapwins[i] == c->window)
|
|
found++;
|
|
}
|
|
if (!found)
|
|
installcmap(c->screen, c->cmap);
|
|
} else if (
|
|
c->trans != None && (cc = getclient(c->trans, 0)) != 0 &&
|
|
cc->ncmapwins != 0)
|
|
cmapfocus(cc);
|
|
else
|
|
installcmap(c->screen, c->cmap);
|
|
}
|
|
|
|
void cmapnofocus(ScreenInfo* s) { installcmap(s, None); }
|
|
|
|
void getcmaps(Client* c) {
|
|
int n, i;
|
|
Window* cw;
|
|
XWindowAttributes attr;
|
|
|
|
if (!c->init) {
|
|
ignore_badwindow = 1;
|
|
XGetWindowAttributes(dpy, c->window, &attr);
|
|
c->cmap = attr.colormap;
|
|
XSync(dpy, False);
|
|
ignore_badwindow = 0;
|
|
}
|
|
|
|
n = _getprop(c->window, wm_colormaps, XA_WINDOW, 100L, (void*)&cw);
|
|
if (c->ncmapwins != 0) {
|
|
XFree((char*)c->cmapwins);
|
|
free((char*)c->wmcmaps);
|
|
}
|
|
if (n <= 0) {
|
|
c->ncmapwins = 0;
|
|
return;
|
|
}
|
|
|
|
c->ncmapwins = n;
|
|
c->cmapwins = cw;
|
|
|
|
c->wmcmaps = (Colormap*)malloc(n * sizeof(Colormap));
|
|
for (i = 0; i < n; i++) {
|
|
if (cw[i] == c->window)
|
|
c->wmcmaps[i] = c->cmap;
|
|
else {
|
|
/* flush any errors (e.g., caused by mozilla tabs) */
|
|
ignore_badwindow = 1;
|
|
XSelectInput(dpy, cw[i], ColormapChangeMask);
|
|
XGetWindowAttributes(dpy, cw[i], &attr);
|
|
c->wmcmaps[i] = attr.colormap;
|
|
XSync(dpy, False);
|
|
ignore_badwindow = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void setlabel(Client* c) {
|
|
char *label, *p, *lc;
|
|
int i;
|
|
|
|
if (c->iconname != 0)
|
|
label = c->iconname;
|
|
else if (c->name != 0)
|
|
label = c->name;
|
|
else if (c->instance != 0)
|
|
label = c->instance;
|
|
else if (c->class != 0)
|
|
label = c->class;
|
|
else
|
|
label = "no label";
|
|
if ((p = index(label, ':')) != 0)
|
|
*p = '\0';
|
|
for (i = 0, lc = label; *lc != '\0'; lc++, i++) {
|
|
if (i >= 23) {
|
|
label[22] = '~';
|
|
label[23] = '\0';
|
|
}
|
|
}
|
|
|
|
c->label = label;
|
|
}
|
|
|
|
#ifdef SHAPE
|
|
void setshape(Client* c) {
|
|
int n, order;
|
|
XRectangle* rect;
|
|
|
|
/* don't try to add a border if the window is non-rectangular */
|
|
rect = XShapeGetRectangles(dpy, c->window, ShapeBounding, &n, &order);
|
|
if (n > 1)
|
|
XShapeCombineShape(
|
|
dpy,
|
|
c->parent,
|
|
ShapeBounding,
|
|
BORDER,
|
|
BORDER,
|
|
c->window,
|
|
ShapeBounding,
|
|
ShapeSet);
|
|
XFree((void*)rect);
|
|
}
|
|
#endif
|
|
|
|
int _getprop(Window w, Atom a, Atom type, long len, unsigned char** p) {
|
|
Atom real_type;
|
|
int format;
|
|
unsigned long n, extra;
|
|
int status;
|
|
|
|
status = XGetWindowProperty(
|
|
dpy,
|
|
w,
|
|
a,
|
|
0L,
|
|
len,
|
|
False,
|
|
type,
|
|
&real_type,
|
|
&format,
|
|
&n,
|
|
&extra,
|
|
p);
|
|
if (status != Success || *p == 0)
|
|
return -1;
|
|
if (n == 0)
|
|
XFree((void*)*p);
|
|
/* could check real_type, format, extra here... */
|
|
return n;
|
|
}
|
|
|
|
char* getprop(Window w, Atom a) {
|
|
unsigned char* p;
|
|
|
|
if (_getprop(w, a, XA_STRING, 100L, &p) <= 0)
|
|
return 0;
|
|
return (char*)p;
|
|
}
|
|
|
|
int get1prop(Window w, Atom a, Atom type) {
|
|
char **p, *x;
|
|
|
|
if (_getprop(w, a, type, 1L, (void*)&p) <= 0)
|
|
return 0;
|
|
x = *p;
|
|
XFree((void*)p);
|
|
return (int)(uintptr_t)x;
|
|
}
|
|
|
|
Window getwprop(Window w, Atom a) { return get1prop(w, a, XA_WINDOW); }
|
|
|
|
int getiprop(Window w, Atom a) { return get1prop(w, a, XA_INTEGER); }
|
|
|
|
void setstate(Client* c, int state) {
|
|
long data[2];
|
|
|
|
data[0] = (long)state;
|
|
data[1] = (long)None;
|
|
|
|
c->state = state;
|
|
XChangeProperty(
|
|
dpy,
|
|
c->window,
|
|
wm_state,
|
|
wm_state,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char*)data,
|
|
2);
|
|
}
|
|
|
|
int getstate(Window w, int* state) {
|
|
long* p = 0;
|
|
|
|
if (_getprop(w, wm_state, wm_state, 2L, (void*)&p) <= 0)
|
|
return 0;
|
|
|
|
*state = (int)*p;
|
|
XFree((char*)p);
|
|
return 1;
|
|
}
|
|
|
|
void getproto(Client* c) {
|
|
Atom* p;
|
|
int i;
|
|
long n;
|
|
Window w;
|
|
|
|
w = c->window;
|
|
c->proto = 0;
|
|
if ((n = _getprop(w, wm_protocols, XA_ATOM, 20L, (void*)&p)) <= 0)
|
|
return;
|
|
|
|
for (i = 0; i < n; i++)
|
|
if (p[i] == wm_delete)
|
|
c->proto |= Pdelete;
|
|
else if (p[i] == wm_take_focus)
|
|
c->proto |= Ptakefocus;
|
|
else if (p[i] == wm_lose_focus)
|
|
c->proto |= Plosefocus;
|
|
|
|
XFree((char*)p);
|
|
}
|