Update README.md
[herbe.git] / herbe.c
1 #include <X11/Xlib.h>
2 #include <X11/Xft/Xft.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <signal.h>
6 #include <unistd.h>
7 #include <string.h>
8 #include <stdarg.h>
9 #include <fcntl.h>
10 #include <semaphore.h>
11
12 #include "config.h"
13
14 #define EXIT_ACTION 3
15
16 Display *display;
17 Window window;
18 int exit_code = EXIT_SUCCESS;
19
20 static void die(const char *format, ...)
21 {
22 va_list ap;
23 va_start(ap, format);
24 vfprintf(stderr, format, ap);
25 fprintf(stderr, "\n");
26 va_end(ap);
27 exit(EXIT_FAILURE);
28 }
29
30 int get_max_len(char *body, XftFont *font, int max_text_width)
31 {
32 int eol = strlen(body);
33 XGlyphInfo info;
34 XftTextExtentsUtf8(display, font, (FcChar8 *)body, eol, &info);
35
36 if (info.width > max_text_width)
37 {
38 eol = max_text_width / font->max_advance_width;
39 info.width = 0;
40
41 while (info.width < max_text_width)
42 {
43 eol++;
44 XftTextExtentsUtf8(display, font, (FcChar8 *)body, eol, &info);
45 }
46
47 eol--;
48 }
49
50 for (int i = 0; i < eol; i++)
51 if (body[i] == '\n')
52 {
53 body[i] = ' ';
54 return ++i;
55 }
56
57 if (info.width < max_text_width)
58 return eol;
59
60 int temp = eol;
61
62 while (body[eol] != ' ' && eol)
63 --eol;
64
65 if (eol == 0)
66 return temp;
67 else
68 return ++eol;
69 }
70
71 void expire()
72 {
73 XEvent event;
74 event.type = ButtonPress;
75 event.xbutton.button = DISMISS_BUTTON;
76 XSendEvent(display, window, 0, 0, &event);
77 XFlush(display);
78 }
79
80 void action()
81 {
82 exit_code = EXIT_ACTION;
83 expire();
84 }
85
86 int main(int argc, char *argv[])
87 {
88 if (argc == 1)
89 {
90 sem_unlink("/herbe");
91 die("Usage: %s body", argv[0]);
92 }
93
94 signal(SIGALRM, expire);
95 signal(SIGTERM, expire);
96 signal(SIGINT, expire);
97 signal(SIGUSR1, SIG_IGN);
98 signal(SIGUSR2, SIG_IGN);
99
100 display = XOpenDisplay(0);
101
102 if (display == 0)
103 die("Cannot open display");
104
105 int screen = DefaultScreen(display);
106 Visual *visual = DefaultVisual(display, screen);
107 Colormap colormap = DefaultColormap(display, screen);
108
109 int screen_width = DisplayWidth(display, screen);
110 int screen_height = DisplayHeight(display, screen);
111
112 XftColor color;
113
114 XSetWindowAttributes attributes;
115 attributes.override_redirect = True;
116 XftColorAllocName(display, visual, colormap, background_color, &color);
117 attributes.background_pixel = color.pixel;
118 XftColorAllocName(display, visual, colormap, border_color, &color);
119 attributes.border_pixel = color.pixel;
120
121 XftFont *font = XftFontOpenName(display, screen, font_pattern);
122
123 int num_of_lines = 0;
124 int max_text_width = width - 2 * padding;
125 int words_size = 5;
126 char **words = malloc(words_size * sizeof(char *));
127 if (!words)
128 die("malloc failed");
129
130 for (int i = 1; i < argc; i++)
131 {
132 char *body = argv[i];
133
134 for (unsigned int eol = get_max_len(body, font, max_text_width); eol <= strlen(body) && eol; body += eol, num_of_lines++, eol = get_max_len(body, font, max_text_width))
135 {
136 if (words_size <= num_of_lines)
137 {
138 words = realloc(words, (words_size += 5) * sizeof(char *));
139 if (!words)
140 die("malloc failed");
141 }
142 words[num_of_lines] = malloc((eol + 1) * sizeof(char));
143 if (!words[num_of_lines])
144 die("malloc failed");
145 strncpy(words[num_of_lines], body, eol);
146 words[num_of_lines][eol] = '\0';
147 }
148 }
149
150 unsigned int x = pos_x;
151 unsigned int y = pos_y;
152 unsigned int text_height = font->ascent - font->descent;
153 unsigned int height = (num_of_lines - 1) * line_spacing + num_of_lines * text_height + 2 * padding;
154
155 if (corner == TOP_RIGHT || corner == BOTTOM_RIGHT)
156 x = screen_width - width - border_size * 2 - pos_x;
157
158 if (corner == BOTTOM_LEFT || corner == BOTTOM_RIGHT)
159 y = screen_height - height - border_size * 2 - pos_y;
160
161 window = XCreateWindow(display, RootWindow(display, screen), x, y, width, height, border_size, DefaultDepth(display, screen), CopyFromParent, visual,
162 CWOverrideRedirect | CWBackPixel | CWBorderPixel, &attributes);
163
164 XftDraw *draw = XftDrawCreate(display, window, visual, colormap);
165 XftColorAllocName(display, visual, colormap, font_color, &color);
166
167 XSelectInput(display, window, ExposureMask | ButtonPress);
168
169 XMapWindow(display, window);
170
171 sem_t *mutex = sem_open("/herbe", O_CREAT, 0644, 1);
172 sem_wait(mutex);
173
174 signal(SIGUSR1, expire);
175 signal(SIGUSR2, action);
176
177 if (duration != 0)
178 alarm(duration);
179
180 while (1)
181 {
182 XEvent event;
183 XNextEvent(display, &event);
184
185 if (event.type == Expose)
186 {
187 XClearWindow(display, window);
188 for (int i = 0; i < num_of_lines; i++)
189 XftDrawStringUtf8(draw, &color, font, padding, line_spacing * i + text_height * (i + 1) + padding, (FcChar8 *)words[i], strlen(words[i]));
190 }
191 if (event.type == ButtonPress)
192 {
193 if (event.xbutton.button == DISMISS_BUTTON)
194 break;
195 else if (event.xbutton.button == ACTION_BUTTON)
196 {
197 exit_code = EXIT_ACTION;
198 break;
199 }
200 }
201 }
202
203 sem_post(mutex);
204 sem_close(mutex);
205
206 for (int i = 0; i < num_of_lines; i++)
207 free(words[i]);
208
209 free(words);
210 XftDrawDestroy(draw);
211 XftColorFree(display, visual, colormap, &color);
212 XftFontClose(display, font);
213 XCloseDisplay(display);
214
215 exit(exit_code);
216 }