nfy

Minimal and daemonless notification program for X
git clone git://git.christosmarg.xyz/nfy.git
Log | Files | Refs | README | LICENSE

commit 3dd4fe5416c2f9050723fc1c0ebf50f9e0a77094
parent eb435af87ca87831003675138764932e9aa2fd12
Author: Christos Margiolis <christos@margiolis.net>
Date:   Wed, 28 Apr 2021 21:02:37 +0300

added queuing using a lockfile

Diffstat:
MLICENSE | 43++++++++++++++++---------------------------
MMakefile | 6+++---
MREADME | 2+-
Mconfig.h | 24+++++++++++++-----------
Mnfy.c | 309++++++++++++++++++++++++++++++++++++++++++-------------------------------------
5 files changed, 198 insertions(+), 186 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -1,31 +1,20 @@ -BSD 3-Clause License +MIT License -Copyright (c) 2021-present, Christos Margiolis. -All rights reserved. +(c) 2021-Present Christos Margiolis <christos@christosmarg.xyz> -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY -THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND -CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT -NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile @@ -15,9 +15,9 @@ all: options ${BIN} options: @echo ${BIN} build options: - @echo "CFLAGS = ${CFLAGS}" - @echo "LDFLAGS = ${LDFLAGS}" - @echo "CC = ${CC}" + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" ${BIN}: ${OBJ} ${CC} ${LDFLAGS} ${OBJ} -o $@ diff --git a/README b/README @@ -18,7 +18,7 @@ setup and preferences. # make install clean $ nfy str... -nfy will be installed in /usr/local/bin by default. +nfy will be installed in `/usr/local/bin` by default. Configuration is done by editing config.h and recompiling the source code. diff --git a/config.h b/config.h @@ -1,14 +1,16 @@ /* See LICENSE file for copyright and license details. */ enum { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }; /* Window positions */ -static const char *bgcol = "#453588"; /* Background color */ -static const char *bordercol = "#282828"; /* Border color */ -static const char *fontcol = "#ebdbb2"; /* Font color */ +static const char *bgcolor = "#201f1c"; /* Background color */ +static const char *bordercolor = "#f28f19"; /* Border color */ +static const char *fontcolor = "#e6e5e3"; /* Font color */ +/* TODO: add fallbacks */ static const char *fonts = "monospace:size=15"; /* Font */ -static const int padding = 15; /* Padding (pixels) */ -static const int borderw = 2; /* Border width (pixels) */ -static const int duration = 3; /* Notification duration (seconds) */ -static const int pos = TOP_RIGHT; /* Window position */ -static const int mx = 10; /* Margin X (pixels) */ -static const int my = 25; /* Margin Y (pixels) */ -static const int maxlen = 250; /* Max window length (pixels) */ -static const int linespace = 10; /* Line spacing (pixels) */ +static const int padding = 15; /* Padding (pixels) */ +static const int borderw = 2; /* Border width (pixels) */ +static const int duration = 3; /* Notification duration (seconds) */ +static const int pos = TOP_RIGHT; /* Window position */ +static const int mx = 10; /* Margin X (pixels) */ +static const int my = 25; /* Margin Y (pixels) */ +static const int maxlen = 250; /* Max window length (pixels) */ +static const int linespace = 10; /* Line spacing (pixels) */ +static const char *lockfile = "/tmp/nfy.lock"; diff --git a/nfy.c b/nfy.c @@ -1,4 +1,6 @@ /* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <fcntl.h> #include <signal.h> #include <stdarg.h> #include <stdio.h> @@ -12,164 +14,183 @@ #include "config.h" +static void die(const char *, ...); +static void recvalrm(int); + +static char *argv0; static Display *dpy; static Window win; static void die(const char *fmt, ...) { - va_list args; - - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); - - if (fmt[0] && fmt[strlen(fmt)-1] == ':') { - fputc(' ', stderr); - perror(NULL); - } else - fputc('\n', stderr); - - exit(EXIT_FAILURE); + va_list args; + + fprintf(stderr, "%s: ", argv0); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else + fputc('\n', stderr); + + exit(EXIT_FAILURE); } static void recvalrm(int sig) { - XEvent ev; - - ev.type = ButtonPress; - XSendEvent(dpy, win, 0, 0, &ev); - XFlush(dpy); + XEvent ev; + + ev.type = ButtonPress; + XSendEvent(dpy, win, 0, 0, &ev); + XFlush(dpy); } int main(int argc, char *argv[]) { - Visual *vis; - Colormap colormap; - XEvent ev; - XSetWindowAttributes attrs; - XftColor color; - XftFont *font; - XftDraw *drw; - XRRScreenResources *screens; - XRRCrtcInfo *info = NULL; - struct sigaction sig; - pid_t pid; - int scr, scrw, scrh; - int x, y, w, h, th; - int i, j, len; - - if (argc < 2) - die("usage: %s str...", argv[0]); - - if (!(dpy = XOpenDisplay(NULL))) - die("cannot open display"); - - pid = fork(); - if (pid < 0) - die("fork:"); - if (pid > 0) - exit(EXIT_SUCCESS); - umask(0); - if (setsid() < 0) - die("setsid:"); - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - - scr = DefaultScreen(dpy); - screens = XRRGetScreenResources(dpy, RootWindow(dpy, scr)); - info = XRRGetCrtcInfo(dpy, screens, screens->crtcs[0]); - scrw = info->width; - scrh = info->height; - - vis = DefaultVisual(dpy, scr); - colormap = DefaultColormap(dpy, scr); - XftColorAllocName(dpy, vis, colormap, bgcol, &color); - attrs.background_pixel = color.pixel; - XftColorAllocName(dpy, vis, colormap, bordercol, &color); - attrs.border_pixel = color.pixel; - attrs.override_redirect = True; - - font = XftFontOpenName(dpy, scr, fonts); - th = font->ascent - font->descent; - - w = 0; - for (i = 1; i < argc; i++) { - len = strlen(argv[i]); - for (j = 0; j < len; j++) - if (argv[i][j] == '\n') - argv[i][j] = ' '; - /* TODO: handle maxlen */ - if (len > w) - w = len; - } - - w *= th + borderw; - h = th * (argc - 1) + (linespace * (argc - 2)) + (padding << 1); - - switch (pos) { - case TOP_LEFT: - x = mx; - y = my; - break; - case TOP_RIGHT: - default: - x = scrw - w - my; - y = my; - break; - case BOTTOM_LEFT: - x = mx; - y = scrh - h - my; - break; - case BOTTOM_RIGHT: - x = scrw - w - mx; - y = scrh - h - my; - break; - } - - win = XCreateWindow(dpy, RootWindow(dpy, scr), x, y, w, h, borderw, - DefaultDepth(dpy, scr), CopyFromParent, vis, - CWOverrideRedirect | CWBackPixel | CWBorderPixel, &attrs); - - drw = XftDrawCreate(dpy, win, vis, colormap); - XftColorAllocName(dpy, vis, colormap, fontcol, &color); - - XSelectInput(dpy, win, ExposureMask | ButtonPress); - XMapWindow(dpy, win); - - sig.sa_handler = recvalrm; - sig.sa_flags = SA_RESTART; - sigemptyset(&sig.sa_mask); - sigaction(SIGALRM, &sig, 0); - sigaction(SIGTERM, &sig, 0); - sigaction(SIGINT, &sig, 0); - /* XXX: replace with just singal? */ - - if (duration > 0) - alarm(duration); - - for (;;) { - XNextEvent(dpy, &ev); - - if (ev.type == Expose) { - XClearWindow(dpy, win); - for (i = 1; i < argc; i++) - XftDrawStringUtf8(drw, &color, font, - w >> 3, linespace * (i - 1) + th * i + padding, - (FcChar8 *)argv[i], strlen(argv[i])); - } else if (ev.type == ButtonPress) - break; - } - - XftDrawDestroy(drw); - XftColorFree(dpy, vis, colormap, &color); - XftFontClose(dpy, font); - XRRFreeCrtcInfo(info); - XRRFreeScreenResources(screens); - XCloseDisplay(dpy); - - return 0; + Colormap colormap; + Visual *vis; + XEvent ev; + XRRCrtcInfo *info = NULL; + XRRScreenResources *screens; + XSetWindowAttributes attrs; + XftColor color; + XftDraw *drw; + XftFont *font; + struct sigaction sig; + pid_t pid; + int lockfd; + int scr, scrw, scrh; + int x, y, w, h, th; + int i, j, len; + + argv0 = *argv; + if (argc < 2) { + fprintf(stderr, "usage: %s str...\n", argv0); + return 1; + } + + if ((lockfd = open(lockfile, O_CREAT | O_RDWR, 0600)) == -1) + die("open:"); + if (!(dpy = XOpenDisplay(NULL))) + die("XOpenDisplay:"); + + if ((pid = fork()) < 0) + die("fork:"); + if (pid > 0) + exit(EXIT_SUCCESS); + umask(0); + if (setsid() < 0) + die("setsid:"); + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + scr = DefaultScreen(dpy); + screens = XRRGetScreenResources(dpy, RootWindow(dpy, scr)); + info = XRRGetCrtcInfo(dpy, screens, screens->crtcs[0]); + scrw = info->width; + scrh = info->height; + + vis = DefaultVisual(dpy, scr); + colormap = DefaultColormap(dpy, scr); + XftColorAllocName(dpy, vis, colormap, bgcolor, &color); + attrs.background_pixel = color.pixel; + XftColorAllocName(dpy, vis, colormap, bordercolor, &color); + attrs.border_pixel = color.pixel; + attrs.override_redirect = True; + + font = XftFontOpenName(dpy, scr, fonts); + th = font->ascent - font->descent; + + w = 0; + for (i = 1; i < argc; i++) { + len = strlen(argv[i]); + j = len; + while (j--) + if (**argv == '\n') + **argv++ = ' '; + /* TODO: handle maxlen */ + if (len > w) + w = len; + } + + w *= th + borderw; + h = th * (argc - 1) + (linespace * (argc - 2)) + (padding << 1); + + switch (pos) { + case TOP_LEFT: + x = mx; + y = my; + break; + case TOP_RIGHT: + default: + x = scrw - w - my; + y = my; + break; + case BOTTOM_LEFT: + x = mx; + y = scrh - h - my; + break; + case BOTTOM_RIGHT: + x = scrw - w - mx; + y = scrh - h - my; + break; + } + + win = XCreateWindow(dpy, RootWindow(dpy, scr), x, y, w, h, borderw, + DefaultDepth(dpy, scr), CopyFromParent, vis, + CWOverrideRedirect | CWBackPixel | CWBorderPixel, &attrs); + + drw = XftDrawCreate(dpy, win, vis, colormap); + XftColorAllocName(dpy, vis, colormap, fontcolor, &color); + + XSelectInput(dpy, win, ExposureMask | ButtonPress); + XMapWindow(dpy, win); + + sig.sa_handler = recvalrm; + sig.sa_flags = SA_RESTART; + sigemptyset(&sig.sa_mask); + sigaction(SIGALRM, &sig, 0); + sigaction(SIGTERM, &sig, 0); + sigaction(SIGINT, &sig, 0); + /* XXX: replace with just singal? */ + + /* XXX: when being called from another process this one succeeds */ + while (lockf(lockfd, F_LOCK, 0L) == -1) + ; + if (duration > 0) + (void)alarm(duration); + + for (;;) { + XNextEvent(dpy, &ev); + + if (ev.type == Expose) { + XClearWindow(dpy, win); + for (i = 1; i < argc; i++) + XftDrawStringUtf8(drw, &color, font, + w >> 3, linespace * (i - 1) + th * i + padding, + (FcChar8 *)argv[i], strlen(argv[i])); + } else if (ev.type == ButtonPress) + break; + } + + XftDrawDestroy(drw); + XftColorFree(dpy, vis, colormap, &color); + XftFontClose(dpy, font); + XRRFreeCrtcInfo(info); + XRRFreeScreenResources(screens); + XCloseDisplay(dpy); + + (void)lockf(lockfd, F_ULOCK, 0); + (void)close(lockfd); + (void)unlink(lockfile); + + return 0; }