layout toggle: take any combination of layouts as arguments (continuation of #2476)
--- /dev/null
+# 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).
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:*
--------------------------------------------------------------------
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
}
---------------------------
+=== 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]]
]
}
--------------------------------------------------------------------------------
+
+=== 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"
+ }
+]
+--------------------------------------------------------------------------------
*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"
bindsym f exec firefox
bindsym t exec thunderbird
- bindsym Esc mode "default"
+ bindsym Escape mode "default"
bindsym Return mode "default"
}
------------------------------------------------------------------------
=== 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*:
--------------------------
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 <<move_to_outputs>> for how to move a container/workspace to a different
#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;
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;
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, "<Enter>");
+ txt(4, 6, "<Enter>", 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, "<ESC>");
+ txt(5, 8, "<ESC>", 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, "<Alt>");
+ txt(5, 5, "<Alt>", white, black);
else
- txt(logical_px(31), 4, "<Win>");
+ txt(5, 4, "<Win>", 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, "-> <Win>");
+ txt(2, 4, "-> <Win>", white, black);
else
- txt(logical_px(10), 5, "-> <Alt>");
+ txt(2, 5, "-> <Alt>", 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, "<Enter>");
+ txt(4, 9, "<Enter>", 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, "<ESC>");
+ txt(5, 10, "<ESC>", 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;
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)) {
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,
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);
free(event);
}
+ /* Dismiss drawable surface */
+ draw_util_surface_free(conn, &surface);
+
return 0;
}
#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;
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;
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;
}
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;
symbols = xcb_key_symbols_alloc(conn);
+ init_dpi();
font = load_font(pattern, true);
set_font(&font);
/* 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);
free(event);
}
+ draw_util_surface_free(conn, &surface);
return 0;
}
* 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 {
} 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;
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)
/* 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;
}
*/
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);
}
}
+ btn_close.label = i3string_from_utf8("X");
+
int screens;
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
xcb_connection_has_error(conn))
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);
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;
}
}
}
FREE(pattern);
+ draw_util_surface_free(conn, &bar);
return 0;
}
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);
}
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
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
/** 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)
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);
#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
*
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. */
/* 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
* #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;
}
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;
}
/* 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;
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;
/* 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;
}
}
/* 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;
}
#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);
*/
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 */
}
/* 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;
}
}
/*
- * 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);
#include <sanitizer/lsan_interface.h>
#endif
+#define TEXT_PADDING logical_px(2)
+
typedef struct placeholder_state {
/** The X11 placeholder window. */
xcb_window_t window;
/** 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;
}
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) {
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);
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);
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);
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);
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;
}
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;
}
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 */
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',
--- /dev/null
+#!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;