Minor refactoring before first release
[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 0
15 #define EXIT_FAIL 1
16 #define EXIT_DISMISS 2
17
18 Display *display;
19 Window window;
20 int exit_code = EXIT_DISMISS;
21
22 static void die(const char *format, ...)
23 {
24 va_list ap;
25 va_start(ap, format);
26 vfprintf(stderr, format, ap);
27 fprintf(stderr, "\n");
28 va_end(ap);
29 exit(EXIT_FAIL);
30 }
31
32 int get_max_len(char *string, XftFont *font, int max_text_width)
33 {
34 int eol = strlen(string);
35 XGlyphInfo info;
36 XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info);
37
38 if (info.width > max_text_width)
39 {
40 eol = max_text_width / font->max_advance_width;
41 info.width = 0;
42
43 while (info.width < max_text_width)
44 {
45 eol++;
46 XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info);
47 }
48
49 eol--;
50 }
51
52 for (int i = 0; i < eol; i++)
53 if (string[i] == '\n')
54 {
55 string[i] = ' ';
56 return ++i;
57 }
58
59 if (info.width <= max_text_width)
60 return eol;
61
62 int temp = eol;
63
64 while (string[eol] != ' ' && eol)
65 --eol;
66
67 if (eol == 0)
68 return temp;
69 else
70 return ++eol;
71 }
72
73 void expire(int sig)
74 {
75 XEvent event;
76 event.type = ButtonPress;
77 event.xbutton.button = (sig == SIGUSR2) ? (ACTION_BUTTON) : (DISMISS_BUTTON);
78 XSendEvent(display, window, 0, 0, &event);
79 XFlush(display);
80 }
81
82 int main(int argc, char *argv[])
83 {
84 if (argc == 1)
85 {
86 sem_unlink("/herbe");
87 die("Usage: %s body", argv[0]);
88 }
89
90 struct sigaction act_expire, act_ignore;
91
92 act_expire.sa_handler = expire;
93 act_expire.sa_flags = SA_RESTART;
94 sigemptyset(&act_expire.sa_mask);
95
96 act_ignore.sa_handler = SIG_IGN;
97 act_ignore.sa_flags = 0;
98 sigemptyset(&act_ignore.sa_mask);
99
100 sigaction(SIGALRM, &act_expire, 0);
101 sigaction(SIGTERM, &act_expire, 0);
102 sigaction(SIGINT, &act_expire, 0);
103
104 sigaction(SIGUSR1, &act_ignore, 0);
105 sigaction(SIGUSR2, &act_ignore, 0);
106
107 if (!(display = XOpenDisplay(0)))
108 die("Cannot open display");
109
110 int screen = DefaultScreen(display);
111 Visual *visual = DefaultVisual(display, screen);
112 Colormap colormap = DefaultColormap(display, screen);
113
114 int screen_width = DisplayWidth(display, screen);
115 int screen_height = DisplayHeight(display, screen);
116
117 XSetWindowAttributes attributes;
118 attributes.override_redirect = True;
119 XftColor color;
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 int num_of_lines = 0;
126 int max_text_width = width - 2 * padding;
127 int lines_size = 5;
128 char **lines = malloc(lines_size * sizeof(char *));
129 if (!lines)
130 die("malloc failed");
131
132 XftFont *font = XftFontOpenName(display, screen, font_pattern);
133
134 for (int i = 1; i < argc; i++)
135 {
136 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))
137 {
138 if (lines_size <= num_of_lines)
139 {
140 lines = realloc(lines, (lines_size += 5) * sizeof(char *));
141 if (!lines)
142 die("realloc failed");
143 }
144
145 lines[num_of_lines] = malloc((eol + 1) * sizeof(char));
146 if (!lines[num_of_lines])
147 die("malloc failed");
148
149 strncpy(lines[num_of_lines], argv[i], eol);
150 lines[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),
166 CopyFromParent, visual, 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 XMapWindow(display, window);
173
174 sem_t *mutex = sem_open("/herbe", O_CREAT, 0644, 1);
175 sem_wait(mutex);
176
177 sigaction(SIGUSR1, &act_expire, 0);
178 sigaction(SIGUSR2, &act_expire, 0);
179
180 if (duration != 0)
181 alarm(duration);
182
183 for (;;)
184 {
185 XEvent event;
186 XNextEvent(display, &event);
187
188 if (event.type == Expose)
189 {
190 XClearWindow(display, window);
191 for (int i = 0; i < num_of_lines; i++)
192 XftDrawStringUtf8(draw, &color, font, padding, line_spacing * i + text_height * (i + 1) + padding,
193 (FcChar8 *)lines[i], strlen(lines[i]));
194 }
195 else 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(lines[i]);
212
213 free(lines);
214 XftDrawDestroy(draw);
215 XftColorFree(display, visual, colormap, &color);
216 XftFontClose(display, font);
217 XCloseDisplay(display);
218
219 return exit_code;
220 }