commit 83137d7d3a2a5195ba64352961eb572dd6489901
parent 26b3d34aadd1baf01f7bd83b4ae666b88cbe7544
Author: Ivan Delalande <colona@ycc.fr>
Date: Sun, 9 Jul 2017 20:29:11 -0700
dwm: add the new cropwindows patch
Create cropped views of existing windows to optimize screen space.
XReparentWindow is fun!
Diffstat:
2 files changed, 354 insertions(+), 0 deletions(-)
diff --git a/dwm.suckless.org/patches/cropwindows.md b/dwm.suckless.org/patches/cropwindows.md
@@ -0,0 +1,42 @@
+cropwindows
+===========
+
+Description
+-----------
+
+Create cropped views of existing windows to display only part of them, typically
+to reclaim screen space from badly framed videos or programs and websites with
+terrible designs.
+
+Usage
+-----
+
+Look at the changes made to `config.def.h`: pass `1` to `resizemouse` to create
+a cropped window and to `movemouse` to move the underlying window in the crop.
+
+ { ClkClientWin, MODKEY|ShiftMask, Button1, movemouse, {.i = 1} },
+ { ClkClientWin, MODKEY|ShiftMask, Button3, resizemouse, {.i = 1} },
+
+Cropped windows are always in the floating state, use `togglefloating`
+(`mod-shift-space` by default) to uncrop and restore the underlying window to
+its original size and state.
+
+Download
+--------
+
+ * [dwm-cropwindows-20170709-ceac8c9.diff](dwm-cropwindows-20170709-ceac8c9.diff)
+
+Notes
+-----
+
+ * tested with a limited set of clients and use-cases, some X11 events are
+ probably not passed or handled correctly, report bugs if you see any,
+ * known bug: if you crop a window at the same time another window is unmapped,
+ there is a chance that dwm will lose control of the window you are cropping
+ and your only option will be to kill it (this is a pain to fix properly),
+ * improvements: investigate `xextproto/shape` for non-rectangular crops!
+
+Authors
+-------
+
+ * Ivan Delalande <colona@ycc.fr>
diff --git a/dwm.suckless.org/patches/dwm-cropwindows-20170709-ceac8c9.diff b/dwm.suckless.org/patches/dwm-cropwindows-20170709-ceac8c9.diff
@@ -0,0 +1,312 @@
+diff --git a/config.def.h b/config.def.h
+index a9ac303..a9797ac 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -105,8 +105,10 @@ static Button buttons[] = {
+ { ClkWinTitle, 0, Button2, zoom, {0} },
+ { ClkStatusText, 0, Button2, spawn, {.v = termcmd } },
+ { ClkClientWin, MODKEY, Button1, movemouse, {0} },
++ { ClkClientWin, MODKEY|ShiftMask, Button1, movemouse, {.i = 1} },
+ { ClkClientWin, MODKEY, Button2, togglefloating, {0} },
+ { ClkClientWin, MODKEY, Button3, resizemouse, {0} },
++ { ClkClientWin, MODKEY|ShiftMask, Button3, resizemouse, {.i = 1} },
+ { ClkTagBar, 0, Button1, view, {0} },
+ { ClkTagBar, 0, Button3, toggleview, {0} },
+ { ClkTagBar, MODKEY, Button1, tag, {0} },
+diff --git a/dwm.c b/dwm.c
+index a5ce993..e922ef6 100644
+--- a/dwm.c
++++ b/dwm.c
+@@ -98,6 +98,7 @@ struct Client {
+ Client *snext;
+ Monitor *mon;
+ Window win;
++ Client *crop;
+ };
+
+ typedef struct {
+@@ -276,6 +277,88 @@ static Window root, wmcheckwin;
+ struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; };
+
+ /* function implementations */
++Client *
++cropwintoclient(Window w)
++{
++ Client *c;
++ Monitor *m;
++
++ for (m = mons; m; m = m->next)
++ for (c = m->clients; c; c = c->next)
++ if (c->crop && c->crop->win == w)
++ return c;
++ return NULL;
++}
++
++void
++cropwindow(Client *c)
++{
++ int x, y;
++ XEvent ev;
++ XSetWindowAttributes wa = { .event_mask = SubstructureRedirectMask };
++
++ if (!getrootptr(&x, &y))
++ return;
++ if (!c->crop) {
++ c->crop = ecalloc(1, sizeof(Client));
++ memcpy(c->crop, c, sizeof(Client));
++ c->crop->crop = NULL;
++ c->crop->x = c->crop->y = c->crop->bw = 0;
++ c->basew = c->baseh = c->mina = c->maxa = 0;
++ c->maxw = c->maxh = c->incw = c->inch = 0;
++ c->minw = c->minh = 1;
++ if (!c->isfloating)
++ togglefloating(NULL);
++ c->win = XCreateWindow(dpy, root, x, y, 1, 1, c->bw,
++ 0, 0, 0, CWEventMask, &wa);
++ XReparentWindow(dpy, c->crop->win, c->win, 0, 0);
++ XMapWindow(dpy, c->win);
++ focus(c);
++ XCheckTypedWindowEvent(dpy, c->crop->win, UnmapNotify, &ev);
++ if (XCheckTypedWindowEvent(dpy, root, UnmapNotify, &ev)
++ && ev.xunmap.window != c->crop->win)
++ XPutBackEvent(dpy, &ev);
++ }
++ resizeclient(c->crop, c->crop->x + c->x - x, c->crop->y + c->y - y,
++ c->crop->w, c->crop->h);
++ resizeclient(c, x, y, 1, 1);
++}
++
++void
++cropdelete(Client *c)
++{
++ Client *crop;
++ XEvent ev;
++
++ c->crop->x += c->x;
++ c->crop->y += c->y;
++ c->crop->bw = c->bw;
++ c->crop->next = c->next;
++ c->crop->snext = c->snext;
++ c->crop->tags = c->tags;
++ c->crop->mon = c->mon;
++ XReparentWindow(dpy, c->crop->win, root, c->crop->x, c->crop->y);
++ XDestroyWindow(dpy, c->win);
++ crop = c->crop;
++ memcpy(c, c->crop, sizeof(Client));
++ free(crop);
++ resize(c, c->x, c->y, c->w, c->h, 0);
++ focus(c);
++ XCheckTypedWindowEvent(dpy, c->win, UnmapNotify, &ev);
++}
++
++void
++cropresize(Client* c)
++{
++ resizeclient(c->crop,
++ BETWEEN(c->crop->x, -(c->crop->w), 0) ? c->crop->x : 0,
++ BETWEEN(c->crop->y, -(c->crop->h), 0) ? c->crop->y : 0,
++ c->crop->w, c->crop->h);
++ resize(c, c->x, c->y,
++ MIN(c->w, c->crop->x + c->crop->w),
++ MIN(c->h, c->crop->y + c->crop->h), 0);
++}
++
+ void
+ applyrules(Client *c)
+ {
+@@ -516,7 +599,7 @@ clientmessage(XEvent *e)
+ XClientMessageEvent *cme = &e->xclient;
+ Client *c = wintoclient(cme->window);
+
+- if (!c)
++ if (!c && !(c = cropwintoclient(cme->window)))
+ return;
+ if (cme->message_type == netatom[NetWMState]) {
+ if (cme->data.l[1] == netatom[NetWMFullscreen]
+@@ -579,16 +662,19 @@ configurenotify(XEvent *e)
+ void
+ configurerequest(XEvent *e)
+ {
+- Client *c;
++ Client *c, *cc = NULL;
+ Monitor *m;
+ XConfigureRequestEvent *ev = &e->xconfigurerequest;
+ XWindowChanges wc;
+
+- if ((c = wintoclient(ev->window))) {
++ if ((c = wintoclient(ev->window))
++ || (c = cc = cropwintoclient(ev->window))) {
+ if (ev->value_mask & CWBorderWidth)
+ c->bw = ev->border_width;
+ else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) {
+ m = c->mon;
++ if (c->crop)
++ c = c->crop;
+ if (ev->value_mask & CWX) {
+ c->oldx = c->x;
+ c->x = m->mx + ev->x;
+@@ -613,6 +699,8 @@ configurerequest(XEvent *e)
+ configure(c);
+ if (ISVISIBLE(c))
+ XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h);
++ if (cc)
++ cropresize(cc);
+ } else
+ configure(c);
+ } else {
+@@ -651,7 +739,7 @@ destroynotify(XEvent *e)
+ Client *c;
+ XDestroyWindowEvent *ev = &e->xdestroywindow;
+
+- if ((c = wintoclient(ev->window)))
++ if ((c = wintoclient(ev->window)) || (c = cropwintoclient(ev->window)))
+ unmanage(c, 1);
+ }
+
+@@ -762,6 +850,8 @@ enternotify(XEvent *e)
+ if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root)
+ return;
+ c = wintoclient(ev->window);
++ if (!c)
++ c = cropwintoclient(ev->window);
+ m = c ? c->mon : wintomon(ev->window);
+ if (m != selmon) {
+ unfocus(selmon->sel, 1);
+@@ -1005,6 +1095,8 @@ killclient(const Arg *arg)
+ {
+ if (!selmon->sel)
+ return;
++ if (selmon->sel->crop)
++ cropdelete(selmon->sel);
+ if (!sendevent(selmon->sel, wmatom[WMDelete])) {
+ XGrabServer(dpy);
+ XSetErrorHandler(xerrordummy);
+@@ -1150,6 +1242,10 @@ movemouse(const Arg *arg)
+ restack(selmon);
+ ocx = c->x;
+ ocy = c->y;
++ if (arg->i == 1 && c->crop) {
++ ocx = c->crop->x;
++ ocy = c->crop->y;
++ }
+ if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
+ None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess)
+ return;
+@@ -1170,6 +1266,12 @@ movemouse(const Arg *arg)
+
+ nx = ocx + (ev.xmotion.x - x);
+ ny = ocy + (ev.xmotion.y - y);
++ if (arg->i == 1 && c->crop) {
++ c->crop->x = nx;
++ c->crop->y = ny;
++ cropresize(c);
++ continue;
++ }
+ if (abs(selmon->wx - nx) < snap)
+ nx = selmon->wx;
+ else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap)
+@@ -1221,7 +1323,10 @@ propertynotify(XEvent *e)
+ updatestatus();
+ else if (ev->state == PropertyDelete)
+ return; /* ignore */
+- else if ((c = wintoclient(ev->window))) {
++ else if ((c = wintoclient(ev->window))
++ || (c = cropwintoclient(ev->window))) {
++ if (c->crop)
++ c = c->crop;
+ switch(ev->atom) {
+ default: break;
+ case XA_WM_TRANSIENT_FOR:
+@@ -1303,12 +1408,16 @@ resizemouse(const Arg *arg)
+ if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */
+ return;
+ restack(selmon);
++ if (arg->i == 1)
++ cropwindow(c);
+ ocx = c->x;
+ ocy = c->y;
+ if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
+ None, cursor[CurResize]->cursor, CurrentTime) != GrabSuccess)
+ return;
+- XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1);
++ if (arg->i != 1)
++ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0,
++ c->w + c->bw - 1, c->h + c->bw - 1);
+ do {
+ XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev);
+ switch(ev.type) {
+@@ -1324,6 +1433,10 @@ resizemouse(const Arg *arg)
+
+ nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1);
+ nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1);
++ if (c->crop) {
++ nw = MIN(nw, c->crop->w + c->crop->x);
++ nh = MIN(nh, c->crop->h + c->crop->y);
++ }
+ if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww
+ && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh)
+ {
+@@ -1430,6 +1543,8 @@ setclientstate(Client *c, long state)
+ {
+ long data[] = { state, None };
+
++ if (c->crop)
++ c = c->crop;
+ XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32,
+ PropModeReplace, (unsigned char *)data, 2);
+ }
+@@ -1462,6 +1577,8 @@ sendevent(Client *c, Atom proto)
+ void
+ setfocus(Client *c)
+ {
++ if (c->crop)
++ c = c->crop;
+ if (!c->neverfocus) {
+ XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
+ XChangeProperty(dpy, root, netatom[NetActiveWindow],
+@@ -1474,6 +1591,8 @@ setfocus(Client *c)
+ void
+ setfullscreen(Client *c, int fullscreen)
+ {
++ if (c->crop)
++ c = c->crop;
+ if (fullscreen && !c->isfullscreen) {
+ XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
+ PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1);
+@@ -1718,6 +1837,8 @@ togglefloating(const Arg *arg)
+ if (selmon->sel->isfloating)
+ resize(selmon->sel, selmon->sel->x, selmon->sel->y,
+ selmon->sel->w, selmon->sel->h, 0);
++ if (!selmon->sel->isfloating && selmon->sel->crop)
++ cropdelete(selmon->sel);
+ arrange(selmon);
+ }
+
+@@ -1767,6 +1888,8 @@ unmanage(Client *c, int destroyed)
+ Monitor *m = c->mon;
+ XWindowChanges wc;
+
++ if (c->crop)
++ cropdelete(c);
+ detach(c);
+ detachstack(c);
+ if (!destroyed) {
+@@ -1792,7 +1915,8 @@ unmapnotify(XEvent *e)
+ Client *c;
+ XUnmapEvent *ev = &e->xunmap;
+
+- if ((c = wintoclient(ev->window))) {
++ if ((c = wintoclient(ev->window))
++ || (c = cropwintoclient(ev->window))) {
+ if (ev->send_event)
+ setclientstate(c, WithdrawnState);
+ else
+@@ -2070,7 +2194,7 @@ wintomon(Window w)
+ for (m = mons; m; m = m->next)
+ if (w == m->barwin)
+ return m;
+- if ((c = wintoclient(w)))
++ if ((c = wintoclient(w)) || (c = cropwintoclient(w)))
+ return c->mon;
+ return selmon;
+ }