add i3-nagbar. tells you about config file errors (for example)

This commit is contained in:
Michael Stapelberg 2011-07-10 14:33:19 +02:00
parent b63a559c28
commit c55abca115
17 changed files with 864 additions and 45 deletions

View File

@ -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)

28
i3-nagbar/Makefile Normal file
View File

@ -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

6
i3-nagbar/atoms.xmacro Normal file
View File

@ -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)

26
i3-nagbar/i3-nagbar.h Normal file
View File

@ -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

392
i3-nagbar/main.c Normal file
View File

@ -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 applications parent exits (immediately),
* the application is reparented to init (process-id 1), which correctly handles
* childs, so we dont 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;
}

132
i3-nagbar/xcb.c Normal file
View File

@ -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;
}

View 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);

View 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

View 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.
*
@ -21,6 +21,14 @@
#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.

View 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.

View 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

34
man/i3-nagbar.man Normal file
View File

@ -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

View 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 dont enter the ev main loop anymore and after the
* exec(), our old pid is no longer watched. So, ev wont 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);

View 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 */

View File

@ -14,6 +14,7 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include "util.h"
#include "log.h"
@ -23,6 +24,23 @@
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);
}
/*

View 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);
}

View 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();