first commit

This commit is contained in:
Hojun-Cho 2025-12-30 21:19:07 +09:00
parent dd451e517a
commit 43d561508f
16 changed files with 5668 additions and 0 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
snoti
# Prerequisites # Prerequisites
*.d *.d

17
Makefile Normal file
View File

@ -0,0 +1,17 @@
CC = 9c
LD = 9l
CFLAGS = `pkg-config --cflags dbus-1`
LIBS = `pkg-config --libs dbus-1` -lxcb -lXau -lm
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
snoti: $(OBJS)
$(LD) -o $@ $(OBJS) $(LIBS)
%.o: %.c dat.h fn.h
$(CC) $(CFLAGS) $<
clean:
rm -f $(OBJS) snoti

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# snoti
dbus notification daemon
## Dependencies
- plan9port
- libxcb
- libdbus
## Build
```
make
```
## Run
```
./snoti -n 6 -t 10000 -s coin.wav
OR
see run.sh
```
## Reference
- https://specifications.freedesktop.org/notification-spec/

BIN
coin.wav Normal file

Binary file not shown.

40
dat.h Normal file
View File

@ -0,0 +1,40 @@
#include <u.h>
#include <libc.h>
#include <thread.h>
enum
{
Fontsz = 24,
Fontbase = 4,
Nglyphs = 0x20000,
Maxnotify = 16,
Winw = 300,
Winh = 80,
Margin = 20,
Padding = 10,
Colfg = 0x000000,
Colbg = 0xffffea,
};
typedef struct Str Str;
struct Str
{
Rune r[256];
int n;
};
typedef struct Noti Noti;
struct Noti
{
Str summary;
Str body;
};
extern Channel *notic;
extern char *fontpath;
extern char *soundpath;
extern int maxshow;
extern int timeout;

103
dbus.c Normal file
View File

@ -0,0 +1,103 @@
#include <dbus/dbus.h>
#include "dat.h"
#include "fn.h"
static DBusConnection *conn;
static u32int notifyid = 1;
static void
sendnoti(DBusMessage *msg)
{
DBusMessageIter args;
Noti n;
char *summary, *body;
if(!dbus_message_iter_init(msg, &args))
return;
dbus_message_iter_next(&args);
dbus_message_iter_next(&args);
dbus_message_iter_next(&args);
if(dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING)
return;
dbus_message_iter_get_basic(&args, &summary);
dbus_message_iter_next(&args);
if(dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING)
return;
dbus_message_iter_get_basic(&args, &body);
notifyid++;
sinit(&n.summary, summary, strlen(summary));
sinit(&n.body, body, strlen(body));
send(notic, &n);
}
static DBusHandlerResult
filter(DBusConnection *c, DBusMessage *msg, void *data)
{
DBusMessage *reply;
DBusMessageIter iter, arr;
u32int id;
char *s;
USED(c);
USED(data);
if(dbus_message_is_method_call(msg, "org.freedesktop.Notifications", "Notify")){
sendnoti(msg);
reply = dbus_message_new_method_return(msg);
id = notifyid - 1;
dbus_message_append_args(reply, DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID);
dbus_connection_send(conn, reply, nil);
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_HANDLED;
}
if(dbus_message_is_method_call(msg, "org.freedesktop.Notifications", "GetCapabilities")){
reply = dbus_message_new_method_return(msg);
dbus_message_iter_init_append(reply, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &arr);
s = "body"; dbus_message_iter_append_basic(&arr, DBUS_TYPE_STRING, &s);
s = "body-markup"; dbus_message_iter_append_basic(&arr, DBUS_TYPE_STRING, &s);
s = "icon-static"; dbus_message_iter_append_basic(&arr, DBUS_TYPE_STRING, &s);
s = "actions"; dbus_message_iter_append_basic(&arr, DBUS_TYPE_STRING, &s);
s = "persistence"; dbus_message_iter_append_basic(&arr, DBUS_TYPE_STRING, &s);
dbus_message_iter_close_container(&iter, &arr);
dbus_connection_send(conn, reply, nil);
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_HANDLED;
}
if(dbus_message_is_method_call(msg, "org.freedesktop.Notifications", "GetServerInformation")){
reply = dbus_message_new_method_return(msg);
s = "snoti"; dbus_message_append_args(reply, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID);
s = "plan9"; dbus_message_append_args(reply, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID);
s = "1.0"; dbus_message_append_args(reply, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID);
s = "1.2"; dbus_message_append_args(reply, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID);
dbus_connection_send(conn, reply, nil);
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_HANDLED;
}
if(dbus_message_is_method_call(msg, "org.freedesktop.Notifications", "CloseNotification")){
reply = dbus_message_new_method_return(msg);
dbus_connection_send(conn, reply, nil);
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
void
dbusthread(void*)
{
DBusError err;
int ret;
dbus_error_init(&err);
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if(dbus_error_is_set(&err))
die("dbus: %s", err.message);
ret = dbus_bus_request_name(conn, "org.freedesktop.Notifications",
DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
die("dbus: can't get name");
dbus_connection_add_filter(conn, filter, nil, nil);
while(dbus_connection_read_write_dispatch(conn, 100))
;
die("dbus: connection lost");
}

10
fn.h Normal file
View File

@ -0,0 +1,10 @@
void die(char*, ...);
void* emalloc(ulong);
void sinit(Str*, char*, int);
void dbusthread(void*);
void notithread(void*);
void fontinit(char*);
void putfont(u32int*, int, int, int, int, Rune);

125
font.c Normal file
View File

@ -0,0 +1,125 @@
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"
#include "dat.h"
#include "fn.h"
typedef struct Glyph Glyph;
struct Glyph
{
uchar *bmp;
int w, h, ox, oy;
};
enum { Maxfonts = 4 };
static stbtt_fontinfo fonts[Maxfonts];
static uchar *fontdata[Maxfonts];
static float scale[Maxfonts];
static int nfonts;
static Glyph cache[Nglyphs];
static u32int blendtab[256];
static u32int
blend(u32int bg, u32int fg, int a)
{
int br, bg_, bb, fr, fg_, fb, r, g, b, inv;
inv = 255 - a;
br = (bg >> 16) & 0xff;
bg_ = (bg >> 8) & 0xff;
bb = bg & 0xff;
fr = (fg >> 16) & 0xff;
fg_ = (fg >> 8) & 0xff;
fb = fg & 0xff;
r = (br * inv + fr * a) / 255;
g = (bg_ * inv + fg_ * a) / 255;
b = (bb * inv + fb * a) / 255;
return (r << 16) | (g << 8) | b;
}
static void
loadfont(char *path)
{
int fd;
long sz, n;
if(nfonts >= Maxfonts)
die("too many fonts");
fd = open(path, OREAD);
if(fd < 0)
die("can't open font: %s", path);
sz = seek(fd, 0, 2);
seek(fd, 0, 0);
fontdata[nfonts] = emalloc(sz);
n = readn(fd, fontdata[nfonts], sz);
close(fd);
if(n != sz)
die("can't read font: %s", path);
if(!stbtt_InitFont(&fonts[nfonts], fontdata[nfonts], stbtt_GetFontOffsetForIndex(fontdata[nfonts], 0)))
die("can't init font: %s", path);
scale[nfonts] = stbtt_ScaleForPixelHeight(&fonts[nfonts], Fontsz);
nfonts++;
}
void
fontinit(char *dir)
{
int fd, n, i, a;
Dir *d;
char path[256], *p;
fd = open(dir, OREAD);
if(fd < 0)
die("can't open font dir: %s", dir);
n = dirreadall(fd, &d);
close(fd);
if(n < 0)
die("can't read font dir: %s", dir);
for(i = 0; i < n; i++){
p = strrchr(d[i].name, '.');
if(p != nil && (strcmp(p, ".ttf") == 0 || strcmp(p, ".otf") == 0)){
snprint(path, sizeof path, "%s/%s", dir, d[i].name);
loadfont(path);
}
}
free(d);
if(nfonts == 0)
die("no fonts in %s", dir);
for(a = 0; a < 256; a++)
blendtab[a] = blend(Colbg, Colfg, a);
}
void
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
{
Glyph *g;
int i, j, x, y, a, f;
if(r >= Nglyphs)
return;
g = &cache[r];
if(g->bmp == nil){
for(f = 0; f < nfonts; f++){
if(stbtt_FindGlyphIndex(&fonts[f], r) == 0)
continue;
g->bmp = stbtt_GetCodepointBitmap(&fonts[f], scale[f], scale[f], r, &g->w, &g->h, &g->ox, &g->oy);
if(g->bmp != nil)
break;
}
if(g->bmp == nil)
return;
}
for(j = 0; j < g->h; j++){
y = py + j + g->oy + Fontsz - Fontbase;
if(y < 0 || y >= h)
continue;
for(i = 0; i < g->w; i++){
x = px + i + g->ox;
if(x < 0 || x >= w)
continue;
a = g->bmp[j * g->w + i];
if(a > 0)
buf[y * w + x] = blendtab[a];
}
}
}

BIN
font/NotoEmoji-Regular.ttf Normal file

Binary file not shown.

BIN
font/NotoSans-Regular.ttf Normal file

Binary file not shown.

Binary file not shown.

76
main.c Normal file
View File

@ -0,0 +1,76 @@
#include <signal.h>
#include "dat.h"
#include "fn.h"
Channel *notic;
char *fontpath = "font";
char *soundpath = nil;
int maxshow = 4;
int timeout = 5000;
int
threadmaybackground(void)
{
return 1;
}
void
die(char *fmt, ...)
{
va_list arg;
char buf[256];
va_start(arg, fmt);
vsnprint(buf, sizeof buf, fmt, arg);
va_end(arg);
fprint(2, "snoti: %s\n", buf);
threadexitsall("error");
}
void*
emalloc(ulong n)
{
void *p;
p = malloc(n);
if(p == nil)
die("malloc failed");
memset(p, 0, n);
return p;
}
void
usage(void)
{
fprint(2, "usage: snoti [-n maxshow] [-t timeout] [-s soundpath] [fontpath]\n");
threadexitsall("usage");
}
void
threadmain(int argc, char **argv)
{
ARGBEGIN{
case 'n':
maxshow = atoi(EARGF(usage()));
if(maxshow > Maxnotify)
die("maxshow is too large.");
break;
case 's':
soundpath = EARGF(usage());
break;
case 't':
timeout = atoi(EARGF(usage()));
break;
default:
usage();
}ARGEND
if(argc > 0)
fontpath = argv[0];
signal(SIGCHLD, SIG_IGN);
notic = chancreate(sizeof(Noti), 4);
proccreate(notithread, nil, 16384);
proccreate(dbusthread, nil, 16384);
threadexits(nil);
}

169
noti.c Normal file
View File

@ -0,0 +1,169 @@
#include <xcb/xcb.h>
#include "dat.h"
#include "fn.h"
static xcb_connection_t *xconn;
static xcb_screen_t *scr;
static xcb_window_t wins[Maxnotify];
static xcb_gcontext_t gc;
static Noti notifs[Maxnotify];
static vlong createdat[Maxnotify];
static u32int *img;
static int depth;
static int head, count;
static int
nth(int i)
{
return (head - count + i + Maxnotify) % Maxnotify;
}
static void
draw(Noti *n)
{
int i, x;
for(i = 0; i < Winw * Winh; i++)
img[i] = Colbg;
x = Padding;
for(i = 0; i < n->summary.n && x < Winw - Padding; i++){
putfont(img, Winw, Winh, x, Padding, n->summary.r[i]);
x += Fontsz / 2;
}
x = Padding;
for(i = 0; i < n->body.n && x < Winw - Padding; i++){
putfont(img, Winw, Winh, x, Padding + Fontsz, n->body.r[i]);
x += Fontsz / 2;
}
}
static void
redraw(void)
{
int i, x, y;
u32int mask, vals[3];
for(i = 0; i < maxshow; i++){
if(wins[i]){
xcb_destroy_window(xconn, wins[i]);
wins[i] = 0;
}
}
for(i = 0; i < count && i < maxshow; i++){
x = scr->width_in_pixels - Winw - Margin;
y = Margin + i * (Winh + 10);
wins[i] = xcb_generate_id(xconn);
mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
vals[0] = Colbg;
vals[1] = 1;
vals[2] = XCB_EVENT_MASK_BUTTON_PRESS;
xcb_create_window(xconn, XCB_COPY_FROM_PARENT, wins[i], scr->root,
x, y, Winw, Winh, 1,
XCB_WINDOW_CLASS_INPUT_OUTPUT, scr->root_visual, mask, vals);
xcb_map_window(xconn, wins[i]);
draw(&notifs[nth(i)]);
xcb_put_image(xconn, XCB_IMAGE_FORMAT_Z_PIXMAP, wins[i], gc,
Winw, Winh, 0, 0, 0, depth, Winw * Winh * 4, (u8int*)img);
}
xcb_flush(xconn);
}
static void
push(Noti *n)
{
notifs[head] = *n;
createdat[head] = nsec() + (vlong)timeout * 1000000LL;
head = (head + 1) % Maxnotify;
if(count < Maxnotify)
count++;
if(soundpath != nil && fork() == 0){
execl("/usr/bin/aplay", "aplay", "-q", soundpath, nil);
_exit(1);
}
redraw();
}
static void
hide(int slot)
{
if(slot < 0 || slot >= count)
return;
for(; slot < count - 1; slot++){
notifs[nth(slot)] = notifs[nth(slot + 1)];
createdat[nth(slot)] = createdat[nth(slot + 1)];
}
count--;
redraw();
}
static void
delexpired(void)
{
vlong now;
now = nsec();
while(count > 0){
if(createdat[nth(0)] >= now)
break;
count--;
}
redraw();
}
static int
findslotbywin(xcb_window_t win)
{
int i;
for(i = 0; i < maxshow; i++)
if(wins[i] == win)
return i;
return -1;
}
static void
wininit(void)
{
int n;
xcb_screen_iterator_t iter;
xconn = xcb_connect(nil, &n);
if(xconn == nil || xcb_connection_has_error(xconn))
die("xcb_connect");
iter = xcb_setup_roots_iterator(xcb_get_setup(xconn));
while(n-- > 0)
xcb_screen_next(&iter);
scr = iter.data;
if(scr == nil)
die("no screen");
depth = scr->root_depth;
gc = xcb_generate_id(xconn);
xcb_create_gc(xconn, gc, scr->root, 0, nil);
img = emalloc(Winw * Winh * sizeof(u32int));
fontinit(fontpath);
}
void
notithread(void*)
{
Noti n;
xcb_generic_event_t *ev;
int slot;
threadsetname("noti");
wininit();
for(;;){
while((ev = xcb_poll_for_event(xconn)) != nil){
if((ev->response_type & ~0x80) == XCB_BUTTON_PRESS){
slot = findslotbywin(((xcb_button_press_event_t*)ev)->event);
if(slot >= 0)
hide(slot);
}
free(ev);
}
while(nbrecv(notic, &n) > 0)
push(&n);
delexpired();
sleep(100);
}
}

4
run.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
pkill snoti
./snoti -n 6 -t 10000 -s coin.wav "$@"

5079
stb_truetype.h Normal file

File diff suppressed because it is too large Load Diff

21
str.c Normal file
View File

@ -0,0 +1,21 @@
#include "dat.h"
#include "fn.h"
enum
{
Maxrunes = nelem(((Str*)0)->r),
};
void
sinit(Str *s, char *src, int n)
{
int len;
s->n = 0;
while(n > 0 && s->n < Maxrunes){
len = chartorune(&s->r[s->n], src);
s->n++;
src += len;
n -= len;
}
}