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