commit 67e76fcd5dc9874aba5796c3bee20e68a5ef4b21
parent 999824257ffd9b920b172c9fe9ee16b019efba2b
Author: Lucas de Sena <lucas@seninha.org>
Date: Sat, 15 Jul 2023 20:16:56 -0300
Left button does not stretch the window anymore
Several changes:
- Maximization is now done by dropping the window at the top of the
screen, rather than double-clicking the title bar.
- Double-clicking the title bar now stretches the row to fit the column.
- Scrolling the title bar up and down shadows/unshadows the container
- The left button is now the menu button. It invokes shodmenu(1), which
is a xmenu script.
Diffstat:
9 files changed, 194 insertions(+), 39 deletions(-)
diff --git a/config.c b/config.c
@@ -8,6 +8,9 @@ struct Config config = {
* and the color of the dark 3D shadow.
*/
+ /* command to spawn when clicking the menu button */
+ .menucmd = "shodmenu",
+
/* 0-or-1 flags */
.floatdialog = 0, /* set to 1 to use floating dialog windows */
.sloppyfocus = 0, /* set to 1 to use sloppy container focus */
diff --git a/examples/shodmenu b/examples/shodmenu
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+IFS=
+
+run() {
+ set -m
+ while read -r line
+ do case "$line" in
+ (state:*)
+ state="${line#"state:"}"
+ shodc state "-$state" "$WINDOWID"
+ ;;
+ (sendto:*)
+ desk="${line#"sendto:"}"
+ shodc sendto "$desk" "$WINDOWID"
+ ;;
+ (close)
+ shodc close "$WINDOWID"
+ ;;
+ esac done &
+}
+
+exec xmenu -fp "$WINDOWPOS" -t "$WINDOWID" "$@" "Window Control" <<EOF | run
+Stick state:y
+Shade state:s
+Minimize state:m
+Maximize state:M
+Fullscreen state:f
+Above state:a
+Below state:b
+Send To
+ Desktop 1 sendto:1
+ Desktop 2 sendto:2
+ Desktop 3 sendto:3
+ Desktop 4 sendto:4
+ Desktop 5 sendto:5
+
+Close close
+EOF
diff --git a/shod.1 b/shod.1
@@ -10,6 +10,8 @@
.Op Fl AcdhSstW
.Op Ar file
.Pp
+.Nm shodmenu
+.Pp
.Nm shodc
.Cm close
.Op Ar win_id
@@ -138,6 +140,26 @@ This option is incompatible with
.Fl A .
.El
.Pp
+.Nm shodmenu
+is the command called by
+.Nm shod
+when the user interacts with the menu button (the button at the top far left of a title bar).
+This command should be a script written by the user;
+and usually invokes a menu program like
+.Xr xmenu 1 .
+Before calling
+.Nm shodmenu ,
+the environment variables
+.Ev WINDOWID
+and
+.Ev WINDOWPOS
+(containing the id and position of the window whose menu button was activated, respectively)
+are set.
+An example
+.Nm shodmenu
+script is distributed with
+.Nm shod .
+.Pp
.Nm shodc
is the remote controller for
.Nm shod .
@@ -513,10 +535,14 @@ Dragging a tab with the third mouse button detaches the tab from the container.
A detached tab, while being dragged, can be reattached into another container
(or the same container) by dropping it on the title bar, border or divisor;
or can be made into a new container by dropping it elsewhere.
-Double-clicking a tab toggles maximization of its container.
+Double-clicking a tab (un)stretches its row.
+Scrolling a tab up shades its container.
+Scrolling a down up unshades its container.
+.Pp
+Moving a container to the top of the screen maximizes it.
.Pp
Each title bar has a left button.
-Clicking on the left title-bar button with the first mouse button restackes the column
+Clicking on the left title-bar button with the first mouse button stretches the column
by maximizing its rows (and minimizing the other rows in the same column),
or undoes this state.
.Pp
@@ -835,6 +861,26 @@ on.
The shell to run with
.Xr exec 3 .
.El
+.Pp
+The following environment variables are set by
+.Nm shod
+and can affect the execution of the
+.Nm shodmenu
+script.
+.Bl -tag -width Ds
+.It Ev WINDOWID
+A string containing the id number, in decimal ASCII characters,
+of the client window on the tab where the menu button (the left title bar button) is pressed.
+.It Ev WINDOWPOS
+The position, in
+.Qq Ic "+X+Y"
+format (parseable by
+.Xr XParseGeometry 3 )
+of the client window on the tab where the menu button is pressed.
+.El
+.Sh SEE ALSO
+.Xr xmenu 1 ,
+.Xr X 7
.Sh BUGS
.Xr XSizeHints 3
are ignored.
diff --git a/shod.h b/shod.h
@@ -674,6 +674,8 @@ struct Config {
int movetime; /* time (ms) to redraw containers during moving */
int resizetime; /* time (ms) to redraw containers during resizing */
+ char *menucmd; /* command to spawn when clicking the menu button */
+
/* gravities (N for north, NE for northeast, etc) */
const char *notifgravity;
const char *dockgravity;
@@ -728,7 +730,7 @@ void tabdetach(struct Tab *tab, int x, int y);
void tabfocus(struct Tab *tab, int gotodesk);
void tabdecorate(struct Tab *t, int pressed);
void tabupdateurgency(struct Tab *t, int isurgent);
-void rowstack(struct Column *col, struct Row *row);
+void rowstretch(struct Column *col, struct Row *row);
void dialogconfigure(struct Dialog *d, unsigned int valuemask, XWindowChanges *wc);
void dialogmoveresize(struct Dialog *dial);
int tabattach(struct Container *c, struct Tab *t, int x, int y);
diff --git a/shodc.c b/shodc.c
@@ -535,7 +535,7 @@ state(int argc, char *argv[])
action = TOGGLE;
state1 = state2 = None;
- while ((c = getopt(argc, argv, "ATRabfMmsy")) != -1) {
+ while ((c = getopt(argc, argv, "ATRabfMmsSy")) != -1) {
switch (c) {
case 'A':
action = ADD;
@@ -566,6 +566,10 @@ state(int argc, char *argv[])
state1 = atoms[_NET_WM_STATE_HIDDEN];
state2 = None;
break;
+ case 'S':
+ state1 = atoms[_SHOD_WM_STATE_STRETCHED];
+ state2 = None;
+ break;
case 's':
state1 = atoms[_NET_WM_STATE_SHADED];
state2 = None;
diff --git a/xcontainer.c b/xcontainer.c
@@ -1290,6 +1290,11 @@ containersetstate(struct Tab *tab, Atom *props, unsigned long set)
if (props[0] == atoms[_NET_WM_STATE_DEMANDS_ATTENTION] ||
props[1] == atoms[_NET_WM_STATE_DEMANDS_ATTENTION])
tabupdateurgency(tab, set == ADD || (set == TOGGLE && !tab->isurgent));
+ if (props[0] == atoms[_SHOD_WM_STATE_STRETCHED] ||
+ props[1] == atoms[_SHOD_WM_STATE_STRETCHED]) {
+ rowstretch(tab->row->col, tab->row);
+ tabfocus(tab->row->seltab, 0);
+ }
ewmhsetstate(c);
}
@@ -1668,7 +1673,7 @@ tabfocus(struct Tab *tab, int gotodesk)
if (gotodesk)
deskupdate(c->mon, c->issticky ? c->mon->seldesk : c->desk);
if (tab->row->fact == 0.0)
- rowstack(tab->row->col, tab->row);
+ rowstretch(tab->row->col, tab->row);
XRaiseWindow(dpy, tab->row->frame);
XRaiseWindow(dpy, tab->frame);
if (c->isshaded || tab->row->isunmapped) {
@@ -1758,9 +1763,9 @@ tabupdateurgency(struct Tab *t, int isurgent)
}
}
-/* stack rows */
+/* stretch row */
void
-rowstack(struct Column *col, struct Row *row)
+rowstretch(struct Column *col, struct Row *row)
{
struct Row *r;
double fact;
diff --git a/xevents.c b/xevents.c
@@ -1,4 +1,5 @@
#include <err.h>
+#include <spawn.h>
#include "shod.h"
@@ -1045,9 +1046,8 @@ mousemove(Window win, int type, void *p, int xroot, int yroot, enum Octant o)
Window frame;
XEvent ev;
Time lasttime;
- int moved, x, y;
+ int x, y, maximize;
- moved = 0;
if (type == FLOAT_MENU) {
menu = (struct Menu *)p;
menudecorate(menu, o);
@@ -1062,36 +1062,51 @@ mousemove(Window win, int type, void *p, int xroot, int yroot, enum Octant o)
XDefineCursor(dpy, win, wm.cursors[CURSOR_MOVE]);
else if (XGrabPointer(dpy, frame, False, ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, wm.cursors[CURSOR_MOVE], CurrentTime) != GrabSuccess)
goto done;
+ maximize = 0;
while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
switch (ev.type) {
case ButtonRelease:
goto done;
break;
case MotionNotify:
- moved = 1;
- if (ev.xmotion.time - lasttime > (unsigned)config.movetime) {
- x = ev.xmotion.x_root - xroot;
- y = ev.xmotion.y_root - yroot;
- if (type == FLOAT_MENU) {
- menuincrmove(menu, x, y);
- } else {
- containerincrmove(c, x, y);
- }
- lasttime = ev.xmotion.time;
- xroot = ev.xmotion.x_root;
- yroot = ev.xmotion.y_root;
+ if (ev.xmotion.time - lasttime <= (unsigned)config.movetime)
+ break;
+ x = ev.xmotion.x_root - xroot;
+ y = ev.xmotion.y_root - yroot;
+ if (type == FLOAT_MENU)
+ menuincrmove(menu, x, y);
+ else if (maximize < 0)
+ containerincrmove(c, 0, 0);
+ else
+ containerincrmove(c, x, y);
+ lasttime = ev.xmotion.time;
+ xroot = ev.xmotion.x_root;
+ yroot = ev.xmotion.y_root;
+ if (type == FLOAT_MENU)
+ break;
+ if ((maximize > 0 && ev.xmotion.y_root <= 0) ||
+ (maximize < 0 && ev.xmotion.y_root > 0) ||
+ maximize == 0) {
+ containersetstate(
+ c->selcol->selrow->seltab,
+ (Atom []){
+ atoms[_NET_WM_STATE_MAXIMIZED_VERT],
+ atoms[_NET_WM_STATE_MAXIMIZED_HORZ],
+ },
+ ev.xmotion.y_root <= 0 ? ADD : REMOVE
+ );
}
+ if (ev.xmotion.y_root <= 0)
+ maximize = -1;
+ else
+ maximize = 1;
break;
}
}
done:
if (type == FLOAT_MENU) {
- if (!moved)
- menumoveresize(menu);
menudecorate(menu, 0);
} else {
- if (!moved)
- containermoveresize(c, 1);
containerdecorate(c, NULL, NULL, 0, 0);
}
if (win != None) {
@@ -1289,14 +1304,8 @@ xeventbuttonpress(XEvent *e)
goto done;
if (ev->window == tab->title && ev->button == Button1) {
if (lastc == c && ev->time - lasttime < DOUBLECLICK) {
- containersetstate(
- tab,
- (Atom []){
- atoms[_NET_WM_STATE_MAXIMIZED_VERT],
- atoms[_NET_WM_STATE_MAXIMIZED_HORZ],
- },
- TOGGLE
- );
+ rowstretch(tab->row->col, tab->row);
+ tabfocus(tab->row->seltab, 0);
lasttime = 0;
lastc = NULL;
goto done;
@@ -1305,7 +1314,19 @@ xeventbuttonpress(XEvent *e)
lasttime = ev->time;
}
o = getframehandle(c->w, c->h, x, y);
- if (ev->window == tab->title && ev->button == Button3) {
+ if (ev->window == tab->title && ev->button == Button4) {
+ containersetstate(
+ tab,
+ (Atom [2]){ atoms[_NET_WM_STATE_SHADED], None },
+ ADD
+ );
+ } if (ev->window == tab->title && ev->button == Button5) {
+ containersetstate(
+ tab,
+ (Atom [2]){ atoms[_NET_WM_STATE_SHADED], None },
+ REMOVE
+ );
+ } else if (ev->window == tab->title && ev->button == Button3) {
mouseretab(tab, ev->x_root, ev->y_root, ev->x, ev->y);
} else if (ev->window == tab->row->bl && ev->button == Button1) {
wm.presswin = ev->window;
@@ -1318,13 +1339,14 @@ xeventbuttonpress(XEvent *e)
if (cdiv != NULL || rdiv != NULL) {
mouseretile(c, cdiv, rdiv, ev->x, ev->y);
}
- } else if (!c->isfullscreen && !c->isminimized && !c->ismaximized) {
+ } else if (!c->isfullscreen && !c->isminimized) {
if (isvalidstate(ev->state) && ev->button == Button1) {
mousemove(None, FLOAT_CONTAINER, c, ev->x_root, ev->y_root, 0);
} else if (ev->window == c->frame && ev->button == Button3) {
mousemove(None, FLOAT_CONTAINER, c, ev->x_root, ev->y_root, o);
- } else if ((isvalidstate(ev->state) && ev->button == Button3) ||
- (o != C && ev->window == c->frame && ev->button == Button1)) {
+ } else if (!c->ismaximized &&
+ ((isvalidstate(ev->state) && ev->button == Button3) ||
+ (o != C && ev->window == c->frame && ev->button == Button1))) {
if (o == C)
o = getquadrant(c->w, c->h, x, y);
mouseresize(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, o);
@@ -1351,7 +1373,9 @@ xeventbuttonrelease(XEvent *e)
Window win, button;
Pixmap pix;
int perform;
+ char buf[16];
enum { PRESS_CLOSE, PRESS_STACK } action;
+ extern char **environ;
ev = &e->xbutton;
wm.presswin = None;
@@ -1400,8 +1424,29 @@ xeventbuttonrelease(XEvent *e)
buttonrightdecorate(button, pix, FOCUSED, 0);
break;
case PRESS_STACK:
- rowstack(row->col, row);
- tabfocus(row->seltab, 0);
+ (void)snprintf(
+ buf,
+ sizeof(buf),
+ "%lu",
+ (unsigned long)row->seltab->obj.win
+ );
+ (void)setenv("WINDOWID", buf, 1);
+ (void)snprintf(
+ buf,
+ sizeof(buf),
+ "%+d%+d",
+ row->col->c->x + row->col->x,
+ row->col->c->y + row->y + config.titlewidth
+ );
+ (void)setenv("WINDOWPOS", buf, 1);
+ (void)posix_spawnp(
+ NULL,
+ config.menucmd,
+ NULL,
+ NULL,
+ (char *[]){ config.menucmd, NULL },
+ environ
+ );
buttonleftdecorate(button, pix, FOCUSED, 0);
break;
}
diff --git a/xutil.c b/xutil.c
@@ -1,4 +1,6 @@
#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
@@ -179,10 +181,18 @@ initatoms(void)
void
xinit(void)
{
+ int fd;
+
if ((dpy = XOpenDisplay(NULL)) == NULL)
errx(1, "could not open display");
screen = DefaultScreen(dpy);
root = RootWindow(dpy, screen);
+ fd = XConnectionNumber(dpy);
+ while (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
+ if (errno == EINTR)
+ continue;
+ err(EXIT_FAILURE, "fcntl");
+ }
}
void
diff --git a/xutil.h b/xutil.h
@@ -58,6 +58,7 @@
X(_NET_WM_WINDOW_TYPE_UTILITY) \
X(_MOTIF_WM_HINTS) \
X(_GNUSTEP_WM_ATTR) \
+ X(_SHOD_WM_STATE_STRETCHED) \
X(_SHOD_CYCLE) \
X(_SHOD_GROUP_TAB) \
X(_SHOD_GROUP_CONTAINER) \