From 6639e81666ec1793956fa462282a85d3d046aa45 Mon Sep 17 00:00:00 2001 From: Bakkeby Date: Mon, 10 Jan 2022 11:37:31 +0100 Subject: [PATCH] floatpos: Control the size and position of floating windows This patch offers a comprehensive and monitor size agnostic way of positioning new and existing floating windows. Refer to: https://github.com/bakkeby/patches/wiki/floatpos/ --- config.def.h | 46 +++++++++++- dwm.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 233 insertions(+), 5 deletions(-) diff --git a/config.def.h b/config.def.h index a2ac963..91004b2 100644 --- a/config.def.h +++ b/config.def.h @@ -5,6 +5,8 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ static const unsigned int snap = 32; /* snap pixel */ static const int showbar = 1; /* 0 means no bar */ static const int topbar = 1; /* 0 means bottom bar */ +static int floatposgrid_x = 5; /* float grid columns */ +static int floatposgrid_y = 5; /* float grid rows */ static const char *fonts[] = { "monospace:size=10" }; static const char dmenufont[] = "monospace:size=10"; static const char col_gray1[] = "#222222"; @@ -26,9 +28,9 @@ static const Rule rules[] = { * WM_CLASS(STRING) = instance, class * WM_NAME(STRING) = title */ - /* class instance title tags mask isfloating monitor */ - { "Gimp", NULL, NULL, 0, 1, -1 }, - { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, + /* class instance title tags mask isfloating floatpos monitor */ + { "Gimp", NULL, NULL, 0, 1, NULL, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, NULL, -1 }, }; /* layout(s) */ @@ -85,6 +87,44 @@ static Key keys[] = { { MODKEY, XK_period, focusmon, {.i = +1 } }, { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, + /* Client position is limited to monitor window area */ + { Mod4Mask, XK_u, floatpos, {.v = "-26x -26y" } }, // ↖ + { Mod4Mask, XK_i, floatpos, {.v = " 0x -26y" } }, // ↑ + { Mod4Mask, XK_o, floatpos, {.v = " 26x -26y" } }, // ↗ + { Mod4Mask, XK_j, floatpos, {.v = "-26x 0y" } }, // ← + { Mod4Mask, XK_l, floatpos, {.v = " 26x 0y" } }, // → + { Mod4Mask, XK_m, floatpos, {.v = "-26x 26y" } }, // ↙ + { Mod4Mask, XK_comma, floatpos, {.v = " 0x 26y" } }, // ↓ + { Mod4Mask, XK_period, floatpos, {.v = " 26x 26y" } }, // ↘ + /* Absolute positioning (allows moving windows between monitors) */ + { Mod4Mask|ControlMask, XK_u, floatpos, {.v = "-26a -26a" } }, // ↖ + { Mod4Mask|ControlMask, XK_i, floatpos, {.v = " 0a -26a" } }, // ↑ + { Mod4Mask|ControlMask, XK_o, floatpos, {.v = " 26a -26a" } }, // ↗ + { Mod4Mask|ControlMask, XK_j, floatpos, {.v = "-26a 0a" } }, // ← + { Mod4Mask|ControlMask, XK_l, floatpos, {.v = " 26a 0a" } }, // → + { Mod4Mask|ControlMask, XK_m, floatpos, {.v = "-26a 26a" } }, // ↙ + { Mod4Mask|ControlMask, XK_comma, floatpos, {.v = " 0a 26a" } }, // ↓ + { Mod4Mask|ControlMask, XK_period, floatpos, {.v = " 26a 26a" } }, // ↘ + /* Resize client, client center position is fixed which means that client expands in all directions */ + { Mod4Mask|ShiftMask, XK_u, floatpos, {.v = "-26w -26h" } }, // ↖ + { Mod4Mask|ShiftMask, XK_i, floatpos, {.v = " 0w -26h" } }, // ↑ + { Mod4Mask|ShiftMask, XK_o, floatpos, {.v = " 26w -26h" } }, // ↗ + { Mod4Mask|ShiftMask, XK_j, floatpos, {.v = "-26w 0h" } }, // ← + { Mod4Mask|ShiftMask, XK_k, floatpos, {.v = "800W 800H" } }, // · + { Mod4Mask|ShiftMask, XK_l, floatpos, {.v = " 26w 0h" } }, // → + { Mod4Mask|ShiftMask, XK_m, floatpos, {.v = "-26w 26h" } }, // ↙ + { Mod4Mask|ShiftMask, XK_comma, floatpos, {.v = " 0w 26h" } }, // ↓ + { Mod4Mask|ShiftMask, XK_period, floatpos, {.v = " 26w 26h" } }, // ↘ + /* Client is positioned in a floating grid, movement is relative to client's current position */ + { Mod4Mask|Mod1Mask, XK_u, floatpos, {.v = "-1p -1p" } }, // ↖ + { Mod4Mask|Mod1Mask, XK_i, floatpos, {.v = " 0p -1p" } }, // ↑ + { Mod4Mask|Mod1Mask, XK_o, floatpos, {.v = " 1p -1p" } }, // ↗ + { Mod4Mask|Mod1Mask, XK_j, floatpos, {.v = "-1p 0p" } }, // ← + { Mod4Mask|Mod1Mask, XK_k, floatpos, {.v = " 0p 0p" } }, // · + { Mod4Mask|Mod1Mask, XK_l, floatpos, {.v = " 1p 0p" } }, // → + { Mod4Mask|Mod1Mask, XK_m, floatpos, {.v = "-1p 1p" } }, // ↙ + { Mod4Mask|Mod1Mask, XK_comma, floatpos, {.v = " 0p 1p" } }, // ↓ + { Mod4Mask|Mod1Mask, XK_period, floatpos, {.v = " 1p 1p" } }, // ↘ TAGKEYS( XK_1, 0) TAGKEYS( XK_2, 1) TAGKEYS( XK_3, 2) diff --git a/dwm.c b/dwm.c index a96f33c..bf9ba9a 100644 --- a/dwm.c +++ b/dwm.c @@ -93,6 +93,7 @@ struct Client { int bw, oldbw; unsigned int tags; int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; + int ignoresizehints; Client *next; Client *snext; Monitor *mon; @@ -138,6 +139,7 @@ typedef struct { const char *title; unsigned int tags; int isfloating; + const char *floatpos; int monitor; } Rule; @@ -165,11 +167,13 @@ static void drawbar(Monitor *m); static void drawbars(void); static void enternotify(XEvent *e); static void expose(XEvent *e); +static void floatpos(const Arg *arg); static void focus(Client *c); static void focusin(XEvent *e); static void focusmon(const Arg *arg); static void focusstack(const Arg *arg); static Atom getatomprop(Client *c, Atom prop); +static void getfloatpos(int pos, char pCh, int size, char sCh, int min_p, int max_s, int cp, int cs, int cbw, int defgrid, int *out_p, int *out_s); static int getrootptr(int *x, int *y); static long getstate(Window w); static int gettextprop(Window w, Atom atom, char *text, unsigned int size); @@ -198,6 +202,7 @@ static void scan(void); static int sendevent(Client *c, Atom proto); static void sendmon(Client *c, Monitor *m); static void setclientstate(Client *c, long state); +static void setfloatpos(Client *c, const char *floatpos); static void setfocus(Client *c); static void setfullscreen(Client *c, int fullscreen); static void setlayout(const Arg *arg); @@ -303,6 +308,8 @@ applyrules(Client *c) for (m = mons; m && m->num != r->monitor; m = m->next); if (m) c->mon = m; + if (c->isfloating && r->floatpos) + setfloatpos(c, r->floatpos); } } if (ch.res_class) @@ -344,7 +351,7 @@ applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact) *h = bh; if (*w < bh) *w = bh; - if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) { + if (!c->ignoresizehints && (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange)) { /* see last two sentences in ICCCM 4.1.2.3 */ baseismin = c->basew == c->minw && c->baseh == c->minh; if (!baseismin) { /* temporarily remove base dimensions */ @@ -784,6 +791,21 @@ expose(XEvent *e) drawbar(m); } +void +floatpos(const Arg *arg) +{ + Client *c = selmon->sel; + + if (!c || (selmon->lt[selmon->sellt]->arrange && !c->isfloating)) + return; + + setfloatpos(c, (char *)arg->v); + resizeclient(c, c->x, c->y, c->w, c->h); + + XRaiseWindow(dpy, c->win); + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w/2, c->h/2); +} + void focus(Client *c) { @@ -875,6 +897,124 @@ getatomprop(Client *c, Atom prop) return atom; } +void +getfloatpos(int pos, char pCh, int size, char sCh, int min_p, int max_s, int cp, int cs, int cbw, int defgrid, int *out_p, int *out_s) +{ + int abs_p, abs_s, i, delta, rest; + + abs_p = pCh == 'A' || pCh == 'a'; + abs_s = sCh == 'A' || sCh == 'a'; + + cs += 2*cbw; + + switch(pCh) { + case 'A': // absolute position + cp = pos; + break; + case 'a': // absolute relative position + cp += pos; + break; + case 'y': + case 'x': // client relative position + cp = MIN(cp + pos, min_p + max_s); + break; + case 'Y': + case 'X': // client position relative to monitor + cp = min_p + MIN(pos, max_s); + break; + case 'S': // fixed client position (sticky) + case 'C': // fixed client position (center) + case 'Z': // fixed client right-hand position (position + size) + if (pos == -1) + break; + pos = MAX(MIN(pos, max_s), 0); + if (pCh == 'Z') + cs = abs((cp + cs) - (min_p + pos)); + else if (pCh == 'C') + cs = abs((cp + cs / 2) - (min_p + pos)); + else + cs = abs(cp - (min_p + pos)); + cp = min_p + pos; + sCh = 0; // size determined by position, override defined size + break; + case 'G': // grid + if (pos <= 0) + pos = defgrid; // default configurable + if (size == 0 || pos < 2 || (sCh != 'p' && sCh != 'P')) + break; + delta = (max_s - cs) / (pos - 1); + rest = max_s - cs - delta * (pos - 1); + if (sCh == 'P') { + if (size < 1 || size > pos) + break; + cp = min_p + delta * (size - 1); + } else { + for (i = 0; i < pos && cp >= min_p + delta * i + (i > pos - rest ? i + rest - pos + 1 : 0); i++); + cp = min_p + delta * (MAX(MIN(i + size, pos), 1) - 1) + (i > pos - rest ? i + rest - pos + 1 : 0); + } + break; + } + + switch(sCh) { + case 'A': // absolute size + cs = size; + break; + case 'a': // absolute relative size + cs = MAX(1, cs + size); + break; + case '%': // client size percentage in relation to monitor window area size + if (size <= 0) + break; + size = max_s * MIN(size, 100) / 100; + /* falls through */ + case 'h': + case 'w': // size relative to client + if (sCh == 'w' || sCh == 'h') { + if (size == 0) + break; + size += cs; + } + /* falls through */ + case 'H': + case 'W': // normal size, position takes precedence + if (pCh == 'S' && cp + size > min_p + max_s) + size = min_p + max_s - cp; + else if (size > max_s) + size = max_s; + + if (pCh == 'C') { // fixed client center, expand or contract client + delta = size - cs; + if (delta < 0 || (cp - delta / 2 + size <= min_p + max_s)) + cp -= delta / 2; + else if (cp - delta / 2 < min_p) + cp = min_p; + else if (delta) + cp = min_p + max_s; + } else if (pCh == 'Z') + cp -= size - cs; + + cs = size; + break; + } + + if (pCh == '%') // client mid-point position in relation to monitor window area size + cp = min_p + max_s * MAX(MIN(pos, 100), 0) / 100 - (cs) / 2; + if (pCh == 'm' || pCh == 'M') + cp = pos - cs / 2; + + if (!abs_p && cp < min_p) + cp = min_p; + if (cp + cs > min_p + max_s && !(abs_p && abs_s)) { + if (abs_p || cp == min_p) + cs = min_p + max_s - cp; + else + cp = min_p + max_s - cs; + } + + *out_p = cp; + *out_s = MAX(cs - 2*cbw, 1); +} + int getrootptr(int *x, int *y) { @@ -1033,8 +1173,10 @@ manage(Window w, XWindowAttributes *wa) c->w = c->oldw = wa->width; c->h = c->oldh = wa->height; c->oldbw = wa->border_width; + c->ignoresizehints = 0; updatetitle(c); + c->bw = borderpx; if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) { c->mon = t->mon; c->tags = t->tags; @@ -1051,7 +1193,6 @@ manage(Window w, XWindowAttributes *wa) /* only fix client y-offset, if the client center might cover the bar */ c->y = MAX(c->y, ((c->mon->by == c->mon->my) && (c->x + (c->w / 2) >= c->mon->wx) && (c->x + (c->w / 2) < c->mon->wx + c->mon->ww)) ? bh : c->mon->my); - c->bw = borderpx; wc.border_width = c->bw; XConfigureWindow(dpy, w, CWBorderWidth, &wc); @@ -1461,6 +1602,53 @@ sendevent(Client *c, Atom proto) return exists; } +void +setfloatpos(Client *c, const char *floatpos) +{ + char xCh, yCh, wCh, hCh; + int x, y, w, h, wx, ww, wy, wh; + + if (!c || !floatpos) + return; + if (selmon->lt[selmon->sellt]->arrange && !c->isfloating) + return; + switch(sscanf(floatpos, "%d%c %d%c %d%c %d%c", &x, &xCh, &y, &yCh, &w, &wCh, &h, &hCh)) { + case 4: + if (xCh == 'w' || xCh == 'W') { + w = x; wCh = xCh; + h = y; hCh = yCh; + x = -1; xCh = 'C'; + y = -1; yCh = 'C'; + } else if (xCh == 'p' || xCh == 'P') { + w = x; wCh = xCh; + h = y; hCh = yCh; + x = 0; xCh = 'G'; + y = 0; yCh = 'G'; + } else if (xCh == 'm' || xCh == 'M') { + getrootptr(&x, &y); + } else { + w = 0; wCh = 0; + h = 0; hCh = 0; + } + break; + case 8: + if (xCh == 'm' || xCh == 'M') + getrootptr(&x, &y); + break; + default: + return; + } + + wx = c->mon->wx; + wy = c->mon->wy; + ww = c->mon->ww; + wh = c->mon->wh; + c->ignoresizehints = 1; + + getfloatpos(x, xCh, w, wCh, wx, ww, c->x, c->w, c->bw, floatposgrid_x, &c->x, &c->w); + getfloatpos(y, yCh, h, hCh, wy, wh, c->y, c->h, c->bw, floatposgrid_y, &c->y, &c->h); +} + void setfocus(Client *c) { -- 2.19.1