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 [].
------------------------
workspace 1, 3, 4 and 9 and you want to cycle through them with a single key
combination.
+To move a container to another xrandr output such as +LVDS1+ or +VGA1+, you can
+use the +move output+ command followed by the name of the target output. You
+may also use +left+, +right+, +up+, +down+ instead of the xrandr output name to
+move to the the next output in the specified direction.
+
*Examples*:
-------------------------
bindsym mod+1 workspace 1
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') {
#ifndef COMMON_H_
#define COMMON_H_
+#include <stdbool.h>
+
typedef struct rect_t rect;
-typedef int bool;
struct ev_loop* main_loop;
char *statusline;
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"
#include "outputs.h"
#include "util.h"
#include "workspaces.h"
+#include "trayclients.h"
#include "xcb.h"
#include "ucs2_to_utf8.h"
#include "config.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 */
+ struct tc_head *trayclients; /* The tray clients on this output */
- SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
+ SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
};
#endif
--- /dev/null
+/*
+ * i3bar - an xcb-based status- and ws-bar for i3
+ *
+ * © 2010-2011 Axel Wagner and contributors
+ *
+ * See file LICNSE for license information
+ *
+ */
+#ifndef TRAYCLIENT_H_
+#define TRAYCLIENT_H_
+
+#include "common.h"
+
+typedef struct trayclient trayclient;
+
+TAILQ_HEAD(tc_head, trayclient);
+
+struct trayclient {
+ xcb_window_t win; /* The window ID of the tray client */
+ bool mapped; /* Whether this window is mapped */
+ int xe_version; /* The XEMBED version supported by the client */
+
+ TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */
+};
+
+#endif
#include <stdint.h>
//#include "outputs.h"
+#ifdef XCB_COMPAT
+#define XCB_ATOM_CARDINAL CARDINAL
+#endif
+
+#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+#define XEMBED_MAPPED (1 << 0)
+#define XEMBED_EMBEDDED_NOTIFY 0
+
struct xcb_color_strings_t {
char *bar_fg;
char *bar_bg;
ATOM_DO(_NET_WM_WINDOW_TYPE_DOCK)
ATOM_DO(_NET_WM_STRUT_PARTIAL)
ATOM_DO(I3_SOCKET_PATH)
+ATOM_DO(MANAGER)
+ATOM_DO(_NET_SYSTEM_TRAY_ORIENTATION)
+ATOM_DO(_NET_SYSTEM_TRAY_VISUAL)
+ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
+ATOM_DO(_XEMBED_INFO)
+ATOM_DO(_XEMBED)
#undef ATOM_DO
if (rec == buffer_len) {
buffer_len += STDIN_CHUNK_SIZE;
buffer = realloc(buffer, buffer_len);
- }
+ }
}
if (*buffer == '\0') {
FREE(buffer);
* Parse a boolean value (active)
*
*/
-static int outputs_boolean_cb(void *params_, bool val) {
+static int outputs_boolean_cb(void *params_, int val) {
struct outputs_json_params *params = (struct outputs_json_params*) params_;
if (strcmp(params->cur_key, "active")) {
new_output->workspaces = malloc(sizeof(struct ws_head));
TAILQ_INIT(new_output->workspaces);
+ new_output->trayclients = malloc(sizeof(struct tc_head));
+ TAILQ_INIT(new_output->trayclients);
+
params->outputs_walk = new_output;
return 1;
*
*/
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;
}
* Parse a boolean value (visible, focused, urgent)
*
*/
-static int workspaces_boolean_cb(void *params_, bool val) {
+static int workspaces_boolean_cb(void *params_, int val) {
struct workspaces_json_params *params = (struct workspaces_json_params*) params_;
if (!strcmp(params->cur_key, "visible")) {
/* Variables, that are the same for all functions at all times */
xcb_connection_t *xcb_connection;
+int screen;
xcb_screen_t *xcb_screen;
xcb_window_t xcb_root;
xcb_font_t xcb_font;
i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer);
}
+/*
+ * Configures the x coordinate of all trayclients. To be called after adding a
+ * new tray client or removing an old one.
+ *
+ */
+static void configure_trayclients() {
+ trayclient *trayclient;
+ i3_output *output;
+ SLIST_FOREACH(output, outputs, slist) {
+ if (!output->active)
+ continue;
+
+ int clients = 0;
+ TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
+ clients++;
+
+ DLOG("Configuring tray window %08x to x=%d\n",
+ trayclient->win, output->rect.w - (clients * (font_height + 2)));
+ uint32_t x = output->rect.w - (clients * (font_height + 2));
+ xcb_configure_window(xcb_connection,
+ trayclient->win,
+ XCB_CONFIG_WINDOW_X,
+ &x);
+ }
+ }
+}
+
+/*
+ * Handles ClientMessages (messages sent from another client directly to us).
+ *
+ * At the moment, only the tray window will receive client messages. All
+ * supported client messages currently are _NET_SYSTEM_TRAY_OPCODE.
+ *
+ */
+static void handle_client_message(xcb_client_message_event_t* event) {
+ if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
+ event->format == 32) {
+ DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
+ /* event->data.data32[0] is the timestamp */
+ uint32_t op = event->data.data32[1];
+ uint32_t mask;
+ uint32_t values[2];
+ if (op == SYSTEM_TRAY_REQUEST_DOCK) {
+ xcb_window_t client = event->data.data32[2];
+
+ /* Listen for PropertyNotify events to get the most recent value of
+ * the XEMBED_MAPPED atom, also listen for UnmapNotify events */
+ mask = XCB_CW_EVENT_MASK;
+ values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+ xcb_change_window_attributes(xcb_connection,
+ client,
+ mask,
+ values);
+
+ /* Request the _XEMBED_INFO property. The XEMBED specification
+ * (which is referred by the tray specification) says this *has* to
+ * be set, but VLC does not set it… */
+ bool map_it = true;
+ int xe_version = 1;
+ xcb_get_property_cookie_t xembedc;
+ xembedc = xcb_get_property_unchecked(xcb_connection,
+ 0,
+ client,
+ atoms[_XEMBED_INFO],
+ XCB_GET_PROPERTY_TYPE_ANY,
+ 0,
+ 2 * 32);
+
+ xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection,
+ xembedc,
+ NULL);
+ if (xembedr != NULL && xembedr->length != 0) {
+ DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length);
+ uint32_t *xembed = xcb_get_property_value(xembedr);
+ DLOG("xembed version = %d\n", xembed[0]);
+ DLOG("xembed flags = %d\n", xembed[1]);
+ map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
+ xe_version = xembed[0];
+ if (xe_version > 1)
+ xe_version = 1;
+ free(xembedr);
+ } else {
+ ELOG("Window %08x violates the XEMBED protocol, _XEMBED_INFO not set\n", client);
+ }
+
+ DLOG("X window %08x requested docking\n", client);
+ i3_output *walk, *output = NULL;
+ SLIST_FOREACH(walk, outputs, slist) {
+ if (!walk->active)
+ continue;
+ DLOG("using output %s\n", walk->name);
+ output = walk;
+ }
+ if (output == NULL) {
+ ELOG("No output found\n");
+ return;
+ }
+ xcb_reparent_window(xcb_connection,
+ client,
+ output->bar,
+ output->rect.w - font_height - 2,
+ 2);
+ /* We reconfigure the window to use a reasonable size. The systray
+ * specification explicitly says:
+ * Tray icons may be assigned any size by the system tray, and
+ * should do their best to cope with any size effectively
+ */
+ mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
+ values[0] = font_height;
+ values[1] = font_height;
+ xcb_configure_window(xcb_connection,
+ client,
+ mask,
+ values);
+
+ /* send the XEMBED_EMBEDDED_NOTIFY message */
+ void *event = calloc(32, 1);
+ xcb_client_message_event_t *ev = event;
+ ev->response_type = XCB_CLIENT_MESSAGE;
+ ev->window = client;
+ ev->type = atoms[_XEMBED];
+ ev->format = 32;
+ ev->data.data32[0] = XCB_CURRENT_TIME;
+ ev->data.data32[1] = atoms[XEMBED_EMBEDDED_NOTIFY];
+ ev->data.data32[2] = output->bar;
+ ev->data.data32[3] = xe_version;
+ xcb_send_event(xcb_connection,
+ 0,
+ client,
+ XCB_EVENT_MASK_NO_EVENT,
+ (char*)ev);
+ free(event);
+
+ if (map_it) {
+ DLOG("Mapping dock client\n");
+ xcb_map_window(xcb_connection, client);
+ } else {
+ DLOG("Not mapping dock client yet\n");
+ }
+ trayclient *tc = malloc(sizeof(trayclient));
+ tc->win = client;
+ tc->mapped = map_it;
+ tc->xe_version = xe_version;
+ TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
+
+ /* Trigger an update to copy the statusline text to the appropriate
+ * position */
+ configure_trayclients();
+ draw_bars();
+ }
+ }
+}
+
+/*
+ * Handles UnmapNotify events. These events happen when a tray window unmaps
+ * itself. We then update our data structure
+ *
+ */
+static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
+ DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event);
+
+ i3_output *walk;
+ SLIST_FOREACH(walk, outputs, slist) {
+ if (!walk->active)
+ continue;
+ DLOG("checking output %s\n", walk->name);
+ trayclient *trayclient;
+ TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
+ if (trayclient->win != event->window)
+ continue;
+
+ DLOG("Removing tray client with window ID %08x\n", event->window);
+ TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
+
+ /* Trigger an update, we now have more space for the statusline */
+ configure_trayclients();
+ draw_bars();
+ return;
+ }
+ }
+}
+
+/*
+ * Handle PropertyNotify messages. Currently only the _XEMBED_INFO property is
+ * handled, which tells us whether a dock client should be mapped or unmapped.
+ *
+ */
+static void handle_property_notify(xcb_property_notify_event_t *event) {
+ DLOG("PropertyNotify\n");
+ if (event->atom == atoms[_XEMBED_INFO] &&
+ event->state == XCB_PROPERTY_NEW_VALUE) {
+ DLOG("xembed_info updated\n");
+ trayclient *trayclient = NULL, *walk;
+ i3_output *o_walk;
+ SLIST_FOREACH(o_walk, outputs, slist) {
+ if (!o_walk->active)
+ continue;
+
+ TAILQ_FOREACH(walk, o_walk->trayclients, tailq) {
+ if (walk->win != event->window)
+ continue;
+ trayclient = walk;
+ break;
+ }
+
+ if (trayclient)
+ break;
+ }
+ if (!trayclient) {
+ ELOG("PropertyNotify received for unknown window %08x\n",
+ event->window);
+ return;
+ }
+ xcb_get_property_cookie_t xembedc;
+ xembedc = xcb_get_property_unchecked(xcb_connection,
+ 0,
+ trayclient->win,
+ atoms[_XEMBED_INFO],
+ XCB_GET_PROPERTY_TYPE_ANY,
+ 0,
+ 2 * 32);
+
+ xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection,
+ xembedc,
+ NULL);
+ if (xembedr == NULL || xembedr->length == 0)
+ return;
+
+ DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length);
+ uint32_t *xembed = xcb_get_property_value(xembedr);
+ DLOG("xembed version = %d\n", xembed[0]);
+ DLOG("xembed flags = %d\n", xembed[1]);
+ bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
+ DLOG("map-state now %d\n", map_it);
+ if (trayclient->mapped && !map_it) {
+ /* need to unmap the window */
+ xcb_unmap_window(xcb_connection, trayclient->win);
+ trayclient->mapped = map_it;
+ draw_bars();
+ } else if (!trayclient->mapped && map_it) {
+ /* need to map the window */
+ xcb_map_window(xcb_connection, trayclient->win);
+ trayclient->mapped = map_it;
+ draw_bars();
+ }
+ free(xembedr);
+ }
+}
+
/*
* This function is called immediately before the main loop locks. We flush xcb
* then (and only then)
/* Button-press-events are mouse-buttons clicked on one of our bars */
handle_button((xcb_button_press_event_t*) event);
break;
+ case XCB_CLIENT_MESSAGE:
+ /* Client messages are used for client-to-client communication, for
+ * example system tray widgets talk to us directly via client messages. */
+ handle_client_message((xcb_client_message_event_t*) event);
+ break;
+ case XCB_UNMAP_NOTIFY:
+ /* UnmapNotifies are received when a tray window unmaps itself */
+ handle_unmap_notify((xcb_unmap_notify_event_t*) event);
+ break;
+ case XCB_PROPERTY_NOTIFY:
+ /* PropertyNotify */
+ handle_property_notify((xcb_property_notify_event_t*) event);
+ break;
}
FREE(event);
}
*/
char *init_xcb(char *fontname) {
/* FIXME: xcb_connect leaks Memory */
- xcb_connection = xcb_connect(NULL, NULL);
+ xcb_connection = xcb_connect(NULL, &screen);
if (xcb_connection_has_error(xcb_connection)) {
ELOG("Cannot open display\n");
exit(EXIT_FAILURE);
return path;
}
+/*
+ * Initializes tray support by requesting the appropriate _NET_SYSTEM_TRAY atom
+ * for the X11 display we are running on, then acquiring the selection for this
+ * atom. Afterwards, tray clients will send ClientMessages to our window.
+ *
+ */
+void init_tray() {
+ /* request the tray manager atom for the X11 display we are running on */
+ char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
+ snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen);
+ xcb_intern_atom_cookie_t tray_cookie;
+ xcb_intern_atom_reply_t *tray_reply;
+ tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname);
+
+ /* tray support: we need a window to own the selection */
+ xcb_window_t selwin = xcb_generate_id(xcb_connection);
+ uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT;
+ uint32_t selval[] = { 1 };
+ xcb_create_window(xcb_connection,
+ xcb_screen->root_depth,
+ selwin,
+ xcb_root,
+ -1, -1,
+ 1, 1,
+ 1,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ xcb_screen->root_visual,
+ selmask,
+ selval);
+
+ uint32_t orientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
+ /* set the atoms */
+ xcb_change_property(xcb_connection,
+ XCB_PROP_MODE_REPLACE,
+ selwin,
+ atoms[_NET_SYSTEM_TRAY_ORIENTATION],
+ XCB_ATOM_CARDINAL,
+ 32,
+ 1,
+ &orientation);
+
+ if (!(tray_reply = xcb_intern_atom_reply(xcb_connection, tray_cookie, NULL))) {
+ ELOG("Could not get atom %s\n", atomname);
+ exit(EXIT_FAILURE);
+ }
+
+ xcb_set_selection_owner(xcb_connection,
+ selwin,
+ tray_reply->atom,
+ XCB_CURRENT_TIME);
+
+ /* Verify that we have the selection */
+ xcb_get_selection_owner_cookie_t selcookie;
+ xcb_get_selection_owner_reply_t *selreply;
+
+ selcookie = xcb_get_selection_owner(xcb_connection, tray_reply->atom);
+ if (!(selreply = xcb_get_selection_owner_reply(xcb_connection, selcookie, NULL))) {
+ ELOG("Could not get selection owner for %s\n", atomname);
+ exit(EXIT_FAILURE);
+ }
+
+ if (selreply->owner != selwin) {
+ ELOG("Could not set the %s selection. " \
+ "Maybe another tray is already running?\n", atomname);
+ /* NOTE that this error is not fatal. We just can’t provide tray
+ * functionality */
+ free(selreply);
+ return;
+ }
+
+ /* Inform clients waiting for a new _NET_SYSTEM_TRAY that we are here */
+ void *event = calloc(32, 1);
+ xcb_client_message_event_t *ev = event;
+ ev->response_type = XCB_CLIENT_MESSAGE;
+ ev->window = xcb_root;
+ ev->type = atoms[MANAGER];
+ ev->format = 32;
+ ev->data.data32[0] = XCB_CURRENT_TIME;
+ ev->data.data32[1] = tray_reply->atom;
+ ev->data.data32[2] = selwin;
+ xcb_send_event(xcb_connection,
+ 0,
+ xcb_root,
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY,
+ (char*)ev);
+ free(event);
+ free(tray_reply);
+}
+
/*
* Cleanup the xcb-stuff.
* Called once, before the program terminates.
*/
void clean_xcb() {
i3_output *o_walk;
+ trayclient *trayclient;
free_workspaces();
SLIST_FOREACH(o_walk, outputs, slist) {
+ TAILQ_FOREACH(trayclient, o_walk->trayclients, tailq) {
+ /* Unmap, then reparent (to root) the tray client windows */
+ xcb_unmap_window(xcb_connection, trayclient->win);
+ xcb_reparent_window(xcb_connection,
+ trayclient->win,
+ xcb_root,
+ 0,
+ 0);
+ }
destroy_window(o_walk);
+ FREE(o_walk->trayclients);
FREE(o_walk->workspaces);
FREE(o_walk->name);
}
FREE_SLIST(outputs, i3_output);
FREE(outputs);
+ xcb_flush(xcb_connection);
xcb_disconnect(xcb_connection);
ev_check_stop(main_loop, xcb_chk);
if (walk->bar == XCB_NONE) {
DLOG("Creating Window for output %s\n", walk->name);
+ /* TODO: only call init_tray() if the tray is configured for this output */
+ init_tray();
+
walk->bar = xcb_generate_id(xcb_connection);
walk->buffer = xcb_generate_id(xcb_connection);
mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
/* Luckily we already prepared a seperate pixmap containing the rendered
* statusline, we just have to copy the relevant parts to the relevant
* position */
+ trayclient *trayclient;
+ int traypx = 0;
+ TAILQ_FOREACH(trayclient, outputs_walk->trayclients, tailq) {
+ if (!trayclient->mapped)
+ continue;
+ /* We assume the tray icons are quadratic (we use the font
+ * *height* as *width* of the icons) because we configured them
+ * like this. */
+ traypx += font_height;
+ }
+ /* Add 2px of padding if there are any tray icons */
+ if (traypx > 0)
+ traypx += 2;
xcb_copy_area(xcb_connection,
statusline_pm,
outputs_walk->buffer,
outputs_walk->bargc,
MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0,
- MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - 4)), 3,
- MIN(outputs_walk->rect.w - 4, statusline_width), font_height);
+ MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3,
+ MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font_height);
}
if (config.disable_ws) {
/** 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; }
toggle { return TOK_TOGGLE; }
mode_toggle { return TOK_MODE_TOGGLE; }
workspace { WS_STRING; return TOK_WORKSPACE; }
+output { WS_STRING; return TOK_OUTPUT; }
focus { return TOK_FOCUS; }
move { return TOK_MOVE; }
open { return TOK_OPEN; }
%token TOK_ENABLE "enable"
%token TOK_DISABLE "disable"
%token TOK_WORKSPACE "workspace"
+%token TOK_OUTPUT "output"
%token TOK_TOGGLE "toggle"
%token TOK_FOCUS "focus"
%token TOK_MOVE "move"
tree_render();
}
+ | TOK_MOVE TOK_OUTPUT STR
+ {
+ owindow *current;
+
+ printf("should move window to output %s", $3);
+
+ HANDLE_EMPTY_MATCH;
+
+ /* get the output */
+ Output *current_output = NULL;
+ Output *output;
+
+ TAILQ_FOREACH(current, &owindows, owindows)
+ current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
+
+ assert(current_output != NULL);
+
+ if (strcasecmp($3, "up") == 0)
+ output = get_output_next(D_UP, current_output);
+ else if (strcasecmp($3, "down") == 0)
+ output = get_output_next(D_DOWN, current_output);
+ else if (strcasecmp($3, "left") == 0)
+ output = get_output_next(D_LEFT, current_output);
+ else if (strcasecmp($3, "right") == 0)
+ output = get_output_next(D_RIGHT, current_output);
+ else
+ output = get_output_by_name($3);
+ free($3);
+
+ if (!output)
+ break;
+
+ /* get visible workspace on output */
+ Con *ws = NULL;
+ GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
+ if (!ws)
+ break;
+
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ printf("matching: %p / %s\n", current->con, current->con->name);
+ con_move_to_workspace(current->con, ws, false);
+ }
+
+ tree_render();
+ }
;
append_layout:
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;
+
TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes);
TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused);
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;