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:
parent
952914c3c5
commit
9a9ba1b859
2
DEPENDS
2
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)
|
* 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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/
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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 (don’t do that) */
|
/** Never change this, only on major IPC breakage (don’t 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
|
||||||
|
|
92
src/ipc.c
92
src/ipc.c
|
@ -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,7 +153,7 @@ 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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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" );
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue