]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'master' into next
authorMichael Stapelberg <michael@stapelberg.de>
Sat, 17 Sep 2011 13:13:05 +0000 (14:13 +0100)
committerMichael Stapelberg <michael@stapelberg.de>
Sat, 17 Sep 2011 13:13:05 +0000 (14:13 +0100)
45 files changed:
DEPENDS
common.mk
debian/changelog
debian/control
docs/ipc
docs/userguide
i3-msg/main.c
i3bar/include/common.h
i3bar/include/outputs.h
i3bar/include/trayclients.h [new file with mode: 0644]
i3bar/include/xcb.h
i3bar/include/xcb_atoms.def
i3bar/src/child.c
i3bar/src/outputs.c
i3bar/src/ucs2_to_utf8.c
i3bar/src/workspaces.c
i3bar/src/xcb.c
include/all.h
include/config.h
include/data.h
include/i3/ipc.h
include/match.h
include/regex.h [new file with mode: 0644]
include/sd-daemon.h [new file with mode: 0644]
src/cfgparse.l
src/cfgparse.y
src/cmdparse.l
src/cmdparse.y
src/config.c
src/floating.c
src/ipc.c
src/load_layout.c
src/main.c
src/manage.c
src/match.c
src/randr.c
src/regex.c [new file with mode: 0644]
src/sd-daemon.c [new file with mode: 0644]
src/workspace.c
testcases/complete-run.pl
testcases/t/17-workspace.t
testcases/t/19-match.t
testcases/t/66-assign.t
testcases/t/73-get-marks.t [new file with mode: 0644]
testcases/t/74-border-config.t [new file with mode: 0644]

diff --git a/DEPENDS b/DEPENDS
index ea7133a5552f7f8ae263538be754677fc685856a..a6c0986d68671bab1d7c8bdfbbd9fb123384d25c 100644 (file)
--- a/DEPENDS
+++ b/DEPENDS
@@ -20,6 +20,7 @@
 │ docbook-xml │ 4.5    │ 4.5    │ http://www.methods.co.nz/asciidoc/     │
 │ libxcursor  │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/  │
 │ Xlib        │ 1.3.3  │ 1.4.3  │ http://ftp.x.org/pub/current/src/lib/  │
+│ PCRE        │ 8.12   │ 8.12   │ http://www.pcre.org/                   │
 └─────────────┴────────┴────────┴────────────────────────────────────────┘
 
  i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new
index ce41f287394af52d3400517ab19875dcda845f7f..62eb99586e4ddbb9d799b3009319fac5f7b711ec 100644 (file)
--- a/common.mk
+++ b/common.mk
@@ -49,6 +49,7 @@ CFLAGS += $(call cflags_for_lib, xcursor)
 CFLAGS += $(call cflags_for_lib, x11)
 CFLAGS += $(call cflags_for_lib, yajl)
 CFLAGS += $(call cflags_for_lib, libev)
+CFLAGS += $(call cflags_for_lib, libpcre)
 CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
 CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
 CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\"
@@ -70,6 +71,7 @@ LIBS += $(call ldflags_for_lib, xcursor, Xcursor)
 LIBS += $(call ldflags_for_lib, x11, X11)
 LIBS += $(call ldflags_for_lib, yajl, yajl)
 LIBS += $(call ldflags_for_lib, libev, ev)
+LIBS += $(call ldflags_for_lib, libpcre, pcre)
 
 # Please test if -Wl,--as-needed works on your platform and send me a patch.
 # it is known not to work on Darwin (Mac OS X)
index 990badb2dd93e221e0e782d2ab1a5d1bd76560b6..e670cd8fdb41f381265ebb0d6b6b135ce345894a 100644 (file)
@@ -1,6 +1,18 @@
-i3-wm (4.0.3-0) unstable; urgency=low
+i3-wm (4.1-0) unstable; urgency=low
 
   * NOT YET RELEASED!
+  * Implement system tray support in i3bar (for NetworkManager, Skype, …)
+  * Implement support for PCRE regular expressions in criteria
+  * Implement a new assign syntax which uses criteria
+  * Sort named workspaces whose name starts with a number accordingly
+  * Warn on duplicate bindings for the same key
+  * Restrict 'resize' command to left/right for horizontal containers, up/down
+    for vertical containers
+  * Implement the GET_MARKS IPC request to get all marks
+  * Implement the new_float config option (border style for floating windows)
+  * Implement passing IPC sockets to i3 (systemd-style socket activation)
+  * Implement the 'move output' command to move containers to a specific output
+  * Bugfix: Preserve marks when restarting
 
  -- Michael Stapelberg <michael@stapelberg.de>  Sun, 28 Aug 2011 20:17:31 +0200
 
index b546e650d5555687fdea71f6bcbd83f65275a8c2..da13231f385564bdc4921157ddcd17ca59eed68a 100644 (file)
@@ -3,7 +3,7 @@ Section: utils
 Priority: extra
 Maintainer: Michael Stapelberg <michael@stapelberg.de>
 DM-Upload-Allowed: yes
-Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra
+Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra, libpcre3-dev
 Standards-Version: 3.9.2
 Homepage: http://i3wm.org/
 
index 7e71326022606aa2b4d1abf93d4d77d3c448fe1a..4093ffce26e2233b7258c62bedc4e180b3c70fad 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -59,6 +59,10 @@ GET_TREE (4)::
        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:
 --------------------------------------------------
@@ -110,6 +114,8 @@ GET_OUTPUTS (3)::
        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
 
@@ -416,6 +422,16 @@ JSON dump:
    }
  ]
 }
+
+
+=== 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 [].
 ------------------------
 
 
index 27598bc1926f7d2ad0e8593b771279df800bfe55..184848aa2526111f891f6a7a960b9724d9e09bb8 100644 (file)
@@ -431,7 +431,7 @@ change their border style, for example.
 
 *Syntax*:
 -----------------------------
-for_window [criteria] command
+for_window <criteria> command
 -----------------------------
 
 *Examples*:
@@ -478,37 +478,59 @@ configuration file and run it before starting i3 (for example in your
 
 [[assign_workspace]]
 
-Specific windows can be matched by window class and/or window title. It is
-recommended that you match on window classes instead of window titles whenever
-possible because some applications first create their window, and then worry
-about setting the correct title. Firefox with Vimperator comes to mind. The
-window starts up being named Firefox, and only when Vimperator is loaded does
-the title change. As i3 will get the title as soon as the application maps the
+To automatically make a specific window show up on a specific workspace, you
+can use an *assignment*. You can match windows by using any criteria,
+see <<command_criteria>>. It is recommended that you match on window classes
+(and instances, when appropriate) instead of window titles whenever possible
+because some applications first create their window, and then worry about
+setting the correct title. Firefox with Vimperator comes to mind. The window
+starts up being named Firefox, and only when Vimperator is loaded does the
+title change. As i3 will get the title as soon as the application maps the
 window (mapping means actually displaying it on the screen), you’d need to have
 to match on 'Firefox' in this case.
 
-You can prefix or suffix workspaces with a `~` to specify that matching clients
-should be put into floating mode. If you specify only a `~`, the client will
-not be put onto any workspace, but will be set floating on the current one.
-
 *Syntax*:
 ------------------------------------------------------------
-assign ["]window class[/window title]["] [→] [workspace]
+assign <criteria> [→] workspace
 ------------------------------------------------------------
 
 *Examples*:
 ----------------------
-assign urxvt 2
-assign urxvt → 2
-assign urxvt → work
-assign "urxvt" → 2
-assign "urxvt/VIM" → 3
-assign "gecko" → 4
+# Assign URxvt terminals to workspace 2
+assign [class="URxvt"] 2
+
+# Same thing, but more precise (exact match instead of substring)
+assign [class="^URxvt$"] 2
+
+# Same thing, but with a beautiful arrow :)
+assign [class="^URxvt$"] → 2
+
+# Assignment to a named workspace
+assign [class="^URxvt$"] → work
+
+# Start urxvt -name irssi
+assign [class="^URxvt$" instance="^irssi$"] → 3
 ----------------------
 
 Note that the arrow is not required, it just looks good :-). If you decide to
 use it, it has to be a UTF-8 encoded arrow, not `->` or something like that.
 
+To get the class and instance, you can use +xprop+. After clicking on the
+window, you will see the following output:
+
+*xwininfo*:
+-----------------------------------
+WM_CLASS(STRING) = "irssi", "URxvt"
+-----------------------------------
+
+The first part of the WM_CLASS is the instance ("irssi" in this example), the
+second part is the class ("URxvt" in this example).
+
+Should you have any problems with assignments, make sure to check the i3
+logfile first (see http://i3wm.org/docs/debugging.html). It includes more
+details about the matching process and the window’s actual class, instance and
+title when starting up.
+
 === Automatically starting applications on i3 startup
 
 By using the +exec+ keyword outside a keybinding, you can configure
@@ -721,6 +743,9 @@ which have the class Firefox, use:
 *Example*:
 ------------------------------------
 bindsym mod+x [class="Firefox"] kill
+
+# same thing, but case-insensitive
+bindsym mod+x [class="(?i)firefox"] kill
 ------------------------------------
 
 The criteria which are currently implemented are:
@@ -739,8 +764,9 @@ con_id::
        Compares the i3-internal container ID, which you can get via the IPC
        interface. Handy for scripting.
 
-Note that currently all criteria are compared case-insensitive and do not
-support regular expressions. This is planned to change in the future.
+The criteria +class+, +instance+, +title+ and +mark+ are actually regular
+expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for information on
+how to use them.
 
 === Splitting containers
 
@@ -838,6 +864,11 @@ You can also switch to the next and previous workspace with the commands
 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
index 630a345d63c6ce57dc611b19464d8a5b6da0f8cf..2d7cef0e907eb3005b52798f46f4ca39e3d88af8 100644 (file)
@@ -180,9 +180,11 @@ int main(int argc, char *argv[]) {
                 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') {
@@ -243,7 +245,7 @@ int main(int argc, char *argv[]) {
     uint32_t reply_length;
     uint8_t *reply;
     ipc_recv_message(sockfd, message_type, &reply_length, &reply);
-    printf("%.*s", reply_length, reply);
+    printf("%.*s\n", reply_length, reply);
     free(reply);
 
     close(sockfd);
index 22e3ca4337d292aaa120650101e6becaa10f11b4..74bd21528501d3b4701043ef51691c30e2891a61 100644 (file)
@@ -9,18 +9,19 @@
 #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"
@@ -29,6 +30,7 @@ struct rect_t {
 #include "outputs.h"
 #include "util.h"
 #include "workspaces.h"
+#include "trayclients.h"
 #include "xcb.h"
 #include "ucs2_to_utf8.h"
 #include "config.h"
index f74048da5d5f2604d09419e1ea75d7b4cfeb63a9..c6402a5b620b987569769862e2d5b02890bb9cba 100644 (file)
@@ -22,33 +22,34 @@ struct outputs_head *outputs;
  * 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
diff --git a/i3bar/include/trayclients.h b/i3bar/include/trayclients.h
new file mode 100644 (file)
index 0000000..1113dae
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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
index 531fdfe942c76265e0e5b559528b5ccb5e733604..c1b7cc14e3575bd98051ddd3040b89af16fd90aa 100644 (file)
 #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;
index 5d1688731659a38240ed84b40075e0dd10197e25..b75ceabd538afa3a96aba1fb87fd1a23f06a6403 100644 (file)
@@ -2,4 +2,10 @@ ATOM_DO(_NET_WM_WINDOW_TYPE)
 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
index faab9142ee04d6985f120dc2bd469b8c6f9a2a9d..aa9d65549ca7a5abd259ffbcb6e318b7406111c6 100644 (file)
@@ -90,7 +90,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
         if (rec == buffer_len) {
             buffer_len += STDIN_CHUNK_SIZE;
             buffer = realloc(buffer, buffer_len);
-           }
+        }
     }
     if (*buffer == '\0') {
         FREE(buffer);
index 9daf328d0454463ff482e7bb94eccfdad08de694..464f24a0ea52fa171b5caa1904d6d4708672cd89 100644 (file)
@@ -43,7 +43,7 @@ static int outputs_null_cb(void *params_) {
  * 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")) {
@@ -161,6 +161,9 @@ static int outputs_start_map_cb(void *params_) {
         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;
index 8c79c3f9659b5e23be249b0fd5e3c184e1a139e7..689842276d81eee201d5eb50e92ed9d552141305 100644 (file)
@@ -23,18 +23,18 @@ static iconv_t conversion_descriptor2 = 0;
  *
  */
 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) {
@@ -43,17 +43,17 @@ char *convert_ucs_to_utf8(char *input) {
                 }
         }
 
-       /* 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;
 }
 
 /*
@@ -64,18 +64,18 @@ char *convert_ucs_to_utf8(char *input) {
  *
  */
 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) {
@@ -84,20 +84,20 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
                 }
         }
 
-       /* 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;
 }
index eeb9ca349f459d47a7b0ad9c5eacfa39a5b4c831..a84e152b79e981f2bd71099a27a1a146472716cf 100644 (file)
@@ -29,7 +29,7 @@ struct workspaces_json_params {
  * 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")) {
index 51a2781ba65562dd1d3f2309c612d35aff2d402b..dd6e39dccc00577f661f7d293f79a2020ce425aa 100644 (file)
@@ -63,6 +63,7 @@ xcb_atom_t               atoms[NUM_ATOMS];
 
 /* 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;
@@ -389,6 +390,256 @@ void handle_button(xcb_button_press_event_t *event) {
     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)
@@ -419,6 +670,19 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
             /* 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);
 }
@@ -476,7 +740,7 @@ void xkb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
  */
 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);
@@ -640,6 +904,95 @@ char *init_xcb(char *fontname) {
     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.
@@ -647,15 +1000,27 @@ char *init_xcb(char *fontname) {
  */
 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);
@@ -765,6 +1130,9 @@ void reconfig_windows() {
         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;
@@ -954,13 +1322,26 @@ void draw_bars() {
             /* 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) {
index b87be518c1b18193a89a97d11d7bc4b35faa59c9..9c08ebef8e8067c241dfb5f087b45374a5c64cbf 100644 (file)
@@ -64,5 +64,6 @@
 #include "output.h"
 #include "ewmh.h"
 #include "assignments.h"
+#include "regex.h"
 
 #endif
index 1021a612e7a98917a211a61d037b688e255c6861..3234b91e5d5f658cae51ed548d047b5df05116e4 100644 (file)
@@ -126,6 +126,9 @@ struct Config {
         /** 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;
index 5797b7d80d1db633dcb52eb94c4272c9ea55a521..1db7c4428a16bd22da48267bf72062ca6bab496b 100644 (file)
@@ -10,6 +10,7 @@
 #include <xcb/randr.h>
 #include <xcb/xcb_atom.h>
 #include <stdbool.h>
+#include <pcre.h>
 
 #ifndef _DATA_H
 #define _DATA_H
@@ -137,6 +138,21 @@ struct Ignore_Event {
     SLIST_ENTRY(Ignore_Event) ignore_events;
 };
 
+/**
+ * Regular expression wrapper. It contains the pattern itself as a string (like
+ * ^foo[0-9]$) as well as a pointer to the compiled PCRE expression and the
+ * pcre_extra data returned by pcre_study().
+ *
+ * This makes it easier to have a useful logfile, including the matching or
+ * non-matching pattern.
+ *
+ */
+struct regex {
+    char *pattern;
+    pcre *regex;
+    pcre_extra *extra;
+};
+
 /******************************************************************************
  * Major types
  *****************************************************************************/
@@ -277,12 +293,11 @@ struct Window {
 };
 
 struct Match {
-    char *title;
-    int title_len;
-    char *application;
-    char *class;
-    char *instance;
-    char *mark;
+    struct regex *title;
+    struct regex *application;
+    struct regex *class;
+    struct regex *instance;
+    struct regex *mark;
     enum {
         M_DONTCHECK = -1,
         M_NODOCK = 0,
index e81f9a155ad5e7254a631c1fb04d7a6a172e6a48..30b2d3044e353cdb4c202a571e6acfde6138326d 100644 (file)
@@ -38,6 +38,8 @@
 /** 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
@@ -59,6 +61,8 @@
 /** 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.
index 2786c66a8be7792cd25485f48ca7ea971b8d0667..6c0694efeace588462c69fec845d00ba69188261 100644 (file)
@@ -28,4 +28,10 @@ void match_copy(Match *dest, Match *src);
  */
 bool match_matches_window(Match *match, i3Window *window);
 
+/**
+ * Frees the given match. It must not be used afterwards!
+ *
+ */
+void match_free(Match *match);
+
 #endif
diff --git a/include/regex.h b/include/regex.h
new file mode 100644 (file)
index 0000000..adfa665
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ */
+#ifndef _REGEX_H
+#define _REGEX_H
+
+/**
+ * Creates a new 'regex' struct containing the given pattern and a PCRE
+ * compiled regular expression. Also, calls pcre_study because this regex will
+ * most likely be used often (like for every new window and on every relevant
+ * property change of existing windows).
+ *
+ * Returns NULL if the pattern could not be compiled into a regular expression
+ * (and ELOGs an appropriate error message).
+ *
+ */
+struct regex *regex_new(const char *pattern);
+
+/**
+ * Frees the given regular expression. It must not be used afterwards!
+ *
+ */
+void regex_free(struct regex *regex);
+
+/**
+ * Checks if the given regular expression matches the given input and returns
+ * true if it does. In either case, it logs the outcome using LOG(), so it will
+ * be visible without any debug loglevel.
+ *
+ */
+bool regex_matches(struct regex *regex, const char *input);
+
+#endif
diff --git a/include/sd-daemon.h b/include/sd-daemon.h
new file mode 100644 (file)
index 0000000..4b853a1
--- /dev/null
@@ -0,0 +1,265 @@
+/*-*- 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
index 4cf1a1c3777a5fd1f7a115c7cf85b3123764e055..e5dd29c0c972f5d946cacae28019b059aa4a349f 100644 (file)
@@ -75,6 +75,14 @@ EOL     (\r?\n)
 
 
 <FOR_WINDOW_COND>"]"            { yy_pop_state(); return ']'; }
+<ASSIGN_COND>"["                {
+                                  /* this is the case for the new assign syntax
+                                   * that uses criteria */
+                                  yy_pop_state();
+                                  yy_push_state(FOR_WINDOW_COND);
+                                  /* afterwards we will be in ASSIGN_TARGET_COND */
+                                  return '[';
+                                }
 <EAT_WHITESPACE>[ \t]*          { yy_pop_state(); }
 <WANT_QSTRING>\"[^\"]+\"        {
                                   yy_pop_state();
@@ -98,13 +106,6 @@ bindsym                         { yy_push_state(BINDSYM_COND); yy_push_state(EAT
 floating_modifier               { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; }
 workspace                       { BEGIN(INITIAL); return TOKWORKSPACE; }
 output                          { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; }
-screen                          {
-                                  /* for compatibility until v3.φ */
-                                  ELOG("Assignments to screens are DEPRECATED and will not work. " \
-                                       "Please replace them with assignments to outputs.\n");
-                                  yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE);
-                                  return TOKOUTPUT;
-                                }
 terminal                        { WS_STRING; return TOKTERMINAL; }
 font                            { WS_STRING; return TOKFONT; }
 assign                          { yy_push_state(ASSIGN_TARGET_COND); yy_push_state(ASSIGN_COND); return TOKASSIGN; }
@@ -118,6 +119,7 @@ vertical                        { return TOK_VERT; }
 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; }
@@ -193,7 +195,7 @@ title                           { yy_push_state(WANT_QSTRING); return TOK_TITLE;
                                   yylval.string = copy;
                                   return QUOTEDSTRING;
                                 }
-<ASSIGN_COND>[^ \t\"]+          { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; }
+<ASSIGN_COND>[^ \t\"\[]+        { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; }
 <BINDSYM_COND>[a-zA-Z0-9_]+     { yylval.string = sstrdup(yytext); return WORD; }
 [a-zA-Z]+                       { yylval.string = sstrdup(yytext); return WORD; }
 .                               { return (int)yytext[0]; }
index 10ca48ccf609a4d9efff608e21b6843860ada305..8faeff8503b8b587ba74524115d0bea6b2b811bc 100644 (file)
@@ -240,6 +240,18 @@ static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
     configerror_pid = -1;
 }
 
+/*
+ * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
+ * SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
+ *
+ */
+static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
+    if (configerror_pid != -1) {
+        LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", configerror_pid);
+        kill(configerror_pid, SIGKILL);
+    }
+}
+
 /*
  * Starts an i3-nagbar process which alerts the user that his configuration
  * file contains one or more errors. Also offers two buttons: One to launch an
@@ -283,6 +295,12 @@ static void start_configerror_nagbar(const char *config_path) {
     ev_child *child = smalloc(sizeof(ev_child));
     ev_child_init(child, &nagbar_exited, configerror_pid, 0);
     ev_child_start(main_loop, child);
+
+    /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
+     * still running) */
+    ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
+    ev_cleanup_init(cleanup, nagbar_cleanup);
+    ev_cleanup_start(main_loop, cleanup);
 }
 
 /*
@@ -311,6 +329,53 @@ void kill_configerror_nagbar(bool wait_for_it) {
     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;
@@ -469,6 +534,8 @@ void parse_file(const char *f) {
         exit(1);
     }
 
+    check_for_duplicate_bindings(context);
+
     if (context->has_errors) {
         start_configerror_nagbar(f);
     }
@@ -536,6 +603,7 @@ void parse_file(const char *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"
@@ -567,6 +635,7 @@ void parse_file(const char *f) {
 %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
@@ -591,6 +660,7 @@ line:
     | orientation
     | workspace_layout
     | new_window
+    | new_float
     | focus_follows_mouse
     | force_focus_wrapping
     | workspace_bar
@@ -703,12 +773,14 @@ criterion:
     TOK_CLASS '=' STR
     {
         printf("criteria: class = %s\n", $3);
-        current_match.class = $3;
+        current_match.class = regex_new($3);
+        free($3);
     }
     | TOK_INSTANCE '=' STR
     {
         printf("criteria: instance = %s\n", $3);
-        current_match.instance = $3;
+        current_match.instance = regex_new($3);
+        free($3);
     }
     | TOK_CON_ID '=' STR
     {
@@ -743,12 +815,14 @@ criterion:
     | TOK_MARK '=' STR
     {
         printf("criteria: mark = %s\n", $3);
-        current_match.mark = $3;
+        current_match.mark = regex_new($3);
+        free($3);
     }
     | TOK_TITLE '=' STR
     {
         printf("criteria: title = %s\n", $3);
-        current_match.title = $3;
+        current_match.title = regex_new($3);
+        free($3);
     }
     ;
 
@@ -885,6 +959,14 @@ new_window:
     }
     ;
 
+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; }
@@ -998,6 +1080,13 @@ workspace_name:
 assign:
     TOKASSIGN window_class STR
     {
+        /* This is the old, deprecated form of assignments. It’s provided for
+         * compatibility in version (4.1, 4.2, 4.3) and will be removed
+         * afterwards. It triggers an i3-nagbar warning starting from 4.1. */
+        ELOG("You are using the old assign syntax (without criteria). "
+             "Please see the User's Guide for the new syntax and fix "
+             "your config file.\n");
+        context->has_errors = true;
         printf("assignment of %s to *%s*\n", $2, $3);
         char *workspace = $3;
         char *criteria = $2;
@@ -1009,15 +1098,27 @@ assign:
         char *separator = NULL;
         if ((separator = strchr(criteria, '/')) != NULL) {
             *(separator++) = '\0';
-            match->title = sstrdup(separator);
+            char *pattern;
+            if (asprintf(&pattern, "(?i)%s", separator) == -1) {
+                ELOG("asprintf failed\n");
+                break;
+            }
+            match->title = regex_new(pattern);
+            free(pattern);
+            printf("  title = %s\n", separator);
+        }
+        if (*criteria != '\0') {
+            char *pattern;
+            if (asprintf(&pattern, "(?i)%s", criteria) == -1) {
+                ELOG("asprintf failed\n");
+                break;
+            }
+            match->class = regex_new(pattern);
+            free(pattern);
+            printf("  class = %s\n", criteria);
         }
-        if (*criteria != '\0')
-            match->class = sstrdup(criteria);
         free(criteria);
 
-        printf("  class = %s\n", match->class);
-        printf("  title = %s\n", match->title);
-
         /* Compatibility with older versions: If the assignment target starts
          * with ~, we create the equivalent of:
          *
@@ -1045,6 +1146,19 @@ assign:
         assignment->dest.workspace = workspace;
         TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
     }
+    | TOKASSIGN match STR
+    {
+        if (match_is_empty(&current_match)) {
+            ELOG("Match is empty, ignoring this assignment\n");
+            break;
+        }
+        printf("new assignment, using above criteria, to workspace %s\n", $3);
+        Assignment *assignment = scalloc(sizeof(Assignment));
+        assignment->match = current_match;
+        assignment->type = A_TO_WORKSPACE;
+        assignment->dest.workspace = $3;
+        TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+    }
     ;
 
 window_class:
index 6c756b0d42850db11a014c5b1838ec3e21704ec4..c7c64e356f2b74978520a003b16c5faf5f39d823 100644 (file)
@@ -123,6 +123,7 @@ floating                        { return TOK_FLOATING; }
 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; }
index 9ea82efd68c5f8e6a77a4998831b8a8b1a116213..650a2eb883caf928b9cbf5694bdb0c82eb7a040f 100644 (file)
@@ -149,6 +149,7 @@ bool definitelyGreaterThan(float a, float b, float epsilon) {
 %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"
@@ -266,10 +267,9 @@ matchend:
 
                 }
             } else if (current_match.mark != NULL && current->con->mark != NULL &&
-                    strcasecmp(current_match.mark, current->con->mark) == 0) {
+                       regex_matches(current_match.mark, current->con->mark)) {
                 printf("match by mark\n");
-                    TAILQ_INSERT_TAIL(&owindows, current, owindows);
-
+                TAILQ_INSERT_TAIL(&owindows, current, owindows);
             } else {
                 if (current->con->window == NULL)
                     continue;
@@ -299,12 +299,14 @@ criterion:
     TOK_CLASS '=' STR
     {
         printf("criteria: class = %s\n", $3);
-        current_match.class = $3;
+        current_match.class = regex_new($3);
+        free($3);
     }
     | TOK_INSTANCE '=' STR
     {
         printf("criteria: instance = %s\n", $3);
-        current_match.instance = $3;
+        current_match.instance = regex_new($3);
+        free($3);
     }
     | TOK_CON_ID '=' STR
     {
@@ -339,12 +341,14 @@ criterion:
     | TOK_MARK '=' STR
     {
         printf("criteria: mark = %s\n", $3);
-        current_match.mark = $3;
+        current_match.mark = regex_new($3);
+        free($3);
     }
     | TOK_TITLE '=' STR
     {
         printf("criteria: title = %s\n", $3);
-        current_match.title = $3;
+        current_match.title = regex_new($3);
+        free($3);
     }
     ;
 
@@ -704,6 +708,51 @@ 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, true, false);
+        }
+
+        tree_render();
+    }
     ;
 
 append_layout:
@@ -811,6 +860,17 @@ resize:
             double percentage = 1.0 / children;
             LOG("default percentage = %f\n", percentage);
 
+            orientation_t orientation = current->parent->orientation;
+
+            if ((orientation == HORIZ &&
+                 (direction == TOK_UP || direction == TOK_DOWN)) ||
+                (orientation == VERT &&
+                 (direction == TOK_LEFT || direction == TOK_RIGHT))) {
+                LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
+                    (orientation == HORIZ ? "horizontal" : "vertical"));
+                break;
+            }
+
             if (direction == TOK_UP || direction == TOK_LEFT) {
                 other = TAILQ_PREV(current, nodes_head, nodes);
             } else {
@@ -818,7 +878,7 @@ resize:
             }
             if (other == TAILQ_END(workspaces)) {
                 LOG("No other container in this direction found, cannot resize.\n");
-                return 0;
+                break;
             }
             LOG("other->percent = %f\n", other->percent);
             LOG("current->percent before = %f\n", current->percent);
index 14fc6e025922c924f9c11f8f0f80493e44b1ad37..c979d8cdbfcc83ec5bda074a375611688aec4beb 100644 (file)
@@ -257,111 +257,116 @@ static void parse_configuration(const char *override_configpath) {
  *
  */
 void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) {
-        if (reload) {
-                /* First ungrab the keys */
-                ungrab_all_keys(conn);
+    if (reload) {
+        /* First ungrab the keys */
+        ungrab_all_keys(conn);
 
-                struct Mode *mode;
-                Binding *bind;
-                while (!SLIST_EMPTY(&modes)) {
-                        mode = SLIST_FIRST(&modes);
-                        FREE(mode->name);
-
-                        /* Clear the old binding list */
-                        bindings = mode->bindings;
-                        while (!TAILQ_EMPTY(bindings)) {
-                                bind = TAILQ_FIRST(bindings);
-                                TAILQ_REMOVE(bindings, bind, bindings);
-                                FREE(bind->translated_to);
-                                FREE(bind->command);
-                                FREE(bind);
-                        }
-                        FREE(bindings);
-                        SLIST_REMOVE(&modes, mode, Mode, modes);
-                }
+        struct Mode *mode;
+        Binding *bind;
+        while (!SLIST_EMPTY(&modes)) {
+            mode = SLIST_FIRST(&modes);
+            FREE(mode->name);
+
+            /* Clear the old binding list */
+            bindings = mode->bindings;
+            while (!TAILQ_EMPTY(bindings)) {
+                bind = TAILQ_FIRST(bindings);
+                TAILQ_REMOVE(bindings, bind, bindings);
+                FREE(bind->translated_to);
+                FREE(bind->command);
+                FREE(bind);
+            }
+            FREE(bindings);
+            SLIST_REMOVE(&modes, mode, Mode, modes);
+        }
 
-#if 0
-                struct Assignment *assign;
-                while (!TAILQ_EMPTY(&assignments)) {
-                        assign = TAILQ_FIRST(&assignments);
-                        FREE(assign->windowclass_title);
-                        TAILQ_REMOVE(&assignments, assign, assignments);
-                        FREE(assign);
-                }
-#endif
+        struct Assignment *assign;
+        while (!TAILQ_EMPTY(&assignments)) {
+            assign = TAILQ_FIRST(&assignments);
+            if (assign->type == A_TO_WORKSPACE)
+                FREE(assign->dest.workspace);
+            else if (assign->type == A_TO_OUTPUT)
+                FREE(assign->dest.output);
+            else if (assign->type == A_COMMAND)
+                FREE(assign->dest.command);
+            match_free(&(assign->match));
+            TAILQ_REMOVE(&assignments, assign, assignments);
+            FREE(assign);
+        }
 
-                /* Clear workspace names */
+        /* Clear workspace names */
 #if 0
-                Workspace *ws;
-                TAILQ_FOREACH(ws, workspaces, workspaces)
-                        workspace_set_name(ws, NULL);
+        Workspace *ws;
+        TAILQ_FOREACH(ws, workspaces, workspaces)
+            workspace_set_name(ws, NULL);
 #endif
-        }
+    }
 
-        SLIST_INIT(&modes);
+    SLIST_INIT(&modes);
 
-        struct Mode *default_mode = scalloc(sizeof(struct Mode));
-        default_mode->name = sstrdup("default");
-        default_mode->bindings = scalloc(sizeof(struct bindings_head));
-        TAILQ_INIT(default_mode->bindings);
-        SLIST_INSERT_HEAD(&modes, default_mode, modes);
+    struct Mode *default_mode = scalloc(sizeof(struct Mode));
+    default_mode->name = sstrdup("default");
+    default_mode->bindings = scalloc(sizeof(struct bindings_head));
+    TAILQ_INIT(default_mode->bindings);
+    SLIST_INSERT_HEAD(&modes, default_mode, modes);
 
-        bindings = default_mode->bindings;
+    bindings = default_mode->bindings;
 
 #define REQUIRED_OPTION(name) \
-        if (config.name == NULL) \
-                die("You did not specify required configuration option " #name "\n");
+    if (config.name == NULL) \
+        die("You did not specify required configuration option " #name "\n");
 
-        /* Clear the old config or initialize the data structure */
-        memset(&config, 0, sizeof(config));
+    /* Clear the old config or initialize the data structure */
+    memset(&config, 0, sizeof(config));
 
-        /* Initialize default colors */
+    /* Initialize default colors */
 #define INIT_COLOR(x, cborder, cbackground, ctext) \
-        do { \
-                x.border = get_colorpixel(cborder); \
-                x.background = get_colorpixel(cbackground); \
-                x.text = get_colorpixel(ctext); \
-        } while (0)
-
-        config.client.background = get_colorpixel("#000000");
-        INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff");
-        INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff");
-        INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888");
-        INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff");
-        INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff");
-        INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888");
-        INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff");
-
-        config.default_border = BS_NORMAL;
-        /* Set default_orientation to NO_ORIENTATION for auto orientation. */
-        config.default_orientation = NO_ORIENTATION;
-
-        parse_configuration(override_configpath);
-
-        if (reload) {
-                translate_keysyms();
-                grab_all_keys(conn, false);
-        }
+    do { \
+        x.border = get_colorpixel(cborder); \
+        x.background = get_colorpixel(cbackground); \
+        x.text = get_colorpixel(ctext); \
+    } while (0)
+
+    config.client.background = get_colorpixel("#000000");
+    INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff");
+    INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff");
+    INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888");
+    INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff");
+    INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff");
+    INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888");
+    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;
+
+    parse_configuration(override_configpath);
+
+    if (reload) {
+        translate_keysyms();
+        grab_all_keys(conn, false);
+    }
 
-        if (config.font.id == 0) {
-                ELOG("You did not specify required configuration option \"font\"\n");
-                config.font = load_font("fixed", true);
-        }
+    if (config.font.id == 0) {
+        ELOG("You did not specify required configuration option \"font\"\n");
+        config.font = load_font("fixed", true);
+    }
 
 #if 0
-        /* Set an empty name for every workspace which got no name */
-        Workspace *ws;
-        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                if (ws->name != NULL) {
-                        /* If the font was not specified when the workspace name
-                         * was loaded, we need to predict the text width now */
-                        if (ws->text_width == 0)
-                                ws->text_width = predict_text_width(global_conn,
-                                                config.font, ws->name, ws->name_len);
-                        continue;
-                }
-
-                workspace_set_name(ws, NULL);
-        }
+    /* Set an empty name for every workspace which got no name */
+    Workspace *ws;
+    TAILQ_FOREACH(ws, workspaces, workspaces) {
+            if (ws->name != NULL) {
+                    /* If the font was not specified when the workspace name
+                     * was loaded, we need to predict the text width now */
+                    if (ws->text_width == 0)
+                            ws->text_width = predict_text_width(global_conn,
+                                            config.font, ws->name, ws->name_len);
+                    continue;
+            }
+
+            workspace_set_name(ws, NULL);
+    }
 #endif
 }
index 8d3ce4e244a9059ebfe20c6f981ed31d028180f4..2fbf66bcfb275dc4a7f1a030e1e5d71589ea6fef 100644 (file)
@@ -173,6 +173,10 @@ void floating_enable(Con *con, bool automatic) {
     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);
 
index 5f4d59cc48632e4c9ff5f41474756893fc435691..e5034939a78bc2aa3b1503e517bd773c16b88bdf 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -205,6 +205,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     ystr("urgent");
     y(bool, con->urgent);
 
+    if (con->mark != NULL) {
+        ystr("mark");
+        ystr(con->mark);
+    }
+
     ystr("focused");
     y(bool, (con == focused));
 
@@ -338,6 +343,7 @@ IPC_HANDLER(tree) {
     y(free);
 }
 
+
 /*
  * Formats the reply message for a GET_WORKSPACES request and sends it to the
  * client
@@ -468,6 +474,38 @@ IPC_HANDLER(get_outputs) {
     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;
+#if YAJL_MAJOR >= 2
+    size_t length;
+#else
+    unsigned int length;
+#endif
+    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).
  *
@@ -555,12 +593,13 @@ IPC_HANDLER(subscribe) {
 
 /* 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
 };
 
 /*
@@ -683,7 +722,7 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
     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;
index b61a0e5c490210a72d30255ebef30ed576469c4c..37322c4ea3857f2b92e85f05c5ee843054f8e975 100644 (file)
@@ -146,6 +146,10 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
                 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;
index 77e295fcfbae74647ba5d7fbc02619e988975898..ea02bb6eb62e546fbc38282e59d0ea7d26809aa1 100644 (file)
@@ -5,6 +5,8 @@
 #include <fcntl.h>
 #include "all.h"
 
+#include "sd-daemon.h"
+
 static int xkb_event_base;
 
 int xkb_current_group;
@@ -161,6 +163,14 @@ static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
     DLOG("Done\n");
 }
 
+/*
+ * Exit handler which destroys the main_loop. Will trigger cleanup handlers.
+ *
+ */
+static void i3_exit() {
+    ev_loop_destroy(main_loop);
+}
+
 int main(int argc, char *argv[]) {
     //parse_cmd("[ foo ] attach, attach ; focus");
     int screens;
@@ -459,6 +469,21 @@ int main(int argc, char *argv[]) {
         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();
 
@@ -512,5 +537,9 @@ int main(int argc, char *argv[]) {
         start_application(exec_always->command);
     }
 
+    /* Make sure to destroy the event loop to invoke the cleeanup callbacks
+     * when calling exit() */
+    atexit(i3_exit);
+
     ev_loop(main_loop, 0);
 }
index 3dcdbac87626e049c28a8b66af9236596336fbec..9ee3dd72a8add59d806dc5a8ede7e3819d40da41 100644 (file)
@@ -325,7 +325,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
     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
index 3a346117340a9c880d66a70f99fcd91bee242df4..3514aceece2a95ca0a6d112575e6fb593c68a487 100644 (file)
@@ -52,16 +52,19 @@ bool match_is_empty(Match *match) {
 void match_copy(Match *dest, Match *src) {
     memcpy(dest, src, sizeof(Match));
 
-#define STRDUP(field) do { \
+/* The DUPLICATE_REGEX macro creates a new regular expression from the
+ * ->pattern of the old one. It therefore does use a little more memory then
+ *  with a refcounting system, but it’s easier this way. */
+#define DUPLICATE_REGEX(field) do { \
     if (src->field != NULL) \
-        dest->field = sstrdup(src->field); \
+        dest->field = regex_new(src->field->pattern); \
 } while (0)
 
-    STRDUP(title);
-    STRDUP(mark);
-    STRDUP(application);
-    STRDUP(class);
-    STRDUP(instance);
+    DUPLICATE_REGEX(title);
+    DUPLICATE_REGEX(mark);
+    DUPLICATE_REGEX(application);
+    DUPLICATE_REGEX(class);
+    DUPLICATE_REGEX(instance);
 }
 
 /*
@@ -71,9 +74,9 @@ void match_copy(Match *dest, Match *src) {
 bool match_matches_window(Match *match, i3Window *window) {
     LOG("checking window %d (%s)\n", window->id, window->class_class);
 
-    /* TODO: pcre, full matching, … */
     if (match->class != NULL) {
-        if (window->class_class != NULL && strcasecmp(match->class, window->class_class) == 0) {
+        if (window->class_class != NULL &&
+            regex_matches(match->class, window->class_class)) {
             LOG("window class matches (%s)\n", window->class_class);
         } else {
             LOG("window class does not match\n");
@@ -82,7 +85,8 @@ bool match_matches_window(Match *match, i3Window *window) {
     }
 
     if (match->instance != NULL) {
-        if (window->class_instance != NULL && strcasecmp(match->instance, window->class_instance) == 0) {
+        if (window->class_instance != NULL &&
+            regex_matches(match->instance, window->class_instance)) {
             LOG("window instance matches (%s)\n", window->class_instance);
         } else {
             LOG("window instance does not match\n");
@@ -99,9 +103,9 @@ bool match_matches_window(Match *match, i3Window *window) {
         }
     }
 
-    /* TODO: pcre match */
     if (match->title != NULL) {
-        if (window->name_json != NULL && strcasecmp(match->title, window->name_json) == 0) {
+        if (window->name_json != NULL &&
+            regex_matches(match->title, window->name_json)) {
             LOG("title matches (%s)\n", window->name_json);
         } else {
             LOG("title does not match\n");
@@ -132,3 +136,23 @@ bool match_matches_window(Match *match, i3Window *window) {
 
     return true;
 }
+
+/*
+ * Frees the given match. It must not be used afterwards!
+ *
+ */
+void match_free(Match *match) {
+    /* First step: free the regex fields / patterns */
+    regex_free(match->title);
+    regex_free(match->application);
+    regex_free(match->class);
+    regex_free(match->instance);
+    regex_free(match->mark);
+
+    /* Second step: free the regex helper struct itself */
+    FREE(match->title);
+    FREE(match->application);
+    FREE(match->class);
+    FREE(match->instance);
+    FREE(match->mark);
+}
index 6b6cd67d0e35ba524def157e7c0fa138c1f8691d..dd30925b9301c654132105bda8a23f325e452203 100644 (file)
@@ -447,10 +447,12 @@ void init_ws_for_output(Output *output, Con *content) {
         if (!exists) {
             /* Set ->num to the number of the workspace, if the name actually
              * is a number or starts with a number */
-            long parsed_num = strtol(ws->name, NULL, 10);
+            char *endptr = NULL;
+            long parsed_num = strtol(ws->name, &endptr, 10);
             if (parsed_num == LONG_MIN ||
                 parsed_num == LONG_MAX ||
-                parsed_num <= 0)
+                parsed_num < 0 ||
+                endptr == ws->name)
                 ws->num = -1;
             else ws->num = parsed_num;
             LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
diff --git a/src/regex.c b/src/regex.c
new file mode 100644 (file)
index 0000000..f419e4b
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ *
+ */
+
+#include "all.h"
+
+/*
+ * Creates a new 'regex' struct containing the given pattern and a PCRE
+ * compiled regular expression. Also, calls pcre_study because this regex will
+ * most likely be used often (like for every new window and on every relevant
+ * property change of existing windows).
+ *
+ * Returns NULL if the pattern could not be compiled into a regular expression
+ * (and ELOGs an appropriate error message).
+ *
+ */
+struct regex *regex_new(const char *pattern) {
+    const char *error;
+    int errorcode, offset;
+
+    struct regex *re = scalloc(sizeof(struct regex));
+    re->pattern = sstrdup(pattern);
+    /* We use PCRE_UCP so that \B, \b, \D, \d, \S, \s, \W, \w and some POSIX
+     * character classes play nicely with Unicode */
+    int options = PCRE_UCP | PCRE_UTF8;
+    while (!(re->regex = pcre_compile2(pattern, options, &errorcode, &error, &offset, NULL))) {
+        /* If the error is that PCRE was not compiled with UTF-8 support we
+         * disable it and try again */
+        if (errorcode == 32) {
+            options &= ~PCRE_UTF8;
+            continue;
+        }
+        ELOG("PCRE regular expression compilation failed at %d: %s\n",
+             offset, error);
+        return NULL;
+    }
+    re->extra = pcre_study(re->regex, 0, &error);
+    /* If an error happened, we print the error message, but continue.
+     * Studying the regular expression leads to faster matching, but it’s not
+     * absolutely necessary. */
+    if (error) {
+        ELOG("PCRE regular expression studying failed: %s\n", error);
+    }
+    return re;
+}
+
+/*
+ * Frees the given regular expression. It must not be used afterwards!
+ *
+ */
+void regex_free(struct regex *regex) {
+    if (!regex)
+        return;
+    FREE(regex->pattern);
+    FREE(regex->regex);
+    FREE(regex->extra);
+}
+
+/*
+ * Checks if the given regular expression matches the given input and returns
+ * true if it does. In either case, it logs the outcome using LOG(), so it will
+ * be visible without any debug loglevel.
+ *
+ */
+bool regex_matches(struct regex *regex, const char *input) {
+    int rc;
+
+    /* We use strlen() because pcre_exec() expects the length of the input
+     * string in bytes */
+    if ((rc = pcre_exec(regex->regex, regex->extra, input, strlen(input), 0, 0, NULL, 0)) == 0) {
+        LOG("Regular expression \"%s\" matches \"%s\"\n",
+            regex->pattern, input);
+        return true;
+    }
+
+    if (rc == PCRE_ERROR_NOMATCH) {
+        LOG("Regular expression \"%s\" does not match \"%s\"\n",
+            regex->pattern, input);
+        return false;
+    }
+
+    ELOG("PCRE error %d while trying to use regular expression \"%s\" on input \"%s\", see pcreapi(3)\n",
+         rc, regex->pattern, input);
+    return false;
+}
diff --git a/src/sd-daemon.c b/src/sd-daemon.c
new file mode 100644 (file)
index 0000000..6d1eebf
--- /dev/null
@@ -0,0 +1,436 @@
+/*-*- 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
+}
index cf1b40705167a35791ce1e3d087c4a81505b420e..27899a3781656ce61b301b217704aef3f11c2f8e 100644 (file)
@@ -49,12 +49,12 @@ Con *workspace_get(const char *num, bool *created) {
         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);
+        char *endptr = NULL;
+        long parsed_num = strtol(num, &endptr, 10);
         if (parsed_num == LONG_MIN ||
             parsed_num == LONG_MAX ||
             parsed_num < 0 ||
-            (end && *end != '\0'))
+            endptr == num)
             workspace->num = -1;
         else workspace->num = parsed_num;
         LOG("num = %d\n", workspace->num);
index c05d6e5b4341dae8594e22fb95fd25eb5e7ef828..6f00877cbf694e7b59582accbf2024db0e55f6bc 100755 (executable)
@@ -29,6 +29,18 @@ use Try::Tiny;
 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?
@@ -72,7 +84,6 @@ for my $display (@displays) {
     };
 }
 
-my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
 my $config = slurp('i3-test.config');
 
 # 1: get a list of all testcases
@@ -116,21 +127,90 @@ take_job($_) for @wdisplays;
 #
 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;
@@ -148,53 +228,65 @@ sub take_job {
         }
 
         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;
index 19e2df3408fa83ba3a99843dfd17d9654d440f76..3c3b6cc675421a510dc8c25b723198c2e8bf5006 100644 (file)
@@ -98,4 +98,23 @@ cmd 'workspace "prev"';
 ok(workspace_exists('prev'), 'workspace "prev" exists');
 is(focused_ws(), 'prev', 'now on workspace prev');
 
+#####################################################################
+# check that the numbers are assigned/recognized correctly
+#####################################################################
+
+cmd "workspace 3: $tmp";
+my $ws = get_ws("3: $tmp");
+ok(defined($ws), "workspace 3: $tmp was created");
+is($ws->{num}, 3, 'workspace number is 3');
+
+cmd "workspace 0: $tmp";
+my $ws = get_ws("0: $tmp");
+ok(defined($ws), "workspace 0: $tmp was created");
+is($ws->{num}, 0, 'workspace number is 0');
+
+cmd "workspace aa: $tmp";
+my $ws = get_ws("aa: $tmp");
+ok(defined($ws), "workspace aa: $tmp was created");
+is($ws->{num}, -1, 'workspace number is -1');
+
 done_testing;
index 2332bc71c1d31170be5898e21993ef54c1206343..e4fc6ec0225bbbd5116c6c8a7fac26676e2ed86e 100644 (file)
@@ -34,9 +34,10 @@ my $win = $content->[0];
 # first test that matches which should not match this window really do
 # not match it
 ######################################################################
-# TODO: use PCRE expressions
 # TODO: specify more match types
-cmd q|[class="*"] kill|;
+# we can match on any (non-empty) class here since that window does not have
+# WM_CLASS set
+cmd q|[class=".*"] kill|;
 cmd q|[con_id="99999"] kill|;
 
 $content = get_ws_content($tmp);
@@ -118,4 +119,62 @@ sleep 0.25;
 $content = get_ws_content($tmp);
 is(@{$content}, 1, 'one window still there');
 
+######################################################################
+# check that regular expressions work
+######################################################################
+
+$tmp = fresh_workspace;
+
+$left = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+);
+
+$left->_create;
+set_wm_class($left->id, 'special7', 'special7');
+$left->name('left');
+$left->map;
+sleep 0.25;
+
+# two windows should be here
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window opened');
+
+cmd '[class="^special[0-9]$"] kill';
+
+sleep 0.25;
+
+$content = get_ws_content($tmp);
+is(@{$content}, 0, 'window killed');
+
+######################################################################
+# check that UTF-8 works when matching
+######################################################################
+
+$tmp = fresh_workspace;
+
+$left = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+);
+
+$left->_create;
+set_wm_class($left->id, 'special7', 'special7');
+$left->name('ä 3');
+$left->map;
+sleep 0.25;
+
+# two windows should be here
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window opened');
+
+cmd '[title="^\w [3]$"] kill';
+
+sleep 0.25;
+
+$content = get_ws_content($tmp);
+is(@{$content}, 0, 'window killed');
+
 done_testing;
index 776710e7604af4706478718a1cb5fecd43193c8b..b8366917b90c917bd828fa99d1e6db278748476d 100644 (file)
@@ -182,6 +182,46 @@ exit_gracefully($process->pid);
 
 sleep 0.25;
 
+#####################################################################
+# make sure that assignments are case-insensitive in the old syntax.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign "special" → ~
+EOT
+
+$process = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+my $workspaces = get_workspace_names;
+ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+);
+
+$window->_create;
+set_wm_class($window->id, 'SPEcial', 'SPEcial');
+$window->name('special window');
+$window->map;
+sleep 0.25;
+
+my $content = get_ws($tmp);
+ok(@{$content->{nodes}} == 0, 'no tiling cons');
+ok(@{$content->{floating_nodes}} == 1, 'one floating con');
+
+$window->destroy;
+
+exit_gracefully($process->pid);
+
+sleep 0.25;
+
 #####################################################################
 # regression test: dock clients with floating assignments should not crash
 # (instead, nothing should happen - dock clients can’t float)
@@ -200,7 +240,9 @@ $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 my @docked = get_dock_clients;
-is(@docked, 0, 'no dock clients yet');
+# We expect i3-nagbar as the first dock client due to using the old assign
+# syntax
+is(@docked, 1, 'one dock client yet');
 
 my $window = $x->root->create_child(
     class => WINDOW_CLASS_INPUT_OUTPUT,
@@ -219,7 +261,7 @@ my $content = get_ws($tmp);
 ok(@{$content->{nodes}} == 0, 'no tiling cons');
 ok(@{$content->{floating_nodes}} == 0, 'one floating con');
 @docked = get_dock_clients;
-is(@docked, 1, 'no dock clients yet');
+is(@docked, 2, 'two dock clients now');
 
 $window->destroy;
 
diff --git a/testcases/t/73-get-marks.t b/testcases/t/73-get-marks.t
new file mode 100644 (file)
index 0000000..8648174
--- /dev/null
@@ -0,0 +1,63 @@
+#!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;
diff --git a/testcases/t/74-border-config.t b/testcases/t/74-border-config.t
new file mode 100644 (file)
index 0000000..c8a4d73
--- /dev/null
@@ -0,0 +1,140 @@
+#!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;