nfy

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

commit 6ca8386e0b7936572b9e144eeb44033218dbe855
Author: Christos Margiolis <christos@margiolis.net>
Date:   Tue, 26 Jan 2021 23:26:37 +0200

initial commit

Diffstat:
AMakefile | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.h | 11+++++++++++
Aconfig.mk | 40++++++++++++++++++++++++++++++++++++++++
Anfy.1 | 15+++++++++++++++
Anfy.c | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 271 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,53 @@ +# See LICENSE file for copyright and license details. +# nfy - minimal notification program +.POSIX: + +include config.mk + +BIN = nfy +DIST = ${BIN}-${VERSION} +MAN1 = ${BIN}.1 + +SRC = nfy.c +OBJ = ${SRC:.c=.o} + +all: options ${BIN} + +options: + @echo ${BIN} build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +${BIN}: ${OBJ} + ${CC} ${LDFLAGS} ${OBJ} -o $@ + +.c.o: + ${CC} -c ${CFLAGS} $< + +dist: clean + ${MKDIR} ${DIST} + ${CP} -R nfy.c config.h Makefile config.mk ${DIST} + ${TAR} ${DIST}.tar ${DIST} + ${GZIP} ${DIST}.tar + ${RM_DIR} ${DIST} + +run: + ./${BIN} + +install: all + ${MKDIR} ${DESTDIR}${BIN_DIR} ${DESTDIR}${MAN_DIR} + ${CP} ${BIN} ${BIN_DIR} + ${CP} ${MAN1} ${DESTDIR}${MAN_DIR} + sed "s/VERSION/${VERSION}/g" < ${MAN1} > ${DESTDIR}${MAN_DIR}/${MAN1} + chmod 755 ${DESTDIR}${BIN_DIR}/${BIN} + chmod 644 ${DESTDIR}${MAN_DIR}/${MAN1} + +uninstall: + ${RM} ${DESTDIR}${BIN_DIR}/${BIN} + ${RM} ${DESTDIR}${MAN_DIR}/${MAN1} + +clean: + ${RM} ${BIN} ${OBJ} ${DIST}.tar.gz + +.PHONY: all options clean dist install uninstall run diff --git a/config.h b/config.h @@ -0,0 +1,11 @@ +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 *fonts = "monospace:size=15"; /* Font */ +static const int borderw = 2; /* Border width */ +static const int duration = 3; /* Notification duration (seconds) */ +static const int pos = TOP_RIGHT; /* Window position */ +static const int mx = 10; /* Margin X */ +static const int my = 25; /* Margin Y */ +static const int maxlen = 250; /* Max window length (pixels) */ diff --git a/config.mk b/config.mk @@ -0,0 +1,40 @@ +# See LICENSE file for copyright and license details. +# nfy version +VERSION = 0 + +# paths +PREFIX = /usr/local +MAN_DIR = ${PREFIX}/share/man/man1 +BIN_DIR = ${PREFIX}/bin + +# includes and libs +#X11INC = /usr/local/include +#X11LIB = /usr/local/lib + +# Linux +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +FREETYPELIBS = -lfontconfig -lXft +#FREETYPEINC = ${X11INC}/freetype2 +# Linux +FREETYPEINC = /usr/include/freetype2 + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L \ + -D_XOPEN_SOURCE=700 -DVERSION=\"${VERSION}\" +CFLAGS = -std=c99 -pedantic -Wall -Os -I${X11INC} -I${FREETYPEINC} ${CPPFLAGS} +LDFLAGS = -L${X11LIB} ${FREETYPELIBS} -lX11 + +# utils +CP = cp -f +RM = rm -f +RM_DIR = rm -rf +MV = mv +MKDIR = mkdir -p +RM_DIR = rm -rf +TAR = tar -cf +GZIP = gzip + +# compiler +CC = cc diff --git a/nfy.1 b/nfy.1 @@ -0,0 +1,15 @@ +.Dd nfy\-VERSRION +.Dt NFY 1 +.Os +.Sh NAME +.Nm nfy +.Nd a minimal notification program for X +.Sh SYNOPSIS +.Nm +.Ar str... +.Sh DESCRIPTION +.Nm +creates a temporary notification popup window and displays +all the arguments that were passed to it. +.Sh AUTHORS +.An Christos Margiolis Aq Mt christos@christosmarg.xyz diff --git a/nfy.c b/nfy.c @@ -0,0 +1,152 @@ +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <X11/Xlib.h> +#include <X11/Xft/Xft.h> + +#include "config.h" + +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); +} + +static void +recvalrm(int sig) +{ + 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; + struct sigaction sig; + 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"); + scr = DefaultScreen(dpy); + 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 = w * (font->descent << 1) + borderw; + h = (th + (borderw << 2)) * argc; + scrw = DisplayWidth(dpy, scr); + scrh = DisplayHeight(dpy, scr); + + 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); + + 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, + borderw, (th + (borderw << 2)) * i, + (FcChar8 *)argv[i], strlen(argv[i])); + } else if (ev.type == ButtonPress) + break; + } + + XftDrawDestroy(drw); + XftColorFree(dpy, vis, colormap, &color); + XftFontClose(dpy, font); + XCloseDisplay(dpy); + + return 0; +}