sxalert

Simple notification system for X
git clone git://mcdim.xyz/sxalert.git
Log | Files | Refs | README | LICENSE

commit 5b1489ed90d1aec929657788eef32737bbbf4a89
parent 006b5b56e2366cdee5933cb09411eef814430c05
Author: Michail Konstantinos Dimopoulos <mk@mcdim.xyz>
Date:   Sat, 19 Sep 2026 03:25:41 +0300

Updated patch and documentation for upcoming release

Diffstat:
MMakefile | 11+++++++++--
MREADME.md | 45+++++++++++++++++++++++++++++++++++++++------
MREADME.txt | 75+++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mconfig.h | 2+-
Mpatches/wal_colors/README.txt | 8++------
Mpatches/wal_colors/wal.diff | 59+++++++++++++++++++++++++++++++----------------------------
Msxalert.1 | 19++++++++++++++++---
Msxalert.c | 91++++++++++++++++++++++++++++++++++---------------------------------------------
8 files changed, 188 insertions(+), 122 deletions(-)

diff --git a/Makefile b/Makefile @@ -4,11 +4,18 @@ CC=c99 MD=/usr/share/man/man1/ -sxalert: - $(CC) sxalert.c -I/usr/include/freetype2/ -lXft -lX11 -lfreetype -g -o sxalert +sxalert: sxalert.c sxalert.h config.h + $(CC) sxalert.c -I/usr/include/freetype2/ -lXft -lX11 \ + -lfreetype -lXrandr -g -o sxalert \ + -Wall -Wextra -Wpedantic \ + -Wconversion -Wsign-conversion \ + -Wshadow -Wstrict-prototypes \ + -Wmissing-prototypes -Wformat=2 install: mv sxalert /bin/sxalert cp sxalert.1 $(MD) uninstall: rm /bin/sxalert rm $(MD)sxalert.1 +clean: + rm sxalert diff --git a/README.md b/README.md @@ -1,7 +1,7 @@ # Simple X Alert <img src="https://mcdim.xyz/projects/sxalert/alectryon.png" alt="Alectryon rooster sxalert logo" style="height: 222px; width:200px;"/> -Simple notification system for X that works without a daemon. This is mainly for personal use, but I'll be glad to support whoever else wants to use it. Currently in development. +Simple alert pop-up utility for X. ## Features + Alert & exit -- no daemon @@ -11,12 +11,12 @@ Simple notification system for X that works without a daemon. This is mainly for + Dynamic screen positioning & sizing ## Dependencies -To build sxalert, you'll need a c99 compiler, `make`, `Xlib` and `Xft`. +A c99 compiler, `make`, `Xlib` and `Xft`. ## Build & install In order to build, execute the following commands: ``` -$ git clone https://github.com/MichaelDim02/sxalert.git && cd sxalert +$ git clone git://mcdim.xyz/sxalert.git && cd sxalert $ make sxalert ``` And to install on /bin: @@ -35,9 +35,42 @@ $ ./sxalert -d 1000 -b 5 -t FFFFFF -g 000000 -r FFFFFF "First line" "Second line ``` These will overwrite the defaults defined in `config.h` -## TODOs: -+ Read from stdin (perhaps as a patch) -+ <s>Exit by ButtonPress event</s> +## Return values +sxalert returns: ++ 0 - Left click on the notification window ++ 1 - Midde click on the notification window ++ 2 - Right click on the notification window ++ 3 - Time out + +Yes, this does mean that sxalert returns non-zero values even with normal +execution, but for a good reason. This makes sxalert excellent for shell +scripts. For example: + +You can set your mail to read with an acknowledgment + +``` +$ sxalert "New Mail" && mail-set-to-read +``` + +Or you can create a more elaborate interaction + +``` +sxalert "New Mail: X" "Left click to set to read" \ + "Middle click to delete" \ + "Right click to close" +rc=$? + +case $rc in +0) + mail-set-to-read x + ;; +1) + mail-delete x + ;; +2) + echo "Unread mail" + ;; +``` ## Optional D-Bus daemon hack I've created a small Shell script called [sxalertd](https://github.com/MichaelDim02/sxalertd) that can run as a daemon to read the D-Bus and use sxalert to draw the notifications. The script & parsing method are a little hacky but work quite well. diff --git a/README.txt b/README.txt @@ -23,15 +23,12 @@ Simple X Alert - ____________________________________________________________ -| version 0.1 | language C | lines of code <200 | binaries 1 | +============== -Simple notification system for X that works without a daemon. This is mainly for -personal use, but I'll be glad to support whoever else wants to use it. -Currently in development. +Simple alert pop-up utility for X. Features -======== +-------- + Alert & exit -- no daemon + Freetype scalable fonts + UTF-8 support @@ -39,14 +36,14 @@ Features + Dynamic screen positioning & sizing Dependencies -============ -To build sxalert, you'll need a c99 compiler, make, Xlib and Xft. +------------ +A c99 compiler, make, Xlib and Xft. Build & install -=============== +--------------- In order to build, execute the following commands: - $ git clone https://github.com/MichaelDim02/sxalert.git && cd sxalert + $ git clone git://mcdim.xyz/sxalert.git && cd sxalert $ make sxalert And to install on /bin: @@ -54,31 +51,61 @@ And to install on /bin: $ sudo make install Usage -===== +----- You can simply run the tool with each argument being a line of text - $ ./sxalert "First line" "Second line" + $ sxalert "First line" "Second line" You can also set color, border width & duration: - $ ./sxalert -d 1000 -b 5 -t FFFFFF -g 000000 -r FFFFFF "First line" "Second line" + $ sxalert -d 1000 -b 5 -t FFFFFF -g 000000 -r FFFFFF "Hello world!" These will overwrite the defaults defined in `config.h` -TODOs -===== -+ Read from stdin (perhaps as a patch) -+ [DONE] Exit by ButtonPress event +Return values +------------- +sxalert returns: +0 - Left click on the notification window +1 - Midde click on the notification window +2 - Right click on the notification window +3 - Time out + +Yes, this does mean that sxalert returns non-zero values even with normal +execution, but for a good reason. This makes sxalert excellent for shell +scripts. For example: + +You can set your mail to read with an acknowledgment + + $ sxalert "New Mail" && mail-set-to-read + +Or you can create a more elaborate interaction + + sxalert "New Mail: X" "Left click to set to read" \ + "Middle click to delete" \ + "Right click to close" + rc=$? + + case $rc in + 0) + mail-set-to-read x + ;; + 1) + mail-delete x + ;; + 2) + echo "Unread mail" + ;; Optional D-Bus daemon hack -========================== -I've created a small Shell script called sxalertd that can run as a daemon to -read the D-Bus and use sxalert to draw the notifications. The script & parsing +-------------------------- +I've created a small Shell script called sxalertd that can run as a pseudo +daemon that intercepts D-Bus and uses sxalert to draw the notifications. +It's not a proper daemon, and does not own the process, but can work fine. method are a little hacky but work quite well. (https://github.com/MichaelDim02/sxalertd) Credit -====== -The logo is the Alectryon! The rooster image is by Leys, Thomson W. from the -1883 book "Brett's colonists' guide and cyclopaedia" for New Zealand settlers. -The font is Byzantine, and the logo was created in GIMP. +------ +The logo is the Alectryon! The rooster image is by Leys, Thomson W. from +the 1883 book "Brett's colonists' guide and cyclopaedia" for New Zealand +settlers. The font is Byzantine, and the logo was created in GIMP. diff --git a/config.h b/config.h @@ -27,5 +27,5 @@ static const struct alert default_alert = { .y_offset = 40, /* pixels from top */ .font = "DejaVu Sans Mono:size=13:antialias=true", - .text_height = 11, + .text_height = 10, }; diff --git a/patches/wal_colors/README.txt b/patches/wal_colors/README.txt @@ -1,14 +1,10 @@ wal colors patch ================ -Verion: 0.1 -tested sxalert version: 0.1 +Verion: 0.2 +tested sxalert version: 0.2 tested pywal version: 3.3.0 Read background & foreground colors from pywal's Xresources file. Make sure to edit the directory in the config file. Author: Michael Constantine Dimopoulos - -TODOs: -Make function dynamic so that it can be used for all Xresources files - diff --git a/patches/wal_colors/wal.diff b/patches/wal_colors/wal.diff @@ -1,11 +1,30 @@ ---- sxalert.c 2023-11-18 13:24:57.322378330 +0000 -+++ sxalertxr.c 2023-11-18 13:24:26.619213478 +0000 -@@ -70,6 +70,40 @@ +--- ../sxalert_simplification_attempt/config.h 2026-09-19 00:57:10.187027307 +0300 ++++ config.h 2026-09-19 01:23:35.202912393 +0300 +@@ -29,3 +29,5 @@ + .font = "DejaVu Sans Mono:size=13:antialias=true", + .text_height = 10, + }; ++ ++const char *xresources = "/home/michael/.cache/wal/colors.Xresources"; /* wal */ +--- ../sxalert_simplification_attempt/sxalert.h 2026-09-01 15:36:58.132156170 +0300 ++++ sxalert.h 2026-09-19 01:23:11.668554392 +0300 +@@ -1,6 +1,8 @@ + #ifndef SXALERT_H + #define SXALERT_H + ++#define XR_BUFFER 128 ++ + struct alert { + int duration; + char text_color[7]; +--- ../sxalert_simplification_attempt/sxalert.c 2026-09-19 00:59:47.056450915 +0300 ++++ sxalert.c 2026-09-19 01:19:07.926615607 +0300 +@@ -63,6 +63,40 @@ return max; } +void -+get_xresources_colors(void) ++get_xresources_colors(struct alert *a) +{ + FILE *f = fopen(xresources, "r"); + if (!f) { @@ -31,38 +50,22 @@ + strncpy(colors[clr_i++], arr[1], 7); + } + -+ strcpy(text_color, colors[0]); -+ strcpy(border_color, colors[0]); -+ strcpy(bg_color, colors[1]); ++ strcpy(a->text_color, colors[0]); ++ strcpy(a->border_color, colors[0]); ++ strcpy(a->bg_color, colors[1]); + + fclose(f); +} + static int - get_width(Display *dpy, XftFont *font, char **lines, int length) + get_width(Display *dpy, XftFont *font, struct alert *a) { -@@ -176,6 +210,8 @@ - char text[BUFFER]; - extern char *optarg; +@@ -258,6 +292,8 @@ + + struct alert a = default_alert; -+ get_xresources_colors(); ++ get_xresources_colors(&a); + while ((c = getopt(argc, argv, "d:b:t:g:r:vh")) != -1 ) { switch (c) { case 'v': ---- config.h 2023-11-18 13:25:05.212335034 +0000 -+++ configxr.h 2023-11-18 13:19:15.637586640 +0000 -@@ -6,6 +6,7 @@ - */ - - #define BUFFER 2054 -+#define XR_BUFFER 128 - - static int duration = 5000; /* in milliseconds */ - -@@ -26,3 +27,5 @@ - - const char *fontname = "DejaVu Sans Mono:size=13:antialias=true"; - static int text_height = 11; -+ -+const char *xresources = "/home/$USER/.cache/wal/colors.Xresources"; /* wal */ diff --git a/sxalert.1 b/sxalert.1 @@ -1,6 +1,6 @@ .\" Manpage for sxalert -.TH man 8 "11 Nov 2023" "0.1" "sxalert manual page" +.TH man 8 "19 Sept 2026" "0.2" "sxalert manual page" .SH NAME sxalert \- Simple X Alert .SH SYNOPSIS @@ -19,25 +19,38 @@ sxalert will draw text as a notification for X -b border width in pixels -d duration in milliseconds + for values below or equal to 0 the notification will run indefinitely -h print help panel & exit -v print version & exit +.SH EXIT CODES +0 - Left click on the notification window +1 - Midde click on the notification window +2 - Right click on the notification window +3 - Time out + .SH EXAMPLES This will print the string "Updates available": sxalert "Updates avalailable" -The following will also print "Updates available" but will specify text color to be red, background to be black and border width to be 0: +The following will also print "Updates available" but will specify text +color to be red, background to be black and border width to be 0: sxlaert -t 0xff0000 -g 0x000000 -b 0 "Updates available" These will overwrite the defaults specified in config.h +The following will print "New mail" and echo "OK" to stdout if the user +left clicks on the notification window: + +sxalert "New mail" && echo "OK" + .SH AUTHOR -Michael Constantine Dimopoulos <mk@mcdim.xyz> +Michail Konstantinos Dimopoulos <mk@mcdim.xyz> https://mcdim.xyz diff --git a/sxalert.c b/sxalert.c @@ -51,16 +51,6 @@ hex2int(char *str) return num; } -static char * -convert_text_color_code(struct alert *a) /* adds a '#' before the color hex, */ -{ /* as XftColorAllocName requires it */ - char* text_color_pnd = (char*)malloc(8); - strncpy(text_color_pnd+1, a->text_color, 6); - text_color_pnd[0] = '#'; - text_color_pnd[7] = '\0'; - return text_color_pnd; -} - int get_max(int arr[], int len) { @@ -82,7 +72,10 @@ get_width(Display *dpy, XftFont *font, struct alert *a) int width_lines[a->lines_len]; int j = 0; for (int i = 0; i < a->lines_len; i++) { - XftTextExtentsUtf8(dpy, font, (XftChar8*)a->lines[i], strlen(a->lines[i]), &ext); + XftTextExtentsUtf8(dpy, font, + (XftChar8*)a->lines[i], + strlen(a->lines[i]), + &ext); width_lines[i] = ext.xOff; j++; } @@ -127,12 +120,8 @@ write_text(Display *dpy, struct xwin *windows, int count, XEvent ev; XNextEvent(dpy, &ev); - for (int i = 0; i < count; i++) { - XSelectInput(dpy, windows[i].win, - ExposureMask | ButtonPressMask); - } - int i = find_window( windows, count, ev.xany.window); + int i = find_window(windows, count, ev.xany.window); if (i < 0) continue; @@ -141,7 +130,7 @@ write_text(Display *dpy, struct xwin *windows, int count, int spacing = a->text_height * 2; for (int line = 0; line < a->lines_len; ++line) { - XftDrawStringUtf8( windows[i].draw, &color, + XftDrawStringUtf8(windows[i].draw, &color, font, a->text_x_padding, spacing, (XftChar8 *)a->lines[line], (int)strlen(a->lines[line]) @@ -158,7 +147,7 @@ write_text(Display *dpy, struct xwin *windows, int count, exit(1); break; case Button3: /* right click */ - exit(3); + exit(2); break; } } @@ -170,7 +159,7 @@ static void init_render(Display *dpy, struct xrender *r, struct alert *a) { char text_color_pnd[8]; - strncpy(text_color_pnd, convert_text_color_code(a), 8); + snprintf(text_color_pnd, 8, "#%s", a->text_color); r->scr = DefaultScreen(dpy); r->visual = DefaultVisual(dpy, r->scr); @@ -178,47 +167,40 @@ init_render(Display *dpy, struct xrender *r, struct alert *a) r->font = XftFontOpenName(dpy, r->scr, a->font); if (!r->font) - die("Cannot load font\n", EXIT_FAILURE); - if (!XftColorAllocName(dpy, r->visual, r->cmap, text_color_pnd, &(r->color))) - die("Cannot allocate Xft color\n", EXIT_FAILURE); -} - -static inline Display * -get_display(void) -{ - Display *dpy = XOpenDisplay(NULL); - if (!dpy) - die("Cannot open X11 display\n", EXIT_FAILURE); - return dpy; + die("Cannot load font\n", -1); + if (!XftColorAllocName(dpy, r->visual, r->cmap, + text_color_pnd, &(r->color))) + die("Cannot allocate Xft color\n", -1); } static void -init_window(Display *dpy, struct xrender *r, struct xwin *w, struct alert *a, - XRRMonitorInfo * monitor) +init_window(Display *dpy, struct xrender *r, struct xwin *w, + struct alert *a, XRRMonitorInfo * monitor) { w->width = get_width(dpy, r->font, a); - w->height = a->lines_len * (a->text_height * 2) + a->text_height; + w->height = a->lines_len * (a->text_height * 2) + + a->text_height; - //int count_screens = ScreenCount(dpy); Screen *screen = ScreenOfDisplay(dpy, 0); - //w->x = screen->width - w->width - a->x_offset; - //w->x = screen->height - w->height w->x = monitor->x + monitor->width - w->width - a->x_offset; w->y = monitor->y + a->y_offset; w->win = XCreateSimpleWindow(dpy, RootWindow(dpy, r->scr), w->x, - a->y_offset, w->width, w->height, a->border_width, + a->y_offset, w->width, w->height, + a->border_width, hex2int(a->border_color), hex2int(a->bg_color)); - XSetWindowAttributes attributes = { + XSetWindowAttributes attr = { .override_redirect = True }; - XChangeWindowAttributes(dpy, w->win, CWOverrideRedirect, &attributes); + XChangeWindowAttributes(dpy, w->win, CWOverrideRedirect, &attr); XSelectInput(dpy, w->win, ExposureMask | KeyPressMask); XMapWindow(dpy, w->win); w->draw = XftDrawCreate(dpy, w->win, r->visual, r->cmap); + + XSelectInput(dpy, w->win, ExposureMask | ButtonPressMask); } static void @@ -233,29 +215,31 @@ cleanup(Display *dpy, struct xrender *r, struct xwin *w, struct alert *a) static void draw(struct alert a) { - Display *dpy = get_display(); + Display *dpy = XOpenDisplay(NULL); + if (!dpy) + die("Cannot open X11 display\n", -1); struct xrender r; init_render(dpy, &r, &a); -/**/ struct mon m; m.monitor_count = 1; - m.monitors = XRRGetMonitors(dpy, RootWindow(dpy, r.scr), True, &m.monitor_count); - - /* Print monitor info */ + m.monitors = XRRGetMonitors(dpy, RootWindow(dpy, r.scr), + True, &m.monitor_count); + /* for debugging printf("Found %d monitors\n", m.monitor_count); for (int i = 0; i < m.monitor_count; i++) { - printf("Monitor %d: x=%d y=%d width=%d height=%d primary=%d\n", - i, m.monitors[i].x, m.monitors[i].y, m.monitors[i].width, m.monitors[i].height, m.monitors[i].primary); - } + printf("Monitor %d: x=%d y=%d width=%d height=%d " + "primary=%d\n", i, m.monitors[i].x, m.monitors[i].y, + m.monitors[i].width, m.monitors[i].height, + m.monitors[i].primary); + } */ - //Window *windows = calloc(m.monitor_count, sizeof(Window)); struct xwin *w = calloc(m.monitor_count, sizeof(struct xwin)); if (!w) - die("Cannot allocate window array\n", EXIT_FAILURE); -/**/ + die("Cannot allocate window array\n", -1); + for (int i = 0; i < m.monitor_count; i++) { init_window(dpy, &r, &w[i], &a, &m.monitors[i]); @@ -282,6 +266,9 @@ main(int argc, char **argv) help(argv[0]); case 'd': a.duration=atoi(optarg); + if (a.duration == 0) { + a.duration = -1; + } break; case 'b': a.border_width=atoi(optarg); @@ -302,5 +289,5 @@ main(int argc, char **argv) a.lines = argv + optind; /* get lines to print */ draw(a); - return 0; + return 4; }