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