aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Stapelberg <michael@stapelberg.de>2022-10-30 16:19:04 +0100
committerMichael Stapelberg <stapelberg@users.noreply.github.com>2022-11-01 17:55:46 +0100
commitb825dc124a33f247cf35efd5e2d0646003a16f23 (patch)
tree0a1e657f29d85dd451acc5ad55b0208770f6e0ca
parent0b89d4b2a7ae84a852a707d71cf2697e55581ee7 (diff)
downloadi3-b825dc124a33f247cf35efd5e2d0646003a16f23.tar.gz
i3-b825dc124a33f247cf35efd5e2d0646003a16f23.zip
Merge gaps support as-is
This code was copied over unmodified from https://github.com/Airblader/i3-gaps. I have split out the differences between i3-gaps and i3 into three areas: 1. Gaps 2. i3bar height 3. rgba colors
-rw-r--r--include/commands.h6
-rw-r--r--include/con.h7
-rw-r--r--include/config_directives.h3
-rw-r--r--include/configuration.h9
-rw-r--r--include/data.h26
-rw-r--r--include/render.h2
-rw-r--r--parser-specs/commands.spec18
-rw-r--r--parser-specs/config.spec40
-rw-r--r--src/commands.c114
-rw-r--r--src/con.c62
-rw-r--r--src/config.c6
-rw-r--r--src/config_directives.c166
-rw-r--r--src/floating.c6
-rw-r--r--src/ipc.c24
-rw-r--r--src/load_layout.c22
-rw-r--r--src/manage.c4
-rw-r--r--src/render.c88
-rw-r--r--src/tree.c2
-rw-r--r--src/workspace.c25
-rw-r--r--testcases/t/187-commands-parser.t1
-rw-r--r--testcases/t/201-config-parser.t5
-rw-r--r--testcases/t/287-edge-borders.t65
22 files changed, 668 insertions, 33 deletions
diff --git a/include/commands.h b/include/commands.h
index d0f0140b..2ae2643c 100644
--- a/include/commands.h
+++ b/include/commands.h
@@ -333,6 +333,12 @@ void cmd_shmlog(I3_CMD, const char *argument);
void cmd_debuglog(I3_CMD, const char *argument);
/**
+ * Implementation of 'gaps inner|outer|top|right|bottom|left|horizontal|vertical current|all set|plus|minus|toggle <px>'
+ *
+ */
+void cmd_gaps(I3_CMD, const char *type, const char *scope, const char *mode, const char *value);
+
+/**
* Implementation of 'title_window_icon <yes|no|toggle>' and 'title_window_icon <padding|toggle> <px>'
*
*/
diff --git a/include/con.h b/include/con.h
index 8d344994..6577c2d6 100644
--- a/include/con.h
+++ b/include/con.h
@@ -526,6 +526,13 @@ void con_set_urgency(Con *con, bool urgent);
char *con_get_tree_representation(Con *con);
/**
+ * Calculates the effective gap sizes for a container depending
+ * on whether it is adjacent to the edge of the screen or another
+ * container.
+ */
+gaps_t calculate_effective_gaps(Con *con);
+
+/**
* force parent split containers to be redrawn
*
*/
diff --git a/include/config_directives.h b/include/config_directives.h
index 1cc0340f..600226e9 100644
--- a/include/config_directives.h
+++ b/include/config_directives.h
@@ -43,6 +43,9 @@ CFGFUN(include, const char *pattern);
CFGFUN(font, const char *font);
CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command);
CFGFUN(for_window, const char *command);
+CFGFUN(gaps, const char *workspace, const char *type, const long value);
+CFGFUN(smart_borders, const char *enable);
+CFGFUN(smart_gaps, const char *enable);
CFGFUN(floating_minimum_size, const long width, const long height);
CFGFUN(floating_maximum_size, const long width, const long height);
CFGFUN(default_orientation, const char *orientation);
diff --git a/include/configuration.h b/include/configuration.h
index 5eae5f87..99f4b64e 100644
--- a/include/configuration.h
+++ b/include/configuration.h
@@ -268,6 +268,15 @@ struct Config {
int number_barconfigs;
tiling_drag_t tiling_drag;
+
+ /* Gap sizes */
+ gaps_t gaps;
+
+ /* Should single containers on a workspace receive a border? */
+ smart_borders_t smart_borders;
+
+ /* Disable gaps if there is only one container on the workspace */
+ smart_gaps_t smart_gaps;
};
/**
diff --git a/include/data.h b/include/data.h
index 8078fd3e..67a573c2 100644
--- a/include/data.h
+++ b/include/data.h
@@ -47,6 +47,7 @@ typedef struct Con Con;
typedef struct Match Match;
typedef struct Assignment Assignment;
typedef struct Window i3Window;
+typedef struct gaps_t gaps_t;
typedef struct mark_t mark_t;
/******************************************************************************
@@ -80,11 +81,20 @@ typedef enum { ADJ_NONE = 0,
ADJ_UPPER_SCREEN_EDGE = (1 << 2),
ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t;
+typedef enum { SMART_BORDERS_OFF,
+ SMART_BORDERS_ON,
+ SMART_BORDERS_NO_GAPS } smart_borders_t;
+
+typedef enum { SMART_GAPS_OFF,
+ SMART_GAPS_ON,
+ SMART_GAPS_INVERSE_OUTER } smart_gaps_t;
+
typedef enum { HEBM_NONE = ADJ_NONE,
HEBM_VERTICAL = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE,
HEBM_HORIZONTAL = ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE,
HEBM_BOTH = HEBM_VERTICAL | HEBM_HORIZONTAL,
- HEBM_SMART = (1 << 5) } hide_edge_borders_mode_t;
+ HEBM_SMART = (1 << 5),
+ HEBM_SMART_NO_GAPS = (1 << 6) } hide_edge_borders_mode_t;
typedef enum { MM_REPLACE,
MM_ADD } mark_mode_t;
@@ -137,6 +147,14 @@ typedef enum {
POINTER_WARPING_NONE = 1
} warping_t;
+struct gaps_t {
+ int inner;
+ int top;
+ int right;
+ int bottom;
+ int left;
+};
+
/**
* Focus wrapping modes.
*/
@@ -204,12 +222,13 @@ struct deco_render_params {
};
/**
- * Stores which workspace (by name or number) goes to which output.
+ * Stores which workspace (by name or number) goes to which output and its gaps config.
*
*/
struct Workspace_Assignment {
char *name;
char *output;
+ gaps_t gaps;
TAILQ_ENTRY(Workspace_Assignment) ws_assignments;
};
@@ -645,6 +664,9 @@ struct Con {
* workspace is not a named workspace (for named workspaces, num == -1) */
int num;
+ /** Only applicable for containers of type CT_WORKSPACE. */
+ gaps_t gaps;
+
struct Con *parent;
/* The position and size for this con. These coordinates are absolute. Note
diff --git a/include/render.h b/include/render.h
index 03751c01..8500b71d 100644
--- a/include/render.h
+++ b/include/render.h
@@ -40,7 +40,7 @@ typedef struct render_params {
* updated in X11.
*
*/
-void render_con(Con *con);
+void render_con(Con *con, bool already_inset);
/**
* Returns the height for the decorations
diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec
index 486f20dd..c618dfda 100644
--- a/parser-specs/commands.spec
+++ b/parser-specs/commands.spec
@@ -43,6 +43,7 @@ state INITIAL:
'title_window_icon' -> TITLE_WINDOW_ICON
'mode' -> MODE
'bar' -> BAR
+ 'gaps' -> GAPS
state CRITERIA:
ctype = 'class' -> CRITERION
@@ -95,6 +96,23 @@ state BORDER:
'1pixel'
-> call cmd_border("pixel", 1)
+# gaps inner|outer|horizontal|vertical|top|right|bottom|left [current] [set|plus|minus|toggle] <px>
+state GAPS:
+ type = 'inner', 'outer', 'horizontal', 'vertical', 'top', 'right', 'bottom', 'left'
+ -> GAPS_WITH_TYPE
+
+state GAPS_WITH_TYPE:
+ scope = 'current', 'all'
+ -> GAPS_WITH_SCOPE
+
+state GAPS_WITH_SCOPE:
+ mode = 'plus', 'minus', 'set', 'toggle'
+ -> GAPS_WITH_MODE
+
+state GAPS_WITH_MODE:
+ value = word
+ -> call cmd_gaps($type, $scope, $mode, $value)
+
state BORDER_WIDTH:
end
-> call cmd_border($border_style, -1)
diff --git a/parser-specs/config.spec b/parser-specs/config.spec
index e8ea2f9d..c918f142 100644
--- a/parser-specs/config.spec
+++ b/parser-specs/config.spec
@@ -25,6 +25,9 @@ state INITIAL:
'bar' -> BARBRACE
'font' -> FONT
'mode' -> MODENAME
+ 'gaps' -> GAPS
+ 'smart_borders' -> SMART_BORDERS
+ 'smart_gaps' -> SMART_GAPS
'floating_minimum_size' -> FLOATING_MINIMUM_SIZE_WIDTH
'floating_maximum_size' -> FLOATING_MAXIMUM_SIZE_WIDTH
'floating_modifier' -> FLOATING_MODIFIER
@@ -65,6 +68,32 @@ state IGNORE_LINE:
line
-> INITIAL
+# gaps inner|outer|horizontal|vertical|top|right|bottom|left <px>
+state GAPS:
+ scope = 'inner', 'outer', 'horizontal', 'vertical', 'top', 'right', 'bottom', 'left'
+ -> GAPS_WITH_SCOPE
+
+state GAPS_WITH_SCOPE:
+ value = number
+ -> call cfg_gaps($workspace, $scope, &value)
+
+# smart_borders true|false
+# smart_borders no_gaps
+state SMART_BORDERS:
+ enabled = '1', 'yes', 'true', 'on', 'enable', 'active'
+ -> call cfg_smart_borders($enabled)
+ enabled = 'no_gaps'
+ -> call cfg_smart_borders($enabled)
+
+# smart_gaps on|off
+state SMART_GAPS:
+ enabled = '1', 'yes', 'true', 'on', 'enable', 'active'
+ -> call cfg_smart_gaps($enabled)
+ enabled = '0', 'no', 'false', 'off', 'disable', 'inactive'
+ -> call cfg_smart_gaps($enabled)
+ enabled = 'inverse_outer'
+ -> call cfg_smart_gaps($enabled)
+
# include <pattern>
state INCLUDE:
pattern = string
@@ -135,10 +164,10 @@ state DEFAULT_BORDER_PIXELS_PX:
end
-> call cfg_default_border($windowtype, $border, &width)
-# hide_edge_borders <none|vertical|horizontal|both|smart>
+# hide_edge_borders <none|vertical|horizontal|both|smart|no_gaps>
# also hide_edge_borders <bool> for compatibility
state HIDE_EDGE_BORDERS:
- hide_borders = 'none', 'vertical', 'horizontal', 'both', 'smart'
+ hide_borders = 'none', 'vertical', 'horizontal', 'both', 'smart_no_gaps', 'smart'
-> call cfg_hide_edge_borders($hide_borders)
hide_borders = '1', 'yes', 'true', 'on', 'enable', 'active'
-> call cfg_hide_edge_borders($hide_borders)
@@ -297,13 +326,16 @@ state FOCUS_ON_WINDOW_ACTIVATION:
-> call cfg_focus_on_window_activation($mode)
# workspace <workspace> output <output>
+# workspace <workspace> gaps inner|outer <px>
state WORKSPACE:
workspace = word
- -> WORKSPACE_OUTPUT
+ -> WORKSPACE_COMMAND
-state WORKSPACE_OUTPUT:
+state WORKSPACE_COMMAND:
'output'
-> WORKSPACE_OUTPUT_WORD
+ 'gaps'
+ -> GAPS
state WORKSPACE_OUTPUT_WORD:
output = word
diff --git a/src/commands.c b/src/commands.c
index 0a39c55a..52c3fcd1 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -831,7 +831,7 @@ void cmd_append_layout(I3_CMD, const char *cpath) {
// is not executed yet and will be batched with append_layout’s
// needs_tree_render after the parser finished. We should check if that is
// necessary at all.
- render_con(croot);
+ render_con(croot, false);
restore_open_placeholder_windows(parent);
@@ -2374,3 +2374,115 @@ void cmd_debuglog(I3_CMD, const char *argument) {
// XXX: default reply for now, make this a better reply
ysuccess(true);
}
+
+/**
+ * Implementation of 'gaps inner|outer|top|right|bottom|left|horizontal|vertical current|all set|plus|minus|toggle <px>'
+ *
+ */
+void cmd_gaps(I3_CMD, const char *type, const char *scope, const char *mode, const char *value) {
+ int pixels = logical_px(atoi(value));
+ Con *workspace = con_get_workspace(focused);
+
+#define CMD_SET_GAPS_VALUE(type, value, reset) \
+ do { \
+ if (!strcmp(scope, "all")) { \
+ Con *output, *cur_ws = NULL; \
+ TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { \
+ Con *content = output_get_content(output); \
+ TAILQ_FOREACH (cur_ws, &(content->nodes_head), nodes) { \
+ if (reset) \
+ cur_ws->gaps.type = 0; \
+ else if (value + cur_ws->gaps.type < 0) \
+ cur_ws->gaps.type = -value; \
+ } \
+ } \
+ \
+ config.gaps.type = value; \
+ } else { \
+ workspace->gaps.type = value - config.gaps.type; \
+ } \
+ } while (0)
+
+#define CMD_GAPS(type) \
+ do { \
+ int current_value = config.gaps.type; \
+ if (strcmp(scope, "current") == 0) \
+ current_value += workspace->gaps.type; \
+ \
+ bool reset = false; \
+ if (!strcmp(mode, "plus")) \
+ current_value += pixels; \
+ else if (!strcmp(mode, "minus")) \
+ current_value -= pixels; \
+ else if (!strcmp(mode, "set")) { \
+ current_value = pixels; \
+ reset = true; \
+ } else if (!strcmp(mode, "toggle")) { \
+ current_value = !current_value * pixels; \
+ reset = true; \
+ } else { \
+ ELOG("Invalid mode %s when changing gaps", mode); \
+ ysuccess(false); \
+ return; \
+ } \
+ \
+ /* see issue 262 */ \
+ int min_value = 0; \
+ if (strcmp(#type, "inner") != 0) { \
+ min_value = strcmp(scope, "all") ? -config.gaps.inner - workspace->gaps.inner : -config.gaps.inner; \
+ } \
+ \
+ if (current_value < min_value) \
+ current_value = min_value; \
+ \
+ CMD_SET_GAPS_VALUE(type, current_value, reset); \
+ } while (0)
+
+#define CMD_UPDATE_GAPS(type) \
+ do { \
+ if (!strcmp(scope, "all")) { \
+ if (config.gaps.type + config.gaps.inner < 0) \
+ CMD_SET_GAPS_VALUE(type, -config.gaps.inner, true); \
+ } else { \
+ if (config.gaps.type + workspace->gaps.type + config.gaps.inner + workspace->gaps.inner < 0) { \
+ CMD_SET_GAPS_VALUE(type, -config.gaps.inner - workspace->gaps.inner, true); \
+ } \
+ } \
+ } while (0)
+
+ if (!strcmp(type, "inner")) {
+ CMD_GAPS(inner);
+ // update inconsistent values
+ CMD_UPDATE_GAPS(top);
+ CMD_UPDATE_GAPS(bottom);
+ CMD_UPDATE_GAPS(right);
+ CMD_UPDATE_GAPS(left);
+ } else if (!strcmp(type, "outer")) {
+ CMD_GAPS(top);
+ CMD_GAPS(bottom);
+ CMD_GAPS(right);
+ CMD_GAPS(left);
+ } else if (!strcmp(type, "vertical")) {
+ CMD_GAPS(top);
+ CMD_GAPS(bottom);
+ } else if (!strcmp(type, "horizontal")) {
+ CMD_GAPS(right);
+ CMD_GAPS(left);
+ } else if (!strcmp(type, "top")) {
+ CMD_GAPS(top);
+ } else if (!strcmp(type, "bottom")) {
+ CMD_GAPS(bottom);
+ } else if (!strcmp(type, "right")) {
+ CMD_GAPS(right);
+ } else if (!strcmp(type, "left")) {
+ CMD_GAPS(left);
+ } else {
+ ELOG("Invalid type %s when changing gaps", type);
+ ysuccess(false);
+ return;
+ }
+
+ cmd_output->needs_tree_render = true;
+ // XXX: default reply for now, make this a better reply
+ ysuccess(true);
+}
diff --git a/src/con.c b/src/con.c
index aac51e73..51fdab91 100644
--- a/src/con.c
+++ b/src/con.c
@@ -1685,6 +1685,13 @@ Con *con_descend_direction(Con *con, direction_t direction) {
return con_descend_direction(most, direction);
}
+static bool has_outer_gaps(gaps_t gaps) {
+ return gaps.top > 0 ||
+ gaps.right > 0 ||
+ gaps.bottom > 0 ||
+ gaps.left > 0;
+}
+
/*
* Returns a "relative" Rect which contains the amount of pixels that need to
* be added to the original Rect to get the final position (obviously the
@@ -1692,10 +1699,12 @@ Con *con_descend_direction(Con *con, direction_t direction) {
*
*/
Rect con_border_style_rect(Con *con) {
- if (config.hide_edge_borders == HEBM_SMART && con_num_visible_children(con_get_workspace(con)) <= 1) {
- if (!con_is_floating(con)) {
+ if ((config.smart_borders == SMART_BORDERS_ON && con_num_visible_children(con_get_workspace(con)) <= 1) ||
+ (config.smart_borders == SMART_BORDERS_NO_GAPS && !has_outer_gaps(calculate_effective_gaps(con))) ||
+ (config.hide_edge_borders == HEBM_SMART && con_num_visible_children(con_get_workspace(con)) <= 1) ||
+ (config.hide_edge_borders == HEBM_SMART_NO_GAPS && con_num_visible_children(con_get_workspace(con)) <= 1 && !has_outer_gaps(calculate_effective_gaps(con)))) {
+ if (!con_is_floating(con))
return (Rect){0, 0, 0, 0};
- }
}
adjacent_t borders_to_hide = ADJ_NONE;
@@ -1720,7 +1729,13 @@ Rect con_border_style_rect(Con *con) {
result = (Rect){border_width, border_width, -(2 * border_width), -(2 * border_width)};
}
- borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
+ /* If hide_edge_borders is set to no_gaps and it did not pass the no border check, show all borders */
+ if (config.hide_edge_borders == HEBM_SMART_NO_GAPS) {
+ borders_to_hide = con_adjacent_borders(con) & HEBM_NONE;
+ } else {
+ borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
+ }
+
if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
result.x -= border_width;
result.width += border_width;
@@ -2327,6 +2342,45 @@ char *con_get_tree_representation(Con *con) {
return complete_buf;
}
+/**
+ * Calculates the effective gap sizes for a container.
+ */
+gaps_t calculate_effective_gaps(Con *con) {
+ Con *workspace = con_get_workspace(con);
+ if (workspace == NULL)
+ return (gaps_t){0, 0, 0, 0, 0};
+
+ bool one_child = con_num_visible_children(workspace) <= 1 ||
+ (con_num_children(workspace) == 1 &&
+ (TAILQ_FIRST(&(workspace->nodes_head))->layout == L_TABBED ||
+ TAILQ_FIRST(&(workspace->nodes_head))->layout == L_STACKED));
+
+ if (config.smart_gaps == SMART_GAPS_ON && one_child)
+ return (gaps_t){0, 0, 0, 0, 0};
+
+ gaps_t gaps = {
+ .inner = (workspace->gaps.inner + config.gaps.inner) / 2,
+ .top = 0,
+ .right = 0,
+ .bottom = 0,
+ .left = 0};
+
+ if (config.smart_gaps != SMART_GAPS_INVERSE_OUTER || one_child) {
+ gaps.top = workspace->gaps.top + config.gaps.top;
+ gaps.right = workspace->gaps.right + config.gaps.right;
+ gaps.bottom = workspace->gaps.bottom + config.gaps.bottom;
+ gaps.left = workspace->gaps.left + config.gaps.left;
+ }
+
+ /* Outer gaps are added on top of inner gaps. */
+ gaps.top += 2 * gaps.inner;
+ gaps.right += 2 * gaps.inner;
+ gaps.bottom += 2 * gaps.inner;
+ gaps.left += 2 * gaps.inner;
+
+ return gaps;
+}
+
/*
* Returns the container's title considering the current title format.
*
diff --git a/src/config.c b/src/config.c
index 236d682b..7cbdad1f 100644
--- a/src/config.c
+++ b/src/config.c
@@ -216,6 +216,12 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
/* Set default_orientation to NO_ORIENTATION for auto orientation. */
config.default_orientation = NO_ORIENTATION;
+ config.gaps.inner = 0;
+ config.gaps.top = 0;
+ config.gaps.right = 0;
+ config.gaps.bottom = 0;
+ config.gaps.left = 0;
+
/* Set default urgency reset delay to 500ms */
if (config.workspace_urgency_timer == 0)
config.workspace_urgency_timer = 0.5;
diff --git a/src/config_directives.c b/src/config_directives.c
index ae68685b..78a246dd 100644
--- a/src/config_directives.c
+++ b/src/config_directives.c
@@ -231,6 +231,160 @@ CFGFUN(for_window, const char *command) {
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
}
+static void create_gaps_assignment(const char *workspace, const char *scope, gaps_t gaps) {
+ DLOG("Setting gaps for workspace %s", workspace);
+
+ struct Workspace_Assignment *assignment;
+ TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
+ if (strcasecmp(assignment->name, workspace) == 0) {
+ if (!strcmp(scope, "inner")) {
+ assignment->gaps.inner = gaps.inner;
+ } else if (!strcmp(scope, "outer")) {
+ assignment->gaps.top = gaps.top;
+ assignment->gaps.right = gaps.right;
+ assignment->gaps.bottom = gaps.bottom;
+ assignment->gaps.left = gaps.left;
+ } else if (!strcmp(scope, "vertical")) {
+ assignment->gaps.top = gaps.top;
+ assignment->gaps.bottom = gaps.bottom;
+ } else if (!strcmp(scope, "horizontal")) {
+ assignment->gaps.right = gaps.right;
+ assignment->gaps.left = gaps.left;
+ } else if (!strcmp(scope, "top")) {
+ assignment->gaps.top = gaps.top;
+ } else if (!strcmp(scope, "right")) {
+ assignment->gaps.right = gaps.right;
+ } else if (!strcmp(scope, "bottom")) {
+ assignment->gaps.bottom = gaps.bottom;
+ } else if (!strcmp(scope, "left")) {
+ assignment->gaps.left = gaps.left;
+ } else {
+ ELOG("Invalid command, cannot process scope %s", scope);
+ }
+
+ return;
+ }
+ }
+
+ // Assignment does not yet exist, let's create it.
+ assignment = scalloc(1, sizeof(struct Workspace_Assignment));
+ assignment->name = sstrdup(workspace);
+ assignment->output = NULL;
+ if (!strcmp(scope, "inner")) {
+ assignment->gaps.inner = gaps.inner;
+ } else if (!strcmp(scope, "outer")) {
+ assignment->gaps.top = gaps.top;
+ assignment->gaps.right = gaps.right;
+ assignment->gaps.bottom = gaps.bottom;
+ assignment->gaps.left = gaps.left;
+ } else if (!strcmp(scope, "vertical")) {
+ assignment->gaps.top = gaps.top;
+ assignment->gaps.bottom = gaps.bottom;
+ } else if (!strcmp(scope, "horizontal")) {
+ assignment->gaps.right = gaps.right;
+ assignment->gaps.left = gaps.left;
+ } else if (!strcmp(scope, "top")) {
+ assignment->gaps.top = gaps.top;
+ } else if (!strcmp(scope, "right")) {
+ assignment->gaps.right = gaps.right;
+ } else if (!strcmp(scope, "bottom")) {
+ assignment->gaps.bottom = gaps.bottom;
+ } else if (!strcmp(scope, "left")) {
+ assignment->gaps.left = gaps.left;
+ } else {
+ ELOG("Invalid command, cannot process scope %s", scope);
+ }
+ TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
+}
+
+CFGFUN(gaps, const char *workspace, const char *scope, const long value) {
+ int pixels = logical_px(value);
+ gaps_t gaps = (gaps_t){0, 0, 0, 0, 0};
+ if (!strcmp(scope, "inner")) {
+ if (workspace == NULL)
+ config.gaps.inner = pixels;
+ else {
+ gaps.inner = pixels - config.gaps.inner;
+ create_gaps_assignment(workspace, scope, gaps);
+ }
+ } else if (!strcmp(scope, "outer")) {
+ if (workspace == NULL) {
+ config.gaps.top = pixels;
+ config.gaps.right = pixels;
+ config.gaps.bottom = pixels;
+ config.gaps.left = pixels;
+ } else {
+ gaps.top = pixels - config.gaps.top;
+ gaps.right = pixels - config.gaps.right;
+ gaps.bottom = pixels - config.gaps.bottom;
+ gaps.left = pixels - config.gaps.left;
+ create_gaps_assignment(workspace, scope, gaps);
+ }
+ } else if (!strcmp(scope, "vertical")) {
+ if (workspace == NULL) {
+ config.gaps.top = pixels;
+ config.gaps.bottom = pixels;
+ } else {
+ gaps.top = pixels - config.gaps.top;
+ gaps.bottom = pixels - config.gaps.bottom;
+ create_gaps_assignment(workspace, scope, gaps);
+ }
+ } else if (!strcmp(scope, "horizontal")) {
+ if (workspace == NULL) {
+ config.gaps.right = pixels;
+ config.gaps.left = pixels;
+ } else {
+ gaps.right = pixels - config.gaps.right;
+ gaps.left = pixels - config.gaps.left;
+ create_gaps_assignment(workspace, scope, gaps);
+ }
+ } else if (!strcmp(scope, "top")) {
+ if (workspace == NULL)
+ config.gaps.top = pixels;
+ else {
+ gaps.top = pixels - config.gaps.top;
+ create_gaps_assignment(workspace, scope, gaps);
+ }
+ } else if (!strcmp(scope, "right")) {
+ if (workspace == NULL)
+ config.gaps.right = pixels;
+ else {
+ gaps.right = pixels - config.gaps.right;
+ create_gaps_assignment(workspace, scope, gaps);
+ }
+ } else if (!strcmp(scope, "bottom")) {
+ if (workspace == NULL)
+ config.gaps.bottom = pixels;
+ else {
+ gaps.bottom = pixels - config.gaps.bottom;
+ create_gaps_assignment(workspace, scope, gaps);
+ }
+ } else if (!strcmp(scope, "left")) {
+ if (workspace == NULL)
+ config.gaps.left = pixels;
+ else {
+ gaps.left = pixels - config.gaps.left;
+ create_gaps_assignment(workspace, scope, gaps);
+ }
+ } else {
+ ELOG("Invalid command, cannot process scope %s", scope);
+ }
+}
+
+CFGFUN(smart_borders, const char *enable) {
+ if (!strcmp(enable, "no_gaps"))
+ config.smart_borders = SMART_BORDERS_NO_GAPS;
+ else
+ config.smart_borders = boolstr(enable) ? SMART_BORDERS_ON : SMART_BORDERS_OFF;
+}
+
+CFGFUN(smart_gaps, const char *enable) {
+ if (!strcmp(enable, "inverse_outer"))
+ config.smart_gaps = SMART_GAPS_INVERSE_OUTER;
+ else
+ config.smart_gaps = boolstr(enable) ? SMART_GAPS_ON : SMART_GAPS_OFF;
+}
+
CFGFUN(floating_minimum_size, const long width, const long height) {
config.floating_minimum_width = width;
config.floating_minimum_height = height;
@@ -297,7 +451,9 @@ CFGFUN(default_border, const char *windowtype, const char *border, const long wi
}
CFGFUN(hide_edge_borders, const char *borders) {
- if (strcmp(borders, "smart") == 0)
+ if (strcmp(borders, "smart_no_gaps") == 0)
+ config.hide_edge_borders = HEBM_SMART_NO_GAPS;
+ else if (strcmp(borders, "smart") == 0)
config.hide_edge_borders = HEBM_SMART;
else if (strcmp(borders, "vertical") == 0)
config.hide_edge_borders = HEBM_VERTICAL;
@@ -417,9 +573,11 @@ CFGFUN(workspace, const char *workspace, const char *output) {
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
if (strcasecmp(assignment->name, workspace) == 0) {
- ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
- workspace);
- return;
+ if (assignment->output != NULL) {
+ ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
+ workspace);
+ return;
+ }
}
}
diff --git a/src/floating.c b/src/floating.c
index 992ca23c..5eaf639c 100644
--- a/src/floating.c
+++ b/src/floating.c
@@ -415,7 +415,7 @@ bool floating_enable(Con *con, bool automatic) {
DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height);
/* render the cons to get initial window_rect correct */
- render_con(nc);
+ render_con(nc, true);
if (set_focus)
con_activate(con);
@@ -580,7 +580,7 @@ DRAGGING_CB(drag_window_callback) {
con->rect.x = old_rect->x + (new_x - event->root_x);
con->rect.y = old_rect->y + (new_y - event->root_y);
- render_con(con);
+ render_con(con, true);
x_push_node(con);
xcb_flush(conn);
@@ -685,7 +685,7 @@ DRAGGING_CB(resize_window_callback) {
con->rect.x = dest_x;
con->rect.y = dest_y;
- render_con(con);
+ render_con(con, true);
x_push_changes(croot);
}
diff --git a/src/ipc.c b/src/ipc.c
index 0b0451f3..8c59c2eb 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -245,6 +245,28 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) {
y(map_close);
}
+static void dump_gaps(yajl_gen gen, const char *name, gaps_t gaps) {
+ ystr(name);
+ y(map_open);
+ ystr("inner");
+ y(integer, gaps.inner);
+
+ // TODO: the i3ipc Python modules recognize gaps, but only inner/outer
+ // This is currently here to preserve compatibility with that
+ ystr("outer");
+ y(integer, gaps.top);
+
+ ystr("top");
+ y(integer, gaps.top);
+ ystr("right");
+ y(integer, gaps.right);
+ ystr("bottom");
+ y(integer, gaps.bottom);
+ ystr("left");
+ y(integer, gaps.left);
+ y(map_close);
+}
+
static void dump_event_state_mask(yajl_gen gen, Binding *bind) {
y(array_open);
for (int i = 0; i < 20; i++) {
@@ -504,6 +526,8 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
if (con->type == CT_WORKSPACE) {
ystr("num");
y(integer, con->num);
+
+ dump_gaps(gen, "gaps", con->gaps);
}
ystr("window");
diff --git a/src/load_layout.c b/src/load_layout.c
index 7bdf5521..9789fb33 100644
--- a/src/load_layout.c
+++ b/src/load_layout.c
@@ -20,6 +20,7 @@ static char *last_key;
static int incomplete;
static Con *json_node;
static Con *to_focus;
+static bool parsing_gaps;
static bool parsing_swallows;
static bool parsing_rect;
static bool parsing_deco_rect;
@@ -60,7 +61,7 @@ static int json_start_map(void *ctx) {
TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches);
swallow_is_empty = true;
} else {
- if (!parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry) {
+ if (!parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry && !parsing_gaps) {
if (last_key && strcasecmp(last_key, "floating_nodes") == 0) {
DLOG("New floating_node\n");
Con *ws = con_get_workspace(json_node);
@@ -84,7 +85,7 @@ static int json_start_map(void *ctx) {
static int json_end_map(void *ctx) {
LOG("end of map\n");
- if (!parsing_swallows && !parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry) {
+ if (!parsing_swallows && !parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry && !parsing_gaps) {
/* Set a few default values to simplify manually crafted layout files. */
if (json_node->layout == L_DEFAULT) {
DLOG("Setting layout = L_SPLITH\n");
@@ -192,6 +193,7 @@ static int json_end_map(void *ctx) {
return 0;
}
+ parsing_gaps = false;
parsing_rect = false;
parsing_deco_rect = false;
parsing_window_rect = false;
@@ -245,6 +247,9 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) {
if (strcasecmp(last_key, "swallows") == 0)
parsing_swallows = true;
+ if (strcasecmp(last_key, "gaps") == 0)
+ parsing_gaps = true;
+
if (strcasecmp(last_key, "rect") == 0)
parsing_rect = true;
@@ -506,6 +511,18 @@ static int json_int(void *ctx, long long val) {
swallow_is_empty = false;
}
}
+ if (parsing_gaps) {
+ if (strcasecmp(last_key, "inner") == 0)
+ json_node->gaps.inner = val;
+ else if (strcasecmp(last_key, "top") == 0)
+ json_node->gaps.top = val;
+ else if (strcasecmp(last_key, "right") == 0)
+ json_node->gaps.right = val;
+ else if (strcasecmp(last_key, "bottom") == 0)
+ json_node->gaps.bottom = val;
+ else if (strcasecmp(last_key, "left") == 0)
+ json_node->gaps.left = val;
+ }
return 1;
}
@@ -653,6 +670,7 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
yajl_config(hand, yajl_dont_validate_strings, true);
json_node = con;
to_focus = NULL;
+ parsing_gaps = false;
incomplete = 0;
parsing_swallows = false;
parsing_rect = false;
diff --git a/src/manage.c b/src/manage.c
index a7de243e..6125af34 100644
--- a/src/manage.c
+++ b/src/manage.c
@@ -591,13 +591,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
* workspace at all. However, just calling render_con() on the
* workspace isn’t enough either — it needs the rect. */
ws->rect = ws->parent->rect;
- render_con(ws);
+ render_con(ws, false);
/* Disable setting focus, otherwise we’d move focus to an invisible
* workspace, which we generally prevent (e.g. in
* con_move_to_workspace). */
set_focus = false;
}
- render_con(croot);
+ render_con(croot, false);
cwindow->managed_since = time(NULL);
diff --git a/src/render.c b/src/render.c
index cc1c01f8..6917efd1 100644
--- a/src/render.c
+++ b/src/render.c
@@ -20,6 +20,8 @@ static void render_con_split(Con *con, Con *child, render_params *p, int i);
static void render_con_stacked(Con *con, Con *child, render_params *p, int i);
static void render_con_tabbed(Con *con, Con *child, render_params *p, int i);
static void render_con_dockarea(Con *con, Con *child, render_params *p);
+bool should_inset_con(Con *con, int children);
+bool has_adjacent_container(Con *con, direction_t direction);
/*
* Returns the height for the decorations
@@ -39,7 +41,7 @@ int render_deco_height(void) {
* updated in X11.
*
*/
-void render_con(Con *con) {
+void render_con(Con *con, bool already_inset) {
render_params params = {
.rect = con->rect,
.x = con->rect.x,
@@ -49,6 +51,33 @@ void render_con(Con *con) {
DLOG("Rendering node %p / %s / layout %d / children %d\n", con, con->name,
con->layout, params.children);
+ bool should_inset = should_inset_con(con, params.children);
+ if (!already_inset && should_inset) {
+ gaps_t gaps = calculate_effective_gaps(con);
+ Rect inset = (Rect){
+ has_adjacent_container(con, D_LEFT) ? gaps.inner : gaps.left,
+ has_adjacent_container(con, D_UP) ? gaps.inner : gaps.top,
+ has_adjacent_container(con, D_RIGHT) ? -gaps.inner : -gaps.right,
+ has_adjacent_container(con, D_DOWN) ? -gaps.inner : -gaps.bottom};
+ inset.width -= inset.x;
+ inset.height -= inset.y;
+
+ if (con->fullscreen_mode == CF_NONE) {
+ params.rect = rect_add(params.rect, inset);
+ con->rect = rect_add(con->rect, inset);
+ if (con->window) {
+ con->window_rect = rect_add(con->window_rect, inset);
+ }
+ }
+ inset.height = 0;
+ if (con->deco_rect.width != 0 && con->deco_rect.height != 0) {
+ con->deco_rect = rect_add(con->deco_rect, inset);
+ }
+
+ params.x = con->rect.x;
+ params.y = con->rect.y;
+ }
+
int i = 0;
con->mapped = true;
@@ -85,7 +114,7 @@ void render_con(Con *con) {
if (fullscreen) {
fullscreen->rect = params.rect;
x_raise_con(fullscreen);
- render_con(fullscreen);
+ render_con(fullscreen, false);
/* Fullscreen containers are either global (underneath the CT_ROOT
* container) or per-output (underneath the CT_CONTENT container). For
* global fullscreen containers, we cannot abort rendering here yet,
@@ -130,7 +159,7 @@ void render_con(Con *con) {
DLOG("child at (%d, %d) with (%d x %d)\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
x_raise_con(child);
- render_con(child);
+ render_con(child, should_inset || already_inset);
i++;
}
@@ -144,7 +173,7 @@ void render_con(Con *con) {
* that we have a non-leaf-container inside the stack. In that
* case, the children of the non-leaf-container need to be
* raised as well. */
- render_con(child);
+ render_con(child, true);
}
if (params.children != 1)
@@ -193,7 +222,7 @@ static void render_root(Con *con, Con *fullscreen) {
Con *output;
if (!fullscreen) {
TAILQ_FOREACH (output, &(con->nodes_head), nodes) {
- render_con(output);
+ render_con(output, false);
}
}
@@ -237,7 +266,7 @@ static void render_root(Con *con, Con *fullscreen) {
DLOG("floating child at (%d,%d) with %d x %d\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
x_raise_con(child);
- render_con(child);
+ render_con(child, true);
}
}
}
@@ -287,7 +316,7 @@ static void render_output(Con *con) {
if (fullscreen) {
fullscreen->rect = con->rect;
x_raise_con(fullscreen);
- render_con(fullscreen);
+ render_con(fullscreen, false);
return;
}
@@ -328,7 +357,7 @@ static void render_output(Con *con) {
DLOG("child at (%d, %d) with (%d x %d)\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
x_raise_con(child);
- render_con(child);
+ render_con(child, child->type == CT_DOCKAREA);
}
}
@@ -430,3 +459,46 @@ static void render_con_dockarea(Con *con, Con *child, render_params *p) {
child->deco_rect.height = 0;
p->y += child->rect.height;
}
+
+/*
+ * Decides whether the container should be inset.
+ */
+bool should_inset_con(Con *con, int children) {
+ /* Don't inset floating containers and workspaces. */
+ if (con->type == CT_FLOATING_CON || con->type == CT_WORKSPACE)
+ return false;
+
+ if (con_is_leaf(con))
+ return true;
+
+ return (con->layout == L_STACKED || con->layout == L_TABBED) && children > 0;
+}
+
+/*
+ * Returns whether the given container has an adjacent container in the
+ * specified direction. In other words, this returns true if and only if
+ * the container is not touching the edge of the screen in that direction.
+ */
+bool has_adjacent_container(Con *con, direction_t direction) {
+ Con *workspace = con_get_workspace(con);
+ Con *fullscreen = con_get_fullscreen_con(workspace, CF_GLOBAL);
+ if (fullscreen == NULL)
+ fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
+
+ /* If this container is fullscreen by itself, there's no adjacent container. */
+ if (con == fullscreen)
+ return false;
+
+ Con *first = con;
+ Con *second = NULL;
+ bool found_neighbor = resize_find_tiling_participants(&first, &second, direction, false);
+ if (!found_neighbor)
+ return false;
+
+ /* If we have an adjacent container and nothing is fullscreen, we consider it. */
+ if (fullscreen == NULL)
+ return true;
+
+ /* For fullscreen containers, only consider the adjacent container if it is also fullscreen. */
+ return con_has_parent(con, fullscreen) && con_has_parent(second, fullscreen);
+}
diff --git a/src/tree.c b/src/tree.c
index 178ba057..dad205a5 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -458,7 +458,7 @@ void tree_render(void) {
mark_unmapped(croot);
croot->mapped = true;
- render_con(croot);
+ render_con(croot, false);
x_push_changes(croot);
DLOG("-- END RENDERING --\n");
diff --git a/src/workspace.c b/src/workspace.c
index e1ac49d3..73917639 100644
--- a/src/workspace.c
+++ b/src/workspace.c
@@ -85,6 +85,10 @@ Con *get_assigned_output(const char *name, long parsed_num) {
Con *output = NULL;
struct Workspace_Assignment *assignment;
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
+ if (assignment->output == NULL) {
+ continue;
+ }
+
if (name && strcmp(assignment->name, name) == 0) {
DLOG("Found workspace name=\"%s\" assignment to output \"%s\"\n",
name, assignment->output);
@@ -131,11 +135,22 @@ Con *workspace_get(const char *num) {
}
LOG("Creating new workspace \"%s\"\n", num);
+ gaps_t gaps = (gaps_t){0, 0, 0, 0, 0};
/* We set workspace->num to the number if this workspace’s name begins with
* a positive number. Otherwise it’s a named ws and num will be 1. */
const int parsed_num = ws_name_to_number(num);
+ struct Workspace_Assignment *assignment;
+ TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
+ if (strcmp(assignment->name, num) == 0) {
+ gaps = assignment->gaps;
+ break;
+ } else if (parsed_num != -1 && name_is_digits(assignment->name) && ws_name_to_number(assignment->name) == parsed_num) {
+ gaps = assignment->gaps;
+ }
+ }
+
Con *output = get_assigned_output(num, parsed_num);
/* if an assignment is not found, we create this workspace on the current output */
if (!output) {
@@ -156,6 +171,7 @@ Con *workspace_get(const char *num) {
workspace->workspace_layout = config.default_layout;
workspace->num = parsed_num;
workspace->type = CT_WORKSPACE;
+ workspace->gaps = gaps;
con_attach(workspace, output_get_content(output), false);
_workspace_apply_default_orientation(workspace);
@@ -281,6 +297,15 @@ Con *create_workspace_on_output(Output *output, Con *content) {
ws->num = c;
sasprintf(&(ws->name), "%d", c);
}
+
+ struct Workspace_Assignment *assignment;
+ TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
+ if (strcmp(assignment->name, ws->name) == 0) {
+ ws->gaps = assignment->gaps;
+ break;
+ }
+ }
+
con_attach(ws, content, false);
char *name;
diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t
index 06dbce7e..3404df1f 100644
--- a/testcases/t/187-commands-parser.t
+++ b/testcases/t/187-commands-parser.t
@@ -179,6 +179,7 @@ is(parser_calls('unknown_literal'),
title_window_icon
mode
bar
+ gaps
)) . "'\n" .
"ERROR: Your command: unknown_literal\n" .
"ERROR: ^^^^^^^^^^^^^^^",
diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t
index 3ab18483..af50d81d 100644
--- a/testcases/t/201-config-parser.t
+++ b/testcases/t/201-config-parser.t
@@ -515,6 +515,9 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
bar
font
mode
+ gaps
+ smart_borders
+ smart_gaps
floating_minimum_size
floating_maximum_size
floating_modifier
@@ -581,7 +584,7 @@ client.focused #4c7899 #285577 #ffffff #2e9ef4
EOT
$expected = <<'EOT';
-ERROR: CONFIG: Expected one of these tokens: 'none', 'vertical', 'horizontal', 'both', 'smart', '1', 'yes', 'true', 'on', 'enable', 'active'
+ERROR: CONFIG: Expected one of these tokens: 'none', 'vertical', 'horizontal', 'both', 'smart_no_gaps', 'smart', '1', 'yes', 'true', 'on', 'enable', 'active'
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: hide_edge_borders FOOBAR
ERROR: CONFIG: ^^^^^^
diff --git a/testcases/t/287-edge-borders.t b/testcases/t/287-edge-borders.t
index 1fd69786..3dfc3407 100644
--- a/testcases/t/287-edge-borders.t
+++ b/testcases/t/287-edge-borders.t
@@ -160,4 +160,69 @@ is($tilewindow2->rect->width, $tiled[1]->{rect}->{width} - 2*2, 'second tiled bo
exit_gracefully($pid);
+#####################################################################
+# 5: check that the borders are visible on a workspace with one tiled
+# window and edge gaps
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+new_window pixel 2
+new_float pixel 2
+gaps outer 5
+hide_edge_borders smart_no_gaps
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$tilewindow = open_window;
+
+$wscontent = get_ws($tmp);
+
+@tiled = @{$wscontent->{nodes}};
+ok(@tiled == 1, 'one tiled container opened');
+is($tiled[0]->{current_border_width}, 2, 'tiled current border width set to 2');
+is($tilewindow->rect->width, $tiled[0]->{rect}->{width} - 2*2, 'single tiled border width 2');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 5: check that the borders are hidden on a workspace with one tiled
+# window with no gaps
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+new_window pixel 2
+new_float pixel 2
+hide_edge_borders smart_no_gaps
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$tilewindow = open_window;
+
+$wscontent = get_ws($tmp);
+
+@tiled = @{$wscontent->{nodes}};
+ok(@tiled == 1, 'one tiled container opened');
+is($tiled[0]->{current_border_width}, 2, 'tiled current border width set to 2');
+is($tilewindow->rect->width, $tiled[0]->{rect}->{width} - 2*0, 'single tiled border width 0');
+
+exit_gracefully($pid);
+
+
+
done_testing;