]> git.sur5r.net Git - i3/i3/commitdiff
add i3-nagbar. tells you about config file errors (for example)
authorMichael Stapelberg <michael@stapelberg.de>
Sun, 10 Jul 2011 12:33:19 +0000 (14:33 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Sun, 10 Jul 2011 12:33:19 +0000 (14:33 +0200)
17 files changed:
common.mk
i3-nagbar/Makefile [new file with mode: 0644]
i3-nagbar/atoms.xmacro [new file with mode: 0644]
i3-nagbar/i3-nagbar.h [new file with mode: 0644]
i3-nagbar/main.c [new file with mode: 0644]
i3-nagbar/xcb.c [new file with mode: 0644]
include/config.h
include/i3.h
include/log.h
include/util.h
man/Makefile
man/i3-nagbar.man [new file with mode: 0644]
src/cfgparse.y
src/cmdparse.y
src/log.c
src/main.c
src/util.c

index fed147b88614a6186ad030104fadf5e4bb08c454..41946d41246ae06351f6262d34acf4ac0452decc 100644 (file)
--- a/common.mk
+++ b/common.mk
@@ -8,6 +8,7 @@ SYSCONFDIR=/etc
 else
 SYSCONFDIR=$(PREFIX)/etc
 endif
+TERM_EMU=xterm
 # The escaping is absurd, but we need to escape for shell, sed, make, define
 GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f .git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' .git/HEAD || echo 'unknown'))"
 VERSION:=$(shell git describe --tags --abbrev=0)
@@ -46,6 +47,7 @@ CFLAGS += $(call cflags_for_lib, yajl)
 CFLAGS += $(call cflags_for_lib, libev)
 CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
 CFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
+CFLAGS += -DTERM_EMU=\"$(TERM_EMU)\"
 
 LDFLAGS += -lm
 LDFLAGS += $(call ldflags_for_lib, xcb-event, xcb-event)
diff --git a/i3-nagbar/Makefile b/i3-nagbar/Makefile
new file mode 100644 (file)
index 0000000..8d9283b
--- /dev/null
@@ -0,0 +1,28 @@
+# Default value so one can compile i3-nagbar standalone
+TOPDIR=..
+
+include $(TOPDIR)/common.mk
+
+# Depend on the object files of all source-files in src/*.c and on all header files
+FILES=$(patsubst %.c,%.o,$(wildcard *.c))
+HEADERS=$(wildcard *.h)
+
+# Depend on the specific file (.c for each .o) and on all headers
+%.o: %.c ${HEADERS}
+       echo "CC $<"
+       $(CC) $(CFLAGS) -c -o $@ $<
+
+all: ${FILES}
+       echo "LINK i3-nagbar"
+       $(CC) -o i3-nagbar ${FILES} $(LDFLAGS)
+
+install: all
+       echo "INSTALL"
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+       $(INSTALL) -m 0755 i3-nagbar $(DESTDIR)$(PREFIX)/bin/
+
+clean:
+       rm -f *.o
+
+distclean: clean
+       rm -f i3-nagbar
diff --git a/i3-nagbar/atoms.xmacro b/i3-nagbar/atoms.xmacro
new file mode 100644 (file)
index 0000000..333ba2d
--- /dev/null
@@ -0,0 +1,6 @@
+xmacro(_NET_WM_WINDOW_TYPE)
+xmacro(_NET_WM_WINDOW_TYPE_DOCK)
+xmacro(_NET_WM_STRUT_PARTIAL)
+xmacro(I3_SOCKET_PATH)
+xmacro(ATOM)
+xmacro(CARDINAL)
diff --git a/i3-nagbar/i3-nagbar.h b/i3-nagbar/i3-nagbar.h
new file mode 100644 (file)
index 0000000..2fbe3cb
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef _I3_NAGBAR
+#define _I3_NAGBAR
+
+#include <err.h>
+
+#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
+#define FREE(pointer) do { \
+        if (pointer != NULL) { \
+                free(pointer); \
+                pointer = NULL; \
+        } \
+} \
+while (0)
+
+#define xmacro(atom) xcb_atom_t A_ ## atom;
+#include "atoms.xmacro"
+#undef xmacro
+
+extern xcb_window_t root;
+
+uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
+xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
+int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
+void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
+
+#endif
diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c
new file mode 100644 (file)
index 0000000..73d6f63
--- /dev/null
@@ -0,0 +1,392 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009-2011 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ * i3-nagbar is a utility which displays a nag message.
+ *
+ */
+#include <ev.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <limits.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+#include <xcb/xcb_event.h>
+
+#include "i3-nagbar.h"
+
+typedef struct {
+    char *label;
+    char *action;
+    int16_t x;
+    uint16_t width;
+} button_t;
+
+static xcb_window_t win;
+static xcb_pixmap_t pixmap;
+static xcb_gcontext_t pixmap_gc;
+static xcb_rectangle_t rect = { 0, 0, 600, 20 };
+static char *glyphs_ucs[512];
+static int input_position;
+static int font_height;
+static char *prompt = "You have an error in your i3 config file!";
+static button_t *buttons;
+static int buttoncnt;
+xcb_window_t root;
+
+/*
+ * Starts the given application by passing it through a shell. We use double fork
+ * to avoid zombie processes. As the started application’s parent exits (immediately),
+ * the application is reparented to init (process-id 1), which correctly handles
+ * childs, so we don’t have to do it :-).
+ *
+ * The shell is determined by looking for the SHELL environment variable. If it
+ * does not exist, /bin/sh is used.
+ *
+ */
+static void start_application(const char *command) {
+    printf("executing: %s\n", command);
+    if (fork() == 0) {
+        /* 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);
+            /* not reached */
+        }
+        exit(0);
+    }
+    wait(0);
+}
+
+static button_t *get_button_at(int16_t x, int16_t y) {
+    for (int c = 0; c < buttoncnt; c++)
+        if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width))
+            return &buttons[c];
+
+    return NULL;
+}
+
+static void handle_button_press(xcb_connection_t *conn, xcb_button_press_event_t *event) {
+    printf("button pressed on x = %d, y = %d\n",
+            event->event_x, event->event_y);
+    /* TODO: set a flag for the button, re-render */
+}
+
+/*
+ * Called when the user releases the mouse button. Checks whether the
+ * coordinates are over a button and executes the appropriate action.
+ *
+ */
+static void handle_button_release(xcb_connection_t *conn, xcb_button_release_event_t *event) {
+    printf("button released on x = %d, y = %d\n",
+            event->event_x, event->event_y);
+    /* If the user hits the close button, we exit(0) */
+    if (event->event_x >= (rect.width - 32))
+        exit(0);
+    button_t *button = get_button_at(event->event_x, event->event_y);
+    if (!button)
+        return;
+    start_application(button->action);
+
+    /* TODO: unset flag, re-render */
+}
+
+/*
+ * Handles expose events (redraws of the window) and rendering in general. Will
+ * be called from the code with event == NULL or from X with event != NULL.
+ *
+ */
+static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
+    printf("expose!\n");
+
+    /* re-draw the background */
+    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#900000"));
+    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
+
+    /* restore font color */
+    uint32_t values[3];
+    values[0] = get_colorpixel(conn, "#FFFFFF");
+    values[1] = get_colorpixel(conn, "#900000");
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
+    xcb_image_text_8(conn, strlen(prompt), pixmap, pixmap_gc, 4 + 4/* X */,
+                      font_height + 2 + 4 /* Y = baseline of font */, prompt);
+
+    /* render close button */
+    int line_width = 4;
+    int w = 20;
+    int y = rect.width;
+    values[0] = get_colorpixel(conn, "#680a0a");
+    values[1] = line_width;
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
+
+    xcb_rectangle_t close = { y - w - (2 * line_width), 0, w + (2 * line_width), rect.height };
+    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
+
+    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424"));
+    xcb_point_t points[] = {
+        { y - w - (2 * line_width), line_width / 2 },
+        { y - (line_width / 2), line_width / 2 },
+        { y - (line_width / 2), (rect.height - (line_width / 2)) - 2 },
+        { y - w - (2 * line_width), (rect.height - (line_width / 2)) - 2 },
+        { y - w - (2 * line_width), line_width / 2 }
+    };
+    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points);
+
+    values[0] = get_colorpixel(conn, "#ffffff");
+    values[1] = get_colorpixel(conn, "#680a0a");
+    values[2] = 1;
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH, values);
+    xcb_image_text_8(conn, strlen("x"), pixmap, pixmap_gc, y - w - line_width + (w / 2) - 4/* X */,
+                      font_height + 2 + 4 - 1/* Y = baseline of font */, "X");
+    y -= w;
+
+    y -= 20;
+
+    /* render custom buttons */
+    line_width = 1;
+    for (int c = 0; c < buttoncnt; c++) {
+        /* TODO: make w = text extents of the label */
+        w = 90;
+        y -= 30;
+        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#680a0a"));
+        close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 };
+        xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
+
+        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424"));
+        buttons[c].x = y - w - (2 * line_width);
+        buttons[c].width = w;
+        xcb_point_t points2[] = {
+            { y - w - (2 * line_width), (line_width / 2) + 2 },
+            { y - (line_width / 2), (line_width / 2) + 2 },
+            { y - (line_width / 2), (rect.height - 4 - (line_width / 2)) },
+            { y - w - (2 * line_width), (rect.height - 4 - (line_width / 2)) },
+            { y - w - (2 * line_width), (line_width / 2) + 2 }
+        };
+        xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2);
+
+        values[0] = get_colorpixel(conn, "#ffffff");
+        values[1] = get_colorpixel(conn, "#680a0a");
+        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
+        xcb_image_text_8(conn, strlen(buttons[c].label), pixmap, pixmap_gc, y - w - line_width + 6/* X */,
+                          font_height + 2 + 3/* Y = baseline of font */, buttons[c].label);
+
+        y -= w;
+    }
+
+    /* border line at the bottom */
+    line_width = 2;
+    values[0] = get_colorpixel(conn, "#470909");
+    values[1] = line_width;
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
+    xcb_point_t bottom[] = {
+        { 0, rect.height - 0 },
+        { rect.width, rect.height - 0 }
+    };
+    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 2, bottom);
+
+
+    /* Copy the contents of the pixmap to the real window */
+    xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, rect.width, rect.height);
+    xcb_flush(conn);
+
+    return 1;
+}
+
+int main(int argc, char *argv[]) {
+    char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+    int o, option_index = 0;
+
+    static struct option long_options[] = {
+        {"version", no_argument, 0, 'v'},
+        {"font", required_argument, 0, 'f'},
+        {"button", required_argument, 0, 'b'},
+        {"help", no_argument, 0, 'h'},
+        {0, 0, 0, 0}
+    };
+
+    char *options_string = "b:f:vh";
+
+    while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
+        switch (o) {
+            case 'v':
+                printf("i3-nagbar " I3_VERSION);
+                return 0;
+            case 'f':
+                FREE(pattern);
+                pattern = strdup(optarg);
+                break;
+            case 'h':
+                printf("i3-nagbar " I3_VERSION);
+                printf("i3-nagbar [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]\n");
+                return 0;
+            case 'b':
+                buttons = realloc(buttons, sizeof(button_t) * (buttoncnt + 1));
+                buttons[buttoncnt].label = optarg;
+                buttons[buttoncnt].action = argv[optind];
+                printf("button with label *%s* and action *%s*\n",
+                        buttons[buttoncnt].label,
+                        buttons[buttoncnt].action);
+                buttoncnt++;
+                printf("now %d buttons\n", buttoncnt);
+                if (optind < argc)
+                    optind++;
+                break;
+        }
+    }
+
+    int screens;
+    xcb_connection_t *conn = xcb_connect(NULL, &screens);
+    if (xcb_connection_has_error(conn))
+        die("Cannot open display\n");
+
+    /* Place requests for the atoms we need as soon as possible */
+    #define xmacro(atom) \
+        xcb_intern_atom_cookie_t atom ## _cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
+    #include "atoms.xmacro"
+    #undef xmacro
+
+    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
+    root = root_screen->root;
+
+    uint32_t font_id = get_font_id(conn, pattern, &font_height);
+
+    /* Open an input window */
+    win = open_input_window(conn, 500, font_height + 8 + 8 /* 8px padding */);
+
+    /* Setup NetWM atoms */
+    #define xmacro(name) \
+        do { \
+            xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name ## _cookie, NULL); \
+            if (!reply) \
+                die("Could not get atom " # name "\n"); \
+            \
+            A_ ## name = reply->atom; \
+            free(reply); \
+        } while (0);
+    #include "atoms.xmacro"
+    #undef xmacro
+
+    /* Set dock mode */
+    xcb_void_cookie_t dock_cookie = xcb_change_property(conn,
+        XCB_PROP_MODE_REPLACE,
+        win,
+        A__NET_WM_WINDOW_TYPE,
+        A_ATOM,
+        32,
+        1,
+        (unsigned char*) &A__NET_WM_WINDOW_TYPE_DOCK);
+
+    /* Reserve some space at the top of the screen */
+    struct {
+        uint32_t left;
+        uint32_t right;
+        uint32_t top;
+        uint32_t bottom;
+        uint32_t left_start_y;
+        uint32_t left_end_y;
+        uint32_t right_start_y;
+        uint32_t right_end_y;
+        uint32_t top_start_x;
+        uint32_t top_end_x;
+        uint32_t bottom_start_x;
+        uint32_t bottom_end_x;
+    } __attribute__((__packed__)) strut_partial = {0,};
+
+    strut_partial.top = font_height + 6;
+    strut_partial.top_start_x = 0;
+    strut_partial.top_end_x = 800;
+
+    xcb_void_cookie_t strut_cookie = xcb_change_property(conn,
+        XCB_PROP_MODE_REPLACE,
+        win,
+        A__NET_WM_STRUT_PARTIAL,
+        A_CARDINAL,
+        32,
+        12,
+        &strut_partial);
+
+    /* Create pixmap */
+    pixmap = xcb_generate_id(conn);
+    pixmap_gc = xcb_generate_id(conn);
+    xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
+    xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
+
+    /* Create graphics context */
+    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+
+    /* Grab the keyboard to get all input */
+    xcb_flush(conn);
+
+    xcb_generic_event_t *event;
+    while ((event = xcb_wait_for_event(conn)) != NULL) {
+        if (event->response_type == 0) {
+            fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
+            continue;
+        }
+
+        /* Strip off the highest bit (set if the event is generated) */
+        int type = (event->response_type & 0x7F);
+
+        switch (type) {
+            case XCB_EXPOSE:
+                handle_expose(conn, (xcb_expose_event_t*)event);
+                break;
+
+            case XCB_BUTTON_PRESS:
+                handle_button_press(conn, (xcb_button_press_event_t*)event);
+                break;
+
+            case XCB_BUTTON_RELEASE:
+                handle_button_release(conn, (xcb_button_release_event_t*)event);
+                break;
+
+            case XCB_CONFIGURE_NOTIFY: {
+                xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t*)event;
+                rect = (xcb_rectangle_t){
+                    configure_notify->x,
+                    configure_notify->y,
+                    configure_notify->width,
+                    configure_notify->height
+                };
+
+                /* Recreate the pixmap / gc */
+                xcb_free_pixmap(conn, pixmap);
+                xcb_free_gc(conn, pixmap_gc);
+
+                xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, rect.width, rect.height);
+                xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
+
+                /* Create graphics context */
+                xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+                break;
+            }
+        }
+
+        free(event);
+    }
+
+    return 0;
+}
diff --git a/i3-nagbar/xcb.c b/i3-nagbar/xcb.c
new file mode 100644 (file)
index 0000000..ed1bfd8
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_keysyms.h>
+
+#include <X11/keysym.h>
+
+#include "i3-nagbar.h"
+
+/*
+ * Convenience-wrapper around xcb_change_gc which saves us declaring a variable
+ *
+ */
+void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
+        xcb_change_gc(conn, gc, mask, &value);
+}
+
+/*
+ * Returns the colorpixel to use for the given hex color (think of HTML).
+ *
+ * The hex_color has to start with #, for example #FF00FF.
+ *
+ * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
+ * This has to be done by the caller.
+ *
+ */
+uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
+        char strgroups[3][3] = {{hex[1], hex[2], '\0'},
+                                {hex[3], hex[4], '\0'},
+                                {hex[5], hex[6], '\0'}};
+        uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
+                             (strtol(strgroups[1], NULL, 16)),
+                             (strtol(strgroups[2], NULL, 16))};
+
+        return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
+}
+
+/*
+ * Opens the window we use for input/output and maps it
+ *
+ */
+xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
+        xcb_window_t win = xcb_generate_id(conn);
+        //xcb_cursor_t cursor_id = xcb_generate_id(conn);
+
+#if 0
+        /* Use the default cursor (left pointer) */
+        if (cursor > -1) {
+                i3Font *cursor_font = load_font(conn, "cursor");
+                xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
+                                XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
+                                0, 0, 0, 65535, 65535, 65535);
+        }
+#endif
+
+        uint32_t mask = 0;
+        uint32_t values[3];
+
+        mask |= XCB_CW_BACK_PIXEL;
+        values[0] = 0;
+
+       mask |= XCB_CW_EVENT_MASK;
+       values[1] = XCB_EVENT_MASK_EXPOSURE |
+                XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+                XCB_EVENT_MASK_BUTTON_PRESS |
+                XCB_EVENT_MASK_BUTTON_RELEASE;
+
+        xcb_create_window(conn,
+                          XCB_COPY_FROM_PARENT,
+                          win, /* the window id */
+                          root, /* parent == root */
+                          50, 50, width, height, /* dimensions */
+                          0, /* border = 0, we draw our own */
+                          XCB_WINDOW_CLASS_INPUT_OUTPUT,
+                          XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+                          mask,
+                          values);
+
+#if 0
+        if (cursor > -1)
+                xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
+#endif
+
+        /* Map the window (= make it visible) */
+        xcb_map_window(conn, win);
+
+       return win;
+}
+
+/*
+ * Returns the ID of the font matching the given pattern and stores the height
+ * of the font (in pixels) in *font_height. die()s if no font matches.
+ *
+ */
+int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
+        xcb_void_cookie_t font_cookie;
+        xcb_list_fonts_with_info_cookie_t info_cookie;
+
+        /* Send all our requests first */
+        int result;
+        result = xcb_generate_id(conn);
+        font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern);
+        info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
+
+        xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
+        if (error != NULL) {
+                fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code);
+                exit(1);
+        }
+
+        /* Get information (height/name) for this font */
+        xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
+        if (reply == NULL)
+                die("Could not load font \"%s\"\n", pattern);
+
+        *font_height = reply->font_ascent + reply->font_descent;
+
+        return result;
+}
index 9ba5e0f93cdad07e811ba62908cf08f243d941c6..1021a612e7a98917a211a61d037b688e255c6861 100644 (file)
@@ -32,6 +32,8 @@ extern SLIST_HEAD(modes_head, Mode) modes;
  *
  */
 struct context {
+        bool has_errors;
+
         int line_number;
         char *line_copy;
         const char *filename;
@@ -190,6 +192,17 @@ void switch_mode(const char *new_mode);
  */
 Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode);
 
+/**
+ * Kills the configerror i3-nagbar process, if any.
+ *
+ * Called when reloading/restarting.
+ *
+ * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
+ * ev is assumed to handle it (reloading).
+ *
+ */
+void kill_configerror_nagbar(bool wait_for_it);
+
 /* prototype for src/cfgparse.y */
 void parse_file(const char *f);
 
index 7eb48ecc1ce8a6f994b7e62d30811d18d10a00d4..73b61178dfd633e7c0ef9d46c578fa8592f5a92a 100644 (file)
@@ -32,5 +32,6 @@ extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins;
 extern uint8_t root_depth;
 extern bool xcursor_supported, xkb_supported;
 extern xcb_window_t root;
+extern struct ev_loop *main_loop;
 
 #endif
index 9b284f0aacf2f4698fbff8526d0c129525d6939d..c1e10b0639b321727d95efaf09783423ba8d1884 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009-2010 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
 #define DLOG(fmt, ...) debuglog(LOGLEVEL, "%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
 
 extern char *loglevels[];
+extern char *errorfilename;
+
+/**
+ * Initializes logging by creating an error logfile in /tmp (or
+ * XDG_RUNTIME_DIR, see get_process_filename()).
+ *
+ */
+void init_logging();
 
 /**
  * Enables the given loglevel.
index df0e306557dac7e994c2e252a2c5299fbc2d4765..276ea5b9e20db05eca0fcc0b5f0ba59606444188 100644 (file)
@@ -102,6 +102,23 @@ char *sstrdup(const char *str);
  */
 void start_application(const char *command);
 
+/**
+ * exec()s an i3 utility, for example the config file migration script or
+ * i3-nagbar. This function first searches $PATH for the given utility named,
+ * then falls back to the dirname() of the i3 executable path and then falls
+ * back to the dirname() of the target of /proc/self/exe (on linux).
+ *
+ * This function should be called after fork()ing.
+ *
+ * The first argument of the given argv vector will be overwritten with the
+ * executable name, so pass NULL.
+ *
+ * If the utility cannot be found in any of these locations, it exits with
+ * return code 2.
+ *
+ */
+void exec_i3_utility(char *name, char *argv[]);
+
 /**
  * Checks a generic cookie for errors and quits with the given message if
  * there was an error.
index 151b9abc19487d9587469522e0fbdbec192a5284..cea07ed5ee40b721dc65b29086fb066cc7b4b257 100644 (file)
@@ -2,8 +2,9 @@ all:
        a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3.man
        a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-msg.man
        a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-input.man
+       a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-nagbar.man
 clean:
-       for file in "i3 i3-msg i3-input"; \
+       for file in "i3 i3-msg i3-input i3-nagbar"; \
        do \
                rm -f $${file}.1 $${file}.html $${file}.xml; \
        done
diff --git a/man/i3-nagbar.man b/man/i3-nagbar.man
new file mode 100644 (file)
index 0000000..3dd37bb
--- /dev/null
@@ -0,0 +1,34 @@
+i3-nagbar(1)
+============
+Michael Stapelberg <michael+i3@stapelberg.de>
+v4.0, July 2011
+
+== NAME
+
+i3-nagbar - displays an error bar on top of your screen
+
+== SYNOPSIS
+
+i3-nagbar -m 'message' -b 'label' 'action'
+
+== DESCRIPTION
+
+i3-nagbar is used by i3 to tell you about errors in your configuration file
+(for example). While these errors are logged to the logfile (if any), the past
+has proven that users are either not aware of their logfile or do not check it
+after modifying the configuration file.
+
+== EXAMPLE
+
+------------------------------------------------
+i3-nagbar -m 'You have an error in your i3 config file!' \
+-b 'edit config' 'xterm $EDITOR ~/.i3/config'
+------------------------------------------------
+
+== SEE ALSO
+
+i3(1)
+
+== AUTHOR
+
+Michael Stapelberg and contributors
index 83d95fc5b987343c520a2b4a39e4e6797f27d494..227471089571270a1de619490ad2d63e3dd9d277 100644 (file)
@@ -9,10 +9,11 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <limits.h>
-#include <libgen.h>
 
 #include "all.h"
 
+static pid_t configerror_pid = -1;
+
 static Match current_match;
 
 typedef struct yy_buffer_state *YY_BUFFER_STATE;
@@ -30,17 +31,18 @@ static struct context *context;
 //int yydebug = 1;
 
 void yyerror(const char *error_message) {
+    context->has_errors = true;
+
     ELOG("\n");
     ELOG("CONFIG: %s\n", error_message);
     ELOG("CONFIG: in file \"%s\", line %d:\n",
         context->filename, context->line_number);
     ELOG("CONFIG:   %s\n", context->line_copy);
-    ELOG("CONFIG:   ");
+    char buffer[context->last_column+1];
+    buffer[context->last_column] = '\0';
     for (int c = 1; c <= context->last_column; c++)
-        if (c >= context->first_column)
-            printf("^");
-        else printf(" ");
-    printf("\n");
+        buffer[c-1] = (c >= context->first_column ? '^' : ' ');
+    ELOG("CONFIG:   %s\n", buffer);
     ELOG("\n");
 }
 
@@ -146,32 +148,11 @@ static char *migrate_config(char *input, off_t size) {
         close(readpipe[0]);
         dup2(readpipe[1], 1);
 
-        /* start the migration script, search PATH first */
-        char *migratepath = "i3-migrate-config-to-v4.pl";
-        execlp(migratepath, migratepath, NULL);
-
-        /* if the script is not in path, maybe the user installed to a strange
-         * location and runs the i3 binary with an absolute path. We use
-         * argv[0]’s dirname */
-        char *pathbuf = strdup(start_argv[0]);
-        char *dir = dirname(pathbuf);
-        asprintf(&migratepath, "%s/%s", dir, "i3-migrate-config-to-v4.pl");
-        execlp(migratepath, migratepath, NULL);
-
-#if defined(__linux__)
-        /* on linux, we have one more fall-back: dirname(/proc/self/exe) */
-        char buffer[BUFSIZ];
-        if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
-            warn("could not read /proc/self/exe");
-            exit(1);
-        }
-        dir = dirname(buffer);
-        asprintf(&migratepath, "%s/%s", dir, "i3-migrate-config-to-v4.pl");
-        execlp(migratepath, migratepath, NULL);
-#endif
-
-        warn("Could not start i3-migrate-config-to-v4.pl");
-        exit(2);
+        static char *argv[] = {
+            NULL, /* will be replaced by the executable path */
+            NULL
+        };
+        exec_i3_utility("i3-migrate-config-to-v4.pl", argv);
     }
 
     /* parent */
@@ -237,6 +218,98 @@ static char *migrate_config(char *input, off_t size) {
     return converted;
 }
 
+/*
+ * Handler which will be called when we get a SIGCHLD for the nagbar, meaning
+ * it exited (or could not be started, depending on the exit code).
+ *
+ */
+static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
+    ev_child_stop(EV_A_ watcher);
+    if (!WIFEXITED(watcher->rstatus)) {
+        fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
+        return;
+    }
+
+    int exitcode = WEXITSTATUS(watcher->rstatus);
+    printf("i3-nagbar process exited with status %d\n", exitcode);
+    if (exitcode == 2) {
+        fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
+    }
+
+    configerror_pid = -1;
+}
+
+/*
+ * Starts an i3-nagbar process which alerts the user that his configuration
+ * file contains one or more errors. Also offers two buttons: One to launch an
+ * $EDITOR on the config file and another one to launch a $PAGER on the error
+ * logfile.
+ *
+ */
+static void start_configerror_nagbar(const char *config_path) {
+    fprintf(stderr, "Would start i3-nagscreen now\n");
+    configerror_pid = fork();
+    if (configerror_pid == -1) {
+        warn("Could not fork()");
+        return;
+    }
+
+    /* child */
+    if (configerror_pid == 0) {
+        char *editaction,
+             *pageraction;
+        if (asprintf(&editaction, TERM_EMU " -e $EDITOR \"%s\"", config_path) == -1)
+            exit(1);
+        if (asprintf(&pageraction, TERM_EMU " -e $PAGER \"%s\"", errorfilename) == -1)
+            exit(1);
+        char *argv[] = {
+            NULL, /* will be replaced by the executable path */
+            "-m",
+            "You have an error in your i3 config file!",
+            "-b",
+            "edit config",
+            editaction,
+            (errorfilename ? "-b" : NULL),
+            "show errors",
+            pageraction,
+            NULL
+        };
+        exec_i3_utility("i3-nagbar", argv);
+    }
+
+    /* parent */
+    /* install a child watcher */
+    ev_child *child = smalloc(sizeof(ev_child));
+    ev_child_init(child, &nagbar_exited, configerror_pid, 0);
+    ev_child_start(main_loop, child);
+}
+
+/*
+ * Kills the configerror i3-nagbar process, if any.
+ *
+ * Called when reloading/restarting.
+ *
+ * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
+ * ev is assumed to handle it (reloading).
+ *
+ */
+void kill_configerror_nagbar(bool wait_for_it) {
+    if (configerror_pid == -1)
+        return;
+
+    if (kill(configerror_pid, SIGTERM) == -1)
+        warn("kill(configerror_nagbar) failed");
+
+    if (!wait_for_it)
+        return;
+
+    /* When restarting, we don’t enter the ev main loop anymore and after the
+     * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
+     * for us and we would end up with a <defunct> process. Therefore we
+     * waitpid() here. */
+    waitpid(configerror_pid, NULL, 0);
+}
+
 void parse_file(const char *f) {
     SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
     int fd, ret, read_bytes = 0;
@@ -390,6 +463,10 @@ void parse_file(const char *f) {
         exit(1);
     }
 
+    if (context->has_errors) {
+        start_configerror_nagbar(f);
+    }
+
     FREE(context->line_copy);
     free(context);
     free(new);
index 95dc27b6c9ecadc7e03ea7edb9237e656341d708..0b80b6b333748ca0425c4041644f3675779f1d52 100644 (file)
@@ -380,6 +380,7 @@ reload:
     TOK_RELOAD
     {
         printf("reloading\n");
+        kill_configerror_nagbar(false);
         load_configuration(conn, NULL, true);
         x_set_i3_atoms();
         /* Send an IPC event just in case the ws names have changed */
index 0371e9be618eba73a315490ff3e2bcc3c3671868..99c2d4d3d2a5fe1218068e93e54b931bcafc174c 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -14,6 +14,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <stdbool.h>
+#include <stdlib.h>
 
 #include "util.h"
 #include "log.h"
 
 static uint64_t loglevel = 0;
 static bool verbose = true;
+static FILE *errorfile;
+char *errorfilename;
+
+/*
+ * Initializes logging by creating an error logfile in /tmp (or
+ * XDG_RUNTIME_DIR, see get_process_filename()).
+ *
+ */
+void init_logging() {
+    errorfilename = get_process_filename("errorlog");
+    if (errorfilename == NULL) {
+        ELOG("Could not initialize errorlog\n");
+        return;
+    }
+
+    errorfile = fopen(errorfilename, "w");
+}
 
 /*
  * Set verbosity of i3. If verbose is set to true, informative messages will
@@ -101,6 +119,12 @@ void errorlog(char *fmt, ...) {
     va_start(args, fmt);
     vlog(fmt, args);
     va_end(args);
+
+    /* also log to the error logfile, if opened */
+    va_start(args, fmt);
+    vfprintf(errorfile, fmt, args);
+    fflush(errorfile);
+    va_end(args);
 }
 
 /*
index b4ed4a1a061cc18b15cc0b68e5857ed066551063..a2764cc18067054724a6e1bd53f6f94546875e43 100644 (file)
@@ -19,6 +19,8 @@ xcb_connection_t *conn;
 xcb_window_t root;
 uint8_t root_depth;
 
+struct ev_loop *main_loop;
+
 xcb_key_symbols_t *keysyms;
 
 /* Those are our connections to X11 for use with libXcursor and XKB */
@@ -178,6 +180,8 @@ int main(int argc, char *argv[]) {
     if (!isatty(fileno(stdout)))
         setbuf(stdout, NULL);
 
+    init_logging();
+
     start_argv = argv;
 
     while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) {
@@ -254,6 +258,13 @@ int main(int argc, char *argv[]) {
     if (xcb_connection_has_error(conn))
         errx(EXIT_FAILURE, "Cannot open display\n");
 
+    /* Initialize the libev event loop. This needs to be done before loading
+     * the config file because the parser will install an ev_child watcher
+     * for the nagbar when config errors are found. */
+    main_loop = EV_DEFAULT;
+    if (main_loop == NULL)
+            die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
+
     xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
     root = root_screen->root;
     root_depth = root_screen->root_depth;
@@ -395,10 +406,6 @@ int main(int argc, char *argv[]) {
 
     tree_render();
 
-    struct ev_loop *loop = ev_loop_new(0);
-    if (loop == NULL)
-            die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
-
     /* Create the UNIX domain socket for IPC */
     int ipc_socket = ipc_create_socket(config.ipc_socket_path);
     if (ipc_socket == -1) {
@@ -407,7 +414,7 @@ int main(int argc, char *argv[]) {
         free(config.ipc_socket_path);
         struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
         ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
-        ev_io_start(loop, ipc_io);
+        ev_io_start(main_loop, ipc_io);
     }
 
     /* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */
@@ -419,22 +426,22 @@ int main(int argc, char *argv[]) {
     struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare));
 
     ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
-    ev_io_start(loop, xcb_watcher);
+    ev_io_start(main_loop, xcb_watcher);
 
 
     if (xkb_supported) {
         ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
-        ev_io_start(loop, xkb);
+        ev_io_start(main_loop, xkb);
 
         /* Flush the buffer so that libev can properly get new events */
         XFlush(xkbdpy);
     }
 
     ev_check_init(xcb_check, xcb_check_cb);
-    ev_check_start(loop, xcb_check);
+    ev_check_start(main_loop, xcb_check);
 
     ev_prepare_init(xcb_prepare, xcb_prepare_cb);
-    ev_prepare_start(loop, xcb_prepare);
+    ev_prepare_start(main_loop, xcb_prepare);
 
     xcb_flush(conn);
 
@@ -456,5 +463,5 @@ int main(int argc, char *argv[]) {
         }
     }
 
-    ev_loop(loop, 0);
+    ev_loop(main_loop, 0);
 }
index f95ccaf36a23b2d003ef3ba3a89373c1ad073f0a..cc93df216f307f2640081aac5b0536cac85a42ca 100644 (file)
@@ -19,6 +19,7 @@
 #include <fcntl.h>
 #include <pwd.h>
 #include <yajl/yajl_version.h>
+#include <libgen.h>
 
 #include "all.h"
 
@@ -118,6 +119,53 @@ void start_application(const char *command) {
     wait(0);
 }
 
+/*
+ * exec()s an i3 utility, for example the config file migration script or
+ * i3-nagbar. This function first searches $PATH for the given utility named,
+ * then falls back to the dirname() of the i3 executable path and then falls
+ * back to the dirname() of the target of /proc/self/exe (on linux).
+ *
+ * This function should be called after fork()ing.
+ *
+ * The first argument of the given argv vector will be overwritten with the
+ * executable name, so pass NULL.
+ *
+ * If the utility cannot be found in any of these locations, it exits with
+ * return code 2.
+ *
+ */
+void exec_i3_utility(char *name, char *argv[]) {
+    /* start the migration script, search PATH first */
+    char *migratepath = name;
+    argv[0] = migratepath;
+    execvp(migratepath, argv);
+
+    /* if the script is not in path, maybe the user installed to a strange
+     * location and runs the i3 binary with an absolute path. We use
+     * argv[0]’s dirname */
+    char *pathbuf = strdup(start_argv[0]);
+    char *dir = dirname(pathbuf);
+    asprintf(&migratepath, "%s/%s", dir, name);
+    argv[0] = migratepath;
+    execvp(migratepath, argv);
+
+#if defined(__linux__)
+    /* on linux, we have one more fall-back: dirname(/proc/self/exe) */
+    char buffer[BUFSIZ];
+    if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
+        warn("could not read /proc/self/exe");
+        exit(1);
+    }
+    dir = dirname(buffer);
+    asprintf(&migratepath, "%s/%s", dir, name);
+    argv[0] = migratepath;
+    execvp(migratepath, argv);
+#endif
+
+    warn("Could not start %s", name);
+    exit(2);
+}
+
 /*
  * Checks a generic cookie for errors and quits with the given message if there
  * was an error.
@@ -358,6 +406,8 @@ char *store_restart_layout() {
 void i3_restart(bool forget_layout) {
     char *restart_filename = forget_layout ? NULL : store_restart_layout();
 
+    kill_configerror_nagbar(true);
+
     restore_geometry();
 
     ipc_shutdown();