]> git.sur5r.net Git - i3/i3/blobdiff - i3-nagbar/main.c
i3-nagbar: add missing newline when printing version information
[i3/i3] / i3-nagbar / main.c
index 26a282f6521167c3d562723b965e74adc6e9da61..70ad93b0b1c385f7f776e3f0d43ba60893c11131 100644 (file)
@@ -2,15 +2,15 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-nagbar is a utility which displays a nag message, for example in the case
  * when the user has an error in his configuration file.
  *
  */
-#include <ev.h>
 #include <stdio.h>
 #include <sys/types.h>
+#include <sys/stat.h>
 #include <sys/wait.h>
 #include <stdlib.h>
 #include <stdbool.h>
@@ -21,6 +21,8 @@
 #include <stdint.h>
 #include <getopt.h>
 #include <limits.h>
+#include <fcntl.h>
+#include <paths.h>
 
 #include <xcb/xcb.h>
 #include <xcb/xcb_aux.h>
@@ -29,6 +31,8 @@
 #include "libi3.h"
 #include "i3-nagbar.h"
 
+static char *argv0 = NULL;
+
 typedef struct {
     i3String *label;
     char *action;
@@ -54,6 +58,30 @@ static uint32_t color_text;              /* color of the text */
 
 xcb_window_t root;
 xcb_connection_t *conn;
+xcb_screen_t *root_screen;
+
+/*
+ * Having verboselog(), errorlog() and debuglog() is necessary when using libi3.
+ *
+ */
+void verboselog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stdout, fmt, args);
+    va_end(args);
+}
+
+void errorlog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+}
+
+void debuglog(char *fmt, ...) {
+}
 
 /*
  * Starts the given application by passing it through a shell. We use double fork
@@ -71,15 +99,8 @@ static void start_application(const char *command) {
         /* Child process */
         setsid();
         if (fork() == 0) {
-            /* Stores the path of the shell */
-            static const char *shell = NULL;
-
-            if (shell == NULL)
-                if ((shell = getenv("SHELL")) == NULL)
-                    shell = "/bin/sh";
-
             /* This is the child */
-            execl(shell, shell, "-c", command, (void*)NULL);
+            execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void*)NULL);
             /* not reached */
         }
         exit(0);
@@ -115,7 +136,47 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
     button_t *button = get_button_at(event->event_x, event->event_y);
     if (!button)
         return;
-    start_application(button->action);
+
+    /* We need to create a custom script containing our actual command
+     * since not every terminal emulator which is contained in
+     * i3-sensible-terminal supports -e with multiple arguments (and not
+     * all of them support -e with one quoted argument either).
+     *
+     * NB: The paths need to be unique, that is, don’t assume users close
+     * their nagbars at any point in time (and they still need to work).
+     * */
+    char *script_path = get_process_filename("nagbar-cmd");
+
+    int fd = open(script_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+    if (fd == -1) {
+        warn("Could not create temporary script to store the nagbar command");
+        return;
+    }
+    FILE *script = fdopen(fd, "w");
+    if (script == NULL) {
+        warn("Could not fdopen() temporary script to store the nagbar command");
+        return;
+    }
+    fprintf(script, "#!/bin/sh\nrm %s\n%s", script_path, button->action);
+    /* Also closes fd */
+    fclose(script);
+
+    char *link_path;
+    char *exe_path = get_exe_path(argv0);
+    sasprintf(&link_path, "%s.nagbar_cmd", script_path);
+    symlink(exe_path, link_path);
+
+    char *terminal_cmd;
+    sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path);
+    printf("argv0 = %s\n", argv0);
+    printf("terminal_cmd = %s\n", terminal_cmd);
+
+    start_application(terminal_cmd);
+
+    free(link_path);
+    free(terminal_cmd);
+    free(script_path);
+    free(exe_path);
 
     /* TODO: unset flag, re-render */
 }
@@ -136,8 +197,12 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
             4 + 4, 4 + 4, rect.width - 4 - 4);
 
     /* render close button */
+    const char *close_button_label = "X";
     int line_width = 4;
-    int w = 20;
+    /* set width to the width of the label */
+    int w = predict_text_width(i3string_from_utf8(close_button_label));
+    /* account for left/right padding, which seems to be set to 8px (total) below */
+    w += 8;
     int y = rect.width;
     uint32_t values[3];
     values[0] = color_button_background;
@@ -159,7 +224,8 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
 
     values[0] = 1;
     set_font_colors(pixmap_gc, color_text, color_button_background);
-    draw_text_ascii("X", pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
+    /* the x term here seems to set left/right padding */
+    draw_text_ascii(close_button_label, pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
             4 + 4 - 1, rect.width - y + w + line_width - w / 2 + 4);
     y -= w;
 
@@ -168,8 +234,10 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
     /* render custom buttons */
     line_width = 1;
     for (int c = 0; c < buttoncnt; c++) {
-        /* TODO: make w = text extents of the label */
-        w = 100;
+        /* set w to the width of the label */
+        w = predict_text_width(buttons[c].label);
+        /* account for left/right padding, which seems to be set to 12px (total) below */
+        w += 12;
         y -= 30;
         xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ color_button_background });
         close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 };
@@ -190,6 +258,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
         values[0] = color_text;
         values[1] = color_button_background;
         set_font_colors(pixmap_gc, color_text, color_button_background);
+        /* the x term seems to set left/right padding */
         draw_text(buttons[c].label, pixmap, pixmap_gc,
                 y - w - line_width + 6, 4 + 3, rect.width - y + w + line_width - 6);
 
@@ -216,6 +285,41 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
 }
 
 int main(int argc, char *argv[]) {
+    /* The following lines are a terribly horrible kludge. Because terminal
+     * emulators have different ways of interpreting the -e command line
+     * argument (some need -e "less /etc/fstab", others need -e less
+     * /etc/fstab), we need to write commands to a script and then just run
+     * that script. However, since on some machines, $XDG_RUNTIME_DIR and
+     * $TMPDIR are mounted with noexec, we cannot directly execute the script
+     * either.
+     *
+     * Initially, we tried to pass the command via the environment variable
+     * _I3_NAGBAR_CMD. But turns out that some terminal emulators such as
+     * xfce4-terminal run all windows from a single master process and only
+     * pass on the command (not the environment) to that master process.
+     *
+     * Therefore, we symlink i3-nagbar (which MUST reside on an executable
+     * filesystem) with a special name and run that symlink. When i3-nagbar
+     * recognizes it’s started as a binary ending in .nagbar_cmd, it strips off
+     * the .nagbar_cmd suffix and runs /bin/sh on argv[0]. That way, we can run
+     * a shell script on a noexec filesystem.
+     *
+     * From a security point of view, i3-nagbar is just an alias to /bin/sh in
+     * certain circumstances. This should not open any new security issues, I
+     * hope. */
+    char *cmd = NULL;
+    const size_t argv0_len = strlen(argv[0]);
+    if (argv0_len > strlen(".nagbar_cmd") &&
+        strcmp(argv[0] + argv0_len - strlen(".nagbar_cmd"), ".nagbar_cmd") == 0) {
+        unlink(argv[0]);
+        cmd = strdup(argv[0]);
+        *(cmd + argv0_len - strlen(".nagbar_cmd")) = '\0';
+        execl("/bin/sh", "/bin/sh", cmd, NULL);
+        err(EXIT_FAILURE, "execv(/bin/sh, /bin/sh, %s)", cmd);
+    }
+
+    argv0 = argv[0];
+
     char *pattern = sstrdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1");
     int o, option_index = 0;
     enum { TYPE_ERROR = 0, TYPE_WARNING = 1 } bar_type = TYPE_ERROR;
@@ -225,7 +329,7 @@ int main(int argc, char *argv[]) {
         {"font", required_argument, 0, 'f'},
         {"button", required_argument, 0, 'b'},
         {"help", no_argument, 0, 'h'},
-        {"message", no_argument, 0, 'm'},
+        {"message", required_argument, 0, 'm'},
         {"type", required_argument, 0, 't'},
         {0, 0, 0, 0}
     };
@@ -237,7 +341,7 @@ int main(int argc, char *argv[]) {
     while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
         switch (o) {
             case 'v':
-                printf("i3-nagbar " I3_VERSION);
+                printf("i3-nagbar " I3_VERSION "\n");
                 return 0;
             case 'f':
                 FREE(pattern);
@@ -280,7 +384,7 @@ int main(int argc, char *argv[]) {
     #include "atoms.xmacro"
     #undef xmacro
 
-    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
+    root_screen = xcb_aux_get_screen(conn, screens);
     root = root_screen->root;
 
     if (bar_type == TYPE_ERROR) {
@@ -363,7 +467,8 @@ int main(int argc, char *argv[]) {
         uint32_t top_end_x;
         uint32_t bottom_start_x;
         uint32_t bottom_end_x;
-    } __attribute__((__packed__)) strut_partial = {0,};
+    } __attribute__((__packed__)) strut_partial;
+    memset(&strut_partial, 0, sizeof(strut_partial));
 
     strut_partial.top = font.height + 6;
     strut_partial.top_start_x = 0;