i3
sighandler.c
Go to the documentation of this file.
1/*
2 * vim:ts=4:sw=4:expandtab
3 *
4 * i3 - an improved dynamic tiling window manager
5 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6 *
7 */
8#include "all.h"
9
10#include <ev.h>
11#include <iconv.h>
12#include <signal.h>
13#include <sys/wait.h>
14
15#include <xcb/xcb_event.h>
16
17#include <X11/keysym.h>
18
19typedef struct dialog_t {
20 xcb_window_t id;
21 xcb_colormap_t colormap;
24
28
30static int raised_signal;
31static int backtrace_done = 0;
32
33static int sighandler_backtrace(void);
34static void sighandler_setup(void);
35static void sighandler_create_dialogs(void);
36static void sighandler_destroy_dialogs(void);
37static void sighandler_handle_expose(void);
38static void sighandler_draw_dialog(dialog_t *dialog);
39static void sighandler_handle_key_press(xcb_key_press_event_t *event);
40
41static i3String *message_intro;
42static i3String *message_intro2;
43static i3String *message_option_backtrace;
44static i3String *message_option_restart;
45static i3String *message_option_forget;
46static int dialog_width;
47static int dialog_height;
48
49static int border_width = 2;
50static int margin = 4;
51
52/*
53 * Attach gdb to pid_parent and dump a backtrace to i3-backtrace.$pid in the
54 * tmpdir
55 */
56static int sighandler_backtrace(void) {
57 char *tmpdir = getenv("TMPDIR");
58 if (tmpdir == NULL)
59 tmpdir = "/tmp";
60
61 pid_t pid_parent = getpid();
62
63 char *filename = NULL;
64 int suffix = 0;
65 /* Find a unique filename for the backtrace (since the PID of i3 stays the
66 * same), so that we don’t overwrite earlier backtraces. */
67 do {
68 FREE(filename);
69 sasprintf(&filename, "%s/i3-backtrace.%d.%d.txt", tmpdir, pid_parent, suffix);
70 suffix++;
71 } while (path_exists(filename));
72
73 pid_t pid_gdb = fork();
74 if (pid_gdb < 0) {
75 DLOG("Failed to fork for GDB\n");
76 return -1;
77 } else if (pid_gdb == 0) {
78 /* child */
79 int stdin_pipe[2],
80 stdout_pipe[2];
81
82 if (pipe(stdin_pipe) == -1) {
83 ELOG("Failed to init stdin_pipe\n");
84 return -1;
85 }
86 if (pipe(stdout_pipe) == -1) {
87 ELOG("Failed to init stdout_pipe\n");
88 return -1;
89 }
90
91 /* close standard streams in case i3 is started from a terminal; gdb
92 * needs to run without controlling terminal for it to work properly in
93 * this situation */
94 close(STDIN_FILENO);
95 close(STDOUT_FILENO);
96 close(STDERR_FILENO);
97
98 /* We provide pipe file descriptors for stdin/stdout because gdb < 7.5
99 * crashes otherwise, see
100 * https://sourceware.org/bugzilla/show_bug.cgi?id=14114 */
101 dup2(stdin_pipe[0], STDIN_FILENO);
102 dup2(stdout_pipe[1], STDOUT_FILENO);
103
104 char *pid_s, *gdb_log_cmd;
105 sasprintf(&pid_s, "%d", pid_parent);
106 sasprintf(&gdb_log_cmd, "set logging file %s", filename);
107
108 char *args[] = {
109 "gdb",
110 start_argv[0],
111 "-p",
112 pid_s,
113 "-batch",
114 "-nx",
115 "-ex", gdb_log_cmd,
116 "-ex", "set logging on",
117 "-ex", "bt full",
118 "-ex", "quit",
119 NULL};
120 execvp(args[0], args);
121 DLOG("Failed to exec GDB\n");
122 exit(EXIT_FAILURE);
123 }
124 int status = 0;
125
126 waitpid(pid_gdb, &status, 0);
127
128 /* see if the backtrace was successful or not */
129 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
130 DLOG("GDB did not run properly\n");
131 return -1;
132 } else if (!path_exists(filename)) {
133 DLOG("GDB executed successfully, but no backtrace was generated\n");
134 return -1;
135 }
136 return 1;
137}
138
139static void sighandler_setup(void) {
140 border_width = logical_px(border_width);
141 margin = logical_px(margin);
142
143 int num_lines = 5;
144 message_intro = i3string_from_utf8("i3 has just crashed. Please report a bug for this.");
145 message_intro2 = i3string_from_utf8("To debug this problem, you can either attach gdb or choose from the following options:");
146 message_option_backtrace = i3string_from_utf8("- 'b' to save a backtrace (requires gdb)");
147 message_option_restart = i3string_from_utf8("- 'r' to restart i3 in-place");
148 message_option_forget = i3string_from_utf8("- 'f' to forget the previous layout and restart i3");
149
150 int width_longest_message = predict_text_width(message_intro2);
151
152 dialog_width = width_longest_message + 2 * border_width + 2 * margin;
153 dialog_height = num_lines * config.font.height + 2 * border_width + 2 * margin;
154}
155
156static void sighandler_create_dialogs(void) {
157 Output *output;
158 TAILQ_FOREACH(output, &outputs, outputs) {
159 if (!output->active) {
160 continue;
161 }
162
163 dialog_t *dialog = scalloc(1, sizeof(struct dialog_t));
165
166 xcb_visualid_t visual = get_visualid_by_depth(root_depth);
167 dialog->colormap = xcb_generate_id(conn);
168 xcb_create_colormap(conn, XCB_COLORMAP_ALLOC_NONE, dialog->colormap, root, visual);
169
170 uint32_t mask = 0;
171 uint32_t values[4];
172 int i = 0;
173
174 /* Needs to be set in the case of a 32-bit root depth. */
175 mask |= XCB_CW_BACK_PIXEL;
176 values[i++] = root_screen->black_pixel;
177
178 /* Needs to be set in the case of a 32-bit root depth. */
179 mask |= XCB_CW_BORDER_PIXEL;
180 values[i++] = root_screen->black_pixel;
181
182 mask |= XCB_CW_OVERRIDE_REDIRECT;
183 values[i++] = 1;
184
185 /* Needs to be set in the case of a 32-bit root depth. */
186 mask |= XCB_CW_COLORMAP;
187 values[i++] = dialog->colormap;
188
189 dialog->dims.x = output->rect.x + (output->rect.width / 2);
190 dialog->dims.y = output->rect.y + (output->rect.height / 2);
191 dialog->dims.width = dialog_width;
192 dialog->dims.height = dialog_height;
193
194 /* Make sure the dialog is centered. */
195 dialog->dims.x -= dialog->dims.width / 2;
196 dialog->dims.y -= dialog->dims.height / 2;
197
198 dialog->id = create_window(conn, dialog->dims, root_depth, visual,
199 XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER,
200 true, mask, values);
201
202 draw_util_surface_init(conn, &(dialog->surface), dialog->id, get_visualtype_by_id(visual),
203 dialog->dims.width, dialog->dims.height);
204
205 xcb_grab_keyboard(conn, false, dialog->id, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
206
207 /* Confine the pointer to the crash dialog. */
208 xcb_grab_pointer(conn, false, dialog->id, XCB_NONE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, dialog->id,
209 XCB_NONE, XCB_CURRENT_TIME);
210 }
211
213 xcb_flush(conn);
214}
215
216static void sighandler_destroy_dialogs(void) {
217 while (!TAILQ_EMPTY(&dialogs)) {
218 dialog_t *dialog = TAILQ_FIRST(&dialogs);
219
220 xcb_free_colormap(conn, dialog->colormap);
222 xcb_destroy_window(conn, dialog->id);
223
224 TAILQ_REMOVE(&dialogs, dialog, dialogs);
225 free(dialog);
226 }
227
228 xcb_flush(conn);
229}
230
231static void sighandler_handle_expose(void) {
232 dialog_t *current;
233 TAILQ_FOREACH(current, &dialogs, dialogs) {
234 sighandler_draw_dialog(current);
235 }
236
237 xcb_flush(conn);
238}
239
240static void sighandler_draw_dialog(dialog_t *dialog) {
241 const color_t black = draw_util_hex_to_color("#000000");
242 const color_t white = draw_util_hex_to_color("#FFFFFF");
243 const color_t red = draw_util_hex_to_color("#FF0000");
244
245 /* Start with a clean slate and draw a red border. */
246 draw_util_clear_surface(&(dialog->surface), red);
247 draw_util_rectangle(&(dialog->surface), black, border_width, border_width,
248 dialog->dims.width - 2 * border_width, dialog->dims.height - 2 * border_width);
249
250 int y = border_width + margin;
251 const int x = border_width + margin;
252 const int max_width = dialog->dims.width - 2 * x;
253
254 draw_util_text(message_intro, &(dialog->surface), white, black, x, y, max_width);
255 y += config.font.height;
256
257 draw_util_text(message_intro2, &(dialog->surface), white, black, x, y, max_width);
258 y += config.font.height;
259
260 char *bt_color = "#FFFFFF";
261 if (backtrace_done < 0) {
262 bt_color = "#AA0000";
263 } else if (backtrace_done > 0) {
264 bt_color = "#00AA00";
265 }
266 draw_util_text(message_option_backtrace, &(dialog->surface), draw_util_hex_to_color(bt_color), black, x, y, max_width);
267 y += config.font.height;
268
269 draw_util_text(message_option_restart, &(dialog->surface), white, black, x, y, max_width);
270 y += config.font.height;
271
272 draw_util_text(message_option_forget, &(dialog->surface), white, black, x, y, max_width);
273 y += config.font.height;
274}
275
276static void sighandler_handle_key_press(xcb_key_press_event_t *event) {
277 uint16_t state = event->state;
278
279 /* Apparently, after activating numlock once, the numlock modifier
280 * stays turned on (use xev(1) to verify). So, to resolve useful
281 * keysyms, we remove the numlock flag from the event state */
282 state &= ~xcb_numlock_mask;
283
284 xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state);
285
286 if (sym == 'b') {
287 DLOG("User issued core-dump command.\n");
288
289 /* fork and exec/attach GDB to the parent to get a backtrace in the
290 * tmpdir */
291 backtrace_done = sighandler_backtrace();
293 } else if (sym == 'r') {
295 i3_restart(false);
296 } else if (sym == 'f') {
298 i3_restart(true);
299 }
300}
301
302static void handle_signal(int sig, siginfo_t *info, void *data) {
303 DLOG("i3 crashed. SIG: %d\n", sig);
304
305 struct sigaction action;
306 action.sa_handler = SIG_DFL;
307 action.sa_flags = 0;
308 sigemptyset(&action.sa_mask);
309 sigaction(sig, &action, NULL);
310 raised_signal = sig;
311
314
315 xcb_generic_event_t *event;
316 /* Yay, more own eventhandlers… */
317 while ((event = xcb_wait_for_event(conn))) {
318 /* Strip off the highest bit (set if the event is generated) */
319 int type = (event->response_type & 0x7F);
320 switch (type) {
321 case XCB_KEY_PRESS:
322 sighandler_handle_key_press((xcb_key_press_event_t *)event);
323 break;
324 case XCB_EXPOSE:
325 if (((xcb_expose_event_t *)event)->count == 0) {
327 }
328
329 break;
330 }
331
332 free(event);
333 }
334}
335
336/*
337 * Configured a signal handler to gracefully handle crashes and allow the user
338 * to generate a backtrace and rescue their session.
339 *
340 */
342 struct sigaction action;
343
344 action.sa_sigaction = handle_signal;
345 action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
346 sigemptyset(&action.sa_mask);
347
348 /* Catch all signals with default action "Core", see signal(7) */
349 if (sigaction(SIGQUIT, &action, NULL) == -1 ||
350 sigaction(SIGILL, &action, NULL) == -1 ||
351 sigaction(SIGABRT, &action, NULL) == -1 ||
352 sigaction(SIGFPE, &action, NULL) == -1 ||
353 sigaction(SIGSEGV, &action, NULL) == -1)
354 ELOG("Could not setup signal handler.\n");
355}
static cmdp_state state
void i3_restart(bool forget_layout)
Restart i3 in-place appends -a to argument list to disable autostart.
Definition: util.c:286
bool path_exists(const char *path)
Checks if the given path exists by calling stat().
Definition: util.c:182
#define y(x,...)
Definition: commands.c:21
Config config
Definition: config.c:17
struct outputs_head outputs
Definition: randr.c:21
static void sighandler_destroy_dialogs(void)
Definition: sighandler.c:216
static void sighandler_create_dialogs(void)
Definition: sighandler.c:156
static void sighandler_handle_key_press(xcb_key_press_event_t *event)
Definition: sighandler.c:276
static void sighandler_setup(void)
Definition: sighandler.c:139
static void sighandler_draw_dialog(dialog_t *dialog)
Definition: sighandler.c:240
static void handle_signal(int sig, siginfo_t *info, void *data)
Definition: sighandler.c:302
static void sighandler_handle_expose(void)
Definition: sighandler.c:231
void setup_signal_handler(void)
Configured a signal handler to gracefully handle crashes and allow the user to generate a backtrace a...
Definition: sighandler.c:341
xcb_connection_t * conn
XCB connection and root screen.
Definition: main.c:44
xcb_key_symbols_t * keysyms
Definition: main.c:68
uint8_t root_depth
Definition: main.c:62
xcb_window_t root
Definition: main.c:57
xcb_screen_t * root_screen
Definition: main.c:56
char ** start_argv
Definition: main.c:42
xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t depth, xcb_visualid_t visual, uint16_t window_class, enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values)
Convenience wrapper around xcb_create_window which takes care of depth, generating an ID and checking...
Definition: xcb.c:19
xcb_visualid_t get_visualid_by_depth(uint16_t depth)
Get visualid with specified depth.
Definition: xcb.c:242
xcb_visualtype_t * get_visualtype_by_id(xcb_visualid_t visual_id)
Get visual type specified by visualid.
Definition: xcb.c:221
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:347
#define TAILQ_HEAD(name, type)
Definition: queue.h:318
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition: queue.h:376
#define TAILQ_FIRST(head)
Definition: queue.h:336
#define TAILQ_REMOVE(head, elm, field)
Definition: queue.h:402
#define TAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:324
#define TAILQ_EMPTY(head)
Definition: queue.h:344
#define TAILQ_ENTRY(type)
Definition: queue.h:327
void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_drawable_t drawable, xcb_visualtype_t *visual, int width, int height)
Initialize the surface to represent the given drawable.
struct _i3String i3String
Opaque data structure for storing strings.
Definition: libi3.h:48
void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width)
Draw the given text using libi3.
int logical_px(const int logical)
Convert a logical amount of pixels (e.g.
#define DLOG(fmt,...)
Definition: libi3.h:104
void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface)
Destroys the surface.
#define ELOG(fmt,...)
Definition: libi3.h:99
void * scalloc(size_t num, size_t size)
Safe-wrapper around calloc which exits if malloc returns NULL (meaning that there is no more memory a...
color_t draw_util_hex_to_color(const char *color)
Parses the given color in hex format to an internal color representation.
int sasprintf(char **strp, const char *fmt,...)
Safe-wrapper around asprintf which exits if it returns -1 (meaning that there is no more memory avail...
void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h)
Draws a filled rectangle.
i3String * i3string_from_utf8(const char *from_utf8)
Build an i3String from an UTF-8 encoded string.
int predict_text_width(i3String *text)
Predict the text width in pixels for the given text.
void draw_util_clear_surface(surface_t *surface, color_t color)
Clears a surface with the given color.
@ XCURSOR_CURSOR_POINTER
Definition: xcursor.h:17
#define FREE(pointer)
Definition: util.h:47
xcb_window_t id
Definition: sighandler.c:20
Rect dims
Definition: sighandler.c:22
surface_t surface
Definition: sighandler.c:23
xcb_colormap_t colormap
Definition: sighandler.c:21
i3Font font
Definition: configuration.h:98
Stores a rectangle, for example the size of a window, the child window etc.
Definition: data.h:175
uint32_t height
Definition: data.h:179
uint32_t x
Definition: data.h:176
uint32_t y
Definition: data.h:177
uint32_t width
Definition: data.h:178
An Output is a physical output on your graphics driver.
Definition: data.h:393
bool active
Whether the output is currently active (has a CRTC attached with a valid mode)
Definition: data.h:399
Rect rect
x, y, width, height
Definition: data.h:417
int height
The height of the font, built from font_ascent + font_descent.
Definition: libi3.h:67
Definition: libi3.h:413