ipc: implement GET_WORKSPACES message type

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.
This commit is contained in:
Michael Stapelberg 2010-03-11 15:58:39 +01:00
parent 952914c3c5
commit 9a9ba1b859
10 changed files with 192 additions and 10 deletions

View File

@ -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) * xcb-util-0.3.3 (2009-01-31)
* libev * libev
* flex and bison * flex and bison
* yajl (the IPC interface uses JSON to serialize data)
* asciidoc >= 8.3.0 for docs/hacking-howto * asciidoc >= 8.3.0 for docs/hacking-howto
* asciidoc, xmlto, docbook-xml for man/i3.man * asciidoc, xmlto, docbook-xml for man/i3.man
* Xlib, the one that comes with your X-Server * 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://libev.schmorp.de/
http://flex.sourceforge.net/ http://flex.sourceforge.net/
http://www.gnu.org/software/bison/ http://www.gnu.org/software/bison/
http://lloyd.github.com/yajl/
http://i3.zekjur.net/i3lock/ http://i3.zekjur.net/i3lock/
http://tools.suckless.org/dmenu http://tools.suckless.org/dmenu

View File

@ -40,6 +40,7 @@ LDFLAGS += -lxcb-icccm
LDFLAGS += -lxcb-xinerama LDFLAGS += -lxcb-xinerama
LDFLAGS += -lxcb-randr LDFLAGS += -lxcb-randr
LDFLAGS += -lxcb LDFLAGS += -lxcb
LDFLAGS += -lyajl
LDFLAGS += -lX11 LDFLAGS += -lX11
LDFLAGS += -lev LDFLAGS += -lev
LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib

2
debian/control vendored
View File

@ -3,7 +3,7 @@ Section: utils
Priority: extra Priority: extra
Maintainer: Michael Stapelberg <michael@stapelberg.de> Maintainer: Michael Stapelberg <michael@stapelberg.de>
DM-Upload-Allowed: yes 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 Standards-Version: 3.8.3
Homepage: http://i3.zekjur.net/ Homepage: http://i3.zekjur.net/

View File

@ -177,6 +177,9 @@ struct Workspace {
/** Number of this workspace, starting from 0 */ /** Number of this workspace, starting from 0 */
int num; int num;
/** Name of the workspace (in UTF-8) */
char *utf8_name;
/** Name of the workspace (in UCS-2) */ /** Name of the workspace (in UCS-2) */
char *name; char *name;

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -15,10 +15,26 @@
#ifndef _I3_IPC_H #ifndef _I3_IPC_H
#define _I3_IPC_H #define _I3_IPC_H
/*
* Messages from clients to i3
*
*/
/** Never change this, only on major IPC breakage (dont do that) */ /** Never change this, only on major IPC breakage (dont do that) */
#define I3_IPC_MAGIC "i3-ipc" #define I3_IPC_MAGIC "i3-ipc"
/** The payload of the message will be interpreted as a command */ /** 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 #endif

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -22,6 +22,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <ev.h> #include <ev.h>
#include <yajl/yajl_gen.h>
#include "queue.h" #include "queue.h"
#include "i3/ipc.h" #include "i3/ipc.h"
@ -29,6 +30,11 @@
#include "util.h" #include "util.h"
#include "commands.h" #include "commands.h"
#include "log.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 { typedef struct ipc_client {
int fd; int fd;
@ -60,6 +66,83 @@ void broadcast(EV_P_ struct ev_timer *t, int revents) {
} }
#endif #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. * 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. * message_type is the type of the message as the sender specified it.
* *
*/ */
static void ipc_handle_message(uint8_t *message, int size, static void ipc_handle_message(int fd, uint8_t *message, int size,
uint32_t message_size, uint32_t message_type) { uint32_t message_size, uint32_t message_type) {
DLOG("handling message of size %d\n", size); DLOG("handling message of size %d\n", size);
DLOG("sender specified size %d\n", message_size); DLOG("sender specified size %d\n", message_size);
DLOG("sender specified type %d\n", message_type); DLOG("sender specified type %d\n", message_type);
@ -88,6 +171,9 @@ static void ipc_handle_message(uint8_t *message, int size,
break; break;
} }
case I3_IPC_MESSAGE_TYPE_GET_WORKSPACES:
ipc_send_workspaces(fd);
break;
default: default:
DLOG("unhandled ipc message\n"); DLOG("unhandled ipc message\n");
break; break;
@ -175,7 +261,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
message += sizeof(uint32_t); message += sizeof(uint32_t);
n -= 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; n -= message_size;
message += message_size; message += message_size;
} }

View File

@ -84,13 +84,13 @@ void workspace_set_name(Workspace *ws, const char *name) {
errx(1, "asprintf() failed"); errx(1, "asprintf() failed");
FREE(ws->name); FREE(ws->name);
FREE(ws->utf8_name);
ws->name = convert_utf8_to_ucs2(label, &(ws->name_len)); ws->name = convert_utf8_to_ucs2(label, &(ws->name_len));
if (config.font != NULL) if (config.font != NULL)
ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len); ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len);
else ws->text_width = 0; else ws->text_width = 0;
ws->utf8_name = label;
free(label);
} }
/* /*

View File

@ -1,4 +1,4 @@
test: 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 all: test

View File

@ -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" );

View File

@ -40,4 +40,22 @@ sub format_ipc_command {
return $message; 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 1