From be5e55f4a8e64f14a35671d9aee1e091693c429a Mon Sep 17 00:00:00 2001 From: alejandrogs73 Date: Sat, 26 Sep 2026 19:39:02 +0200 Subject: [PATCH 1/5] dwm: borde opaco para ventanas ARGB (st con alpha) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MymT1wzMgXhfC3ZwHqNAVF --- dwm/drw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dwm/drw.c b/dwm/drw.c index 9fdd1a4..5bc9da2 100644 --- a/dwm/drw.c +++ b/dwm/drw.c @@ -176,6 +176,9 @@ drw_clr_create(Drw *drw, Clr *dest, const char *clrname) DefaultColormap(drw->dpy, drw->screen), clrname, dest)) die("error, cannot allocate color '%s'", clrname); + + /* alfa opaco: si no, el borde de las ventanas ARGB (st con alpha) sale transparente */ + dest->pixel |= 0xffU << 24; } /* Create color schemes. */ From ee7e036c7f9f1a2bc9b28cb5d533112156fcae5f Mon Sep 17 00:00:00 2001 From: alejandrogs73 Date: Sat, 26 Sep 2026 19:39:02 +0200 Subject: [PATCH 2/5] dwm: parches restartsig, preserveonrestart, statuscmd-nosignal y swallow - Mod+Shift+r reinicia dwm sin cerrar ventanas y cada una sigue en su tag - Clics en la barra: red (nmtui), volumen (pavucontrol, silenciar, micro, rueda) y bluetooth (blueman, encender/apagar) - swallow: lo que se abre desde st sustituye a la terminal - Reglas: flotan centradas pavucontrol, blueman, nmtui y ventanas de Firefox Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MymT1wzMgXhfC3ZwHqNAVF --- dwm/config.def.h | 20 +- dwm/config.h | 62 ++- dwm/config.mk | 3 +- dwm/dwm.1 | 10 + dwm/dwm.c | 387 +++++++++++++++- dwm/patches/dwm-preserveonrestart-6.3.diff | 118 +++++ dwm/patches/dwm-restartsig-20180523-6.2.diff | 139 ++++++ ...m-statuscmd-nosignal-20210402-67d76bd.diff | 167 +++++++ dwm/patches/dwm-swallow-6.3.diff | 412 ++++++++++++++++++ 9 files changed, 1294 insertions(+), 24 deletions(-) create mode 100644 dwm/patches/dwm-preserveonrestart-6.3.diff create mode 100644 dwm/patches/dwm-restartsig-20180523-6.2.diff create mode 100644 dwm/patches/dwm-statuscmd-nosignal-20210402-67d76bd.diff create mode 100644 dwm/patches/dwm-swallow-6.3.diff diff --git a/dwm/config.def.h b/dwm/config.def.h index 87e61ce..674ac07 100644 --- a/dwm/config.def.h +++ b/dwm/config.def.h @@ -4,6 +4,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ static const unsigned int gappx = 5; /* gaps between windows */ static const unsigned int snap = 32; /* snap pixel */ +static const int swallowfloating = 0; /* 1 means swallow floating windows by default */ static const int showbar = 1; /* 0 means no bar */ static const int topbar = 1; /* 0 means bottom bar */ static const char *fonts[] = { "monospace:size=10" }; @@ -27,9 +28,11 @@ 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 isterminal noswallow monitor */ + { "Gimp", NULL, NULL, 0, 1, 0, 0, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, 0, -1, -1 }, + { "St", NULL, NULL, 0, 0, 1, 0, -1 }, + { NULL, NULL, "Event Tester", 0, 0, 0, 1, -1 }, /* xev */ }; /* layout(s) */ @@ -62,6 +65,12 @@ static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; static const char *termcmd[] = { "st", NULL }; +/* commands spawned when clicking statusbar, the mouse button pressed is exported as BUTTON */ +static const StatusCmd statuscmds[] = { + { "notify-send Mouse$BUTTON", 1 }, +}; +static const char *statuscmd[] = { "/bin/sh", "-c", NULL, NULL }; + static const Key keys[] = { /* modifier key function argument */ { MODKEY, XK_p, spawn, {.v = dmenucmd } }, @@ -100,6 +109,7 @@ static const Key keys[] = { TAGKEYS( XK_8, 7) TAGKEYS( XK_9, 8) { MODKEY|ShiftMask, XK_q, quit, {0} }, + { MODKEY|ControlMask|ShiftMask, XK_q, quit, {1} }, }; /* button definitions */ @@ -109,7 +119,9 @@ static const Button buttons[] = { { ClkLtSymbol, 0, Button1, setlayout, {0} }, { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, { ClkWinTitle, 0, Button2, zoom, {0} }, - { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkStatusText, 0, Button1, spawn, {.v = statuscmd } }, + { ClkStatusText, 0, Button2, spawn, {.v = statuscmd } }, + { ClkStatusText, 0, Button3, spawn, {.v = statuscmd } }, { ClkClientWin, MODKEY, Button1, movemouse, {0} }, { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, diff --git a/dwm/config.h b/dwm/config.h index dc970d6..8ed1c90 100644 --- a/dwm/config.h +++ b/dwm/config.h @@ -6,6 +6,7 @@ static const unsigned int borderpx = 2; /* border pixel of windows */ static const unsigned int gappx = 4; /* gaps between windows */ static const unsigned int snap = 32; /* snap pixel */ +static const int swallowfloating = 0; /* 1 means swallow floating windows by default */ static const int showbar = 1; /* 0 means no bar */ static const int topbar = 1; /* 0 means bottom bar */ static const char *fonts[] = { "Fira Code:size=11:antialias=true:autohint=true" }; @@ -32,8 +33,18 @@ 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 }, + /* Las ventanas que flotan por una regla se abren centradas. + * swallow: lo que se abre desde una terminal (isterminal) la sustituye + * hasta que se cierra; noswallow = 1 lo evita para esa ventana. */ + /* class instance title tags mask isfloating isterminal noswallow monitor */ + { "st-256color", NULL, NULL, 0, 0, 1, 0, -1 }, + { "st-float", NULL, NULL, 0, 1, 0, 1, -1 }, /* st -c st-float (p. ej. nmtui) */ + { "Gimp", NULL, NULL, 0, 1, 0, 0, -1 }, + { NULL, "pavucontrol", NULL, 0, 1, 0, 0, -1 }, + { NULL, "blueman-manager", NULL, 0, 1, 0, 0, -1 }, + { "Firefox", "Places", NULL, 0, 1, 0, 0, -1 }, /* biblioteca/descargas */ + { "Firefox", "Toolkit", NULL, 0, 1, 0, 0, -1 }, /* picture-in-picture */ + { NULL, NULL, "Event Tester", 0, 0, 0, 1, -1 }, /* xev */ }; /* layout(s) */ @@ -70,10 +81,42 @@ static const char *lockcmd[] = { "slock", NULL }; /* Audio: tras cada cambio se avisa a slstatus (SIGUSR1) para que refresque la barra al momento */ #define STATUSREFRESH "; pkill -USR1 -x slstatus" -#define VOLUP SHCMD("wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+" STATUSREFRESH) -#define VOLDOWN SHCMD("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-" STATUSREFRESH) -#define VOLMUTE SHCMD("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle" STATUSREFRESH) -#define MICMUTE SHCMD("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle" STATUSREFRESH) +#define VOLUPCMD "wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+" STATUSREFRESH +#define VOLDOWNCMD "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-" STATUSREFRESH +#define VOLMUTECMD "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle" STATUSREFRESH +#define MICMUTECMD "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle" STATUSREFRESH +#define VOLUP SHCMD(VOLUPCMD) +#define VOLDOWN SHCMD(VOLDOWNCMD) +#define VOLMUTE SHCMD(VOLMUTECMD) +#define MICMUTE SHCMD(MICMUTECMD) + +/* + * Clics en la barra (parche statuscmd). El número es el byte que rodea cada + * zona en el config.h de slstatus; el botón pulsado llega en $BUTTON + * (1 izquierdo, 2 central, 3 derecho, 4/5 rueda arriba/abajo). + */ +static const StatusCmd statuscmds[] = { + /* red: izquierdo conectarse a una red, derecho el menú completo de nmtui */ + { "case $BUTTON in " + "1) st -c st-float -g 90x30 -e nmtui connect ;; " + "3) st -c st-float -g 90x30 -e nmtui ;; " + "esac", 1 }, + /* volumen: izquierdo pavucontrol, central silenciar, derecho micro, rueda subir/bajar */ + { "case $BUTTON in " + "1) pavucontrol ;; " + "2) " VOLMUTECMD " ;; " + "3) " MICMUTECMD " ;; " + "4) " VOLUPCMD " ;; " + "5) " VOLDOWNCMD " ;; " + "esac", 2 }, + /* bluetooth: izquierdo blueman, derecho encender/apagar el adaptador */ + { "case $BUTTON in " + "1) blueman-manager ;; " + "3) if bluetoothctl show | grep -q 'Powered: yes'; " + "then bluetoothctl power off; else bluetoothctl power on; fi >/dev/null" STATUSREFRESH " ;; " + "esac", 3 }, +}; +static const char *statuscmd[] = { "/bin/sh", "-c", NULL, NULL }; /* Capturas: se guardan en ~/Imágenes/Capturas y se copian al portapapeles */ #define SCREENSHOT(opts) SHCMD( \ @@ -123,6 +166,7 @@ static const Key keys[] = { TAGKEYS( XK_8, 7) TAGKEYS( XK_9, 8) { MODKEY|ShiftMask, XK_m, quit, {0} }, + { MODKEY|ShiftMask, XK_r, quit, {1} }, /* reiniciar dwm sin cerrar ventanas */ /* audio */ { 0, XF86XK_AudioRaiseVolume, spawn, VOLUP }, @@ -143,7 +187,11 @@ static const Button buttons[] = { { ClkLtSymbol, 0, Button1, setlayout, {0} }, { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, { ClkWinTitle, 0, Button2, zoom, {0} }, - { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkStatusText, 0, Button1, spawn, {.v = statuscmd } }, + { ClkStatusText, 0, Button2, spawn, {.v = statuscmd } }, + { ClkStatusText, 0, Button3, spawn, {.v = statuscmd } }, + { ClkStatusText, 0, Button4, spawn, {.v = statuscmd } }, + { ClkStatusText, 0, Button5, spawn, {.v = statuscmd } }, { ClkClientWin, MODKEY, Button1, movemouse, {0} }, { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, diff --git a/dwm/config.mk b/dwm/config.mk index 982dc21..49b3c6c 100644 --- a/dwm/config.mk +++ b/dwm/config.mk @@ -20,10 +20,11 @@ FREETYPEINC = /usr/include/freetype2 # OpenBSD (uncomment) #FREETYPEINC = ${X11INC}/freetype2 #MANPREFIX = ${PREFIX}/man +#KVMLIB = -lkvm # includes and libs INCS = -I${X11INC} -I${FREETYPEINC} -LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} +LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lX11-xcb -lxcb -lxcb-res ${KVMLIB} # flags CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} diff --git a/dwm/dwm.1 b/dwm/dwm.1 index ddc8321..7b6cadb 100644 --- a/dwm/dwm.1 +++ b/dwm/dwm.1 @@ -142,6 +142,9 @@ Add/remove all windows with nth tag to/from the view. .TP .B Mod1\-Shift\-q Quit dwm. +.TP +.B Mod1\-Control\-Shift\-q +Restart dwm. .SS Mouse commands .TP .B Mod1\-Button1 @@ -155,6 +158,13 @@ Resize focused window while dragging. Tiled windows will be toggled to the float .SH CUSTOMIZATION dwm is customized by creating a custom config.h and (re)compiling the source code. This keeps it fast, secure and simple. +.SH SIGNALS +.TP +.B SIGHUP - 1 +Restart the dwm process. +.TP +.B SIGTERM - 15 +Cleanly terminate the dwm process. .SH SEE ALSO .BR dmenu (1), .BR st (1) diff --git a/dwm/dwm.c b/dwm/dwm.c index c27ae47..70c0df2 100644 --- a/dwm/dwm.c +++ b/dwm/dwm.c @@ -40,6 +40,12 @@ #include #endif /* XINERAMA */ #include +#include +#include +#ifdef __OpenBSD__ +#include +#include +#endif /* __OpenBSD */ #include "drw.h" #include "util.h" @@ -61,7 +67,7 @@ enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ enum { SchemeNorm, SchemeSel }; /* color schemes */ enum { NetSupported, NetWMName, NetWMState, NetWMCheck, NetWMFullscreen, NetActiveWindow, NetWMWindowType, - NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ + NetWMWindowTypeDialog, NetClientList, NetClientInfo, NetLast }; /* EWMH atoms */ enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ @@ -91,9 +97,11 @@ struct Client { int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid; int bw, oldbw; unsigned int tags; - int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, isterminal, noswallow; + pid_t pid; Client *next; Client *snext; + Client *swallowing; Monitor *mon; Window win; }; @@ -138,9 +146,16 @@ typedef struct { const char *title; unsigned int tags; int isfloating; + int isterminal; + int noswallow; int monitor; } Rule; +typedef struct { + const char *cmd; + int id; +} StatusCmd; + /* function declarations */ static void applyrules(Client *c); static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); @@ -198,6 +213,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 setclienttagprop(Client *c); static void setfocus(Client *c); static void setfullscreen(Client *c, int fullscreen); static void setgaps(const Arg *arg); @@ -206,6 +222,8 @@ static void setmfact(const Arg *arg); static void setup(void); static void seturgent(Client *c, int urg); static void showhide(Client *c); +static void sighup(int unused); +static void sigterm(int unused); static void spawn(const Arg *arg); static void tag(const Arg *arg); static void tagmon(const Arg *arg); @@ -235,9 +253,18 @@ static int xerrordummy(Display *dpy, XErrorEvent *ee); static int xerrorstart(Display *dpy, XErrorEvent *ee); static void zoom(const Arg *arg); +static pid_t getparentprocess(pid_t p); +static int isdescprocess(pid_t p, pid_t c); +static Client *swallowingclient(Window w); +static Client *termforwin(const Client *c); +static pid_t winpid(Window w); + /* variables */ static const char broken[] = "broken"; static char stext[256]; +static int statusw; +static int statuscmdn; +static char lastbutton[] = "-"; static int screen; static int sw, sh; /* X display screen geometry width, height */ static int bh; /* bar height */ @@ -261,6 +288,7 @@ static void (*handler[LASTEvent]) (XEvent *) = { [UnmapNotify] = unmapnotify }; static Atom wmatom[WMLast], netatom[NetLast]; +static int restart = 0; static int running = 1; static Cur *cursor[CurLast]; static Clr **scheme; @@ -269,6 +297,8 @@ static Drw *drw; static Monitor *mons, *selmon; static Window root, wmcheckwin; +static xcb_connection_t *xcon; + /* configuration, allows nested code to access above variables */ #include "config.h" @@ -298,6 +328,8 @@ applyrules(Client *c) && (!r->class || strstr(class, r->class)) && (!r->instance || strstr(instance, r->instance))) { + c->isterminal = r->isterminal; + c->noswallow = r->noswallow; c->isfloating = r->isfloating; c->tags |= r->tags; for (m = mons; m && m->num != r->monitor; m = m->next); @@ -416,6 +448,53 @@ attachstack(Client *c) c->mon->stack = c; } +void +swallow(Client *p, Client *c) +{ + + if (c->noswallow > 0 || c->isterminal) + return; + if (c->noswallow < 0 && !swallowfloating && c->isfloating) + return; + + detach(c); + detachstack(c); + + setclientstate(c, WithdrawnState); + XUnmapWindow(dpy, p->win); + + p->swallowing = c; + c->mon = p->mon; + + Window w = p->win; + p->win = c->win; + c->win = w; + updatetitle(p); + XMoveResizeWindow(dpy, p->win, p->x, p->y, p->w, p->h); + arrange(p->mon); + configure(p); + updateclientlist(); +} + +void +unswallow(Client *c) +{ + c->win = c->swallowing->win; + + free(c->swallowing); + c->swallowing = NULL; + + /* unfullscreen the client */ + setfullscreen(c, 0); + updatetitle(c); + arrange(c->mon); + XMapWindow(dpy, c->win); + XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); + setclientstate(c, NormalState); + focus(NULL); + arrange(c->mon); +} + void buttonpress(XEvent *e) { @@ -442,9 +521,28 @@ buttonpress(XEvent *e) arg.ui = 1 << i; } else if (ev->x < x + TEXTW(selmon->ltsymbol)) click = ClkLtSymbol; - else if (ev->x > selmon->ww - (int)TEXTW(stext) + lrpad - 2) + else if (ev->x > selmon->ww - statusw) { + char *text, *s, ch; + *lastbutton = '0' + ev->button; + + x = selmon->ww - statusw; click = ClkStatusText; - else + + statuscmdn = 0; + for (text = s = stext; *s && x <= ev->x; s++) { + if ((unsigned char)(*s) < ' ') { + ch = *s; + *s = '\0'; + x += TEXTW(text) - lrpad; + *s = ch; + text = s + 1; + if (x >= ev->x) + break; + /* el mismo byte al final cierra la sección clicable */ + statuscmdn = (statuscmdn == ch) ? 0 : ch; + } + } + } else click = ClkWinTitle; } else if ((c = wintoclient(ev->window))) { focus(c); @@ -657,6 +755,9 @@ destroynotify(XEvent *e) if ((c = wintoclient(ev->window))) unmanage(c, 1); + + else if ((c = swallowingclient(ev->window))) + unmanage(c->swallowing, 1); } void @@ -711,9 +812,24 @@ drawbar(Monitor *m) /* draw status first so it can be overdrawn by tags later */ if (m == selmon) { /* status is only drawn on selected monitor */ + char *text, *s, ch; drw_setscheme(drw, scheme[SchemeNorm]); - tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ - drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); + + x = 0; + for (text = s = stext; *s; s++) { + if ((unsigned char)(*s) < ' ') { + ch = *s; + *s = '\0'; + tw = TEXTW(text) - lrpad; + drw_text(drw, m->ww - statusw + x, 0, tw, bh, 0, text, 0); + x += tw; + *s = ch; + text = s + 1; + } + } + tw = TEXTW(text) - lrpad + 2; + drw_text(drw, m->ww - statusw + x, 0, tw, bh, 0, text, 0); + tw = statusw; } for (c = m->clients; c; c = c->next) { @@ -1034,12 +1150,13 @@ killclient(const Arg *arg) void manage(Window w, XWindowAttributes *wa) { - Client *c, *t = NULL; + Client *c, *t = NULL, *term = NULL; Window trans = None; XWindowChanges wc; c = ecalloc(1, sizeof(Client)); c->win = w; + c->pid = winpid(w); /* geometry */ c->x = c->oldx = wa->x; c->y = c->oldy = wa->y; @@ -1054,6 +1171,12 @@ manage(Window w, XWindowAttributes *wa) } else { c->mon = selmon; applyrules(c); + /* las ventanas que flotan por una regla se abren centradas */ + if (c->isfloating) { + c->x = c->mon->wx + (c->mon->ww - WIDTH(c)) / 2; + c->y = c->mon->wy + (c->mon->wh - HEIGHT(c)) / 2; + } + term = termforwin(c); } if (c->x + WIDTH(c) > c->mon->wx + c->mon->ww) @@ -1071,6 +1194,26 @@ manage(Window w, XWindowAttributes *wa) updatewindowtype(c); updatesizehints(c); updatewmhints(c); + { + int format; + unsigned long *data = NULL, n = 0, extra; + Monitor *m; + Atom atom; + if (XGetWindowProperty(dpy, c->win, netatom[NetClientInfo], 0L, 2L, False, XA_CARDINAL, + &atom, &format, &n, &extra, (unsigned char **)&data) == Success && n == 2 && (*data & TAGMASK)) { + c->tags = *data & TAGMASK; + for (m = mons; m; m = m->next) { + if (m->num == *(data+1)) { + c->mon = m; + break; + } + } + } + if (data) + XFree(data); + } + setclienttagprop(c); + XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); grabbuttons(c, 0); if (!c->isfloating) @@ -1088,6 +1231,8 @@ manage(Window w, XWindowAttributes *wa) c->mon->sel = c; arrange(c->mon); XMapWindow(dpy, c->win); + if (term) + swallow(term, c); focus(NULL); } @@ -1261,6 +1406,7 @@ propertynotify(XEvent *e) void quit(const Arg *arg) { + if(arg->i) restart = 1; running = 0; } @@ -1432,6 +1578,7 @@ sendmon(Client *c, Monitor *m) c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ attach(c); attachstack(c); + setclienttagprop(c); if (c->isfullscreen) resizeclient(c, m->mx, m->my, m->mw, m->mh); focus(NULL); @@ -1566,6 +1713,9 @@ setup(void) /* clean up any zombies (inherited from .xinitrc etc) immediately */ while (waitpid(-1, NULL, WNOHANG) > 0); + signal(SIGHUP, sighup); + signal(SIGTERM, sigterm); + /* init screen */ screen = DefaultScreen(dpy); sw = DisplayWidth(dpy, screen); @@ -1592,6 +1742,7 @@ setup(void) netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); + netatom[NetClientInfo] = XInternAtom(dpy, "_NET_CLIENT_INFO", False); /* init cursors */ cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); cursor[CurResize] = drw_cur_create(drw, XC_sizing); @@ -1615,6 +1766,7 @@ setup(void) XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, PropModeReplace, (unsigned char *) netatom, NetLast); XDeleteProperty(dpy, root, netatom[NetClientList]); + XDeleteProperty(dpy, root, netatom[NetClientInfo]); /* select events */ wa.cursor = cursor[CurNormal]->cursor; wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask @@ -1657,6 +1809,20 @@ showhide(Client *c) } } +void +sighup(int unused) +{ + Arg a = {.i = 1}; + quit(&a); +} + +void +sigterm(int unused) +{ + Arg a = {.i = 0}; + quit(&a); +} + void spawn(const Arg *arg) { @@ -1667,6 +1833,17 @@ spawn(const Arg *arg) if (fork() == 0) { if (dpy) close(ConnectionNumber(dpy)); + if (arg->v == statuscmd) { + for (int i = 0; i < LENGTH(statuscmds); i++) { + if (statuscmdn == statuscmds[i].id) { + statuscmd[2] = statuscmds[i].cmd; + setenv("BUTTON", lastbutton, 1); + break; + } + } + if (!statuscmd[2]) + exit(EXIT_SUCCESS); + } setsid(); sigemptyset(&sa.sa_mask); @@ -1679,11 +1856,22 @@ spawn(const Arg *arg) } } +void +setclienttagprop(Client *c) +{ + long data[] = { (long) c->tags, (long) c->mon->num }; + XChangeProperty(dpy, c->win, netatom[NetClientInfo], XA_CARDINAL, 32, + PropModeReplace, (unsigned char *) data, 2); +} + void tag(const Arg *arg) { + Client *c; if (selmon->sel && arg->ui & TAGMASK) { + c = selmon->sel; selmon->sel->tags = arg->ui & TAGMASK; + setclienttagprop(c); focus(NULL); arrange(selmon); } @@ -1758,6 +1946,7 @@ toggletag(const Arg *arg) newtags = selmon->sel->tags ^ (arg->ui & TAGMASK); if (newtags) { selmon->sel->tags = newtags; + setclienttagprop(selmon->sel); focus(NULL); arrange(selmon); } @@ -1794,6 +1983,20 @@ unmanage(Client *c, int destroyed) Monitor *m = c->mon; XWindowChanges wc; + if (c->swallowing) { + unswallow(c); + return; + } + + Client *s = swallowingclient(c->win); + if (s) { + free(s->swallowing); + s->swallowing = NULL; + arrange(m); + focus(NULL); + return; + } + detach(c); detachstack(c); if (!destroyed) { @@ -1809,9 +2012,12 @@ unmanage(Client *c, int destroyed) XUngrabServer(dpy); } free(c); - focus(NULL); - updateclientlist(); - arrange(m); + + if (!s) { + arrange(m); + focus(NULL); + updateclientlist(); + } } void @@ -2018,8 +2224,23 @@ updatesizehints(Client *c) void updatestatus(void) { - if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) + if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) { strcpy(stext, "dwm-"VERSION); + statusw = TEXTW(stext) - lrpad + 2; + } else { + char *text, *s, ch; + statusw = 0; + for (text = s = stext; *s; s++) { + if ((unsigned char)(*s) < ' ') { + ch = *s; + *s = '\0'; + statusw += TEXTW(text) - lrpad; + *s = ch; + text = s + 1; + } + } + statusw += TEXTW(text) - lrpad + 2; + } drawbar(selmon); } @@ -2075,6 +2296,136 @@ view(const Arg *arg) arrange(selmon); } +pid_t +winpid(Window w) +{ + + pid_t result = 0; + +#ifdef __linux__ + xcb_res_client_id_spec_t spec = {0}; + spec.client = w; + spec.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID; + + xcb_generic_error_t *e = NULL; + xcb_res_query_client_ids_cookie_t c = xcb_res_query_client_ids(xcon, 1, &spec); + xcb_res_query_client_ids_reply_t *r = xcb_res_query_client_ids_reply(xcon, c, &e); + + if (!r) + return (pid_t)0; + + xcb_res_client_id_value_iterator_t i = xcb_res_query_client_ids_ids_iterator(r); + for (; i.rem; xcb_res_client_id_value_next(&i)) { + spec = i.data->spec; + if (spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) { + uint32_t *t = xcb_res_client_id_value_value(i.data); + result = *t; + break; + } + } + + free(r); + + if (result == (pid_t)-1) + result = 0; + +#endif /* __linux__ */ + +#ifdef __OpenBSD__ + Atom type; + int format; + unsigned long len, bytes; + unsigned char *prop; + pid_t ret; + + if (XGetWindowProperty(dpy, w, XInternAtom(dpy, "_NET_WM_PID", 0), 0, 1, False, AnyPropertyType, &type, &format, &len, &bytes, &prop) != Success || !prop) + return 0; + + ret = *(pid_t*)prop; + XFree(prop); + result = ret; + +#endif /* __OpenBSD__ */ + return result; +} + +pid_t +getparentprocess(pid_t p) +{ + unsigned int v = 0; + +#ifdef __linux__ + FILE *f; + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)p); + + if (!(f = fopen(buf, "r"))) + return 0; + + fscanf(f, "%*u %*s %*c %u", &v); + fclose(f); +#endif /* __linux__*/ + +#ifdef __OpenBSD__ + int n; + kvm_t *kd; + struct kinfo_proc *kp; + + kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, NULL); + if (!kd) + return 0; + + kp = kvm_getprocs(kd, KERN_PROC_PID, p, sizeof(*kp), &n); + v = kp->p_ppid; +#endif /* __OpenBSD__ */ + + return (pid_t)v; +} + +int +isdescprocess(pid_t p, pid_t c) +{ + while (p != c && c != 0) + c = getparentprocess(c); + + return (int)c; +} + +Client * +termforwin(const Client *w) +{ + Client *c; + Monitor *m; + + if (!w->pid || w->isterminal) + return NULL; + + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) { + if (c->isterminal && !c->swallowing && c->pid && isdescprocess(c->pid, w->pid)) + return c; + } + } + + return NULL; +} + +Client * +swallowingclient(Window w) +{ + Client *c; + Monitor *m; + + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) { + if (c->swallowing && c->swallowing->win == w) + return c; + } + } + + return NULL; +} + Client * wintoclient(Window w) { @@ -2164,14 +2515,26 @@ main(int argc, char *argv[]) fputs("warning: no locale support\n", stderr); if (!(dpy = XOpenDisplay(NULL))) die("dwm: cannot open display"); + if (!(xcon = XGetXCBConnection(dpy))) + die("dwm: cannot get xcb connection\n"); checkotherwm(); setup(); #ifdef __OpenBSD__ - if (pledge("stdio rpath proc exec", NULL) == -1) + if (pledge("stdio rpath proc exec ps", NULL) == -1) die("pledge"); #endif /* __OpenBSD__ */ scan(); run(); + if (restart) { + /* volver a mostrar las terminales tragadas (swallow); si no, el dwm + * nuevo no las ve y se quedan ocultas para siempre */ + for (Monitor *m = mons; m; m = m->next) + for (Client *c = m->clients; c; c = c->next) + if (c->swallowing) + XMapWindow(dpy, c->swallowing->win); + XSync(dpy, False); + execvp(argv[0], argv); + } cleanup(); XCloseDisplay(dpy); return EXIT_SUCCESS; diff --git a/dwm/patches/dwm-preserveonrestart-6.3.diff b/dwm/patches/dwm-preserveonrestart-6.3.diff new file mode 100644 index 0000000..079cf05 --- /dev/null +++ b/dwm/patches/dwm-preserveonrestart-6.3.diff @@ -0,0 +1,118 @@ +From 713fa8650f5a20006451ebcccf57a4512e83bae8 Mon Sep 17 00:00:00 2001 +From: Arda Atci +Date: Wed, 18 May 2022 17:23:16 +0300 +Subject: [PATCH] preserve clients on old tags when renewing dwm + +By default, when dwm is recompiled-restarted all clients will +lose it's current tag and collapse to first tag. This patch preserves +clients on old tags, however note that layout order is not preserved. + +--- + dwm.c | 38 +++++++++++++++++++++++++++++++++++++- + 1 file changed, 37 insertions(+), 1 deletion(-) + +diff --git a/dwm.c b/dwm.c +index a96f33c..a12e0bd 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -62,7 +62,7 @@ enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ + enum { SchemeNorm, SchemeSel }; /* color schemes */ + enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, +- NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ ++ NetWMWindowTypeDialog, NetClientList, NetClientInfo, NetLast }; /* EWMH atoms */ + enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ + enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ +@@ -198,6 +198,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 setclienttagprop(Client *c); + static void setfocus(Client *c); + static void setfullscreen(Client *c, int fullscreen); + static void setlayout(const Arg *arg); +@@ -1060,6 +1061,26 @@ manage(Window w, XWindowAttributes *wa) + updatewindowtype(c); + updatesizehints(c); + updatewmhints(c); ++ { ++ int format; ++ unsigned long *data, n, extra; ++ Monitor *m; ++ Atom atom; ++ if (XGetWindowProperty(dpy, c->win, netatom[NetClientInfo], 0L, 2L, False, XA_CARDINAL, ++ &atom, &format, &n, &extra, (unsigned char **)&data) == Success && n == 2) { ++ c->tags = *data; ++ for (m = mons; m; m = m->next) { ++ if (m->num == *(data+1)) { ++ c->mon = m; ++ break; ++ } ++ } ++ } ++ if (n > 0) ++ XFree(data); ++ } ++ setclienttagprop(c); ++ + XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); + grabbuttons(c, 0); + if (!c->isfloating) +@@ -1423,6 +1444,7 @@ sendmon(Client *c, Monitor *m) + c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ + attach(c); + attachstack(c); ++ setclienttagprop(c); + focus(NULL); + arrange(NULL); + } +@@ -1566,6 +1588,7 @@ setup(void) + netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); ++ netatom[NetClientInfo] = XInternAtom(dpy, "_NET_CLIENT_INFO", False); + /* init cursors */ + cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); + cursor[CurResize] = drw_cur_create(drw, XC_sizing); +@@ -1589,6 +1612,7 @@ setup(void) + XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, + PropModeReplace, (unsigned char *) netatom, NetLast); + XDeleteProperty(dpy, root, netatom[NetClientList]); ++ XDeleteProperty(dpy, root, netatom[NetClientInfo]); + /* select events */ + wa.cursor = cursor[CurNormal]->cursor; + wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask +@@ -1656,11 +1680,22 @@ spawn(const Arg *arg) + } + } + ++void ++setclienttagprop(Client *c) ++{ ++ long data[] = { (long) c->tags, (long) c->mon->num }; ++ XChangeProperty(dpy, c->win, netatom[NetClientInfo], XA_CARDINAL, 32, ++ PropModeReplace, (unsigned char *) data, 2); ++} ++ + void + tag(const Arg *arg) + { ++ Client *c; + if (selmon->sel && arg->ui & TAGMASK) { ++ c = selmon->sel; + selmon->sel->tags = arg->ui & TAGMASK; ++ setclienttagprop(c); + focus(NULL); + arrange(selmon); + } +@@ -1735,6 +1770,7 @@ toggletag(const Arg *arg) + newtags = selmon->sel->tags ^ (arg->ui & TAGMASK); + if (newtags) { + selmon->sel->tags = newtags; ++ setclienttagprop(selmon->sel); + focus(NULL); + arrange(selmon); + } +-- +2.36.1 diff --git a/dwm/patches/dwm-restartsig-20180523-6.2.diff b/dwm/patches/dwm-restartsig-20180523-6.2.diff new file mode 100644 index 0000000..f1f8680 --- /dev/null +++ b/dwm/patches/dwm-restartsig-20180523-6.2.diff @@ -0,0 +1,139 @@ +From 2991f37f0aaf44b9f9b11e7893ff0af8eb88f649 Mon Sep 17 00:00:00 2001 +From: Christopher Drelich +Date: Wed, 23 May 2018 22:50:38 -0400 +Subject: [PATCH] Modifies quit to handle restarts and adds SIGHUP and SIGTERM + handlers. + +Modified quit() to restart if it receives arg .i = 1 +MOD+CTRL+SHIFT+Q was added to confid.def.h to do just that. + +Signal handlers were handled for SIGHUP and SIGTERM. +If dwm receives these signals it calls quit() with +arg .i = to 1 or 0, respectively. + +To restart dwm: +MOD+CTRL+SHIFT+Q +or +kill -HUP dwmpid + +To quit dwm cleanly: +MOD+SHIFT+Q +or +kill -TERM dwmpid +--- + config.def.h | 1 + + dwm.1 | 10 ++++++++++ + dwm.c | 22 ++++++++++++++++++++++ + 3 files changed, 33 insertions(+) + +diff --git a/config.def.h b/config.def.h +index a9ac303..e559429 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -94,6 +94,7 @@ static Key keys[] = { + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + { MODKEY|ShiftMask, XK_q, quit, {0} }, ++ { MODKEY|ControlMask|ShiftMask, XK_q, quit, {1} }, + }; + + /* button definitions */ +diff --git a/dwm.1 b/dwm.1 +index 13b3729..36a331c 100644 +--- a/dwm.1 ++++ b/dwm.1 +@@ -142,6 +142,9 @@ Add/remove all windows with nth tag to/from the view. + .TP + .B Mod1\-Shift\-q + Quit dwm. ++.TP ++.B Mod1\-Control\-Shift\-q ++Restart dwm. + .SS Mouse commands + .TP + .B Mod1\-Button1 +@@ -155,6 +158,13 @@ Resize focused window while dragging. Tiled windows will be toggled to the float + .SH CUSTOMIZATION + dwm is customized by creating a custom config.h and (re)compiling the source + code. This keeps it fast, secure and simple. ++.SH SIGNALS ++.TP ++.B SIGHUP - 1 ++Restart the dwm process. ++.TP ++.B SIGTERM - 15 ++Cleanly terminate the dwm process. + .SH SEE ALSO + .BR dmenu (1), + .BR st (1) +diff --git a/dwm.c b/dwm.c +index bb95e26..286eecd 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -205,6 +205,8 @@ static void setup(void); + static void seturgent(Client *c, int urg); + static void showhide(Client *c); + static void sigchld(int unused); ++static void sighup(int unused); ++static void sigterm(int unused); + static void spawn(const Arg *arg); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); +@@ -260,6 +262,7 @@ static void (*handler[LASTEvent]) (XEvent *) = { + [UnmapNotify] = unmapnotify + }; + static Atom wmatom[WMLast], netatom[NetLast]; ++static int restart = 0; + static int running = 1; + static Cur *cursor[CurLast]; + static Clr **scheme; +@@ -1248,6 +1251,7 @@ propertynotify(XEvent *e) + void + quit(const Arg *arg) + { ++ if(arg->i) restart = 1; + running = 0; + } + +@@ -1536,6 +1540,9 @@ setup(void) + /* clean up any zombies immediately */ + sigchld(0); + ++ signal(SIGHUP, sighup); ++ signal(SIGTERM, sigterm); ++ + /* init screen */ + screen = DefaultScreen(dpy); + sw = DisplayWidth(dpy, screen); +@@ -1637,6 +1644,20 @@ sigchld(int unused) + } + + void ++sighup(int unused) ++{ ++ Arg a = {.i = 1}; ++ quit(&a); ++} ++ ++void ++sigterm(int unused) ++{ ++ Arg a = {.i = 0}; ++ quit(&a); ++} ++ ++void + spawn(const Arg *arg) + { + if (arg->v == dmenucmd) +@@ -2139,6 +2160,7 @@ main(int argc, char *argv[]) + setup(); + scan(); + run(); ++ if(restart) execvp(argv[0], argv); + cleanup(); + XCloseDisplay(dpy); + return EXIT_SUCCESS; +-- +2.7.4 + diff --git a/dwm/patches/dwm-statuscmd-nosignal-20210402-67d76bd.diff b/dwm/patches/dwm-statuscmd-nosignal-20210402-67d76bd.diff new file mode 100644 index 0000000..1dc5c53 --- /dev/null +++ b/dwm/patches/dwm-statuscmd-nosignal-20210402-67d76bd.diff @@ -0,0 +1,167 @@ +From 02c4a28dd7f3a88eef3a4e533ace35f79cf09d57 Mon Sep 17 00:00:00 2001 +From: Daniel Bylinka +Date: Fri, 2 Apr 2021 19:34:38 +0200 +Subject: [PATCH] [statuscmd] Run shell commands based on mouse location and + button + +--- + config.def.h | 10 ++++++- + dwm.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++--- + 2 files changed, 81 insertions(+), 5 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1c0b587..8f88366 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -59,6 +59,12 @@ static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() + static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; + static const char *termcmd[] = { "st", NULL }; + ++/* commands spawned when clicking statusbar, the mouse button pressed is exported as BUTTON */ ++static const StatusCmd statuscmds[] = { ++ { "notify-send Mouse$BUTTON", 1 }, ++}; ++static const char *statuscmd[] = { "/bin/sh", "-c", NULL, NULL }; ++ + static Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_p, spawn, {.v = dmenucmd } }, +@@ -103,7 +109,9 @@ static Button buttons[] = { + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, +- { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, ++ { ClkStatusText, 0, Button1, spawn, {.v = statuscmd } }, ++ { ClkStatusText, 0, Button2, spawn, {.v = statuscmd } }, ++ { ClkStatusText, 0, Button3, spawn, {.v = statuscmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, +diff --git a/dwm.c b/dwm.c +index b0b3466..eb478a5 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -141,6 +141,11 @@ typedef struct { + int monitor; + } Rule; + ++typedef struct { ++ const char *cmd; ++ int id; ++} StatusCmd; ++ + /* function declarations */ + static void applyrules(Client *c); + static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); +@@ -238,6 +243,9 @@ static void zoom(const Arg *arg); + /* variables */ + static const char broken[] = "broken"; + static char stext[256]; ++static int statusw; ++static int statuscmdn; ++static char lastbutton[] = "-"; + static int screen; + static int sw, sh; /* X display screen geometry width, height */ + static int bh, blw = 0; /* bar geometry */ +@@ -440,8 +448,27 @@ buttonpress(XEvent *e) + arg.ui = 1 << i; + } else if (ev->x < x + blw) + click = ClkLtSymbol; +- else if (ev->x > selmon->ww - (int)TEXTW(stext)) ++ else if (ev->x > selmon->ww - statusw) { ++ char *text, *s, ch; ++ *lastbutton = '0' + ev->button; ++ ++ x = selmon->ww - statusw; + click = ClkStatusText; ++ ++ statuscmdn = 0; ++ for (text = s = stext; *s && x <= ev->x; s++) { ++ if ((unsigned char)(*s) < ' ') { ++ ch = *s; ++ *s = '\0'; ++ x += TEXTW(text) - lrpad; ++ *s = ch; ++ text = s + 1; ++ if (x >= ev->x) ++ break; ++ statuscmdn = ch; ++ } ++ } ++ } + else + click = ClkWinTitle; + } else if ((c = wintoclient(ev->window))) { +@@ -704,9 +731,24 @@ drawbar(Monitor *m) + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ ++ char *text, *s, ch; + drw_setscheme(drw, scheme[SchemeNorm]); +- tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ +- drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); ++ ++ x = 0; ++ for (text = s = stext; *s; s++) { ++ if ((unsigned char)(*s) < ' ') { ++ ch = *s; ++ *s = '\0'; ++ tw = TEXTW(text) - lrpad; ++ drw_text(drw, m->ww - statusw + x, 0, tw, bh, 0, text, 0); ++ x += tw; ++ *s = ch; ++ text = s + 1; ++ } ++ } ++ tw = TEXTW(text) - lrpad + 2; ++ drw_text(drw, m->ww - statusw + x, 0, tw, bh, 0, text, 0); ++ tw = statusw; + } + + for (c = m->clients; c; c = c->next) { +@@ -1645,6 +1687,17 @@ spawn(const Arg *arg) + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); ++ if (arg->v == statuscmd) { ++ for (int i = 0; i < LENGTH(statuscmds); i++) { ++ if (statuscmdn == statuscmds[i].id) { ++ statuscmd[2] = statuscmds[i].cmd; ++ setenv("BUTTON", lastbutton, 1); ++ break; ++ } ++ } ++ if (!statuscmd[2]) ++ exit(EXIT_SUCCESS); ++ } + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + fprintf(stderr, "dwm: execvp %s", ((char **)arg->v)[0]); +@@ -1990,8 +2043,23 @@ updatesizehints(Client *c) + void + updatestatus(void) + { +- if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) ++ if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) { + strcpy(stext, "dwm-"VERSION); ++ statusw = TEXTW(stext) - lrpad + 2; ++ } else { ++ char *text, *s, ch; ++ statusw = 0; ++ for (text = s = stext; *s; s++) { ++ if ((unsigned char)(*s) < ' ') { ++ ch = *s; ++ *s = '\0'; ++ statusw += TEXTW(text) - lrpad; ++ *s = ch; ++ text = s + 1; ++ } ++ } ++ statusw += TEXTW(text) - lrpad + 2; ++ } + drawbar(selmon); + } + +-- +2.31.0 + diff --git a/dwm/patches/dwm-swallow-6.3.diff b/dwm/patches/dwm-swallow-6.3.diff new file mode 100644 index 0000000..47586a0 --- /dev/null +++ b/dwm/patches/dwm-swallow-6.3.diff @@ -0,0 +1,412 @@ +From 0cf9a007511f7dfd7dd94171b172562ebac9b6d5 Mon Sep 17 00:00:00 2001 +From: Tom Schwindl +Date: Sat, 10 Sep 2022 12:51:09 +0200 +Subject: [PATCH] 6.3 swallow patch + +--- + config.def.h | 9 +- + config.mk | 3 +- + dwm.c | 235 +++++++++++++++++++++++++++++++++++++++++++++++++-- + 3 files changed, 237 insertions(+), 10 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 061ad662f82a..0b2b8ffd30d5 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -3,6 +3,7 @@ + /* appearance */ + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const unsigned int snap = 32; /* snap pixel */ ++static const int swallowfloating = 0; /* 1 means swallow floating windows by default */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ + static const char *fonts[] = { "monospace:size=10" }; +@@ -26,9 +27,11 @@ 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 isterminal noswallow monitor */ ++ { "Gimp", NULL, NULL, 0, 1, 0, 0, -1 }, ++ { "Firefox", NULL, NULL, 1 << 8, 0, 0, -1, -1 }, ++ { "St", NULL, NULL, 0, 0, 1, 0, -1 }, ++ { NULL, NULL, "Event Tester", 0, 0, 0, 1, -1 }, /* xev */ + }; + + /* layout(s) */ +diff --git a/config.mk b/config.mk +index 81c493ef4aff..52d1ebf30bec 100644 +--- a/config.mk ++++ b/config.mk +@@ -20,10 +20,11 @@ FREETYPEINC = /usr/include/freetype2 + # OpenBSD (uncomment) + #FREETYPEINC = ${X11INC}/freetype2 + #MANPREFIX = ${PREFIX}/man ++#KVMLIB = -lkvm + + # includes and libs + INCS = -I${X11INC} -I${FREETYPEINC} +-LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} ++LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lX11-xcb -lxcb -lxcb-res ${KVMLIB} + + # flags + CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} +diff --git a/dwm.c b/dwm.c +index e5efb6a22806..e68294b6b679 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -40,6 +40,12 @@ + #include + #endif /* XINERAMA */ + #include ++#include ++#include ++#ifdef __OpenBSD__ ++#include ++#include ++#endif /* __OpenBSD */ + + #include "drw.h" + #include "util.h" +@@ -92,9 +98,11 @@ struct Client { + int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid; + int bw, oldbw; + unsigned int tags; +- int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; ++ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, isterminal, noswallow; ++ pid_t pid; + Client *next; + Client *snext; ++ Client *swallowing; + Monitor *mon; + Window win; + }; +@@ -138,6 +146,8 @@ typedef struct { + const char *title; + unsigned int tags; + int isfloating; ++ int isterminal; ++ int noswallow; + int monitor; + } Rule; + +@@ -235,6 +245,12 @@ static int xerrordummy(Display *dpy, XErrorEvent *ee); + static int xerrorstart(Display *dpy, XErrorEvent *ee); + static void zoom(const Arg *arg); + ++static pid_t getparentprocess(pid_t p); ++static int isdescprocess(pid_t p, pid_t c); ++static Client *swallowingclient(Window w); ++static Client *termforwin(const Client *c); ++static pid_t winpid(Window w); ++ + /* variables */ + static const char broken[] = "broken"; + static char stext[256]; +@@ -269,6 +285,8 @@ static Drw *drw; + static Monitor *mons, *selmon; + static Window root, wmcheckwin; + ++static xcb_connection_t *xcon; ++ + /* configuration, allows nested code to access above variables */ + #include "config.h" + +@@ -298,6 +316,8 @@ applyrules(Client *c) + && (!r->class || strstr(class, r->class)) + && (!r->instance || strstr(instance, r->instance))) + { ++ c->isterminal = r->isterminal; ++ c->noswallow = r->noswallow; + c->isfloating = r->isfloating; + c->tags |= r->tags; + for (m = mons; m && m->num != r->monitor; m = m->next); +@@ -416,6 +436,53 @@ attachstack(Client *c) + c->mon->stack = c; + } + ++void ++swallow(Client *p, Client *c) ++{ ++ ++ if (c->noswallow || c->isterminal) ++ return; ++ if (c->noswallow && !swallowfloating && c->isfloating) ++ return; ++ ++ detach(c); ++ detachstack(c); ++ ++ setclientstate(c, WithdrawnState); ++ XUnmapWindow(dpy, p->win); ++ ++ p->swallowing = c; ++ c->mon = p->mon; ++ ++ Window w = p->win; ++ p->win = c->win; ++ c->win = w; ++ updatetitle(p); ++ XMoveResizeWindow(dpy, p->win, p->x, p->y, p->w, p->h); ++ arrange(p->mon); ++ configure(p); ++ updateclientlist(); ++} ++ ++void ++unswallow(Client *c) ++{ ++ c->win = c->swallowing->win; ++ ++ free(c->swallowing); ++ c->swallowing = NULL; ++ ++ /* unfullscreen the client */ ++ setfullscreen(c, 0); ++ updatetitle(c); ++ arrange(c->mon); ++ XMapWindow(dpy, c->win); ++ XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); ++ setclientstate(c, NormalState); ++ focus(NULL); ++ arrange(c->mon); ++} ++ + void + buttonpress(XEvent *e) + { +@@ -656,6 +723,9 @@ destroynotify(XEvent *e) + + if ((c = wintoclient(ev->window))) + unmanage(c, 1); ++ ++ else if ((c = swallowingclient(ev->window))) ++ unmanage(c->swallowing, 1); + } + + void +@@ -1022,12 +1092,13 @@ killclient(const Arg *arg) + void + manage(Window w, XWindowAttributes *wa) + { +- Client *c, *t = NULL; ++ Client *c, *t = NULL, *term = NULL; + Window trans = None; + XWindowChanges wc; + + c = ecalloc(1, sizeof(Client)); + c->win = w; ++ c->pid = winpid(w); + /* geometry */ + c->x = c->oldx = wa->x; + c->y = c->oldy = wa->y; +@@ -1042,6 +1113,7 @@ manage(Window w, XWindowAttributes *wa) + } else { + c->mon = selmon; + applyrules(c); ++ term = termforwin(c); + } + + if (c->x + WIDTH(c) > c->mon->wx + c->mon->ww) +@@ -1076,6 +1148,8 @@ manage(Window w, XWindowAttributes *wa) + c->mon->sel = c; + arrange(c->mon); + XMapWindow(dpy, c->win); ++ if (term) ++ swallow(term, c); + focus(NULL); + } + +@@ -1763,6 +1837,20 @@ unmanage(Client *c, int destroyed) + Monitor *m = c->mon; + XWindowChanges wc; + ++ if (c->swallowing) { ++ unswallow(c); ++ return; ++ } ++ ++ Client *s = swallowingclient(c->win); ++ if (s) { ++ free(s->swallowing); ++ s->swallowing = NULL; ++ arrange(m); ++ focus(NULL); ++ return; ++ } ++ + detach(c); + detachstack(c); + if (!destroyed) { +@@ -1778,9 +1866,12 @@ unmanage(Client *c, int destroyed) + XUngrabServer(dpy); + } + free(c); +- focus(NULL); +- updateclientlist(); +- arrange(m); ++ ++ if (!s) { ++ arrange(m); ++ focus(NULL); ++ updateclientlist(); ++ } + } + + void +@@ -2044,6 +2135,136 @@ view(const Arg *arg) + arrange(selmon); + } + ++pid_t ++winpid(Window w) ++{ ++ ++ pid_t result = 0; ++ ++#ifdef __linux__ ++ xcb_res_client_id_spec_t spec = {0}; ++ spec.client = w; ++ spec.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID; ++ ++ xcb_generic_error_t *e = NULL; ++ xcb_res_query_client_ids_cookie_t c = xcb_res_query_client_ids(xcon, 1, &spec); ++ xcb_res_query_client_ids_reply_t *r = xcb_res_query_client_ids_reply(xcon, c, &e); ++ ++ if (!r) ++ return (pid_t)0; ++ ++ xcb_res_client_id_value_iterator_t i = xcb_res_query_client_ids_ids_iterator(r); ++ for (; i.rem; xcb_res_client_id_value_next(&i)) { ++ spec = i.data->spec; ++ if (spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) { ++ uint32_t *t = xcb_res_client_id_value_value(i.data); ++ result = *t; ++ break; ++ } ++ } ++ ++ free(r); ++ ++ if (result == (pid_t)-1) ++ result = 0; ++ ++#endif /* __linux__ */ ++ ++#ifdef __OpenBSD__ ++ Atom type; ++ int format; ++ unsigned long len, bytes; ++ unsigned char *prop; ++ pid_t ret; ++ ++ if (XGetWindowProperty(dpy, w, XInternAtom(dpy, "_NET_WM_PID", 0), 0, 1, False, AnyPropertyType, &type, &format, &len, &bytes, &prop) != Success || !prop) ++ return 0; ++ ++ ret = *(pid_t*)prop; ++ XFree(prop); ++ result = ret; ++ ++#endif /* __OpenBSD__ */ ++ return result; ++} ++ ++pid_t ++getparentprocess(pid_t p) ++{ ++ unsigned int v = 0; ++ ++#ifdef __linux__ ++ FILE *f; ++ char buf[256]; ++ snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)p); ++ ++ if (!(f = fopen(buf, "r"))) ++ return 0; ++ ++ fscanf(f, "%*u %*s %*c %u", &v); ++ fclose(f); ++#endif /* __linux__*/ ++ ++#ifdef __OpenBSD__ ++ int n; ++ kvm_t *kd; ++ struct kinfo_proc *kp; ++ ++ kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, NULL); ++ if (!kd) ++ return 0; ++ ++ kp = kvm_getprocs(kd, KERN_PROC_PID, p, sizeof(*kp), &n); ++ v = kp->p_ppid; ++#endif /* __OpenBSD__ */ ++ ++ return (pid_t)v; ++} ++ ++int ++isdescprocess(pid_t p, pid_t c) ++{ ++ while (p != c && c != 0) ++ c = getparentprocess(c); ++ ++ return (int)c; ++} ++ ++Client * ++termforwin(const Client *w) ++{ ++ Client *c; ++ Monitor *m; ++ ++ if (!w->pid || w->isterminal) ++ return NULL; ++ ++ for (m = mons; m; m = m->next) { ++ for (c = m->clients; c; c = c->next) { ++ if (c->isterminal && !c->swallowing && c->pid && isdescprocess(c->pid, w->pid)) ++ return c; ++ } ++ } ++ ++ return NULL; ++} ++ ++Client * ++swallowingclient(Window w) ++{ ++ Client *c; ++ Monitor *m; ++ ++ for (m = mons; m; m = m->next) { ++ for (c = m->clients; c; c = c->next) { ++ if (c->swallowing && c->swallowing->win == w) ++ return c; ++ } ++ } ++ ++ return NULL; ++} ++ + Client * + wintoclient(Window w) + { +@@ -2133,10 +2354,12 @@ main(int argc, char *argv[]) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("dwm: cannot open display"); ++ if (!(xcon = XGetXCBConnection(dpy))) ++ die("dwm: cannot get xcb connection\n"); + checkotherwm(); + setup(); + #ifdef __OpenBSD__ +- if (pledge("stdio rpath proc exec", NULL) == -1) ++ if (pledge("stdio rpath proc exec ps", NULL) == -1) + die("pledge"); + #endif /* __OpenBSD__ */ + scan(); +-- +2.37.2 + From 6e3577087b5c548444e424fb8f4d6bec9791079f Mon Sep 17 00:00:00 2001 From: alejandrogs73 Date: Sat, 26 Sep 2026 19:39:02 +0200 Subject: [PATCH 3/5] slstatus: red sin procesos, zonas clicables, BT OFF y refresco cada 2 s Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MymT1wzMgXhfC3ZwHqNAVF --- slstatus/config.h | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/slstatus/config.h b/slstatus/config.h index a2ab6f3..66018ae 100644 --- a/slstatus/config.h +++ b/slstatus/config.h @@ -1,7 +1,7 @@ /* See LICENSE file for copyright and license details. */ /* interval between updates (in ms) */ -const unsigned int interval = 1000; +const unsigned int interval = 2000; /* text to show if no value can be retrieved */ static const char unknown_str[] = "N/A"; @@ -21,18 +21,39 @@ static const char unknown_str[] = "N/A"; * run_command output of a shell command command * wifi_essid WiFi network name interface name (wlan0) */ +/* + * Red sin lanzar procesos: ETH si hay cable en enp3s0; si no, la red WiFi de + * wlp4s0 (se lee por netlink con el componente wifi_essid de slstatus). + */ +static const char * +red(const char *unused) +{ + const char *s; + + if ((s = cat("/sys/class/net/enp3s0/operstate")) && !strcmp(s, "up")) + return "ETH"; + if (!(s = wifi_essid("wlp4s0"))) + return "WiFi: N/A"; + return bprintf("WiFi: %s", s); +} + +/* + * Los bytes \001, \002 y \003 delimitan las zonas clicables de la barra + * (parche statuscmd de dwm): la red, el volumen y el bluetooth. Lo que hace + * cada clic está en statuscmds, en el config.h de dwm. + */ static const struct arg args[] = { - /* function format argument */ - { cpu_perc, "[ CPU: %s%% ] ", NULL }, - { ram_perc, "[ RAM: %s%% ] ", NULL }, - /* ETH si hay cable en enp3s0; si no, la red WiFi de wlp4s0 */ - { run_command, "[ %s ] ", "if [ \"$(cat /sys/class/net/enp3s0/operstate)\" = up ]; then echo ETH; " - "else s=$(iw dev wlp4s0 link | sed -n 's/^[[:space:]]*SSID: //p'); echo \"WiFi: ${s:-N/A}\"; fi" }, - { battery_perc, "[ BAT: %s%%", "BAT1" }, - { battery_state, " %s ] ", "BAT1" }, + /* function format argument */ + { cpu_perc, "[ CPU: %s%% ] ", NULL }, + { ram_perc, "[ RAM: %s%% ] ", NULL }, + { red, "\001[ %s ]\001 ", NULL }, + { battery_perc, "[ BAT: %s%%", "BAT1" }, + { battery_state, " %s ] ", "BAT1" }, /* mismo volumen que maneja wpctl (PipeWire); MIC OFF si el micro está silenciado */ - { run_command, "[ VOL: %s ] ", "wpctl get-volume @DEFAULT_AUDIO_SINK@ | awk '{ printf \"%d%%\", $2 * 100 + 0.5 } /MUTED/ { printf \" MUT\" }'; " - "wpctl get-volume @DEFAULT_AUDIO_SOURCE@ | grep -q MUTED && printf ' MIC OFF'" }, - { run_command, "[ BT: %s ] ", "bluetoothctl devices Connected | head -1 | cut -d' ' -f3-" }, - { datetime, "[ %s ]", "%d/%m/%y %H:%M:%S" }, + { run_command, "\002[ VOL: %s ]\002 ", "wpctl get-volume @DEFAULT_AUDIO_SINK@ | awk '{ printf \"%d%%\", $2 * 100 + 0.5 } /MUTED/ { printf \" MUT\" }'; " + "wpctl get-volume @DEFAULT_AUDIO_SOURCE@ | grep -q MUTED && printf ' MIC OFF'" }, + /* OFF si el adaptador está apagado; si no, el primer dispositivo conectado */ + { run_command, "\003[ BT: %s ]\003 ", "bluetoothctl show | grep -q 'Powered: yes' || { echo OFF; exit; }; " + "bluetoothctl devices Connected | head -1 | cut -d' ' -f3-" }, + { datetime, "[ %s ]", "%d/%m/%y %H:%M" }, }; From c736ea8b01934d255b7a2e64c5691280beb2a907 Mon Sep 17 00:00:00 2001 From: alejandrogs73 Date: Sat, 26 Sep 2026 19:39:02 +0200 Subject: [PATCH 4/5] =?UTF-8?q?sistema:=20bloquear=20con=20slock=20antes?= =?UTF-8?q?=20de=20suspender=20y=20evitar=20la=20doble=20suspensi=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit elogind ignora la tapa y los botones para que solo actúe acpid (zzz), y un hook de zzz lanza slock antes de suspender. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MymT1wzMgXhfC3ZwHqNAVF --- .../etc/elogind/logind.conf.d/10-acpid.conf | 11 ++++++++++ sistema/etc/zzz.d/suspend/10-slock | 22 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 sistema/etc/elogind/logind.conf.d/10-acpid.conf create mode 100755 sistema/etc/zzz.d/suspend/10-slock diff --git a/sistema/etc/elogind/logind.conf.d/10-acpid.conf b/sistema/etc/elogind/logind.conf.d/10-acpid.conf new file mode 100644 index 0000000..6ea7964 --- /dev/null +++ b/sistema/etc/elogind/logind.conf.d/10-acpid.conf @@ -0,0 +1,11 @@ +# acpid ya se encarga de la tapa y del botón de suspender (llama a zzz) y del +# botón de encendido. Si elogind también reacciona, el portátil se suspende +# dos veces: al abrir la tapa se enciende, se vuelve a suspender y luego +# arranca. Así solo lo gestiona acpid. +[Login] +HandlePowerKey=ignore +HandleSuspendKey=ignore +HandleHibernateKey=ignore +HandleLidSwitch=ignore +HandleLidSwitchExternalPower=ignore +HandleLidSwitchDocked=ignore diff --git a/sistema/etc/zzz.d/suspend/10-slock b/sistema/etc/zzz.d/suspend/10-slock new file mode 100755 index 0000000..e1cdc82 --- /dev/null +++ b/sistema/etc/zzz.d/suspend/10-slock @@ -0,0 +1,22 @@ +#!/bin/sh +# Bloquear con slock ANTES de suspender, para que al volver no se vea el +# escritorio. zzz ejecuta este hook como root; slock se lanza como el +# usuario de la sesión de dwm, con su DISPLAY y XAUTHORITY. + +pgrep -x slock >/dev/null && exit 0 + +pid=$(pgrep -xo dwm) || exit 0 +user=$(getent passwd "$(stat -c %u "/proc/$pid")" | cut -d: -f1) +env=$(tr '\0' '\n' <"/proc/$pid/environ" | grep -E '^(DISPLAY|XAUTHORITY)=') +[ -n "$user" ] && [ -n "$env" ] || exit 0 + +env $env setsid chpst -u "$user" /usr/local/bin/slock >/dev/null 2>&1 & + +# Esperar a que slock arranque y tape la pantalla (máximo 2 s) antes de +# dejar que zzz siga con la suspensión. +i=0 +while ! pgrep -x slock >/dev/null && [ "$i" -lt 20 ]; do + sleep 0.1 + i=$((i + 1)) +done +sleep 0.5 From ce3a0db5bf17fb0e36ec77606a52e937732bd01f Mon Sep 17 00:00:00 2001 From: alejandrogs73 Date: Sun, 27 Sep 2026 14:29:49 +0200 Subject: [PATCH 5/5] =?UTF-8?q?slock:=20pantalla=20con=20reloj,=20fecha,?= =?UTF-8?q?=20bater=C3=ADa=20y=20barra=20de=20estado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El fondo es fijo y el color del estado (inicio, escribiendo, error) pasa a una barra inferior. Se muestran la hora, la fecha, un punto por carácter tecleado, el usuario y la batería, refrescando reloj y batería cada segundo. Usa Xft y los iconos de Symbols Nerd Font. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016UdJsHrbkmzhoCb6TthP2m --- slock/config.def.h | 16 ++ slock/config.h | 19 ++- slock/config.mk | 6 +- slock/slock.c | 375 +++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 382 insertions(+), 34 deletions(-) diff --git a/slock/config.def.h b/slock/config.def.h index 9855e21..b51b48c 100644 --- a/slock/config.def.h +++ b/slock/config.def.h @@ -8,5 +8,21 @@ static const char *colorname[NUMCOLS] = { [FAILED] = "#CC3333", /* wrong password */ }; +/* lock screen */ +static const char *bgcolor = "black"; +static const char *fgcolor = "#eeeeee"; +static const char *dimcolor = "#888888"; + +static const char *clockfont = "monospace:bold:pixelsize=160"; +static const char *datefont = "monospace:pixelsize=34"; +static const char *textfont = "monospace:pixelsize=22"; +static const char *iconfont = "Symbols Nerd Font Mono:pixelsize=24"; + +static const int barheight = 10; /* height of the bottom bar */ +static const unsigned int maxdots = 24; /* max dots shown while typing */ + +static const char *battery = "BAT0"; /* /sys/class/power_supply/... */ +static const int batterylow = 20; /* percent shown as low */ + /* treat a cleared input like a wrong password (color) */ static const int failonclear = 1; diff --git a/slock/config.h b/slock/config.h index fd5a3b0..d2bc65a 100644 --- a/slock/config.h +++ b/slock/config.h @@ -2,11 +2,28 @@ static const char *user = "nobody"; static const char *group = "nogroup"; +/* colores de la barra inferior según el estado */ static const char *colorname[NUMCOLS] = { - [INIT] = "#2d353b", /* after initialization (fondo Everforest) */ + [INIT] = "#475258", /* after initialization (gris Everforest) */ [INPUT] = "#a7c080", /* during input (verde acento) */ [FAILED] = "#e67e80", /* wrong password (rojo Everforest) */ }; +/* pantalla de bloqueo (paleta Everforest) */ +static const char *bgcolor = "#2d353b"; /* fondo */ +static const char *fgcolor = "#d3c6aa"; /* reloj y texto */ +static const char *dimcolor = "#859289"; /* fecha y avisos */ + +static const char *clockfont = "Fira Code:style=Bold:pixelsize=160:antialias=true"; +static const char *datefont = "Fira Code:pixelsize=34:antialias=true"; +static const char *textfont = "Fira Code:pixelsize=22:antialias=true"; +static const char *iconfont = "Symbols Nerd Font Mono:pixelsize=24:antialias=true"; + +static const int barheight = 10; /* alto de la barra inferior */ +static const unsigned int maxdots = 24; /* puntos máximos al teclear */ + +static const char *battery = "BAT1"; /* /sys/class/power_supply/... */ +static const int batterylow = 20; /* % para mostrarla en rojo */ + /* treat a cleared input like a wrong password (color) */ static const int failonclear = 1; diff --git a/slock/config.mk b/slock/config.mk index 9fe5bff..3e12e2a 100644 --- a/slock/config.mk +++ b/slock/config.mk @@ -11,8 +11,8 @@ X11INC = /usr/X11R6/include X11LIB = /usr/X11R6/lib # includes and libs -INCS = -I. -I/usr/include -I${X11INC} -LIBS = -L/usr/lib -lc -lcrypt -L${X11LIB} -lX11 -lXext -lXrandr +INCS = -I. -I/usr/include -I${X11INC} -I/usr/include/freetype2 +LIBS = -L/usr/lib -lc -lcrypt -L${X11LIB} -lX11 -lXext -lXrandr -lXft -lfontconfig # flags CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE -DHAVE_SHADOW_H @@ -21,7 +21,7 @@ LDFLAGS = -s ${LIBS} COMPATSRC = explicit_bzero.c # On OpenBSD and Darwin remove -lcrypt from LIBS -#LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 -lXext -lXrandr +#LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 -lXext -lXrandr -lXft -lfontconfig # On *BSD remove -DHAVE_SHADOW_H from CPPFLAGS # On NetBSD add -D_NETBSD_SOURCE to CPPFLAGS #CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE -D_NETBSD_SOURCE diff --git a/slock/slock.c b/slock/slock.c index 9c587e1..6aa996c 100644 --- a/slock/slock.c +++ b/slock/slock.c @@ -12,12 +12,15 @@ #include #include #include +#include #include +#include #include #include #include #include #include +#include #include "util.h" @@ -30,13 +33,40 @@ enum { NUMCOLS }; +enum { + COLBG, + COLFG, + COLDIM, + COLCHARGE, + COLLOW, + NUMUICOLS +}; + struct lock { int screen; Window root, win; Pixmap pmap; unsigned long colors[NUMCOLS]; + /* dibujado: doble búfer del tamaño de la pantalla */ + int w, h; + Pixmap buf; + XftDraw *draw; + XftColor bar[NUMCOLS]; + XftColor ui[NUMUICOLS]; + XftFont *fclock, *fdate, *ftext, *ficon; }; +/* estado que se muestra en la pantalla de bloqueo */ +struct state { + unsigned int len; /* caracteres tecleados */ + int color; /* INIT, INPUT o FAILED */ + int failure; /* ha habido una contraseña incorrecta */ + char clock[16], date[64], bat[16]; + int batlevel, charging; +}; + +static char username[64]; + struct xrandr { int active; int evbase; @@ -123,23 +153,296 @@ gethash(void) return hash; } +static XftFont * +loadfont(Display *dpy, int screen, const char *name) +{ + XftFont *f; + + if (!(f = XftFontOpenName(dpy, screen, name)) && + !(f = XftFontOpenName(dpy, screen, "monospace"))) + die("slock: cannot load font %s\n", name); + return f; +} + +static void +setupdraw(Display *dpy, struct lock *lock) +{ + Visual *vis = DefaultVisual(dpy, lock->screen); + Colormap cmap = DefaultColormap(dpy, lock->screen); + const char *uinames[NUMUICOLS] = { + [COLBG] = bgcolor, [COLFG] = fgcolor, [COLDIM] = dimcolor, + [COLCHARGE] = colorname[INPUT], [COLLOW] = colorname[FAILED], + }; + int i; + + for (i = 0; i < NUMCOLS; i++) + if (!XftColorAllocName(dpy, vis, cmap, colorname[i], &lock->bar[i])) + die("slock: cannot allocate color %s\n", colorname[i]); + for (i = 0; i < NUMUICOLS; i++) + if (!XftColorAllocName(dpy, vis, cmap, uinames[i], &lock->ui[i])) + die("slock: cannot allocate color %s\n", uinames[i]); + + lock->fclock = loadfont(dpy, lock->screen, clockfont); + lock->fdate = loadfont(dpy, lock->screen, datefont); + lock->ftext = loadfont(dpy, lock->screen, textfont); + lock->ficon = loadfont(dpy, lock->screen, iconfont); + + lock->w = DisplayWidth(dpy, lock->screen); + lock->h = DisplayHeight(dpy, lock->screen); + lock->buf = XCreatePixmap(dpy, lock->root, lock->w, lock->h, + DefaultDepth(dpy, lock->screen)); + lock->draw = XftDrawCreate(dpy, lock->buf, vis, cmap); +} + +static void +resizedraw(Display *dpy, struct lock *lock, int w, int h) +{ + if (w == lock->w && h == lock->h) + return; + lock->w = w; + lock->h = h; + XFreePixmap(dpy, lock->buf); + lock->buf = XCreatePixmap(dpy, lock->root, w, h, + DefaultDepth(dpy, lock->screen)); + XftDrawChange(lock->draw, lock->buf); +} + +static int +textw(Display *dpy, XftFont *f, const char *s) +{ + XGlyphInfo ext; + + XftTextExtentsUtf8(dpy, f, (const FcChar8 *)s, strlen(s), &ext); + return ext.xOff; +} + +static int +iconw(Display *dpy, XftFont *f, FcChar32 icon) +{ + XGlyphInfo ext; + + if (!icon || !XftCharExists(dpy, f, icon)) + return 0; + XftTextExtents32(dpy, f, &icon, 1, &ext); + return ext.xOff; +} + +/* icono (opcional) + separación + texto, con la línea base en y */ +static int +drawlabel(Display *dpy, struct lock *lock, XftFont *f, XftColor *c, + int x, int y, FcChar32 icon, const char *s) +{ + int iw = iconw(dpy, lock->ficon, icon), gap = iw ? f->height / 2 : 0; + + if (iw) + XftDrawString32(lock->draw, c, lock->ficon, x, y, &icon, 1); + XftDrawStringUtf8(lock->draw, c, f, x + iw + gap, y, + (const FcChar8 *)s, strlen(s)); + return iw + gap + textw(dpy, f, s); +} + +static int +labelw(Display *dpy, struct lock *lock, XftFont *f, FcChar32 icon, + const char *s) +{ + int iw = iconw(dpy, lock->ficon, icon); + + return iw + (iw ? f->height / 2 : 0) + textw(dpy, f, s); +} + +/* Actualiza hora, fecha y batería. Devuelve 1 si algo ha cambiado. */ +static int +updatestate(struct state *st) +{ + static const char *wdays[] = { "domingo", "lunes", "martes", + "miércoles", "jueves", "viernes", "sábado" }; + static const char *months[] = { "enero", "febrero", "marzo", "abril", + "mayo", "junio", "julio", "agosto", "septiembre", "octubre", + "noviembre", "diciembre" }; + char clk[sizeof(st->clock)], date[sizeof(st->date)], bat[sizeof(st->bat)]; + char path[128], status[32] = ""; + int level = -1, charging = 0, changed; + time_t t = time(NULL); + struct tm *tm = localtime(&t); + FILE *f; + + strftime(clk, sizeof(clk), "%H:%M", tm); + snprintf(date, sizeof(date), "%s, %d de %s", wdays[tm->tm_wday], + tm->tm_mday, months[tm->tm_mon]); + date[0] = toupper((unsigned char)date[0]); + + snprintf(path, sizeof(path), "/sys/class/power_supply/%s/capacity", + battery); + if ((f = fopen(path, "r"))) { + if (fscanf(f, "%d", &level) != 1) + level = -1; + fclose(f); + } + snprintf(path, sizeof(path), "/sys/class/power_supply/%s/status", + battery); + if ((f = fopen(path, "r"))) { + if (fscanf(f, "%31s", status) != 1) + status[0] = '\0'; + fclose(f); + } + charging = !strcmp(status, "Charging") || !strcmp(status, "Full"); + if (level >= 0) + snprintf(bat, sizeof(bat), "%d%%", level); + else + bat[0] = '\0'; + + changed = strcmp(clk, st->clock) || strcmp(date, st->date) || + strcmp(bat, st->bat) || charging != st->charging; + memcpy(st->clock, clk, sizeof(clk)); + memcpy(st->date, date, sizeof(date)); + memcpy(st->bat, bat, sizeof(bat)); + st->batlevel = level; + st->charging = charging; + return changed; +} + +/* Símbolos de Nerd Font (Material Design) */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define ICON_LOCK 0xF033E +#define ICON_ALERT 0xF0026 +#define ICON_USER 0xF0004 +#define ICON_CHARGING 0xF0084 +#define ICON_BAT10 0xF007A /* 10 % .. 90 % seguidos; 100 % es 0xF0079 */ +#define ICON_BAT100 0xF0079 + +static FcChar32 +baticon(const struct state *st) +{ + if (st->charging) + return ICON_CHARGING; + if (st->batlevel >= 95) + return ICON_BAT100; + if (st->batlevel < 10) + return ICON_ALERT; + return ICON_BAT10 + st->batlevel / 10 - 1; +} + +/* Dibuja la pantalla de bloqueo en el rectángulo de un monitor */ +static void +drawmon(Display *dpy, struct lock *lock, const struct state *st, + int mx, int my, int mw, int mh) +{ + XftColor *fg = &lock->ui[COLFG], *dim = &lock->ui[COLDIM], *msgc; + char msg[128]; + FcChar32 icon = 0; + int x, y, w, pad = mh / 24, low; + unsigned int i, n; + + /* reloj y fecha, algo por encima del centro */ + y = my + mh * 2 / 5; + x = mx + (mw - textw(dpy, lock->fclock, st->clock)) / 2; + XftDrawStringUtf8(lock->draw, fg, lock->fclock, x, y, + (const FcChar8 *)st->clock, strlen(st->clock)); + y += lock->fdate->height * 3 / 2; + x = mx + (mw - textw(dpy, lock->fdate, st->date)) / 2; + XftDrawStringUtf8(lock->draw, dim, lock->fdate, x, y, + (const FcChar8 *)st->date, strlen(st->date)); + + /* mensaje de estado: indicación, puntos o error */ + msgc = dim; + if (st->color == INPUT) { + n = MIN(st->len, maxdots); + for (i = 0, msg[0] = '\0'; i < n; i++) + strcat(msg, i ? " ●" : "●"); + msgc = &lock->bar[INPUT]; + } else if (st->color == FAILED && st->failure) { + icon = ICON_ALERT; + snprintf(msg, sizeof(msg), "Contraseña incorrecta"); + msgc = &lock->bar[FAILED]; + } else { + icon = ICON_LOCK; + snprintf(msg, sizeof(msg), "Escribe la contraseña"); + } + y = my + mh * 3 / 5 + lock->ftext->ascent; + x = mx + (mw - labelw(dpy, lock, lock->ftext, icon, msg)) / 2; + drawlabel(dpy, lock, lock->ftext, msgc, x, y, icon, msg); + + /* abajo: usuario a la izquierda, batería a la derecha */ + y = my + mh - barheight - pad; + drawlabel(dpy, lock, lock->ftext, dim, mx + pad, y, ICON_USER, username); + if (st->bat[0]) { + low = !st->charging && st->batlevel <= batterylow; + w = labelw(dpy, lock, lock->ftext, baticon(st), st->bat); + drawlabel(dpy, lock, lock->ftext, + low ? &lock->ui[COLLOW] : + st->charging ? &lock->ui[COLCHARGE] : fg, + mx + mw - pad - w, y, baticon(st), st->bat); + } + + /* barra con el color del estado */ + XftDrawRect(lock->draw, &lock->bar[st->color], mx, my + mh - barheight, + mw, barheight); +} + +static void +drawlock(Display *dpy, struct lock *lock, const struct state *st) +{ + XRRMonitorInfo *mons; + int i, n = 0; + + XftDrawRect(lock->draw, &lock->ui[COLBG], 0, 0, lock->w, lock->h); + mons = XRRGetMonitors(dpy, lock->root, True, &n); + for (i = 0; i < n; i++) + drawmon(dpy, lock, st, mons[i].x, mons[i].y, + mons[i].width, mons[i].height); + if (mons) + XRRFreeMonitors(mons); + if (n <= 0) + drawmon(dpy, lock, st, 0, 0, lock->w, lock->h); + XCopyArea(dpy, lock->buf, lock->win, DefaultGC(dpy, lock->screen), + 0, 0, lock->w, lock->h, 0, 0); +} + +static void +drawall(Display *dpy, struct lock **locks, int nscreens, + const struct state *st) +{ + int screen; + + for (screen = 0; screen < nscreens; screen++) + drawlock(dpy, locks[screen], st); + XFlush(dpy); +} + static void readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens, const char *hash) { XRRScreenChangeNotifyEvent *rre; char buf[32], passwd[256], *inputhash; - int num, screen, running, failure, oldc; - unsigned int len, color; + int num, screen, running, xfd; + struct state st = { 0 }; + struct timeval tv; KeySym ksym; XEvent ev; + fd_set fds; - len = 0; running = 1; - failure = 0; - oldc = INIT; + st.color = INIT; + xfd = ConnectionNumber(dpy); + updatestate(&st); + drawall(dpy, locks, nscreens, &st); - while (running && !XNextEvent(dpy, &ev)) { + while (running) { + /* sin eventos pendientes: esperar como mucho 1 s y refrescar + * reloj y batería si han cambiado (también tras suspender) */ + if (!XPending(dpy)) { + FD_ZERO(&fds); + FD_SET(xfd, &fds); + tv.tv_sec = 1; + tv.tv_usec = 0; + if (select(xfd + 1, &fds, NULL, NULL, &tv) <= 0 && + updatestate(&st)) + drawall(dpy, locks, nscreens, &st); + continue; + } + XNextEvent(dpy, &ev); if (ev.type == KeyPress) { explicit_bzero(&buf, sizeof(buf)); num = XLookupString(&ev.xkey, buf, sizeof(buf), &ksym, 0); @@ -157,7 +460,7 @@ readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens, continue; switch (ksym) { case XK_Return: - passwd[len] = '\0'; + passwd[st.len] = '\0'; errno = 0; if (!(inputhash = crypt(passwd, hash))) fprintf(stderr, "slock: crypt: %s\n", strerror(errno)); @@ -165,52 +468,56 @@ readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens, running = !!strcmp(inputhash, hash); if (running) { XBell(dpy, 100); - failure = 1; + st.failure = 1; } explicit_bzero(&passwd, sizeof(passwd)); - len = 0; + st.len = 0; break; case XK_Escape: explicit_bzero(&passwd, sizeof(passwd)); - len = 0; + st.len = 0; break; case XK_BackSpace: - if (len) - passwd[--len] = '\0'; + if (st.len) + passwd[--st.len] = '\0'; break; default: if (num && !iscntrl((unsigned char)buf[0]) && - (len + num < sizeof(passwd))) { - memcpy(passwd + len, buf, num); - len += num; + (st.len + num < sizeof(passwd))) { + memcpy(passwd + st.len, buf, num); + st.len += num; } else if (buf[0] == '\025') { /* ctrl-u clears input */ explicit_bzero(&passwd, sizeof(passwd)); - len = 0; + st.len = 0; } break; } - color = len ? INPUT : ((failure || failonclear) ? FAILED : INIT); - if (running && oldc != color) { - for (screen = 0; screen < nscreens; screen++) { - XSetWindowBackground(dpy, - locks[screen]->win, - locks[screen]->colors[color]); - XClearWindow(dpy, locks[screen]->win); - } - oldc = color; + st.color = st.len ? INPUT : + ((st.failure || failonclear) ? FAILED : INIT); + if (running) { + updatestate(&st); + drawall(dpy, locks, nscreens, &st); } + } else if (ev.type == Expose) { + if (ev.xexpose.count == 0) + drawall(dpy, locks, nscreens, &st); } else if (rr->active && ev.type == rr->evbase + RRScreenChangeNotify) { rre = (XRRScreenChangeNotifyEvent*)&ev; for (screen = 0; screen < nscreens; screen++) { if (locks[screen]->win == rre->window) { if (rre->rotation == RR_Rotate_90 || - rre->rotation == RR_Rotate_270) + rre->rotation == RR_Rotate_270) { XResizeWindow(dpy, locks[screen]->win, rre->height, rre->width); - else + resizedraw(dpy, locks[screen], + rre->height, rre->width); + } else { XResizeWindow(dpy, locks[screen]->win, rre->width, rre->height); - XClearWindow(dpy, locks[screen]->win); + resizedraw(dpy, locks[screen], + rre->width, rre->height); + } + drawlock(dpy, locks[screen], &st); break; } } @@ -243,16 +550,20 @@ lockscreen(Display *dpy, struct xrandr *rr, int screen) lock->colors[i] = color.pixel; } + setupdraw(dpy, lock); + /* init */ wa.override_redirect = 1; - wa.background_pixel = lock->colors[INIT]; + wa.background_pixel = lock->ui[COLBG].pixel; + wa.event_mask = ExposureMask; lock->win = XCreateWindow(dpy, lock->root, 0, 0, DisplayWidth(dpy, lock->screen), DisplayHeight(dpy, lock->screen), 0, DefaultDepth(dpy, lock->screen), CopyFromParent, DefaultVisual(dpy, lock->screen), - CWOverrideRedirect | CWBackPixel, &wa); + CWOverrideRedirect | CWBackPixel | CWEventMask, + &wa); lock->pmap = XCreateBitmapFromData(dpy, lock->win, curs, 8, 8); invisible = XCreatePixmapCursor(dpy, lock->pmap, lock->pmap, &color, &color, 0, 0); @@ -340,6 +651,10 @@ main(int argc, char **argv) { dontkillme(); #endif + /* nombre del usuario que bloquea, para mostrarlo */ + if ((pwd = getpwuid(getuid()))) + snprintf(username, sizeof(username), "%s", pwd->pw_name); + hash = gethash(); errno = 0; if (!crypt("", hash))