diff --git a/DEPENDS b/DEPENDS index 47258068..31947bf9 100644 --- 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 diff --git a/common.mk b/common.mk index cca747be..b1940140 100644 --- 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 diff --git a/debian/control b/debian/control index 48af208c..8dde5b80 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: extra Maintainer: Michael Stapelberg 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/ diff --git a/include/data.h b/include/data.h index 2cc8362f..6819ca95 100644 --- a/include/data.h +++ b/include/data.h @@ -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; diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 40e01158..131c4b16 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -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. * @@ -15,10 +15,26 @@ #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 diff --git a/src/ipc.c b/src/ipc.c index f7aeccf4..15676e4e 100644 --- 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 #include #include +#include #include "queue.h" #include "i3/ipc.h" @@ -29,6 +30,11 @@ #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; } diff --git a/src/workspace.c b/src/workspace.c index 94d6a068..59040132 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -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; } /* diff --git a/testcases/Makefile b/testcases/Makefile index d37450ab..010b595f 100644 --- a/testcases/Makefile +++ b/testcases/Makefile @@ -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 index 00000000..01947094 --- /dev/null +++ b/testcases/t/15-ipc-workspaces.t @@ -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" ); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index a60fd6d2..540551b0 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -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