first commit
This commit is contained in:
57
.gitignore
vendored
Normal file
57
.gitignore
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
snoti
|
||||||
|
|
||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
*.o
|
||||||
|
*.ko
|
||||||
|
*.obj
|
||||||
|
*.elf
|
||||||
|
|
||||||
|
# Linker output
|
||||||
|
*.ilk
|
||||||
|
*.map
|
||||||
|
*.exp
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.la
|
||||||
|
*.lo
|
||||||
|
|
||||||
|
# Shared objects (inc. Windows DLLs)
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
*.i*86
|
||||||
|
*.x86_64
|
||||||
|
*.hex
|
||||||
|
|
||||||
|
# Debug files
|
||||||
|
*.dSYM/
|
||||||
|
*.su
|
||||||
|
*.idb
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Kernel Module Compile Results
|
||||||
|
*.mod*
|
||||||
|
*.cmd
|
||||||
|
.tmp_versions/
|
||||||
|
modules.order
|
||||||
|
Module.symvers
|
||||||
|
Mkfile.old
|
||||||
|
dkms.conf
|
||||||
|
|
||||||
|
# debug information files
|
||||||
|
*.dwo
|
||||||
17
Makefile
Normal file
17
Makefile
Normal 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
22
README.md
Normal 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/
|
||||||
53
dat.h
Normal file
53
dat.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include <u.h>
|
||||||
|
#include <libc.h>
|
||||||
|
#include <thread.h>
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Fontsz = 24,
|
||||||
|
Fontbase = 4,
|
||||||
|
Nglyphs = 0x20000,
|
||||||
|
|
||||||
|
Maxnotify = 16,
|
||||||
|
|
||||||
|
Lineh = Fontsz,
|
||||||
|
|
||||||
|
Winw = 300,
|
||||||
|
Winh = 80,
|
||||||
|
Margin = 20,
|
||||||
|
Padding = 10,
|
||||||
|
Gap = 10,
|
||||||
|
|
||||||
|
Colfg = 0x000000,
|
||||||
|
Colbg = 0xffffea,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct Str Str;
|
||||||
|
struct Str
|
||||||
|
{
|
||||||
|
Rune r[256];
|
||||||
|
int n;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct Noti Noti;
|
||||||
|
struct Noti
|
||||||
|
{
|
||||||
|
u32int id;
|
||||||
|
Str summary;
|
||||||
|
Str body;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct CloseEv CloseEv;
|
||||||
|
struct CloseEv
|
||||||
|
{
|
||||||
|
u32int id;
|
||||||
|
u32int reason;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Channel *notic;
|
||||||
|
extern Channel *closec;
|
||||||
|
extern Channel *closereqc;
|
||||||
|
extern char *fontpath;
|
||||||
|
extern char *soundpath;
|
||||||
|
extern int maxshow;
|
||||||
|
extern int timeout;
|
||||||
136
dbus.c
Normal file
136
dbus.c
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
#include <dbus/dbus.h>
|
||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
|
|
||||||
|
static DBusConnection *conn;
|
||||||
|
static u32int notifyid = 1;
|
||||||
|
|
||||||
|
static u32int
|
||||||
|
sendnoti(DBusMessage *msg)
|
||||||
|
{
|
||||||
|
DBusMessageIter args;
|
||||||
|
Noti n;
|
||||||
|
char *summary, *body;
|
||||||
|
u32int replaces;
|
||||||
|
|
||||||
|
memset(&n, 0, sizeof n);
|
||||||
|
summary = "";
|
||||||
|
body = "";
|
||||||
|
replaces = 0;
|
||||||
|
|
||||||
|
if(dbus_message_iter_init(msg, &args)){
|
||||||
|
dbus_message_iter_next(&args);
|
||||||
|
if(dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_UINT32)
|
||||||
|
dbus_message_iter_get_basic(&args, &replaces);
|
||||||
|
dbus_message_iter_next(&args);
|
||||||
|
dbus_message_iter_next(&args);
|
||||||
|
if(dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_STRING)
|
||||||
|
dbus_message_iter_get_basic(&args, &summary);
|
||||||
|
dbus_message_iter_next(&args);
|
||||||
|
if(dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_STRING)
|
||||||
|
dbus_message_iter_get_basic(&args, &body);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(replaces != 0){
|
||||||
|
n.id = replaces;
|
||||||
|
if(replaces >= notifyid)
|
||||||
|
notifyid = replaces + 1;
|
||||||
|
} else {
|
||||||
|
n.id = notifyid++;
|
||||||
|
}
|
||||||
|
sinit(&n.summary, summary, strlen(summary));
|
||||||
|
sinit(&n.body, body, strlen(body));
|
||||||
|
send(notic, &n);
|
||||||
|
return n.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
emitclosed(u32int id, u32int reason)
|
||||||
|
{
|
||||||
|
DBusMessage *sig;
|
||||||
|
|
||||||
|
sig = dbus_message_new_signal("/org/freedesktop/Notifications",
|
||||||
|
"org.freedesktop.Notifications", "NotificationClosed");
|
||||||
|
if(sig == nil)
|
||||||
|
return;
|
||||||
|
dbus_message_append_args(sig,
|
||||||
|
DBUS_TYPE_UINT32, &id,
|
||||||
|
DBUS_TYPE_UINT32, &reason,
|
||||||
|
DBUS_TYPE_INVALID);
|
||||||
|
dbus_connection_send(conn, sig, nil);
|
||||||
|
dbus_message_unref(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
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")){
|
||||||
|
id = sendnoti(msg);
|
||||||
|
reply = dbus_message_new_method_return(msg);
|
||||||
|
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 = "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")){
|
||||||
|
id = 0;
|
||||||
|
if(dbus_message_get_args(msg, nil, DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID) && id != 0)
|
||||||
|
nbsend(closereqc, &id);
|
||||||
|
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;
|
||||||
|
CloseEv ev;
|
||||||
|
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)){
|
||||||
|
while(nbrecv(closec, &ev) > 0)
|
||||||
|
emitclosed(ev.id, ev.reason);
|
||||||
|
}
|
||||||
|
die("dbus: connection lost");
|
||||||
|
}
|
||||||
11
fn.h
Normal file
11
fn.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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);
|
||||||
|
int fontadvance(Rune);
|
||||||
149
font.c
Normal file
149
font.c
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#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, adv;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Glyph *
|
||||||
|
loadglyph(Rune r)
|
||||||
|
{
|
||||||
|
Glyph *g;
|
||||||
|
int f, gi, aw, lsb;
|
||||||
|
|
||||||
|
if(r >= Nglyphs)
|
||||||
|
return nil;
|
||||||
|
g = &cache[r];
|
||||||
|
if(g->adv != 0)
|
||||||
|
return g;
|
||||||
|
for(f = 0; f < nfonts; f++){
|
||||||
|
gi = stbtt_FindGlyphIndex(&fonts[f], r);
|
||||||
|
if(gi == 0)
|
||||||
|
continue;
|
||||||
|
stbtt_GetGlyphHMetrics(&fonts[f], gi, &aw, &lsb);
|
||||||
|
g->bmp = stbtt_GetCodepointBitmap(&fonts[f], scale[f], scale[f], r, &g->w, &g->h, &g->ox, &g->oy);
|
||||||
|
g->adv = (int)(aw * scale[f] + 0.5);
|
||||||
|
if(g->adv < 1)
|
||||||
|
g->adv = 1;
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
g->adv = Fontsz / 2;
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
fontadvance(Rune r)
|
||||||
|
{
|
||||||
|
Glyph *g;
|
||||||
|
|
||||||
|
g = loadglyph(r);
|
||||||
|
return g != nil ? g->adv : Fontsz / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
|
||||||
|
{
|
||||||
|
Glyph *g;
|
||||||
|
int i, j, x, y, a;
|
||||||
|
|
||||||
|
g = loadglyph(r);
|
||||||
|
if(g == nil || 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
BIN
font/NotoEmoji-Regular.ttf
Normal file
Binary file not shown.
BIN
font/NotoSans-Regular.ttf
Normal file
BIN
font/NotoSans-Regular.ttf
Normal file
Binary file not shown.
BIN
font/NotoSansMonoCJKsc-Regular.otf
Normal file
BIN
font/NotoSansMonoCJKsc-Regular.otf
Normal file
Binary file not shown.
80
main.c
Normal file
80
main.c
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#include <signal.h>
|
||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
|
|
||||||
|
Channel *notic;
|
||||||
|
Channel *closec;
|
||||||
|
Channel *closereqc;
|
||||||
|
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);
|
||||||
|
closec = chancreate(sizeof(CloseEv), 32);
|
||||||
|
closereqc = chancreate(sizeof(u32int), 8);
|
||||||
|
proccreate(notithread, nil, 16384);
|
||||||
|
proccreate(dbusthread, nil, 16384);
|
||||||
|
|
||||||
|
threadexits(nil);
|
||||||
|
}
|
||||||
333
noti.c
Normal file
333
noti.c
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
#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 int expanded[Maxnotify];
|
||||||
|
static u32int *img;
|
||||||
|
static int imgcap;
|
||||||
|
static int depth;
|
||||||
|
static int head, count;
|
||||||
|
|
||||||
|
static int
|
||||||
|
nth(int i)
|
||||||
|
{
|
||||||
|
return (head - count + i + Maxnotify) % Maxnotify;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
wraplines(Rune *r, int n, int maxw)
|
||||||
|
{
|
||||||
|
int i, x, adv, lines;
|
||||||
|
|
||||||
|
if(n <= 0)
|
||||||
|
return 0;
|
||||||
|
x = 0;
|
||||||
|
lines = 1;
|
||||||
|
for(i = 0; i < n; i++){
|
||||||
|
adv = fontadvance(r[i]);
|
||||||
|
if(x + adv > maxw && x > 0){
|
||||||
|
x = 0;
|
||||||
|
lines++;
|
||||||
|
}
|
||||||
|
x += adv;
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
notiheight(int slot)
|
||||||
|
{
|
||||||
|
int textw, sl, bl, h;
|
||||||
|
Noti *n;
|
||||||
|
|
||||||
|
if(!expanded[nth(slot)])
|
||||||
|
return Winh;
|
||||||
|
n = ¬ifs[nth(slot)];
|
||||||
|
textw = Winw - 2*Padding;
|
||||||
|
sl = wraplines(n->summary.r, n->summary.n, textw);
|
||||||
|
bl = wraplines(n->body.r, n->body.n, textw);
|
||||||
|
if(sl < 1) sl = 1;
|
||||||
|
if(bl < 1) bl = 1;
|
||||||
|
h = 2*Padding + (sl + bl) * Lineh;
|
||||||
|
if(h < Winh)
|
||||||
|
h = Winh;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
drawline(u32int *buf, int w, int h, int x, int y, Rune *r, int n, int maxw)
|
||||||
|
{
|
||||||
|
int i, adv, used, ellaw;
|
||||||
|
|
||||||
|
used = 0;
|
||||||
|
for(i = 0; i < n; i++)
|
||||||
|
used += fontadvance(r[i]);
|
||||||
|
if(used <= maxw){
|
||||||
|
for(i = 0; i < n; i++){
|
||||||
|
putfont(buf, w, h, x, y, r[i]);
|
||||||
|
x += fontadvance(r[i]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ellaw = fontadvance(0x2026);
|
||||||
|
used = 0;
|
||||||
|
for(i = 0; i < n; i++){
|
||||||
|
adv = fontadvance(r[i]);
|
||||||
|
if(used + adv + ellaw > maxw)
|
||||||
|
break;
|
||||||
|
putfont(buf, w, h, x, y, r[i]);
|
||||||
|
x += adv;
|
||||||
|
used += adv;
|
||||||
|
}
|
||||||
|
putfont(buf, w, h, x, y, 0x2026);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
drawwrap(u32int *buf, int w, int h, int x0, int y0, Rune *r, int n, int maxw)
|
||||||
|
{
|
||||||
|
int i, x, y, adv;
|
||||||
|
|
||||||
|
x = x0;
|
||||||
|
y = y0;
|
||||||
|
for(i = 0; i < n; i++){
|
||||||
|
adv = fontadvance(r[i]);
|
||||||
|
if(x + adv > x0 + maxw && x > x0){
|
||||||
|
x = x0;
|
||||||
|
y += Lineh;
|
||||||
|
}
|
||||||
|
if(y >= h)
|
||||||
|
break;
|
||||||
|
putfont(buf, w, h, x, y, r[i]);
|
||||||
|
x += adv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw(Noti *n, int wh, int isexpanded)
|
||||||
|
{
|
||||||
|
int i, total, textw, y, sl;
|
||||||
|
|
||||||
|
total = Winw * wh;
|
||||||
|
if(total > imgcap){
|
||||||
|
free(img);
|
||||||
|
img = emalloc(total * sizeof(u32int));
|
||||||
|
imgcap = total;
|
||||||
|
}
|
||||||
|
for(i = 0; i < total; i++)
|
||||||
|
img[i] = Colbg;
|
||||||
|
textw = Winw - 2*Padding;
|
||||||
|
y = Padding;
|
||||||
|
if(isexpanded){
|
||||||
|
sl = wraplines(n->summary.r, n->summary.n, textw);
|
||||||
|
if(sl < 1) sl = 1;
|
||||||
|
drawwrap(img, Winw, wh, Padding, y, n->summary.r, n->summary.n, textw);
|
||||||
|
y += sl * Lineh;
|
||||||
|
drawwrap(img, Winw, wh, Padding, y, n->body.r, n->body.n, textw);
|
||||||
|
} else {
|
||||||
|
drawline(img, Winw, wh, Padding, y, n->summary.r, n->summary.n, textw);
|
||||||
|
y += Lineh;
|
||||||
|
drawline(img, Winw, wh, Padding, y, n->body.r, n->body.n, textw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
redraw(void)
|
||||||
|
{
|
||||||
|
int i, x, y, wh;
|
||||||
|
u32int mask, vals[3];
|
||||||
|
|
||||||
|
for(i = 0; i < Maxnotify; i++){
|
||||||
|
if(wins[i]){
|
||||||
|
xcb_destroy_window(xconn, wins[i]);
|
||||||
|
wins[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
y = Margin;
|
||||||
|
for(i = 0; i < count && i < maxshow; i++){
|
||||||
|
wh = notiheight(i);
|
||||||
|
x = scr->width_in_pixels - Winw - Margin;
|
||||||
|
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, wh, 1,
|
||||||
|
XCB_WINDOW_CLASS_INPUT_OUTPUT, scr->root_visual, mask, vals);
|
||||||
|
xcb_map_window(xconn, wins[i]);
|
||||||
|
draw(¬ifs[nth(i)], wh, expanded[nth(i)]);
|
||||||
|
xcb_put_image(xconn, XCB_IMAGE_FORMAT_Z_PIXMAP, wins[i], gc,
|
||||||
|
Winw, wh, 0, 0, 0, depth, Winw * wh * 4, (u8int*)img);
|
||||||
|
y += wh + Gap;
|
||||||
|
}
|
||||||
|
xcb_flush(xconn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
emitclose(u32int id, u32int reason)
|
||||||
|
{
|
||||||
|
CloseEv ev;
|
||||||
|
|
||||||
|
ev.id = id;
|
||||||
|
ev.reason = reason;
|
||||||
|
nbsend(closec, &ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
push(Noti *n)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
CloseEv ev;
|
||||||
|
|
||||||
|
if(n->id != 0){
|
||||||
|
for(i = 0; i < count; i++){
|
||||||
|
if(notifs[nth(i)].id == n->id){
|
||||||
|
notifs[nth(i)] = *n;
|
||||||
|
createdat[nth(i)] = nsec() + (vlong)timeout * 1000000LL;
|
||||||
|
expanded[nth(i)] = 0;
|
||||||
|
redraw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(count == Maxnotify){
|
||||||
|
ev.id = notifs[head].id;
|
||||||
|
ev.reason = 4;
|
||||||
|
nbsend(closec, &ev);
|
||||||
|
}
|
||||||
|
notifs[head] = *n;
|
||||||
|
createdat[head] = nsec() + (vlong)timeout * 1000000LL;
|
||||||
|
expanded[head] = 0;
|
||||||
|
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, int reason)
|
||||||
|
{
|
||||||
|
if(slot < 0 || slot >= count)
|
||||||
|
return;
|
||||||
|
if(reason > 0)
|
||||||
|
emitclose(notifs[nth(slot)].id, reason);
|
||||||
|
for(; slot < count - 1; slot++){
|
||||||
|
notifs[nth(slot)] = notifs[nth(slot + 1)];
|
||||||
|
createdat[nth(slot)] = createdat[nth(slot + 1)];
|
||||||
|
expanded[nth(slot)] = expanded[nth(slot + 1)];
|
||||||
|
}
|
||||||
|
head = (head - 1 + Maxnotify) % Maxnotify;
|
||||||
|
count--;
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
closebyid(u32int id)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < count; i++){
|
||||||
|
if(notifs[nth(i)].id == id){
|
||||||
|
hide(i, 3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
delexpired(void)
|
||||||
|
{
|
||||||
|
vlong now;
|
||||||
|
int c0;
|
||||||
|
|
||||||
|
now = nsec();
|
||||||
|
c0 = count;
|
||||||
|
while(count > 0){
|
||||||
|
if(expanded[nth(0)])
|
||||||
|
break;
|
||||||
|
if(createdat[nth(0)] >= now)
|
||||||
|
break;
|
||||||
|
emitclose(notifs[nth(0)].id, 1);
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
if(count != c0)
|
||||||
|
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);
|
||||||
|
fontinit(fontpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notithread(void*)
|
||||||
|
{
|
||||||
|
Noti n;
|
||||||
|
xcb_generic_event_t *ev;
|
||||||
|
xcb_button_press_event_t *be;
|
||||||
|
u32int closeid;
|
||||||
|
int slot;
|
||||||
|
|
||||||
|
threadsetname("noti");
|
||||||
|
wininit();
|
||||||
|
for(;;){
|
||||||
|
while((ev = xcb_poll_for_event(xconn)) != nil){
|
||||||
|
if((ev->response_type & ~0x80) == XCB_BUTTON_PRESS){
|
||||||
|
be = (xcb_button_press_event_t*)ev;
|
||||||
|
slot = findslotbywin(be->event);
|
||||||
|
if(slot >= 0){
|
||||||
|
if(be->detail == 1){
|
||||||
|
expanded[nth(slot)] = !expanded[nth(slot)];
|
||||||
|
redraw();
|
||||||
|
} else if(be->detail == 3){
|
||||||
|
hide(slot, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(ev);
|
||||||
|
}
|
||||||
|
while(nbrecv(notic, &n) > 0)
|
||||||
|
push(&n);
|
||||||
|
while(nbrecv(closereqc, &closeid) > 0)
|
||||||
|
closebyid(closeid);
|
||||||
|
delexpired();
|
||||||
|
sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
run.sh
Executable file
4
run.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
pkill snoti
|
||||||
|
./snoti -n 6 -t 10000 -s coin.wav "$@"
|
||||||
5079
stb_truetype.h
Normal file
5079
stb_truetype.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user