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:
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;
}