From: Ingo Bürk Date: Thu, 23 Feb 2017 23:58:19 +0000 (+0100) Subject: Merge pull request #2649 from s3rb31/next X-Git-Tag: 4.14~49 X-Git-Url: https://git.sur5r.net/?p=i3%2Fi3;a=commitdiff_plain;h=c474ddd782782190f48c0ea045d485e7974977a0;hp=37658bd6d7066489cef38dfbb8797975d2024b2a Merge pull request #2649 from s3rb31/next layout toggle: take any combination of layouts as arguments (continuation of #2476) --- diff --git a/.github/GOVERNANCE.md b/.github/GOVERNANCE.md new file mode 100644 index 00000000..44e13345 --- /dev/null +++ b/.github/GOVERNANCE.md @@ -0,0 +1,30 @@ +# i3 project governance + +## Overview + +The i3 project uses a governance model commonly described as Benevolent +Dictator For Life (BDFL). This document outlines our understanding of what this +means. + +## Roles + +* user: anyone who interacts with the i3 project +* core contributor: a handful of people who have contributed significantly to + the project by any means (issue triage, support, documentation, code, etc.). + Core contributors are recognizable via GitHub’s “Member” badge. +* BDFL: a single individual who makes decisions when consensus cannot be + reached. i3’s current BDFL is [@stapelberg](https://github.com/stapelberg). + +## Decision making process + +In general, we try to reach consensus in discussions. In case consensus cannot +be reached, the BDFL makes a decision. + +For feature requests and code contributions specifically, the values with which +we consider them can be found on the bottom of https://i3wm.org/. These values +are not set in stone and are to be treated as guiding principles, not absolute +rules that must be followed in every case. + +## Contribution process + +Please see [CONTRIBUTING](CONTRIBUTING.md). diff --git a/docs/ipc b/docs/ipc index fda289a0..466b6596 100644 --- a/docs/ipc +++ b/docs/ipc @@ -671,6 +671,8 @@ barconfig_update (4):: binding (5):: Sent when a configured command binding is triggered with the keyboard or mouse +shutdown (6):: + Sent when the ipc shuts down because of a restart or exit by user command *Example:* -------------------------------------------------------------------- @@ -694,9 +696,9 @@ if ($is_event) { This event consists of a single serialized map containing a property +change (string)+ which indicates the type of the change ("focus", "init", -"empty", "urgent"). A +current (object)+ property will be present with the -affected workspace whenever the type of event affects a workspace (otherwise, -it will be +null). +"empty", "urgent", "reload", "rename", "restored"). A +current (object)+ +property will be present with the affected workspace whenever the type of event +affects a workspace (otherwise, it will be +null). When the change is "focus", an +old (object)+ property will be present with the previous workspace. When the first switch occurs (when i3 focuses the @@ -829,6 +831,20 @@ input_type (string):: } --------------------------- +=== shutdown event + +This event is triggered when the connection to the ipc is about to shutdown +because of a user action such as a +restart+ or +exit+ command. The +change +(string)+ field indicates why the ipc is shutting down. It can be either ++"restart"+ or +"exit"+. + +*Example:* +--------------------------- +{ + "change": "restart" +} +--------------------------- + == See also (existing libraries) [[libraries]] diff --git a/docs/layout-saving b/docs/layout-saving index 5897036e..6ca08fa2 100644 --- a/docs/layout-saving +++ b/docs/layout-saving @@ -259,3 +259,27 @@ container: ] } -------------------------------------------------------------------------------- + +=== Placeholders using window title matches don't swallow the window + +If you use the +title+ attribute to match a window and find that it doesn't +work or only works sometimes, the reason might be that the application sets the +title only after making the window visible. This will be especially true for +programs running inside terminal emulators, e.g., +urxvt -e irssi+ when +matching on +title: "irssi"+. + +One way to deal with this is to not rely on the title, but instead use, e.g., +the +instance+ attribute and running the program to set this window instance to +that value: + +-------------------------------------------------------------------------------- +# Run irssi via +# urxvt -name "irssi-container" -e irssi + +"swallows": [ + { + "class": "URxvt", + "instance": "irssi-container" + } +] +-------------------------------------------------------------------------------- diff --git a/docs/userguide b/docs/userguide index 21d08625..40e9e3b9 100644 --- a/docs/userguide +++ b/docs/userguide @@ -479,7 +479,7 @@ mode *Example*: ------------------------------------------------------------------------ -# Press $mod+o followed by either f, t, Esc or Return to launch firefox, +# Press $mod+o followed by either f, t, Escape or Return to launch firefox, # thunderbird or return to the default mode, respectively. set $mode_launcher Launch: [f]irefox [t]hunderbird bindsym $mod+o mode "$mode_launcher" @@ -488,7 +488,7 @@ mode "$mode_launcher" { bindsym f exec firefox bindsym t exec thunderbird - bindsym Esc mode "default" + bindsym Escape mode "default" bindsym Return mode "default" } ------------------------------------------------------------------------ @@ -946,12 +946,12 @@ the next section. === Focus follows mouse -By default, window focus follows your mouse movements. However, if you have a -setup where your mouse usually is in your way (like a touchpad on your laptop -which you do not want to disable completely), you might want to disable 'focus -follows mouse' and control focus only by using your keyboard. The mouse will -still be useful inside the currently active window (for example to click on -links in your browser window). +By default, window focus follows your mouse movements as the mouse crosses +window borders. However, if you have a setup where your mouse usually is in your +way (like a touchpad on your laptop which you do not want to disable +completely), you might want to disable 'focus follows mouse' and control focus +only by using your keyboard. The mouse will still be useful inside the +currently active window (for example to click on links in your browser window). *Syntax*: -------------------------- @@ -2106,6 +2106,23 @@ i3-msg 'rename workspace to "2: mail"' bindsym $mod+r exec i3-input -F 'rename workspace to "%s"' -P 'New name: ' -------------------------------------------------------------------------- +If you want to rename workspaces on demand while keeping the navigation stable, +you can use a setup like this: + +*Example*: +------------------------- +bindsym $mod+1 workspace number "1: www" +bindsym $mod+2 workspace number "2: mail" +... +------------------------- + +If a workspace does not exist, the command +workspace number "1: mail"+ will +create workspace "1: mail". + +If a workspace with number 1 does already exist, the command will switch to this +workspace and ignore the text part. So even when the workspace has been renamed +to "1: web", the above command will still switch to it. + === Moving workspaces to a different screen See <> for how to move a container/workspace to a different diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index 8eec941c..dd58fd12 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -69,10 +69,16 @@ #include "xcb.h" #include "libi3.h" +#define TEXT_PADDING logical_px(4) +#define WIN_POS_X logical_px(490) +#define WIN_POS_Y logical_px(297) +#define WIN_WIDTH logical_px(300) +#define WIN_HEIGHT (15 * font.height + TEXT_PADDING) + +#define col_x(col) \ + (((col)-1) * char_width + TEXT_PADDING) #define row_y(row) \ - (((row)-1) * font.height + logical_px(4)) -#define window_height() \ - (row_y(15) + font.height) + (((row)-1) * font.height + TEXT_PADDING) enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME; @@ -90,8 +96,7 @@ static i3Font bold_font; static int char_width; static char *socket_path; static xcb_window_t win; -static xcb_pixmap_t pixmap; -static xcb_gcontext_t pixmap_gc; +static surface_t surface; static xcb_key_symbols_t *symbols; xcb_window_t root; static struct xkb_keymap *xkb_keymap; @@ -463,82 +468,73 @@ void errorlog(char *fmt, ...) { void debuglog(char *fmt, ...) { } +static void txt(int col, int row, char *text, color_t fg, color_t bg) { + int x = col_x(col); + int y = row_y(row); + i3String *string = i3string_from_utf8(text); + draw_util_text(string, &surface, fg, bg, x, y, WIN_WIDTH - x - TEXT_PADDING); + i3string_free(string); +} + /* * Handles expose events, that is, draws the window contents. * */ static int handle_expose() { - /* re-draw the background */ - xcb_rectangle_t border = {0, 0, logical_px(300), window_height()}; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); + const color_t black = draw_util_hex_to_color("#000000"); + const color_t white = draw_util_hex_to_color("#FFFFFF"); + const color_t green = draw_util_hex_to_color("#00FF00"); + const color_t red = draw_util_hex_to_color("#FF0000"); - set_font(&font); + /* draw background */ + draw_util_clear_surface(&surface, black); -#define txt(x, row, text) \ - draw_text_ascii(text, pixmap, pixmap_gc, \ - x, row_y(row), logical_px(500) - x * 2) + set_font(&font); if (current_step == STEP_WELCOME) { - /* restore font color */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); - - txt(logical_px(10), 2, "You have not configured i3 yet."); - txt(logical_px(10), 3, "Do you want me to generate a config at"); + txt(2, 2, "You have not configured i3 yet.", white, black); + txt(2, 3, "Do you want me to generate a config at", white, black); char *msg; sasprintf(&msg, "%s?", config_path); - txt(logical_px(10), 4, msg); + txt(2, 4, msg, white, black); free(msg); - txt(logical_px(85), 6, "Yes, generate the config"); - txt(logical_px(85), 8, "No, I will use the defaults"); + txt(13, 6, "Yes, generate the config", white, black); + txt(13, 8, "No, I will use the defaults", white, black); - /* green */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000")); - txt(logical_px(25), 6, ""); + txt(4, 6, "", green, black); - /* red */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000")); - txt(logical_px(31), 8, ""); + txt(5, 8, "", red, black); } if (current_step == STEP_GENERATE) { - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); - - txt(logical_px(10), 2, "Please choose either:"); - txt(logical_px(85), 4, "Win as default modifier"); - txt(logical_px(85), 5, "Alt as default modifier"); - txt(logical_px(10), 7, "Afterwards, press"); - txt(logical_px(85), 9, "to write the config"); - txt(logical_px(85), 10, "to abort"); + txt(2, 2, "Please choose either:", white, black); + txt(13, 4, "Win as default modifier", white, black); + txt(13, 5, "Alt as default modifier", white, black); + txt(2, 7, "Afterwards, press", white, black); + txt(13, 9, "to write the config", white, black); + txt(13, 10, "to abort", white, black); /* the not-selected modifier */ if (modifier == MOD_Mod4) - txt(logical_px(31), 5, ""); + txt(5, 5, "", white, black); else - txt(logical_px(31), 4, ""); + txt(5, 4, "", white, black); /* the selected modifier */ set_font(&bold_font); - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); if (modifier == MOD_Mod4) - txt(logical_px(10), 4, "-> "); + txt(2, 4, "-> ", white, black); else - txt(logical_px(10), 5, "-> "); + txt(2, 5, "-> ", white, black); - /* green */ set_font(&font); - set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000")); - txt(logical_px(25), 9, ""); + txt(4, 9, "", green, black); - /* red */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000")); - txt(logical_px(31), 10, ""); + txt(5, 10, "", red, black); } - /* Copy the contents of the pixmap to the real window */ - xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), logical_px(500)); xcb_flush(conn); return 1; @@ -625,8 +621,7 @@ static void handle_button_press(xcb_button_press_event_t *event) { if (current_step != STEP_GENERATE) return; - if (event->event_x < logical_px(32) || - event->event_x > (logical_px(32) + char_width * 5)) + if (event->event_x < col_x(5) || event->event_x > col_x(10)) return; if (event->event_y >= row_y(4) && event->event_y <= (row_y(4) + font.height)) { @@ -867,10 +862,10 @@ int main(int argc, char *argv[]) { xcb_create_window( conn, XCB_COPY_FROM_PARENT, - win, /* the window id */ - root, /* parent == root */ - logical_px(490), logical_px(297), logical_px(300), window_height(), /* dimensions */ - 0, /* X11 border = 0, we draw our own */ + win, /* the window id */ + root, /* parent == root */ + WIN_POS_X, WIN_POS_Y, WIN_WIDTH, WIN_HEIGHT, /* dimensions */ + 0, /* X11 border = 0, we draw our own */ XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, @@ -915,11 +910,8 @@ int main(int argc, char *argv[]) { strlen("i3: first configuration"), "i3: first configuration"); - /* Create pixmap */ - pixmap = xcb_generate_id(conn); - pixmap_gc = xcb_generate_id(conn); - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), logical_px(500)); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + /* Initialize drawable surface */ + draw_util_surface_init(conn, &surface, win, get_visualtype(root_screen), WIN_WIDTH, WIN_HEIGHT); /* Grab the keyboard to get all input */ xcb_flush(conn); @@ -976,5 +968,8 @@ int main(int argc, char *argv[]) { free(event); } + /* Dismiss drawable surface */ + draw_util_surface_free(conn, &surface); + return 0; } diff --git a/i3-input/main.c b/i3-input/main.c index 6d1e3378..785a133f 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -33,6 +33,10 @@ #include "i3-input.h" +#define MAX_WIDTH logical_px(500) +#define BORDER logical_px(2) +#define PADDING logical_px(2) + /* IPC format string. %s will be replaced with what the user entered, then * the command will be sent to i3 */ static char *format; @@ -42,8 +46,7 @@ static int sockfd; static xcb_key_symbols_t *symbols; static bool modeswitch_active = false; static xcb_window_t win; -static xcb_pixmap_t pixmap; -static xcb_gcontext_t pixmap_gc; +static surface_t surface; static xcb_char2b_t glyphs_ucs[512]; static char *glyphs_utf8[512]; static int input_position; @@ -109,30 +112,30 @@ static uint8_t *concat_strings(char **glyphs, int max) { static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) { printf("expose!\n"); - /* re-draw the background */ - xcb_rectangle_t border = {0, 0, logical_px(500), font.height + logical_px(8)}, - inner = {logical_px(2), logical_px(2), logical_px(496), font.height + logical_px(8) - logical_px(4)}; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); + color_t border_color = draw_util_hex_to_color("#FF0000"); + color_t fg_color = draw_util_hex_to_color("#FFFFFF"); + color_t bg_color = draw_util_hex_to_color("#000000"); + + int text_offset = BORDER + PADDING; - /* restore font color */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); + /* draw border */ + draw_util_rectangle(&surface, border_color, 0, 0, surface.width, surface.height); + + /* draw background */ + draw_util_rectangle(&surface, bg_color, BORDER, BORDER, surface.width - 2 * BORDER, surface.height - 2 * BORDER); /* draw the prompt … */ if (prompt != NULL) { - draw_text(prompt, pixmap, pixmap_gc, NULL, logical_px(4), logical_px(4), logical_px(492)); + draw_util_text(prompt, &surface, fg_color, bg_color, text_offset, text_offset, MAX_WIDTH - text_offset); } + /* … and the text */ if (input_position > 0) { i3String *input = i3string_from_ucs2(glyphs_ucs, input_position); - draw_text(input, pixmap, pixmap_gc, NULL, prompt_offset + logical_px(4), logical_px(4), logical_px(492)); + draw_util_text(input, &surface, fg_color, bg_color, text_offset + prompt_offset, text_offset, MAX_WIDTH - text_offset); i3string_free(input); } - /* Copy the contents of the pixmap to the real window */ - xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), font.height + logical_px(8)); xcb_flush(conn); return 1; @@ -287,7 +290,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press } static xcb_rectangle_t get_window_position(void) { - xcb_rectangle_t result = (xcb_rectangle_t){logical_px(50), logical_px(50), logical_px(500), font.height + logical_px(8)}; + xcb_rectangle_t result = (xcb_rectangle_t){logical_px(50), logical_px(50), MAX_WIDTH, font.height + 2 * BORDER + 2 * PADDING}; xcb_get_property_reply_t *supporting_wm_reply = NULL; xcb_get_input_focus_reply_t *input_focus = NULL; @@ -448,6 +451,7 @@ int main(int argc, char *argv[]) { symbols = xcb_key_symbols_alloc(conn); + init_dpi(); font = load_font(pattern, true); set_font(&font); @@ -476,11 +480,8 @@ int main(int argc, char *argv[]) { /* Map the window (make it visible) */ xcb_map_window(conn, win); - /* Create pixmap */ - pixmap = xcb_generate_id(conn); - pixmap_gc = xcb_generate_id(conn); - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), font.height + logical_px(8)); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + /* Initialize the drawable surface */ + draw_util_surface_init(conn, &surface, win, get_visualtype(root_screen), win_pos.width, win_pos.height); /* Grab the keyboard to get all input */ xcb_flush(conn); @@ -535,5 +536,6 @@ int main(int argc, char *argv[]) { free(event); } + draw_util_surface_free(conn, &surface); return 0; } diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index eb25e9cb..7d38f731 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -38,6 +38,13 @@ * constant for that. */ #define XCB_CURSOR_LEFT_PTR 68 +#define MSG_PADDING logical_px(8) +#define BTN_PADDING logical_px(3) +#define BTN_BORDER logical_px(3) +#define BTN_GAP logical_px(20) +#define CLOSE_BTN_GAP logical_px(15) +#define BAR_BORDER logical_px(2) + static char *argv0 = NULL; typedef struct { @@ -48,11 +55,12 @@ typedef struct { } button_t; static xcb_window_t win; -static xcb_pixmap_t pixmap; -static xcb_gcontext_t pixmap_gc; -static xcb_rectangle_t rect = {0, 0, 600, 20}; +static surface_t bar; + static i3Font font; static i3String *prompt; + +static button_t btn_close; static button_t *buttons; static int buttoncnt; @@ -138,7 +146,7 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve printf("button released on x = %d, y = %d\n", event->event_x, event->event_y); /* If the user hits the close button, we exit(0) */ - if (event->event_x >= (rect.width - logical_px(32))) + if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width) exit(0); button_t *button = get_button_at(event->event_x, event->event_y); if (!button) @@ -190,108 +198,64 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve /* TODO: unset flag, re-render */ } +/* + * Draws a button and returns its width + * + */ +static int button_draw(button_t *button, int position) { + int text_width = predict_text_width(button->label); + button->width = text_width + 2 * BTN_PADDING + 2 * BTN_BORDER; + button->x = position - button->width; + + /* draw border */ + draw_util_rectangle(&bar, color_border, + position - button->width, + MSG_PADDING - BTN_PADDING - BTN_BORDER, + button->width, + font.height + 2 * BTN_PADDING + 2 * BTN_BORDER); + /* draw background */ + draw_util_rectangle(&bar, color_button_background, + position - button->width + BTN_BORDER, + MSG_PADDING - BTN_PADDING, + text_width + 2 * BTN_PADDING, + font.height + 2 * BTN_PADDING); + /* draw label */ + draw_util_text(button->label, &bar, color_text, color_button_background, + position - button->width + BTN_BORDER + BTN_PADDING, + MSG_PADDING, + 200); + return button->width; +} + /* * Handles expose events (redraws of the window) and rendering in general. Will * be called from the code with event == NULL or from X with event != NULL. * */ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { - /* re-draw the background */ - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_background.colorpixel}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect); + /* draw background */ + draw_util_clear_surface(&bar, color_background); + /* draw message */ + draw_util_text(prompt, &bar, color_text, color_background, + MSG_PADDING, MSG_PADDING, + bar.width - 2 * MSG_PADDING); - /* restore font color */ - set_font_colors(pixmap_gc, color_text, color_background); - draw_text(prompt, pixmap, pixmap_gc, NULL, - logical_px(4) + logical_px(4), - logical_px(4) + logical_px(4), - rect.width - logical_px(4) - logical_px(4)); + int position = bar.width - (MSG_PADDING - BTN_BORDER - BTN_PADDING); /* render close button */ - const char *close_button_label = "X"; - int line_width = logical_px(4); - /* set width to the width of the label */ - int w = predict_text_width(i3string_from_utf8(close_button_label)); - /* account for left/right padding, which seems to be set to 8px (total) below */ - w += logical_px(8); - int y = rect.width; - uint32_t values[3]; - values[0] = color_button_background.colorpixel; - values[1] = line_width; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); - - xcb_rectangle_t close = {y - w - (2 * line_width), 0, w + (2 * line_width), rect.height}; - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); - - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel}); - xcb_point_t points[] = { - {y - w - (2 * line_width), line_width / 2}, - {y - (line_width / 2), line_width / 2}, - {y - (line_width / 2), (rect.height - (line_width / 2)) - logical_px(2)}, - {y - w - (2 * line_width), (rect.height - (line_width / 2)) - logical_px(2)}, - {y - w - (2 * line_width), line_width / 2}}; - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points); - - values[0] = 1; - set_font_colors(pixmap_gc, color_text, color_button_background); - /* the x term here seems to set left/right padding */ - draw_text_ascii(close_button_label, pixmap, pixmap_gc, - y - w - line_width + w / 2 - logical_px(4), - logical_px(4) + logical_px(3), - rect.width - y + w + line_width - w / 2 + logical_px(4)); - y -= w; - - y -= logical_px(20); + position -= button_draw(&btn_close, position); + position -= CLOSE_BTN_GAP; /* render custom buttons */ - line_width = 1; - for (int c = 0; c < buttoncnt; c++) { - /* set w to the width of the label */ - w = predict_text_width(buttons[c].label); - /* account for left/right padding, which seems to be set to 12px (total) below */ - w += logical_px(12); - y -= logical_px(30); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_button_background.colorpixel}); - close = (xcb_rectangle_t){y - w - (2 * line_width), logical_px(2), w + (2 * line_width), rect.height - logical_px(6)}; - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); - - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel}); - buttons[c].x = y - w - (2 * line_width); - buttons[c].width = w; - xcb_point_t points2[] = { - {y - w - (2 * line_width), (line_width / 2) + logical_px(2)}, - {y - (line_width / 2), (line_width / 2) + logical_px(2)}, - {y - (line_width / 2), (rect.height - logical_px(4) - (line_width / 2))}, - {y - w - (2 * line_width), (rect.height - logical_px(4) - (line_width / 2))}, - {y - w - (2 * line_width), (line_width / 2) + logical_px(2)}}; - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2); - - values[0] = color_text.colorpixel; - values[1] = color_button_background.colorpixel; - set_font_colors(pixmap_gc, color_text, color_button_background); - /* the x term seems to set left/right padding */ - draw_text(buttons[c].label, pixmap, pixmap_gc, NULL, - y - w - line_width + logical_px(6), - logical_px(4) + logical_px(3), - rect.width - y + w + line_width - logical_px(6)); - - y -= w; + for (int i = 0; i < buttoncnt; i++) { + position -= BTN_GAP; + position -= button_draw(&buttons[i], position); } /* border line at the bottom */ - line_width = logical_px(2); - values[0] = color_border_bottom.colorpixel; - values[1] = line_width; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); - xcb_point_t bottom[] = { - {0, rect.height - 0}, - {rect.width, rect.height - 0}}; - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 2, bottom); - - /* Copy the contents of the pixmap to the real window */ - xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, rect.width, rect.height); - xcb_flush(conn); + draw_util_rectangle(&bar, color_border_bottom, 0, bar.height - BAR_BORDER, bar.width, BAR_BORDER); + xcb_flush(conn); return 1; } @@ -301,7 +265,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { */ static xcb_rectangle_t get_window_position(void) { /* Default values if we cannot determine the primary output or its CRTC info. */ - xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + logical_px(8) + logical_px(8)}; + xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER}; xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root); xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root); @@ -438,6 +402,8 @@ int main(int argc, char *argv[]) { } } + btn_close.label = i3string_from_utf8("X"); + int screens; if ((conn = xcb_connect(NULL, &screens)) == NULL || xcb_connection_has_error(conn)) @@ -575,11 +541,8 @@ int main(int argc, char *argv[]) { 12, &strut_partial); - /* Create pixmap */ - pixmap = xcb_generate_id(conn); - pixmap_gc = xcb_generate_id(conn); - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + logical_px(8)); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + /* Initialize the drawable bar */ + draw_util_surface_init(conn, &bar, win, get_visualtype(root_screen), win_pos.width, win_pos.height); /* Grab the keyboard to get all input */ xcb_flush(conn); @@ -612,18 +575,7 @@ int main(int argc, char *argv[]) { case XCB_CONFIGURE_NOTIFY: { xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event; - rect = (xcb_rectangle_t){ - configure_notify->x, - configure_notify->y, - configure_notify->width, - configure_notify->height}; - - /* Recreate the pixmap / gc */ - xcb_free_pixmap(conn, pixmap); - xcb_free_gc(conn, pixmap_gc); - - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, rect.width, rect.height); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height); break; } } @@ -632,6 +584,7 @@ int main(int argc, char *argv[]) { } FREE(pattern); + draw_util_surface_free(conn, &bar); return 0; } diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 4a090ad7..cc5074e5 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -178,6 +178,11 @@ void got_bar_config_update(char *event) { init_xcb_late(config.fontname); init_colors(&(config.colors)); + /* restart status command process */ + kill_child(); + start_child(config.command); + FREE(config.command); + draw_bars(false); } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index edef9b7e..24f91642 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -531,7 +531,8 @@ void handle_button(xcb_button_press_event_t *event) { return; } switch (event->detail) { - case 4: + case XCB_BUTTON_SCROLL_UP: + case XCB_BUTTON_SCROLL_LEFT: /* Mouse wheel up. We select the previous ws, if any. * If there is no more workspace, don’t even send the workspace * command, otherwise (with workspace auto_back_and_forth) we’d end @@ -541,7 +542,8 @@ void handle_button(xcb_button_press_event_t *event) { cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq); break; - case 5: + case XCB_BUTTON_SCROLL_DOWN: + case XCB_BUTTON_SCROLL_RIGHT: /* Mouse wheel down. We select the next ws, if any. * If there is no more workspace, don’t even send the workspace * command, otherwise (with workspace auto_back_and_forth) we’d end diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 98ac35b0..249cc32e 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -91,3 +91,6 @@ typedef struct i3_ipc_header { /** The binding event will be triggered when bindings run */ #define I3_IPC_EVENT_BINDING (I3_IPC_EVENT_MASK | 5) + +/** The shutdown event will be triggered when the ipc shuts down */ +#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6) diff --git a/include/ipc.h b/include/ipc.h index 7ff4704c..7ffbf7a8 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -77,11 +77,18 @@ int ipc_create_socket(const char *filename); void ipc_send_event(const char *event, uint32_t message_type, const char *payload); /** - * Calls shutdown() on each socket and closes it. This function to be called - * when exiting or restarting only! + * Calls to ipc_shutdown() should provide a reason for the shutdown. + */ +typedef enum { + SHUTDOWN_REASON_RESTART, + SHUTDOWN_REASON_EXIT +} shutdown_reason_t; + +/** + * Calls shutdown() on each socket and closes it. * */ -void ipc_shutdown(void); +void ipc_shutdown(shutdown_reason_t reason); void dump_node(yajl_gen gen, Con *con, bool inplace_restart); diff --git a/include/libi3.h b/include/libi3.h index d33d6c71..dbb29e1f 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -24,6 +24,16 @@ #define DEFAULT_DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) +/** Mouse buttons */ +#define XCB_BUTTON_CLICK_LEFT XCB_BUTTON_INDEX_1 +#define XCB_BUTTON_CLICK_MIDDLE XCB_BUTTON_INDEX_2 +#define XCB_BUTTON_CLICK_RIGHT XCB_BUTTON_INDEX_3 +#define XCB_BUTTON_SCROLL_UP XCB_BUTTON_INDEX_4 +#define XCB_BUTTON_SCROLL_DOWN XCB_BUTTON_INDEX_5 +/* xcb doesn't define constants for these. */ +#define XCB_BUTTON_SCROLL_LEFT 6 +#define XCB_BUTTON_SCROLL_RIGHT 7 + /** * XCB connection and root screen * diff --git a/src/click.c b/src/click.c index 913741b4..e989b88d 100644 --- a/src/click.c +++ b/src/click.c @@ -178,8 +178,8 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod if (con->parent->type == CT_DOCKAREA) goto done; - const bool is_left_or_right_click = (event->detail == XCB_BUTTON_INDEX_1 || - event->detail == XCB_BUTTON_INDEX_3); + const bool is_left_or_right_click = (event->detail == XCB_BUTTON_CLICK_LEFT || + event->detail == XCB_BUTTON_CLICK_RIGHT); /* if the user has bound an action to this click, it should override the * default behavior. */ @@ -228,8 +228,10 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */ if (in_stacked && dest == CLICK_DECORATION && - (event->detail == XCB_BUTTON_INDEX_4 || - event->detail == XCB_BUTTON_INDEX_5)) { + (event->detail == XCB_BUTTON_SCROLL_UP || + event->detail == XCB_BUTTON_SCROLL_DOWN || + event->detail == XCB_BUTTON_SCROLL_LEFT || + event->detail == XCB_BUTTON_SCROLL_RIGHT)) { DLOG("Scrolling on a window decoration\n"); orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ); /* Focus the currently focused container on the same level that the @@ -244,10 +246,12 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod * #557), we first check if scrolling is possible at all. */ bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL); bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL); - if (event->detail == XCB_BUTTON_INDEX_4 && scroll_prev_possible) + if ((event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) && scroll_prev_possible) { tree_next('p', orientation); - else if (event->detail == XCB_BUTTON_INDEX_5 && scroll_next_possible) + } else if ((event->detail == XCB_BUTTON_SCROLL_DOWN || event->detail == XCB_BUTTON_SCROLL_RIGHT) && scroll_next_possible) { tree_next('n', orientation); + } + goto done; } @@ -261,7 +265,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod floating_raise_con(floatingcon); /* 4: floating_modifier plus left mouse button drags */ - if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1) { + if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) { floating_drag_window(floatingcon, event); return 1; } @@ -269,7 +273,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod /* 5: resize (floating) if this was a (left or right) click on the * left/right/bottom border, or a right click on the decoration. * also try resizing (tiling) if it was a click on the top */ - if (mod_pressed && event->detail == XCB_BUTTON_INDEX_3) { + if (mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) { DLOG("floating resize due to floatingmodifier\n"); floating_resize_window(floatingcon, proportional, event); return 1; @@ -283,7 +287,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod goto done; } - if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_INDEX_3) { + if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_CLICK_RIGHT) { DLOG("floating resize due to decoration right click\n"); floating_resize_window(floatingcon, proportional, event); return 1; @@ -298,7 +302,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod /* 6: dragging, if this was a click on a decoration (which did not lead * to a resize) */ if (!in_stacked && dest == CLICK_DECORATION && - (event->detail == XCB_BUTTON_INDEX_1)) { + (event->detail == XCB_BUTTON_CLICK_LEFT)) { floating_drag_window(floatingcon, event); return 1; } @@ -313,7 +317,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod } /* 7: floating modifier pressed, initiate a resize */ - if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_INDEX_3) { + if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) { if (floating_mod_on_tiled_client(con, event)) return 1; } diff --git a/src/commands.c b/src/commands.c index 44a5d8a4..33737f71 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1549,7 +1549,7 @@ void cmd_exit(I3_CMD) { #ifdef I3_ASAN_ENABLED __lsan_do_leak_check(); #endif - ipc_shutdown(); + ipc_shutdown(SHUTDOWN_REASON_EXIT); unlink(config.ipc_socket_path); xcb_disconnect(conn); exit(0); @@ -1582,7 +1582,7 @@ void cmd_reload(I3_CMD) { */ void cmd_restart(I3_CMD) { LOG("restarting i3\n"); - ipc_shutdown(); + ipc_shutdown(SHUTDOWN_REASON_RESTART); unlink(config.ipc_socket_path); /* We need to call this manually since atexit handlers don’t get called * when exec()ing */ diff --git a/src/handlers.c b/src/handlers.c index 315688c4..9fb9040e 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -614,12 +614,9 @@ static void handle_expose_event(xcb_expose_event_t *event) { } /* Since we render to our surface on every change anyways, expose events - * only tell us that the X server lost (parts of) the window contents. We - * can handle that by copying the appropriate part from our surface to the - * window. */ + * only tell us that the X server lost (parts of) the window contents. */ draw_util_copy_surface(&(parent->frame_buffer), &(parent->frame), - event->x, event->y, event->x, event->y, - event->width, event->height); + 0, 0, 0, 0, parent->rect.width, parent->rect.height); xcb_flush(conn); return; } diff --git a/src/ipc.c b/src/ipc.c index db2fa362..bb20b340 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -62,11 +62,39 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa } /* - * Calls shutdown() on each socket and closes it. This function to be called + * For shutdown events, we send the reason for the shutdown. + */ +static void ipc_send_shutdown_event(shutdown_reason_t reason) { + yajl_gen gen = ygenalloc(); + y(map_open); + + ystr("change"); + + if (reason == SHUTDOWN_REASON_RESTART) { + ystr("restart"); + } else if (reason == SHUTDOWN_REASON_EXIT) { + ystr("exit"); + } + + y(map_close); + + const unsigned char *payload; + ylength length; + + y(get_buf, &payload, &length); + ipc_send_event("shutdown", I3_IPC_EVENT_SHUTDOWN, (const char *)payload); + + y(free); +} + +/* + * Calls shutdown() on each socket and closes it. This function is to be called * when exiting or restarting only! * */ -void ipc_shutdown(void) { +void ipc_shutdown(shutdown_reason_t reason) { + ipc_send_shutdown_event(reason); + ipc_client *current; while (!TAILQ_EMPTY(&all_clients)) { current = TAILQ_FIRST(&all_clients); diff --git a/src/restore_layout.c b/src/restore_layout.c index 9edf4b11..d567e971 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -15,6 +15,8 @@ #include #endif +#define TEXT_PADDING logical_px(2) + typedef struct placeholder_state { /** The X11 placeholder window. */ xcb_window_t window; @@ -24,10 +26,8 @@ typedef struct placeholder_state { /** Current size of the placeholder window (to detect size changes). */ Rect rect; - /** The pixmap to render on (back buffer). */ - xcb_pixmap_t pixmap; - /** The graphics context for “pixmap”. */ - xcb_gcontext_t gc; + /** The drawable surface */ + surface_t surface; TAILQ_ENTRY(placeholder_state) state; @@ -138,17 +138,15 @@ void restore_connect(void) { } static void update_placeholder_contents(placeholder_state *state) { - xcb_change_gc(restore_conn, state->gc, XCB_GC_FOREGROUND, - (uint32_t[]){config.client.placeholder.background.colorpixel}); - xcb_poly_fill_rectangle(restore_conn, state->pixmap, state->gc, 1, - (xcb_rectangle_t[]){{0, 0, state->rect.width, state->rect.height}}); + const color_t foreground = config.client.placeholder.text; + const color_t background = config.client.placeholder.background; + + draw_util_clear_surface(&(state->surface), background); // TODO: make i3font functions per-connection, at least these two for now…? xcb_flush(restore_conn); xcb_aux_sync(restore_conn); - set_font_colors(state->gc, config.client.placeholder.text, config.client.placeholder.background); - Match *swallows; int n = 0; TAILQ_FOREACH(swallows, &(state->con->swallow_head), matches) { @@ -175,7 +173,10 @@ static void update_placeholder_contents(placeholder_state *state) { DLOG("con %p (placeholder 0x%08x) line %d: %s\n", state->con, state->window, n, serialized); i3String *str = i3string_from_utf8(serialized); - draw_text(str, state->pixmap, state->gc, NULL, 2, (n * (config.font.height + 2)) + 2, state->rect.width - 2); + draw_util_text(str, &(state->surface), foreground, background, + TEXT_PADDING, + (n * (config.font.height + TEXT_PADDING)) + TEXT_PADDING, + state->rect.width - 2 * TEXT_PADDING); i3string_free(str); n++; free(serialized); @@ -186,7 +187,7 @@ static void update_placeholder_contents(placeholder_state *state) { int text_width = predict_text_width(line); int x = (state->rect.width / 2) - (text_width / 2); int y = (state->rect.height / 2) - (config.font.height / 2); - draw_text(line, state->pixmap, state->gc, NULL, x, y, text_width); + draw_util_text(line, &(state->surface), foreground, background, x, y, text_width); i3string_free(line); xcb_flush(conn); xcb_aux_sync(conn); @@ -228,11 +229,8 @@ static void open_placeholder_window(Con *con) { state->window = placeholder; state->con = con; state->rect = con->rect; - state->pixmap = xcb_generate_id(restore_conn); - xcb_create_pixmap(restore_conn, root_depth, state->pixmap, - state->window, state->rect.width, state->rect.height); - state->gc = xcb_generate_id(restore_conn); - xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0}); + + draw_util_surface_init(conn, &(state->surface), placeholder, get_visualtype(root_screen), state->rect.width, state->rect.height); update_placeholder_contents(state); TAILQ_INSERT_TAIL(&state_head, state, state); @@ -286,8 +284,7 @@ bool restore_kill_placeholder(xcb_window_t placeholder) { continue; xcb_destroy_window(restore_conn, state->window); - xcb_free_pixmap(restore_conn, state->pixmap); - xcb_free_gc(restore_conn, state->gc); + draw_util_surface_free(restore_conn, &(state->surface)); TAILQ_REMOVE(&state_head, state, state); free(state); DLOG("placeholder window 0x%08x destroyed.\n", placeholder); @@ -306,14 +303,8 @@ static void expose_event(xcb_expose_event_t *event) { DLOG("refreshing window 0x%08x contents (con %p)\n", state->window, state->con); - /* Since we render to our pixmap on every change anyways, expose events - * only tell us that the X server lost (parts of) the window contents. We - * can handle that by copying the appropriate part from our pixmap to the - * window. */ - xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc, - event->x, event->y, event->x, event->y, - event->width, event->height); - xcb_flush(restore_conn); + update_placeholder_contents(state); + return; } @@ -338,19 +329,10 @@ static void configure_notify(xcb_configure_notify_event_t *event) { state->rect.width = event->width; state->rect.height = event->height; - xcb_free_pixmap(restore_conn, state->pixmap); - xcb_free_gc(restore_conn, state->gc); - - state->pixmap = xcb_generate_id(restore_conn); - xcb_create_pixmap(restore_conn, root_depth, state->pixmap, - state->window, state->rect.width, state->rect.height); - state->gc = xcb_generate_id(restore_conn); - xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0}); + draw_util_surface_set_size(&(state->surface), state->rect.width, state->rect.height); update_placeholder_contents(state); - xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc, - 0, 0, 0, 0, state->rect.width, state->rect.height); - xcb_flush(restore_conn); + return; } diff --git a/src/util.c b/src/util.c index 0289ded9..06fbea2a 100644 --- a/src/util.c +++ b/src/util.c @@ -287,7 +287,7 @@ void i3_restart(bool forget_layout) { restore_geometry(); - ipc_shutdown(); + ipc_shutdown(SHUTDOWN_REASON_RESTART); LOG("restarting \"%s\"...\n", start_argv[0]); /* make sure -a is in the argument list or add it */ diff --git a/testcases/complete-run.pl.in b/testcases/complete-run.pl.in index d872bda1..2019253c 100755 --- a/testcases/complete-run.pl.in +++ b/testcases/complete-run.pl.in @@ -87,6 +87,17 @@ foreach my $binary (@binaries) { die "$binary is not an executable" unless -x $binary; } +my @test_binaries = qw( + @abs_top_builddir@/test.commands_parser + @abs_top_builddir@/test.config_parser + @abs_top_builddir@/test.inject_randr15 + ); + +foreach my $binary (@test_binaries) { + die "$binary executable not found, did you run “make check”?" unless -e $binary; + die "$binary is not an executable" unless -x $binary; +} + $ENV{PATH} = join(':', '@abs_top_builddir@/i3-nagbar', '@abs_top_builddir@/i3-msg', diff --git a/testcases/t/264-ipc-shutdown-event.t b/testcases/t/264-ipc-shutdown-event.t new file mode 100644 index 00000000..379b9bf2 --- /dev/null +++ b/testcases/t/264-ipc-shutdown-event.t @@ -0,0 +1,71 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test the ipc shutdown event. This event is triggered when the connection to +# the ipc is about to shutdown because of a user action such as with a +# `restart` or `exit` command. The `change` field indicates why the ipc is +# shutting down. It can be either "restart" or "exit". +# +# Ticket: #2318 +# Bug still in: 4.12-46-g2123888 +use i3test; + +SKIP: { + skip "AnyEvent::I3 too old (need >= 0.17)", 1 if $AnyEvent::I3::VERSION < 0.17; + +my $i3 = i3(get_socket_path()); +$i3->connect->recv; + +my $cv = AE::cv; +my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; + +$i3->subscribe({ + shutdown => sub { + $cv->send(shift); + } + })->recv; + +cmd 'restart'; + +my $e = $cv->recv; + +diag "Event:\n", Dumper($e); +ok($e, 'the shutdown event should emit when the ipc is restarted by command'); +is($e->{change}, 'restart', 'the `change` field should tell the reason for the shutdown'); + +# restarting kills the ipc client so we have to make a new one +$i3 = i3(get_socket_path()); +$i3->connect->recv; + +$cv = AE::cv; +$timer = AE::timer 0.5, 0, sub { $cv->send(0); }; + +$i3->subscribe({ + shutdown => sub { + $cv->send(shift); + } + })->recv; + +cmd 'exit'; + +$e = $cv->recv; + +diag "Event:\n", Dumper($e); +ok($e, 'the shutdown event should emit when the ipc is exited by command'); +is($e->{change}, 'exit', 'the `change` field should tell the reason for the shutdown'); +} + +done_testing;