Merge branch 'master' of https://github.com/dudik/herbe into master
[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(int sig)
72 {
73 XEvent event;
74 event.type = ButtonPress;
75 event.xbutton.button = (sig == SIGUSR2) ? (ACTION_BUTTON) : (DISMISS_BUTTON);
76 XSendEvent(display, window, 0, 0, &event);
77 XFlush(display);
78 }
79
80 int main(int argc, char *argv[])
81 {
82 if (argc == 1)
83 {
84 sem_unlink("/herbe");
85 die("Usage: %s body", argv[0]);
86 }
87
88 struct sigaction act_expire, act_ignore;
89 act_expire.sa_handler = expire;
90 act_expire.sa_flags = SA_RESTART;
91 sigemptyset(&act_expire.sa_mask);
92
93 act_ignore.sa_handler = SIG_IGN;
94 act_ignore.sa_flags = 0;
95 sigemptyset(&act_ignore.sa_mask);
96
97 sigaction(SIGALRM, &act_expire, 0);
98 sigaction(SIGTERM, &act_expire, 0);
99 sigaction(SIGINT, &act_expire, 0);
100
101 sigaction(SIGUSR1, &act_ignore, 0);
102 sigaction(SIGUSR2, &act_ignore, 0);
103
104 display = XOpenDisplay(0);
105
106 if (display == 0)
107 die("Cannot open display");
108
109 int screen = DefaultScreen(display);
110 Visual *visual = DefaultVisual(display, screen);
111 Colormap colormap = DefaultColormap(display, screen);
112
113 int screen_width = DisplayWidth(display, screen);
114 int screen_height = DisplayHeight(display, screen);
115
116 XftColor color;
117
118 XSetWindowAttributes attributes;
119 attributes.override_redirect = True;
120 XftColorAllocName(display, visual, colormap, background_color, &color);
121 attributes.background_pixel = color.pixel;
122 XftColorAllocName(display, visual, colormap, border_color, &color);
123 attributes.border_pixel = color.pixel;
124
125 XftFont *font = XftFontOpenName(display, screen, font_pattern);
126
127 int num_of_lines = 0;
128 int max_text_width = width - 2 * padding;
129 int words_size = 5;
130 char **words = malloc(words_size * sizeof(char *));
131 if (!words)
132 die("malloc failed");
133
134 for (int i = 1; i < argc; i++)
135 {
136 char *body = argv[i];
137
138 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))
139 {
140 if (words_size <= num_of_lines)
141 {
142 words = realloc(words, (words_size += 5) * sizeof(char *));
143 if (!words)
144 die("malloc failed");
145 }
146 words[num_of_lines] = malloc((eol + 1) * sizeof(char));
147 if (!words[num_of_lines])
148 die("malloc failed");
149 strncpy(words[num_of_lines], body, eol);
150 words[num_of_lines][eol] = '\0';
151 }
152 }
153
154 unsigned int x = pos_x;
155 unsigned int y = pos_y;
156 unsigned int text_height = font->ascent - font->descent;
157 unsigned int height = (num_of_lines - 1) * line_spacing + num_of_lines * text_height + 2 * padding;
158
159 if (corner == TOP_RIGHT || corner == BOTTOM_RIGHT)
160 x = screen_width - width - border_size * 2 - pos_x;
161
162 if (corner == BOTTOM_LEFT || corner == BOTTOM_RIGHT)
163 y = screen_height - height - border_size * 2 - pos_y;
164
165 window = XCreateWindow(display, RootWindow(display, screen), x, y, width, height, border_size, DefaultDepth(display, screen), CopyFromParent, visual,
166 CWOverrideRedirect | CWBackPixel | CWBorderPixel, &attributes);
167
168 XftDraw *draw = XftDrawCreate(display, window, visual, colormap);
169 XftColorAllocName(display, visual, colormap, font_color, &color);
170
171 XSelectInput(display, window, ExposureMask | ButtonPress);
172
173 XMapWindow(display, window);
174
175 sem_t *mutex = sem_open("/herbe", O_CREAT, 0644, 1);
176 sem_wait(mutex);
177
178 sigaction(SIGUSR1, &act_expire, 0);
179 sigaction(SIGUSR2, &act_expire, 0);
180
181 if (duration != 0)
182 alarm(duration);
183
184 while (1)
185 {
186 XEvent event;
187 XNextEvent(display, &event);
188
189 if (event.type == Expose)
190 {
191 XClearWindow(display, window);
192 for (int i = 0; i < num_of_lines; i++)
193 XftDrawStringUtf8(draw, &color, font, padding, line_spacing * i + text_height * (i + 1) + padding, (FcChar8 *)words[i], strlen(words[i]));
194 }
195 if (event.type == ButtonPress)
196 {
197 if (event.xbutton.button == DISMISS_BUTTON)
198 break;
199 else if (event.xbutton.button == ACTION_BUTTON)
200 {
201 exit_code = EXIT_ACTION;
202 break;
203 }
204 }
205 }
206
207 sem_post(mutex);
208 sem_close(mutex);
209
210 for (int i = 0; i < num_of_lines; i++)
211 free(words[i]);
212
213 free(words);
214 XftDrawDestroy(draw);
215 XftColorFree(display, visual, colormap, &color);
216 XftFontClose(display, font);
217 XCloseDisplay(display);
218
219 exit(exit_code);
220 }