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)
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)
--- /dev/null
+# 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
--- /dev/null
+xmacro(_NET_WM_WINDOW_TYPE)
+xmacro(_NET_WM_WINDOW_TYPE_DOCK)
+xmacro(_NET_WM_STRUT_PARTIAL)
+xmacro(I3_SOCKET_PATH)
+xmacro(ATOM)
+xmacro(CARDINAL)
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
*
*/
struct context {
+ bool has_errors;
+
int line_number;
char *line_copy;
const char *filename;
*/
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);
extern uint8_t root_depth;
extern bool xcursor_supported, xkb_supported;
extern xcb_window_t root;
+extern struct ev_loop *main_loop;
#endif
/*
- * 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.
*/
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.
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
--- /dev/null
+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
#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;
//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");
}
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 */
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;
exit(1);
}
+ if (context->has_errors) {
+ start_configerror_nagbar(f);
+ }
+
FREE(context->line_copy);
free(context);
free(new);
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 */
#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
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);
}
/*
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 */
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) {
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;
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) {
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 */
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);
}
}
- ev_loop(loop, 0);
+ ev_loop(main_loop, 0);
}
#include <fcntl.h>
#include <pwd.h>
#include <yajl/yajl_version.h>
+#include <libgen.h>
#include "all.h"
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.
void i3_restart(bool forget_layout) {
char *restart_filename = forget_layout ? NULL : store_restart_layout();
+ kill_configerror_nagbar(true);
+
restore_geometry();
ipc_shutdown();