diff --git a/include/sighandler.h b/include/sighandler.h new file mode 100644 index 00000000..cbb72cdd --- /dev/null +++ b/include/sighandler.h @@ -0,0 +1,21 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * © 2009 Jan-Erik Rediger + * + * See file LICENSE for license information. + * + */ +#ifndef _SIGHANDLER_H +#define _SIGHANDLER_H + +/* + * Setup signal handlers to safely handle SIGSEGV and SIGFPE + * + */ +void setup_signal_handler(); + +#endif diff --git a/include/util.h b/include/util.h index f4e95604..e45fc75a 100644 --- a/include/util.h +++ b/include/util.h @@ -150,6 +150,13 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode); Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle, Client *specific); +/* + * Restart i3 in-place + * appends -a to argument list to disable autostart + * + */ +void i3_restart(); + #if defined(__OpenBSD__) /* OpenBSD does not provide memmem(), so we provide FreeBSD’s implementation */ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len); diff --git a/src/commands.c b/src/commands.c index 31564bb9..92b4aa8c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -31,6 +31,7 @@ #include "commands.h" #include "resize.h" #include "log.h" +#include "sighandler.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -759,29 +760,6 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { } } -/* - * Goes through the list of arguments (for exec()) and checks if the given argument - * is present. If not, it copies the arguments (because we cannot realloc it) and - * appends the given argument. - * - */ -static char **append_argument(char **original, char *argument) { - int num_args; - for (num_args = 0; original[num_args] != NULL; num_args++) { - DLOG("original argument: \"%s\"\n", original[num_args]); - /* If the argument is already present we return the original pointer */ - if (strcmp(original[num_args], argument) == 0) - return original; - } - /* Copy the original array */ - char **result = smalloc((num_args+2) * sizeof(char*)); - memcpy(result, original, num_args * sizeof(char*)); - result[num_args] = argument; - result[num_args+1] = NULL; - - return result; -} - /* * Switch to next or previous existing workspace * @@ -965,12 +943,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it ? Then restart in place. */ if (STARTS_WITH(command, "restart")) { - LOG("restarting \"%s\"...\n", start_argv[0]); - /* make sure -a is in the argument list or append it */ - start_argv = append_argument(start_argv, "-a"); - - execvp(start_argv[0], start_argv); - /* not reached */ + i3_restart(); } if (STARTS_WITH(command, "kill")) { diff --git a/src/mainx.c b/src/mainx.c index 3ff8c022..61e71613 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -49,6 +49,7 @@ #include "manage.h" #include "ipc.h" #include "log.h" +#include "sighandler.h" xcb_connection_t *global_conn; @@ -499,6 +500,7 @@ int main(int argc, char *argv[], char *env[]) { /* Handle the events which arrived until now */ xcb_check_cb(NULL, NULL, 0); + setup_signal_handler(); /* Ungrab the server to receive events and enter libev’s eventloop */ xcb_ungrab_server(conn); ev_loop(loop, 0); diff --git a/src/sighandler.c b/src/sighandler.c new file mode 100644 index 00000000..9eeb2398 --- /dev/null +++ b/src/sighandler.c @@ -0,0 +1,215 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * © 2009 Jan-Erik Rediger + * + * See file LICENSE for license information. + * + * sighandler.c: contains all functions for signal handling + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "i3.h" +#include "util.h" +#include "xcb.h" +#include "log.h" +#include "config.h" +#include "xinerama.h" + +static xcb_gcontext_t pixmap_gc; +static xcb_pixmap_t pixmap; +static int raised_signal; + +static char *crash_text[] = { + "i3 just crashed.", + "To debug this problem, either attach gdb now", + "or press 'e' to exit and get a core-dump.", + "If you want to keep your session,", + "press 'r' to restart i3 in-place." +}; +static int crash_text_longest = 1; + +/* + * Draw the window containing the info text + * + */ +static int sig_draw_window(xcb_connection_t *conn, xcb_window_t win, int width, int height, int font_height) { + /* re-draw the background */ + xcb_rectangle_t border = {0, 0, width, height}, inner = {2, 2, width-4, height-4}; + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); + + /* restore font color */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF")); + + char *full_text; + int text_len; + int i; + int crash_text_num = sizeof(crash_text) / sizeof(char*); + for (i = 0; i < crash_text_num; i++) { + text_len = strlen(crash_text[i]); + full_text = convert_utf8_to_ucs2(crash_text[i], &text_len); + xcb_image_text_16(conn, text_len, pixmap, pixmap_gc, 8 /* X */, + 3 + (i+1)*font_height /* Y = baseline of font */, (xcb_char2b_t*)full_text); + free(full_text); + } + + /* Copy the contents of the pixmap to the real window */ + xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height); + xcb_flush(conn); + + return 1; +} + + +/* + * Handles keypresses of 'e' or 'r' to exit or restart i3 + * + */ +static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { + xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, event->state); + + if (sym == 'e') { + LOG("User issued exit-command, raising error again.\n"); + raise(raised_signal); + exit(1); + } + if (sym == 'r') + i3_restart(); + + return 1; +} + +/* + * Opens the window we use for input/output and maps it + * + */ +static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, uint32_t width, uint32_t height) { + xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; + xcb_window_t win = xcb_generate_id(conn); + + uint32_t mask = 0; + uint32_t values[3]; + + mask |= XCB_CW_BACK_PIXEL; + values[0] = 0; + + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[1] = 1; + + mask |= XCB_CW_EVENT_MASK; + values[2] = XCB_EVENT_MASK_EXPOSURE; + + /* center each popup on the specified screen */ + uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width/2)), + y = screen_rect.y + ((screen_rect.height / 2) - (height/2)); + + xcb_create_window(conn, + XCB_COPY_FROM_PARENT, + win, /* the window id */ + root, /* parent == root */ + x, y, 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); + + /* Map the window (= make it visible) */ + xcb_map_window(conn, win); + + return win; +} + +/* + * Handle signals + * It creates a window asking the user to restart in-place + * or exit to generate a core dump + * + */ +void handle_signal(int sig, siginfo_t *info, void *data) { + LOG("i3 crashed. SIG: %d\n", sig); + + struct sigaction action; + action.sa_handler = SIG_DFL; + sigaction(sig, &action, NULL); + raised_signal = sig; + + xcb_connection_t *conn = global_conn; + + /* Set up event handlers for key press and key release */ + xcb_event_handlers_t sig_evenths; + memset(&sig_evenths, 0, sizeof(xcb_event_handlers_t)); + xcb_event_handlers_init(conn, &sig_evenths); + xcb_event_set_key_press_handler(&sig_evenths, sig_handle_key_press, NULL); + + i3Font *font = load_font(conn, config.font); + + /* width and height of the popup window, so that the text fits in */ + int crash_text_num = sizeof(crash_text) / sizeof(char*); + int height = 13 + (crash_text_num * font->height); + + /* calculate width for longest text */ + int text_len = strlen(crash_text[crash_text_longest]); + char *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len); + int font_width = predict_text_width(conn, config.font, longest_text, text_len); + int width = font_width + 20; + + /* Open an popup window on each virtual screen */ + i3Screen *screen; + xcb_window_t win; + TAILQ_FOREACH(screen, virtual_screens, screens) { + LOG("%d, %d, %d, %d\n", screen->rect.width, screen->rect.height, screen->rect.x, screen->rect.y); + + win = open_input_window(conn, screen->rect, width, height); + + /* Create pixmap */ + pixmap = xcb_generate_id(conn); + pixmap_gc = xcb_generate_id(conn); + xcb_create_pixmap(conn, root_depth, pixmap, win, width, 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); + + /* Grab the keyboard to get all input */ + xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + + sig_draw_window(conn, win, width, height, font->height); + xcb_flush(conn); + } + + xcb_event_wait_for_event_loop(&sig_evenths); +} + +/* + * Setup signal handlers to safely handle SIGSEGV and SIGFPE + * + */ +void setup_signal_handler() { + struct sigaction action; + action.sa_sigaction = handle_signal; + action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; + sigemptyset(&action.sa_mask); + sigaction(SIGSEGV, &action, NULL); + sigaction(SIGFPE, &action, NULL); +} diff --git a/src/util.c b/src/util.c index bc54caa5..ba4bd6dd 100644 --- a/src/util.c +++ b/src/util.c @@ -159,16 +159,16 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes * */ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { - size_t input_size = strlen(input) + 1; - /* UCS-2 consumes exactly two bytes for each glyph */ - int buffer_size = input_size * 2; + size_t input_size = strlen(input) + 1; + /* UCS-2 consumes exactly two bytes for each glyph */ + int buffer_size = input_size * 2; - char *buffer = smalloc(buffer_size); - size_t output_size = buffer_size; - /* We need to use an additional pointer, because iconv() modifies it */ - char *output = buffer; + char *buffer = smalloc(buffer_size); + size_t output_size = buffer_size; + /* We need to use an additional pointer, because iconv() modifies it */ + char *output = buffer; - /* We convert the input into UCS-2 big endian */ + /* We convert the input into UCS-2 big endian */ if (conversion_descriptor == 0) { conversion_descriptor = iconv_open("UCS-2BE", "UTF-8"); if (conversion_descriptor == 0) { @@ -177,22 +177,22 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { } } - /* Get the conversion descriptor back to original state */ - iconv(conversion_descriptor, NULL, NULL, NULL, NULL); + /* Get the conversion descriptor back to original state */ + iconv(conversion_descriptor, NULL, NULL, NULL, NULL); - /* Convert our text */ - int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); + /* Convert our text */ + int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); if (rc == (size_t)-1) { perror("Converting to UCS-2 failed"); if (real_strlen != NULL) - *real_strlen = 0; + *real_strlen = 0; return NULL; - } + } if (real_strlen != NULL) - *real_strlen = ((buffer_size - output_size) / 2) - 1; + *real_strlen = ((buffer_size - output_size) / 2) - 1; - return buffer; + return buffer; } /* @@ -463,6 +463,43 @@ done: return matching; } +/* + * Goes through the list of arguments (for exec()) and checks if the given argument + * is present. If not, it copies the arguments (because we cannot realloc it) and + * appends the given argument. + * + */ +static char **append_argument(char **original, char *argument) { + int num_args; + for (num_args = 0; original[num_args] != NULL; num_args++) { + DLOG("original argument: \"%s\"\n", original[num_args]); + /* If the argument is already present we return the original pointer */ + if (strcmp(original[num_args], argument) == 0) + return original; + } + /* Copy the original array */ + char **result = smalloc((num_args+2) * sizeof(char*)); + memcpy(result, original, num_args * sizeof(char*)); + result[num_args] = argument; + result[num_args+1] = NULL; + + return result; +} + +/* + * Restart i3 in-place + * appends -a to argument list to disable autostart + * + */ +void i3_restart() { + LOG("restarting \"%s\"...\n", start_argv[0]); + /* make sure -a is in the argument list or append it */ + start_argv = append_argument(start_argv, "-a"); + + execvp(start_argv[0], start_argv); + /* not reached */ +} + #if defined(__OpenBSD__) /*