diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 40fd8399f..a8aea2005 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -4,7 +4,7 @@
Note that bug reports and feature requests for related projects should be filed in the corresponding repositories for [i3status](https://github.com/i3/i3status) and [i3lock](https://github.com/i3/i3lock).
-## i3 bug reports and feature requests
+## i3 bug reports
1. Read the [debugging instructions](https://i3wm.org/docs/debugging.html).
2. Make sure you include a link to your logfile in your report (section 3).
@@ -18,6 +18,20 @@ Note that bug reports and feature requests for related projects should be filed
encountered the issue you are about to report while using a compositor,
please try reproducing it without a compositor.
+## i3 feature requests
+
+1. Read the [project goals](https://i3wm.org) on the website and make sure that
+ they are compatible with the feature you want to suggest.
+2. We are generally happy with the current feature set of i3 and instead focus
+ on maintenance such as stability and fixing bugs. New features will rarely
+ be considered if they require additional configuration and/or commands, or
+ if they add significant complexity (either through the exposed configuration
+ or mental complexity) to the project.
+3. Explain in detail what problem the feature addresses and why existing
+ features fall short.
+4. Consider whether the feature could instead be implemented using the
+ [IPC](https://i3wm.org/docs/ipc.html) or other external tooling.
+
## Pull requests
* Before sending a pull request for new features, please check with us that the
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 726b40079..857c1defd 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -6,7 +6,12 @@ IMPORTANT NOTE: If your issue is not specific to any feature provided by i3-gaps
-->
## I'm submitting a…
-
+
[ ] Bug
[ ] Feature Request
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..02251489a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,4 @@
+contact_links:
+ - name: Ask a question or request support for using i3
+ url: https://github.com/i3/i3/discussions/new
+ about: Ask a question or request support for using i3
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index e1c169a58..c41d1cac3 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -8,7 +8,7 @@ PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATI
-->
## I'm submitting a…
-
+
[ ] Bug
[x] Feature Request
@@ -28,6 +28,15 @@ Describe the desired behavior you expect after mitigation of the issue,
e.g., »The window left next to the current window should be focused.«
-->
+## Impact
+
+
+[ ] This feature requires new configuration and/or commands
+
+
## Environment
+ inkscape:version="1.0.1 (c497b03c, 2020-09-10)"
+ sodipodi:docname="logo.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ version="1.1">
@@ -112,10 +112,10 @@
@@ -282,15 +282,17 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
- inkscape:cx="239.17981"
- inkscape:cy="807.75327"
+ inkscape:cx="164.67981"
+ inkscape:cy="285.75327"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1272"
- inkscape:window-height="950"
+ inkscape:window-height="856"
inkscape:window-x="24"
- inkscape:window-y="24" />
+ inkscape:window-y="22"
+ inkscape:document-rotation="0"
+ inkscape:window-maximized="0" />
@@ -495,7 +497,7 @@
] [-b ] [-B ] [-t warning|error] [-f ] [-v]
+i3-nagbar [-m ] [-b ] [-B ] [-t warning|error] [-f ] [-v] [-p]
== OPTIONS
@@ -39,6 +39,10 @@ i3-sensible-terminal.
Same as above, but will execute the shell commands directly, without launching a
terminal emulator.
+*-p, --primary*::
+Always opens the i3-nagbar on the primary monitor. By default it opens on the
+focused monitor.
+
== DESCRIPTION
i3-nagbar is used by i3 to tell you about errors in your configuration file
diff --git a/man/i3-sensible-terminal.man b/man/i3-sensible-terminal.man
index bda5a7239..1b83e07bf 100644
--- a/man/i3-sensible-terminal.man
+++ b/man/i3-sensible-terminal.man
@@ -23,20 +23,20 @@ It tries to start one of the following (in that order):
* $TERMINAL (this is a non-standard variable)
* x-terminal-emulator (only present on Debian and derivatives)
+* mate-terminal
+* gnome-terminal
+* terminator
+* xfce4-terminal
* urxvt
* rxvt
* termit
-* terminator
* Eterm
* aterm
* uxterm
* xterm
-* gnome-terminal
* roxterm
-* xfce4-terminal
* termite
* lxterminal
-* mate-terminal
* terminology
* st
* qterminal
diff --git a/man/i3.man b/man/i3.man
index 8157ac779..1974f0b9a 100644
--- a/man/i3.man
+++ b/man/i3.man
@@ -44,6 +44,9 @@ Retrieve the i3 IPC socket path from X11, print it, then exit.
Limits the size of the i3 SHM log to bytes. Setting this to 0 disables
SHM logging entirely. The default is 0 bytes.
+--replace::
+Replace an existing window manager.
+
== DESCRIPTION
=== INTRODUCTION
diff --git a/man/i3bar.man b/man/i3bar.man
index d0b0148c7..479e10fcb 100644
--- a/man/i3bar.man
+++ b/man/i3bar.man
@@ -9,7 +9,7 @@ i3bar - xcb-based status- and workspace-bar
== SYNOPSIS
-*i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*]
+*i3bar* [*-b* 'bar_id'] [*-s* 'sock_path'] [*-t*] [*-h*] [*-v*] [*-V*]
== WARNING
@@ -25,14 +25,21 @@ You have been warned!
Overwrites the path to the i3 IPC socket.
*-b, --bar_id* 'bar_id'::
-Specifies the bar ID for which to get the configuration from i3.
+Specifies the bar ID for which to get the configuration from i3. By default,
+i3bar will use the first bar block as configured in i3.
-*-v, --version*::
-Display version number and exit.
+*-t, --transparency*::
+Enable transparency (RGBA colors)
*-h, --help*::
Display a short help-message and exit
+*-v, --version*::
+Display version number and exit.
+
+*-V*::
+Be verbose.
+
== DESCRIPTION
*i3bar* displays a bar at the bottom (or top) of your monitor(s) containing
diff --git a/meson.build b/meson.build
index b052efa45..eb02f369e 100644
--- a/meson.build
+++ b/meson.build
@@ -6,7 +6,7 @@
project(
'i3',
'c',
- version: '4.19.1',
+ version: '4.20',
default_options: [
'c_std=c11',
'warning_level=1', # enable all warnings (-Wall)
@@ -63,7 +63,7 @@ config_h = declare_dependency(
sources: vcs_tag(
input: config_h_in,
output: 'config.h',
- fallback: meson.project_version() + ' (2021-02-01)',
+ fallback: meson.project_version() + ' (2021-10-19)',
)
)
@@ -325,6 +325,8 @@ ev_dep = cc.find_library('ev')
inc = include_directories('include')
libi3srcs = [
+ 'libi3/boolstr.c',
+ 'libi3/create_socket.c',
'libi3/dpi.c',
'libi3/draw_util.c',
'libi3/fake_configure_notify.c',
@@ -341,11 +343,15 @@ libi3srcs = [
'libi3/ipc_recv_message.c',
'libi3/ipc_send_message.c',
'libi3/is_debug_build.c',
+ 'libi3/path_exists.c',
'libi3/resolve_tilde.c',
'libi3/root_atom_contents.c',
'libi3/safewrappers.c',
'libi3/string.c',
'libi3/ucs2_conversion.c',
+ 'libi3/nonblock.c',
+ 'libi3/screenshot_wallpaper.c',
+ 'libi3/is_background_set.c',
]
if not cdata.get('HAVE_STRNDUP')
@@ -514,6 +520,7 @@ executable(
'i3-config-wizard/i3-config-wizard-atoms.xmacro.h',
'i3-config-wizard/main.c',
'i3-config-wizard/xcb.h',
+ config_parser,
],
install: true,
include_directories: include_directories('include', 'i3-config-wizard'),
@@ -642,7 +649,7 @@ if get_option('docs')
'@OUTPUT@',
],
install: true,
- install_dir: join_paths(get_option('datadir'), 'doc', 'i3'),
+ install_dir: docdir,
)
custom_target(
@@ -655,7 +662,7 @@ if get_option('docs')
'@OUTPUT@',
],
install: true,
- install_dir: join_paths(get_option('datadir'), 'doc', 'i3'),
+ install_dir: docdir,
)
endif
@@ -669,7 +676,10 @@ executable(
executable(
'test.commands_parser',
- 'src/commands_parser.c',
+ [
+ 'src/commands_parser.c',
+ command_parser,
+ ],
include_directories: inc,
c_args: '-DTEST_PARSER',
dependencies: common_deps,
@@ -678,7 +688,10 @@ executable(
executable(
'test.config_parser',
- 'src/config_parser.c',
+ [
+ 'src/config_parser.c',
+ config_parser,
+ ],
include_directories: inc,
c_args: '-DTEST_PARSER',
dependencies: common_deps,
@@ -706,6 +719,7 @@ if meson.version().version_compare('>=0.46.0')
anyevent_i3,
i3test_pm,
],
+ timeout: 120, # Default of 30 seconds can cause timeouts on slower machines
)
else
# meson < 0.46.0 does not support the depends arg in test targets.
diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec
index 20de3e442..d53945d4c 100644
--- a/parser-specs/commands.spec
+++ b/parser-specs/commands.spec
@@ -40,6 +40,7 @@ state INITIAL:
'scratchpad' -> SCRATCHPAD
'swap' -> SWAP
'title_format' -> TITLE_FORMAT
+ 'title_window_icon' -> TITLE_WINDOW_ICON
'mode' -> MODE
'bar' -> BAR
'gaps' -> GAPS
@@ -55,7 +56,8 @@ state CRITERIA:
ctype = 'title' -> CRITERION
ctype = 'urgent' -> CRITERION
ctype = 'workspace' -> CRITERION
- ctype = 'tiling', 'floating'
+ ctype = 'machine' -> CRITERION
+ ctype = 'tiling', 'floating', 'all'
-> call cmd_criteria_add($ctype, NULL); CRITERIA
']' -> call cmd_criteria_match_windows(); INITIAL
@@ -402,8 +404,10 @@ state MOVE_WORKSPACE_NUMBER:
-> call cmd_move_con_to_workspace_number($number, $no_auto_back_and_forth)
state MOVE_TO_OUTPUT:
- output = string
- -> call cmd_move_con_to_output($output)
+ output = word
+ -> call cmd_move_con_to_output($output, 0); MOVE_TO_OUTPUT
+ end
+ -> call cmd_move_con_to_output(NULL, 0); INITIAL
state MOVE_TO_MARK:
mark = string
@@ -411,9 +415,13 @@ state MOVE_TO_MARK:
state MOVE_WORKSPACE_TO_OUTPUT:
'output'
- ->
- output = string
- -> call cmd_move_workspace_to_output($output)
+ -> MOVE_WORKSPACE_TO_OUTPUT_WORD
+
+state MOVE_WORKSPACE_TO_OUTPUT_WORD:
+ output = word
+ -> call cmd_move_con_to_output($output, 1); MOVE_WORKSPACE_TO_OUTPUT_WORD
+ end
+ -> call cmd_move_con_to_output(NULL, 1); INITIAL
state MOVE_TO_ABSOLUTE_POSITION:
'position'
@@ -473,6 +481,20 @@ state TITLE_FORMAT:
format = string
-> call cmd_title_format($format)
+state TITLE_WINDOW_ICON:
+ 'padding'
+ -> TITLE_WINDOW_ICON_PADDING
+ enable = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive'
+ -> call cmd_title_window_icon($enable, 0)
+
+state TITLE_WINDOW_ICON_PADDING:
+ end
+ -> call cmd_title_window_icon($enable, &padding)
+ 'px'
+ -> call cmd_title_window_icon($enable, &padding)
+ padding = number
+ ->
+
# bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) []
state BAR:
'hidden_state'
diff --git a/parser-specs/config.spec b/parser-specs/config.spec
index 9f06406e4..2bd3434ef 100644
--- a/parser-specs/config.spec
+++ b/parser-specs/config.spec
@@ -20,6 +20,7 @@ state INITIAL:
'set ' -> IGNORE_LINE
'set ' -> IGNORE_LINE
'set_from_resource' -> IGNORE_LINE
+ 'include' -> INCLUDE
bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING
'bar' -> BARBRACE
'font' -> FONT
@@ -90,6 +91,11 @@ state SMART_GAPS:
enabled = 'inverse_outer'
-> call cfg_smart_gaps($enabled)
+# include
+state INCLUDE:
+ pattern = string
+ -> call cfg_include($pattern)
+
# floating_minimum_size x
state FLOATING_MINIMUM_SIZE_WIDTH:
width = number
@@ -218,9 +224,10 @@ state CRITERIA:
ctype = 'title' -> CRITERION
ctype = 'urgent' -> CRITERION
ctype = 'workspace' -> CRITERION
+ ctype = 'machine' -> CRITERION
ctype = 'floating_from' -> CRITERION_FROM
ctype = 'tiling_from' -> CRITERION_FROM
- ctype = 'tiling', 'floating'
+ ctype = 'tiling', 'floating', 'all'
-> call cfg_criteria_add($ctype, NULL); CRITERIA
']'
-> call cfg_criteria_pop_state()
@@ -423,6 +430,8 @@ state BINDCOMMAND:
->
command = string
-> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
+ end
+ -> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
################################################################################
# Mode configuration
diff --git a/release.sh b/release.sh
index c6b045c0a..134423734 100755
--- a/release.sh
+++ b/release.sh
@@ -1,8 +1,10 @@
#!/bin/zsh
# This script is used to prepare a new release of i3.
+set -eu
+
export RELEASE_VERSION="4.19"
-export PREVIOUS_VERSION="4.18"
+export PREVIOUS_VERSION="4.18.3"
export RELEASE_BRANCH="next"
if [ ! -e "../i3.github.io" ]
@@ -24,12 +26,6 @@ then
exit 1
fi
-if git diff-files --quiet --exit-code debian/changelog
-then
- echo "Expected debian/changelog to be changed (containing the changelog for ${RELEASE_VERSION})."
- exit 1
-fi
-
eval $(gpg-agent --daemon)
export GPG_AGENT_INFO
@@ -52,28 +48,32 @@ if [ ! -e "${STARTDIR}/RELEASE-NOTES-${RELEASE_VERSION}" ]; then
exit 1
fi
git checkout -b release-${RELEASE_VERSION}
+git rm RELEASE-NOTES-*
cp "${STARTDIR}/RELEASE-NOTES-${RELEASE_VERSION}" "RELEASE-NOTES-${RELEASE_VERSION}"
git add RELEASE-NOTES-${RELEASE_VERSION}
-git rm RELEASE-NOTES-${PREVIOUS_VERSION}
-sed -i "s/^\s*version: '${PREVIOUS_VERSION}'/ version: '${RELEASE_VERSION}'/" meson.build
+# Update the release version:
+sed -i "s/^\s*version: '4.[^']*'/ version: '${RELEASE_VERSION}'/" meson.build
+cp meson.build "${TMPDIR}/meson.build"
+# Inject the release date into meson.build for the dist tarball:
+sed -i "s/'-non-git'/' ($(date +'%Y-%m-%d'))'/" meson.build
git commit -a -m "release i3 ${RELEASE_VERSION}"
git tag "${RELEASE_VERSION}" -m "release i3 ${RELEASE_VERSION}" --sign --local-user=0x4AC8EE1D
mkdir build
(cd build && meson .. && ninja dist)
-cp build/meson-build/i3-${RELEASE_VERSION}.tar.xz .
+cp build/meson-dist/i3-${RELEASE_VERSION}.tar.xz .
echo "Differences in the release tarball file lists:"
diff --color -u \
- <(tar tf ../i3-${PREVIOUS_VERSION}.tar.xz | sed "s,i3-${PREVIOUS_VERSION}/,,g" | sort) \
- <(tar tf i3-${RELEASE_VERSION}.tar.xz | sed "s,i3-${RELEASE_VERSION}/,,g" | sort)
+ <(tar tf ../i3-${PREVIOUS_VERSION}.tar.* | sed "s,i3-${PREVIOUS_VERSION}/,,g" | sort) \
+ <(tar tf i3-${RELEASE_VERSION}.tar.xz | sed "s,i3-${RELEASE_VERSION}/,,g" | sort) || true
gpg --armor -b i3-${RELEASE_VERSION}.tar.xz
-echo "${RELEASE_VERSION}-non-git" > I3_VERSION
-git add I3_VERSION
-git commit -a -m "Set non-git version to ${RELEASE_VERSION}-non-git."
+mv "${TMPDIR}/meson.build" .
+git add meson.build
+git commit -a -m "Restore non-git version suffix"
if [ "${RELEASE_BRANCH}" = "stable" ]; then
git checkout stable
@@ -94,17 +94,12 @@ git config --add remote.origin.push "+refs/heads/next:refs/heads/next"
git config --add remote.origin.push "+refs/heads/stable:refs/heads/stable"
################################################################################
-# Section 2: Debian packaging
+# Section 2: Debian packaging (for QA)
################################################################################
cd "${TMPDIR}"
mkdir debian
-# Copy over the changelog because we expect it to be locally modified in the
-# start directory.
-cp "${STARTDIR}/debian/changelog" i3/debian/changelog
-(cd i3 && git add debian/changelog && git commit -m 'Update debian/changelog')
-
cat > ${TMPDIR}/Dockerfile <input_type != b->input_type) {
+ return false;
+ }
+
+ /* Check if one is using keysym while the other is using bindsym. */
+ if ((a->symbol == NULL && b->symbol != NULL) ||
+ (a->symbol != NULL && b->symbol == NULL)) {
+ return false;
+ }
+
+ /* If a is NULL, b has to be NULL, too (see previous conditional).
+ * If the keycodes differ, it can't be a duplicate. */
+ if (a->symbol != NULL &&
+ strcasecmp(a->symbol, b->symbol) != 0) {
+ return false;
+ }
+
+ /* Check if the keycodes or modifiers are different. If so, they
+ * can't be duplicate */
+ if (a->keycode != b->keycode ||
+ a->event_state_mask != b->event_state_mask ||
+ a->release != b->release) {
+ return false;
+ }
+
+ return true;
+}
+
/*
* Checks for duplicate key bindings (the same keycode or keysym is configured
* more than once). If a duplicate binding is found, a message is printed to
@@ -730,31 +764,13 @@ void check_for_duplicate_bindings(struct context *context) {
TAILQ_FOREACH (bind, bindings, bindings) {
/* Abort when we reach the current keybinding, only check the
* bindings before */
- if (bind == current)
+ if (bind == current) {
break;
+ }
- /* Check if the input types are different */
- if (bind->input_type != current->input_type)
- continue;
-
- /* Check if one is using keysym while the other is using bindsym.
- * If so, skip. */
- if ((bind->symbol == NULL && current->symbol != NULL) ||
- (bind->symbol != NULL && current->symbol == NULL))
- continue;
-
- /* If bind is NULL, current has to be NULL, too (see above).
- * If the keycodes differ, it can't be a duplicate. */
- if (bind->symbol != NULL &&
- strcasecmp(bind->symbol, current->symbol) != 0)
- continue;
-
- /* Check if the keycodes or modifiers are different. If so, they
- * can't be duplicate */
- if (bind->keycode != current->keycode ||
- bind->event_state_mask != current->event_state_mask ||
- bind->release != current->release)
+ if (!binding_same_key(bind, current)) {
continue;
+ }
context->has_errors = true;
if (current->keycode != 0) {
diff --git a/src/click.c b/src/click.c
index 40edc1f58..62fb08ca2 100644
--- a/src/click.c
+++ b/src/click.c
@@ -218,8 +218,20 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
goto done;
}
- /* 2: focus this con. */
- con_activate(con);
+ /* 2: focus this con or one of its children. */
+ Con *con_to_focus = con;
+ if (in_stacked && dest == CLICK_DECORATION) {
+ /* If the container is a tab/stacked container and the click happened
+ * on a tab, switch to the tab. If the tab contents were already
+ * focused, focus the tab container itself. If the tab container was
+ * already focused, cycle back to focusing the tab contents. */
+ if (was_focused || !con_has_parent(focused, con)) {
+ while (!TAILQ_EMPTY(&(con_to_focus->focus_head))) {
+ con_to_focus = TAILQ_FIRST(&(con_to_focus->focus_head));
+ }
+ }
+ }
+ con_activate(con_to_focus);
/* 3: For floating containers, we also want to raise them on click.
* We will skip handling events on floating cons in fullscreen mode */
diff --git a/src/commands.c b/src/commands.c
index 7bbd6de77..51c9ce987 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -1024,23 +1024,113 @@ void cmd_mode(I3_CMD, const char *mode) {
}
/*
- * Implementation of 'move [window|container] [to] output '.
+ * Implementation of 'move [window|container|workspace] [to] output '.
*
*/
-void cmd_move_con_to_output(I3_CMD, const char *name) {
- DLOG("Should move window to output \"%s\".\n", name);
+void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
+ /* Initialize a data structure that is used to save multiple user-specified
+ * output names since this function is called multiple types for each
+ * command call. */
+ typedef struct user_output_name {
+ char *name;
+ TAILQ_ENTRY(user_output_name) user_output_names;
+ } user_output_name;
+ static TAILQ_HEAD(user_output_names_head, user_output_name) user_output_names = TAILQ_HEAD_INITIALIZER(user_output_names);
+
+ if (name) {
+ if (strcmp(name, "next") == 0) {
+ /* "next" here works like a wildcard: It "expands" to all available
+ * outputs. */
+ Output *output;
+ TAILQ_FOREACH (output, &outputs, outputs) {
+ user_output_name *co = scalloc(sizeof(user_output_name), 1);
+ co->name = sstrdup(output_primary_name(output));
+ TAILQ_INSERT_TAIL(&user_output_names, co, user_output_names);
+ }
+ return;
+ }
+
+ user_output_name *co = scalloc(sizeof(user_output_name), 1);
+ co->name = sstrdup(name);
+ TAILQ_INSERT_TAIL(&user_output_names, co, user_output_names);
+ return;
+ }
+
HANDLE_EMPTY_MATCH;
+ if (TAILQ_EMPTY(&user_output_names)) {
+ yerror("At least one output must be specified");
+ return;
+ }
+
+ bool success = false;
+ user_output_name *uo;
owindow *current;
- bool had_error = false;
TAILQ_FOREACH (current, &owindows, owindows) {
- DLOG("matching: %p / %s\n", current->con, current->con->name);
+ Con *ws = con_get_workspace(current->con);
+ if (con_is_internal(ws)) {
+ continue;
+ }
+
+ Output *current_output = get_output_for_con(ws);
+
+ Output *target_output = NULL;
+ TAILQ_FOREACH (uo, &user_output_names, user_output_names) {
+ if (strcasecmp(output_primary_name(current_output), uo->name) == 0) {
+ /* The current output is in the user list */
+ while (true) {
+ /* This corrupts the outer loop but it is ok since we are
+ * going to break anyway. */
+ uo = TAILQ_NEXT(uo, user_output_names);
+ if (!uo) {
+ /* We reached the end of the list. We should use the
+ * first available output that, if it exists, is
+ * already saved in target_output. */
+ break;
+ }
+ Output *out = get_output_from_string(current_output, uo->name);
+ if (out) {
+ DLOG("Found next target for workspace %s from user list: %s\n", ws->name, uo->name);
+ target_output = out;
+ break;
+ }
+ }
+ break;
+ }
+ if (!target_output) {
+ /* The first available output from the list is used in 2 cases:
+ * 1. When we must wrap around the user list. For example, if
+ * user specifies outputs A B C and C is `current_output`.
+ * 2. When the current output is not in the user list. For
+ * example, user specifies A B C and D is `current_output`.
+ */
+ DLOG("Found first target for workspace %s from user list: %s\n", ws->name, uo->name);
+ target_output = get_output_from_string(current_output, uo->name);
+ }
+ }
+ if (target_output) {
+ if (move_workspace) {
+ workspace_move_to_output(ws, target_output);
+ } else {
+ con_move_to_output(current->con, target_output, true);
+ }
+ success = true;
+ }
+ }
- had_error |= !con_move_to_output_name(current->con, name, true);
+ while (!TAILQ_EMPTY(&user_output_names)) {
+ uo = TAILQ_FIRST(&user_output_names);
+ free(uo->name);
+ TAILQ_REMOVE(&user_output_names, uo, user_output_names);
+ free(uo);
}
- cmd_output->needs_tree_render = true;
- ysuccess(!had_error);
+ cmd_output->needs_tree_render = success;
+ if (success) {
+ ysuccess(true);
+ } else {
+ yerror("No output matched");
+ }
}
/*
@@ -1094,36 +1184,6 @@ void cmd_floating(I3_CMD, const char *floating_mode) {
ysuccess(true);
}
-/*
- * Implementation of 'move workspace to [output] '.
- *
- */
-void cmd_move_workspace_to_output(I3_CMD, const char *name) {
- DLOG("should move workspace to output %s\n", name);
-
- HANDLE_EMPTY_MATCH;
-
- owindow *current;
- TAILQ_FOREACH (current, &owindows, owindows) {
- Con *ws = con_get_workspace(current->con);
- if (con_is_internal(ws)) {
- continue;
- }
-
- Output *current_output = get_output_for_con(ws);
- Output *target_output = get_output_from_string(current_output, name);
- if (!target_output) {
- yerror("Could not get output from string \"%s\"", name);
- return;
- }
-
- workspace_move_to_output(ws, target_output);
- }
-
- cmd_output->needs_tree_render = true;
- ysuccess(true);
-}
-
/*
* Implementation of 'split v|h|t|vertical|horizontal|toggle'.
*
@@ -1669,6 +1729,9 @@ void cmd_restart(I3_CMD) {
}
ipc_shutdown(SHUTDOWN_REASON_RESTART, exempt_fd);
unlink(config.ipc_socket_path);
+ if (current_log_stream_socket_path != NULL) {
+ unlink(current_log_stream_socket_path);
+ }
/* We need to call this manually since atexit handlers don’t get called
* when exec()ing */
purge_zerobyte_logfile();
@@ -1982,6 +2045,35 @@ void cmd_title_format(I3_CMD, const char *format) {
ysuccess(true);
}
+/*
+ * Implementation of 'title_window_icon ' and 'title_window_icon padding '
+ *
+ */
+void cmd_title_window_icon(I3_CMD, const char *enable, int padding) {
+ if (enable != NULL && !boolstr(enable)) {
+ padding = -1;
+ }
+ DLOG("setting window_icon=%d\n", padding);
+ HANDLE_EMPTY_MATCH;
+
+ owindow *current;
+ TAILQ_FOREACH (current, &owindows, owindows) {
+ DLOG("setting window_icon for %p / %s\n", current->con, current->con->name);
+ current->con->window_icon_padding = padding;
+
+ if (current->con->window != NULL) {
+ /* Make sure the window title is redrawn immediately. */
+ current->con->window->name_x_changed = true;
+ } else {
+ /* For windowless containers we also need to force the redrawing. */
+ FREE(current->con->deco_render_params);
+ }
+ }
+
+ cmd_output->needs_tree_render = true;
+ ysuccess(true);
+}
+
/*
* Implementation of 'rename workspace [] to '
*
diff --git a/src/commands_parser.c b/src/commands_parser.c
index 6c7914151..fd02293dc 100644
--- a/src/commands_parser.c
+++ b/src/commands_parser.c
@@ -56,40 +56,19 @@ typedef struct tokenptr {
#include "GENERATED_command_tokens.h"
-/*******************************************************************************
- * The (small) stack where identified literals are stored during the parsing
- * of a single command (like $workspace).
- ******************************************************************************/
-
-struct stack_entry {
- /* Just a pointer, not dynamically allocated. */
- const char *identifier;
- enum {
- STACK_STR = 0,
- STACK_LONG = 1,
- } type;
- union {
- char *str;
- long num;
- } val;
-};
-
-/* 10 entries should be enough for everybody. */
-static struct stack_entry stack[10];
-
/*
* Pushes a string (identified by 'identifier') on the stack. We simply use a
* single array, since the number of entries we have to store is very small.
*
*/
-static void push_string(const char *identifier, char *str) {
+static void push_string(struct stack *stack, const char *identifier, char *str) {
for (int c = 0; c < 10; c++) {
- if (stack[c].identifier != NULL)
+ if (stack->stack[c].identifier != NULL)
continue;
/* Found a free slot, let’s store it here. */
- stack[c].identifier = identifier;
- stack[c].val.str = str;
- stack[c].type = STACK_STR;
+ stack->stack[c].identifier = identifier;
+ stack->stack[c].val.str = str;
+ stack->stack[c].type = STACK_STR;
return;
}
@@ -103,15 +82,15 @@ static void push_string(const char *identifier, char *str) {
}
// TODO move to a common util
-static void push_long(const char *identifier, long num) {
+static void push_long(struct stack *stack, const char *identifier, long num) {
for (int c = 0; c < 10; c++) {
- if (stack[c].identifier != NULL) {
+ if (stack->stack[c].identifier != NULL) {
continue;
}
- stack[c].identifier = identifier;
- stack[c].val.num = num;
- stack[c].type = STACK_LONG;
+ stack->stack[c].identifier = identifier;
+ stack->stack[c].val.num = num;
+ stack->stack[c].type = STACK_LONG;
return;
}
@@ -125,36 +104,36 @@ static void push_long(const char *identifier, long num) {
}
// TODO move to a common util
-static const char *get_string(const char *identifier) {
+static const char *get_string(struct stack *stack, const char *identifier) {
for (int c = 0; c < 10; c++) {
- if (stack[c].identifier == NULL)
+ if (stack->stack[c].identifier == NULL)
break;
- if (strcmp(identifier, stack[c].identifier) == 0)
- return stack[c].val.str;
+ if (strcmp(identifier, stack->stack[c].identifier) == 0)
+ return stack->stack[c].val.str;
}
return NULL;
}
// TODO move to a common util
-static long get_long(const char *identifier) {
+static long get_long(struct stack *stack, const char *identifier) {
for (int c = 0; c < 10; c++) {
- if (stack[c].identifier == NULL)
+ if (stack->stack[c].identifier == NULL)
break;
- if (strcmp(identifier, stack[c].identifier) == 0)
- return stack[c].val.num;
+ if (strcmp(identifier, stack->stack[c].identifier) == 0)
+ return stack->stack[c].val.num;
}
return 0;
}
// TODO move to a common util
-static void clear_stack(void) {
+static void clear_stack(struct stack *stack) {
for (int c = 0; c < 10; c++) {
- if (stack[c].type == STACK_STR)
- free(stack[c].val.str);
- stack[c].identifier = NULL;
- stack[c].val.str = NULL;
- stack[c].val.num = 0;
+ if (stack->stack[c].type == STACK_STR)
+ free(stack->stack[c].val.str);
+ stack->stack[c].identifier = NULL;
+ stack->stack[c].val.str = NULL;
+ stack->stack[c].val.num = 0;
}
}
@@ -163,9 +142,12 @@ static void clear_stack(void) {
******************************************************************************/
static cmdp_state state;
-#ifndef TEST_PARSER
static Match current_match;
-#endif
+/*******************************************************************************
+ * The (small) stack where identified literals are stored during the parsing
+ * of a single command (like $workspace).
+ ******************************************************************************/
+static struct stack stack;
static struct CommandResultIR subcommand_output;
static struct CommandResultIR command_output;
@@ -176,19 +158,19 @@ static void next_state(const cmdp_token *token) {
subcommand_output.json_gen = command_output.json_gen;
subcommand_output.client = command_output.client;
subcommand_output.needs_tree_render = false;
- GENERATED_call(token->extra.call_identifier, &subcommand_output);
+ GENERATED_call(¤t_match, &stack, token->extra.call_identifier, &subcommand_output);
state = subcommand_output.next_state;
/* If any subcommand requires a tree_render(), we need to make the
* whole parser result request a tree_render(). */
if (subcommand_output.needs_tree_render)
command_output.needs_tree_render = true;
- clear_stack();
+ clear_stack(&stack);
return;
}
state = token->next_state;
if (state == INITIAL) {
- clear_stack();
+ clear_stack(&stack);
}
}
@@ -296,8 +278,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
/* A literal. */
if (token->name[0] == '\'') {
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
- if (token->identifier != NULL)
- push_string(token->identifier, sstrdup(token->name + 1));
+ if (token->identifier != NULL) {
+ push_string(&stack, token->identifier, sstrdup(token->name + 1));
+ }
walk += strlen(token->name) - 1;
next_state(token);
token_handled = true;
@@ -319,8 +302,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
if (end == walk)
continue;
- if (token->identifier != NULL)
- push_long(token->identifier, num);
+ if (token->identifier != NULL) {
+ push_long(&stack, token->identifier, num);
+ }
/* Set walk to the first non-number character */
walk = end;
@@ -333,8 +317,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
strcmp(token->name, "word") == 0) {
char *str = parse_string(&walk, (token->name[0] != 's'));
if (str != NULL) {
- if (token->identifier)
- push_string(token->identifier, str);
+ if (token->identifier) {
+ push_string(&stack, token->identifier, str);
+ }
/* If we are at the end of a quoted string, skip the ending
* double quote. */
if (*walk == '"')
@@ -436,7 +421,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
y(map_close);
free(position);
- clear_stack();
+ clear_stack(&stack);
break;
}
}
diff --git a/src/con.c b/src/con.c
index 445d29001..d8c0e908d 100644
--- a/src/con.c
+++ b/src/con.c
@@ -43,6 +43,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) {
new->window = window;
new->border_style = config.default_border;
new->current_border_width = -1;
+ new->window_icon_padding = -1;
if (window) {
new->depth = window->depth;
} else {
@@ -1873,9 +1874,9 @@ void con_set_layout(Con *con, layout_t layout) {
con_attach(new, con, false);
tree_flatten(croot);
+ con_force_split_parents_redraw(con);
+ return;
}
- con_force_split_parents_redraw(con);
- return;
}
if (layout == L_DEFAULT) {
@@ -2361,20 +2362,25 @@ i3String *con_parse_title_format(Con *con) {
char *title;
char *class;
char *instance;
+ char *machine;
if (win == NULL) {
title = pango_escape_markup(con_get_tree_representation(con));
class = sstrdup("i3-frame");
instance = sstrdup("i3-frame");
+ machine = sstrdup("");
} else {
title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name)));
class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class));
instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance));
+ machine = pango_escape_markup(sstrdup((win->machine == NULL) ? "" : win->machine));
}
placeholder_t placeholders[] = {
{.name = "%title", .value = title},
{.name = "%class", .value = class},
- {.name = "%instance", .value = instance}};
+ {.name = "%instance", .value = instance},
+ {.name = "%machine", .value = machine},
+ };
const size_t num = sizeof(placeholders) / sizeof(placeholder_t);
char *formatted_str = format_placeholders(con->title_format, &placeholders[0], num);
diff --git a/src/config.c b/src/config.c
index 77ba38cbd..003b3dbd4 100644
--- a/src/config.c
+++ b/src/config.c
@@ -10,13 +10,16 @@
*/
#include "all.h"
+#include
+#include
+
#include
char *current_configpath = NULL;
-char *current_config = NULL;
Config config;
struct modes_head modes;
struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);
+struct includedfiles_head included_files = TAILQ_HEAD_INITIALIZER(included_files);
/*
* Ungrabs all keys, to be called before re-grabbing the keys because of a
@@ -231,8 +234,43 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
"$XDG_CONFIG_HOME/i3/config, ~/.i3/config, $XDG_CONFIG_DIRS/i3/config "
"and " SYSCONFDIR "/i3/config)");
}
- LOG("Parsing configfile %s\n", current_configpath);
- const bool result = parse_file(current_configpath, load_type != C_VALIDATE);
+
+ IncludedFile *file;
+ while (!TAILQ_EMPTY(&included_files)) {
+ file = TAILQ_FIRST(&included_files);
+ FREE(file->path);
+ FREE(file->raw_contents);
+ FREE(file->variable_replaced_contents);
+ TAILQ_REMOVE(&included_files, file, files);
+ FREE(file);
+ }
+
+ char resolved_path[PATH_MAX] = {'\0'};
+ if (realpath(current_configpath, resolved_path) == NULL) {
+ die("realpath(%s): %s", current_configpath, strerror(errno));
+ }
+
+ file = scalloc(1, sizeof(IncludedFile));
+ file->path = sstrdup(resolved_path);
+ TAILQ_INSERT_TAIL(&included_files, file, files);
+
+ LOG("Parsing configfile %s\n", resolved_path);
+ struct stack stack;
+ memset(&stack, '\0', sizeof(struct stack));
+ struct parser_ctx ctx = {
+ .use_nagbar = (load_type != C_VALIDATE),
+ .assume_v4 = false,
+ .stack = &stack,
+ };
+ SLIST_INIT(&(ctx.variables));
+ const int result = parse_file(&ctx, resolved_path, file);
+ free_variables(&ctx);
+ if (result == -1) {
+ die("Could not open configuration file: %s\n", strerror(errno));
+ }
+
+ extract_workspace_names_from_bindings();
+ reorder_bindings();
if (config.font.type == FONT_TYPE_NONE && load_type != C_VALIDATE) {
ELOG("You did not specify required configuration option \"font\"\n");
@@ -251,5 +289,5 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
xcb_flush(conn);
}
- return result;
+ return result == 0;
}
diff --git a/src/config_directives.c b/src/config_directives.c
index 52362547a..6d461c340 100644
--- a/src/config_directives.c
+++ b/src/config_directives.c
@@ -9,6 +9,86 @@
*/
#include "all.h"
+#include
+
+/*******************************************************************************
+ * Include functions.
+ ******************************************************************************/
+
+CFGFUN(include, const char *pattern) {
+ DLOG("include %s\n", pattern);
+
+ wordexp_t p;
+ const int ret = wordexp(pattern, &p, 0);
+ if (ret != 0) {
+ ELOG("wordexp(%s): error %d\n", pattern, ret);
+ result->has_errors = true;
+ return;
+ }
+ char **w = p.we_wordv;
+ for (size_t i = 0; i < p.we_wordc; i++) {
+ char resolved_path[PATH_MAX] = {'\0'};
+ if (realpath(w[i], resolved_path) == NULL) {
+ LOG("Skipping %s: %s\n", w[i], strerror(errno));
+ continue;
+ }
+
+ bool skip = false;
+ IncludedFile *file;
+ TAILQ_FOREACH (file, &included_files, files) {
+ if (strcmp(file->path, resolved_path) == 0) {
+ skip = true;
+ break;
+ }
+ }
+ if (skip) {
+ LOG("Skipping file %s (already included)\n", resolved_path);
+ continue;
+ }
+
+ LOG("Including config file %s\n", resolved_path);
+
+ file = scalloc(1, sizeof(IncludedFile));
+ file->path = sstrdup(resolved_path);
+ TAILQ_INSERT_TAIL(&included_files, file, files);
+
+ struct stack stack;
+ memset(&stack, '\0', sizeof(struct stack));
+ struct parser_ctx ctx = {
+ .use_nagbar = result->ctx->use_nagbar,
+ /* The include mechanism was added in v4, so we can skip the
+ * auto-detection and get rid of the risk of detecting the wrong
+ * version in potentially very short include fragments: */
+ .assume_v4 = true,
+ .stack = &stack,
+ .variables = result->ctx->variables,
+ };
+ switch (parse_file(&ctx, resolved_path, file)) {
+ case PARSE_FILE_SUCCESS:
+ break;
+
+ case PARSE_FILE_FAILED:
+ ELOG("including config file %s: %s\n", resolved_path, strerror(errno));
+ /* fallthrough */
+
+ case PARSE_FILE_CONFIG_ERRORS:
+ result->has_errors = true;
+ TAILQ_REMOVE(&included_files, file, files);
+ FREE(file->path);
+ FREE(file->raw_contents);
+ FREE(file->variable_replaced_contents);
+ FREE(file);
+ break;
+
+ default:
+ /* missing case statement */
+ assert(false);
+ break;
+ }
+ }
+ wordfree(&p);
+}
+
/*******************************************************************************
* Criteria functions.
******************************************************************************/
@@ -45,15 +125,6 @@ CFGFUN(criteria_add, const char *ctype, const char *cvalue) {
* Utility functions
******************************************************************************/
-static bool eval_boolstr(const char *str) {
- return (strcasecmp(str, "1") == 0 ||
- strcasecmp(str, "yes") == 0 ||
- strcasecmp(str, "true") == 0 ||
- strcasecmp(str, "on") == 0 ||
- strcasecmp(str, "enable") == 0 ||
- strcasecmp(str, "active") == 0);
-}
-
/*
* A utility function to convert a string containing the group and modifiers to
* the corresponding bit mask.
@@ -305,14 +376,14 @@ CFGFUN(smart_borders, const char *enable) {
if (!strcmp(enable, "no_gaps"))
config.smart_borders = SMART_BORDERS_NO_GAPS;
else
- config.smart_borders = eval_boolstr(enable) ? SMART_BORDERS_ON : SMART_BORDERS_OFF;
+ 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 = eval_boolstr(enable) ? SMART_GAPS_ON : SMART_GAPS_OFF;
+ config.smart_gaps = boolstr(enable) ? SMART_GAPS_ON : SMART_GAPS_OFF;
}
CFGFUN(floating_minimum_size, const long width, const long height) {
@@ -393,14 +464,14 @@ CFGFUN(hide_edge_borders, const char *borders) {
config.hide_edge_borders = HEBM_BOTH;
else if (strcmp(borders, "none") == 0)
config.hide_edge_borders = HEBM_NONE;
- else if (eval_boolstr(borders))
+ else if (boolstr(borders))
config.hide_edge_borders = HEBM_VERTICAL;
else
config.hide_edge_borders = HEBM_NONE;
}
CFGFUN(focus_follows_mouse, const char *value) {
- config.disable_focus_follows_mouse = !eval_boolstr(value);
+ config.disable_focus_follows_mouse = !boolstr(value);
}
CFGFUN(mouse_warping, const char *value) {
@@ -411,11 +482,11 @@ CFGFUN(mouse_warping, const char *value) {
}
CFGFUN(force_xinerama, const char *value) {
- config.force_xinerama = eval_boolstr(value);
+ config.force_xinerama = boolstr(value);
}
CFGFUN(disable_randr15, const char *value) {
- config.disable_randr15 = eval_boolstr(value);
+ config.disable_randr15 = boolstr(value);
}
CFGFUN(focus_wrapping, const char *value) {
@@ -423,7 +494,7 @@ CFGFUN(focus_wrapping, const char *value) {
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
} else if (strcmp(value, "workspace") == 0) {
config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE;
- } else if (eval_boolstr(value)) {
+ } else if (boolstr(value)) {
config.focus_wrapping = FOCUS_WRAPPING_ON;
} else {
config.focus_wrapping = FOCUS_WRAPPING_OFF;
@@ -432,7 +503,7 @@ CFGFUN(focus_wrapping, const char *value) {
CFGFUN(force_focus_wrapping, const char *value) {
/* Legacy syntax. */
- if (eval_boolstr(value)) {
+ if (boolstr(value)) {
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
} else {
/* For "force_focus_wrapping off", don't enable or disable
@@ -444,7 +515,7 @@ CFGFUN(force_focus_wrapping, const char *value) {
}
CFGFUN(workspace_back_and_forth, const char *value) {
- config.workspace_auto_back_and_forth = eval_boolstr(value);
+ config.workspace_auto_back_and_forth = boolstr(value);
}
CFGFUN(fake_outputs, const char *outputs) {
@@ -486,7 +557,7 @@ CFGFUN(title_align, const char *alignment) {
}
CFGFUN(show_marks, const char *value) {
- config.show_marks = eval_boolstr(value);
+ config.show_marks = boolstr(value);
}
static char *current_workspace = NULL;
@@ -676,7 +747,7 @@ CFGFUN(bar_output, const char *output) {
}
CFGFUN(bar_verbose, const char *verbose) {
- current_bar->verbose = eval_boolstr(verbose);
+ current_bar->verbose = boolstr(verbose);
}
CFGFUN(bar_height, const long height) {
@@ -800,11 +871,11 @@ CFGFUN(bar_status_command, const char *command) {
}
CFGFUN(bar_binding_mode_indicator, const char *value) {
- current_bar->hide_binding_mode_indicator = !eval_boolstr(value);
+ current_bar->hide_binding_mode_indicator = !boolstr(value);
}
CFGFUN(bar_workspace_buttons, const char *value) {
- current_bar->hide_workspace_buttons = !eval_boolstr(value);
+ current_bar->hide_workspace_buttons = !boolstr(value);
}
CFGFUN(bar_workspace_min_width, const long width) {
@@ -812,11 +883,11 @@ CFGFUN(bar_workspace_min_width, const long width) {
}
CFGFUN(bar_strip_workspace_numbers, const char *value) {
- current_bar->strip_workspace_numbers = eval_boolstr(value);
+ current_bar->strip_workspace_numbers = boolstr(value);
}
CFGFUN(bar_strip_workspace_name, const char *value) {
- current_bar->strip_workspace_name = eval_boolstr(value);
+ current_bar->strip_workspace_name = boolstr(value);
}
CFGFUN(bar_start) {
diff --git a/src/config_parser.c b/src/config_parser.c
index f78e75f83..860ea86f9 100644
--- a/src/config_parser.c
+++ b/src/config_parser.c
@@ -35,18 +35,14 @@
#include
#include
#include
+#include
#include
-// Macros to make the YAJL API a bit easier to use.
-#define y(x, ...) yajl_gen_##x(command_output.json_gen, ##__VA_ARGS__)
-#define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char *)str, strlen(str))
-
xcb_xrm_database_t *database = NULL;
#ifndef TEST_PARSER
pid_t config_error_nagbar_pid = -1;
-static struct context *context;
#endif
/*******************************************************************************
@@ -76,46 +72,25 @@ typedef struct tokenptr {
#include "GENERATED_config_tokens.h"
-/*******************************************************************************
- * The (small) stack where identified literals are stored during the parsing
- * of a single command (like $workspace).
- ******************************************************************************/
-
-struct stack_entry {
- /* Just a pointer, not dynamically allocated. */
- const char *identifier;
- enum {
- STACK_STR = 0,
- STACK_LONG = 1,
- } type;
- union {
- char *str;
- long num;
- } val;
-};
-
-/* 10 entries should be enough for everybody. */
-static struct stack_entry stack[10];
-
/*
* Pushes a string (identified by 'identifier') on the stack. We simply use a
* single array, since the number of entries we have to store is very small.
*
*/
-static void push_string(const char *identifier, const char *str) {
+static void push_string(struct stack *ctx, const char *identifier, const char *str) {
for (int c = 0; c < 10; c++) {
- if (stack[c].identifier != NULL &&
- strcmp(stack[c].identifier, identifier) != 0)
+ if (ctx->stack[c].identifier != NULL &&
+ strcmp(ctx->stack[c].identifier, identifier) != 0)
continue;
- if (stack[c].identifier == NULL) {
+ if (ctx->stack[c].identifier == NULL) {
/* Found a free slot, let’s store it here. */
- stack[c].identifier = identifier;
- stack[c].val.str = sstrdup(str);
- stack[c].type = STACK_STR;
+ ctx->stack[c].identifier = identifier;
+ ctx->stack[c].val.str = sstrdup(str);
+ ctx->stack[c].type = STACK_STR;
} else {
/* Append the value. */
- char *prev = stack[c].val.str;
- sasprintf(&(stack[c].val.str), "%s,%s", prev, str);
+ char *prev = ctx->stack[c].val.str;
+ sasprintf(&(ctx->stack[c].val.str), "%s,%s", prev, str);
free(prev);
}
return;
@@ -130,14 +105,15 @@ static void push_string(const char *identifier, const char *str) {
exit(EXIT_FAILURE);
}
-static void push_long(const char *identifier, long num) {
+static void push_long(struct stack *ctx, const char *identifier, long num) {
for (int c = 0; c < 10; c++) {
- if (stack[c].identifier != NULL)
+ if (ctx->stack[c].identifier != NULL) {
continue;
+ }
/* Found a free slot, let’s store it here. */
- stack[c].identifier = identifier;
- stack[c].val.num = num;
- stack[c].type = STACK_LONG;
+ ctx->stack[c].identifier = identifier;
+ ctx->stack[c].val.num = num;
+ ctx->stack[c].type = STACK_LONG;
return;
}
@@ -150,33 +126,33 @@ static void push_long(const char *identifier, long num) {
exit(EXIT_FAILURE);
}
-static const char *get_string(const char *identifier) {
+static const char *get_string(struct stack *ctx, const char *identifier) {
for (int c = 0; c < 10; c++) {
- if (stack[c].identifier == NULL)
+ if (ctx->stack[c].identifier == NULL)
break;
- if (strcmp(identifier, stack[c].identifier) == 0)
- return stack[c].val.str;
+ if (strcmp(identifier, ctx->stack[c].identifier) == 0)
+ return ctx->stack[c].val.str;
}
return NULL;
}
-static long get_long(const char *identifier) {
+static long get_long(struct stack *ctx, const char *identifier) {
for (int c = 0; c < 10; c++) {
- if (stack[c].identifier == NULL)
+ if (ctx->stack[c].identifier == NULL)
break;
- if (strcmp(identifier, stack[c].identifier) == 0)
- return stack[c].val.num;
+ if (strcmp(identifier, ctx->stack[c].identifier) == 0)
+ return ctx->stack[c].val.num;
}
return 0;
}
-static void clear_stack(void) {
+static void clear_stack(struct stack *ctx) {
for (int c = 0; c < 10; c++) {
- if (stack[c].type == STACK_STR)
- free(stack[c].val.str);
- stack[c].identifier = NULL;
- stack[c].val.str = NULL;
- stack[c].val.num = 0;
+ if (ctx->stack[c].type == STACK_STR)
+ free(ctx->stack[c].val.str);
+ ctx->stack[c].identifier = NULL;
+ ctx->stack[c].val.str = NULL;
+ ctx->stack[c].val.num = 0;
}
}
@@ -184,50 +160,42 @@ static void clear_stack(void) {
* The parser itself.
******************************************************************************/
-static cmdp_state state;
-static Match current_match;
-static struct ConfigResultIR subcommand_output;
-static struct ConfigResultIR command_output;
-
-/* A list which contains the states that lead to the current state, e.g.
- * INITIAL, WORKSPACE_LAYOUT.
- * When jumping back to INITIAL, statelist_idx will simply be set to 1
- * (likewise for other states, e.g. MODE or BAR).
- * This list is used to process the nearest error token. */
-static cmdp_state statelist[10] = {INITIAL};
-/* NB: statelist_idx points to where the next entry will be inserted */
-static int statelist_idx = 1;
-
#include "GENERATED_config_call.h"
-static void next_state(const cmdp_token *token) {
+static void next_state(const cmdp_token *token, struct parser_ctx *ctx) {
cmdp_state _next_state = token->next_state;
//printf("token = name %s identifier %s\n", token->name, token->identifier);
//printf("next_state = %d\n", token->next_state);
if (token->next_state == __CALL) {
- subcommand_output.json_gen = command_output.json_gen;
- GENERATED_call(token->extra.call_identifier, &subcommand_output);
+ struct ConfigResultIR subcommand_output = {
+ .ctx = ctx,
+ };
+ GENERATED_call(&(ctx->current_match), ctx->stack, token->extra.call_identifier, &subcommand_output);
+ if (subcommand_output.has_errors) {
+ ctx->has_errors = true;
+ }
_next_state = subcommand_output.next_state;
- clear_stack();
+ clear_stack(ctx->stack);
}
- state = _next_state;
- if (state == INITIAL) {
- clear_stack();
+ ctx->state = _next_state;
+ if (ctx->state == INITIAL) {
+ clear_stack(ctx->stack);
}
/* See if we are jumping back to a state in which we were in previously
* (statelist contains INITIAL) and just move statelist_idx accordingly. */
- for (int i = 0; i < statelist_idx; i++) {
- if (statelist[i] != _next_state)
+ for (int i = 0; i < ctx->statelist_idx; i++) {
+ if ((cmdp_state)(ctx->statelist[i]) != _next_state) {
continue;
- statelist_idx = i + 1;
+ }
+ ctx->statelist_idx = i + 1;
return;
}
/* Otherwise, the state is new and we add it to the list */
- statelist[statelist_idx++] = _next_state;
+ ctx->statelist[ctx->statelist_idx++] = _next_state;
}
/*
@@ -257,7 +225,7 @@ static char *single_line(const char *start) {
return result;
}
-struct ConfigResultIR *parse_config(const char *input, struct context *context) {
+static void parse_config(struct parser_ctx *ctx, const char *input, struct context *context) {
/* Dump the entire config file into the debug log. We cannot just use
* DLOG("%s", input); because one log message must not exceed 4 KiB. */
const char *dumpwalk = input;
@@ -273,13 +241,11 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
}
linecnt++;
}
- state = INITIAL;
- statelist_idx = 1;
-
- /* A YAJL JSON generator used for formatting replies. */
- command_output.json_gen = yajl_gen_alloc(NULL);
-
- y(array_open);
+ ctx->state = INITIAL;
+ for (int i = 0; i < 10; i++) {
+ ctx->statelist[i] = INITIAL;
+ }
+ ctx->statelist_idx = 1;
const char *walk = input;
const size_t len = strlen(input);
@@ -290,7 +256,10 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
// TODO: make this testable
#ifndef TEST_PARSER
- cfg_criteria_init(¤t_match, &subcommand_output, INITIAL);
+ struct ConfigResultIR subcommand_output = {
+ .ctx = ctx,
+ };
+ cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
#endif
/* The "<=" operator is intentional: We also handle the terminating 0-byte
@@ -303,7 +272,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
//printf("remaining input: %s\n", walk);
- cmdp_token_ptr *ptr = &(tokens[state]);
+ cmdp_token_ptr *ptr = &(tokens[ctx->state]);
token_handled = false;
for (c = 0; c < ptr->n; c++) {
token = &(ptr->array[c]);
@@ -311,10 +280,11 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
/* A literal. */
if (token->name[0] == '\'') {
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
- if (token->identifier != NULL)
- push_string(token->identifier, token->name + 1);
+ if (token->identifier != NULL) {
+ push_string(ctx->stack, token->identifier, token->name + 1);
+ }
walk += strlen(token->name) - 1;
- next_state(token);
+ next_state(token, ctx);
token_handled = true;
break;
}
@@ -334,12 +304,13 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
if (end == walk)
continue;
- if (token->identifier != NULL)
- push_long(token->identifier, num);
+ if (token->identifier != NULL) {
+ push_long(ctx->stack, token->identifier, num);
+ }
/* Set walk to the first non-number character */
walk = end;
- next_state(token);
+ next_state(token, ctx);
token_handled = true;
break;
}
@@ -382,14 +353,15 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
inpos++;
str[outpos] = beginning[inpos];
}
- if (token->identifier)
- push_string(token->identifier, str);
+ if (token->identifier) {
+ push_string(ctx->stack, token->identifier, str);
+ }
free(str);
/* If we are at the end of a quoted string, skip the ending
* double quote. */
if (*walk == '"')
walk++;
- next_state(token);
+ next_state(token, ctx);
token_handled = true;
break;
}
@@ -398,7 +370,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
if (strcmp(token->name, "line") == 0) {
while (*walk != '\0' && *walk != '\n' && *walk != '\r')
walk++;
- next_state(token);
+ next_state(token, ctx);
token_handled = true;
linecnt++;
walk++;
@@ -408,7 +380,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
if (strcmp(token->name, "end") == 0) {
//printf("checking for end: *%s*\n", walk);
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
- next_state(token);
+ next_state(token, ctx);
token_handled = true;
/* To make sure we start with an appropriate matching
* datastructure for commands which do *not* specify any
@@ -416,7 +388,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
* every command. */
// TODO: make this testable
#ifndef TEST_PARSER
- cfg_criteria_init(¤t_match, &subcommand_output, INITIAL);
+ cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
#endif
linecnt++;
walk++;
@@ -515,41 +487,24 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
context->has_errors = true;
- /* Format this error message as a JSON reply. */
- y(map_open);
- ystr("success");
- y(bool, false);
- /* We set parse_error to true to distinguish this from other
- * errors. i3-nagbar is spawned upon keypresses only for parser
- * errors. */
- ystr("parse_error");
- y(bool, true);
- ystr("error");
- ystr(errormessage);
- ystr("input");
- ystr(input);
- ystr("errorposition");
- ystr(position);
- y(map_close);
-
/* Skip the rest of this line, but continue parsing. */
while ((size_t)(walk - input) <= len && *walk != '\n')
walk++;
free(position);
free(errormessage);
- clear_stack();
+ clear_stack(ctx->stack);
/* To figure out in which state to go (e.g. MODE or INITIAL),
* we find the nearest state which contains an token
* and follow that one. */
bool error_token_found = false;
- for (int i = statelist_idx - 1; (i >= 0) && !error_token_found; i--) {
- cmdp_token_ptr *errptr = &(tokens[statelist[i]]);
+ for (int i = ctx->statelist_idx - 1; (i >= 0) && !error_token_found; i--) {
+ cmdp_token_ptr *errptr = &(tokens[ctx->statelist[i]]);
for (int j = 0; j < errptr->n; j++) {
if (strcmp(errptr->array[j].name, "error") != 0)
continue;
- next_state(&(errptr->array[j]));
+ next_state(&(errptr->array[j]), ctx);
error_token_found = true;
break;
}
@@ -558,10 +513,6 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
assert(error_token_found);
}
}
-
- y(array_close);
-
- return &command_output;
}
/*******************************************************************************
@@ -612,9 +563,17 @@ int main(int argc, char *argv[]) {
fprintf(stderr, "Syntax: %s \n", argv[0]);
return 1;
}
+ struct stack stack;
+ memset(&stack, '\0', sizeof(struct stack));
+ struct parser_ctx ctx = {
+ .use_nagbar = false,
+ .assume_v4 = false,
+ .stack = &stack,
+ };
+ SLIST_INIT(&(ctx.variables));
struct context context;
context.filename = "";
- parse_config(argv[1], &context);
+ parse_config(&ctx, argv[1], &context);
}
#else
@@ -636,6 +595,7 @@ static int detect_version(char *buf) {
/* check for some v4-only statements */
if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
+ strncasecmp(line, "include", strlen("include")) == 0 ||
strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
@@ -877,34 +837,63 @@ static char *get_resource(char *name) {
return resource;
}
+/*
+ * Releases the memory of all variables in ctx.
+ *
+ */
+void free_variables(struct parser_ctx *ctx) {
+ struct Variable *current;
+ while (!SLIST_EMPTY(&(ctx->variables))) {
+ current = SLIST_FIRST(&(ctx->variables));
+ FREE(current->key);
+ FREE(current->value);
+ SLIST_REMOVE_HEAD(&(ctx->variables), variables);
+ FREE(current);
+ }
+}
+
/*
* Parses the given file by first replacing the variables, then calling
* parse_config and possibly launching i3-nagbar.
*
*/
-bool parse_file(const char *f, bool use_nagbar) {
- struct variables_head variables = SLIST_HEAD_INITIALIZER(&variables);
+parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file) {
int fd;
struct stat stbuf;
char *buf;
FILE *fstr;
char buffer[4096], key[512], value[4096], *continuation = NULL;
- if ((fd = open(f, O_RDONLY)) == -1)
- die("Could not open configuration file: %s\n", strerror(errno));
+ char *old_dir = get_current_dir_name();
+ char *dir = NULL;
+ /* dirname(3) might modify the buffer, so make a copy: */
+ char *dirbuf = sstrdup(f);
+ if ((dir = dirname(dirbuf)) != NULL) {
+ LOG("Changing working directory to config file directory %s\n", dir);
+ if (chdir(dir) == -1) {
+ ELOG("chdir(%s) failed: %s\n", dir, strerror(errno));
+ return PARSE_FILE_FAILED;
+ }
+ }
+ free(dirbuf);
- if (fstat(fd, &stbuf) == -1)
- die("Could not fstat file: %s\n", strerror(errno));
+ if ((fd = open(f, O_RDONLY)) == -1) {
+ return PARSE_FILE_FAILED;
+ }
+
+ if (fstat(fd, &stbuf) == -1) {
+ return PARSE_FILE_FAILED;
+ }
buf = scalloc(stbuf.st_size + 1, 1);
- if ((fstr = fdopen(fd, "r")) == NULL)
- die("Could not fdopen: %s\n", strerror(errno));
+ if ((fstr = fdopen(fd, "r")) == NULL) {
+ return PARSE_FILE_FAILED;
+ }
- FREE(current_config);
- current_config = scalloc(stbuf.st_size + 1, 1);
- if ((ssize_t)fread(current_config, 1, stbuf.st_size, fstr) != stbuf.st_size) {
- die("Could not fread: %s\n", strerror(errno));
+ included_file->raw_contents = scalloc(stbuf.st_size + 1, 1);
+ if ((ssize_t)fread(included_file->raw_contents, 1, stbuf.st_size, fstr) != stbuf.st_size) {
+ return PARSE_FILE_FAILED;
}
rewind(fstr);
@@ -916,7 +905,7 @@ bool parse_file(const char *f, bool use_nagbar) {
if (fgets(continuation, sizeof(buffer) - (continuation - buffer), fstr) == NULL) {
if (feof(fstr))
break;
- die("Could not read configuration file\n");
+ return PARSE_FILE_FAILED;
}
if (buffer[strlen(buffer) - 1] != '\n' && !feof(fstr)) {
ELOG("Your line continuation is too long, it exceeds %zd bytes\n", sizeof(buffer));
@@ -960,7 +949,7 @@ bool parse_file(const char *f, bool use_nagbar) {
continue;
}
- upsert_variable(&variables, v_key, v_value);
+ upsert_variable(&(ctx->variables), v_key, v_value);
continue;
} else if (strcasecmp(key, "set_from_resource") == 0) {
char res_name[512] = {'\0'};
@@ -993,7 +982,7 @@ bool parse_file(const char *f, bool use_nagbar) {
res_value = sstrdup(fallback);
}
- upsert_variable(&variables, v_key, res_value);
+ upsert_variable(&(ctx->variables), v_key, res_value);
FREE(res_value);
continue;
}
@@ -1014,7 +1003,7 @@ bool parse_file(const char *f, bool use_nagbar) {
* variables (otherwise we will count them twice, which is bad when
* 'extra' is negative) */
char *bufcopy = sstrdup(buf);
- SLIST_FOREACH (current, &variables, variables) {
+ SLIST_FOREACH (current, &(ctx->variables), variables) {
int extra = (strlen(current->value) - strlen(current->key));
char *next;
for (next = bufcopy;
@@ -1034,12 +1023,12 @@ bool parse_file(const char *f, bool use_nagbar) {
destwalk = new;
while (walk < (buf + stbuf.st_size)) {
/* Find the next variable */
- SLIST_FOREACH (current, &variables, variables) {
+ SLIST_FOREACH (current, &(ctx->variables), variables) {
current->next_match = strcasestr(walk, current->key);
}
nearest = NULL;
int distance = stbuf.st_size;
- SLIST_FOREACH (current, &variables, variables) {
+ SLIST_FOREACH (current, &(ctx->variables), variables) {
if (current->next_match == NULL)
continue;
if ((current->next_match - walk) < distance) {
@@ -1064,7 +1053,10 @@ bool parse_file(const char *f, bool use_nagbar) {
/* analyze the string to find out whether this is an old config file (3.x)
* or a new config file (4.x). If it’s old, we run the converter script. */
- int version = detect_version(buf);
+ int version = 4;
+ if (!ctx->assume_v4) {
+ version = detect_version(buf);
+ }
if (version == 3) {
/* We need to convert this v3 configuration */
char *converted = migrate_config(new, strlen(new));
@@ -1090,17 +1082,18 @@ bool parse_file(const char *f, bool use_nagbar) {
}
}
- context = scalloc(1, sizeof(struct context));
- context->filename = f;
+ included_file->variable_replaced_contents = sstrdup(new);
- struct ConfigResultIR *config_output = parse_config(new, context);
- yajl_gen_free(config_output->json_gen);
+ struct context *context = scalloc(1, sizeof(struct context));
+ context->filename = f;
+ parse_config(ctx, new, context);
+ if (ctx->has_errors) {
+ context->has_errors = true;
+ }
- extract_workspace_names_from_bindings();
check_for_duplicate_bindings(context);
- reorder_bindings();
- if (use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) {
+ if (ctx->use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) {
ELOG("FYI: You are using i3 version %s\n", i3_version);
if (version == 3)
ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
@@ -1108,22 +1101,22 @@ bool parse_file(const char *f, bool use_nagbar) {
start_config_error_nagbar(f, context->has_errors || invalid_sets);
}
- bool has_errors = context->has_errors;
+ const bool has_errors = context->has_errors;
FREE(context->line_copy);
free(context);
free(new);
free(buf);
- while (!SLIST_EMPTY(&variables)) {
- current = SLIST_FIRST(&variables);
- FREE(current->key);
- FREE(current->value);
- SLIST_REMOVE_HEAD(&variables, variables);
- FREE(current);
+ if (chdir(old_dir) == -1) {
+ ELOG("chdir(%s) failed: %s\n", old_dir, strerror(errno));
+ return PARSE_FILE_FAILED;
}
-
- return !has_errors;
+ free(old_dir);
+ if (has_errors) {
+ return PARSE_FILE_CONFIG_ERRORS;
+ }
+ return PARSE_FILE_SUCCESS;
}
#endif
diff --git a/src/display_version.c b/src/display_version.c
index 32250c155..bced4c191 100644
--- a/src/display_version.c
+++ b/src/display_version.c
@@ -14,22 +14,34 @@
#include
#include
-static bool human_readable_key, loaded_config_file_name_key;
-static char *human_readable_version, *loaded_config_file_name;
+static bool human_readable_key;
+static bool loaded_config_file_name_key;
+static bool included_config_file_names;
+
+static char *human_readable_version;
+static char *loaded_config_file_name;
static int version_string(void *ctx, const unsigned char *val, size_t len) {
- if (human_readable_key)
+ if (human_readable_key) {
sasprintf(&human_readable_version, "%.*s", (int)len, val);
- if (loaded_config_file_name_key)
+ }
+ if (loaded_config_file_name_key) {
sasprintf(&loaded_config_file_name, "%.*s", (int)len, val);
+ }
+ if (included_config_file_names) {
+ IncludedFile *file = scalloc(1, sizeof(IncludedFile));
+ sasprintf(&(file->path), "%.*s", (int)len, val);
+ TAILQ_INSERT_TAIL(&included_files, file, files);
+ }
return 1;
}
static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
- human_readable_key = (stringlen == strlen("human_readable") &&
- strncmp((const char *)stringval, "human_readable", strlen("human_readable")) == 0);
- loaded_config_file_name_key = (stringlen == strlen("loaded_config_file_name") &&
- strncmp((const char *)stringval, "loaded_config_file_name", strlen("loaded_config_file_name")) == 0);
+#define KEY_MATCHES(x) (stringlen == strlen(x) && strncmp((const char *)stringval, x, strlen(x)) == 0)
+ human_readable_key = KEY_MATCHES("human_readable");
+ loaded_config_file_name_key = KEY_MATCHES("loaded_config_file_name");
+ included_config_file_names = KEY_MATCHES("included_config_file_names");
+#undef KEY_MATCHES
return 1;
}
@@ -38,6 +50,22 @@ static yajl_callbacks version_callbacks = {
.yajl_map_key = version_map_key,
};
+static void print_config_path(const char *path, const char *role) {
+ struct stat sb;
+ time_t now;
+ char mtime[64];
+
+ printf(" %s (%s)", path, role);
+ if (stat(path, &sb) == -1) {
+ printf("\n");
+ ELOG("Cannot stat config file \"%s\"\n", path);
+ } else {
+ strftime(mtime, sizeof(mtime), "%c", localtime(&(sb.st_mtime)));
+ time(&now);
+ printf(" (last modified: %s, %.f seconds ago)\n", mtime, difftime(now, sb.st_mtime));
+ }
+}
+
/*
* Connects to i3 to find out the currently running version. Useful since it
* might be different from the version compiled into this binary (maybe the
@@ -98,17 +126,11 @@ void display_running_version(void) {
printf("Running i3 version: %s (pid %s)\n", human_readable_version, pid_from_atom);
if (loaded_config_file_name) {
- struct stat sb;
- time_t now;
- char mtime[64];
- printf("Loaded i3 config: %s", loaded_config_file_name);
- if (stat(loaded_config_file_name, &sb) == -1) {
- printf("\n");
- ELOG("Cannot stat config file \"%s\"\n", loaded_config_file_name);
- } else {
- strftime(mtime, sizeof(mtime), "%c", localtime(&(sb.st_mtime)));
- time(&now);
- printf(" (Last modified: %s, %.f seconds ago)\n", mtime, difftime(now, sb.st_mtime));
+ printf("Loaded i3 config:\n");
+ print_config_path(loaded_config_file_name, "main");
+ IncludedFile *file;
+ TAILQ_FOREACH (file, &included_files, files) {
+ print_config_path(file->path, "included");
}
}
diff --git a/src/ewmh.c b/src/ewmh.c
index c61fb5f39..ba91093db 100644
--- a/src/ewmh.c
+++ b/src/ewmh.c
@@ -278,10 +278,10 @@ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows) {
*/
void ewmh_update_sticky(xcb_window_t window, bool sticky) {
if (sticky) {
- DLOG("Setting _NET_WM_STATE_STICKY for window = %d.\n", window);
+ DLOG("Setting _NET_WM_STATE_STICKY for window = %08x.\n", window);
xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
} else {
- DLOG("Removing _NET_WM_STATE_STICKY for window = %d.\n", window);
+ DLOG("Removing _NET_WM_STATE_STICKY for window = %08x.\n", window);
xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
}
}
@@ -292,10 +292,10 @@ void ewmh_update_sticky(xcb_window_t window, bool sticky) {
*/
void ewmh_update_focused(xcb_window_t window, bool is_focused) {
if (is_focused) {
- DLOG("Setting _NET_WM_STATE_FOCUSED for window = %d.\n", window);
+ DLOG("Setting _NET_WM_STATE_FOCUSED for window = %08x.\n", window);
xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
} else {
- DLOG("Removing _NET_WM_STATE_FOCUSED for window = %d.\n", window);
+ DLOG("Removing _NET_WM_STATE_FOCUSED for window = %08x.\n", window);
xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
}
}
diff --git a/src/handlers.c b/src/handlers.c
index 5cffea1d9..884eaf2d5 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -818,12 +818,12 @@ static void handle_client_message(xcb_client_message_event_t *event) {
if (event->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) {
/* For compatiblity reasons, Wine will request iconic state and cannot ensure that the WM has agreed on it;
* immediately revert to normal to avoid being stuck in a paused state. */
- DLOG("Client has requested iconic state, rejecting. (window = %d)\n", event->window);
+ DLOG("Client has requested iconic state, rejecting. (window = %08x)\n", event->window);
long data[] = {XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE};
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, event->window,
A_WM_STATE, A_WM_STATE, 32, 2, data);
} else {
- DLOG("Not handling WM_CHANGE_STATE request. (window = %d, state = %d)\n", event->window, event->data.data32[0]);
+ DLOG("Not handling WM_CHANGE_STATE request. (window = %08x, state = %d)\n", event->window, event->data.data32[0]);
}
} else if (event->type == A__NET_CURRENT_DESKTOP) {
/* This request is used by pagers and bars to change the current
@@ -891,7 +891,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
tree_close_internal(con, KILL_WINDOW, false);
tree_render();
} else {
- DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window);
+ DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %08x)\n", event->window);
}
} else if (event->type == A__NET_WM_MOVERESIZE) {
/*
@@ -900,7 +900,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
*/
Con *con = con_by_window_id(event->window);
if (!con || !con_is_floating(con)) {
- DLOG("Couldn't find con for _NET_WM_MOVERESIZE request, or con not floating (window = %d)\n", event->window);
+ DLOG("Couldn't find con for _NET_WM_MOVERESIZE request, or con not floating (window = %08x)\n", event->window);
return;
}
DLOG("Handling _NET_WM_MOVERESIZE request (con = %p)\n", con);
@@ -1073,6 +1073,76 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
tree_render();
}
+/*
+ * Log FocusOut events.
+ *
+ */
+static void handle_focus_out(xcb_focus_in_event_t *event) {
+ Con *con = con_by_window_id(event->event);
+ const char *window_name, *mode, *detail;
+
+ if (con != NULL) {
+ window_name = con->name;
+ if (window_name == NULL) {
+ window_name = "";
+ }
+ } else if (event->event == root) {
+ window_name = "";
+ } else {
+ window_name = "";
+ }
+
+ switch (event->mode) {
+ case XCB_NOTIFY_MODE_NORMAL:
+ mode = "Normal";
+ break;
+ case XCB_NOTIFY_MODE_GRAB:
+ mode = "Grab";
+ break;
+ case XCB_NOTIFY_MODE_UNGRAB:
+ mode = "Ungrab";
+ break;
+ case XCB_NOTIFY_MODE_WHILE_GRABBED:
+ mode = "WhileGrabbed";
+ break;
+ default:
+ mode = "";
+ break;
+ }
+
+ switch (event->detail) {
+ case XCB_NOTIFY_DETAIL_ANCESTOR:
+ detail = "Ancestor";
+ break;
+ case XCB_NOTIFY_DETAIL_VIRTUAL:
+ detail = "Virtual";
+ break;
+ case XCB_NOTIFY_DETAIL_INFERIOR:
+ detail = "Inferior";
+ break;
+ case XCB_NOTIFY_DETAIL_NONLINEAR:
+ detail = "Nonlinear";
+ break;
+ case XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL:
+ detail = "NonlinearVirtual";
+ break;
+ case XCB_NOTIFY_DETAIL_POINTER:
+ detail = "Pointer";
+ break;
+ case XCB_NOTIFY_DETAIL_POINTER_ROOT:
+ detail = "PointerRoot";
+ break;
+ case XCB_NOTIFY_DETAIL_NONE:
+ detail = "NONE";
+ break;
+ default:
+ detail = "unknown";
+ break;
+ }
+
+ DLOG("focus change out: window 0x%08x (con %p, %s) lost focus with detail=%s, mode=%s\n", event->event, con, window_name, detail, mode);
+}
+
/*
* Handles ConfigureNotify events for the root window, which are generated when
* the monitor configuration changed.
@@ -1089,6 +1159,23 @@ static void handle_configure_notify(xcb_configure_notify_event_t *event) {
return;
}
randr_query_outputs();
+
+ ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
+}
+
+/*
+ * Handles SelectionClear events for the root window, which are generated when
+ * we lose ownership of a selection.
+ */
+static void handle_selection_clear(xcb_selection_clear_event_t *event) {
+ if (event->selection != wm_sn) {
+ DLOG("SelectionClear for unknown selection %d, ignoring\n", event->selection);
+ return;
+ }
+ LOG("Lost WM_Sn selection, exiting.\n");
+ exit(EXIT_SUCCESS);
+
+ /* unreachable */
}
/*
@@ -1101,6 +1188,16 @@ static bool handle_class_change(Con *con, xcb_get_property_reply_t *prop) {
return true;
}
+/*
+ * Handles the WM_CLIENT_MACHINE property for assignments and criteria selection.
+ *
+ */
+static bool handle_machine_change(Con *con, xcb_get_property_reply_t *prop) {
+ window_update_machine(con->window, prop);
+ con = remanage_window(con);
+ return true;
+}
+
/*
* Handles the _MOTIF_WM_HINTS property of specifing window deocration settings.
*
@@ -1190,6 +1287,14 @@ static bool handle_i3_floating(Con *con, xcb_get_property_reply_t *prop) {
return true;
}
+static bool handle_windowicon_change(Con *con, xcb_get_property_reply_t *prop) {
+ window_update_icon(con->window, prop);
+
+ x_push_changes(croot);
+
+ return true;
+}
+
/* Returns false if the event could not be processed (e.g. the window could not
* be found), true otherwise */
typedef bool (*cb_property_handler_t)(Con *con, xcb_get_property_reply_t *property);
@@ -1212,7 +1317,9 @@ static struct property_handler_t property_handlers[] = {
{0, UINT_MAX, handle_strut_partial_change},
{0, UINT_MAX, handle_window_type},
{0, UINT_MAX, handle_i3_floating},
- {0, 5 * sizeof(uint64_t), handle_motif_hints_change}};
+ {0, 128, handle_machine_change},
+ {0, 5 * sizeof(uint64_t), handle_motif_hints_change},
+ {0, UINT_MAX, handle_windowicon_change}};
#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
/*
@@ -1234,7 +1341,9 @@ void property_handlers_init(void) {
property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL;
property_handlers[9].atom = A__NET_WM_WINDOW_TYPE;
property_handlers[10].atom = A_I3_FLOATING_WINDOW;
- property_handlers[11].atom = A__MOTIF_WM_HINTS;
+ property_handlers[11].atom = XCB_ATOM_WM_CLIENT_MACHINE;
+ property_handlers[12].atom = A__MOTIF_WM_HINTS;
+ property_handlers[13].atom = A__NET_WM_ICON;
}
static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
@@ -1410,6 +1519,10 @@ void handle_event(int type, xcb_generic_event_t *event) {
handle_focus_in((xcb_focus_in_event_t *)event);
break;
+ case XCB_FOCUS_OUT:
+ handle_focus_out((xcb_focus_out_event_t *)event);
+ break;
+
case XCB_PROPERTY_NOTIFY: {
xcb_property_notify_event_t *e = (xcb_property_notify_event_t *)event;
last_timestamp = e->time;
@@ -1421,6 +1534,10 @@ void handle_event(int type, xcb_generic_event_t *event) {
handle_configure_notify((xcb_configure_notify_event_t *)event);
break;
+ case XCB_SELECTION_CLEAR:
+ handle_selection_clear((xcb_selection_clear_event_t *)event);
+ break;
+
default:
//DLOG("Unhandled event of type %d\n", type);
break;
diff --git a/src/ipc.c b/src/ipc.c
index eac4d9b38..147332108 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -27,22 +27,6 @@ char *current_socketpath = NULL;
TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
-/*
- * Puts the given socket file descriptor into non-blocking mode or dies if
- * setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our
- * IPC model because we should by no means block the window manager.
- *
- */
-static void set_nonblock(int sockfd) {
- int flags = fcntl(sockfd, F_GETFL, 0);
- if (flags & O_NONBLOCK) {
- return;
- }
- flags |= O_NONBLOCK;
- if (fcntl(sockfd, F_SETFL, flags) < 0)
- err(-1, "Could not set O_NONBLOCK");
-}
-
static void ipc_client_timeout(EV_P_ ev_timer *w, int revents);
static void ipc_socket_writeable_cb(EV_P_ struct ev_io *w, int revents);
@@ -251,9 +235,9 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) {
ystr(name);
y(map_open);
ystr("x");
- y(integer, r.x);
+ y(integer, (int32_t)r.x);
ystr("y");
- y(integer, r.y);
+ y(integer, (int32_t)r.y);
ystr("width");
y(integer, r.width);
ystr("height");
@@ -536,6 +520,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
ystr(con->title_format);
}
+ ystr("window_icon_padding");
+ y(integer, con->window_icon_padding);
+
if (con->type == CT_WORKSPACE) {
ystr("num");
y(integer, con->num);
@@ -597,6 +584,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
DUMP_PROPERTY("class", class_class);
DUMP_PROPERTY("instance", class_instance);
DUMP_PROPERTY("window_role", role);
+ DUMP_PROPERTY("machine", machine);
if (con->window->name != NULL) {
ystr("title");
@@ -686,6 +674,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
DUMP_REGEX(instance);
DUMP_REGEX(window_role);
DUMP_REGEX(title);
+ DUMP_REGEX(machine);
#undef DUMP_REGEX
y(map_close);
@@ -1083,6 +1072,17 @@ IPC_HANDLER(get_version) {
ystr("loaded_config_file_name");
ystr(current_configpath);
+ ystr("included_config_file_names");
+ y(array_open);
+ IncludedFile *file;
+ TAILQ_FOREACH (file, &included_files, files) {
+ if (file == TAILQ_FIRST(&included_files)) {
+ /* Skip the first file, which is current_configpath. */
+ continue;
+ }
+ ystr(file->path);
+ }
+ y(array_close);
y(map_close);
const unsigned char *payload;
@@ -1265,7 +1265,22 @@ IPC_HANDLER(get_config) {
y(map_open);
ystr("config");
- ystr(current_config);
+ IncludedFile *file = TAILQ_FIRST(&included_files);
+ ystr(file->raw_contents);
+
+ ystr("included_configs");
+ y(array_open);
+ TAILQ_FOREACH (file, &included_files, files) {
+ y(map_open);
+ ystr("path");
+ ystr(file->path);
+ ystr("raw_contents");
+ ystr(file->raw_contents);
+ ystr("variable_replaced_contents");
+ ystr(file->variable_replaced_contents);
+ y(map_close);
+ }
+ y(array_close);
y(map_close);
@@ -1552,57 +1567,6 @@ ipc_client *ipc_new_client_on_fd(EV_P_ int fd) {
return client;
}
-/*
- * Creates the UNIX domain socket at the given path, sets it to non-blocking
- * mode, bind()s and listen()s on it.
- *
- */
-int ipc_create_socket(const char *filename) {
- int sockfd;
-
- FREE(current_socketpath);
-
- char *resolved = resolve_tilde(filename);
- DLOG("Creating IPC-socket at %s\n", resolved);
- char *copy = sstrdup(resolved);
- const char *dir = dirname(copy);
- if (!path_exists(dir))
- mkdirp(dir, DEFAULT_DIR_MODE);
- free(copy);
-
- /* Unlink the unix domain socket before */
- unlink(resolved);
-
- if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
- perror("socket()");
- free(resolved);
- return -1;
- }
-
- (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
-
- struct sockaddr_un addr;
- memset(&addr, 0, sizeof(struct sockaddr_un));
- addr.sun_family = AF_LOCAL;
- strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1);
- if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
- perror("bind()");
- free(resolved);
- return -1;
- }
-
- set_nonblock(sockfd);
-
- if (listen(sockfd, 5) < 0) {
- perror("listen()");
- free(resolved);
- return -1;
- }
-
- current_socketpath = resolved;
- return sockfd;
-}
-
/*
* Generates a json workspace event. Returns a dynamically allocated yajl
* generator. Free with yajl_gen_free().
diff --git a/src/load_layout.c b/src/load_layout.c
index c117f9c6a..9789fb339 100644
--- a/src/load_layout.c
+++ b/src/load_layout.c
@@ -290,6 +290,9 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
} else if (strcasecmp(last_key, "title") == 0) {
current_swallow->title = regex_new(sval);
swallow_is_empty = false;
+ } else if (strcasecmp(last_key, "machine") == 0) {
+ current_swallow->machine = regex_new(sval);
+ swallow_is_empty = false;
} else {
ELOG("swallow key %s unknown\n", last_key);
}
@@ -457,6 +460,10 @@ static int json_int(void *ctx, long long val) {
if (strcasecmp(last_key, "current_border_width") == 0)
json_node->current_border_width = val;
+ if (strcasecmp(last_key, "window_icon_padding") == 0) {
+ json_node->window_icon_padding = val;
+ }
+
if (strcasecmp(last_key, "depth") == 0)
json_node->depth = val;
diff --git a/src/log.c b/src/log.c
index 326f82b8c..183bb8e63 100644
--- a/src/log.c
+++ b/src/log.c
@@ -12,6 +12,10 @@
#include "all.h"
#include "shmlog.h"
+#include
+#include
+#include
+#include
#include
#include
#include
@@ -23,9 +27,6 @@
#include
#include
#include
-#if !defined(__OpenBSD__)
-#include
-#endif
#if defined(__APPLE__)
#include
@@ -60,6 +61,18 @@ static int logbuffer_shm;
/* Size (in bytes) of physical memory */
static long long physical_mem_bytes;
+typedef struct log_client {
+ int fd;
+
+ TAILQ_ENTRY(log_client)
+ clients;
+} log_client;
+
+TAILQ_HEAD(log_client_head, log_client)
+log_clients = TAILQ_HEAD_INITIALIZER(log_clients);
+
+void log_broadcast_to_clients(const char *message, size_t len);
+
/*
* Writes the offsets for the next write and for the last wrap to the
* shmlog_header.
@@ -161,14 +174,6 @@ void open_logbuffer(void) {
header = (i3_shmlog_header *)logbuffer;
-#if !defined(__OpenBSD__)
- pthread_condattr_t cond_attr;
- pthread_condattr_init(&cond_attr);
- if (pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED) != 0)
- fprintf(stderr, "pthread_condattr_setpshared() failed, i3-dump-log -f will not work!\n");
- pthread_cond_init(&(header->condvar), &cond_attr);
-#endif
-
logwalk = logbuffer + sizeof(i3_shmlog_header);
loglastwrap = logbuffer + logbuffer_size;
store_log_markers();
@@ -283,13 +288,10 @@ static void vlog(const bool print, const char *fmt, va_list args) {
store_log_markers();
-#if !defined(__OpenBSD__)
- /* Wake up all (i3-dump-log) processes waiting for condvar. */
- pthread_cond_broadcast(&(header->condvar));
-#endif
-
if (print)
fwrite(message, len, 1, stdout);
+
+ log_broadcast_to_clients(message, len);
}
}
@@ -370,3 +372,51 @@ void purge_zerobyte_logfile(void) {
rmdir(errorfilename);
}
}
+
+char *current_log_stream_socket_path = NULL;
+
+/*
+ * Handler for activity on the listening socket, meaning that a new client
+ * has just connected and we should accept() them. Sets up the event handler
+ * for activity on the new connection and inserts the file descriptor into
+ * the list of log clients.
+ *
+ */
+void log_new_client(EV_P_ struct ev_io *w, int revents) {
+ struct sockaddr_un peer;
+ socklen_t len = sizeof(struct sockaddr_un);
+ int fd;
+ if ((fd = accept(w->fd, (struct sockaddr *)&peer, &len)) < 0) {
+ if (errno != EINTR) {
+ perror("accept()");
+ }
+ return;
+ }
+
+ /* Close this file descriptor on exec() */
+ (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
+
+ set_nonblock(fd);
+
+ log_client *client = scalloc(1, sizeof(log_client));
+ client->fd = fd;
+ TAILQ_INSERT_TAIL(&log_clients, client, clients);
+
+ DLOG("log: new client connected on fd %d\n", fd);
+}
+
+void log_broadcast_to_clients(const char *message, size_t len) {
+ log_client *current = TAILQ_FIRST(&log_clients);
+ while (current != TAILQ_END(&log_clients)) {
+ /* XXX: In case slow connections turn out to be a problem here
+ * (unlikely as long as i3-dump-log is the only consumer), introduce
+ * buffering, similar to the IPC interface. */
+ ssize_t n = writeall(current->fd, message, len);
+ log_client *previous = current;
+ current = TAILQ_NEXT(current, clients);
+ if (n < 0) {
+ TAILQ_REMOVE(&log_clients, previous, clients);
+ free(previous);
+ }
+ }
+}
diff --git a/src/main.c b/src/main.c
index 220195597..c73601da6 100644
--- a/src/main.c
+++ b/src/main.c
@@ -24,6 +24,9 @@
#include
#include
#include
+#include
+#include
+#include
#ifdef I3_ASAN_ENABLED
#include
@@ -63,6 +66,9 @@ xcb_timestamp_t last_timestamp = XCB_CURRENT_TIME;
xcb_screen_t *root_screen;
xcb_window_t root;
+xcb_window_t wm_sn_selection_owner;
+xcb_atom_t wm_sn;
+
/* Color depth, visual id and colormap to use when creating windows and
* pixmaps. Will use 32 bit depth and an appropriate visual, if available,
* otherwise the root window’s default (usually 24 bit TrueColor). */
@@ -181,6 +187,9 @@ static void i3_exit(void) {
}
ipc_shutdown(SHUTDOWN_REASON_EXIT, -1);
unlink(config.ipc_socket_path);
+ if (current_log_stream_socket_path != NULL) {
+ unlink(current_log_stream_socket_path);
+ }
xcb_disconnect(conn);
/* If a nagbar is active, kill it */
@@ -279,6 +288,7 @@ int main(int argc, char *argv[]) {
char *fake_outputs = NULL;
bool disable_signalhandler = false;
bool only_check_config = false;
+ bool replace_wm = false;
static struct option long_options[] = {
{"no-autostart", no_argument, 0, 'a'},
{"config", required_argument, 0, 'c'},
@@ -301,6 +311,7 @@ int main(int argc, char *argv[]) {
{"fake_outputs", required_argument, 0, 0},
{"fake-outputs", required_argument, 0, 0},
{"force-old-config-parser-v4.4-only", no_argument, 0, 0},
+ {"replace", no_argument, 0, 'r'},
{0, 0, 0, 0}};
int option_index = 0, opt;
@@ -325,7 +336,7 @@ int main(int argc, char *argv[]) {
start_argv = argv;
- while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:V", long_options, &option_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:Vr", long_options, &option_index)) != -1) {
switch (opt) {
case 'a':
LOG("Autostart disabled using -a\n");
@@ -363,6 +374,9 @@ int main(int argc, char *argv[]) {
case 'l':
/* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
break;
+ case 'r':
+ replace_wm = true;
+ break;
case 0:
if (strcmp(long_options[option_index].name, "force-xinerama") == 0 ||
strcmp(long_options[option_index].name, "force_xinerama") == 0) {
@@ -443,6 +457,9 @@ int main(int argc, char *argv[]) {
"\tThe default is %d bytes.\n",
shmlog_size);
fprintf(stderr, "\n");
+ fprintf(stderr, "\t--replace\n"
+ "\tReplace an existing window manager.\n");
+ fprintf(stderr, "\n");
fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n"
"to send to a currently running i3 (like i3-msg). This allows you to\n"
"use nice and logical commands, such as:\n"
@@ -570,6 +587,21 @@ int main(int argc, char *argv[]) {
root_screen = xcb_aux_get_screen(conn, conn_screen);
root = root_screen->root;
+ /* Prefetch X11 extensions that we are interested in. */
+ xcb_prefetch_extension_data(conn, &xcb_xkb_id);
+ xcb_prefetch_extension_data(conn, &xcb_shape_id);
+ /* BIG-REQUESTS is used by libxcb internally. */
+ xcb_prefetch_extension_data(conn, &xcb_big_requests_id);
+ if (force_xinerama) {
+ xcb_prefetch_extension_data(conn, &xcb_xinerama_id);
+ } else {
+ xcb_prefetch_extension_data(conn, &xcb_randr_id);
+ }
+
+ /* Prepare for us to get a current timestamp as recommended by ICCCM */
+ xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){XCB_EVENT_MASK_PROPERTY_CHANGE});
+ xcb_change_property(conn, XCB_PROP_MODE_APPEND, root, XCB_ATOM_SUPERSCRIPT_X, XCB_ATOM_CARDINAL, 32, 0, "");
+
/* Place requests for the atoms we need as soon as possible */
#define xmacro(atom) \
xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
@@ -599,6 +631,8 @@ int main(int argc, char *argv[]) {
visual_type = get_visualtype(root_screen);
}
+ xcb_prefetch_maximum_request_length(conn);
+
init_dpi();
DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_type->visual_id);
@@ -609,6 +643,22 @@ int main(int argc, char *argv[]) {
xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root);
+ /* Get the PropertyNotify event we caused above */
+ xcb_flush(conn);
+ {
+ xcb_generic_event_t *event;
+ DLOG("waiting for PropertyNotify event\n");
+ while ((event = xcb_wait_for_event(conn)) != NULL) {
+ if (event->response_type == XCB_PROPERTY_NOTIFY) {
+ last_timestamp = ((xcb_property_notify_event_t *)event)->time;
+ free(event);
+ break;
+ }
+ free(event);
+ }
+ DLOG("got timestamp %d\n", last_timestamp);
+ }
+
/* Setup NetWM atoms */
#define xmacro(name) \
do { \
@@ -638,6 +688,103 @@ int main(int argc, char *argv[]) {
force_xinerama = true;
}
+ /* Acquire the WM_Sn selection. */
+ {
+ /* Get the WM_Sn atom */
+ char *atom_name = xcb_atom_name_by_screen("WM_S", conn_screen);
+ wm_sn_selection_owner = xcb_generate_id(conn);
+
+ if (atom_name == NULL) {
+ ELOG("xcb_atom_name_by_screen(\"WM_S\", %d) failed, exiting\n", conn_screen);
+ return 1;
+ }
+
+ xcb_intern_atom_reply_t *atom_reply;
+ atom_reply = xcb_intern_atom_reply(conn,
+ xcb_intern_atom_unchecked(conn,
+ 0,
+ strlen(atom_name),
+ atom_name),
+ NULL);
+ free(atom_name);
+ if (atom_reply == NULL) {
+ ELOG("Failed to intern the WM_Sn atom, exiting\n");
+ return 1;
+ }
+ wm_sn = atom_reply->atom;
+ free(atom_reply);
+
+ /* Check if the selection is already owned */
+ xcb_get_selection_owner_reply_t *selection_reply =
+ xcb_get_selection_owner_reply(conn,
+ xcb_get_selection_owner(conn, wm_sn),
+ NULL);
+ if (selection_reply && selection_reply->owner != XCB_NONE && !replace_wm) {
+ ELOG("Another window manager is already running (WM_Sn is owned)");
+ return 1;
+ }
+
+ /* Become the selection owner */
+ xcb_create_window(conn,
+ root_screen->root_depth,
+ wm_sn_selection_owner, /* window id */
+ root_screen->root, /* parent */
+ -1, -1, 1, 1, /* geometry */
+ 0, /* border width */
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ root_screen->root_visual,
+ 0, NULL);
+ xcb_change_property(conn,
+ XCB_PROP_MODE_REPLACE,
+ wm_sn_selection_owner,
+ XCB_ATOM_WM_CLASS,
+ XCB_ATOM_STRING,
+ 8,
+ (strlen("i3-WM_Sn") + 1) * 2,
+ "i3-WM_Sn\0i3-WM_Sn\0");
+
+ xcb_set_selection_owner(conn, wm_sn_selection_owner, wm_sn, last_timestamp);
+
+ if (selection_reply && selection_reply->owner != XCB_NONE) {
+ unsigned int usleep_time = 100000; /* 0.1 seconds */
+ int check_rounds = 150; /* Wait for a maximum of 15 seconds */
+ xcb_get_geometry_reply_t *geom_reply = NULL;
+
+ DLOG("waiting for old WM_Sn selection owner to exit");
+ do {
+ free(geom_reply);
+ usleep(usleep_time);
+ if (check_rounds-- == 0) {
+ ELOG("The old window manager is not exiting");
+ return 1;
+ }
+ geom_reply = xcb_get_geometry_reply(conn,
+ xcb_get_geometry(conn, selection_reply->owner),
+ NULL);
+ } while (geom_reply != NULL);
+ }
+ free(selection_reply);
+
+ /* Announce that we are the new owner */
+ /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
+ * In order to properly initialize these bytes, we allocate 32 bytes even
+ * though we only need less for an xcb_client_message_event_t */
+ union {
+ xcb_client_message_event_t message;
+ char storage[32];
+ } event;
+ memset(&event, 0, sizeof(event));
+ event.message.response_type = XCB_CLIENT_MESSAGE;
+ event.message.window = root_screen->root;
+ event.message.format = 32;
+ event.message.type = A_MANAGER;
+ event.message.data.data32[0] = last_timestamp;
+ event.message.data.data32[1] = wm_sn;
+ event.message.data.data32[2] = wm_sn_selection_owner;
+
+ xcb_send_event(conn, 0, root_screen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY, event.storage);
+ }
+
xcb_void_cookie_t cookie;
cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK});
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
@@ -663,9 +810,6 @@ int main(int argc, char *argv[]) {
xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER);
const xcb_query_extension_reply_t *extreply;
- xcb_prefetch_extension_data(conn, &xcb_xkb_id);
- xcb_prefetch_extension_data(conn, &xcb_shape_id);
-
extreply = xcb_get_extension_data(conn, &xcb_xkb_id);
xkb_supported = extreply->present;
if (!extreply->present) {
@@ -845,7 +989,7 @@ int main(int argc, char *argv[]) {
tree_render();
/* Create the UNIX domain socket for IPC */
- int ipc_socket = ipc_create_socket(config.ipc_socket_path);
+ int ipc_socket = create_socket(config.ipc_socket_path, ¤t_socketpath);
if (ipc_socket == -1) {
ELOG("Could not create the IPC socket, IPC disabled\n");
} else {
@@ -854,9 +998,21 @@ int main(int argc, char *argv[]) {
ev_io_start(main_loop, ipc_io);
}
- /* Also handle the UNIX domain sockets passed via socket activation. The
- * parameter 1 means "remove the environment variables", we don’t want to
- * pass these to child processes. */
+ /* Chose a file name in /tmp/ based on the PID */
+ char *log_stream_socket_path = get_process_filename("log-stream-socket");
+ int log_socket = create_socket(log_stream_socket_path, ¤t_log_stream_socket_path);
+ free(log_stream_socket_path);
+ if (log_socket == -1) {
+ ELOG("Could not create the log socket, i3-dump-log -f will not work\n");
+ } else {
+ struct ev_io *log_io = scalloc(1, sizeof(struct ev_io));
+ ev_io_init(log_io, log_new_client, log_socket, EV_READ);
+ ev_io_start(main_loop, log_io);
+ }
+
+ /* Also handle the UNIX domain sockets passed via socket
+ * activation. The parameter 0 means "do not remove the
+ * environment variables", we need to be able to reexec. */
listen_fds = sd_listen_fds(0);
if (listen_fds < 0)
ELOG("socket activation: Error in sd_listen_fds\n");
@@ -950,24 +1106,23 @@ int main(int argc, char *argv[]) {
xcb_ungrab_server(conn);
if (autostart) {
- LOG("This is not an in-place restart, copying root window contents to a pixmap\n");
+ /* When the root's window background is set to NONE, that might mean
+ * that old content stays visible when a window is closed. That has
+ * unpleasant effect of "my terminal (does not seem to) close!".
+ *
+ * There does not seem to be an easy way to query for this problem, so
+ * we test for it: Open & close a window and check if the background is
+ * redrawn or the window contents stay visible.
+ */
+ LOG("This is not an in-place restart, checking if a wallpaper is set.\n");
+
xcb_screen_t *root = xcb_aux_get_screen(conn, conn_screen);
- uint16_t width = root->width_in_pixels;
- uint16_t height = root->height_in_pixels;
- xcb_pixmap_t pixmap = xcb_generate_id(conn);
- xcb_gcontext_t gc = xcb_generate_id(conn);
-
- xcb_create_pixmap(conn, root->root_depth, pixmap, root->root, width, height);
-
- xcb_create_gc(conn, gc, root->root,
- XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE,
- (uint32_t[]){XCB_GX_COPY, ~0, XCB_FILL_STYLE_SOLID, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS});
-
- xcb_copy_area(conn, root->root, pixmap, gc, 0, 0, 0, 0, width, height);
- xcb_change_window_attributes(conn, root->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){pixmap});
- xcb_flush(conn);
- xcb_free_gc(conn, gc);
- xcb_free_pixmap(conn, pixmap);
+ if (is_background_set(conn, root)) {
+ LOG("A wallpaper is set, so no screenshot is necessary.\n");
+ } else {
+ LOG("No wallpaper set, copying root window contents to a pixmap\n");
+ set_screenshot_as_wallpaper(conn, root);
+ }
}
#if defined(__OpenBSD__)
@@ -1041,5 +1196,6 @@ int main(int argc, char *argv[]) {
* when calling exit() */
atexit(i3_exit);
+ sd_notify(1, "READY=1");
ev_loop(main_loop, 0);
}
diff --git a/src/manage.c b/src/manage.c
index e3ea59b6c..3c8ebc8a7 100644
--- a/src/manage.c
+++ b/src/manage.c
@@ -116,7 +116,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
utf8_title_cookie, title_cookie,
class_cookie, leader_cookie, transient_cookie,
role_cookie, startup_id_cookie, wm_hints_cookie,
- wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie;
+ wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie,
+ wm_machine_cookie;
+
+ xcb_get_property_cookie_t wm_icon_cookie;
geomc = xcb_get_geometry(conn, d);
@@ -189,6 +192,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
motif_wm_hints_cookie = GET_PROPERTY(A__MOTIF_WM_HINTS, 5 * sizeof(uint64_t));
wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX);
wm_desktop_cookie = GET_PROPERTY(A__NET_WM_DESKTOP, UINT32_MAX);
+ wm_machine_cookie = GET_PROPERTY(XCB_ATOM_WM_CLIENT_MACHINE, UINT32_MAX);
+ wm_icon_cookie = GET_PROPERTY(A__NET_WM_ICON, UINT32_MAX);
i3Window *cwindow = scalloc(1, sizeof(i3Window));
cwindow->id = window;
@@ -202,6 +207,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL));
window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL));
window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL));
+ window_update_icon(cwindow, xcb_get_property_reply(conn, wm_icon_cookie, NULL));
window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
@@ -211,6 +217,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
border_style_t motif_border_style = BS_NORMAL;
window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
window_update_normal_hints(cwindow, xcb_get_property_reply(conn, wm_normal_hints_cookie, NULL), geom);
+ window_update_machine(cwindow, xcb_get_property_reply(conn, wm_machine_cookie, NULL));
xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL);
diff --git a/src/match.c b/src/match.c
index 6ac312e5d..34314e256 100644
--- a/src/match.c
+++ b/src/match.c
@@ -47,12 +47,14 @@ bool match_is_empty(Match *match) {
match->instance == NULL &&
match->window_role == NULL &&
match->workspace == NULL &&
+ match->machine == NULL &&
match->urgent == U_DONTCHECK &&
match->id == XCB_NONE &&
match->window_type == UINT32_MAX &&
match->con_id == NULL &&
match->dock == M_NODOCK &&
- match->window_mode == WM_ANY);
+ match->window_mode == WM_ANY &&
+ match->match_all_windows == false);
}
/*
@@ -130,6 +132,8 @@ bool match_matches_window(Match *match, i3Window *window) {
}
}
+ CHECK_WINDOW_FIELD(machine, machine, str);
+
Con *con = NULL;
if (match->urgent == U_LATEST) {
/* if the window isn't urgent, no sense in searching */
@@ -257,6 +261,10 @@ bool match_matches_window(Match *match, i3Window *window) {
LOG("window_mode matches\n");
}
+ /* NOTE: See the comment regarding 'all' in match_parse_property()
+ * for an explanation of why match_all_windows isn't explicitly
+ * checked. */
+
return true;
}
@@ -273,6 +281,7 @@ void match_free(Match *match) {
regex_free(match->mark);
regex_free(match->window_role);
regex_free(match->workspace);
+ regex_free(match->machine);
}
/*
@@ -390,6 +399,12 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) {
return;
}
+ if (strcmp(ctype, "machine") == 0) {
+ regex_free(match->machine);
+ match->machine = regex_new(cvalue);
+ return;
+ }
+
if (strcmp(ctype, "tiling") == 0) {
match->window_mode = WM_TILING;
return;
@@ -428,5 +443,16 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) {
return;
}
+ /* match_matches_window() only checks negatively, so match_all_windows
+ * won't actually be used there, but that's OK because if no negative
+ * match is found (e.g. because of a more restrictive criterion) the
+ * return value of match_matches_window() is true.
+ * Setting it here only serves to cause match_is_empty() to return false,
+ * otherwise empty criteria rules apply, and that's not what we want. */
+ if (strcmp(ctype, "all") == 0) {
+ match->match_all_windows = true;
+ return;
+ }
+
ELOG("Unknown criterion: %s\n", ctype);
}
diff --git a/src/restore_layout.c b/src/restore_layout.c
index c51bfcbe3..6f35d1653 100644
--- a/src/restore_layout.c
+++ b/src/restore_layout.c
@@ -134,7 +134,6 @@ static void update_placeholder_contents(placeholder_state *state) {
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);
Match *swallows;
@@ -153,6 +152,7 @@ static void update_placeholder_contents(placeholder_state *state) {
APPEND_REGEX(instance);
APPEND_REGEX(window_role);
APPEND_REGEX(title);
+ APPEND_REGEX(machine);
if (serialized == NULL) {
DLOG("This swallows specification is not serializable?!\n");
@@ -179,7 +179,6 @@ static void update_placeholder_contents(placeholder_state *state) {
int y = (state->rect.height / 2) - (config.font.height / 2);
draw_util_text(line, &(state->surface), foreground, background, x, y, text_width);
i3string_free(line);
- xcb_flush(restore_conn);
xcb_aux_sync(restore_conn);
}
diff --git a/src/util.c b/src/util.c
index cf7f41c8a..f2c562228 100644
--- a/src/util.c
+++ b/src/util.c
@@ -176,15 +176,6 @@ void exec_i3_utility(char *name, char *argv[]) {
_exit(2);
}
-/*
- * Checks if the given path exists by calling stat().
- *
- */
-bool path_exists(const char *path) {
- struct stat buf;
- return (stat(path, &buf) == 0);
-}
-
/*
* Goes through the list of arguments (for exec()) and add/replace the given option,
* including the option name, its argument, and the option character.
diff --git a/src/window.c b/src/window.c
index bee3fa668..734a3264b 100644
--- a/src/window.c
+++ b/src/window.c
@@ -18,7 +18,10 @@
void window_free(i3Window *win) {
FREE(win->class_class);
FREE(win->class_instance);
+ FREE(win->role);
+ FREE(win->machine);
i3string_free(win->name);
+ cairo_surface_destroy(win->icon);
FREE(win->ran_assignments);
FREE(win);
}
@@ -466,3 +469,130 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
#undef MWM_DECOR_BORDER
#undef MWM_DECOR_TITLE
}
+
+/*
+ * Updates the WM_CLIENT_MACHINE
+ *
+ */
+void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop) {
+ if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+ DLOG("WM_CLIENT_MACHINE not set.\n");
+ FREE(prop);
+ return;
+ }
+
+ FREE(win->machine);
+ win->machine = sstrndup((char *)xcb_get_property_value(prop), xcb_get_property_value_length(prop));
+ LOG("WM_CLIENT_MACHINE changed to \"%s\"\n", win->machine);
+
+ free(prop);
+}
+
+void window_update_icon(i3Window *win, xcb_get_property_reply_t *prop) {
+ uint32_t *data = NULL;
+ uint32_t width, height;
+ uint64_t len = 0;
+ const uint32_t pref_size = (uint32_t)(render_deco_height() - logical_px(2));
+
+ if (!prop || prop->type != XCB_ATOM_CARDINAL || prop->format != 32) {
+ DLOG("_NET_WM_ICON is not set\n");
+ FREE(prop);
+ return;
+ }
+
+ uint32_t prop_value_len = xcb_get_property_value_length(prop);
+ uint32_t *prop_value = (uint32_t *)xcb_get_property_value(prop);
+
+ /* Find an icon matching the preferred size.
+ * If there is no such icon, take the smallest icon having at least
+ * the preferred size.
+ * If all icons are smaller than the preferred size, chose the largest.
+ */
+ while (prop_value_len > (sizeof(uint32_t) * 2) && prop_value &&
+ prop_value[0] && prop_value[1]) {
+ const uint32_t cur_width = prop_value[0];
+ const uint32_t cur_height = prop_value[1];
+ /* Check that the property is as long as it should be (in bytes),
+ handling integer overflow. "+2" to handle the width and height
+ fields. */
+ const uint64_t cur_len = cur_width * (uint64_t)cur_height;
+ const uint64_t expected_len = (cur_len + 2) * 4;
+
+ if (expected_len > prop_value_len) {
+ break;
+ }
+
+ DLOG("Found _NET_WM_ICON of size: (%d,%d)\n", cur_width, cur_height);
+
+ const bool at_least_preferred_size = (cur_width >= pref_size &&
+ cur_height >= pref_size);
+ const bool smaller_than_current = (cur_width < width ||
+ cur_height < height);
+ const bool larger_than_current = (cur_width > width ||
+ cur_height > height);
+ const bool not_yet_at_preferred = (width < pref_size ||
+ height < pref_size);
+ if (len == 0 ||
+ (at_least_preferred_size &&
+ (smaller_than_current || not_yet_at_preferred)) ||
+ (!at_least_preferred_size &&
+ not_yet_at_preferred &&
+ larger_than_current)) {
+ len = cur_len;
+ width = cur_width;
+ height = cur_height;
+ data = prop_value;
+ }
+
+ if (width == pref_size && height == pref_size) {
+ break;
+ }
+
+ /* Find pointer to next icon in the reply. */
+ prop_value_len -= expected_len;
+ prop_value = (uint32_t *)(((uint8_t *)prop_value) + expected_len);
+ }
+
+ if (!data) {
+ DLOG("Could not get _NET_WM_ICON\n");
+ FREE(prop);
+ return;
+ }
+
+ DLOG("Using icon of size (%d,%d) (preferred size: %d)\n",
+ width, height, pref_size);
+
+ win->name_x_changed = true; /* trigger a redraw */
+
+ uint32_t *icon = smalloc(len * 4);
+
+ for (uint64_t i = 0; i < len; i++) {
+ uint8_t r, g, b, a;
+ const uint32_t pixel = data[2 + i];
+ a = (pixel >> 24) & 0xff;
+ r = (pixel >> 16) & 0xff;
+ g = (pixel >> 8) & 0xff;
+ b = (pixel >> 0) & 0xff;
+
+ /* Cairo uses premultiplied alpha */
+ r = (r * a) / 0xff;
+ g = (g * a) / 0xff;
+ b = (b * a) / 0xff;
+
+ icon[i] = ((uint32_t)a << 24) | (r << 16) | (g << 8) | b;
+ }
+
+ if (win->icon != NULL) {
+ cairo_surface_destroy(win->icon);
+ }
+ win->icon = cairo_image_surface_create_for_data(
+ (unsigned char *)icon,
+ CAIRO_FORMAT_ARGB32,
+ width,
+ height,
+ width * 4);
+ static cairo_user_data_key_t free_data;
+ cairo_surface_set_user_data(win->icon, &free_data, icon, free);
+
+ FREE(prop);
+}
diff --git a/src/workspace.c b/src/workspace.c
index e77d544b7..526a2bf70 100644
--- a/src/workspace.c
+++ b/src/workspace.c
@@ -139,7 +139,7 @@ Con *workspace_get(const char *num) {
/* 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 long parsed_num = ws_name_to_number(num);
+ const int parsed_num = ws_name_to_number(num);
struct Workspace_Assignment *assignment;
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
@@ -254,7 +254,6 @@ void extract_workspace_names_from_bindings(void) {
*/
Con *create_workspace_on_output(Output *output, Con *content) {
/* add a workspace to this output */
- char *name;
bool exists = true;
Con *ws = con_new(NULL, NULL);
ws->type = CT_WORKSPACE;
@@ -270,13 +269,16 @@ Con *create_workspace_on_output(Output *output, Con *content) {
continue;
}
- exists = (get_existing_workspace_by_name(target_name) != NULL);
+ const int num = ws_name_to_number(target_name);
+ exists = (num == -1)
+ ? get_existing_workspace_by_name(target_name)
+ : get_existing_workspace_by_num(num);
if (!exists) {
ws->name = sstrdup(target_name);
/* Set ->num to the number of the workspace, if the name actually
* is a number or starts with a number */
- ws->num = ws_name_to_number(ws->name);
- LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
+ ws->num = num;
+ DLOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
break;
}
@@ -306,6 +308,7 @@ Con *create_workspace_on_output(Output *output, Con *content) {
con_attach(ws, content, false);
+ char *name;
sasprintf(&name, "[i3 con] workspace %s", ws->name);
x_set_name(ws, name);
free(name);
@@ -1012,14 +1015,14 @@ void workspace_move_to_output(Con *ws, Output *output) {
bool used_assignment = false;
struct Workspace_Assignment *assignment;
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
- bool attached;
- int num;
if (!output_triggers_assignment(current_output, assignment)) {
continue;
}
/* check if this workspace's name or num is already attached to the tree */
- num = ws_name_to_number(assignment->name);
- attached = ((num == -1) ? get_existing_workspace_by_name(assignment->name) : get_existing_workspace_by_num(num)) != NULL;
+ const int num = ws_name_to_number(assignment->name);
+ const bool attached = (num == -1)
+ ? get_existing_workspace_by_name(assignment->name)
+ : get_existing_workspace_by_num(num);
if (attached) {
continue;
}
diff --git a/src/x.c b/src/x.c
index 80e8efd0d..2401e5336 100644
--- a/src/x.c
+++ b/src/x.c
@@ -539,6 +539,9 @@ void x_draw_decoration(Con *con) {
/* 2: draw the client.background, but only for the parts around the window_rect */
if (con->window != NULL) {
+ /* Clear visible windows before beginning to draw */
+ draw_util_clear_surface(&(con->frame_buffer), (color_t){.red = 0.0, .green = 0.0, .blue = 0.0});
+
/* top area */
draw_util_rectangle(&(con->frame_buffer), config.client.background,
0, 0, r->width, w->y);
@@ -611,11 +614,14 @@ void x_draw_decoration(Con *con) {
/* 5: draw title border */
x_draw_title_border(con, p);
- /* 6: draw the title */
+ /* 6: draw the icon and title */
int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
- const int title_padding = logical_px(2);
+ struct Window *win = con->window;
+
const int deco_width = (int)con->deco_rect.width;
+ const int title_padding = logical_px(2);
+
int mark_width = 0;
if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
char *formatted_mark = sstrdup("");
@@ -654,7 +660,6 @@ void x_draw_decoration(Con *con) {
}
i3String *title = NULL;
- struct Window *win = con->window;
if (win == NULL) {
if (con->title_format == NULL) {
char *_title;
@@ -674,25 +679,45 @@ void x_draw_decoration(Con *con) {
goto copy_pixmaps;
}
+ /* icon_padding is applied horizontally only, the icon will always use all
+ * available vertical space. */
+ int icon_size = max(0, con->deco_rect.height - logical_px(2));
+ int icon_padding = logical_px(max(1, con->window_icon_padding));
+ int total_icon_space = icon_size + 2 * icon_padding;
+ const bool has_icon = (con->window_icon_padding > -1) && win && win->icon && (total_icon_space < deco_width);
+ if (!has_icon) {
+ icon_size = icon_padding = total_icon_space = 0;
+ }
+ /* Determine x offsets according to title alignment */
+ int icon_offset_x;
int title_offset_x;
switch (config.title_align) {
case ALIGN_LEFT:
- /* (pad)[text ](pad)[mark + its pad) */
- title_offset_x = title_padding;
+ /* (pad)[(pad)(icon)(pad)][text ](pad)[mark + its pad)
+ * ^ ^--- title_offset_x
+ * ^--- icon_offset_x */
+ icon_offset_x = icon_padding;
+ title_offset_x = title_padding + total_icon_space;
break;
case ALIGN_CENTER:
- /* (pad)[ text ](pad)[mark + its pad)
- * To center the text inside its allocated space, the surface
- * between the brackets, we use the formula
- * (surface_width - predict_text_width) / 2
- * where surface_width = deco_width - 2 * pad - mark_width
- * so, offset = pad + (surface_width - predict_text_width) / 2 =
- * = … = (deco_width - mark_width - predict_text_width) / 2 */
- title_offset_x = max(title_padding, (deco_width - mark_width - predict_text_width(title)) / 2);
+ /* (pad)[ ][(pad)(icon)(pad)][text ](pad)[mark + its pad)
+ * ^ ^--- title_offset_x
+ * ^--- icon_offset_x
+ * Text should come right after the icon (+padding). We calculate
+ * the offset for the icon (white space in the title) by dividing
+ * by two the total available area. That's the decoration width
+ * minus the elements that come after icon_offset_x (icon, its
+ * padding, text, marks). */
+ icon_offset_x = max(icon_padding, (deco_width - icon_padding - icon_size - predict_text_width(title) - title_padding - mark_width) / 2);
+ title_offset_x = max(title_padding, icon_offset_x + icon_padding + icon_size);
break;
case ALIGN_RIGHT:
- /* [mark + its pad](pad)[ text](pad) */
- title_offset_x = max(title_padding + mark_width, deco_width - title_padding - predict_text_width(title));
+ /* [mark + its pad](pad)[ text][(pad)(icon)(pad)](pad)
+ * ^ ^--- icon_offset_x
+ * ^--- title_offset_x */
+ title_offset_x = max(title_padding + mark_width, deco_width - title_padding - predict_text_width(title) - total_icon_space);
+ /* Make sure the icon does not escape title boundaries */
+ icon_offset_x = min(deco_width - icon_size - icon_padding - title_padding, title_offset_x + predict_text_width(title) + icon_padding);
break;
}
@@ -700,7 +725,16 @@ void x_draw_decoration(Con *con) {
p->color->text, p->color->background,
con->deco_rect.x + title_offset_x,
con->deco_rect.y + text_offset_y,
- deco_width - mark_width - 2 * title_padding);
+ deco_width - mark_width - 2 * title_padding - total_icon_space);
+ if (has_icon) {
+ draw_util_image(
+ win->icon,
+ &(parent->frame_buffer),
+ con->deco_rect.x + icon_offset_x,
+ con->deco_rect.y + logical_px(1),
+ icon_size,
+ icon_size);
+ }
if (win == NULL || con->title_format != NULL) {
I3STRING_FREE(title);
@@ -1426,6 +1460,8 @@ void x_set_i3_atoms(void) {
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8,
strlen(current_configpath), current_configpath);
+ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_LOG_STREAM_SOCKET_PATH, A_UTF8_STRING, 8,
+ strlen(current_log_stream_socket_path), current_log_stream_socket_path);
update_shmlog_atom();
}
diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in
index aedaa6017..0f5252a67 100644
--- a/testcases/lib/i3test.pm.in
+++ b/testcases/lib/i3test.pm.in
@@ -410,7 +410,7 @@ Returns the name of the output on which this workspace resides
cmd 'focus output fake-1';
cmd 'workspace 1';
- is(get_output_for_workspace('1', 'fake-0', 'Workspace 1 in output fake-0');
+ is(get_output_for_workspace('1'), 'fake-0', 'Workspace 1 in output fake-0');
=cut
sub get_output_for_workspace {
@@ -419,10 +419,12 @@ sub get_output_for_workspace {
my $tree = $i3->get_tree->recv;
my @outputs = @{$tree->{nodes}};
- foreach (grep { not $_->{name} =~ /^__/ } @outputs) {
- my $output = $_->{name};
- foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) {
- return $output if $_->{nodes}[0]->{name} =~ $ws_name;
+ for my $output (@outputs) {
+ next if $output->{name} eq '__i3';
+ # get the first CT_CON of each output
+ my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
+ if (grep {$_->{name} eq $ws_name} @{$content->{nodes}}){
+ return $output->{name};
}
}
}
diff --git a/testcases/t/116-nestedcons.t b/testcases/t/116-nestedcons.t
index 6a2274d4d..7b15c5f0e 100644
--- a/testcases/t/116-nestedcons.t
+++ b/testcases/t/116-nestedcons.t
@@ -73,6 +73,7 @@ my $expected = {
workspace_layout => 'default',
current_border_width => -1,
marks => $ignore,
+ window_icon_padding => -1,
};
# a shallow copy is sufficient, since we only ignore values at the root
diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t
index 723a7563d..3404df1fb 100644
--- a/testcases/t/187-commands-parser.t
+++ b/testcases/t/187-commands-parser.t
@@ -54,6 +54,7 @@ is(parser_calls(
'move workspace foobar; ' .
'move workspace torrent; ' .
'move workspace to output LVDS1; ' .
+ 'move to output LVDS1 DVI1; ' .
'move workspace 3: foobar; ' .
'move workspace "3: foobar"; ' .
'move workspace "3: foobar, baz"; '),
@@ -62,7 +63,11 @@ is(parser_calls(
"cmd_move_con_to_workspace_name(3, (null))\n" .
"cmd_move_con_to_workspace_name(foobar, (null))\n" .
"cmd_move_con_to_workspace_name(torrent, (null))\n" .
- "cmd_move_workspace_to_output(LVDS1)\n" .
+ "cmd_move_con_to_output(LVDS1, 1)\n" .
+ "cmd_move_con_to_output(NULL, 1)\n" .
+ "cmd_move_con_to_output(LVDS1, 0)\n" .
+ "cmd_move_con_to_output(DVI1, 0)\n" .
+ "cmd_move_con_to_output(NULL, 0)\n" .
"cmd_move_con_to_workspace_name(3: foobar, (null))\n" .
"cmd_move_con_to_workspace_name(3: foobar, (null))\n" .
"cmd_move_con_to_workspace_name(3: foobar, baz, (null))",
@@ -171,6 +176,7 @@ is(parser_calls('unknown_literal'),
scratchpad
swap
title_format
+ title_window_icon
mode
bar
gaps
diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t
index 2d8b63c7a..39eb80cb9 100644
--- a/testcases/t/201-config-parser.t
+++ b/testcases/t/201-config-parser.t
@@ -506,6 +506,7 @@ EOT
my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: , '#', '" . join("', '", 'set ', 'set ', qw(
set_from_resource
+ include
bindsym
bindcode
bind
diff --git a/testcases/t/254-move-to-output-with-criteria.t b/testcases/t/254-move-to-output-with-criteria.t
index 17aa1bbc8..c129aa715 100644
--- a/testcases/t/254-move-to-output-with-criteria.t
+++ b/testcases/t/254-move-to-output-with-criteria.t
@@ -41,4 +41,12 @@ is_num_children($ws_top_right, 1, 'one container on the upper right workspace');
is_num_children($ws_bottom_left, 0, 'no containers on the lower left workspace');
is_num_children($ws_bottom_right, 1, 'one container on the lower right workspace');
+# Also test with multiple explicit outputs
+cmd '[class="moveme"] move window to output fake-1 fake-2';
+
+is_num_children($ws_top_left, 0, 'no containers on the upper left workspace');
+is_num_children($ws_top_right, 1, 'one container on the upper right workspace');
+is_num_children($ws_bottom_left, 1, 'one container on the lower left workspace');
+is_num_children($ws_bottom_right, 0, 'no containers on the lower right workspace');
+
done_testing;
diff --git a/testcases/t/312-regress-layout-default.t b/testcases/t/312-regress-layout-default.t
new file mode 100644
index 000000000..b23cf50b7
--- /dev/null
+++ b/testcases/t/312-regress-layout-default.t
@@ -0,0 +1,25 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://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)
+#
+# Verifies i3 does not crash when using layout default.
+# Ticket: #4408
+# Bug still in: 4.19.2-83-gc8158875
+use i3test;
+
+cmd 'layout default';
+fresh_workspace;
+
+done_testing;
diff --git a/testcases/t/313-include.t b/testcases/t/313-include.t
new file mode 100644
index 000000000..9b8b0fabb
--- /dev/null
+++ b/testcases/t/313-include.t
@@ -0,0 +1,361 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://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)
+#
+# Verifies the include directive.
+
+use File::Temp qw(tempfile tempdir);
+use File::Basename qw(basename);
+use i3test i3_autostart => 0;
+
+# starts i3 with the given config, opens a window, returns its border style
+sub launch_get_border {
+ my ($config) = @_;
+
+ my $pid = launch_with_config($config);
+
+ my $i3 = i3(get_socket_path(0));
+ my $tmp = fresh_workspace;
+
+ my $window = open_window(name => 'special title');
+
+ my @content = @{get_ws_content($tmp)};
+ cmp_ok(@content, '==', 1, 'one node on this workspace now');
+ my $border = $content[0]->{border};
+
+ exit_gracefully($pid);
+
+ return $border;
+}
+
+#####################################################################
+# test thet windows get the default border
+#####################################################################
+
+my $config = < 1);
+my $varconfig = <<'EOT';
+set $vartest special title
+for_window [title="$vartest"] border none
+EOT
+print $fh $varconfig;
+$fh->flush;
+
+$config = < 1);
+print $indirectfh <flush;
+
+$config = < 1);
+print $indirectfh2 <flush;
+
+$config = < 1);
+$permissiondeniedfh->flush;
+my $mode = 0055;
+chmod($mode, $permissiondenied);
+
+$config = < 1);
+unlink($dangling);
+symlink("/dangling", $dangling);
+
+$config = < 1);
+print $varfh <<'EOT';
+for_window [title="$vartest"] border none
+
+EOT
+$varfh->flush;
+
+$config = < 1);
+print $varfh <<'EOT';
+set $vartest special title
+EOT
+$varfh->flush;
+
+$config = < 1);
+$wsfh->flush;
+
+$config = <get_workspaces->recv->[0]->{name};
+
+ exit_gracefully($pid);
+
+ return $name;
+}
+
+is(launch_get_workspace_name($config), '1: eggs', 'workspace name');
+
+################################################################################
+# loop prevention
+################################################################################
+
+my ($loopfh1, $loopname1) = tempfile(UNLINK => 1);
+my ($loopfh2, $loopname2) = tempfile(UNLINK => 1);
+
+print $loopfh1 <flush;
+
+print $loopfh2 <flush;
+
+$config = <get_version()->recv;
+my $included = $version->{included_config_file_names};
+
+is_deeply($included, [ $indirectfilename2, $filename ], 'included config file names correct');
+
+exit_gracefully($pid);
+
+################################################################################
+# Verify the GET_CONFIG IPC reply returns the top-level config
+################################################################################
+
+my $tmpdir = tempdir(CLEANUP => 1);
+my $socketpath = $tmpdir . "/config.sock";
+ok(! -e $socketpath, "$socketpath does not exist yet");
+
+my ($indirectfh3, $indirectfilename3) = tempfile(UNLINK => 1);
+my $indirectconfig = <flush;
+
+$config = < 1, dont_create_temp_dir => 1);
+
+my $i3 = i3(get_socket_path(0));
+my $config_reply = $i3->get_config()->recv;
+
+is($config_reply->{config}, $config, 'GET_CONFIG returns the top-level config file');
+
+my $included = $config_reply->{included_configs};
+is(scalar @{$included}, 3, 'included_configs contains all 3 files');
+is($included->[0]->{raw_contents}, $config, 'included_configs->[0]->{raw_contents} contains top-level config');
+is($included->[1]->{raw_contents}, $indirectconfig, 'included_configs->[1]->{raw_contents} contains indirect config');
+is($included->[2]->{raw_contents}, $varconfig, 'included_configs->[2]->{raw_contents} contains variable config');
+
+my $indirect_replaced_config = <[1]->{variable_replaced_contents}, $indirect_replaced_config, 'included_configs->[1]->{variable_replaced_contents} contains config with variables replaced');
+
+exit_gracefully($pid);
+
+
+done_testing;
diff --git a/testcases/t/314-window-icon-padding.t b/testcases/t/314-window-icon-padding.t
new file mode 100644
index 000000000..5320ab9f8
--- /dev/null
+++ b/testcases/t/314-window-icon-padding.t
@@ -0,0 +1,62 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://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)
+#
+# Verifies title_window_icon behavior.
+use i3test i3_autostart => 0;
+
+sub window_icon_padding {
+ my ($ws) = @_;
+ my ($nodes, $focus) = get_ws_content($ws);
+ ok(@{$nodes} == 1, 'precisely one container on workspace');
+ return $nodes->[0]->{'window_icon_padding'};
+}
+
+my $config = <<"EOT";
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+cmd 'open';
+is(window_icon_padding($tmp), -1, 'window_icon_padding defaults to -1');
+
+cmd 'title_window_icon on';
+isnt(window_icon_padding($tmp), -1, 'window_icon_padding no longer -1');
+
+exit_gracefully($pid);
+
+################################################################################
+# Verify title_window_icon can be used with for_window as expected
+################################################################################
+
+$config = <<"EOT";
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+for_window [class=".*"] title_window_icon padding 3px
+EOT
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+open_window;
+is(window_icon_padding($tmp), 3, 'window_icon_padding set to 3');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/315-all-criterion.t b/testcases/t/315-all-criterion.t
new file mode 100644
index 000000000..f11410fdb
--- /dev/null
+++ b/testcases/t/315-all-criterion.t
@@ -0,0 +1,90 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://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)
+#
+# Tests all kinds of matching methods
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Open a new window
+my $window = open_window;
+my $content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window mapped');
+my $win = $content->[0];
+
+######################################################################
+# check that simple matching works.
+######################################################################
+cmd '[all] kill';
+
+sync_with_i3;
+
+is_num_children($tmp, 0, 'window killed');
+
+######################################################################
+# check that simple matching against multiple windows works.
+######################################################################
+
+$tmp = fresh_workspace;
+
+my $left = open_window;
+ok($left->mapped, 'left window mapped');
+
+my $right = open_window;
+ok($right->mapped, 'right window mapped');
+
+# two windows should be here
+is_num_children($tmp, 2, 'two windows opened');
+
+cmd '[all] kill';
+
+sync_with_i3;
+
+is_num_children($tmp, 0, 'two windows killed');
+
+######################################################################
+# check that multiple criteria work are checked with a logical AND,
+# not a logical OR (that is, matching is not cancelled after the first
+# criterion matches).
+######################################################################
+
+$tmp = fresh_workspace;
+
+my $left = open_window(name => 'left');
+ok($left->mapped, 'left window mapped');
+
+my $right = open_window(name => 'right');
+ok($right->mapped, 'right window mapped');
+
+# two windows should be here
+is_num_children($tmp, 2, 'two windows opened');
+
+cmd '[all title="left"] kill';
+
+sync_with_i3;
+
+is_num_children($tmp, 1, 'one window still there');
+
+cmd '[all] kill';
+
+sync_with_i3;
+
+is_num_children($tmp, 0, 'all windows killed');
+
+done_testing;
diff --git a/testcases/t/543-move-workspace-to-multiple-outputs.t b/testcases/t/543-move-workspace-to-multiple-outputs.t
new file mode 100644
index 000000000..b9bfabec8
--- /dev/null
+++ b/testcases/t/543-move-workspace-to-multiple-outputs.t
@@ -0,0 +1,93 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://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 using multiple workspaces for 'move workspace to output …'
+# Ticket: #4337
+use i3test i3_config => < $out_num: $msg");
+}
+
+###############################################################################
+# Test using "next" special keyword
+###############################################################################
+
+is_ws(1, 0, 'sanity check');
+is_ws(3, 2, 'sanity check');
+
+for (my $i = 1; $i < 9; $i++) {
+ cmd '[con_mark=a] move workspace to output next';
+ my $out1 = $i % 4;
+ my $out3 = ($i + 2) % 4;
+
+ is_ws(1, $out1, 'move workspace to next');
+ is_ws(3, $out3, 'move workspace to next');
+}
+
+###############################################################################
+# Same as above but explicitely type all the outputs
+###############################################################################
+
+is_ws(1, 0, 'sanity check');
+is_ws(3, 2, 'sanity check');
+
+for (my $i = 1; $i < 10; $i++) {
+ cmd '[con_mark=a] move workspace to output fake-0 fake-1 fake-2 fake-3';
+ my $out1 = $i % 4;
+ my $out3 = ($i + 2) % 4;
+
+ is_ws(1, $out1, 'cycle through explicit outputs');
+ is_ws(3, $out3, 'cycle through explicit outputs');
+}
+
+###############################################################################
+# Use a subset of the outputs plus some non-existing outputs
+###############################################################################
+
+cmd '[con_mark=aa] move workspace to output fake-1';
+cmd '[con_mark=ab] move workspace to output fake-1';
+is_ws(1, 1, 'start from fake-1 which is not included in output list');
+is_ws(3, 1, 'start from fake-1 which is not included in output list');
+
+my @order = (0, 3, 2);
+for (my $i = 0; $i < 10; $i++) {
+ cmd '[con_mark=a] move workspace to output doesnotexist fake-0 alsodoesnotexist fake-3 fake-2';
+
+ my $out = $order[$i % 3];
+ is_ws(1, $out, 'cycle through shuffled outputs');
+ is_ws(3, $out, 'cycle through shuffled outputs');
+
+}
+
+done_testing;
diff --git a/travis/bintray-autobuild-debian.json b/travis/bintray-autobuild-debian.json
deleted file mode 100644
index d01b7dbaa..000000000
--- a/travis/bintray-autobuild-debian.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "package": {
- "name": "i3-wm",
- "repo": "i3-autobuild",
- "subject": "i3"
- },
-
- "version": {
- "name": "%version%",
- "desc": "TODO",
- "gpgSign": false
- },
-
- "files": [
- {
- "includePattern": "distbuild/deb/debian-amd64/(.*\\.deb)$",
- "matrixParams": {
- "deb_distribution": "sid",
- "deb_component": "main",
- "deb_architecture": "amd64"
- },
- "uploadPattern": "$1"
- },
- {
- "includePattern": "distbuild/deb/debian-i386/(.*\\.deb)$",
- "matrixParams": {
- "deb_distribution": "sid",
- "deb_component": "main",
- "deb_architecture": "i386"
- },
- "uploadPattern": "$1"
- }
- ],
-
- "publish": true
-}
diff --git a/travis/bintray-autobuild-ubuntu.json b/travis/bintray-autobuild-ubuntu.json
deleted file mode 100644
index 4c0d6114c..000000000
--- a/travis/bintray-autobuild-ubuntu.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "package": {
- "name": "i3-wm",
- "repo": "i3-autobuild-ubuntu",
- "subject": "i3"
- },
-
- "version": {
- "name": "%version%",
- "desc": "TODO",
- "gpgSign": false
- },
-
- "files": [
- {
- "includePattern": "distbuild/deb/ubuntu-amd64/(.*\\.deb)$",
- "matrixParams": {
- "deb_distribution": "bionic",
- "deb_component": "main",
- "deb_architecture": "amd64"
- },
- "uploadPattern": "$1"
- },
- {
- "includePattern": "distbuild/deb/ubuntu-i386/(.*\\.deb)$",
- "matrixParams": {
- "deb_distribution": "bionic",
- "deb_component": "main",
- "deb_architecture": "i386"
- },
- "uploadPattern": "$1"
- }
- ],
-
- "publish": true
-}
diff --git a/travis/check-spelling.pl b/travis/check-spelling.pl
index f2e79b514..6357e78a4 100755
--- a/travis/check-spelling.pl
+++ b/travis/check-spelling.pl
@@ -11,6 +11,7 @@
use v5.10;
use autodie;
use lib 'testcases/lib';
+use lib '/usr/share/lintian/lib';
use i3test::Util qw(slurp);
use Lintian::Spelling qw(check_spelling);
@@ -21,8 +22,6 @@
my $profile = Lintian::Profile->new;
$profile->load('debian', ['/usr/share/lintian']);
-Lintian::Data->set_vendor($profile);
-
my $exitcode = 0;
# Whitelist for spelling errors in manpages, in case the spell checker has
@@ -30,6 +29,7 @@
my $binary_spelling_exceptions = {
#'exmaple' => 1, # Example for how to add entries to this whitelist.
'betwen' => 1, # asan_flags.inc contains this spelling error.
+ 'dissassemble' => 1, # https://reviews.llvm.org/D93902
};
my @binaries = qw(
build/i3
@@ -41,7 +41,7 @@
build/i3bar
);
for my $binary (@binaries) {
- check_spelling(slurp($binary), $binary_spelling_exceptions, sub {
+ check_spelling($profile, slurp($binary), $binary_spelling_exceptions, sub {
my ($current, $fixed) = @_;
say STDERR qq|Binary "$binary" contains a spelling error: "$current" should be "$fixed"|;
$exitcode = 1;
@@ -56,7 +56,7 @@
for my $name (glob('build/man/*.1')) {
for my $line (split(/\n/, slurp($name))) {
next if $line =~ /^\.\\\"/o;
- check_spelling($line, $manpage_spelling_exceptions, sub {
+ check_spelling($profile, $line, $manpage_spelling_exceptions, sub {
my ($current, $fixed) = @_;
say STDERR qq|Manpage "$name" contains a spelling error: "$current" should be "$fixed"|;
$exitcode = 1;
diff --git a/travis/deploy-github-pages.sh b/travis/deploy-github-pages.sh
index 2d4548208..b5ead0be1 100755
--- a/travis/deploy-github-pages.sh
+++ b/travis/deploy-github-pages.sh
@@ -9,6 +9,10 @@ mkdir build.i3wm.org
cp -r deb/COPY-DOCS build.i3wm.org/docs
cd build.i3wm.org
echo build.i3wm.org > CNAME
+# Disallow search engine indexing for build.i3wm.org: users should find the
+# release version instead, and only developers should use build.i3wm.org.
+echo 'User-Agent: *' > robots.txt
+echo 'Disallow: /' >> robots.txt
git init
git config user.name "Travis CI"
diff --git a/travis/prep-bintray.sh b/travis/prep-bintray.sh
deleted file mode 100755
index 4ea56dcd4..000000000
--- a/travis/prep-bintray.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-
-set -e
-set -x
-
-sed -i "s,%version%,$(git describe --tags),g" travis/bintray-autobuild-*.json
diff --git a/travis/push-balto.sh b/travis/push-balto.sh
new file mode 100755
index 000000000..ffa5ee15a
--- /dev/null
+++ b/travis/push-balto.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+set -e
+
+for fn in distbuild/deb/debian-amd64/*.deb distbuild/deb/debian-i386/*.deb
+do
+ echo "pushing $fn to balto"
+ curl \
+ --header "Authorization: Bearer ${BALTO_TOKEN}" \
+ --form "package=@${fn}" \
+ --form distribution=all \
+ https://i3.baltorepo.com/i3/i3-autobuild/upload/
+done
+
+for fn in distbuild/deb/ubuntu-amd64/*.deb distbuild/deb/ubuntu-i386/*.deb
+do
+ echo "pushing $fn to balto"
+ curl \
+ --header "Authorization: Bearer ${BALTO_TOKEN}" \
+ --form "package=@${fn}" \
+ --form distribution=all \
+ https://i3.baltorepo.com/i3/i3-autobuild-ubuntu/upload/
+done
diff --git a/travis/skip-pkg.sh b/travis/skip-pkg.sh
index 4337120b7..0ca1d7494 100755
--- a/travis/skip-pkg.sh
+++ b/travis/skip-pkg.sh
@@ -2,11 +2,11 @@
# Returns true if Debian/Ubuntu packages should be skipped because this CI run
# was triggered by a pull request.
-# Verify BINTRAY_USER is present (only set on github.com/i3/i3),
+# Verify BALTO_TOKEN is present (only set on github.com/i3/i3),
# otherwise the CI run was triggered by a pull request.
# Verify CC=gcc so that we only build packages once for each commit,
# not twice (with gcc and clang).
-if [ ! -z "$BINTRAY_USER" ] && [ "$CC" = "gcc" ]
+if [ ! -z "$BALTO_TOKEN" ] && [ "$CC" = "gcc" ]
then
exit 1
fi