commit 41a6f1fc48b828be0f509752a49dcdc5b17e90f7
parent 5c92f82b2cfa18c2c4d2495c21e58e11784070d6
Author: Justinas Grigas <dev@jstnas.com>
Date: Mon, 17 Jun 2024 01:43:03 +0100
dmenu(qalc): update to 5.3
the main motivation behind the new patch is to prevent dmenu hanging
when qalc is not installed. This is done by using poll instead of
select.
Diffstat:
2 files changed, 286 insertions(+), 9 deletions(-)
diff --git a/tools.suckless.org/dmenu/patches/qalc/dmenu-qalc-5.3.diff b/tools.suckless.org/dmenu/patches/qalc/dmenu-qalc-5.3.diff
@@ -0,0 +1,265 @@
+From 1e5ee55ff70ca7abcc8c223ae34ff5b94e0a647d Mon Sep 17 00:00:00 2001
+From: Justinas Grigas <dev@jstnas.com>
+Date: Sat, 15 Jun 2024 21:13:12 +0100
+Subject: [PATCH] qalc: calculator mode
+
+Updates over previous patch:
+- add flag to manpage synopsis.
+- uses poll instead of select (prevents hang when qalc is not installed).
+- follow suckless style.
+- qalc function names start with qalc.
+---
+ dmenu.1 | 5 +-
+ dmenu.c | 160 +++++++++++++++++++++++++++++++++++++++++++++-----------
+ 2 files changed, 133 insertions(+), 32 deletions(-)
+
+diff --git a/dmenu.1 b/dmenu.1
+index 323f93c..ee5ca31 100644
+--- a/dmenu.1
++++ b/dmenu.1
+@@ -3,7 +3,7 @@
+ dmenu \- dynamic menu
+ .SH SYNOPSIS
+ .B dmenu
+-.RB [ \-bfiv ]
++.RB [ \-bCfiv ]
+ .RB [ \-l
+ .IR lines ]
+ .RB [ \-m
+@@ -40,6 +40,9 @@ which lists programs in the user's $PATH and runs the result in their $SHELL.
+ .B \-b
+ dmenu appears at the bottom of the screen.
+ .TP
++.B \-C
++dmenu becomes a calculator.
++.TP
+ .B \-f
+ dmenu grabs the keyboard before reading stdin if not reading from a tty. This
+ is faster, but will lock up X until stdin reaches end\-of\-file.
+diff --git a/dmenu.c b/dmenu.c
+index 40f93e0..67b7f02 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -7,6 +7,11 @@
+ #include <strings.h>
+ #include <time.h>
+ #include <unistd.h>
++#include <errno.h>
++#include <fcntl.h>
++#include <poll.h>
++#include <signal.h>
++#include <sys/prctl.h>
+
+ #include <X11/Xlib.h>
+ #include <X11/Xatom.h>
+@@ -33,6 +38,12 @@ struct item {
+ int out;
+ };
+
++static struct {
++ pid_t pid;
++ int enable, in[2], out[2];
++ char buf[256];
++} qalc;
++
+ static char text[BUFSIZ] = "";
+ static char *embed;
+ static int bh, mw, mh;
+@@ -226,9 +237,78 @@ grabkeyboard(void)
+ die("cannot grab keyboard");
+ }
+
++static void
++qalc_init(void)
++{
++ pipe(qalc.in);
++ pipe2(qalc.out, O_NONBLOCK);
++ qalc.pid = fork();
++ if (qalc.pid == -1)
++ die("failed to fork for qalc");
++ if (qalc.pid == 0) {
++ dup2(qalc.in[0], STDIN_FILENO);
++ dup2(qalc.out[1], STDOUT_FILENO);
++ close(qalc.in[1]);
++ close(qalc.out[0]);
++ prctl(PR_SET_PDEATHSIG, SIGTERM);
++ execl("/usr/bin/qalc", "qalc", "-c0", "-t", NULL);
++ die("execl qalc failed");
++ } else { /* parent */
++ close(qalc.in[0]);
++ close(qalc.out[1]);
++ items = malloc(sizeof(struct item) * 2);
++ items[0].text = malloc(LENGTH(qalc.buf));
++ strcpy(items[0].text, "no result");
++ items[1].out = 0;
++ items[1].text = NULL;
++ }
++}
++
++static void
++qalc_recv(void)
++{
++ ssize_t r = read(qalc.out[0], qalc.buf, LENGTH(qalc.buf));
++ if (r < 0)
++ die("error reading qalc.out");
++ if (qalc.buf[0] == '\n') {
++ int i;
++ for (i = 3; i < LENGTH(qalc.buf) && qalc.buf[i] != '\n'; ++i)
++ items[0].text[i - 3] = qalc.buf[i];
++ items[0].text[i - 3] = 0;
++ if (r != LENGTH(qalc.buf))
++ return;
++ }
++ while (read(qalc.out[0], qalc.buf, LENGTH(qalc.buf)) != -1)
++ ; /* empty the pipe */
++ if (errno != EAGAIN && errno != EWOULDBLOCK)
++ die("error emptying qalc.out");
++}
++
++static void
++qalc_send(void)
++{
++ int s = strlen(text);
++ text[s] = '\n';
++ write(qalc.in[1], text, s + 1);
++ text[s] = 0;
++}
++
++static void
++qalc_match(void)
++{
++ matches = matchend = NULL;
++ appenditem(items, &matches, &matchend);
++ curr = sel = matches;
++ calcoffsets();
++}
++
+ static void
+ match(void)
+ {
++ if (qalc.enable) {
++ qalc_match();
++ return;
++ }
+ static char **tokv = NULL;
+ static int tokn = 0;
+
+@@ -523,6 +603,9 @@ insert:
+ break;
+ }
+
++ if (qalc.enable)
++ qalc_send();
++
+ draw:
+ drawmenu();
+ }
+@@ -576,36 +659,46 @@ static void
+ run(void)
+ {
+ XEvent ev;
+-
+- while (!XNextEvent(dpy, &ev)) {
+- if (XFilterEvent(&ev, win))
+- continue;
+- switch(ev.type) {
+- case DestroyNotify:
+- if (ev.xdestroywindow.window != win)
++ int xfd = ConnectionNumber(dpy);
++ struct pollfd fds[] = {
++ {xfd, POLLIN, 0},
++ {qalc.out[0], POLLIN, 0},
++ };
++ while (poll(fds, 2, -1) > 0) {
++ if (qalc.enable && fds[1].revents & POLLIN) {
++ qalc_recv();
++ drawmenu();
++ }
++ while (XPending(dpy) && !XNextEvent(dpy, &ev)) {
++ if (XFilterEvent(&ev, win))
++ continue;
++ switch (ev.type) {
++ case DestroyNotify:
++ if (ev.xdestroywindow.window != win)
++ break;
++ cleanup();
++ exit(1);
++ case Expose:
++ if (ev.xexpose.count == 0)
++ drw_map(drw, win, 0, 0, mw, mh);
+ break;
+- cleanup();
+- exit(1);
+- case Expose:
+- if (ev.xexpose.count == 0)
+- drw_map(drw, win, 0, 0, mw, mh);
+- break;
+- case FocusIn:
+- /* regrab focus from parent window */
+- if (ev.xfocus.window != win)
+- grabfocus();
+- break;
+- case KeyPress:
+- keypress(&ev.xkey);
+- break;
+- case SelectionNotify:
+- if (ev.xselection.property == utf8)
+- paste();
+- break;
+- case VisibilityNotify:
+- if (ev.xvisibility.state != VisibilityUnobscured)
+- XRaiseWindow(dpy, win);
+- break;
++ case FocusIn:
++ /* regrab focus from parent window */
++ if (ev.xfocus.window != win)
++ grabfocus();
++ break;
++ case KeyPress:
++ keypress(&ev.xkey);
++ break;
++ case SelectionNotify:
++ if (ev.xselection.property == utf8)
++ paste();
++ break;
++ case VisibilityNotify:
++ if (ev.xvisibility.state != VisibilityUnobscured)
++ XRaiseWindow(dpy, win);
++ break;
++ }
+ }
+ }
+ }
+@@ -715,7 +808,7 @@ setup(void)
+ static void
+ usage(void)
+ {
+- die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
++ die("usage: dmenu [-bCfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
+ " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]");
+ }
+
+@@ -732,6 +825,8 @@ main(int argc, char *argv[])
+ exit(0);
+ } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */
+ topbar = 0;
++ else if (!strcmp(argv[i], "-C")) /* enable calculator */
++ qalc.enable = 1;
+ else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */
+ fast = 1;
+ else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
+@@ -782,7 +877,10 @@ main(int argc, char *argv[])
+ die("pledge");
+ #endif
+
+- if (fast && !isatty(0)) {
++ if (qalc.enable) {
++ qalc_init();
++ grabkeyboard();
++ } else if (fast && !isatty(0)) {
+ grabkeyboard();
+ readstdin();
+ } else {
+--
+2.45.2
+
diff --git a/tools.suckless.org/dmenu/patches/qalc/index.md b/tools.suckless.org/dmenu/patches/qalc/index.md
@@ -1,11 +1,23 @@
-qalc
-==================
-This patch adds an additional flag to dmenu, -C, which turns the app into a calculator, à la rofi-calc. The feedback is very smooth and the result of the equation is updated every time a key is pressed. This patch requires the *qalculate* CLI tool to be installed in /usr/bin/. For more info on its capabilities and syntax, visit <https://qalculate.github.io/>.
+# qalc
-Download
---------
-* [dmenu-qalc-5.2.diff](dmenu-qalc-5.2.diff)
+## Description
-Author
-------
-* woland <w3323421@gmail.com>
+Add a calculator mode to dmenu using `qalc`.
+Uses `poll` to update result on key press for smooth feedback.
+
+## Notes
+
+- Enable calculator mode using `-C` flag.
+- Make sure to install qalculate CLI tool `qalc`.
+- For more info on `qalc` capabilities and syntax, visit
+ <https://qalculate.github.io/>.
+
+## Download
+
+- [dmenu-qalc-5.2.diff](./dmenu-qalc-5.2.diff)
+- [dmenu-qalc-5.3.diff](./dmenu-qalc-5.2.diff)
+
+## Authors
+
+- woland <w3323421@gmail.com>
+- Justinas Grigas <dev@jstnas.com> (5.3)