]> git.sur5r.net Git - i3/i3/blob - i3-nagbar/main.c
Merge branch 'tree' into next
[i3/i3] / i3-nagbar / main.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  *
6  * © 2009-2011 Michael Stapelberg and contributors
7  *
8  * See file LICENSE for license information.
9  *
10  * i3-nagbar is a utility which displays a nag message.
11  *
12  */
13 #include <ev.h>
14 #include <stdio.h>
15 #include <sys/types.h>
16 #include <sys/wait.h>
17 #include <stdlib.h>
18 #include <stdbool.h>
19 #include <unistd.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <err.h>
23 #include <stdint.h>
24 #include <getopt.h>
25 #include <limits.h>
26
27 #include <xcb/xcb.h>
28 #include <xcb/xcb_aux.h>
29 #include <xcb/xcb_event.h>
30
31 #include "i3-nagbar.h"
32
33 typedef struct {
34     char *label;
35     char *action;
36     int16_t x;
37     uint16_t width;
38 } button_t;
39
40 static xcb_window_t win;
41 static xcb_pixmap_t pixmap;
42 static xcb_gcontext_t pixmap_gc;
43 static xcb_rectangle_t rect = { 0, 0, 600, 20 };
44 static int font_height;
45 static char *prompt = "Please do not run this program.";
46 static button_t *buttons;
47 static int buttoncnt;
48 xcb_window_t root;
49
50 /*
51  * Starts the given application by passing it through a shell. We use double fork
52  * to avoid zombie processes. As the started application’s parent exits (immediately),
53  * the application is reparented to init (process-id 1), which correctly handles
54  * childs, so we don’t have to do it :-).
55  *
56  * The shell is determined by looking for the SHELL environment variable. If it
57  * does not exist, /bin/sh is used.
58  *
59  */
60 static void start_application(const char *command) {
61     printf("executing: %s\n", command);
62     if (fork() == 0) {
63         /* Child process */
64         setsid();
65         if (fork() == 0) {
66             /* Stores the path of the shell */
67             static const char *shell = NULL;
68
69             if (shell == NULL)
70                 if ((shell = getenv("SHELL")) == NULL)
71                     shell = "/bin/sh";
72
73             /* This is the child */
74             execl(shell, shell, "-c", command, (void*)NULL);
75             /* not reached */
76         }
77         exit(0);
78     }
79     wait(0);
80 }
81
82 static button_t *get_button_at(int16_t x, int16_t y) {
83     for (int c = 0; c < buttoncnt; c++)
84         if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width))
85             return &buttons[c];
86
87     return NULL;
88 }
89
90 static void handle_button_press(xcb_connection_t *conn, xcb_button_press_event_t *event) {
91     printf("button pressed on x = %d, y = %d\n",
92             event->event_x, event->event_y);
93     /* TODO: set a flag for the button, re-render */
94 }
95
96 /*
97  * Called when the user releases the mouse button. Checks whether the
98  * coordinates are over a button and executes the appropriate action.
99  *
100  */
101 static void handle_button_release(xcb_connection_t *conn, xcb_button_release_event_t *event) {
102     printf("button released on x = %d, y = %d\n",
103             event->event_x, event->event_y);
104     /* If the user hits the close button, we exit(0) */
105     if (event->event_x >= (rect.width - 32))
106         exit(0);
107     button_t *button = get_button_at(event->event_x, event->event_y);
108     if (!button)
109         return;
110     start_application(button->action);
111
112     /* TODO: unset flag, re-render */
113 }
114
115 /*
116  * Handles expose events (redraws of the window) and rendering in general. Will
117  * be called from the code with event == NULL or from X with event != NULL.
118  *
119  */
120 static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
121     printf("expose!\n");
122
123     /* re-draw the background */
124     xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#900000"));
125     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
126
127     /* restore font color */
128     uint32_t values[3];
129     values[0] = get_colorpixel(conn, "#FFFFFF");
130     values[1] = get_colorpixel(conn, "#900000");
131     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
132     xcb_image_text_8(conn, strlen(prompt), pixmap, pixmap_gc, 4 + 4/* X */,
133                       font_height + 2 + 4 /* Y = baseline of font */, prompt);
134
135     /* render close button */
136     int line_width = 4;
137     int w = 20;
138     int y = rect.width;
139     values[0] = get_colorpixel(conn, "#680a0a");
140     values[1] = line_width;
141     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
142
143     xcb_rectangle_t close = { y - w - (2 * line_width), 0, w + (2 * line_width), rect.height };
144     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
145
146     xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424"));
147     xcb_point_t points[] = {
148         { y - w - (2 * line_width), line_width / 2 },
149         { y - (line_width / 2), line_width / 2 },
150         { y - (line_width / 2), (rect.height - (line_width / 2)) - 2 },
151         { y - w - (2 * line_width), (rect.height - (line_width / 2)) - 2 },
152         { y - w - (2 * line_width), line_width / 2 }
153     };
154     xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points);
155
156     values[0] = get_colorpixel(conn, "#ffffff");
157     values[1] = get_colorpixel(conn, "#680a0a");
158     values[2] = 1;
159     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH, values);
160     xcb_image_text_8(conn, strlen("x"), pixmap, pixmap_gc, y - w - line_width + (w / 2) - 4/* X */,
161                       font_height + 2 + 4 - 1/* Y = baseline of font */, "X");
162     y -= w;
163
164     y -= 20;
165
166     /* render custom buttons */
167     line_width = 1;
168     for (int c = 0; c < buttoncnt; c++) {
169         /* TODO: make w = text extents of the label */
170         w = 90;
171         y -= 30;
172         xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#680a0a"));
173         close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 };
174         xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
175
176         xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424"));
177         buttons[c].x = y - w - (2 * line_width);
178         buttons[c].width = w;
179         xcb_point_t points2[] = {
180             { y - w - (2 * line_width), (line_width / 2) + 2 },
181             { y - (line_width / 2), (line_width / 2) + 2 },
182             { y - (line_width / 2), (rect.height - 4 - (line_width / 2)) },
183             { y - w - (2 * line_width), (rect.height - 4 - (line_width / 2)) },
184             { y - w - (2 * line_width), (line_width / 2) + 2 }
185         };
186         xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2);
187
188         values[0] = get_colorpixel(conn, "#ffffff");
189         values[1] = get_colorpixel(conn, "#680a0a");
190         xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
191         xcb_image_text_8(conn, strlen(buttons[c].label), pixmap, pixmap_gc, y - w - line_width + 6/* X */,
192                           font_height + 2 + 3/* Y = baseline of font */, buttons[c].label);
193
194         y -= w;
195     }
196
197     /* border line at the bottom */
198     line_width = 2;
199     values[0] = get_colorpixel(conn, "#470909");
200     values[1] = line_width;
201     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
202     xcb_point_t bottom[] = {
203         { 0, rect.height - 0 },
204         { rect.width, rect.height - 0 }
205     };
206     xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 2, bottom);
207
208
209     /* Copy the contents of the pixmap to the real window */
210     xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, rect.width, rect.height);
211     xcb_flush(conn);
212
213     return 1;
214 }
215
216 int main(int argc, char *argv[]) {
217     char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
218     int o, option_index = 0;
219
220     static struct option long_options[] = {
221         {"version", no_argument, 0, 'v'},
222         {"font", required_argument, 0, 'f'},
223         {"button", required_argument, 0, 'b'},
224         {"help", no_argument, 0, 'h'},
225         {"message", no_argument, 0, 'm'},
226         {0, 0, 0, 0}
227     };
228
229     char *options_string = "b:f:m:vh";
230
231     while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
232         switch (o) {
233             case 'v':
234                 printf("i3-nagbar " I3_VERSION);
235                 return 0;
236             case 'f':
237                 FREE(pattern);
238                 pattern = strdup(optarg);
239                 break;
240             case 'm':
241                 prompt = strdup(optarg);
242                 break;
243             case 'h':
244                 printf("i3-nagbar " I3_VERSION "\n");
245                 printf("i3-nagbar [-m <message>] [-b <button> <action>] [-f <font>] [-v]\n");
246                 return 0;
247             case 'b':
248                 buttons = realloc(buttons, sizeof(button_t) * (buttoncnt + 1));
249                 buttons[buttoncnt].label = optarg;
250                 buttons[buttoncnt].action = argv[optind];
251                 printf("button with label *%s* and action *%s*\n",
252                         buttons[buttoncnt].label,
253                         buttons[buttoncnt].action);
254                 buttoncnt++;
255                 printf("now %d buttons\n", buttoncnt);
256                 if (optind < argc)
257                     optind++;
258                 break;
259         }
260     }
261
262     int screens;
263     xcb_connection_t *conn;
264     if ((conn = xcb_connect(NULL, &screens)) == NULL ||
265         xcb_connection_has_error(conn))
266         die("Cannot open display\n");
267
268     /* Place requests for the atoms we need as soon as possible */
269     #define xmacro(atom) \
270         xcb_intern_atom_cookie_t atom ## _cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
271     #include "atoms.xmacro"
272     #undef xmacro
273
274     xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
275     root = root_screen->root;
276
277     uint32_t font_id = get_font_id(conn, pattern, &font_height);
278
279     /* Open an input window */
280     win = open_input_window(conn, 500, font_height + 8 + 8 /* 8px padding */);
281
282     /* Setup NetWM atoms */
283     #define xmacro(name) \
284         do { \
285             xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name ## _cookie, NULL); \
286             if (!reply) \
287                 die("Could not get atom " # name "\n"); \
288             \
289             A_ ## name = reply->atom; \
290             free(reply); \
291         } while (0);
292     #include "atoms.xmacro"
293     #undef xmacro
294
295     /* Set dock mode */
296     xcb_change_property(conn,
297         XCB_PROP_MODE_REPLACE,
298         win,
299         A__NET_WM_WINDOW_TYPE,
300         A_ATOM,
301         32,
302         1,
303         (unsigned char*) &A__NET_WM_WINDOW_TYPE_DOCK);
304
305     /* Reserve some space at the top of the screen */
306     struct {
307         uint32_t left;
308         uint32_t right;
309         uint32_t top;
310         uint32_t bottom;
311         uint32_t left_start_y;
312         uint32_t left_end_y;
313         uint32_t right_start_y;
314         uint32_t right_end_y;
315         uint32_t top_start_x;
316         uint32_t top_end_x;
317         uint32_t bottom_start_x;
318         uint32_t bottom_end_x;
319     } __attribute__((__packed__)) strut_partial = {0,};
320
321     strut_partial.top = font_height + 6;
322     strut_partial.top_start_x = 0;
323     strut_partial.top_end_x = 800;
324
325     xcb_change_property(conn,
326         XCB_PROP_MODE_REPLACE,
327         win,
328         A__NET_WM_STRUT_PARTIAL,
329         A_CARDINAL,
330         32,
331         12,
332         &strut_partial);
333
334     /* Create pixmap */
335     pixmap = xcb_generate_id(conn);
336     pixmap_gc = xcb_generate_id(conn);
337     xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
338     xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
339
340     /* Create graphics context */
341     xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
342
343     /* Grab the keyboard to get all input */
344     xcb_flush(conn);
345
346     xcb_generic_event_t *event;
347     while ((event = xcb_wait_for_event(conn)) != NULL) {
348         if (event->response_type == 0) {
349             fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
350             continue;
351         }
352
353         /* Strip off the highest bit (set if the event is generated) */
354         int type = (event->response_type & 0x7F);
355
356         switch (type) {
357             case XCB_EXPOSE:
358                 handle_expose(conn, (xcb_expose_event_t*)event);
359                 break;
360
361             case XCB_BUTTON_PRESS:
362                 handle_button_press(conn, (xcb_button_press_event_t*)event);
363                 break;
364
365             case XCB_BUTTON_RELEASE:
366                 handle_button_release(conn, (xcb_button_release_event_t*)event);
367                 break;
368
369             case XCB_CONFIGURE_NOTIFY: {
370                 xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t*)event;
371                 rect = (xcb_rectangle_t){
372                     configure_notify->x,
373                     configure_notify->y,
374                     configure_notify->width,
375                     configure_notify->height
376                 };
377
378                 /* Recreate the pixmap / gc */
379                 xcb_free_pixmap(conn, pixmap);
380                 xcb_free_gc(conn, pixmap_gc);
381
382                 xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, rect.width, rect.height);
383                 xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
384
385                 /* Create graphics context */
386                 xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
387                 break;
388             }
389         }
390
391         free(event);
392     }
393
394     return 0;
395 }