shod

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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:
Mconfig.c | 3+++
Aexamples/shodmenu | 39+++++++++++++++++++++++++++++++++++++++
Mshod.1 | 50++++++++++++++++++++++++++++++++++++++++++++++++--
Mshod.h | 4+++-
Mshodc.c | 6+++++-
Mxcontainer.c | 11++++++++---
Mxevents.c | 109++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mxutil.c | 10++++++++++
Mxutil.h | 1+
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) \