]> git.sur5r.net Git - i3/i3/commitdiff
Merge pull request #3022 from orestisf1993/i3bar-leaks
authorIngo Bürk <admin@airblader.de>
Sun, 3 Dec 2017 18:51:25 +0000 (19:51 +0100)
committerGitHub <noreply@github.com>
Sun, 3 Dec 2017 18:51:25 +0000 (19:51 +0100)
Fix i3bar leaks

36 files changed:
docs/userguide
etc/config
etc/config.keycodes
i3-config-wizard/main.c
i3-dump-log/main.c
i3-input/i3-input.h
i3-input/main.c
i3-msg/main.c
i3-nagbar/i3-nagbar.h
i3-sensible-terminal
i3bar/include/util.h
i3bar/src/config.c
i3bar/src/xcb.c
include/commands.h
include/i3.h
include/util.h
libi3/ipc_connect.c
libi3/ipc_recv_message.c
man/i3-sensible-terminal.man
parser-specs/commands.spec
src/commands.c
src/display_version.c
src/floating.c
src/handlers.c
src/main.c
src/restore_layout.c
src/tree.c
src/x.c
testcases/lib/i3test.pm.in
testcases/t/207-shmlog.t
testcases/t/219-ipc-window-focus.t
testcases/t/252-floating-size.t
testcases/t/504-move-workspace-to-output.t
testcases/t/510-focus-across-outputs.t
testcases/t/525-i3bar-mouse-bindings.t
testcases/t/540-sigterm-cleanup.t [new file with mode: 0644]

index 54ac9c8d63cb2cd7012feaad7284d1b8c5e35f33..7a1621da86e86af18c423da3f1f42ec02ac3ee8f 100644 (file)
@@ -2302,7 +2302,7 @@ If you want to resize containers/windows using your keyboard, you can use the
 *Syntax*:
 -------------------------------------------------------
 resize grow|shrink <direction> [<px> px [or <ppt> ppt]]
-resize set <width> [px] <height> [px]
+resize set <width> [px | ppt] <height> [px | ppt]
 -------------------------------------------------------
 
 Direction can either be one of +up+, +down+, +left+ or +right+. Or you can be
@@ -2398,10 +2398,10 @@ TODO: make i3-input replace %s
 *Examples*:
 ---------------------------------------
 # Read 1 character and mark the current window with this character
-bindsym $mod+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
+bindsym $mod+m exec i3-input -F 'mark %s' -l 1 -P 'Mark: '
 
 # Read 1 character and go to the window with the character
-bindsym $mod+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
+bindsym $mod+g exec i3-input -F '[con_mark="%s"] focus' -l 1 -P 'Goto: '
 ---------------------------------------
 
 Alternatively, if you do not want to mess with +i3-input+, you could create
index 483694c1c78d6d5c26661a0990f7b492cfb39e92..5b751d00df55f7f6831e5845697d4ce98c9a8eff 100644 (file)
@@ -22,7 +22,7 @@ font pango:monospace 8
 # The font above is very space-efficient, that is, it looks good, sharp and
 # clear in small sizes. However, its unicode glyph coverage is limited, the old
 # X core fonts rendering does not support right-to-left and this being a bitmap
-# font, it doesnt scale on retina/hidpi displays.
+# font, it doesn't scale on retina/hidpi displays.
 
 # use these keys for focus, movement, and resize directions when reaching for
 # the arrows is not convenient
@@ -157,6 +157,7 @@ mode "resize" {
         # back to normal: Enter or Escape
         bindsym Return mode "default"
         bindsym Escape mode "default"
+        bindsym Mod1+r mode "default"
 }
 
 bindsym Mod1+r mode "resize"
index 6d10fad293fdda9a3e83709af64a03873200b460..c07462b4e1cc144c0eef197bb1678398f42cbb09 100644 (file)
@@ -144,6 +144,7 @@ mode "resize" {
         # back to normal: Enter or Escape
         bindcode 36 mode "default"
         bindcode 9 mode "default"
+        bindcode $mod+27 mode "default"
 }
 
 bindcode $mod+27 mode "resize"
index dd58fd124547df7dcee0ce40785784eb1e43be4d..b368921f2a9b896c5b23b343da99f42fd48026f2 100644 (file)
 #error "SYSCONFDIR not defined"
 #endif
 
-#define FREE(pointer)          \
-    do {                       \
-        if (pointer != NULL) { \
-            free(pointer);     \
-            pointer = NULL;    \
-        }                      \
+#define FREE(pointer)   \
+    do {                \
+        free(pointer);  \
+        pointer = NULL; \
     } while (0)
 
 #include "xcb.h"
@@ -94,7 +92,7 @@ static xcb_get_modifier_mapping_reply_t *modmap_reply;
 static i3Font font;
 static i3Font bold_font;
 static int char_width;
-static char *socket_path;
+static char *socket_path = NULL;
 static xcb_window_t win;
 static surface_t surface;
 static xcb_key_symbols_t *symbols;
@@ -744,7 +742,6 @@ static void finish() {
 
 int main(int argc, char *argv[]) {
     char *xdg_config_home;
-    socket_path = getenv("I3SOCK");
     char *pattern = "pango:monospace 8";
     char *patternbold = "pango:monospace bold 8";
     int o, option_index = 0;
@@ -824,12 +821,6 @@ int main(int argc, char *argv[]) {
                                     &xkb_base_error) != 1)
         errx(EXIT_FAILURE, "Could not setup XKB extension.");
 
-    if (socket_path == NULL)
-        socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);
-
-    if (socket_path == NULL)
-        socket_path = "/tmp/i3-ipc.sock";
-
     keysyms = xcb_key_symbols_alloc(conn);
     xcb_get_modifier_mapping_cookie_t modmap_cookie;
     modmap_cookie = xcb_get_modifier_mapping(conn);
index 478af310c32318e42c12cedcae5730ad2100058c..e9901f8ee3f2c90c0e9aa37a864a6f96e08aad63 100644 (file)
@@ -25,6 +25,7 @@
 #include <fcntl.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
+#include <signal.h>
 
 #include "libi3.h"
 #include "shmlog.h"
@@ -38,6 +39,29 @@ static uint32_t wrap_count;
 static i3_shmlog_header *header;
 static char *logbuffer,
     *walk;
+static int ipcfd = -1;
+
+static volatile bool interrupted = false;
+
+static void sighandler(int signal) {
+    interrupted = true;
+}
+
+static void disable_shmlog(void) {
+    const char *disablecmd = "debuglog off; shmlog off";
+    if (ipc_send_message(ipcfd, strlen(disablecmd),
+                         I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)disablecmd) != 0)
+        err(EXIT_FAILURE, "IPC send");
+
+    /* Ensure the command was sent by waiting for the reply: */
+    uint32_t reply_length = 0;
+    uint8_t *reply = NULL;
+    if (ipc_recv_message(ipcfd, I3_IPC_REPLY_TYPE_COMMAND,
+                         &reply_length, &reply) != 0) {
+        err(EXIT_FAILURE, "IPC recv");
+    }
+    free(reply);
+}
 
 static int check_for_wrap(void) {
     if (wrap_count == header->wrap_count)
@@ -59,6 +83,14 @@ static void print_till_end(void) {
     walk += len;
 }
 
+void errorlog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+}
+
 int main(int argc, char *argv[]) {
     int o, option_index = 0;
     bool verbose = false;
@@ -123,15 +155,35 @@ int main(int argc, char *argv[]) {
             exit(1);
         }
         if (root_atom_contents("I3_CONFIG_PATH", conn, screen) != NULL) {
-            fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled.\n\n");
-            if (!is_debug_build()) {
+            fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled. Enabling SHM log until cancelled\n\n");
+            ipcfd = ipc_connect(NULL);
+            const char *enablecmd = "debuglog on; shmlog 5242880";
+            if (ipc_send_message(ipcfd, strlen(enablecmd),
+                                 I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)enablecmd) != 0)
+                err(EXIT_FAILURE, "IPC send");
+            /* By the time we receive a reply, I3_SHMLOG_PATH is set: */
+            uint32_t reply_length = 0;
+            uint8_t *reply = NULL;
+            if (ipc_recv_message(ipcfd, I3_IPC_REPLY_TYPE_COMMAND,
+                                 &reply_length, &reply) != 0) {
+                err(EXIT_FAILURE, "IPC recv");
+            }
+            free(reply);
+
+            atexit(disable_shmlog);
+
+            /* Retry: */
+            shmname = root_atom_contents("I3_SHMLOG_PATH", NULL, 0);
+            if (shmname == NULL && !is_debug_build()) {
                 fprintf(stderr, "You seem to be using a release version of i3:\n  %s\n\n", I3_VERSION);
                 fprintf(stderr, "Release versions do not use SHM logging by default,\ntherefore i3-dump-log does not work.\n\n");
                 fprintf(stderr, "Please follow this guide instead:\nhttps://i3wm.org/docs/debugging-release-version.html\n");
                 exit(1);
             }
         }
-        errx(EXIT_FAILURE, "Cannot get I3_SHMLOG_PATH atom contents. Is i3 running on this display?");
+        if (shmname == NULL) {
+            errx(EXIT_FAILURE, "Cannot get I3_SHMLOG_PATH atom contents. Is i3 running on this display?");
+        }
     }
 
     if (*shmname == '\0')
@@ -182,22 +234,32 @@ int main(int argc, char *argv[]) {
     print_till_end();
 
 #if !defined(__OpenBSD__)
-    if (follow) {
-        /* Since pthread_cond_wait() expects a mutex, we need to provide one.
+    if (!follow) {
+        return 0;
+    }
+
+    /* Handle SIGINT gracefully to invoke atexit handlers, if any. */
+    struct sigaction action;
+    action.sa_handler = sighandler;
+    sigemptyset(&action.sa_mask);
+    action.sa_flags = 0;
+    sigaction(SIGINT, &action, NULL);
+
+    /* Since pthread_cond_wait() expects a mutex, we need to provide one.
          * To not lock i3 (that’s bad, mhkay?) we just define one outside of
          * the shared memory. */
-        pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
-        pthread_mutex_lock(&dummy_mutex);
-        while (1) {
-            pthread_cond_wait(&(header->condvar), &dummy_mutex);
-            /* If this was not a spurious wakeup, print the new lines. */
-            if (header->offset_next_write != offset_next_write) {
-                offset_next_write = header->offset_next_write;
-                print_till_end();
-            }
+    pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
+    pthread_mutex_lock(&dummy_mutex);
+    while (!interrupted) {
+        pthread_cond_wait(&(header->condvar), &dummy_mutex);
+        /* If this was not a spurious wakeup, print the new lines. */
+        if (header->offset_next_write != offset_next_write) {
+            offset_next_write = header->offset_next_write;
+            print_till_end();
         }
     }
-#endif
 
+#endif
+    exit(0);
     return 0;
 }
index d347506fe61e69907bc9c13fbc5672ff3fd8113c..d7aae66b5f8ffb1033f93155d5e74eed20c2146a 100644 (file)
@@ -5,12 +5,10 @@
 #include <err.h>
 
 #define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
-#define FREE(pointer)          \
-    do {                       \
-        if (pointer != NULL) { \
-            free(pointer);     \
-            pointer = NULL;    \
-        }                      \
+#define FREE(pointer)   \
+    do {                \
+        free(pointer);  \
+        pointer = NULL; \
     } while (0)
 
 extern xcb_window_t root;
index 785a133fc10e7de57b307821b1c3302ee0c2069b..efb7b20c2b87a48183e7796427c0e4c60e5349b0 100644 (file)
@@ -41,7 +41,6 @@
  * the command will be sent to i3 */
 static char *format;
 
-static char *socket_path;
 static int sockfd;
 static xcb_key_symbols_t *symbols;
 static bool modeswitch_active = false;
@@ -374,7 +373,7 @@ free_resources:
 
 int main(int argc, char *argv[]) {
     format = sstrdup("%s");
-    socket_path = getenv("I3SOCK");
+    char *socket_path = NULL;
     char *pattern = sstrdup("pango:monospace 8");
     int o, option_index = 0;
 
@@ -438,12 +437,6 @@ int main(int argc, char *argv[]) {
     if (!conn || xcb_connection_has_error(conn))
         die("Cannot open display\n");
 
-    if (socket_path == NULL)
-        socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);
-
-    if (socket_path == NULL)
-        socket_path = "/tmp/i3-ipc.sock";
-
     sockfd = ipc_connect(socket_path);
 
     root_screen = xcb_aux_get_screen(conn, screen);
index a4f948500f16c0749b2548704c5d3289bc9e9fda..91a714e56637b5df408885509f680ad14e6c4b48 100644 (file)
@@ -38,8 +38,6 @@
 
 #include <i3/ipc.h>
 
-static char *socket_path;
-
 /*
  * Having verboselog() and errorlog() is necessary when using libi3.
  *
@@ -161,11 +159,7 @@ int main(int argc, char *argv[]) {
     if (pledge("stdio rpath unix", NULL) == -1)
         err(EXIT_FAILURE, "pledge");
 #endif
-    char *env_socket_path = getenv("I3SOCK");
-    if (env_socket_path)
-        socket_path = sstrdup(env_socket_path);
-    else
-        socket_path = NULL;
+    char *socket_path = NULL;
     int o, option_index = 0;
     uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND;
     char *payload = NULL;
@@ -183,8 +177,7 @@ int main(int argc, char *argv[]) {
 
     while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
         if (o == 's') {
-            if (socket_path != NULL)
-                free(socket_path);
+            free(socket_path);
             socket_path = sstrdup(optarg);
         } else if (o == 't') {
             if (strcasecmp(optarg, "command") == 0) {
@@ -228,13 +221,6 @@ int main(int argc, char *argv[]) {
         }
     }
 
-    if (socket_path == NULL)
-        socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
-
-    /* Fall back to the default socket path */
-    if (socket_path == NULL)
-        socket_path = sstrdup("/tmp/i3-ipc.sock");
-
     /* Use all arguments, separated by whitespace, as payload.
      * This way, you don’t have to do i3-msg 'mark foo', you can use
      * i3-msg mark foo */
@@ -253,17 +239,7 @@ int main(int argc, char *argv[]) {
     if (!payload)
         payload = sstrdup("");
 
-    int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
-    if (sockfd == -1)
-        err(EXIT_FAILURE, "Could not create socket");
-
-    struct sockaddr_un addr;
-    memset(&addr, 0, sizeof(struct sockaddr_un));
-    addr.sun_family = AF_LOCAL;
-    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
-    if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
-        err(EXIT_FAILURE, "Could not connect to i3 on socket \"%s\"", socket_path);
-
+    int sockfd = ipc_connect(socket_path);
     if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1)
         err(EXIT_FAILURE, "IPC: write()");
     free(payload);
index c5e94cc6140fb554d3fa48f1f6b26c1c2107f1db..cb672bead868f59460f5735f94c611e444b4783f 100644 (file)
@@ -5,12 +5,10 @@
 #include <err.h>
 
 #define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
-#define FREE(pointer)          \
-    do {                       \
-        if (pointer != NULL) { \
-            free(pointer);     \
-            pointer = NULL;    \
-        }                      \
+#define FREE(pointer)   \
+    do {                \
+        free(pointer);  \
+        pointer = NULL; \
     } while (0)
 
 #define xmacro(atom) xcb_atom_t A_##atom;
index f92ff224a8989d3fbef991257d0a9ccd8deb56b7..23cb5688f716de0ddb013c61c4ecea35d13cb18b 100755 (executable)
@@ -8,7 +8,7 @@
 # We welcome patches that add distribution-specific mechanisms to find the
 # preferred terminal emulator. On Debian, there is the x-terminal-emulator
 # symlink for example.
-for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole; do
+for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty; do
     if command -v "$terminal" > /dev/null 2>&1; then
         exec "$terminal" "$@"
     fi
index 3af79ed779c9704495b7fe400f5dacd482979420..1f56361106dfc97670bc412bf5d6e2ad90fb6450 100644 (file)
 #define STARTS_WITH(string, len, needle) (((len) >= strlen((needle))) && strncasecmp((string), (needle), strlen((needle))) == 0)
 
 /* Securely free p */
-#define FREE(p)          \
-    do {                 \
-        if (p != NULL) { \
-            free(p);     \
-            p = NULL;    \
-        }                \
+#define FREE(p)   \
+    do {          \
+        free(p);  \
+        p = NULL; \
     } while (0)
 
 /* Securely free single-linked list */
index edccd3c66f811f2cba68147ba7245bd826bdef3b..79e106c07447f9d56a994958b34229dc8dab216b 100644 (file)
@@ -107,34 +107,34 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
 
     if (!strcmp(cur_key, "mode")) {
         DLOG("mode = %.*s, len = %d\n", len, val, len);
-        config.hide_on_modifier = (len == 4 && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK
-                                                                                                   : (len == 4 && !strncmp((const char *)val, "hide", strlen("hide")) ? M_HIDE
-                                                                                                                                                                      : M_INVISIBLE));
+        config.hide_on_modifier = (len == strlen("dock") && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK
+                                                                                                                : (len == strlen("hide") && !strncmp((const char *)val, "hide", strlen("hide")) ? M_HIDE
+                                                                                                                                                                                                : M_INVISIBLE));
         return 1;
     }
 
     if (!strcmp(cur_key, "hidden_state")) {
         DLOG("hidden_state = %.*s, len = %d\n", len, val, len);
-        config.hidden_state = (len == 4 && !strncmp((const char *)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW);
+        config.hidden_state = (len == strlen("hide") && !strncmp((const char *)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW);
         return 1;
     }
 
     if (!strcmp(cur_key, "modifier")) {
         DLOG("modifier = %.*s\n", len, val);
-        if (len == 4 && !strncmp((const char *)val, "none", strlen("none"))) {
+        if (len == strlen("none") && !strncmp((const char *)val, "none", strlen("none"))) {
             config.modifier = XCB_NONE;
             return 1;
         }
 
-        if (len == 5 && !strncmp((const char *)val, "shift", strlen("shift"))) {
+        if (len == strlen("shift") && !strncmp((const char *)val, "shift", strlen("shift"))) {
             config.modifier = ShiftMask;
             return 1;
         }
-        if (len == 4 && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
+        if (len == strlen("ctrl") && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
             config.modifier = ControlMask;
             return 1;
         }
-        if (len == 4 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
+        if (len == strlen("Mod") + 1 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
             switch (val[3]) {
                 case '1':
                     config.modifier = Mod1Mask;
@@ -179,7 +179,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
 
     if (!strcmp(cur_key, "position")) {
         DLOG("position = %.*s\n", len, val);
-        config.position = (len == 3 && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT);
+        config.position = (len == strlen("top") && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT);
         return 1;
     }
 
index 815f41fdf3678eea41ad1f4ab4041430925f35f4..1a9240fb996dae3e14ca162650ec4eef7dbac656 100644 (file)
@@ -83,7 +83,6 @@ int mod_pressed = 0;
 
 /* Event watchers, to interact with the user */
 ev_prepare *xcb_prep;
-ev_check *xcb_chk;
 ev_io *xcb_io;
 ev_io *xkb_io;
 
@@ -1075,21 +1074,11 @@ static void handle_resize_request(xcb_resize_request_event_t *event) {
 }
 
 /*
- * This function is called immediately before the main loop locks. We flush xcb
- * then (and only then)
+ * This function is called immediately before the main loop locks. We check for
+ * events from X11, handle them, then flush our outgoing queue.
  *
  */
 void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
-    xcb_flush(xcb_connection);
-}
-
-/*
- * This function is called immediately after the main loop locks, so when one
- * of the watchers registered an event.
- * We check whether an X-Event arrived and handle it.
- *
- */
-void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
     xcb_generic_event_t *event;
 
     if (xcb_connection_has_error(xcb_connection)) {
@@ -1210,6 +1199,8 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
         }
         free(event);
     }
+
+    xcb_flush(xcb_connection);
 }
 
 /*
@@ -1267,21 +1258,12 @@ char *init_xcb_early() {
     /* The various watchers to communicate with xcb */
     xcb_io = smalloc(sizeof(ev_io));
     xcb_prep = smalloc(sizeof(ev_prepare));
-    xcb_chk = smalloc(sizeof(ev_check));
 
     ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
     ev_prepare_init(xcb_prep, &xcb_prep_cb);
-    ev_check_init(xcb_chk, &xcb_chk_cb);
-
-    /* Within an event loop iteration, run the xcb_chk watcher last: other
-     * watchers might call xcb_flush(), which, unexpectedly, can also read
-     * events into the queue (see _xcb_conn_wait). Hence, we need to drain xcb’s
-     * queue last, otherwise we risk dead-locking. */
-    ev_set_priority(xcb_chk, EV_MINPRI);
 
     ev_io_start(main_loop, xcb_io);
     ev_prepare_start(main_loop, xcb_prep);
-    ev_check_start(main_loop, xcb_chk);
 
     /* Now we get the atoms and save them in a nice data structure */
     get_atoms();
@@ -1526,11 +1508,9 @@ void clean_xcb(void) {
     xcb_aux_sync(xcb_connection);
     xcb_disconnect(xcb_connection);
 
-    ev_check_stop(main_loop, xcb_chk);
     ev_prepare_stop(main_loop, xcb_prep);
     ev_io_stop(main_loop, xcb_io);
 
-    FREE(xcb_chk);
     FREE(xcb_prep);
     FREE(xcb_io);
 }
index 9780f788b5bb53b308609df0818017e420d77024..85d5fe78a9f27056e565c048d52e4edc806e9bbc 100644 (file)
@@ -66,7 +66,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_
  * Implementation of 'resize set <px> [px] <px> [px]'.
  *
  */
-void cmd_resize_set(I3_CMD, long cwidth, long cheight);
+void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height);
 
 /**
  * Implementation of 'resize grow|shrink <direction> [<px> px] [or <ppt> ppt]'.
index 4d13d448f1395fdd97e13e82a477178e28ee4ea0..93a7e0a34cf6ca5983fe41cf7298b369bd32d9fe 100644 (file)
@@ -74,3 +74,4 @@ extern bool xcursor_supported, xkb_supported;
 extern xcb_window_t root;
 extern struct ev_loop *main_loop;
 extern bool only_check_config;
+extern bool force_xinerama;
index 526ab88107038ac583edd508466b23a40b4597be..3547d8d7b50df6533fc566aec682efe0e63bc1a3 100644 (file)
         break;                            \
     }
 
-#define FREE(pointer)          \
-    do {                       \
-        if (pointer != NULL) { \
-            free(pointer);     \
-            pointer = NULL;    \
-        }                      \
+#define FREE(pointer)   \
+    do {                \
+        free(pointer);  \
+        pointer = NULL; \
     } while (0)
 
 #define CALL(obj, member, ...) obj->member(obj, ##__VA_ARGS__)
index 2e6283425d47baf3853e4334f5837cd19d038496..f659a1a4720f566fe0083c5076e53d89a5969ba7 100644 (file)
  *
  */
 int ipc_connect(const char *socket_path) {
+    char *path = NULL;
+    if (socket_path != NULL) {
+        path = sstrdup(socket_path);
+    }
+
+    if (path == NULL) {
+        if ((path = getenv("I3SOCK")) != NULL) {
+            path = sstrdup(path);
+        }
+    }
+
+    if (path == NULL) {
+        path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
+    }
+
+    if (path == NULL) {
+        path = sstrdup("/tmp/i3-ipc.sock");
+    }
+
     int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
     if (sockfd == -1)
         err(EXIT_FAILURE, "Could not create socket");
@@ -31,9 +50,9 @@ int ipc_connect(const char *socket_path) {
     struct sockaddr_un addr;
     memset(&addr, 0, sizeof(struct sockaddr_un));
     addr.sun_family = AF_LOCAL;
-    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
+    strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
     if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
-        err(EXIT_FAILURE, "Could not connect to i3");
-
+        err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path);
+    free(path);
     return sockfd;
 }
index 16dda90d89b8cbcd5ec540aff00b1c3aba86d46c..84da5aa36cb90d7b913ec7c0afc87568102a9d70 100644 (file)
@@ -13,6 +13,7 @@
 #include <stdint.h>
 #include <unistd.h>
 #include <errno.h>
+#include <inttypes.h>
 
 #include <i3/ipc.h>
 
@@ -41,14 +42,21 @@ int ipc_recv_message(int sockfd, uint32_t *message_type,
         if (n == -1)
             return -1;
         if (n == 0) {
-            return -2;
+            if (read_bytes == 0) {
+                return -2;
+            } else {
+                ELOG("IPC: unexpected EOF while reading header, got %" PRIu32 " bytes, want %" PRIu32 " bytes\n",
+                     read_bytes, to_read);
+                return -3;
+            }
         }
 
         read_bytes += n;
     }
 
     if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
-        ELOG("IPC: invalid magic in reply\n");
+        ELOG("IPC: invalid magic in header, got \"%.*s\", want \"%s\"\n",
+             (int)strlen(I3_IPC_MAGIC), walk, I3_IPC_MAGIC);
         return -3;
     }
 
@@ -61,13 +69,18 @@ int ipc_recv_message(int sockfd, uint32_t *message_type,
     *reply = smalloc(*reply_length);
 
     read_bytes = 0;
-    int n;
     while (read_bytes < *reply_length) {
-        if ((n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes)) == -1) {
+        const int n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes);
+        if (n == -1) {
             if (errno == EINTR || errno == EAGAIN)
                 continue;
             return -1;
         }
+        if (n == 0) {
+            ELOG("IPC: unexpected EOF while reading payload, got %" PRIu32 " bytes, want %" PRIu32 " bytes\n",
+                 read_bytes, *reply_length);
+            return -3;
+        }
 
         read_bytes += n;
     }
index 20a6810c59bb8ed9ec4a1ab717e85de16ad63b3e..bccc824a86959e3d16656f7e8eddd549f2888a45 100644 (file)
@@ -44,6 +44,7 @@ It tries to start one of the following (in that order):
 * tilix
 * terminix
 * konsole
+* kitty
 
 Please don’t complain about the order: If the user has any preference, they will
 have $TERMINAL set or modified their i3 configuration file.
index a587332817e8b23bde3d48cd8ef434c43924b275..0289fa1ab6018a404cb2445f5fa57b61eaf283eb 100644 (file)
@@ -258,14 +258,16 @@ state RESIZE_SET:
       -> RESIZE_WIDTH
 
 state RESIZE_WIDTH:
-  'px'
+  mode_width = 'px', 'ppt'
       ->
   height = number
       -> RESIZE_HEIGHT
 
 state RESIZE_HEIGHT:
-  'px', end
-      -> call cmd_resize_set(&width, &height)
+  mode_height = 'px', 'ppt'
+      ->
+  end
+      -> call cmd_resize_set(&width, $mode_width, &height, $mode_height)
 
 # rename workspace <name> to <name>
 # rename workspace to <name>
index d7cdf2198f7d6170f4d5038cba7dc396a2897361..a9187866cd33402ae7dc4431640f16af4cf17804 100644 (file)
@@ -676,13 +676,13 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
 }
 
 /*
- * Implementation of 'resize set <px> [px] <px> [px]'.
+ * Implementation of 'resize set <width> [px | ppt] <height> [px | ppt]'.
  *
  */
-void cmd_resize_set(I3_CMD, long cwidth, long cheight) {
-    DLOG("resizing to %ldx%ld px\n", cwidth, cheight);
+void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height) {
+    DLOG("resizing to %ld %s x %ld %s\n", cwidth, mode_width, cheight, mode_height);
     if (cwidth <= 0 || cheight <= 0) {
-        ELOG("Resize failed: dimensions cannot be negative (was %ldx%ld)\n", cwidth, cheight);
+        ELOG("Resize failed: dimensions cannot be negative (was %ld %s x %ld %s)\n", cwidth, mode_width, cheight, mode_height);
         return;
     }
 
@@ -692,6 +692,13 @@ void cmd_resize_set(I3_CMD, long cwidth, long cheight) {
     TAILQ_FOREACH(current, &owindows, owindows) {
         Con *floating_con;
         if ((floating_con = con_inside_floating(current->con))) {
+            Con *output = con_get_output(floating_con);
+            if (mode_width && strcmp(mode_width, "ppt") == 0) {
+                cwidth = output->rect.width * ((double)cwidth / 100.0);
+            }
+            if (mode_height && strcmp(mode_height, "ppt") == 0) {
+                cheight = output->rect.height * ((double)cheight / 100.0);
+            }
             floating_resize(floating_con, cwidth, cheight);
         } else {
             ELOG("Resize failed: %p not a floating container\n", current->con);
@@ -1114,6 +1121,10 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) {
     owindow *current;
     TAILQ_FOREACH(current, &owindows, owindows) {
         Con *ws = con_get_workspace(current->con);
+        if (con_is_internal(ws)) {
+            continue;
+        }
+
         bool success = workspace_move_to_output(ws, name);
         if (!success) {
             ELOG("Failed to move workspace to output.\n");
index 764ee753177f1875a5de4fdd65827a19fadad985..2e05cafa89cc75a7f65fdfe2c6c9467e049736c2 100644 (file)
@@ -55,10 +55,6 @@ static yajl_callbacks version_callbacks = {
  *
  */
 void display_running_version(void) {
-    char *socket_path = root_atom_contents("I3_SOCKET_PATH", conn, conn_screen);
-    if (socket_path == NULL)
-        exit(EXIT_SUCCESS);
-
     char *pid_from_atom = root_atom_contents("I3_PID", conn, conn_screen);
     if (pid_from_atom == NULL) {
         /* If I3_PID is not set, the running version is older than 4.2-200. */
@@ -71,18 +67,7 @@ void display_running_version(void) {
     printf("(Getting version from running i3, press ctrl-c to abort…)");
     fflush(stdout);
 
-    /* TODO: refactor this with the code for sending commands */
-    int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
-    if (sockfd == -1)
-        err(EXIT_FAILURE, "Could not create socket");
-
-    struct sockaddr_un addr;
-    memset(&addr, 0, sizeof(struct sockaddr_un));
-    addr.sun_family = AF_LOCAL;
-    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
-    if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
-        err(EXIT_FAILURE, "Could not connect to i3");
-
+    int sockfd = ipc_connect(NULL);
     if (ipc_send_message(sockfd, 0, I3_IPC_MESSAGE_TYPE_GET_VERSION,
                          (uint8_t *)"") == -1)
         err(EXIT_FAILURE, "IPC: write()");
@@ -184,5 +169,4 @@ void display_running_version(void) {
     yajl_free(handle);
     free(reply);
     free(pid_from_atom);
-    free(socket_path);
 }
index 5f46dcf9755f9fb8850db07492954620c3850954..14988818418c972e70a9d1181cf10d990c886256 100644 (file)
@@ -667,7 +667,7 @@ void floating_resize_window(Con *con, const bool proportional,
 
 /* Custom data structure used to track dragging-related events. */
 struct drag_x11_cb {
-    ev_check check;
+    ev_prepare prepare;
 
     /* Whether this modal event loop should be exited and with which result. */
     drag_result_t result;
@@ -686,7 +686,7 @@ struct drag_x11_cb {
     const void *extra;
 };
 
-static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
+static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
     struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
     xcb_motion_notify_event_t *last_motion_notify = NULL;
     xcb_generic_event_t *event;
@@ -765,6 +765,8 @@ static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
             dragloop->extra);
     }
     free(last_motion_notify);
+
+    xcb_flush(conn);
 }
 
 /*
@@ -831,18 +833,18 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
         .callback = callback,
         .extra = extra,
     };
-    ev_check *check = &loop.check;
+    ev_prepare *prepare = &loop.prepare;
     if (con)
         loop.old_rect = con->rect;
-    ev_check_init(check, xcb_drag_check_cb);
-    check->data = &loop;
+    ev_prepare_init(prepare, xcb_drag_prepare_cb);
+    prepare->data = &loop;
     main_set_x11_cb(false);
-    ev_check_start(main_loop, check);
+    ev_prepare_start(main_loop, prepare);
 
     while (loop.result == DRAGGING)
         ev_run(main_loop, EVRUN_ONCE);
 
-    ev_check_stop(main_loop, check);
+    ev_prepare_stop(main_loop, prepare);
     main_set_x11_cb(true);
 
     xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
index 3140e4057674ca7596719ece8df9f5fbba70b19a..0f81afae19a095233e4c568e0b8dff7667edeb8b 100644 (file)
@@ -774,6 +774,8 @@ static void handle_client_message(xcb_client_message_event_t *event) {
                 scratchpad_show(con);
             } else {
                 workspace_show(ws);
+                /* Re-set focus, even if unchanged from i3’s perspective. */
+                focused_id = XCB_NONE;
                 con_focus(con);
             }
         } else {
@@ -1262,6 +1264,9 @@ static void handle_configure_notify(xcb_configure_notify_event_t *event) {
     }
     DLOG("ConfigureNotify for root window 0x%08x\n", event->event);
 
+    if (force_xinerama) {
+        return;
+    }
     randr_query_outputs();
 }
 
index 0d1457fdc1e18ca443fe652292f7343c9948b91f..b634b139f90276db0e4273d28a70922f732a63f5 100644 (file)
@@ -35,9 +35,9 @@ struct rlimit original_rlimit_core;
 /** The number of file descriptors passed via socket activation. */
 int listen_fds;
 
-/* We keep the xcb_check watcher around to be able to enable and disable it
+/* We keep the xcb_prepare watcher around to be able to enable and disable it
  * temporarily for drag_pointer(). */
-static struct ev_check *xcb_check;
+static struct ev_prepare *xcb_prepare;
 
 extern Con *focused;
 
@@ -92,29 +92,26 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment
 bool xcursor_supported = true;
 bool xkb_supported = true;
 
+bool force_xinerama = false;
+
 /*
- * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
+ * This callback is only a dummy, see xcb_prepare_cb.
  * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
  *
  */
 static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
-    /* empty, because xcb_prepare_cb and xcb_check_cb are used */
+    /* empty, because xcb_prepare_cb are used */
 }
 
 /*
- * Flush before blocking (and waiting for new events)
+ * Called just before the event loop sleeps. Ensures xcb’s incoming and outgoing
+ * queues are empty so that any activity will trigger another event loop
+ * iteration, and hence another xcb_prepare_cb invocation.
  *
  */
 static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
-    xcb_flush(conn);
-}
-
-/*
- * Instead of polling the X connection socket we leave this to
- * xcb_poll_for_event() which knows better than we can ever know.
- *
- */
-static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
+    /* Process all queued (and possibly new) events before the event loop
+       sleeps. */
     xcb_generic_event_t *event;
 
     while ((event = xcb_poll_for_event(conn)) != NULL) {
@@ -137,6 +134,9 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
 
         free(event);
     }
+
+    /* Flush all queued events to X11. */
+    xcb_flush(conn);
 }
 
 /*
@@ -148,12 +148,12 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
 void main_set_x11_cb(bool enable) {
     DLOG("Setting main X11 callback to enabled=%d\n", enable);
     if (enable) {
-        ev_check_start(main_loop, xcb_check);
+        ev_prepare_start(main_loop, xcb_prepare);
         /* Trigger the watcher explicitly to handle all remaining X11 events.
          * drag_pointer()’s event handler exits in the middle of the loop. */
-        ev_feed_event(main_loop, xcb_check, 0);
+        ev_feed_event(main_loop, xcb_prepare, 0);
     } else {
-        ev_check_stop(main_loop, xcb_check);
+        ev_prepare_stop(main_loop, xcb_prepare);
     }
 }
 
@@ -174,21 +174,64 @@ static void i3_exit(void) {
         fflush(stderr);
         shm_unlink(shmlogname);
     }
+    ipc_shutdown(SHUTDOWN_REASON_EXIT);
+    unlink(config.ipc_socket_path);
 }
 
 /*
- * (One-shot) Handler for all signals with default action "Term", see signal(7)
+ * (One-shot) Handler for all signals with default action "Core", see signal(7)
  *
  * Unlinks the SHM log and re-raises the signal.
  *
  */
-static void handle_signal(int sig, siginfo_t *info, void *data) {
+static void handle_core_signal(int sig, siginfo_t *info, void *data) {
     if (*shmlogname != '\0') {
         shm_unlink(shmlogname);
     }
     raise(sig);
 }
 
+/*
+ * (One-shot) Handler for all signals with default action "Term", see signal(7)
+ *
+ * Exits the program gracefully.
+ *
+ */
+static void handle_term_signal(struct ev_loop *loop, ev_signal *signal, int revents) {
+    /* We exit gracefully here in the sense that cleanup handlers
+     * installed via atexit are invoked. */
+    exit(128 + signal->signum);
+}
+
+/*
+ * Set up handlers for all signals with default action "Term", see signal(7)
+ *
+ */
+static void setup_term_handlers(void) {
+    static struct ev_signal signal_watchers[6];
+    size_t num_watchers = sizeof(signal_watchers) / sizeof(signal_watchers[0]);
+
+    /* We have to rely on libev functionality here and should not use
+     * sigaction handlers because we need to invoke the exit handlers
+     * and cannot do so from an asynchronous signal handling context as
+     * not all code triggered during exit is signal safe (and exiting
+     * the main loop from said handler is not easily possible). libev's
+     * signal handlers does not impose such a constraint on us. */
+    ev_signal_init(&signal_watchers[0], handle_term_signal, SIGHUP);
+    ev_signal_init(&signal_watchers[1], handle_term_signal, SIGINT);
+    ev_signal_init(&signal_watchers[2], handle_term_signal, SIGALRM);
+    ev_signal_init(&signal_watchers[3], handle_term_signal, SIGTERM);
+    ev_signal_init(&signal_watchers[4], handle_term_signal, SIGUSR1);
+    ev_signal_init(&signal_watchers[5], handle_term_signal, SIGUSR1);
+    for (size_t i = 0; i < num_watchers; i++) {
+        ev_signal_start(main_loop, &signal_watchers[i]);
+        /* The signal handlers should not block ev_run from returning
+         * and so none of the signal handlers should hold a reference to
+         * the main loop. */
+        ev_unref(main_loop);
+    }
+}
+
 int main(int argc, char *argv[]) {
     /* Keep a symbol pointing to the I3_VERSION string constant so that we have
      * it in gdb backtraces. */
@@ -197,7 +240,6 @@ int main(int argc, char *argv[]) {
     bool autostart = true;
     char *layout_path = NULL;
     bool delete_layout_path = false;
-    bool force_xinerama = false;
     bool disable_randr15 = false;
     char *fake_outputs = NULL;
     bool disable_signalhandler = false;
@@ -550,6 +592,10 @@ int main(int argc, char *argv[]) {
             config.ipc_socket_path = sstrdup(config.ipc_socket_path);
     }
 
+    if (config.force_xinerama) {
+        force_xinerama = true;
+    }
+
     xcb_void_cookie_t cookie;
     cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK});
     xcb_generic_error_t *error = xcb_request_check(conn, cookie);
@@ -668,7 +714,7 @@ int main(int argc, char *argv[]) {
         fake_outputs_init(fake_outputs);
         FREE(fake_outputs);
         config.fake_outputs = NULL;
-    } else if (force_xinerama || config.force_xinerama) {
+    } else if (force_xinerama) {
         /* Force Xinerama (for drivers which don't support RandR yet, esp. the
          * nVidia binary graphics driver), when specified either in the config
          * file or on command-line */
@@ -776,15 +822,11 @@ int main(int argc, char *argv[]) {
     ewmh_update_desktop_viewport();
 
     struct ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io));
-    xcb_check = scalloc(1, sizeof(struct ev_check));
-    struct ev_prepare *xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
+    xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
 
     ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
     ev_io_start(main_loop, xcb_watcher);
 
-    ev_check_init(xcb_check, xcb_check_cb);
-    ev_check_start(main_loop, xcb_check);
-
     ev_prepare_init(xcb_prepare, xcb_prepare_cb);
     ev_prepare_start(main_loop, xcb_prepare);
 
@@ -854,15 +896,15 @@ int main(int argc, char *argv[]) {
         err(EXIT_FAILURE, "pledge");
 #endif
 
-    struct sigaction action;
-
-    action.sa_sigaction = handle_signal;
-    action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
-    sigemptyset(&action.sa_mask);
-
     if (!disable_signalhandler)
         setup_signal_handler();
     else {
+        struct sigaction action;
+
+        action.sa_sigaction = handle_core_signal;
+        action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
+        sigemptyset(&action.sa_mask);
+
         /* Catch all signals with default action "Core", see signal(7) */
         if (sigaction(SIGQUIT, &action, NULL) == -1 ||
             sigaction(SIGILL, &action, NULL) == -1 ||
@@ -872,14 +914,7 @@ int main(int argc, char *argv[]) {
             ELOG("Could not setup signal handler.\n");
     }
 
-    /* Catch all signals with default action "Term", see signal(7) */
-    if (sigaction(SIGHUP, &action, NULL) == -1 ||
-        sigaction(SIGINT, &action, NULL) == -1 ||
-        sigaction(SIGALRM, &action, NULL) == -1 ||
-        sigaction(SIGUSR1, &action, NULL) == -1 ||
-        sigaction(SIGUSR2, &action, NULL) == -1)
-        ELOG("Could not setup signal handler.\n");
-
+    setup_term_handlers();
     /* Ignore SIGPIPE to survive errors when an IPC client disconnects
      * while we are sending them a message */
     signal(SIGPIPE, SIG_IGN);
@@ -922,7 +957,7 @@ int main(int argc, char *argv[]) {
         free(command);
     }
 
-    /* Make sure to destroy the event loop to invoke the cleeanup callbacks
+    /* Make sure to destroy the event loop to invoke the cleanup callbacks
      * when calling exit() */
     atexit(i3_exit);
 
index bf16c864c04be4a28d757071cff9edc3db4f5afb..b99a50c165ebbd922379f996527c8a57e00e3dc5 100644 (file)
@@ -39,7 +39,6 @@ static TAILQ_HEAD(state_head, placeholder_state) state_head =
 static xcb_connection_t *restore_conn;
 
 static struct ev_io *xcb_watcher;
-static struct ev_check *xcb_check;
 static struct ev_prepare *xcb_prepare;
 
 static void restore_handle_event(int type, xcb_generic_event_t *event);
@@ -49,10 +48,6 @@ static void restore_xcb_got_event(EV_P_ struct ev_io *w, int revents) {
 }
 
 static void restore_xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
-    xcb_flush(restore_conn);
-}
-
-static void restore_xcb_check_cb(EV_P_ ev_check *w, int revents) {
     xcb_generic_event_t *event;
 
     if (xcb_connection_has_error(restore_conn)) {
@@ -77,6 +72,8 @@ static void restore_xcb_check_cb(EV_P_ ev_check *w, int revents) {
 
         free(event);
     }
+
+    xcb_flush(restore_conn);
 }
 
 /*
@@ -91,7 +88,6 @@ void restore_connect(void) {
         /* This is not the initial connect, but a reconnect, most likely
          * because our X11 connection was killed (e.g. by a user with xkill. */
         ev_io_stop(main_loop, xcb_watcher);
-        ev_check_stop(main_loop, xcb_check);
         ev_prepare_stop(main_loop, xcb_prepare);
 
         placeholder_state *state;
@@ -107,7 +103,6 @@ void restore_connect(void) {
          */
         xcb_disconnect(restore_conn);
         free(xcb_watcher);
-        free(xcb_check);
         free(xcb_prepare);
     }
 
@@ -124,15 +119,11 @@ void restore_connect(void) {
     }
 
     xcb_watcher = scalloc(1, sizeof(struct ev_io));
-    xcb_check = scalloc(1, sizeof(struct ev_check));
     xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
 
     ev_io_init(xcb_watcher, restore_xcb_got_event, xcb_get_file_descriptor(restore_conn), EV_READ);
     ev_io_start(main_loop, xcb_watcher);
 
-    ev_check_init(xcb_check, restore_xcb_check_cb);
-    ev_check_start(main_loop, xcb_check);
-
     ev_prepare_init(xcb_prepare, restore_xcb_prepare_cb);
     ev_prepare_start(main_loop, xcb_prepare);
 }
index a6cc59fbb8faf8260e21719edba5bb85e164fc85..710bb655f6ab39254f0187884d69f565e718f9be 100644 (file)
@@ -560,21 +560,10 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
         if (!workspace)
             return false;
 
-        workspace_show(workspace);
-
-        /* If a workspace has an active fullscreen container, one of its
-         * children should always be focused. The above workspace_show()
-         * should be adequate for that, so return. */
-        if (con_get_fullscreen_con(workspace, CF_OUTPUT))
-            return true;
-
-        Con *focus = con_descend_direction(workspace, direction);
-
-        /* special case: if there was no tiling con to focus and the workspace
-         * has a floating con in the focus stack, focus the top of the focus
-         * stack (which may be floating) */
-        if (focus == workspace)
+        Con *focus = con_descend_tiling_focused(workspace);
+        if (focus == workspace) {
             focus = con_descend_focused(workspace);
+        }
 
         if (focus) {
             con_focus(focus);
diff --git a/src/x.c b/src/x.c
index 09a604931926aa4915d8e388177f849bfdbe16c5..7829079bb0363f8514e5b1abe6513434c0db809a 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -1227,9 +1227,13 @@ void x_set_name(Con *con, const char *name) {
  *
  */
 void update_shmlog_atom() {
-    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
-                        A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
-                        strlen(shmlogname), shmlogname);
+    if (*shmlogname == '\0') {
+        xcb_delete_property(conn, root, A_I3_SHMLOG_PATH);
+    } else {
+        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
+                            A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
+                            strlen(shmlogname), shmlogname);
+    }
 }
 
 /*
index 5b24ab39144a28c8422201092eda9ca95c074a2a..e754c0c17f6bf3b8b5b92e72233ebbe05ab8198a 100644 (file)
@@ -12,6 +12,7 @@ use AnyEvent::I3;
 use List::Util qw(first);
 use Time::HiRes qw(sleep);
 use Cwd qw(abs_path);
+use POSIX ':sys_wait_h';
 use Scalar::Util qw(blessed);
 use SocketActivation;
 use i3test::Util qw(slurp);
@@ -37,6 +38,7 @@ our @EXPORT = qw(
     cmd
     sync_with_i3
     exit_gracefully
+    exit_forcefully
     workspace_exists
     focused_ws
     get_socket_path
@@ -123,7 +125,7 @@ END {
 
     } else {
         kill(-9, $i3_pid)
-            or $tester->BAIL_OUT("could not kill i3");
+            or $tester->BAIL_OUT("could not kill i3: $!");
 
         waitpid $i3_pid, 0;
     }
@@ -133,6 +135,22 @@ sub import {
     my ($class, %args) = @_;
     my $pkg = caller;
 
+    $x ||= i3test::X11->new;
+    # set the pointer to a predictable position in case a previous test has
+    # disturbed it
+    $x->warp_pointer(
+       0, # src_window (None)
+       $x->get_root_window(), # dst_window (None)
+       0, # src_x
+       0, # src_y
+       0, # src_width
+       0, # src_height
+       0, # dst_x
+       0); # dst_y
+    # Synchronize with X11 to ensure the pointer has been warped before i3
+    # starts up.
+    $x->get_input_focus_reply($x->get_input_focus()->{sequence});
+
     $i3_autostart = delete($args{i3_autostart}) // 1;
     my $i3_config = delete($args{i3_config}) // '-default';
 
@@ -155,10 +173,6 @@ __
     strict->import;
     warnings->import;
 
-    $x ||= i3test::X11->new;
-    # set the pointer to a predictable position in case a previous test has
-    # disturbed it
-    $x->root->warp_pointer(0, 0);
     $cv->recv if $i3_autostart;
 
     @_ = ($class);
@@ -181,29 +195,11 @@ received, etc.
 sub wait_for_event {
     my ($timeout, $cb) = @_;
 
-    my $cv = AE::cv;
-
     $x->flush;
 
-    # unfortunately, there is no constant for this
-    my $ae_read = 0;
-
-    my $guard = AE::io $x->get_file_descriptor, $ae_read, sub {
-        while (defined(my $event = $x->poll_for_event)) {
-            if ($cb->($event)) {
-                $cv->send(1);
-                last;
-            }
-        }
-    };
-
-    # Trigger timeout after $timeout seconds (can be fractional)
-    my $t = AE::timer $timeout, 0, sub { warn "timeout ($timeout secs)"; $cv->send(0) };
-
-    my $result = $cv->recv;
-    undef $t;
-    undef $guard;
-    return $result;
+    while (defined(my $event = $x->wait_for_event)) {
+       return 1 if $cb->($event);
+    }
 }
 
 =head2 wait_for_map($window)
@@ -350,6 +346,12 @@ sub open_window {
 
     $window->map;
     wait_for_map($window);
+
+    # MapWindow is sent before i3 even starts rendering: the window is placed at
+    # temporary off-screen coordinates first, and x_push_changes() sends further
+    # X11 requests to set focus etc. Hence, we sync with i3 before continuing.
+    sync_with_i3();
+
     return $window;
 }
 
@@ -688,6 +690,7 @@ sub sync_with_i3 {
         $_sync_window = open_window(
             rect => [ -15, -15, 10, 10 ],
             override_redirect => 1,
+            dont_map => 1,
         );
     }
 
@@ -758,7 +761,7 @@ sub exit_gracefully {
 
     if (!$exited) {
         kill(9, $pid)
-            or $tester->BAIL_OUT("could not kill i3");
+            or $tester->BAIL_OUT("could not kill i3: $!");
     }
 
     if ($socketpath =~ m,^/tmp/i3-test-socket-,) {
@@ -769,6 +772,47 @@ sub exit_gracefully {
     undef $i3_pid;
 }
 
+=head2 exit_forcefully($pid, [ $signal ])
+
+Tries to exit i3 forcefully by sending a signal (defaults to SIGTERM).
+
+You only need to use this function if you want to test signal handling
+(in which case you must have launched i3 on your own with
+C<launch_with_config>).
+
+  use i3test i3_autostart => 0;
+  my $pid = launch_with_config($config);
+  # …
+  exit_forcefully($pid);
+
+=cut
+sub exit_forcefully {
+    my ($pid, $signal) = @_;
+    $signal ||= 'TERM';
+
+    # Send the given signal to the i3 instance and wait for up to 10s
+    # for it to terminate.
+    kill($signal, $pid)
+        or $tester->BAIL_OUT("could not kill i3: $!");
+    my $status;
+    my $timeout = 10;
+    do {
+        $status = waitpid $pid, WNOHANG;
+
+        if ($status <= 0) {
+            sleep(1);
+            $timeout--;
+        }
+    } while ($status <= 0 && $timeout > 0);
+
+    if ($status <= 0) {
+        kill('KILL', $pid)
+            or $tester->BAIL_OUT("could not kill i3: $!");
+        waitpid $pid, 0;
+    }
+    undef $i3_pid;
+}
+
 =head2 get_socket_path([ $cache ])
 
 Gets the socket path from the C<I3_SOCKET_PATH> atom stored on the X11 root
index 1351d568d77a05c7e7a8d24b95b205ac1d620275..c2b2ebaa3c1294c5c0b2652b004799062a7250ab 100644 (file)
@@ -54,7 +54,7 @@ like($stderr, qr#^$#, 'stderr empty');
 # 3: change size of the shared memory log buffer and verify old content is gone
 ################################################################################
 
-cmd 'shmlog ' . (23 * 1024 * 1024);
+cmd 'shmlog ' . (1 * 1024 * 1024);
 
 run [ 'i3-dump-log' ],
     '>', \$stdout,
index 5baf68a80163a200110cbe340f1571730da9ce4d..b1c8ba1836148b377a706da4848143d040a2b951 100644 (file)
@@ -44,11 +44,11 @@ sub focus_subtest {
     is($events[0]->{container}->{name}, $name, "$name focused");
 }
 
-subtest 'focus left (1)', \&focus_subtest, 'focus left', 'Window 1';
-subtest 'focus left (2)', \&focus_subtest, 'focus left', 'Window 0';
-subtest 'focus right (1)', \&focus_subtest, 'focus right', 'Window 1';
-subtest 'focus right (2)', \&focus_subtest, 'focus right', 'Window 2';
-subtest 'focus right (3)', \&focus_subtest, 'focus right', 'Window 0';
-subtest 'focus left', \&focus_subtest, 'focus left', 'Window 2';
+subtest 'focus left (1)', \&focus_subtest, 'focus left', $win1->name;
+subtest 'focus left (2)', \&focus_subtest, 'focus left', $win0->name;
+subtest 'focus right (1)', \&focus_subtest, 'focus right', $win1->name;
+subtest 'focus right (2)', \&focus_subtest, 'focus right', $win2->name;
+subtest 'focus right (3)', \&focus_subtest, 'focus right', $win0->name;
+subtest 'focus left', \&focus_subtest, 'focus left', $win2->name;
 
 done_testing;
index 8d8d412036105fd17a18ebb9aaf72bae8e176733..ac0c48d03d81a8ba31209197df47dd9c601426c0 100644 (file)
 # Test behavior of "resize <width> <height>" command.
 # Ticket: #1727
 # Bug still in: 4.10.2-1-gc0dbc5d
-use i3test;
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1333x999+0+0
+workspace ws output fake-0
+EOT
 
 ################################################################################
 # Check that setting floating windows size works
@@ -42,4 +48,54 @@ cmp_ok($content[0]->{rect}->{height}, '!=', $oldrect->{width}, 'height changed')
 cmp_ok($content[0]->{rect}->{width}, '==', 100, 'width changed to 100 px');
 cmp_ok($content[0]->{rect}->{height}, '==', 250, 'height changed to 250 px');
 
+################################################################################
+# Same but with ppt instead of px
+################################################################################
+
+kill_all_windows;
+$tmp = 'ws';
+cmd "workspace $tmp";
+open_floating_window;
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+is(@content, 1, 'one floating node on this ws');
+
+$oldrect = $content[0]->{rect};
+
+cmd 'resize set 33 ppt 20 ppt';
+my $expected_width = int(0.33 * 1333);
+my $expected_height = int(0.2 * 999);
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
+cmp_ok($content[0]->{rect}->{width}, '!=', $oldrect->{width}, 'width changed');
+cmp_ok($content[0]->{rect}->{height}, '!=', $oldrect->{width}, 'height changed');
+cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px");
+cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px");
+
+################################################################################
+# Mix ppt and px in a single resize set command
+################################################################################
+
+cmd 'resize set 44 ppt 111 px';
+my $expected_width = int(0.44 * 1333);
+my $expected_height = 111;
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
+cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px");
+cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px");
+
+cmd 'resize set 222 px 100 ppt';
+my $expected_width = 222;
+my $expected_height = 999;
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
+cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px");
+cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px");
+
 done_testing;
index 8e48bafe366c0209da1e22d4aa00347d69468eff..86bad5e505a1b2bb8c6f68834c2cd17f52d8310b 100644 (file)
@@ -179,6 +179,36 @@ cmd '[con_mark=marked] move workspace to output current';
 ($x0, $x1) = workspaces_per_screen();
 ok($ws1 ~~ @$x0, 'ws1 on fake-0');
 
+################################################################################
+# Verify that '[class=".*"] move workspace to output' doesn't fail when
+# containers in the scratchpad are matched.
+# See issue: #3064.
+################################################################################
+my $__i3_scratch = get_ws('__i3_scratch');
+is(scalar @{$__i3_scratch->{floating_nodes}}, 0, 'scratchpad is empty');
+
+my $ws0 = fresh_workspace(output => 0);
+open_window(wm_class => 'a');
+
+my $ws1 = fresh_workspace(output => 1);
+open_window(wm_class => 'b');
+my $scratchpad_window = open_window(wm_class => 'c');
+cmd 'move to scratchpad';
+
+($x0, $x1) = workspaces_per_screen();
+ok($ws0 ~~ @$x0, 'ws0 on fake-0');
+ok($ws1 ~~ @$x1, 'ws1 on fake-1');
+
+my $reply = cmd '[class=".*"] move workspace to output fake-1';
+ok($reply->[0]->{success}, 'move successful');
+
+($x0, $x1) = workspaces_per_screen();
+ok($ws0 ~~ @$x1, 'ws0 on fake-1');
+ok($ws1 ~~ @$x1, 'ws1 on fake-1');
+
+$__i3_scratch = get_ws('__i3_scratch');
+is(scalar @{$__i3_scratch->{floating_nodes}}, 1, 'window still in scratchpad');
+
 ################################################################################
 
 done_testing;
index 21169adbc3b25ca155f6c9f5ad4e180702e79181..07a2115c06497d851f5eee9c2f1eef2121d0474b 100644 (file)
@@ -92,7 +92,7 @@ reset_focus $s3_ws;
 
 cmd "workspace $s2_ws";
 cmd 'focus right';
-is($x->input_focus, $sixth->id, 'sixth window focused');
+is($x->input_focus, $seventh->id, 'seventh window focused');
 reset_focus $s3_ws;
 
 cmd "workspace $s2_ws";
@@ -110,6 +110,8 @@ is($x->input_focus, $third->id, 'third window focused');
 cmd 'focus parent';
 cmd 'focus parent';
 cmd 'split v';
+# Focus second or else $first gets to the top of the focus stack.
+cmd '[id=' . $second->id . '] focus';
 reset_focus $s0_ws;
 
 cmd "workspace $s3_ws";
@@ -137,4 +139,96 @@ cmd "workspace $s2_ws";
 cmd 'focus up';
 is($x->input_focus, $second->id, 'second window focused');
 
+###################################################################
+# Test that focus (left|down|right|up), when focusing across
+# outputs, doesn't focus the next window in the given direction but
+# the most focused window of the container in the given direction.
+# In the following layout:
+# [ WS1*[ ] WS2[ H[ A B* ] ] ]
+# (where the asterisk denotes the focused container within its
+# parent) moving right from WS1 should focus B which is focused
+# inside WS2, not A which is the next window on the right of WS1.
+# See issue #1160.
+###################################################################
+
+kill_all_windows;
+
+sync_with_i3;
+$x->root->warp_pointer(1025, 0);  # Second screen.
+sync_with_i3;
+$s1_ws = fresh_workspace;
+$first = open_window;
+$second = open_window;
+
+sync_with_i3;
+$x->root->warp_pointer(0, 0);  # First screen.
+sync_with_i3;
+$s0_ws = fresh_workspace;
+open_window;
+$third = open_window;
+
+cmd 'focus right';
+is($x->input_focus, $second->id, 'second window (rightmost) focused');
+cmd 'focus left';
+is($x->input_focus, $first->id, 'first window focused');
+cmd 'focus left';
+is($x->input_focus, $third->id, 'third window focused');
+
+
+###################################################################
+# Similar but with a tabbed layout.
+###################################################################
+
+cmd 'layout tabbed';
+$fourth = open_window;
+cmd 'focus left';
+is($x->input_focus, $third->id, 'third window (tabbed) focused');
+cmd "workspace $s1_ws";
+cmd 'focus left';
+is($x->input_focus, $third->id, 'third window (tabbed) focused');
+
+
+###################################################################
+# Similar but with a stacked layout on the bottom screen.
+###################################################################
+
+sync_with_i3;
+$x->root->warp_pointer(0, 769);  # Third screen.
+sync_with_i3;
+$s2_ws = fresh_workspace;
+cmd 'layout stacked';
+$fifth = open_window;
+$sixth = open_window;
+
+cmd "workspace $s0_ws";
+cmd 'focus down';
+is($x->input_focus, $sixth->id, 'sixth window (stacked) focused');
+
+###################################################################
+# Similar but with a more complex layout.
+###################################################################
+
+sync_with_i3;
+$x->root->warp_pointer(1025, 769);  # Fourth screen.
+sync_with_i3;
+$s3_ws = fresh_workspace;
+open_window;
+open_window;
+cmd 'split v';
+open_window;
+open_window;
+cmd 'split h';
+my $nested = open_window;
+open_window;
+cmd 'focus left';
+is($x->input_focus, $nested->id, 'nested window focused');
+
+cmd "workspace $s1_ws";
+cmd 'focus down';
+is($x->input_focus, $nested->id, 'nested window focused from workspace above');
+
+cmd "workspace $s2_ws";
+cmd 'focus right';
+is($x->input_focus, $nested->id, 'nested window focused from workspace on the left');
+
 done_testing;
index e67f0df9909eb9389f8be3f18b57c974eda7aa97..57786deaf2f8bc6e727498f61268ce450ee4e88d 100644 (file)
@@ -87,8 +87,6 @@ diag('i3bar window = ' . $i3bar_window);
 my $left = open_window;
 my $right = open_window;
 sync_with_i3;
-my $con = $cv->recv;
-is($con->{window}, $right->{id}, 'focus is initially on the right container');
 
 sub focus_subtest {
     my ($subscribecb, $want, $msg) = @_;
diff --git a/testcases/t/540-sigterm-cleanup.t b/testcases/t/540-sigterm-cleanup.t
new file mode 100644 (file)
index 0000000..5e5b9bf
--- /dev/null
@@ -0,0 +1,35 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests that the socket file is cleaned up properly after gracefully
+# shutting down i3 via SIGTERM.
+# Ticket: #3049
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config, dont_add_socket_path => 1);
+my $socket = get_socket_path();
+ok(-S $socket, "socket $socket exists");
+
+exit_forcefully($pid, 'TERM');
+
+ok(!-e $socket, "socket $socket no longer exists");
+
+done_testing;