]> git.sur5r.net Git - i3/i3/commitdiff
ipc: implement GET_WORKSPACES message type
authorMichael Stapelberg <michael@stapelberg.de>
Thu, 11 Mar 2010 14:58:39 +0000 (15:58 +0100)
committerMichael Stapelberg <michael@stapelberg.de>
Thu, 11 Mar 2010 14:58:39 +0000 (15:58 +0100)
This is the foundation to use dzen2 or similar as a complete
replacement for the internal workspaces bar.

A testcase is included, more documentation about the IPC interface
will follow.

DEPENDS
common.mk
debian/control
include/data.h
include/i3/ipc.h
src/ipc.c
src/workspace.c
testcases/Makefile
testcases/t/15-ipc-workspaces.t [new file with mode: 0644]
testcases/t/lib/i3test.pm

diff --git a/DEPENDS b/DEPENDS
index 47258068ef2553c9fd052b43bb01758a9d82ed62..31947bf98fb829850089f57c3a325d37ba1eecb1 100644 (file)
--- a/DEPENDS
+++ b/DEPENDS
@@ -7,6 +7,7 @@ In that case, please try using the versions mentioned below until a fix is provi
  * xcb-util-0.3.3 (2009-01-31)
  * libev
  * flex and bison
+ * yajl (the IPC interface uses JSON to serialize data)
  * asciidoc >= 8.3.0 for docs/hacking-howto
  * asciidoc, xmlto, docbook-xml for man/i3.man
  * Xlib, the one that comes with your X-Server
@@ -24,6 +25,7 @@ http://xcb.freedesktop.org/dist/xcb-util-0.3.5.tar.bz2
 http://libev.schmorp.de/
 http://flex.sourceforge.net/
 http://www.gnu.org/software/bison/
+http://lloyd.github.com/yajl/
 
 http://i3.zekjur.net/i3lock/
 http://tools.suckless.org/dmenu
index cca747bef55afa5a0aa0208e3cc9701a3cec4c0d..b1940140b34381cab3a9d34420f974a6235cf963 100644 (file)
--- a/common.mk
+++ b/common.mk
@@ -40,6 +40,7 @@ LDFLAGS += -lxcb-icccm
 LDFLAGS += -lxcb-xinerama
 LDFLAGS += -lxcb-randr
 LDFLAGS += -lxcb
+LDFLAGS += -lyajl
 LDFLAGS += -lX11
 LDFLAGS += -lev
 LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib
index 48af208c7afcb4658d3b0257a0a4112c7603ab24..8dde5b8045d14afdf45cb512f5ab48d533b28287 100644 (file)
@@ -3,7 +3,7 @@ Section: utils
 Priority: extra
 Maintainer: Michael Stapelberg <michael@stapelberg.de>
 DM-Upload-Allowed: yes
-Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison
+Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev
 Standards-Version: 3.8.3
 Homepage: http://i3.zekjur.net/
 
index 2cc8362f267c6969dcce2ff979f2c4bff1e1d02b..6819ca95f80edb9eec6b4ac4ad271c25dd9c1d9b 100644 (file)
@@ -177,6 +177,9 @@ struct Workspace {
         /** Number of this workspace, starting from 0 */
         int num;
 
+        /** Name of the workspace (in UTF-8) */
+        char *utf8_name;
+
         /** Name of the workspace (in UCS-2) */
         char *name;
 
index 40e01158547782e54a4347529b3e018ceee70c61..131c4b167aafc966b7a1d0aa62843597a26e44df 100644 (file)
@@ -3,7 +3,7 @@
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009 Michael Stapelberg and contributors
+ * © 2009-2010 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
 #ifndef _I3_IPC_H
 #define _I3_IPC_H
 
+/*
+ * Messages from clients to i3
+ *
+ */
+
 /** Never change this, only on major IPC breakage (don’t do that) */
 #define I3_IPC_MAGIC                   "i3-ipc"
 
 /** The payload of the message will be interpreted as a command */
-#define I3_IPC_MESSAGE_TYPE_COMMAND    0
+#define I3_IPC_MESSAGE_TYPE_COMMAND            0
+
+/** Requests the current workspaces from i3 */
+#define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES      1
+
+/*
+ * Messages from i3 to clients
+ *
+ */
+
+/** Workspaces reply type */
+#define I3_IPC_REPLY_TYPE_WORKSPACES            1
 
 #endif
index f7aeccf433dee4cc5346afd418a7ea4b65557824..15676e4e123d84c6a52ecc8737a7810d79e5dac8 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -3,7 +3,7 @@
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009 Michael Stapelberg and contributors
+ * © 2009-2010 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
@@ -22,6 +22,7 @@
 #include <stdint.h>
 #include <stdio.h>
 #include <ev.h>
+#include <yajl/yajl_gen.h>
 
 #include "queue.h"
 #include "i3/ipc.h"
 #include "util.h"
 #include "commands.h"
 #include "log.h"
+#include "table.h"
+
+/* Shorter names for all those yajl_gen_* functions */
+#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
+#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
 
 typedef struct ipc_client {
         int fd;
@@ -60,6 +66,83 @@ void broadcast(EV_P_ struct ev_timer *t, int revents) {
 }
 #endif
 
+static void ipc_send_message(int fd, const unsigned char *payload,
+                             int message_type, int message_size) {
+        int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) +
+                          sizeof(uint32_t) + message_size;
+        char msg[buffer_size];
+        char *walk = msg;
+
+        strcpy(walk, "i3-ipc");
+        walk += strlen("i3-ipc");
+        memcpy(walk, &message_size, sizeof(uint32_t));
+        walk += sizeof(uint32_t);
+        memcpy(walk, &message_type, sizeof(uint32_t));
+        walk += sizeof(uint32_t);
+        memcpy(walk, payload, message_size);
+
+        int sent_bytes = 0;
+        int bytes_to_go = buffer_size;
+        while (sent_bytes < bytes_to_go) {
+                int n = write(fd, msg + sent_bytes, bytes_to_go);
+                if (n == -1)
+                        err(EXIT_FAILURE, "write() failed");
+
+                sent_bytes += n;
+                bytes_to_go -= n;
+        }
+}
+
+/*
+ * Formats the reply message for a GET_WORKSPACES request and sends it to the
+ * client
+ *
+ */
+static void ipc_send_workspaces(int fd) {
+        Workspace *ws;
+
+        yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+        y(array_open);
+
+        TAILQ_FOREACH(ws, workspaces, workspaces) {
+                if (ws->output == NULL)
+                        continue;
+
+                y(map_open);
+                ystr("num");
+                y(integer, ws->num);
+
+                ystr("name");
+                ystr(ws->utf8_name);
+
+                ystr("rect");
+                y(map_open);
+                ystr("x");
+                y(integer, ws->rect.x);
+                ystr("y");
+                y(integer, ws->rect.y);
+                ystr("width");
+                y(integer, ws->rect.width);
+                ystr("height");
+                y(integer, ws->rect.height);
+                y(map_close);
+
+                ystr("output");
+                ystr(ws->output->name);
+
+                y(map_close);
+        }
+
+        y(array_close);
+
+        const unsigned char *payload;
+        unsigned int length;
+        y(get_buf, &payload, &length);
+
+        ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length);
+        y(free);
+}
+
 /*
  * Decides what to do with the received message.
  *
@@ -70,8 +153,8 @@ void broadcast(EV_P_ struct ev_timer *t, int revents) {
  * message_type is the type of the message as the sender specified it.
  *
  */
-static void ipc_handle_message(uint8_t *message, int size,
-                                uint32_t message_size, uint32_t message_type) {
+static void ipc_handle_message(int fd, uint8_t *message, int size,
+                               uint32_t message_size, uint32_t message_type) {
         DLOG("handling message of size %d\n", size);
         DLOG("sender specified size %d\n", message_size);
         DLOG("sender specified type %d\n", message_type);
@@ -88,6 +171,9 @@ static void ipc_handle_message(uint8_t *message, int size,
 
                         break;
                 }
+                case I3_IPC_MESSAGE_TYPE_GET_WORKSPACES:
+                        ipc_send_workspaces(fd);
+                        break;
                 default:
                         DLOG("unhandled ipc message\n");
                         break;
@@ -175,7 +261,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
                 message += sizeof(uint32_t);
                 n -= sizeof(uint32_t);
 
-                ipc_handle_message(message, n, message_size, message_type);
+                ipc_handle_message(w->fd, message, n, message_size, message_type);
                 n -= message_size;
                 message += message_size;
         }
index 94d6a068e0b5ad2941b5127405db6148333810ee..59040132fad04653f757d9ee26071bb48f8999ed 100644 (file)
@@ -84,13 +84,13 @@ void workspace_set_name(Workspace *ws, const char *name) {
                 errx(1, "asprintf() failed");
 
         FREE(ws->name);
+        FREE(ws->utf8_name);
 
         ws->name = convert_utf8_to_ucs2(label, &(ws->name_len));
         if (config.font != NULL)
                 ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len);
         else ws->text_width = 0;
-
-        free(label);
+        ws->utf8_name = label;
 }
 
 /*
index d37450ab320bbbd445cfa00dd942bc9eed0ad239..010b595f56a036aeaf554f1141edad96be60afe0 100644 (file)
@@ -1,4 +1,4 @@
 test:
-       PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" t/*.t
+       PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" t/15*.t
 
 all: test
diff --git a/testcases/t/15-ipc-workspaces.t b/testcases/t/15-ipc-workspaces.t
new file mode 100644 (file)
index 0000000..0194709
--- /dev/null
@@ -0,0 +1,56 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use Test::More tests => 8;
+use Test::Exception;
+use Data::Dumper;
+use JSON::XS;
+use List::MoreUtils qw(all);
+use FindBin;
+use lib "$FindBin::Bin/lib";
+use i3test;
+
+BEGIN {
+    use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
+    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
+isa_ok($sock, 'IO::Socket::UNIX');
+
+####################
+# Request workspaces
+####################
+
+# message type 1 is GET_WORKSPACES
+my $message = "i3-ipc" . pack("LL", 0, 1);
+$sock->write($message);
+
+#######################################
+# Test the reply format for correctness
+#######################################
+
+# The following lines duplicate functionality from recv_ipc_command
+# to have it included in the test-suite.
+my $buffer;
+$sock->read($buffer, length($message));
+is(substr($buffer, 0, length("i3-ipc")), "i3-ipc", "ipc message received");
+my ($len, $type) = unpack("LL", substr($buffer, 6));
+is($type, 1, "correct reply type");
+
+# read the payload
+$sock->read($buffer, $len);
+my $workspaces;
+
+#########################
+# Actually test the reply
+#########################
+
+lives_ok { $workspaces = decode_json($buffer) } 'JSON could be decoded';
+
+ok(@{$workspaces} > 0, "More than zero workspaces found");
+
+my $name_exists = all { defined($_->{name}) } @{$workspaces};
+ok($name_exists, "All workspaces have a name");
+
+diag( "Testing i3, Perl $], $^X" );
index a60fd6d238614c980d31954ff85b343d68de1fba..540551b044526c102ef5c37076dcca0edcfc8852 100644 (file)
@@ -40,4 +40,22 @@ sub format_ipc_command {
     return $message;
 }
 
+sub recv_ipc_command {
+    my ($sock, $expected) = @_;
+
+    my $buffer;
+    # header is 14 bytes ("i3-ipc" + 32 bit + 32 bit)
+    $sock->read($buffer, 14);
+    return undef unless substr($buffer, 0, length("i3-ipc")) eq "i3-ipc";
+
+    my ($len, $type) = unpack("LL", substr($buffer, 6));
+
+    return undef unless $type == $expected;
+
+    # read the payload
+    $sock->read($buffer, $len);
+
+    decode_json($buffer)
+}
+
 1