i3
src/sighandler.c
Go to the documentation of this file.
00001 /*
00002  * vim:ts=4:sw=4:expandtab
00003  *
00004  * i3 - an improved dynamic tiling window manager
00005  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
00006  * © 2009-2010 Jan-Erik Rediger
00007  *
00008  * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
00009  *               to restart inplace).
00010  *
00011  */
00012 #include "all.h"
00013 
00014 #include <ev.h>
00015 #include <iconv.h>
00016 #include <signal.h>
00017 
00018 #include <xcb/xcb_event.h>
00019 
00020 #include <X11/keysym.h>
00021 
00022 static xcb_gcontext_t pixmap_gc;
00023 static xcb_pixmap_t pixmap;
00024 static int raised_signal;
00025 
00026 static char *crash_text[] = {
00027     "i3 just crashed.",
00028     "To debug this problem, either attach gdb now",
00029     "or press",
00030     "- 'e' to exit and get a core-dump,",
00031     "- 'r' to restart i3 in-place or",
00032     "- 'f' to forget the current layout and restart"
00033 };
00034 static int crash_text_longest = 5;
00035 
00036 /*
00037  * Draw the window containing the info text
00038  *
00039  */
00040 static int sig_draw_window(xcb_window_t win, int width, int height, int font_height) {
00041     /* re-draw the background */
00042     xcb_rectangle_t border = { 0, 0, width, height},
00043                     inner = { 2, 2, width - 4, height - 4};
00044     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
00045     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
00046     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#000000") });
00047     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
00048 
00049     /* restore font color */
00050     set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
00051 
00052     for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) {
00053         draw_text(crash_text[i], strlen(crash_text[i]), false, pixmap, pixmap_gc,
00054                 8, 5 + i * font_height, width - 16);
00055     }
00056 
00057     /* Copy the contents of the pixmap to the real window */
00058     xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height);
00059     xcb_flush(conn);
00060 
00061     return 1;
00062 }
00063 
00064 /*
00065  * Handles keypresses of 'e' or 'r' to exit or restart i3
00066  *
00067  */
00068 static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
00069     uint16_t state = event->state;
00070 
00071     /* Apparantly, after activating numlock once, the numlock modifier
00072      * stays turned on (use xev(1) to verify). So, to resolve useful
00073      * keysyms, we remove the numlock flag from the event state */
00074     state &= ~xcb_numlock_mask;
00075 
00076     xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state);
00077 
00078     if (sym == 'e') {
00079         DLOG("User issued exit-command, raising error again.\n");
00080         raise(raised_signal);
00081         exit(1);
00082     }
00083 
00084     if (sym == 'r')
00085         i3_restart(false);
00086 
00087     if (sym == 'f')
00088         i3_restart(true);
00089 
00090     return 1;
00091 }
00092 
00093 /*
00094  * Opens the window we use for input/output and maps it
00095  *
00096  */
00097 static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, uint32_t width, uint32_t height) {
00098     xcb_window_t win = xcb_generate_id(conn);
00099 
00100     uint32_t mask = 0;
00101     uint32_t values[2];
00102 
00103     mask |= XCB_CW_BACK_PIXEL;
00104     values[0] = 0;
00105 
00106     mask |= XCB_CW_OVERRIDE_REDIRECT;
00107     values[1] = 1;
00108 
00109     /* center each popup on the specified screen */
00110     uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width / 2)),
00111              y = screen_rect.y + ((screen_rect.height / 2) - (height / 2));
00112 
00113     xcb_create_window(conn,
00114                       XCB_COPY_FROM_PARENT,
00115                       win, /* the window id */
00116                       root, /* parent == root */
00117                       x, y, width, height, /* dimensions */
00118                       0, /* border = 0, we draw our own */
00119                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
00120                       XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
00121                       mask,
00122                       values);
00123 
00124     /* Map the window (= make it visible) */
00125     xcb_map_window(conn, win);
00126 
00127     return win;
00128 }
00129 
00130 /*
00131  * Handle signals
00132  * It creates a window asking the user to restart in-place
00133  * or exit to generate a core dump
00134  *
00135  */
00136 void handle_signal(int sig, siginfo_t *info, void *data) {
00137     DLOG("i3 crashed. SIG: %d\n", sig);
00138 
00139     struct sigaction action;
00140     action.sa_handler = SIG_DFL;
00141     sigaction(sig, &action, NULL);
00142     raised_signal = sig;
00143 
00144     /* width and height of the popup window, so that the text fits in */
00145     int crash_text_num = sizeof(crash_text) / sizeof(char*);
00146     int height = 13 + (crash_text_num * config.font.height);
00147 
00148     /* calculate width for longest text */
00149     size_t text_len = strlen(crash_text[crash_text_longest]);
00150     xcb_char2b_t *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len);
00151     int font_width = predict_text_width((char *)longest_text, text_len, true);
00152     int width = font_width + 20;
00153 
00154     /* Open a popup window on each virtual screen */
00155     Output *screen;
00156     xcb_window_t win;
00157     TAILQ_FOREACH(screen, &outputs, outputs) {
00158         if (!screen->active)
00159             continue;
00160         win = open_input_window(conn, screen->rect, width, height);
00161 
00162         /* Create pixmap */
00163         pixmap = xcb_generate_id(conn);
00164         pixmap_gc = xcb_generate_id(conn);
00165         xcb_create_pixmap(conn, root_depth, pixmap, win, width, height);
00166         xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
00167 
00168         /* Grab the keyboard to get all input */
00169         xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
00170 
00171         /* Grab the cursor inside the popup */
00172         xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC,
00173                          XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME);
00174 
00175         sig_draw_window(win, width, height, config.font.height);
00176         xcb_flush(conn);
00177     }
00178 
00179     xcb_generic_event_t *event;
00180     /* Yay, more own eventhandlers… */
00181     while ((event = xcb_wait_for_event(conn))) {
00182         /* Strip off the highest bit (set if the event is generated) */
00183         int type = (event->response_type & 0x7F);
00184         if (type == XCB_KEY_PRESS) {
00185             sig_handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
00186         }
00187         free(event);
00188     }
00189 }
00190 
00191 /*
00192  * Setup signal handlers to safely handle SIGSEGV and SIGFPE
00193  *
00194  */
00195 void setup_signal_handler(void) {
00196     struct sigaction action;
00197 
00198     action.sa_sigaction = handle_signal;
00199     action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
00200     sigemptyset(&action.sa_mask);
00201 
00202     /* Catch all signals with default action "Core", see signal(7) */
00203     if (sigaction(SIGQUIT, &action, NULL) == -1 ||
00204         sigaction(SIGILL, &action, NULL) == -1 ||
00205         sigaction(SIGABRT, &action, NULL) == -1 ||
00206         sigaction(SIGFPE, &action, NULL) == -1 ||
00207         sigaction(SIGSEGV, &action, NULL) == -1)
00208         ELOG("Could not setup signal handler");
00209 }