herbe.c (8868B)
1 #include <X11/Xft/Xft.h> 2 #include <X11/Xlib.h> 3 #include <errno.h> 4 #include <fcntl.h> 5 #include <fcntl.h> 6 #include <mqueue.h> 7 #include <signal.h> 8 #include <stdarg.h> 9 #include <X11/Xresource.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <string.h> 13 #include <sys/file.h> 14 #include <sys/ipc.h> 15 #include <sys/shm.h> 16 #include <unistd.h> 17 18 #include "config.h" 19 20 #define EXIT_ACTION 0 21 #define EXIT_FAIL 1 22 #define EXIT_DISMISS 2 23 24 #define XRES_STR(name) \ 25 if (XrmGetResource(db, "herbe." #name, "*", &type, &val)) \ 26 name = val.addr 27 #define XRES_INT(name) \ 28 if (XrmGetResource(db, "herbe." #name, "*", &type, &val)) \ 29 name = strtoul(val.addr, 0, 10) 30 31 Display *display; 32 XftFont *font; 33 Window window; 34 int num_of_lines; 35 char **lines; 36 int exit_code = EXIT_DISMISS; 37 38 struct mq_object { 39 pid_t pid; 40 long timestamp; 41 char buffer[1024]; 42 }; 43 long lastTimestamp; 44 45 static void die(const char *format, ...) 46 { 47 va_list ap; 48 va_start(ap, format); 49 vfprintf(stderr, format, ap); 50 fprintf(stderr, "\n"); 51 va_end(ap); 52 exit(EXIT_FAIL); 53 } 54 55 void read_y_offset(unsigned int **offset, int *id) { 56 int shm_id = shmget(8432, sizeof(unsigned int), IPC_CREAT | 0660); 57 if (shm_id == -1) die("shmget failed"); 58 59 *offset = (unsigned int *)shmat(shm_id, 0, 0); 60 if (*offset == (unsigned int *)-1) die("shmat failed\n"); 61 *id = shm_id; 62 } 63 64 void free_y_offset(int id) { 65 shmctl(id, IPC_RMID, NULL); 66 } 67 68 int get_max_len(char *string, XftFont *font, int max_text_width) 69 { 70 int eol = strlen(string); 71 XGlyphInfo info; 72 XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info); 73 74 if (info.width > max_text_width) 75 { 76 eol = max_text_width / font->max_advance_width; 77 info.width = 0; 78 79 while (info.width < max_text_width) 80 { 81 eol++; 82 XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info); 83 } 84 85 eol--; 86 } 87 88 for (int i = 0; i < eol; i++) 89 if (string[i] == '\n') 90 { 91 string[i] = ' '; 92 return ++i; 93 } 94 95 if (info.width <= max_text_width) 96 return eol; 97 98 int temp = eol; 99 100 while (string[eol] != ' ' && eol) 101 --eol; 102 103 if (eol == 0) 104 return temp; 105 else 106 return ++eol; 107 } 108 109 void freeLines() { 110 if(lines) { 111 for (int i = 0; i < num_of_lines; i++) 112 free(lines[i]); 113 free(lines); 114 } 115 } 116 117 void constructLines(char* strList[], int numberOfStrings) { 118 freeLines(); 119 int max_text_width = width - 2 * padding; 120 num_of_lines = 0; 121 int lines_size = 5; 122 lines = malloc(lines_size * sizeof(char *)); 123 if (!lines) 124 die("malloc failed"); 125 126 for (int i = 0; i < numberOfStrings; i++) 127 { 128 for (unsigned int eol = get_max_len(strList[i], font, max_text_width); eol; strList[i] += eol, num_of_lines++, eol = get_max_len(strList[i], font, max_text_width)) 129 { 130 if (lines_size <= num_of_lines) 131 { 132 lines = realloc(lines, (lines_size += 5) * sizeof(char *)); 133 if (!lines) 134 die("realloc failed"); 135 } 136 lines[num_of_lines] = malloc((eol + 1) * sizeof(char)); 137 if (!lines[num_of_lines]) 138 die("malloc failed"); 139 140 strncpy(lines[num_of_lines], strList[i], eol); 141 lines[num_of_lines][eol] = '\0'; 142 } 143 } 144 } 145 146 void reload(union sigval sv); 147 void readAllEvents(mqd_t mqd) { 148 149 struct sigevent event = {.sigev_notify=SIGEV_THREAD, .sigev_signo=SIGHUP, .sigev_value.sival_int=mqd, .sigev_notify_function=reload}; 150 if(mq_notify(mqd, &event) == -1) { 151 perror("mq_notify failed"); 152 exit(1); 153 } 154 struct mq_object object; 155 while(1) { 156 int ret = mq_receive(mqd, (char*)&object, sizeof(object), NULL); 157 if(ret==-1) { 158 if(errno == EAGAIN) 159 return; 160 perror("mq_receive"); 161 exit(1); 162 } 163 if(object.timestamp && lastTimestamp > object.timestamp) 164 return; 165 if(object.timestamp) 166 lastTimestamp = object.timestamp; 167 char *buffer = object.buffer; 168 169 constructLines(&buffer, 1); 170 kill(object.pid, SIGTERM); 171 } 172 } 173 void reload(union sigval sv) { 174 // we've already timed out 175 if(alarm(duration) == 0) 176 return; 177 readAllEvents(sv.sival_int); 178 XEvent event; 179 event.type = Expose; 180 XSendEvent(display, window, 0, 0, &event); 181 XFlush(display); 182 } 183 184 void expire(int sig) 185 { 186 XEvent event; 187 event.type = ButtonPress; 188 event.xbutton.button = (sig == SIGUSR2) ? (ACTION_BUTTON) : (DISMISS_BUTTON); 189 XSendEvent(display, window, 0, 0, &event); 190 XFlush(display); 191 } 192 193 void exitSuccess() { 194 exit(0); 195 } 196 197 int main(int argc, char *argv[]) 198 { 199 if (argc == 1) 200 { 201 die("Usage: %s body", argv[0]); 202 } 203 204 const char* id =getenv("HERBE_ID"); 205 mqd_t mqd=-1; 206 if(id) { 207 struct mq_attr attr = { .mq_maxmsg = 10, .mq_msgsize = sizeof(struct mq_object) }; 208 mqd = mq_open(id, O_RDWR|O_CREAT|O_NONBLOCK, 0722, &attr); 209 if(mqd==-1){ 210 perror("mq_open"); 211 die("mq_open"); 212 } 213 while (1) { 214 if(flock(mqd, LOCK_EX|LOCK_NB) == 0) { 215 // if we get the lock, register for events 216 break; 217 } 218 if(errno != EWOULDBLOCK) { 219 perror("flock"); 220 exit(1); 221 } 222 // someone else is listening for events 223 char* ts_str = getenv("NOTIFICATION_ID"); 224 lastTimestamp = ts_str?atol(ts_str):0; 225 struct mq_object object = {getpid(), lastTimestamp, {0}}; 226 char *buffer=object.buffer; 227 for(int i=1;i<argc;i++) { 228 strcat(buffer, argv[i]); 229 strcat(buffer, "\n"); 230 } 231 signal(SIGTERM, exitSuccess); 232 if(mq_send(mqd, (char*)&object, sizeof(object), 1)==-1) { 233 perror("mq_send"); 234 exit(1); 235 } 236 signal(SIGALRM, SIG_IGN); 237 alarm(1); 238 pause(); 239 } 240 } 241 242 struct sigaction act_expire, act_ignore; 243 244 act_expire.sa_handler = expire; 245 act_expire.sa_flags = SA_RESTART; 246 sigemptyset(&act_expire.sa_mask); 247 248 act_ignore.sa_handler = SIG_IGN; 249 act_ignore.sa_flags = 0; 250 sigemptyset(&act_ignore.sa_mask); 251 252 sigaction(SIGALRM, &act_expire, 0); 253 sigaction(SIGTERM, &act_expire, 0); 254 sigaction(SIGINT, &act_expire, 0); 255 256 sigaction(SIGUSR1, &act_ignore, 0); 257 sigaction(SIGUSR2, &act_ignore, 0); 258 259 if (!(display = XOpenDisplay(0))) 260 die("Cannot open display"); 261 262 XrmInitialize(); 263 264 char *res_man = XResourceManagerString(display); 265 XrmDatabase db = XrmGetStringDatabase(res_man); 266 267 char *type; 268 XrmValue val; 269 270 XRES_STR(background_color); 271 XRES_STR(border_color); 272 XRES_STR(font_color); 273 XRES_STR(font_pattern); 274 275 XRES_INT(line_spacing); 276 XRES_INT(padding); 277 XRES_INT(width); 278 XRES_INT(border_size); 279 XRES_INT(pos_x); 280 XRES_INT(pos_y); 281 XRES_INT(corner); 282 XRES_INT(duration); 283 284 int screen = DefaultScreen(display); 285 Visual *visual = DefaultVisual(display, screen); 286 Colormap colormap = DefaultColormap(display, screen); 287 288 int screen_width = DisplayWidth(display, screen); 289 int screen_height = DisplayHeight(display, screen); 290 291 XSetWindowAttributes attributes; 292 attributes.override_redirect = True; 293 XftColor color; 294 XftColorAllocName(display, visual, colormap, background_color, &color); 295 attributes.background_pixel = color.pixel; 296 XftColorAllocName(display, visual, colormap, border_color, &color); 297 attributes.border_pixel = color.pixel; 298 font = XftFontOpenName(display, screen, font_pattern); 299 300 constructLines(argv+1, argc-1); 301 302 int y_offset_id; 303 unsigned int *y_offset; 304 read_y_offset(&y_offset, &y_offset_id); 305 306 unsigned int text_height = font->ascent - font->descent; 307 unsigned int height = (num_of_lines - 1) * line_spacing + num_of_lines * text_height + 2 * padding; 308 unsigned int x = pos_x; 309 unsigned int y = pos_y + *y_offset; 310 unsigned int used_y_offset = (*y_offset) += height + padding; 311 312 if (corner == TOP_RIGHT || corner == BOTTOM_RIGHT) 313 x = screen_width - width - border_size * 2 - x; 314 if (corner == BOTTOM_LEFT || corner == BOTTOM_RIGHT) 315 y = screen_height - height - border_size * 2 - y; 316 317 window = XCreateWindow(display, RootWindow(display, screen), x, y, width, height, border_size, DefaultDepth(display, screen), 318 CopyFromParent, visual, CWOverrideRedirect | CWBackPixel | CWBorderPixel, &attributes); 319 320 XftDraw *draw = XftDrawCreate(display, window, visual, colormap); 321 XftColorAllocName(display, visual, colormap, font_color, &color); 322 323 XSelectInput(display, window, ExposureMask | ButtonPress); 324 XMapWindow(display, window); 325 326 sigaction(SIGUSR1, &act_expire, 0); 327 sigaction(SIGUSR2, &act_expire, 0); 328 329 if (duration != 0) 330 alarm(duration); 331 332 if(id) { 333 readAllEvents(mqd); 334 } 335 336 for (;;) 337 { 338 XEvent event; 339 XNextEvent(display, &event); 340 341 if (event.type == Expose) 342 { 343 XClearWindow(display, window); 344 for (int i = 0; i < num_of_lines; i++) 345 XftDrawStringUtf8(draw, &color, font, padding, line_spacing * i + text_height * (i + 1) + padding, 346 (FcChar8 *)lines[i], strlen(lines[i])); 347 } 348 else if (event.type == ButtonPress) 349 { 350 if (event.xbutton.button == DISMISS_BUTTON) 351 break; 352 else if (event.xbutton.button == ACTION_BUTTON) 353 { 354 exit_code = EXIT_ACTION; 355 break; 356 } 357 } 358 } 359 360 if (used_y_offset == *y_offset) free_y_offset(y_offset_id); 361 freeLines(); 362 363 XftDrawDestroy(draw); 364 XftColorFree(display, visual, colormap, &color); 365 XftFontClose(display, font); 366 XrmDestroyDatabase(db); 367 XCloseDisplay(display); 368 369 if(id) { 370 mq_close(mqd); 371 } 372 373 return exit_code; 374 }