Gets the layout tree. i3 uses a tree as data structure which includes
every container. The reply will be the JSON-encoded tree (see the reply
section).
+GET_MARKS (5)::
+ Gets a list of marks (identifiers for containers to easily jump to them
+ later). The reply will be a JSON-encoded list of window marks (see
+ reply section).
So, a typical message could look like this:
--------------------------------------------------
Reply to the GET_OUTPUTS message.
GET_TREE (4)::
Reply to the GET_TREE message.
+GET_MARKS (5)::
+ Reply to the GET_MARKS message.
=== COMMAND reply
}
]
}
+
+
+=== GET_MARKS reply
+
+The reply consists of a single array of strings for each container that has a
+mark. The order of that array is undefined. If more than one container has the
+same mark, it will be represented multiple times in the reply (the array
+contents are not unique).
+
+If no window has a mark the response will be the empty array [].
------------------------
message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS;
else if (strcasecmp(optarg, "get_tree") == 0)
message_type = I3_IPC_MESSAGE_TYPE_GET_TREE;
+ else if (strcasecmp(optarg, "get_marks") == 0)
+ message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS;
else {
printf("Unknown message type\n");
- printf("Known types: command, get_workspaces, get_outputs, get_tree\n");
+ printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks\n");
exit(EXIT_FAILURE);
}
} else if (o == 'q') {
char *statusline_buffer;
struct rect_t {
- int x;
- int y;
- int w;
- int h;
+ int x;
+ int y;
+ int w;
+ int h;
};
#include "queue.h"
* Start parsing the received json-string
*
*/
-void parse_outputs_json(char* json);
+void parse_outputs_json(char* json);
/*
* Initiate the output-list
*
*/
-void init_outputs();
+void init_outputs();
/*
* Returns the output with the given name
*
*/
-i3_output* get_output_by_name(char* name);
+i3_output* get_output_by_name(char* name);
struct i3_output {
- char* name; /* Name of the output */
- bool active; /* If the output is active */
- int ws; /* The number of the currently visible ws */
- rect rect; /* The rect (relative to the root-win) */
+ char* name; /* Name of the output */
+ bool active; /* If the output is active */
+ int ws; /* The number of the currently visible ws */
+ rect rect; /* The rect (relative to the root-win) */
- xcb_window_t bar; /* The id of the bar of the output */
- xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */
- xcb_gcontext_t bargc; /* The graphical context of the bar */
+ xcb_window_t bar; /* The id of the bar of the output */
+ xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */
+ xcb_gcontext_t bargc; /* The graphical context of the bar */
- struct ws_head *workspaces; /* The workspaces on this output */
+ struct ws_head *workspaces; /* The workspaces on this output */
- SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
+ SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
};
#endif
if (rec == buffer_len) {
buffer_len += STDIN_CHUNK_SIZE;
buffer = realloc(buffer, buffer_len);
- }
+ }
}
if (*buffer == '\0') {
FREE(buffer);
*
*/
char *convert_ucs_to_utf8(char *input) {
- size_t input_size = 2;
- /* UTF-8 may consume up to 4 byte */
- int buffer_size = 8;
+ size_t input_size = 2;
+ /* UTF-8 may consume up to 4 byte */
+ int buffer_size = 8;
- char *buffer = calloc(buffer_size, 1);
+ char *buffer = calloc(buffer_size, 1);
if (buffer == NULL)
err(EXIT_FAILURE, "malloc() failed\n");
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
+ size_t output_size = buffer_size;
+ /* We need to use an additional pointer, because iconv() modifies it */
+ char *output = buffer;
- /* We convert the input into UCS-2 big endian */
+ /* We convert the input into UCS-2 big endian */
if (conversion_descriptor == 0) {
conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
if (conversion_descriptor == 0) {
}
}
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
+ /* Get the conversion descriptor back to original state */
+ iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
- /* Convert our text */
- int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
+ /* Convert our text */
+ int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
if (rc == (size_t)-1) {
perror("Converting to UCS-2 failed");
return NULL;
- }
+ }
- return buffer;
+ return buffer;
}
/*
*
*/
char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
- size_t input_size = strlen(input) + 1;
- /* UCS-2 consumes exactly two bytes for each glyph */
- int buffer_size = input_size * 2;
+ size_t input_size = strlen(input) + 1;
+ /* UCS-2 consumes exactly two bytes for each glyph */
+ int buffer_size = input_size * 2;
- char *buffer = malloc(buffer_size);
+ char *buffer = malloc(buffer_size);
if (buffer == NULL)
err(EXIT_FAILURE, "malloc() failed\n");
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
+ size_t output_size = buffer_size;
+ /* We need to use an additional pointer, because iconv() modifies it */
+ char *output = buffer;
- /* We convert the input into UCS-2 big endian */
+ /* We convert the input into UCS-2 big endian */
if (conversion_descriptor2 == 0) {
conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
if (conversion_descriptor2 == 0) {
}
}
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
+ /* Get the conversion descriptor back to original state */
+ iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
- /* Convert our text */
- int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
+ /* Convert our text */
+ int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
if (rc == (size_t)-1) {
perror("Converting to UCS-2 failed");
if (real_strlen != NULL)
- *real_strlen = 0;
+ *real_strlen = 0;
return NULL;
- }
+ }
if (real_strlen != NULL)
- *real_strlen = ((buffer_size - output_size) / 2) - 1;
+ *real_strlen = ((buffer_size - output_size) / 2) - 1;
- return buffer;
+ return buffer;
}
/** The default border style for new windows. */
border_style_t default_border;
+ /** The default border style for new floating windows. */
+ border_style_t default_floating_border;
+
/** The modifier which needs to be pressed in combination with your mouse
* buttons to do things with floating windows (move, resize) */
uint32_t floating_modifier;
/** Requests the tree layout from i3 */
#define I3_IPC_MESSAGE_TYPE_GET_TREE 4
+/** Request the current defined marks from i3 */
+#define I3_IPC_MESSAGE_TYPE_GET_MARKS 5
/*
* Messages from i3 to clients
/** Tree reply type */
#define I3_IPC_REPLY_TYPE_TREE 4
+/** Marks reply type*/
+#define I3_IPC_REPLY_TYPE_MARKS 5
/*
* Events from i3 to clients. Events have the first bit set high.
--- /dev/null
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosddaemonhfoo
+#define foosddaemonhfoo
+
+/***
+ Copyright 2010 Lennart Poettering
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ Reference implementation of a few systemd related interfaces for
+ writing daemons. These interfaces are trivial to implement. To
+ simplify porting we provide this reference implementation.
+ Applications are welcome to reimplement the algorithms described
+ here if they do not want to include these two source files.
+
+ The following functionality is provided:
+
+ - Support for logging with log levels on stderr
+ - File descriptor passing for socket-based activation
+ - Daemon startup and status notification
+ - Detection of systemd boots
+
+ You may compile this with -DDISABLE_SYSTEMD to disable systemd
+ support. This makes all those calls NOPs that are directly related to
+ systemd (i.e. only sd_is_xxx() will stay useful).
+
+ Since this is drop-in code we don't want any of our symbols to be
+ exported in any case. Hence we declare hidden visibility for all of
+ them.
+
+ You may find an up-to-date version of these source files online:
+
+ http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.h
+ http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.c
+
+ This should compile on non-Linux systems, too, but with the
+ exception of the sd_is_xxx() calls all functions will become NOPs.
+
+ See sd-daemon(7) for more information.
+*/
+
+#ifndef _sd_printf_attr_
+#if __GNUC__ >= 4
+#define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b)))
+#else
+#define _sd_printf_attr_(a,b)
+#endif
+#endif
+
+#ifndef _sd_hidden_
+#if (__GNUC__ >= 4) && !defined(SD_EXPORT_SYMBOLS)
+#define _sd_hidden_ __attribute__ ((visibility("hidden")))
+#else
+#define _sd_hidden_
+#endif
+#endif
+
+/*
+ Log levels for usage on stderr:
+
+ fprintf(stderr, SD_NOTICE "Hello World!\n");
+
+ This is similar to printk() usage in the kernel.
+*/
+#define SD_EMERG "<0>" /* system is unusable */
+#define SD_ALERT "<1>" /* action must be taken immediately */
+#define SD_CRIT "<2>" /* critical conditions */
+#define SD_ERR "<3>" /* error conditions */
+#define SD_WARNING "<4>" /* warning conditions */
+#define SD_NOTICE "<5>" /* normal but significant condition */
+#define SD_INFO "<6>" /* informational */
+#define SD_DEBUG "<7>" /* debug-level messages */
+
+/* The first passed file descriptor is fd 3 */
+#define SD_LISTEN_FDS_START 3
+
+/*
+ Returns how many file descriptors have been passed, or a negative
+ errno code on failure. Optionally, removes the $LISTEN_FDS and
+ $LISTEN_PID file descriptors from the environment (recommended, but
+ problematic in threaded environments). If r is the return value of
+ this function you'll find the file descriptors passed as fds
+ SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative
+ errno style error code on failure. This function call ensures that
+ the FD_CLOEXEC flag is set for the passed file descriptors, to make
+ sure they are not passed on to child processes. If FD_CLOEXEC shall
+ not be set, the caller needs to unset it after this call for all file
+ descriptors that are used.
+
+ See sd_listen_fds(3) for more information.
+*/
+int sd_listen_fds(int unset_environment) _sd_hidden_;
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is a FIFO in the file system stored under the
+ specified path, 0 otherwise. If path is NULL a path name check will
+ not be done and the call only verifies if the file descriptor
+ refers to a FIFO. Returns a negative errno style error code on
+ failure.
+
+ See sd_is_fifo(3) for more information.
+*/
+int sd_is_fifo(int fd, const char *path) _sd_hidden_;
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is a socket of the specified family (AF_INET,
+ ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If
+ family is 0 a socket family check will not be done. If type is 0 a
+ socket type check will not be done and the call only verifies if
+ the file descriptor refers to a socket. If listening is > 0 it is
+ verified that the socket is in listening mode. (i.e. listen() has
+ been called) If listening is == 0 it is verified that the socket is
+ not in listening mode. If listening is < 0 no listening mode check
+ is done. Returns a negative errno style error code on failure.
+
+ See sd_is_socket(3) for more information.
+*/
+int sd_is_socket(int fd, int family, int type, int listening) _sd_hidden_;
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is an Internet socket, of the specified family
+ (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM,
+ SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version
+ check is not done. If type is 0 a socket type check will not be
+ done. If port is 0 a socket port check will not be done. The
+ listening flag is used the same way as in sd_is_socket(). Returns a
+ negative errno style error code on failure.
+
+ See sd_is_socket_inet(3) for more information.
+*/
+int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) _sd_hidden_;
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is an AF_UNIX socket of the specified type
+ (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0
+ a socket type check will not be done. If path is NULL a socket path
+ check will not be done. For normal AF_UNIX sockets set length to
+ 0. For abstract namespace sockets set length to the length of the
+ socket name (including the initial 0 byte), and pass the full
+ socket path in path (including the initial 0 byte). The listening
+ flag is used the same way as in sd_is_socket(). Returns a negative
+ errno style error code on failure.
+
+ See sd_is_socket_unix(3) for more information.
+*/
+int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) _sd_hidden_;
+
+/*
+ Informs systemd about changed daemon state. This takes a number of
+ newline separated environment-style variable assignments in a
+ string. The following variables are known:
+
+ READY=1 Tells systemd that daemon startup is finished (only
+ relevant for services of Type=notify). The passed
+ argument is a boolean "1" or "0". Since there is
+ little value in signaling non-readiness the only
+ value daemons should send is "READY=1".
+
+ STATUS=... Passes a single-line status string back to systemd
+ that describes the daemon state. This is free-from
+ and can be used for various purposes: general state
+ feedback, fsck-like programs could pass completion
+ percentages and failing programs could pass a human
+ readable error message. Example: "STATUS=Completed
+ 66% of file system check..."
+
+ ERRNO=... If a daemon fails, the errno-style error code,
+ formatted as string. Example: "ERRNO=2" for ENOENT.
+
+ BUSERROR=... If a daemon fails, the D-Bus error-style error
+ code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut"
+
+ MAINPID=... The main pid of a daemon, in case systemd did not
+ fork off the process itself. Example: "MAINPID=4711"
+
+ Daemons can choose to send additional variables. However, it is
+ recommended to prefix variable names not listed above with X_.
+
+ Returns a negative errno-style error code on failure. Returns > 0
+ if systemd could be notified, 0 if it couldn't possibly because
+ systemd is not running.
+
+ Example: When a daemon finished starting up, it could issue this
+ call to notify systemd about it:
+
+ sd_notify(0, "READY=1");
+
+ See sd_notifyf() for more complete examples.
+
+ See sd_notify(3) for more information.
+*/
+int sd_notify(int unset_environment, const char *state) _sd_hidden_;
+
+/*
+ Similar to sd_notify() but takes a format string.
+
+ Example 1: A daemon could send the following after initialization:
+
+ sd_notifyf(0, "READY=1\n"
+ "STATUS=Processing requests...\n"
+ "MAINPID=%lu",
+ (unsigned long) getpid());
+
+ Example 2: A daemon could send the following shortly before
+ exiting, on failure:
+
+ sd_notifyf(0, "STATUS=Failed to start up: %s\n"
+ "ERRNO=%i",
+ strerror(errno),
+ errno);
+
+ See sd_notifyf(3) for more information.
+*/
+int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3) _sd_hidden_;
+
+/*
+ Returns > 0 if the system was booted with systemd. Returns < 0 on
+ error. Returns 0 if the system was not booted with systemd. Note
+ that all of the functions above handle non-systemd boots just
+ fine. You should NOT protect them with a call to this function. Also
+ note that this function checks whether the system, not the user
+ session is controlled by systemd. However the functions above work
+ for both user and system services.
+
+ See sd_booted(3) for more information.
+*/
+int sd_booted(void) _sd_hidden_;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
auto { return TOK_AUTO; }
workspace_layout { return TOK_WORKSPACE_LAYOUT; }
new_window { return TOKNEWWINDOW; }
+new_float { return TOKNEWFLOAT; }
normal { return TOK_NORMAL; }
none { return TOK_NONE; }
1pixel { return TOK_1PIXEL; }
waitpid(configerror_pid, NULL, 0);
}
+/*
+ * 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
+ * stderr and the has_errors variable is set to true, which will start
+ * i3-nagbar.
+ *
+ */
+static void check_for_duplicate_bindings(struct context *context) {
+ Binding *bind, *current;
+ TAILQ_FOREACH(current, bindings, bindings) {
+ TAILQ_FOREACH(bind, bindings, bindings) {
+ /* Abort when we reach the current keybinding, only check the
+ * bindings before */
+ if (bind == current)
+ break;
+
+ /* Check if one is using keysym while the other is using bindsym.
+ * If so, skip. */
+ /* XXX: It should be checked at a later place (when translating the
+ * keysym to keycodes) if there are any duplicates */
+ 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->mods != current->mods)
+ continue;
+ context->has_errors = true;
+ if (current->keycode != 0) {
+ ELOG("Duplicate keybinding in config file:\n modmask %d with keycode %d, command \"%s\"\n",
+ current->mods, current->keycode, current->command);
+ } else {
+ ELOG("Duplicate keybinding in config file:\n modmask %d with keysym %s, command \"%s\"\n",
+ current->mods, current->symbol, current->command);
+ }
+ }
+ }
+}
+
void parse_file(const char *f) {
SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
int fd, ret, read_bytes = 0;
exit(1);
}
+ check_for_duplicate_bindings(context);
+
if (context->has_errors) {
start_configerror_nagbar(f);
}
%token TOK_AUTO "auto"
%token TOK_WORKSPACE_LAYOUT "workspace_layout"
%token TOKNEWWINDOW "new_window"
+%token TOKNEWFLOAT "new_float"
%token TOK_NORMAL "normal"
%token TOK_NONE "none"
%token TOK_1PIXEL "1pixel"
%type <number> layout_mode
%type <number> border_style
%type <number> new_window
+%type <number> new_float
%type <number> colorpixel
%type <number> bool
%type <number> popup_setting
| orientation
| workspace_layout
| new_window
+ | new_float
| focus_follows_mouse
| force_focus_wrapping
| workspace_bar
}
;
+new_float:
+ TOKNEWFLOAT border_style
+ {
+ DLOG("new floating windows should start with border style %d\n", $2);
+ config.default_floating_border = $2;
+ }
+ ;
+
border_style:
TOK_NORMAL { $$ = BS_NORMAL; }
| TOK_NONE { $$ = BS_NONE; }
INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff");
config.default_border = BS_NORMAL;
+ config.default_floating_border = BS_NORMAL;
/* Set default_orientation to NO_ORIENTATION for auto orientation. */
config.default_orientation = NO_ORIENTATION;
con->percent = 1.0;
con->floating = FLOATING_USER_ON;
+ /* 4: set the border style as specified with new_float */
+ if (automatic)
+ con->border_style = config.default_floating_border;
+
/* Some clients (like GIMP’s color picker window) get mapped
* to (0, 0), so we push them to a reasonable position
* (centered over their leader) */
ystr("urgent");
y(bool, con->urgent);
+ if (con->mark != NULL) {
+ ystr("mark");
+ ystr(con->mark);
+ }
+
ystr("focused");
y(bool, (con == focused));
y(free);
}
+
/*
* Formats the reply message for a GET_WORKSPACES request and sends it to the
* client
y(free);
}
+/*
+ * Formats the reply message for a GET_MARKS request and sends it to the
+ * client
+ *
+ */
+IPC_HANDLER(get_marks) {
+#if YAJL_MAJOR >= 2
+ yajl_gen gen = yajl_gen_alloc(NULL);
+#else
+ yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+#endif
+ y(array_open);
+
+ Con *con;
+ TAILQ_FOREACH(con, &all_cons, all_cons)
+ if (con->mark != NULL)
+ ystr(con->mark);
+
+ y(array_close);
+
+ const unsigned char *payload;
+ unsigned int length;
+ y(get_buf, &payload, &length);
+
+ ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_MARKS, length);
+ y(free);
+}
+
/*
* Callback for the YAJL parser (will be called when a string is parsed).
*
/* The index of each callback function corresponds to the numeric
* value of the message type (see include/i3/ipc.h) */
-handler_t handlers[5] = {
+handler_t handlers[6] = {
handle_command,
handle_get_workspaces,
handle_subscribe,
handle_get_outputs,
- handle_tree
+ handle_tree,
+ handle_get_marks
};
/*
ev_io_init(package, ipc_receive_message, client, EV_READ);
ev_io_start(EV_A_ package);
- DLOG("IPC: new client connected\n");
+ DLOG("IPC: new client connected on fd %d\n", w->fd);
ipc_client *new = scalloc(sizeof(ipc_client));
new->fd = client;
json_node->layout = L_OUTPUT;
else LOG("Unhandled \"layout\": %s\n", buf);
free(buf);
+ } else if (strcasecmp(last_key, "mark") == 0) {
+ char *buf = NULL;
+ asprintf(&buf, "%.*s", (int)len, val);
+ json_node->mark = buf;
}
}
return 1;
#include <fcntl.h>
#include "all.h"
+#include "sd-daemon.h"
+
static int xkb_event_base;
int xkb_current_group;
ev_io_start(main_loop, ipc_io);
}
+ /* Also handle the UNIX domain sockets passed via socket activation */
+ int fds = sd_listen_fds(1);
+ if (fds < 0)
+ ELOG("socket activation: Error in sd_listen_fds\n");
+ else if (fds == 0)
+ DLOG("socket activation: no sockets passed\n");
+ else {
+ for (int fd = SD_LISTEN_FDS_START; fd < (SD_LISTEN_FDS_START + fds); fd++) {
+ DLOG("socket activation: also listening on fd %d\n", fd);
+ struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
+ ev_io_init(ipc_io, ipc_new_client, fd, EV_READ);
+ ev_io_start(main_loop, ipc_io);
+ }
+ }
+
/* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */
x_set_i3_atoms();
if (want_floating) {
DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
- floating_enable(nc, false);
+ floating_enable(nc, true);
}
/* to avoid getting an UnmapNotify event due to reparenting, we temporarily
--- /dev/null
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ Copyright 2010 Lennart Poettering
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/fcntl.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stddef.h>
+
+#include "sd-daemon.h"
+
+int sd_listen_fds(int unset_environment) {
+
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+ return 0;
+#else
+ int r, fd;
+ const char *e;
+ char *p = NULL;
+ unsigned long l;
+
+ if (!(e = getenv("LISTEN_PID"))) {
+ r = 0;
+ goto finish;
+ }
+
+ errno = 0;
+ l = strtoul(e, &p, 10);
+
+ if (errno != 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (!p || *p || l <= 0) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ /* Is this for us? */
+ if (getpid() != (pid_t) l) {
+ r = 0;
+ goto finish;
+ }
+
+ if (!(e = getenv("LISTEN_FDS"))) {
+ r = 0;
+ goto finish;
+ }
+
+ errno = 0;
+ l = strtoul(e, &p, 10);
+
+ if (errno != 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (!p || *p) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) {
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFD)) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (flags & FD_CLOEXEC)
+ continue;
+
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
+ r = -errno;
+ goto finish;
+ }
+ }
+
+ r = (int) l;
+
+finish:
+ if (unset_environment) {
+ unsetenv("LISTEN_PID");
+ unsetenv("LISTEN_FDS");
+ }
+
+ return r;
+#endif
+}
+
+int sd_is_fifo(int fd, const char *path) {
+ struct stat st_fd;
+
+ if (fd < 0)
+ return -EINVAL;
+
+ memset(&st_fd, 0, sizeof(st_fd));
+ if (fstat(fd, &st_fd) < 0)
+ return -errno;
+
+ if (!S_ISFIFO(st_fd.st_mode))
+ return 0;
+
+ if (path) {
+ struct stat st_path;
+
+ memset(&st_path, 0, sizeof(st_path));
+ if (stat(path, &st_path) < 0) {
+
+ if (errno == ENOENT || errno == ENOTDIR)
+ return 0;
+
+ return -errno;
+ }
+
+ return
+ st_path.st_dev == st_fd.st_dev &&
+ st_path.st_ino == st_fd.st_ino;
+ }
+
+ return 1;
+}
+
+static int sd_is_socket_internal(int fd, int type, int listening) {
+ struct stat st_fd;
+
+ if (fd < 0 || type < 0)
+ return -EINVAL;
+
+ if (fstat(fd, &st_fd) < 0)
+ return -errno;
+
+ if (!S_ISSOCK(st_fd.st_mode))
+ return 0;
+
+ if (type != 0) {
+ int other_type = 0;
+ socklen_t l = sizeof(other_type);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0)
+ return -errno;
+
+ if (l != sizeof(other_type))
+ return -EINVAL;
+
+ if (other_type != type)
+ return 0;
+ }
+
+ if (listening >= 0) {
+ int accepting = 0;
+ socklen_t l = sizeof(accepting);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0)
+ return -errno;
+
+ if (l != sizeof(accepting))
+ return -EINVAL;
+
+ if (!accepting != !listening)
+ return 0;
+ }
+
+ return 1;
+}
+
+union sockaddr_union {
+ struct sockaddr sa;
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ struct sockaddr_un un;
+ struct sockaddr_storage storage;
+};
+
+int sd_is_socket(int fd, int family, int type, int listening) {
+ int r;
+
+ if (family < 0)
+ return -EINVAL;
+
+ if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+ return r;
+
+ if (family > 0) {
+ union sockaddr_union sockaddr;
+ socklen_t l;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ l = sizeof(sockaddr);
+
+ if (getsockname(fd, &sockaddr.sa, &l) < 0)
+ return -errno;
+
+ if (l < sizeof(sa_family_t))
+ return -EINVAL;
+
+ return sockaddr.sa.sa_family == family;
+ }
+
+ return 1;
+}
+
+int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) {
+ union sockaddr_union sockaddr;
+ socklen_t l;
+ int r;
+
+ if (family != 0 && family != AF_INET && family != AF_INET6)
+ return -EINVAL;
+
+ if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+ return r;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ l = sizeof(sockaddr);
+
+ if (getsockname(fd, &sockaddr.sa, &l) < 0)
+ return -errno;
+
+ if (l < sizeof(sa_family_t))
+ return -EINVAL;
+
+ if (sockaddr.sa.sa_family != AF_INET &&
+ sockaddr.sa.sa_family != AF_INET6)
+ return 0;
+
+ if (family > 0)
+ if (sockaddr.sa.sa_family != family)
+ return 0;
+
+ if (port > 0) {
+ if (sockaddr.sa.sa_family == AF_INET) {
+ if (l < sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ return htons(port) == sockaddr.in4.sin_port;
+ } else {
+ if (l < sizeof(struct sockaddr_in6))
+ return -EINVAL;
+
+ return htons(port) == sockaddr.in6.sin6_port;
+ }
+ }
+
+ return 1;
+}
+
+int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) {
+ union sockaddr_union sockaddr;
+ socklen_t l;
+ int r;
+
+ if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+ return r;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ l = sizeof(sockaddr);
+
+ if (getsockname(fd, &sockaddr.sa, &l) < 0)
+ return -errno;
+
+ if (l < sizeof(sa_family_t))
+ return -EINVAL;
+
+ if (sockaddr.sa.sa_family != AF_UNIX)
+ return 0;
+
+ if (path) {
+ if (length <= 0)
+ length = strlen(path);
+
+ if (length <= 0)
+ /* Unnamed socket */
+ return l == offsetof(struct sockaddr_un, sun_path);
+
+ if (path[0])
+ /* Normal path socket */
+ return
+ (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) &&
+ memcmp(path, sockaddr.un.sun_path, length+1) == 0;
+ else
+ /* Abstract namespace socket */
+ return
+ (l == offsetof(struct sockaddr_un, sun_path) + length) &&
+ memcmp(path, sockaddr.un.sun_path, length) == 0;
+ }
+
+ return 1;
+}
+
+int sd_notify(int unset_environment, const char *state) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__) || !defined(SOCK_CLOEXEC)
+ return 0;
+#else
+ int fd = -1, r;
+ struct msghdr msghdr;
+ struct iovec iovec;
+ union sockaddr_union sockaddr;
+ const char *e;
+
+ if (!state) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ if (!(e = getenv("NOTIFY_SOCKET")))
+ return 0;
+
+ /* Must be an abstract socket, or an absolute path */
+ if ((e[0] != '@' && e[0] != '/') || e[1] == 0) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ sockaddr.sa.sa_family = AF_UNIX;
+ strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path));
+
+ if (sockaddr.un.sun_path[0] == '@')
+ sockaddr.un.sun_path[0] = 0;
+
+ memset(&iovec, 0, sizeof(iovec));
+ iovec.iov_base = (char*) state;
+ iovec.iov_len = strlen(state);
+
+ memset(&msghdr, 0, sizeof(msghdr));
+ msghdr.msg_name = &sockaddr;
+ msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e);
+
+ if (msghdr.msg_namelen > sizeof(struct sockaddr_un))
+ msghdr.msg_namelen = sizeof(struct sockaddr_un);
+
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+
+ if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ r = 1;
+
+finish:
+ if (unset_environment)
+ unsetenv("NOTIFY_SOCKET");
+
+ if (fd >= 0)
+ close(fd);
+
+ return r;
+#endif
+}
+
+int sd_notifyf(int unset_environment, const char *format, ...) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+ return 0;
+#else
+ va_list ap;
+ char *p = NULL;
+ int r;
+
+ va_start(ap, format);
+ r = vasprintf(&p, format, ap);
+ va_end(ap);
+
+ if (r < 0 || !p)
+ return -ENOMEM;
+
+ r = sd_notify(unset_environment, p);
+ free(p);
+
+ return r;
+#endif
+}
+
+int sd_booted(void) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+ return 0;
+#else
+
+ struct stat a, b;
+
+ /* We simply test whether the systemd cgroup hierarchy is
+ * mounted */
+
+ if (lstat("/sys/fs/cgroup", &a) < 0)
+ return 0;
+
+ if (lstat("/sys/fs/cgroup/systemd", &b) < 0)
+ return 0;
+
+ return a.st_dev != b.st_dev;
+#endif
+}
workspace->name = sstrdup(num);
/* We set ->num to the number if this workspace’s name consists only of
* a positive number. Otherwise it’s a named ws and num will be -1. */
- char *end;
- long parsed_num = strtol(num, &end, 10);
+
+ long parsed_num = strtol(num, NULL, 10);
if (parsed_num == LONG_MIN ||
parsed_num == LONG_MAX ||
- parsed_num < 0 ||
- (end && *end != '\0'))
+ parsed_num <= 0)
workspace->num = -1;
else workspace->num = parsed_num;
LOG("num = %d\n", workspace->num);
use Getopt::Long;
use Time::HiRes qw(sleep);
use X11::XCB::Connection;
+use IO::Socket::UNIX; # core
+use POSIX; # core
+use AnyEvent::Handle;
+
+# open a file so that we get file descriptor 3. we will later close it in the
+# child and dup() the listening socket file descriptor to 3 to pass it to i3
+open(my $reserved, '<', '/dev/null');
+if (fileno($reserved) != 3) {
+ warn "Socket file descriptor is not 3.";
+ warn "Please don't start this script within a subshell of vim or something.";
+ exit 1;
+}
# install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV
# XXX: we could maybe also use a different loop than the default loop in EV?
};
}
-my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
my $config = slurp('i3-test.config');
# 1: get a list of all testcases
#
sub take_job {
my ($display) = @_;
+
+ my $test = shift @testfiles;
+ return unless $test;
+ my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/);
+ my $logpath = "$outdir/i3-log-for-" . basename($test);
+
my ($fh, $tmpfile) = tempfile();
say $fh $config;
say $fh "ipc-socket /tmp/nested-$display";
close($fh);
- my $test = shift @testfiles;
- return unless $test;
- my $logpath = "$outdir/i3-log-for-" . basename($test);
- my $cmd = "export DISPLAY=$display; exec $i3cmd -c $tmpfile >$logpath 2>&1";
- my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/);
+ my $activate_cv = AnyEvent->condvar;
+ my $start_i3 = sub {
+ # remove the old unix socket
+ unlink("/tmp/nested-$display-activation");
+
+ # pass all file descriptors up to three to the children.
+ # we need to set this flag before opening the socket.
+ open(my $fdtest, '<', '/dev/null');
+ $^F = fileno($fdtest);
+ close($fdtest);
+ my $socket = IO::Socket::UNIX->new(
+ Listen => 1,
+ Local => "/tmp/nested-$display-activation",
+ );
- my $process = Proc::Background->new($cmd) unless $dont_start;
- say "[$display] Running $test with logfile $logpath";
+ my $pid = fork;
+ if (!defined($pid)) {
+ die "could not fork()";
+ }
+ say "pid = $pid";
+ if ($pid == 0) {
+ say "child!";
+ $ENV{LISTEN_PID} = $$;
+ $ENV{LISTEN_FDS} = 1;
+ $ENV{DISPLAY} = $display;
+ $^F = 3;
+
+ say "fileno is " . fileno($socket);
+ close($reserved);
+ POSIX::dup2(fileno($socket), 3);
+
+ # now execute i3
+ my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
+ my $cmd = "exec $i3cmd -c $tmpfile >$logpath 2>&1";
+ exec "/bin/sh", '-c', $cmd;
+
+ # if we are still here, i3 could not be found or exec failed. bail out.
+ exit 1;
+ }
+
+ my $child_watcher;
+ $child_watcher = AnyEvent->child(pid => $pid, cb => sub {
+ say "child died. pid = $pid";
+ undef $child_watcher;
+ });
+
+ # close the socket, the child process should be the only one which keeps a file
+ # descriptor on the listening socket.
+ $socket->close;
+
+ # We now connect (will succeed immediately) and send a request afterwards.
+ # As soon as the reply is there, i3 is considered ready.
+ my $cl = IO::Socket::UNIX->new(Peer => "/tmp/nested-$display-activation");
+ my $hdl;
+ $hdl = AnyEvent::Handle->new(fh => $cl, on_error => sub { $activate_cv->send(0) });
+
+ # send a get_tree message without payload
+ $hdl->push_write('i3-ipc' . pack("LL", 0, 4));
+
+ # wait for the reply
+ $hdl->push_read(chunk => 1, => sub {
+ my ($h, $line) = @_;
+ say "read something from i3";
+ $activate_cv->send(1);
+ undef $hdl;
+ });
+
+ return $pid;
+ };
+
+ my $pid;
+ $pid = $start_i3->() unless $dont_start;
- sleep 0.5;
my $kill_i3 = sub {
# Don’t bother killing i3 when we haven’t started it
return if $dont_start;
}
say "[$display] killing i3";
- kill(9, $process->pid) or die "could not kill i3";
+ kill(9, $pid) or die "could not kill i3";
};
- my $output;
- my $parser = TAP::Parser->new({
- exec => [ 'sh', '-c', "DISPLAY=$display /usr/bin/perl -It/lib $test" ],
- spool => IO::Scalar->new(\$output),
- merge => 1,
- });
-
- my @watchers;
- my ($stdout, $stderr) = $parser->get_select_handles;
- for my $handle ($parser->get_select_handles) {
- my $w;
- $w = AnyEvent->io(
- fh => $handle,
- poll => 'r',
- cb => sub {
- # Ignore activity on stderr (unnecessary with merge => 1,
- # but let’s keep it in here if we want to use merge => 0
- # for some reason in the future).
- return if defined($stderr) and $handle == $stderr;
-
- my $result = $parser->next;
- if (defined($result)) {
- # TODO: check if we should bail out
- return;
+ # This will be called as soon as i3 is running and answered to our
+ # IPC request
+ $activate_cv->cb(sub {
+ say "cb";
+ my ($status) = $activate_cv->recv;
+ say "complete-run: status = $status";
+
+ say "[$display] Running $test with logfile $logpath";
+
+ my $output;
+ my $parser = TAP::Parser->new({
+ exec => [ 'sh', '-c', "DISPLAY=$display /usr/bin/perl -It/lib $test" ],
+ spool => IO::Scalar->new(\$output),
+ merge => 1,
+ });
+
+ my @watchers;
+ my ($stdout, $stderr) = $parser->get_select_handles;
+ for my $handle ($parser->get_select_handles) {
+ my $w;
+ $w = AnyEvent->io(
+ fh => $handle,
+ poll => 'r',
+ cb => sub {
+ # Ignore activity on stderr (unnecessary with merge => 1,
+ # but let’s keep it in here if we want to use merge => 0
+ # for some reason in the future).
+ return if defined($stderr) and $handle == $stderr;
+
+ my $result = $parser->next;
+ if (defined($result)) {
+ # TODO: check if we should bail out
+ return;
+ }
+
+ # $result is not defined, we are done parsing
+ say "[$display] $test finished";
+ close($parser->delete_spool);
+ $aggregator->add($test, $parser);
+ push @done, [ $test, $output ];
+
+ $kill_i3->();
+
+ undef $_ for @watchers;
+ if (@done == $num) {
+ $cv->send;
+ } else {
+ take_job($display);
+ }
}
+ );
+ push @watchers, $w;
+ }
+ });
- # $result is not defined, we are done parsing
- say "[$display] $test finished";
- close($parser->delete_spool);
- $aggregator->add($test, $parser);
- push @done, [ $test, $output ];
-
- $kill_i3->();
-
- undef $_ for @watchers;
- if (@done == $num) {
- $cv->send;
- } else {
- take_job($display);
- }
- }
- );
- push @watchers, $w;
- }
+ $activate_cv->send(1) if $dont_start;
}
$cv->recv;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# checks if the IPC message type get_marks works correctly
+#
+use i3test;
+
+# TODO: this will be available in AnyEvent::I3 soon
+sub get_marks {
+ my $i3 = i3(get_socket_path());
+ $i3->connect->recv;
+ my $cv = AnyEvent->condvar;
+ my $msg = $i3->message(5);
+ my $t;
+ $msg->cb(sub {
+ my ($_cv) = @_;
+ $cv->send($_cv->recv);
+ });
+ $t = AnyEvent->timer(after => 2, cb => sub {
+ $cv->croak('timeout while waiting for the marks');
+ });
+ return $cv->recv;
+}
+
+##############################################################
+# 1: check that get_marks returns no marks yet
+##############################################################
+
+my $tmp = fresh_workspace;
+
+my $marks = get_marks();
+cmp_deeply($marks, [], 'no marks set so far');
+
+##############################################################
+# 2: check that setting a mark is reflected in the get_marks reply
+##############################################################
+
+cmd 'open';
+cmd 'mark foo';
+
+cmp_deeply(get_marks(), [ 'foo' ], 'mark foo set');
+
+##############################################################
+# 3: check that the mark is gone after killing the container
+##############################################################
+
+cmd 'kill';
+
+cmp_deeply(get_marks(), [ ], 'mark gone');
+
+##############################################################
+# 4: check that duplicate marks are included twice in the get_marks reply
+##############################################################
+
+cmd 'open';
+cmd 'mark bar';
+
+cmd 'open';
+cmd 'mark bar';
+
+cmp_deeply(get_marks(), [ 'bar', 'bar' ], 'duplicate mark found twice');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests the new_window and new_float config option.
+#
+
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+#####################################################################
+# 1: check that new windows start with 'normal' border unless configured
+# otherwise
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $process = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_standard_window($x);
+
+my @content = @{get_ws_content($tmp)};
+ok(@content == 1, 'one container opened');
+is($content[0]->{border}, 'normal', 'border normal by default');
+
+exit_gracefully($process->pid);
+
+#####################################################################
+# 2: check that new tiling windows start with '1pixel' border when
+# configured
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+new_window 1pixel
+EOT
+
+$process = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_standard_window($x);
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 1, 'one container opened');
+is($content[0]->{border}, '1pixel', 'border normal by default');
+
+exit_gracefully($process->pid);
+
+#####################################################################
+# 3: check that new floating windows start with 'normal' border unless
+# configured otherwise
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+$process = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+$first = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30],
+ background_color => '#C0C0C0',
+ # replace the type with 'utility' as soon as the coercion works again in X11::XCB
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+);
+
+$first->map;
+
+sleep 0.25;
+
+my $wscontent = get_ws($tmp);
+my @floating = @{$wscontent->{floating_nodes}};
+ok(@floating == 1, 'one floating container opened');
+my $floatingcon = $floating[0];
+is($floatingcon->{nodes}->[0]->{border}, 'normal', 'border normal by default');
+
+exit_gracefully($process->pid);
+
+#####################################################################
+# 4: check that new floating windows start with '1pixel' border when
+# configured
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+new_float 1pixel
+EOT
+
+$process = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+$first = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30],
+ background_color => '#C0C0C0',
+ # replace the type with 'utility' as soon as the coercion works again in X11::XCB
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+);
+
+$first->map;
+
+sleep 0.25;
+
+$wscontent = get_ws($tmp);
+@floating = @{$wscontent->{floating_nodes}};
+ok(@floating == 1, 'one floating container opened');
+$floatingcon = $floating[0];
+is($floatingcon->{nodes}->[0]->{border}, '1pixel', 'border normal by default');
+
+exit_gracefully($process->pid);
+
+done_testing;