- CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror" make -j
- (cd testcases && xvfb-run ./complete-run.pl --parallel=1 || (cat latest/complete-run.log; false))
- clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
+ - |
+ funcs='malloc|calloc|realloc|strdup|strndup|asprintf|write'
+ cstring='"([^"\\]|\\.)*"'
+ cchar="'[^\\\\]'|'\\\\.[^']*'"
+ regex="^([^'\"]|${cstring}|${cchar})*\<(${funcs})\>"
+ detected=0
+ while IFS= read -r file; do
+ if { cpp -w -fpreprocessed "$file" || exit "$?"; } | grep -E -- "$regex"; then
+ echo "^ $file calls a function that has a safe counterpart."
+ detected=1
+ fi
+ done << EOF
+ $(find -name '*.c' -not -name safewrappers.c -not -name strndup.c)
+ EOF
+ if [ "$detected" -ne 0 ]; then
+ echo
+ echo "Calls of functions that have safe counterparts were detected."
+ exit 1
+ fi
-# i3status/i3lock bugreports/feature requests
+# Contributing
-Note that i3status and i3lock related bugreports and feature requests should be
-filed in the corresponding repositories, i.e. https://github.com/i3/i3status
-and https://github.com/i3/i3lock
+## i3status/i3lock bug reports and feature requests
-# i3 bugreports/feature requests
+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).
-1. Read http://i3wm.org/docs/debugging.html
+## i3 bug reports and feature requests
+
+1. Read the [debugging instructions](http://i3wm.org/docs/debugging.html).
2. Make sure you include a link to your logfile in your report (section 3).
3. Make sure you include the i3 version number in your report (section 1).
4. Please be aware that we cannot support compatibility issues with
experience has shown that often, the software in question is responsible for
the issue. Please raise an issue with the software in question, not i3.
-# Pull requests
+## Pull requests
* Before sending a pull request for new features, please check with us that the
feature is something we want to see in i3 by opening an issue which has
- â\80\9cfeature requestâ\80\9d or â\80\9cenhancement” in its title.
+ â\80\9dfeature requestâ\80\9d or â\80\9denhancement” in its title.
* Use the `next` branch for developing and sending your pull request.
* Use `clang-format` to format your code.
-* Run the testsuite, see http://i3wm.org/docs/testsuite.html
+* Run the [testsuite](http://i3wm.org/docs/testsuite.html)
-# Finding something to do
+## Finding something to do
* Find a [reproducible bug](https://github.com/i3/i3/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3Areproducible+label%3Abug+) from the issue tracker. These issues have been reviewed and confirmed by a project contributor.
* Find an [accepted enhancement](https://github.com/i3/i3/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3Aaccepted+label%3Aenhancement) from the issue tracker. These have been approved and are ok to start working on.
+++ /dev/null
-
- ┌──────────────────────────────┐
- │ Release notes for i3 v4.10.2 │
- └──────────────────────────────┘
-
-This is i3 v4.10.2. This version is considered stable. All users of i3 are
-strongly encouraged to upgrade.
-
- ┌────────────────────────────┐
- │ Bugfixes │
- └────────────────────────────┘
-
- • Cope with non-null-terminated x class properties.
- • Get workspace name when renaming current workspace (fixes crash).
- • Use a reasonable default sep_block_width if a separator_symbol is given.
- • Remove windows from the save set when unmapping.
-
- ┌────────────────────────────┐
- │ Thanks! │
- └────────────────────────────┘
-
-Thanks for testing, bugfixes, discussions and everything I forgot go out to:
-
- Ingo Bürk, Michael Hofmann,
-
--- Michael Stapelberg, 2015-04-16
--- /dev/null
+
+ ┌──────────────────────────────┐
+ │ Release notes for i3 v4.10.3 │
+ └──────────────────────────────┘
+
+This is i3 v4.10.3. This version is considered stable. All users of i3 are
+strongly encouraged to upgrade.
+
+ ┌────────────────────────────┐
+ │ Bugfixes │
+ └────────────────────────────┘
+
+ • serialize con_id with %p in run_binding() (For FreeBSD)
+ • ignore InputHint when not in WM_HINTS (fixes e.g. mupdf focus)
+ • disable physically disconnect RandR outputs
+ • initialize workspace rect to the output's upon creation
+ • userguide: quoted strings need to be used, escaping isn’t possible
+ • mkdirp: do not throw an error if directory exists (fixes layout loss for
+ in-place restarts)
+ • i3bar: fix freeing static strings
+
+ ┌────────────────────────────┐
+ │ Thanks! │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+ Tony Crisci, Deiz, Theo Buehler, shdown
+
+-- Michael Stapelberg, 2015-07-30
-i3-wm (4.10.3-1) experimental; urgency=medium
+i3-wm (4.10.4-1) unstable; urgency=medium
* NOT YET RELEASED.
- -- Michael Stapelberg <stapelberg@debian.org> Thu, 16 Apr 2015 09:08:30 +0200
+ -- Michael Stapelberg <stapelberg@debian.org> Thu, 30 Jul 2015 22:30:45 +0200
+
+i3-wm (4.10.3-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org> Thu, 30 Jul 2015 21:51:27 +0200
+
+i3-wm (4.10.2-2) unstable; urgency=medium
+
+ * New upstream release.
+ * experimental to unstable because i3-wm 4.10.2-1 was only in experimental
+ due to the freeze.
+
+ -- Michael Stapelberg <stapelberg@debian.org> Fri, 01 May 2015 20:21:18 +0200
i3-wm (4.10.2-1) experimental; urgency=medium
}
}
if (walk != beginning) {
- char *str = scalloc(walk - beginning + 1);
+ char *str = scalloc(walk - beginning + 1, 1);
/* We copy manually to handle escaping of characters. */
int inpos, outpos;
for (inpos = 0, outpos = 0;
switch (o) {
case 's':
FREE(socket_path);
- socket_path = strdup(optarg);
+ socket_path = sstrdup(optarg);
break;
case 'v':
printf("i3-config-wizard " I3_VERSION "\n");
*
*/
static uint8_t *concat_strings(char **glyphs, int max) {
- uint8_t *output = calloc(max + 1, 4);
+ uint8_t *output = scalloc(max + 1, 4);
uint8_t *walk = output;
for (int c = 0; c < max; c++) {
printf("at %c\n", glyphs[c][0]);
/* allocate space for the output */
int inputlen = strlen(command);
- char *full = calloc(1,
- strlen(format) - (2 * cnt) /* format without all %s */
- + (inputlen * cnt) /* replaced %s */
- + 1); /* trailing NUL */
+ char *full = scalloc(strlen(format) - (2 * cnt) /* format without all %s */
+ + (inputlen * cnt) /* replaced %s */
+ + 1, /* trailing NUL */
+ 1);
char *dest = full;
for (c = 0; c < len; c++) {
/* if this is not % or it is % but without a following 's',
}
int main(int argc, char *argv[]) {
- format = strdup("%s");
+ format = sstrdup("%s");
socket_path = getenv("I3SOCK");
char *pattern = sstrdup("pango:monospace 8");
int o, option_index = 0;
switch (o) {
case 's':
FREE(socket_path);
- socket_path = strdup(optarg);
+ socket_path = sstrdup(optarg);
break;
case 'v':
printf("i3-input " I3_VERSION);
break;
case 'f':
FREE(pattern);
- pattern = strdup(optarg);
+ pattern = sstrdup(optarg);
break;
case 'F':
FREE(format);
- format = strdup(optarg);
+ format = sstrdup(optarg);
break;
case 'h':
printf("i3-input " I3_VERSION "\n");
}
static int reply_string_cb(void *params, const unsigned char *val, size_t len) {
- char *str = scalloc(len + 1);
+ char *str = scalloc(len + 1, 1);
strncpy(str, (const char *)val, len);
if (strcmp(last_key, "error") == 0)
last_reply.error = str;
static int reply_map_key_cb(void *params, const unsigned char *keyVal, size_t keyLen) {
free(last_key);
- last_key = scalloc(keyLen + 1);
+ last_key = scalloc(keyLen + 1, 1);
strncpy(last_key, (const char *)keyVal, keyLen);
return 1;
}
payload = sstrdup(argv[optind]);
} else {
char *both;
- if (asprintf(&both, "%s %s", payload, argv[optind]) == -1)
- err(EXIT_FAILURE, "asprintf");
+ sasprintf(&both, "%s %s", payload, argv[optind]);
free(payload);
payload = both;
}
if (argv0_len > strlen(".nagbar_cmd") &&
strcmp(argv[0] + argv0_len - strlen(".nagbar_cmd"), ".nagbar_cmd") == 0) {
unlink(argv[0]);
- cmd = strdup(argv[0]);
+ cmd = sstrdup(argv[0]);
*(cmd + argv0_len - strlen(".nagbar_cmd")) = '\0';
execl("/bin/sh", "/bin/sh", cmd, NULL);
err(EXIT_FAILURE, "execv(/bin/sh, /bin/sh, %s)", cmd);
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
return 0;
case 'b':
- buttons = realloc(buttons, sizeof(button_t) * (buttoncnt + 1));
+ buttons = srealloc(buttons, sizeof(button_t) * (buttoncnt + 1));
buttons[buttoncnt].label = i3string_from_utf8(optarg);
buttons[buttoncnt].action = argv[optind];
printf("button with label *%s* and action *%s*\n",
# We welcome patches that add distribution-specific mechanisms to find the
# preferred terminal emulator. On Debian, there is the x-terminal-emulator
# symlink for example.
-for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal; do
+for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology; do
if command -v $terminal > /dev/null 2>&1; then
exec $terminal "$@"
fi
va_start(args, format);
(void)vasprintf(&message, format, args);
- struct status_block *err_block = scalloc(sizeof(struct status_block));
+ struct status_block *err_block = scalloc(1, sizeof(struct status_block));
err_block->full_text = i3string_from_utf8("Error: ");
err_block->name = sstrdup("error");
err_block->color = sstrdup("red");
err_block->no_separator = true;
- struct status_block *message_block = scalloc(sizeof(struct status_block));
+ struct status_block *message_block = scalloc(1, sizeof(struct status_block));
message_block->full_text = i3string_from_utf8(message);
message_block->name = sstrdup("error_message");
message_block->color = sstrdup("red");
return 1;
}
if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
- char *copy = (char *)malloc(len + 1);
+ char *copy = (char *)smalloc(len + 1);
strncpy(copy, (const char *)val, len);
copy[len] = 0;
ctx->block.min_width_str = copy;
return 1;
}
if (strcasecmp(ctx->last_map_key, "name") == 0) {
- char *copy = (char *)malloc(len + 1);
+ char *copy = (char *)smalloc(len + 1);
strncpy(copy, (const char *)val, len);
copy[len] = 0;
ctx->block.name = copy;
return 1;
}
if (strcasecmp(ctx->last_map_key, "instance") == 0) {
- char *copy = (char *)malloc(len + 1);
+ char *copy = (char *)smalloc(len + 1);
strncpy(copy, (const char *)val, len);
copy[len] = 0;
ctx->block.instance = copy;
} else {
/* In case of plaintext, we just add a single block and change its
* full_text pointer later. */
- struct status_block *new_block = scalloc(sizeof(struct status_block));
+ struct status_block *new_block = scalloc(1, sizeof(struct status_block));
TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
read_flat_input((char *)buffer, rec);
}
* users updating from that version and restarting i3bar before i3. */
if (!strcmp(cur_key, "wheel_up_cmd")) {
DLOG("wheel_up_cmd = %.*s\n", len, val);
- binding_t *binding = scalloc(sizeof(binding_t));
+ binding_t *binding = scalloc(1, sizeof(binding_t));
binding->input_code = 4;
sasprintf(&(binding->command), "%.*s", len, val);
TAILQ_INSERT_TAIL(&(config.bindings), binding, bindings);
* users updating from that version and restarting i3bar before i3. */
if (!strcmp(cur_key, "wheel_down_cmd")) {
DLOG("wheel_down_cmd = %.*s\n", len, val);
- binding_t *binding = scalloc(sizeof(binding_t));
+ binding_t *binding = scalloc(1, sizeof(binding_t));
binding->input_code = 5;
sasprintf(&(binding->command), "%.*s", len, val);
TAILQ_INSERT_TAIL(&(config.bindings), binding, bindings);
static int config_integer_cb(void *params_, long long val) {
if (parsing_bindings) {
if (strcmp(cur_key, "input_code") == 0) {
- binding_t *binding = scalloc(sizeof(binding_t));
+ binding_t *binding = scalloc(1, sizeof(binding_t));
binding->input_code = val;
TAILQ_INSERT_TAIL(&(config.bindings), binding, bindings);
}
const size_t len = namelen + strlen("workspace \"\"") + 1;
- char *buffer = scalloc(len + num_quotes);
+ char *buffer = scalloc(len + num_quotes, 1);
strncpy(buffer, "workspace \"", strlen("workspace \""));
size_t inpos, outpos;
for (inpos = 0, outpos = strlen("workspace \"");
values);
/* send the XEMBED_EMBEDDED_NOTIFY message */
- void *event = scalloc(32);
+ void *event = scalloc(32, 1);
xcb_client_message_event_t *ev = event;
ev->response_type = XCB_CLIENT_MESSAGE;
ev->window = client;
"i3bar\0i3bar\0");
char *name;
- if (asprintf(&name, "i3bar for output %s", walk->name) == -1)
- err(EXIT_FAILURE, "asprintf()");
+ sasprintf(&name, "i3bar for output %s", walk->name);
xcb_void_cookie_t name_cookie;
name_cookie = xcb_change_property(xcb_connection,
XCB_PROP_MODE_REPLACE,
* there is no more memory available)
*
*/
-void *scalloc(size_t size);
+void *scalloc(size_t num, size_t size);
/**
* Safe-wrapper around realloc which exits if realloc returns NULL (meaning
/* 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_configure_notify_event_t */
- void *event = scalloc(32);
+ void *event = scalloc(32, 1);
xcb_configure_notify_event_t *generated_event = event;
generated_event->event = window;
}
sasprintf(&path, ":%s", tmp);
} else {
- path = strdup(path);
+ path = sstrdup(path);
}
const char *component;
char *str = path;
err(EXIT_FAILURE, "glob() failed");
} else {
head = globbuf.gl_pathv[0];
- result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1);
+ result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1, 1);
strncpy(result, head, strlen(head));
if (tail)
strncat(result, tail, strlen(tail));
if (prop_reply->type == XCB_ATOM_CARDINAL) {
/* We treat a CARDINAL as a >= 32-bit unsigned int. The only CARDINAL
* we query is I3_PID, which is 32-bit. */
- if (asprintf(&content, "%u", *((unsigned int *)xcb_get_property_value(prop_reply))) == -1) {
- free(atom_reply);
- free(prop_reply);
- return NULL;
- }
+ sasprintf(&content, "%u", *((unsigned int *)xcb_get_property_value(prop_reply)));
} else {
- if (asprintf(&content, "%.*s", xcb_get_property_value_length(prop_reply),
- (char *)xcb_get_property_value(prop_reply)) == -1) {
- free(atom_reply);
- free(prop_reply);
- return NULL;
- }
+ sasprintf(&content, "%.*s", xcb_get_property_value_length(prop_reply),
+ (char *)xcb_get_property_value(prop_reply));
}
if (provided_conn == NULL)
xcb_disconnect(conn);
return result;
}
-void *scalloc(size_t size) {
- void *result = calloc(size, 1);
+void *scalloc(size_t num, size_t size) {
+ void *result = calloc(num, size);
if (result == NULL)
- err(EXIT_FAILURE, "calloc(%zd)", size);
+ err(EXIT_FAILURE, "calloc(%zd, %zd)", num, size);
return result;
}
*
*/
i3String *i3string_from_utf8(const char *from_utf8) {
- i3String *str = scalloc(sizeof(i3String));
+ i3String *str = scalloc(1, sizeof(i3String));
/* Get the text */
str->utf8 = sstrdup(from_utf8);
*
*/
i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes) {
- i3String *str = scalloc(sizeof(i3String));
+ i3String *str = scalloc(1, sizeof(i3String));
/* Copy the actual text to our i3String */
- str->utf8 = scalloc(sizeof(char) * (num_bytes + 1));
+ str->utf8 = scalloc(num_bytes + 1, 1);
strncpy(str->utf8, from_utf8, num_bytes);
str->utf8[num_bytes] = '\0';
*
*/
i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs) {
- i3String *str = scalloc(sizeof(i3String));
+ i3String *str = scalloc(1, sizeof(i3String));
/* Copy the actual text to our i3String */
- size_t num_bytes = num_glyphs * sizeof(xcb_char2b_t);
- str->ucs2 = scalloc(num_bytes);
- memcpy(str->ucs2, from_ucs2, num_bytes);
+ str->ucs2 = scalloc(num_glyphs, sizeof(xcb_char2b_t));
+ memcpy(str->ucs2, from_ucs2, num_glyphs * sizeof(xcb_char2b_t));
/* Store the length */
str->num_glyphs = num_glyphs;
*/
char *convert_ucs2_to_utf8(xcb_char2b_t *text, size_t num_glyphs) {
/* Allocate the output buffer (UTF-8 is at most 4 bytes per glyph) */
- size_t buffer_size = num_glyphs * 4 * sizeof(char) + 1;
- char *buffer = scalloc(buffer_size);
+ size_t buffer_size = num_glyphs * 4 + 1;
+ char *buffer = scalloc(buffer_size, 1);
/* We need to use an additional pointer, because iconv() modifies it */
char *output = buffer;
<refentrytitle>{mantitle}</refentrytitle>
<manvolnum>{manvolnum}</manvolnum>
<refmiscinfo class="source">i3</refmiscinfo>
-<refmiscinfo class="version">4.10.2</refmiscinfo>
+<refmiscinfo class="version">4.10.3</refmiscinfo>
<refmiscinfo class="manual">i3 Manual</refmiscinfo>
</refmeta>
<refnamediv>
#!/bin/zsh
# This script is used to prepare a new release of i3.
-export RELEASE_VERSION="4.10.2"
-export PREVIOUS_VERSION="4.10.1"
+export RELEASE_VERSION="4.10.3"
+export PREVIOUS_VERSION="4.10.2"
export RELEASE_BRANCH="master"
if [ ! -e "../i3.github.io" ]
exit 1
fi
+if ! (cd ../i3.github.io && git pull)
+then
+ echo "Could not update ../i3.github.io repository"
+ exit 1
+fi
+
if [ ! -e "RELEASE-NOTES-${RELEASE_VERSION}" ]
then
echo "RELEASE-NOTES-${RELEASE_VERSION} not found."
git checkout master
git merge --no-ff release-${RELEASE_VERSION} -m "Merge branch 'release-${RELEASE_VERSION}'"
git checkout next
- git merge --no-ff master -m "Merge branch 'master' into next"
+ git merge --no-ff -X ours master -m "Merge branch 'master' into next"
else
git checkout next
git merge --no-ff release-${RELEASE_VERSION} -m "Merge branch 'release-${RELEASE_VERSION}'"
git checkout master
- git merge --no-ff next -m "Merge branch 'next' into master"
+ git merge --no-ff -X theirs next -m "Merge branch 'next' into master"
fi
git remote remove origin
# 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 <<EOT
FROM debian:sid
cp ${TMPDIR}/i3/RELEASE-NOTES-${RELEASE_VERSION} downloads/RELEASE-NOTES-${RELEASE_VERSION}.txt
git add downloads/RELEASE-NOTES-${RELEASE_VERSION}.txt
sed -i "s,<h2>Documentation for i3 v[^<]*</h2>,<h2>Documentation for i3 v${RELEASE_VERSION}</h2>,g" docs/index.html
-sed -i "s,Verify you are using i3 ≥ .*,Verify you are using i3 ≥ ${RELEASE_VERSION},g" docs/debugging.html
sed -i "s,<span style=\"margin-left: 2em; color: #c0c0c0\">[^<]*</span>,<span style=\"margin-left: 2em; color: #c0c0c0\">${RELEASE_VERSION}</span>,g" index.html
sed -i "s,The current stable version is .*$,The current stable version is ${RELEASE_VERSION}.,g" downloads/index.html
sed -i "s,<tbody>,<tbody>\n <tr>\n <td>${RELEASE_VERSION}</td>\n <td><a href=\"/downloads/i3-${RELEASE_VERSION}.tar.bz2\">i3-${RELEASE_VERSION}.tar.bz2</a></td>\n <td>$(ls -lh ../i3/i3-${RELEASE_VERSION}.tar.bz2 | awk -F " " {'print $5'} | sed 's/K$/ KiB/g')</td>\n <td><a href=\"/downloads/i3-${RELEASE_VERSION}.tar.bz2.asc\">signature</a></td>\n <td>$(date +'%Y-%m-%d')</td>\n <td><a href=\"/downloads/RELEASE-NOTES-${RELEASE_VERSION}.txt\">release notes</a></td>\n </tr>\n,g" downloads/index.html
for i in $(find _docs -maxdepth 1 -and -type f -and \! -regex ".*\.\(html\|man\)$" -and \! -name "Makefile")
do
base="$(basename $i)"
- [ -e "${STARTDIR}/docs/${base}" ] && cp "${STARTDIR}/docs/${base}" "_docs/${base}"
+ [ -e "${TMPDIR}/i3/docs/${base}" ] && cp "${TMPDIR}/i3/docs/${base}" "_docs/${base}"
done
+sed -i "s,Verify you are using i3 ≥ .*,Verify you are using i3 ≥ ${RELEASE_VERSION},g" _docs/debugging
+
(cd _docs && make)
for i in $(find _docs -maxdepth 1 -and -type f -and \! -regex ".*\.\(html\|man\)$" -and \! -name "Makefile")
do
base="$(basename $i)"
- [ -e "${STARTDIR}/docs/${base}" ] && cp "_docs/${base}.html" docs/
+ [ -e "${TMPDIR}/i3/docs/${base}" ] && cp "_docs/${base}.html" docs/
done
git commit -a -m "update docs for ${RELEASE_VERSION}"
echo "Announce on:"
echo " twitter"
echo " google+"
-echo " mailing list"
echo " #i3 topic"
}
/* If the mode was not found, create a new one */
- mode = scalloc(sizeof(struct Mode));
+ mode = scalloc(1, sizeof(struct Mode));
mode->name = sstrdup(name);
- mode->bindings = scalloc(sizeof(struct bindings_head));
+ mode->bindings = scalloc(1, sizeof(struct bindings_head));
TAILQ_INIT(mode->bindings);
SLIST_INSERT_HEAD(&modes, mode, modes);
Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
const char *release, const char *border, const char *whole_window,
const char *command, const char *modename) {
- Binding *new_binding = scalloc(sizeof(Binding));
+ Binding *new_binding = scalloc(1, sizeof(Binding));
DLOG("bindtype %s, modifiers %s, input code %s, release %s\n", bindtype, modifiers, input_code, release);
new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
new_binding->border = (border != NULL);
Binding *ret = smalloc(sizeof(Binding));
*ret = *bind;
if (bind->symbol != NULL)
- ret->symbol = strdup(bind->symbol);
+ ret->symbol = sstrdup(bind->symbol);
if (bind->command != NULL)
- ret->command = strdup(bind->command);
+ ret->command = sstrdup(bind->command);
if (bind->translated_to != NULL) {
ret->translated_to = smalloc(sizeof(xcb_keycode_t) * bind->number_keycodes);
memcpy(ret->translated_to, bind->translated_to, sizeof(xcb_keycode_t) * bind->number_keycodes);
*/
static void push_criterion(void *unused_criteria, const char *type,
const char *value) {
- struct criterion *criterion = malloc(sizeof(struct criterion));
- criterion->type = strdup(type);
- criterion->value = strdup(value);
+ struct criterion *criterion = smalloc(sizeof(struct criterion));
+ criterion->type = sstrdup(type);
+ criterion->value = sstrdup(value);
TAILQ_INSERT_TAIL(&criteria, criterion, criteria);
}
if (*walk == beginning)
return NULL;
- char *str = scalloc(*walk - beginning + 1);
+ char *str = scalloc(*walk - beginning + 1, 1);
/* We copy manually to handle escaping of characters. */
int inpos, outpos;
for (inpos = 0, outpos = 0;
CommandResult *parse_command(const char *input, yajl_gen gen) {
DLOG("COMMAND: *%s*\n", input);
state = INITIAL;
- CommandResult *result = scalloc(sizeof(CommandResult));
+ CommandResult *result = scalloc(1, sizeof(CommandResult));
/* A YAJL JSON generator used for formatting replies. */
command_output.json_gen = gen;
*
*/
Con *con_new_skeleton(Con *parent, i3Window *window) {
- Con *new = scalloc(sizeof(Con));
+ Con *new = scalloc(1, sizeof(Con));
new->on_remove_child = con_on_remove_child;
TAILQ_INSERT_TAIL(&all_cons, new, all_cons);
new->aspect_ratio = 0.0;
* checks before resetting the urgency.
*/
if (con->urgent && con_is_leaf(con)) {
- con->urgent = false;
+ con_set_urgency(con, false);
con_update_parents_urgency(con);
workspace_update_urgent_flag(con_get_workspace(con));
ipc_send_window_event("urgent", con);
*
*/
void con_set_urgency(Con *con, bool urgent) {
- if (focused == con) {
+ if (urgent && focused == con) {
DLOG("Ignoring urgency flag for current client\n");
- con->window->urgent.tv_sec = 0;
- con->window->urgent.tv_usec = 0;
return;
}
+ const bool old_urgent = con->urgent;
+
if (con->urgency_timer == NULL) {
con->urgent = urgent;
} else
if ((ws = con_get_workspace(con)) != NULL)
workspace_update_urgent_flag(ws);
- if (con->urgent == urgent) {
+ if (con->urgent != old_urgent) {
LOG("Urgency flag changed to %d\n", con->urgent);
ipc_send_window_event("urgent", con);
}
/* initialize default bindings if we're just validating the config file */
if (!use_nagbar && bindings == NULL) {
- bindings = scalloc(sizeof(struct bindings_head));
+ bindings = scalloc(1, sizeof(struct bindings_head));
TAILQ_INIT(bindings);
}
SLIST_INIT(&modes);
- struct Mode *default_mode = scalloc(sizeof(struct Mode));
+ struct Mode *default_mode = scalloc(1, sizeof(struct Mode));
default_mode->name = sstrdup("default");
- default_mode->bindings = scalloc(sizeof(struct bindings_head));
+ default_mode->bindings = scalloc(1, sizeof(struct bindings_head));
TAILQ_INIT(default_mode->bindings);
SLIST_INSERT_HEAD(&modes, default_mode, modes);
return;
}
DLOG("\t should execute command %s for the criteria mentioned above\n", command);
- Assignment *assignment = scalloc(sizeof(Assignment));
+ Assignment *assignment = scalloc(1, sizeof(Assignment));
assignment->type = A_COMMAND;
match_copy(&(assignment->match), current_match);
assignment->dest.command = sstrdup(command);
}
}
if (!duplicate) {
- assignment = scalloc(sizeof(struct Workspace_Assignment));
+ assignment = scalloc(1, sizeof(struct Workspace_Assignment));
assignment->name = sstrdup(workspace);
assignment->output = sstrdup(output);
TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
return;
}
DLOG("New assignment, using above criteria, to workspace \"%s\".\n", workspace);
- Assignment *assignment = scalloc(sizeof(Assignment));
+ Assignment *assignment = scalloc(1, sizeof(Assignment));
match_copy(&(assignment->match), current_match);
assignment->type = A_TO_WORKSPACE;
assignment->dest.workspace = sstrdup(workspace);
}
DLOG("New assignment, using above criteria, to ignore focus on manage.\n");
- Assignment *assignment = scalloc(sizeof(Assignment));
+ Assignment *assignment = scalloc(1, sizeof(Assignment));
match_copy(&(assignment->match), current_match);
assignment->type = A_NO_FOCUS;
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
}
}
- struct Barbinding *new_binding = scalloc(sizeof(struct Barbinding));
+ struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding));
new_binding->input_code = input_code;
new_binding->command = sstrdup(command);
TAILQ_INSERT_TAIL(&(current_bar.bar_bindings), new_binding, bindings);
/* Copy the current (static) structure into a dynamically allocated
* one, then cleanup our static one. */
- Barconfig *bar_config = scalloc(sizeof(Barconfig));
+ Barconfig *bar_config = scalloc(1, sizeof(Barconfig));
memcpy(bar_config, ¤t_bar, sizeof(Barconfig));
TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
*/
static void push_criterion(void *unused_criteria, const char *type,
const char *value) {
- struct criterion *criterion = malloc(sizeof(struct criterion));
- criterion->type = strdup(type);
- criterion->value = strdup(value);
+ struct criterion *criterion = smalloc(sizeof(struct criterion));
+ criterion->type = sstrdup(type);
+ criterion->value = sstrdup(value);
TAILQ_INSERT_TAIL(&criteria, criterion, criteria);
}
}
}
if (walk != beginning) {
- char *str = scalloc(walk - beginning + 1);
+ char *str = scalloc(walk - beginning + 1, 1);
/* We copy manually to handle escaping of characters. */
int inpos, outpos;
for (inpos = 0, outpos = 0;
/* Contains the same amount of characters as 'input' has, but with
* the unparseable part highlighted using ^ characters. */
- char *position = scalloc(strlen(error_line) + 1);
+ char *position = scalloc(strlen(error_line) + 1, 1);
const char *copywalk;
for (copywalk = error_line;
*copywalk != '\n' && *copywalk != '\r' && *copywalk != '\0';
/* read the script’s output */
int conv_size = 65535;
- char *converted = malloc(conv_size);
+ char *converted = smalloc(conv_size);
int read_bytes = 0, ret;
do {
if (read_bytes == conv_size) {
conv_size += 65535;
- converted = realloc(converted, conv_size);
+ converted = srealloc(converted, conv_size);
}
ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
if (ret == -1) {
if (fstat(fd, &stbuf) == -1)
die("Could not fstat file: %s\n", strerror(errno));
- buf = scalloc((stbuf.st_size + 1) * sizeof(char));
+ buf = scalloc(stbuf.st_size + 1, 1);
if ((fstr = fdopen(fd, "r")) == NULL)
die("Could not fdopen: %s\n", strerror(errno));
while (*v_value == '\t' || *v_value == ' ')
v_value++;
- struct Variable *new = scalloc(sizeof(struct Variable));
+ struct Variable *new = scalloc(1, sizeof(struct Variable));
new->key = sstrdup(v_key);
new->value = sstrdup(v_value);
SLIST_INSERT_HEAD(&variables, new, variables);
/* Then, allocate a new buffer and copy the file over to the new one,
* but replace occurences of our variables */
char *walk = buf, *destwalk;
- char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
+ char *new = smalloc(stbuf.st_size + extra_bytes + 1);
destwalk = new;
while (walk < (buf + stbuf.st_size)) {
/* Find the next variable */
}
}
- context = scalloc(sizeof(struct context));
+ context = scalloc(1, sizeof(struct context));
context->filename = f;
struct ConfigResultIR *config_output = parse_config(new, context);
new_output->rect.width = min(new_output->rect.width, width);
new_output->rect.height = min(new_output->rect.height, height);
} else {
- new_output = scalloc(sizeof(Output));
+ new_output = scalloc(1, sizeof(Output));
sasprintf(&(new_output->name), "fake-%d", num_screens);
DLOG("Created new fake output %s (%p)\n", new_output->name, new_output);
new_output->active = true;
uint32_t rnd = event->data.data32[1];
DLOG("[i3 sync protocol] Sending random value %d back to X11 window 0x%08x\n", rnd, window);
- void *reply = scalloc(32);
+ void *reply = scalloc(32, 1);
xcb_client_message_event_t *ev = reply;
ev->response_type = XCB_CLIENT_MESSAGE;
*
*/
void handle_event(int type, xcb_generic_event_t *event) {
- DLOG("event type %d, xkb_base %d\n", type, xkb_base);
+ if (type != XCB_MOTION_NOTIFY)
+ DLOG("event type %d, xkb_base %d\n", type, xkb_base);
+
if (randr_base > -1 &&
type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
handle_screen_change(event);
IPC_HANDLER(command) {
/* To get a properly terminated buffer, we copy
* message_size bytes out of the buffer */
- char *command = scalloc(message_size + 1);
+ char *command = scalloc(message_size + 1, 1);
strncpy(command, (const char *)message, message_size);
LOG("IPC: received: *%s*\n", command);
yajl_gen gen = yajl_gen_alloc(NULL);
/* To get a properly terminated buffer, we copy
* message_size bytes out of the buffer */
- char *bar_id = scalloc(message_size + 1);
+ char *bar_id = scalloc(message_size + 1, 1);
strncpy(bar_id, (const char *)message, message_size);
LOG("IPC: looking for config for bar ID \"%s\"\n", bar_id);
Barconfig *current, *config = NULL;
int event = client->num_events;
client->num_events++;
- client->events = realloc(client->events, client->num_events * sizeof(char *));
+ client->events = srealloc(client->events, client->num_events * sizeof(char *));
/* We copy the string because it is not null-terminated and strndup()
* is missing on some BSD systems */
- client->events[event] = scalloc(len + 1);
+ client->events[event] = scalloc(len + 1, 1);
memcpy(client->events[event], s, len);
DLOG("client is now subscribed to:\n");
set_nonblock(client);
- struct ev_io *package = scalloc(sizeof(struct ev_io));
+ struct ev_io *package = scalloc(1, sizeof(struct ev_io));
ev_io_init(package, ipc_receive_message, client, EV_READ);
ev_io_start(EV_A_ package);
DLOG("IPC: new client connected on fd %d\n", w->fd);
- ipc_client *new = scalloc(sizeof(ipc_client));
+ ipc_client *new = scalloc(1, sizeof(ipc_client));
new->fd = client;
TAILQ_INSERT_TAIL(&all_clients, new, clients);
static int json_key(void *ctx, const unsigned char *val, size_t len) {
LOG("key: %.*s\n", (int)len, val);
FREE(last_key);
- last_key = scalloc((len + 1) * sizeof(char));
+ last_key = scalloc(len + 1, 1);
memcpy(last_key, val, len);
if (strcasecmp(last_key, "swallows") == 0)
parsing_swallows = true;
free(sval);
} else {
if (strcasecmp(last_key, "name") == 0) {
- json_node->name = scalloc((len + 1) * sizeof(char));
+ json_node->name = scalloc(len + 1, 1);
memcpy(json_node->name, val, len);
} else if (strcasecmp(last_key, "sticky_group") == 0) {
- json_node->sticky_group = scalloc((len + 1) * sizeof(char));
+ json_node->sticky_group = scalloc(len + 1, 1);
memcpy(json_node->sticky_group, val, len);
LOG("sticky_group of this container is %s\n", json_node->sticky_group);
} else if (strcasecmp(last_key, "orientation") == 0) {
json_node->old_id = val;
if (parsing_focus) {
- struct focus_mapping *focus_mapping = scalloc(sizeof(struct focus_mapping));
+ struct focus_mapping *focus_mapping = scalloc(1, sizeof(struct focus_mapping));
focus_mapping->old_id = val;
TAILQ_INSERT_TAIL(&focus_mappings, focus_mapping, focus_mappings);
}
if (ipc_socket == -1) {
ELOG("Could not create the IPC socket, IPC disabled\n");
} else {
- struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
+ struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
ev_io_start(main_loop, ipc_io);
}
ELOG("Could not disable FD_CLOEXEC on fd %d\n", fd);
}
- struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
+ struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
ev_io_init(ipc_io, ipc_new_client, fd, EV_READ);
ev_io_start(main_loop, ipc_io);
}
ewmh_update_desktop_names();
ewmh_update_desktop_viewport();
- struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io));
- xcb_check = scalloc(sizeof(struct ev_check));
- struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare));
+ struct ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io));
+ xcb_check = scalloc(1, sizeof(struct ev_check));
+ struct ev_prepare *xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
ev_io_start(main_loop, xcb_watcher);
DLOG("Managing window 0x%08x\n", window);
- i3Window *cwindow = scalloc(sizeof(i3Window));
+ i3Window *cwindow = scalloc(1, sizeof(i3Window));
cwindow->id = window;
cwindow->depth = get_visual_depth(attr->visual);
if (cwindow->dock)
want_floating = false;
+ /* Plasma windows set their geometry in WM_SIZE_HINTS. */
+ if ((wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_US_POSITION || wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_POSITION) &&
+ (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_US_SIZE || wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_SIZE)) {
+ DLOG("We are setting geometry according to wm_size_hints x=%d y=%d w=%d h=%d\n",
+ wm_size_hints.x, wm_size_hints.y, wm_size_hints.width, wm_size_hints.height);
+ geom->x = wm_size_hints.x;
+ geom->y = wm_size_hints.y;
+ geom->width = wm_size_hints.width;
+ geom->height = wm_size_hints.height;
+ }
+
/* Store the requested geometry. The width/height gets raised to at least
* 75x50 when entering floating mode, which is the minimum size for a
* window to be useful (smaller windows are usually overlays/toolbars/…
void disable_randr(xcb_connection_t *conn) {
DLOG("RandR extension unusable, disabling.\n");
- Output *s = scalloc(sizeof(Output));
+ Output *s = scalloc(1, sizeof(Output));
s->active = true;
s->rect.x = 0;
topdock->type = CT_DOCKAREA;
topdock->layout = L_DOCKAREA;
/* this container swallows dock clients */
- Match *match = scalloc(sizeof(Match));
+ Match *match = scalloc(1, sizeof(Match));
match_init(match);
match->dock = M_DOCK_TOP;
match->insert_where = M_BELOW;
bottomdock->type = CT_DOCKAREA;
bottomdock->layout = L_DOCKAREA;
/* this container swallows dock clients */
- match = scalloc(sizeof(Match));
+ match = scalloc(1, sizeof(Match));
match_init(match);
match->dock = M_DOCK_BOTTOM;
match->insert_where = M_BELOW;
Output *new = get_output_by_id(id);
bool existing = (new != NULL);
if (!existing)
- new = scalloc(sizeof(Output));
+ new = scalloc(1, sizeof(Output));
new->id = id;
new->primary = (primary && primary->output == id);
FREE(new->name);
const char *error;
int errorcode, offset;
- struct regex *re = scalloc(sizeof(struct regex));
+ struct regex *re = scalloc(1, sizeof(struct regex));
re->pattern = sstrdup(pattern);
int options = PCRE_UTF8;
#ifdef PCRE_HAS_UCP
if (restore_conn == NULL || xcb_connection_has_error(restore_conn))
errx(EXIT_FAILURE, "Cannot open display\n");
- xcb_watcher = scalloc(sizeof(struct ev_io));
- xcb_check = scalloc(sizeof(struct ev_check));
- xcb_prepare = scalloc(sizeof(struct ev_prepare));
+ xcb_watcher = scalloc(1, sizeof(struct ev_io));
+ xcb_check = scalloc(1, sizeof(struct ev_check));
+ xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
ev_io_init(xcb_watcher, restore_xcb_got_event, xcb_get_file_descriptor(restore_conn), EV_READ);
ev_io_start(main_loop, xcb_watcher);
DLOG("Created placeholder window 0x%08x for leaf container %p / %s\n",
placeholder, con, con->name);
- placeholder_state *state = scalloc(sizeof(placeholder_state));
+ placeholder_state *state = scalloc(1, sizeof(placeholder_state));
state->window = placeholder;
state->con = con;
state->rect = con->rect;
free(first_word);
/* Trigger a timeout after 60 seconds */
- struct ev_timer *timeout = scalloc(sizeof(struct ev_timer));
+ struct ev_timer *timeout = scalloc(1, sizeof(struct ev_timer));
ev_timer_init(timeout, startup_timeout, 60.0, 0.);
timeout->data = context;
ev_timer_start(main_loop, timeout);
/* Save the ID and current workspace in our internal list of startup
* sequences */
Con *ws = con_get_workspace(focused);
- struct Startup_Sequence *sequence = scalloc(sizeof(struct Startup_Sequence));
+ struct Startup_Sequence *sequence = scalloc(1, sizeof(struct Startup_Sequence));
sequence->id = sstrdup(sn_launcher_context_get_startup_id(context));
sequence->workspace = sstrdup(ws->name);
sequence->context = context;
}
char *startup_id;
- if (asprintf(&startup_id, "%.*s", xcb_get_property_value_length(startup_id_reply),
- (char *)xcb_get_property_value(startup_id_reply)) == -1) {
- perror("asprintf()");
- DLOG("Could not get _NET_STARTUP_ID\n");
- free(startup_id_reply);
- return NULL;
- }
-
+ sasprintf(&startup_id, "%.*s", xcb_get_property_value_length(startup_id_reply),
+ (char *)xcb_get_property_value(startup_id_reply));
struct Startup_Sequence *current, *sequence = NULL;
TAILQ_FOREACH(current, &startup_sequences, sequences) {
if (strcmp(current->id, startup_id) != 0)
/* remove the urgency hint of the workspace (if set) */
if (con->urgent) {
- con->urgent = false;
+ con_set_urgency(con, false);
con_update_parents_urgency(con);
workspace_update_urgent_flag(con_get_workspace(con));
}
/* if the script is not in path, maybe the user installed to a strange
* location and runs the i3 binary with an absolute path. We use
* argv[0]’s dirname */
- char *pathbuf = strdup(start_argv[0]);
+ char *pathbuf = sstrdup(start_argv[0]);
char *dir = dirname(pathbuf);
sasprintf(&migratepath, "%s/%s", dir, name);
argv[0] = migratepath;
int num_args;
for (num_args = 0; start_argv[num_args] != NULL; num_args++)
;
- char **new_argv = scalloc((num_args + 3) * sizeof(char *));
+ char **new_argv = scalloc(num_args + 3, sizeof(char *));
/* copy the arguments, but skip the ones we'll replace */
int write_index = 0;
}
char *new_role;
- if (asprintf(&new_role, "%.*s", xcb_get_property_value_length(prop),
- (char *)xcb_get_property_value(prop)) == -1) {
- perror("asprintf()");
- DLOG("Could not get WM_WINDOW_ROLE\n");
- free(prop);
- return;
- }
+ sasprintf(&new_role, "%.*s", xcb_get_property_value_length(prop),
+ (char *)xcb_get_property_value(prop));
FREE(win->role);
win->role = new_role;
LOG("WM_WINDOW_ROLE changed to \"%s\"\n", win->role);
static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) {
Con *con = w->data;
+ ev_timer_stop(main_loop, con->urgency_timer);
+ FREE(con->urgency_timer);
+
if (con->urgent) {
DLOG("Resetting urgency flag of con %p by timer\n", con);
- con->urgent = false;
+ con_set_urgency(con, false);
con_update_parents_urgency(con);
workspace_update_urgent_flag(con_get_workspace(con));
ipc_send_window_event("urgent", con);
tree_render();
}
-
- ev_timer_stop(main_loop, con->urgency_timer);
- FREE(con->urgency_timer);
}
static void _workspace_show(Con *workspace) {
if (focused->urgency_timer == NULL) {
DLOG("Deferring reset of urgency flag of con %p on newly shown workspace %p\n",
focused, workspace);
- focused->urgency_timer = scalloc(sizeof(struct ev_timer));
+ focused->urgency_timer = scalloc(1, sizeof(struct ev_timer));
/* use a repeating timer to allow for easy resets */
ev_timer_init(focused->urgency_timer, workspace_defer_update_urgent_hint_cb,
config.workspace_urgency_timer, config.workspace_urgency_timer);
if (win_colormap != XCB_NONE)
xcb_free_colormap(conn, win_colormap);
- struct con_state *state = scalloc(sizeof(struct con_state));
+ struct con_state *state = scalloc(1, sizeof(struct con_state));
state->id = con->frame;
state->mapped = false;
state->initial = true;
/* 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_configure_notify_event_t */
- void *event = scalloc(32);
+ void *event = scalloc(32, 1);
xcb_client_message_event_t *ev = event;
ev->response_type = XCB_CLIENT_MESSAGE;
return;
/* 1: build deco_params and compare with cache */
- struct deco_render_params *p = scalloc(sizeof(struct deco_render_params));
+ struct deco_render_params *p = scalloc(1, sizeof(struct deco_render_params));
/* find out which colors to use */
if (con->urgent)
/* 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_configure_notify_event_t */
- void *event = scalloc(32);
+ void *event = scalloc(32, 1);
xcb_client_message_event_t *ev = event;
ev->response_type = XCB_CLIENT_MESSAGE;
s->rect.width = min(s->rect.width, screen_info[screen].width);
s->rect.height = min(s->rect.height, screen_info[screen].height);
} else {
- s = scalloc(sizeof(Output));
+ s = scalloc(1, sizeof(Output));
sasprintf(&(s->name), "xinerama-%d", num_screens);
DLOG("Created new Xinerama screen %s (%p)\n", s->name, s);
s->active = true;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Ensures the urgency hint is cleared properly in the case where i3 set it (due
+# to focus_on_window_activation=urgent), hence the application not clearing it.
+# Ticket: #1825
+# Bug still in: 4.10.3-253-g03799dd
+use i3test i3_autostart => 0;
+
+sub send_net_active_window {
+ my ($id) = @_;
+
+ my $msg = pack "CCSLLLLLLL",
+ X11::XCB::CLIENT_MESSAGE, # response_type
+ 32, # format
+ 0, # sequence
+ $id, # destination window
+ $x->atom(name => '_NET_ACTIVE_WINDOW')->id,
+ 0, # source
+ 0, 0, 0, 0;
+
+ $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+}
+
+my $config = <<'EOT';
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation urgent
+EOT
+
+my $pid = launch_with_config($config);
+my $i3 = i3(get_socket_path(0));
+my $ws = fresh_workspace;
+my $first = open_window;
+my $second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+is($x->input_focus, $second->id, 'second window still focused');
+
+cmd '[urgent=latest] focus';
+sync_with_i3;
+is($x->input_focus, $first->id, 'first window focused');
+
+cmd 'focus right';
+sync_with_i3;
+is($x->input_focus, $second->id, 'second window focused again');
+
+cmd '[urgent=latest] focus';
+sync_with_i3;
+is($x->input_focus, $second->id, 'second window still focused');
+
+exit_gracefully($pid);
+
+done_testing;