add color change if argv[1] is a color in #RRGGBB format
[herbe.git] / herbe.c
1 #include <X11/Xlib.h>
2 #include <X11/Xft/Xft.h>
3 #include <regex.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <signal.h>
7 #include <unistd.h>
8 #include <string.h>
9 #include <stdarg.h>
10 #include <fcntl.h>
11 #include <semaphore.h>
12
13 #include "config.h"
14
15 #define EXIT_ACTION 0
16 #define EXIT_FAIL 1
17 #define EXIT_DISMISS 2
18
19 Display *display;
20 Window window;
21 int exit_code = EXIT_DISMISS;
22
23 static void die(const char *format, ...)
24 {
25 va_list ap;
26 va_start(ap, format);
27 vfprintf(stderr, format, ap);
28 fprintf(stderr, "\n");
29 va_end(ap);
30 exit(EXIT_FAIL);
31 }
32
33 char *read_stdin_walloc(void)
34 {
35 char *buff = NULL;
36 size_t size = 0;
37 FILE *memstream = open_memstream(&buff, &size);
38
39 if (!memstream)
40 die("open_memstream failed");
41
42 char *temp = malloc(4096);
43 if (!temp)
44 die("malloc failed");
45
46 size_t len;
47 while ((len = fread(temp, 1, 4096, stdin)) > 0)
48 fwrite(temp, 1, len, memstream);
49
50 free(temp);
51 fclose(memstream);
52
53 return buff;
54 }
55
56 char *invert_color(const char *color)
57 {
58 char c;
59 static char inverted[8];
60
61 strcpy(inverted, color);
62 for (int i = 1; i < 7; i++)
63 {
64 c = color[i];
65 inverted[i] = (c < 'a') ?
66 "0123456789ABCDEF"[15 - (c - '0' - (c > '9' ? 7 : 0))] :
67 "0123456789abcdef"[15 - (c - 'a')];
68 }
69
70 inverted[7] = '\0';
71 return inverted;
72 }
73
74 int get_max_len(char *string, XftFont *font, int max_text_width)
75 {
76 int eol = strlen(string);
77 XGlyphInfo info;
78 XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info);
79
80 if (info.width > max_text_width)
81 {
82 eol = max_text_width / font->max_advance_width;
83 info.width = 0;
84
85 while (info.width < max_text_width)
86 {
87 eol++;
88 XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info);
89 }
90
91 eol--;
92 }
93
94 for (int i = 0; i < eol; i++)
95 if (string[i] == '\n')
96 {
97 string[i] = ' ';
98 return ++i;
99 }
100
101 if (info.width <= max_text_width)
102 return eol;
103
104 int temp = eol;
105
106 while (string[eol] != ' ' && eol)
107 --eol;
108
109 if (eol == 0)
110 return temp;
111 else
112 return ++eol;
113 }
114
115 void expire(int sig)
116 {
117 XEvent event;
118 event.type = ButtonPress;
119 event.xbutton.button = (sig == SIGUSR2) ? (ACTION_BUTTON) : (DISMISS_BUTTON);
120 XSendEvent(display, window, 0, 0, &event);
121 XFlush(display);
122 }
123
124 int main(int argc, char *argv[])
125 {
126 int nargc = 0, rstatus;
127 regex_t regex;
128 regmatch_t match;
129 char **nargv = NULL;
130 char *body = NULL, *bptr;
131 const char *pattern = "#[a-fA-F0-9]{6}";
132
133 if (!isatty(STDIN_FILENO))
134 {
135 body = read_stdin_walloc();
136 bptr = body;
137
138 while (*bptr)
139 {
140 if (*bptr == '\n' && *(bptr + 1)) nargc++;
141 bptr++;
142 }
143
144 nargc++;
145 bptr = body;
146
147 nargv = malloc((argc + nargc) * sizeof(char *));
148 if (!nargv)
149 die("malloc failed");
150
151 memcpy(nargv, argv, argc * sizeof(char *));
152
153 int c = argc;
154 nargv[c] = body;
155 c++;
156
157 while (*bptr)
158 {
159 if (c == nargc + argc) break;
160 if (*bptr == '\n' && *(bptr + 1))
161 {
162 nargv[c] = bptr+1;
163 *bptr = '\0';
164 c++;
165 }
166 bptr++;
167 }
168
169 argc += nargc;
170 argv = nargv;
171 }
172 else if (argc == 1)
173 {
174 sem_unlink("/herbe");
175 die("usage: %s [body]", argv[0]);
176 }
177
178 struct sigaction act_expire, act_ignore;
179
180 if ((rstatus = regcomp(&regex, pattern, REG_EXTENDED)))
181 die("%s: regex compilation failed", argv[0]);
182
183 if (!(rstatus = regexec(&regex, argv[1], 1, &match, 0)))
184 {
185 background_color = argv[1];
186 font_color = invert_color(argv[1]);
187 border_color = font_color;
188 }
189
190 act_expire.sa_handler = expire;
191 act_expire.sa_flags = SA_RESTART;
192 sigemptyset(&act_expire.sa_mask);
193
194 act_ignore.sa_handler = SIG_IGN;
195 act_ignore.sa_flags = 0;
196 sigemptyset(&act_ignore.sa_mask);
197
198 sigaction(SIGALRM, &act_expire, 0);
199 sigaction(SIGTERM, &act_expire, 0);
200 sigaction(SIGINT, &act_expire, 0);
201
202 sigaction(SIGUSR1, &act_ignore, 0);
203 sigaction(SIGUSR2, &act_ignore, 0);
204
205 if (!(display = XOpenDisplay(0)))
206 die("Cannot open display");
207
208 int screen = DefaultScreen(display);
209 Visual *visual = DefaultVisual(display, screen);
210 Colormap colormap = DefaultColormap(display, screen);
211
212 int screen_width = DisplayWidth(display, screen);
213 int screen_height = DisplayHeight(display, screen);
214
215 XSetWindowAttributes attributes;
216 attributes.override_redirect = True;
217 XftColor color;
218 XftColorAllocName(display, visual, colormap, background_color, &color);
219 attributes.background_pixel = color.pixel;
220 XftColorAllocName(display, visual, colormap, border_color, &color);
221 attributes.border_pixel = color.pixel;
222
223 int num_of_lines = 0;
224 int max_text_width = width - 2 * padding;
225 int lines_size = 5;
226 char **lines = malloc(lines_size * sizeof(char *));
227 if (!lines)
228 die("malloc failed");
229
230 XftFont *font = XftFontOpenName(display, screen, font_pattern);
231
232 for (int i = 1; i < argc; i++)
233 {
234 for (unsigned int eol = get_max_len(argv[i], font, max_text_width); eol; argv[i] += eol, num_of_lines++, eol = get_max_len(argv[i], font, max_text_width))
235 {
236 if (lines_size <= num_of_lines)
237 {
238 lines = realloc(lines, (lines_size += 5) * sizeof(char *));
239 if (!lines)
240 die("realloc failed");
241 }
242
243 lines[num_of_lines] = malloc((eol + 1) * sizeof(char));
244 if (!lines[num_of_lines])
245 die("malloc failed");
246
247 strncpy(lines[num_of_lines], argv[i], eol);
248 lines[num_of_lines][eol] = '\0';
249 }
250 }
251
252 unsigned int x = pos_x;
253 unsigned int y = pos_y;
254 unsigned int text_height = font->ascent - font->descent;
255 unsigned int height = (num_of_lines - 1) * line_spacing + num_of_lines * text_height + 2 * padding;
256
257 if (corner == TOP_RIGHT || corner == BOTTOM_RIGHT)
258 x = screen_width - width - border_size * 2 - pos_x;
259
260 if (corner == BOTTOM_LEFT || corner == BOTTOM_RIGHT)
261 y = screen_height - height - border_size * 2 - pos_y;
262
263 window = XCreateWindow(display, RootWindow(display, screen), x, y, width, height, border_size, DefaultDepth(display, screen),
264 CopyFromParent, visual, CWOverrideRedirect | CWBackPixel | CWBorderPixel, &attributes);
265
266 XftDraw *draw = XftDrawCreate(display, window, visual, colormap);
267 XftColorAllocName(display, visual, colormap, font_color, &color);
268
269 XSelectInput(display, window, ExposureMask | ButtonPress);
270 XMapWindow(display, window);
271
272 sem_t *mutex = sem_open("/herbe", O_CREAT, 0644, 1);
273 sem_wait(mutex);
274
275 sigaction(SIGUSR1, &act_expire, 0);
276 sigaction(SIGUSR2, &act_expire, 0);
277
278 if (duration != 0)
279 alarm(duration);
280
281 for (;;)
282 {
283 XEvent event;
284 XNextEvent(display, &event);
285
286 if (event.type == Expose)
287 {
288 XClearWindow(display, window);
289 for (int i = 0; i < num_of_lines; i++)
290 XftDrawStringUtf8(draw, &color, font, padding, line_spacing * i + text_height * (i + 1) + padding,
291 (FcChar8 *)lines[i], strlen(lines[i]));
292 }
293 else if (event.type == ButtonPress)
294 {
295 if (event.xbutton.button == DISMISS_BUTTON)
296 break;
297 else if (event.xbutton.button == ACTION_BUTTON)
298 {
299 exit_code = EXIT_ACTION;
300 break;
301 }
302 }
303 }
304
305 sem_post(mutex);
306 sem_close(mutex);
307
308 for (int i = 0; i < num_of_lines; i++)
309 free(lines[i]);
310
311 if (nargv)
312 free(nargv);
313 if (body)
314 free(body);
315 free(lines);
316 regfree(&regex);
317 XftDrawDestroy(draw);
318 XftColorFree(display, visual, colormap, &color);
319 XftFontClose(display, font);
320 XCloseDisplay(display);
321
322 return exit_code;
323 }