commit - c243e7413d92458368975c45ee74d5f45aec5337
commit + 7b13656e3ef0f51754993932e6c95f5a3d71f894
blob - 711c03363990f9fabb4fefa46e01655a51784b0f
blob + c6c6d155c3a77956ad8ec22c5b60a818ede9a9ed
--- bxnotify.c
+++ bxnotify.c
#include "config.h"
+typedef struct {
+ char **lines;
+ int count;
+} TextBlock;
+
/* Global Variables */
static Display *dpy;
static int screen;
static XftDraw *xft_draw;
static XftColor fg, bg, border;
static int running = 1;
-static char text[1024];
+static TextBlock tb;
+static int total_height;
static time_t start_time;
/* Forward Declarations */
static void die(const char *fmt, ...);
static void cleanup(void);
static void sigusr1(int sig);
-static void setup(void);
+static int measure_text(const char *text);
+static void init_display(void);
+static void calc_layout(void);
+static void setup(int win_h);
static void readstdin(void);
static void draw(void);
static void run(void);
/* TODO: parse cli flags, override config.h */
readstdin();
- setup();
+ init_display();
+ calc_layout();
+ setup(total_height);
+
start_time = time(NULL);
run();
cleanup();
return 0;
}
-
static void
die(const char *fmt, ...)
{
static void
cleanup(void)
{
- if (draw) XftDrawDestroy(xft_draw);
- if (font) XftFontClose(dpy, font);
- if (win) XDestroyWindow(dpy, win);
- if (dpy) XCloseDisplay(dpy);
+ for (int i = 0; i < tb.count; i++)
+ free(tb.lines[i]);
+ free(tb.lines);
+
+ if (xft_draw) XftDrawDestroy(xft_draw);
+ if (font) XftFontClose(dpy, font);
+ if (win) XDestroyWindow(dpy, win);
+ if (dpy) XCloseDisplay(dpy);
}
static void
running = 0;
}
+static int
+measure_text(const char *text)
+{
+ XGlyphInfo ext;
+ XftTextExtentsUtf8(dpy, font, (const FcChar8 *)text, strlen(text), &ext);
+ return ext.width;
+}
+
static void
-setup(void)
+readstdin(void)
{
- XSetWindowAttributes swa;
- int x, y, height;
+ char *line = NULL;
+ size_t linesize = 0;
+ ssize_t len;
- /* open display */
+ tb.lines = NULL;
+ tb.count = 0;
+
+ while ((len = getline(&line, &linesize, stdin)) != -1) {
+ if (len > 0 && line[len - 1] == '\n')
+ line[len - 1] = '\0';
+
+ tb.lines = realloc(tb.lines, (tb.count + 1) * sizeof(char *));
+ if (!tb.lines)
+ die("bxnotify: realloc failed\n");
+
+ tb.lines[tb.count] = strdup(line);
+ if (!tb.lines[tb.count])
+ die("bxnotify: strdup failed\n");
+
+ tb.count++;
+ }
+
+ free(line);
+
+ if (tb.count == 0)
+ die("bxnotify: no input received\n");
+}
+
+static void
+init_display(void)
+{
if (!(dpy = XOpenDisplay(NULL)))
die("bxnotify: cannot open display\n");
screen = DefaultScreen(dpy);
- /* load font first — needed for height calculation */
if (!(font = XftFontOpenName(dpy, screen, FONT)))
die("bxnotify: cannot load font\n");
+}
- /* auto-height: font ascent + descent + padding top/bottom */
- height = font->ascent + font->descent + PADDING * 2;
+static void
+calc_layout(void)
+{
+ int line_h = font->ascent + font->descent;
+ int wrapped_count = 0;
+ char **wrapped = NULL;
- /* position: top-right, below bxbar */
+ for (int i = 0; i < tb.count; i++) {
+ char *line = tb.lines[i];
+
+ while (measure_text(line) > WIN_WIDTH) {
+ /* Find break point */
+ char *p = line + strlen(line);
+ while (p > line && measure_text(line) > WIN_WIDTH) {
+ while (p > line && *p != ' ') p--;
+ if (p == line) break; /* Cannot wrap, forced break */
+ *p = '\0';
+ }
+ /* Add chunk */
+ wrapped = realloc(wrapped, (++wrapped_count) * sizeof(char *));
+ wrapped[wrapped_count - 1] = strdup(line);
+ line = p + 1;
+ }
+ /* Add remainder */
+ wrapped = realloc(wrapped, (++wrapped_count) * sizeof(char *));
+ wrapped[wrapped_count - 1] = strdup(line);
+ }
+
+ /* Update global state */
+ for (int i = 0; i < tb.count; i++) free(tb.lines[i]);
+ free(tb.lines);
+ tb.lines = wrapped;
+ tb.count = wrapped_count;
+ total_height = (tb.count * line_h) + ((tb.count + 1) * PADDING) + (2 * BORDER_WIDTH);
+}
+
+static void
+setup(int win_h)
+{
+ XSetWindowAttributes swa;
+ int x, y;
int win_w = WIN_WIDTH + 2 * BORDER_WIDTH;
- int win_h = height + 2 * BORDER_WIDTH;
x = DisplayWidth(dpy, screen) - win_w - WIN_X;
y = WIN_Y;
- /* create window — override_redirect so bxwm ignores it */
swa.override_redirect = True;
swa.background_pixel = bg.pixel;
swa.border_pixel = 0;
swa.event_mask = ExposureMask;
win = XCreateWindow(dpy, RootWindow(dpy, screen), x, y,
- win_w, win_h, 0, /* border_width = 0 */
+ win_w, win_h, 0,
CopyFromParent, CopyFromParent, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask,
&swa);
- /* allocate colors */
- if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen),
- DefaultColormap(dpy, screen), FG_COLOR, &fg))
+ /* Allocate colors and draw context... */
+ if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), FG_COLOR, &fg))
die("bxnotify: cannot allocate fg color\n");
- if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen),
- DefaultColormap(dpy, screen), BG_COLOR, &bg))
+ if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), BG_COLOR, &bg))
die("bxnotify: cannot allocate bg color\n");
- if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen),
- DefaultColormap(dpy, screen), BORDER_COLOR, &border))
+ if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), BORDER_COLOR, &border))
die("bxnotify: cannot allocate border color\n");
- /* create draw context */
- xft_draw = XftDrawCreate(dpy, win, DefaultVisual(dpy, screen),
- DefaultColormap(dpy, screen));
+ xft_draw = XftDrawCreate(dpy, win, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));
XMapWindow(dpy, win);
}
static void
-readstdin(void)
-{
- if (fgets(text, sizeof(text), stdin) == NULL) {
- die("bxnotify: no input received\n");
- }
- /* remove trailing newline */
- text[strcspn(text, "\n")] = '\0';
-}
-
-static void
draw(void)
{
- int height = font->ascent + font->descent + PADDING * 2;
- int win_w = WIN_WIDTH + 2 * BORDER_WIDTH;
- int win_h = height + 2 * BORDER_WIDTH;
- int x, y;
- XGlyphInfo ext;
+ int line_h = font->ascent + font->descent;
+ /* Redraw background... */
+ XftDrawRect(xft_draw, &border, 0, 0, WIN_WIDTH + 2 * BORDER_WIDTH, total_height);
+ XftDrawRect(xft_draw, &bg, BORDER_WIDTH, BORDER_WIDTH, WIN_WIDTH, total_height - 2 * BORDER_WIDTH);
- /* draw border (full window) */
- XftDrawRect(xft_draw, &border, 0, 0, win_w, win_h);
-
- /* draw background (inner area) */
- XftDrawRect(xft_draw, &bg, BORDER_WIDTH, BORDER_WIDTH, WIN_WIDTH, height);
-
- switch (ALIGN) {
- case 0: /* left */
- x = PADDING;
- break;
- case 1: /* center */
- XftTextExtentsUtf8(dpy, font, (XftChar8 *)text, strlen(text), &ext);
- x = (WIN_WIDTH - ext.width) / 2;
- break;
- case 2: /* right */
- XftTextExtentsUtf8(dpy, font, (XftChar8 *)text, strlen(text), &ext);
- x = WIN_WIDTH - ext.width - PADDING;
- break;
+ for (int i = 0; i < tb.count; i++) {
+ /* Calculate Y for this specific line */
+ int y = PADDING + BORDER_WIDTH + (i * line_h) + font->ascent;
+ int x;
+ XGlyphInfo ext;
+ XftTextExtentsUtf8(dpy, font, (XftChar8 *)tb.lines[i], strlen(tb.lines[i]), &ext);
+
+ switch (ALIGN) {
+ case 0: x = BORDER_WIDTH + PADDING; break;
+ case 1: x = BORDER_WIDTH + (WIN_WIDTH - ext.width) / 2; break;
+ case 2: x = BORDER_WIDTH + (WIN_WIDTH - ext.width - PADDING); break;
+ default: x = BORDER_WIDTH + PADDING; break;
+ }
+ XftDrawStringUtf8(xft_draw, &fg, font, x, y, (XftChar8 *)tb.lines[i], strlen(tb.lines[i]));
}
-
- y = (height - (font->ascent + font->descent)) / 2 + font->ascent;
-
- /* offset text into inner content area */
- x += BORDER_WIDTH;
- y += BORDER_WIDTH;
-
- XftDrawStringUtf8(xft_draw, &fg, font, x, y,
- (XftChar8 *)text, strlen(text));
-
XFlush(dpy);
}
draw();
}
- /* check duration timeout */
+ /* Check duration timeout */
if (DURATION > 0 && (time(NULL) - start_time) >= DURATION)
running = 0;