commit
ffa228e653
|
@ -99,11 +99,12 @@ use constant TYPE_GET_BAR_CONFIG => 6;
|
||||||
use constant TYPE_GET_VERSION => 7;
|
use constant TYPE_GET_VERSION => 7;
|
||||||
use constant TYPE_GET_BINDING_MODES => 8;
|
use constant TYPE_GET_BINDING_MODES => 8;
|
||||||
use constant TYPE_GET_CONFIG => 9;
|
use constant TYPE_GET_CONFIG => 9;
|
||||||
|
use constant TYPE_SEND_TICK => 10;
|
||||||
|
|
||||||
our %EXPORT_TAGS = ( 'all' => [
|
our %EXPORT_TAGS = ( 'all' => [
|
||||||
qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
|
qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
|
||||||
TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION
|
TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION
|
||||||
TYPE_GET_BINDING_MODES TYPE_GET_CONFIG)
|
TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK)
|
||||||
] );
|
] );
|
||||||
|
|
||||||
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
|
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
|
||||||
|
@ -120,6 +121,7 @@ my %events = (
|
||||||
barconfig_update => ($event_mask | 4),
|
barconfig_update => ($event_mask | 4),
|
||||||
binding => ($event_mask | 5),
|
binding => ($event_mask | 5),
|
||||||
shutdown => ($event_mask | 6),
|
shutdown => ($event_mask | 6),
|
||||||
|
tick => ($event_mask | 7),
|
||||||
_error => 0xFFFFFFFF,
|
_error => 0xFFFFFFFF,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -519,6 +521,18 @@ sub get_config {
|
||||||
$self->message(TYPE_GET_CONFIG);
|
$self->message(TYPE_GET_CONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
=head2 send_tick
|
||||||
|
|
||||||
|
Sends a tick event. Requires i3 >= 4.15
|
||||||
|
|
||||||
|
=cut
|
||||||
|
sub send_tick {
|
||||||
|
my ($self, $payload) = @_;
|
||||||
|
|
||||||
|
$self->_ensure_connection;
|
||||||
|
|
||||||
|
$self->message(TYPE_SEND_TICK, $payload);
|
||||||
|
}
|
||||||
|
|
||||||
=head2 command($content)
|
=head2 command($content)
|
||||||
|
|
||||||
|
|
41
docs/ipc
41
docs/ipc
|
@ -64,6 +64,7 @@ to do that).
|
||||||
| 7 | +GET_VERSION+ | <<_version_reply,VERSION>> | Gets the i3 version.
|
| 7 | +GET_VERSION+ | <<_version_reply,VERSION>> | Gets the i3 version.
|
||||||
| 8 | +GET_BINDING_MODES+ | <<_binding_modes_reply,BINDING_MODES>> | Gets the names of all currently configured binding modes.
|
| 8 | +GET_BINDING_MODES+ | <<_binding_modes_reply,BINDING_MODES>> | Gets the names of all currently configured binding modes.
|
||||||
| 9 | +GET_CONFIG+ | <<_config_reply,CONFIG>> | Returns the last loaded i3 config.
|
| 9 | +GET_CONFIG+ | <<_config_reply,CONFIG>> | Returns the last loaded i3 config.
|
||||||
|
| 10 | +SEND_TICK+ | <<_tick_reply,TICK>> | Sends a tick event with the specified payload.
|
||||||
|======================================================
|
|======================================================
|
||||||
|
|
||||||
So, a typical message could look like this:
|
So, a typical message could look like this:
|
||||||
|
@ -126,6 +127,8 @@ BINDING_MODES (8)::
|
||||||
Reply to the GET_BINDING_MODES message.
|
Reply to the GET_BINDING_MODES message.
|
||||||
GET_CONFIG (9)::
|
GET_CONFIG (9)::
|
||||||
Reply to the GET_CONFIG message.
|
Reply to the GET_CONFIG message.
|
||||||
|
TICK (10)::
|
||||||
|
Reply to the SEND_TICK message.
|
||||||
|
|
||||||
[[_command_reply]]
|
[[_command_reply]]
|
||||||
=== COMMAND reply
|
=== COMMAND reply
|
||||||
|
@ -637,6 +640,19 @@ which is a string containing the config file as loaded by i3 most recently.
|
||||||
{ "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" }
|
{ "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" }
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
[[_tick_reply]]
|
||||||
|
=== TICK reply
|
||||||
|
|
||||||
|
The reply is a map containing the "success" member. After the reply was
|
||||||
|
received, the tick event has been written to all IPC connections which subscribe
|
||||||
|
to tick events. UNIX sockets are usually buffered, but you can be certain that
|
||||||
|
once you receive the tick event you just triggered, you must have received all
|
||||||
|
events generated prior to the +SEND_TICK+ message (happened-before relation).
|
||||||
|
|
||||||
|
*Example:*
|
||||||
|
-------------------
|
||||||
|
{ "success": true }
|
||||||
|
-------------------
|
||||||
|
|
||||||
== Events
|
== Events
|
||||||
|
|
||||||
|
@ -694,6 +710,10 @@ binding (5)::
|
||||||
mouse
|
mouse
|
||||||
shutdown (6)::
|
shutdown (6)::
|
||||||
Sent when the ipc shuts down because of a restart or exit by user command
|
Sent when the ipc shuts down because of a restart or exit by user command
|
||||||
|
tick (7)::
|
||||||
|
Sent when the ipc client subscribes to the tick event (with +"first":
|
||||||
|
true+) or when any ipc client sends a SEND_TICK message (with +"first":
|
||||||
|
false+).
|
||||||
|
|
||||||
*Example:*
|
*Example:*
|
||||||
--------------------------------------------------------------------
|
--------------------------------------------------------------------
|
||||||
|
@ -866,6 +886,27 @@ because of a user action such as a +restart+ or +exit+ command. The +change
|
||||||
}
|
}
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
=== tick event
|
||||||
|
|
||||||
|
This event is triggered by a subscription to tick events or by a +SEND_TICK+
|
||||||
|
message.
|
||||||
|
|
||||||
|
*Example (upon subscription):*
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
{
|
||||||
|
"first": true,
|
||||||
|
"payload": ""
|
||||||
|
}
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
*Example (upon +SEND_TICK+ with a payload of +arbitrary string+):*
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
{
|
||||||
|
"first": false,
|
||||||
|
"payload": "arbitrary string"
|
||||||
|
}
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
== See also (existing libraries)
|
== See also (existing libraries)
|
||||||
|
|
||||||
[[libraries]]
|
[[libraries]]
|
||||||
|
|
|
@ -207,9 +207,11 @@ int main(int argc, char *argv[]) {
|
||||||
message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION;
|
message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION;
|
||||||
} else if (strcasecmp(optarg, "get_config") == 0) {
|
} else if (strcasecmp(optarg, "get_config") == 0) {
|
||||||
message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
|
message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
|
||||||
|
} else if (strcasecmp(optarg, "send_tick") == 0) {
|
||||||
|
message_type = I3_IPC_MESSAGE_TYPE_SEND_TICK;
|
||||||
} else {
|
} else {
|
||||||
printf("Unknown message type\n");
|
printf("Unknown message type\n");
|
||||||
printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n");
|
printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config, send_tick\n");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
} else if (o == 'q') {
|
} else if (o == 'q') {
|
||||||
|
|
|
@ -9,4 +9,5 @@ ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
|
||||||
ATOM_DO(_NET_SYSTEM_TRAY_COLORS)
|
ATOM_DO(_NET_SYSTEM_TRAY_COLORS)
|
||||||
ATOM_DO(_XEMBED_INFO)
|
ATOM_DO(_XEMBED_INFO)
|
||||||
ATOM_DO(_XEMBED)
|
ATOM_DO(_XEMBED)
|
||||||
|
ATOM_DO(I3_SYNC)
|
||||||
#undef ATOM_DO
|
#undef ATOM_DO
|
||||||
|
|
|
@ -678,8 +678,26 @@ static void configure_trayclients(void) {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static void handle_client_message(xcb_client_message_event_t *event) {
|
static void handle_client_message(xcb_client_message_event_t *event) {
|
||||||
if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
|
if (event->type == atoms[I3_SYNC]) {
|
||||||
event->format == 32) {
|
xcb_window_t window = event->data.data32[0];
|
||||||
|
uint32_t rnd = event->data.data32[1];
|
||||||
|
DLOG("[i3 sync protocol] Forwarding random value %d, X11 window 0x%08x to i3\n", rnd, window);
|
||||||
|
|
||||||
|
void *reply = scalloc(32, 1);
|
||||||
|
xcb_client_message_event_t *ev = reply;
|
||||||
|
|
||||||
|
ev->response_type = XCB_CLIENT_MESSAGE;
|
||||||
|
ev->window = window;
|
||||||
|
ev->type = atoms[I3_SYNC];
|
||||||
|
ev->format = 32;
|
||||||
|
ev->data.data32[0] = window;
|
||||||
|
ev->data.data32[1] = rnd;
|
||||||
|
|
||||||
|
xcb_send_event(conn, false, xcb_root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)ev);
|
||||||
|
xcb_flush(conn);
|
||||||
|
free(reply);
|
||||||
|
} else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
|
||||||
|
event->format == 32) {
|
||||||
DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
|
DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
|
||||||
/* event->data.data32[0] is the timestamp */
|
/* event->data.data32[0] is the timestamp */
|
||||||
uint32_t op = event->data.data32[1];
|
uint32_t op = event->data.data32[1];
|
||||||
|
|
|
@ -60,6 +60,9 @@ typedef struct i3_ipc_header {
|
||||||
/** Request the raw last loaded i3 config. */
|
/** Request the raw last loaded i3 config. */
|
||||||
#define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9
|
#define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9
|
||||||
|
|
||||||
|
/** Send a tick event to all subscribers. */
|
||||||
|
#define I3_IPC_MESSAGE_TYPE_SEND_TICK 10
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Messages from i3 to clients
|
* Messages from i3 to clients
|
||||||
*
|
*
|
||||||
|
@ -74,6 +77,7 @@ typedef struct i3_ipc_header {
|
||||||
#define I3_IPC_REPLY_TYPE_VERSION 7
|
#define I3_IPC_REPLY_TYPE_VERSION 7
|
||||||
#define I3_IPC_REPLY_TYPE_BINDING_MODES 8
|
#define I3_IPC_REPLY_TYPE_BINDING_MODES 8
|
||||||
#define I3_IPC_REPLY_TYPE_CONFIG 9
|
#define I3_IPC_REPLY_TYPE_CONFIG 9
|
||||||
|
#define I3_IPC_REPLY_TYPE_TICK 10
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Events from i3 to clients. Events have the first bit set high.
|
* Events from i3 to clients. Events have the first bit set high.
|
||||||
|
@ -101,3 +105,6 @@ typedef struct i3_ipc_header {
|
||||||
|
|
||||||
/** The shutdown event will be triggered when the ipc shuts down */
|
/** The shutdown event will be triggered when the ipc shuts down */
|
||||||
#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6)
|
#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6)
|
||||||
|
|
||||||
|
/** The tick event will be sent upon a tick IPC message */
|
||||||
|
#define I3_IPC_EVENT_TICK (I3_IPC_EVENT_MASK | 7)
|
||||||
|
|
|
@ -31,6 +31,10 @@ typedef struct ipc_client {
|
||||||
int num_events;
|
int num_events;
|
||||||
char **events;
|
char **events;
|
||||||
|
|
||||||
|
/* For clients which subscribe to the tick event: whether the first tick
|
||||||
|
* event has been sent by i3. */
|
||||||
|
bool first_tick_sent;
|
||||||
|
|
||||||
TAILQ_ENTRY(ipc_client)
|
TAILQ_ENTRY(ipc_client)
|
||||||
clients;
|
clients;
|
||||||
} ipc_client;
|
} ipc_client;
|
||||||
|
|
51
src/ipc.c
51
src/ipc.c
|
@ -1046,8 +1046,9 @@ static int add_subscription(void *extra, const unsigned char *s,
|
||||||
memcpy(client->events[event], s, len);
|
memcpy(client->events[event], s, len);
|
||||||
|
|
||||||
DLOG("client is now subscribed to:\n");
|
DLOG("client is now subscribed to:\n");
|
||||||
for (int i = 0; i < client->num_events; i++)
|
for (int i = 0; i < client->num_events; i++) {
|
||||||
DLOG("event %s\n", client->events[i]);
|
DLOG("event %s\n", client->events[i]);
|
||||||
|
}
|
||||||
DLOG("(done)\n");
|
DLOG("(done)\n");
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -1099,6 +1100,25 @@ IPC_HANDLER(subscribe) {
|
||||||
yajl_free(p);
|
yajl_free(p);
|
||||||
const char *reply = "{\"success\":true}";
|
const char *reply = "{\"success\":true}";
|
||||||
ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
|
ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
|
||||||
|
|
||||||
|
if (client->first_tick_sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_tick = false;
|
||||||
|
for (int i = 0; i < client->num_events; i++) {
|
||||||
|
if (strcmp(client->events[i], "tick") == 0) {
|
||||||
|
is_tick = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!is_tick) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client->first_tick_sent = true;
|
||||||
|
const char *payload = "{\"first\":true,\"payload\":\"\"}";
|
||||||
|
ipc_send_message(client->fd, strlen(payload), I3_IPC_EVENT_TICK, (const uint8_t *)payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1122,9 +1142,35 @@ IPC_HANDLER(get_config) {
|
||||||
y(free);
|
y(free);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sends the tick event from the message payload to subscribers. Establishes a
|
||||||
|
* synchronization point in event-related tests.
|
||||||
|
*/
|
||||||
|
IPC_HANDLER(send_tick) {
|
||||||
|
yajl_gen gen = ygenalloc();
|
||||||
|
|
||||||
|
y(map_open);
|
||||||
|
|
||||||
|
ystr("payload");
|
||||||
|
yajl_gen_string(gen, (unsigned char *)message, message_size);
|
||||||
|
|
||||||
|
y(map_close);
|
||||||
|
|
||||||
|
const unsigned char *payload;
|
||||||
|
ylength length;
|
||||||
|
y(get_buf, &payload, &length);
|
||||||
|
|
||||||
|
ipc_send_event("tick", I3_IPC_EVENT_TICK, (const char *)payload);
|
||||||
|
y(free);
|
||||||
|
|
||||||
|
const char *reply = "{\"success\":true}";
|
||||||
|
ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_TICK, (const uint8_t *)reply);
|
||||||
|
DLOG("Sent tick event\n");
|
||||||
|
}
|
||||||
|
|
||||||
/* The index of each callback function corresponds to the numeric
|
/* The index of each callback function corresponds to the numeric
|
||||||
* value of the message type (see include/i3/ipc.h) */
|
* value of the message type (see include/i3/ipc.h) */
|
||||||
handler_t handlers[10] = {
|
handler_t handlers[11] = {
|
||||||
handle_run_command,
|
handle_run_command,
|
||||||
handle_get_workspaces,
|
handle_get_workspaces,
|
||||||
handle_subscribe,
|
handle_subscribe,
|
||||||
|
@ -1135,6 +1181,7 @@ handler_t handlers[10] = {
|
||||||
handle_get_version,
|
handle_get_version,
|
||||||
handle_get_binding_modes,
|
handle_get_binding_modes,
|
||||||
handle_get_config,
|
handle_get_config,
|
||||||
|
handle_send_tick,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -47,6 +47,8 @@ our @EXPORT = qw(
|
||||||
wait_for_unmap
|
wait_for_unmap
|
||||||
$x
|
$x
|
||||||
kill_all_windows
|
kill_all_windows
|
||||||
|
events_for
|
||||||
|
listen_for_binding
|
||||||
);
|
);
|
||||||
|
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
|
@ -900,6 +902,86 @@ sub kill_all_windows {
|
||||||
cmd '[title=".*"] kill';
|
cmd '[title=".*"] kill';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
=head2 events_for($subscribecb, [ $rettype ], [ $eventcbs ])
|
||||||
|
|
||||||
|
Helper function which returns an array containing all events of type $rettype
|
||||||
|
which were generated by i3 while $subscribecb was running.
|
||||||
|
|
||||||
|
Set $eventcbs to subscribe to multiple event types and/or perform your own event
|
||||||
|
aggregation.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
sub events_for {
|
||||||
|
my ($subscribecb, $rettype, $eventcbs) = @_;
|
||||||
|
|
||||||
|
my @events;
|
||||||
|
$eventcbs //= {};
|
||||||
|
if (defined($rettype)) {
|
||||||
|
$eventcbs->{$rettype} = sub { push @events, shift };
|
||||||
|
}
|
||||||
|
my $subscribed = AnyEvent->condvar;
|
||||||
|
my $flushed = AnyEvent->condvar;
|
||||||
|
$eventcbs->{tick} = sub {
|
||||||
|
my ($event) = @_;
|
||||||
|
if ($event->{first}) {
|
||||||
|
$subscribed->send($event);
|
||||||
|
} else {
|
||||||
|
$flushed->send($event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
my $i3 = i3(get_socket_path(0));
|
||||||
|
$i3->connect->recv;
|
||||||
|
$i3->subscribe($eventcbs)->recv;
|
||||||
|
$subscribed->recv;
|
||||||
|
# Subscription established, run the callback.
|
||||||
|
$subscribecb->();
|
||||||
|
# Now generate a tick event, which we know we’ll receive (and at which point
|
||||||
|
# all other events have been received).
|
||||||
|
my $nonce = int(rand(255)) + 1;
|
||||||
|
$i3->send_tick($nonce);
|
||||||
|
|
||||||
|
my $tick = $flushed->recv;
|
||||||
|
$tester->is_eq($tick->{payload}, $nonce, 'tick nonce received');
|
||||||
|
return @events;
|
||||||
|
}
|
||||||
|
|
||||||
|
=head2 listen_for_binding($cb)
|
||||||
|
|
||||||
|
Helper function to evaluate whether sending KeyPress/KeyRelease events via XTEST
|
||||||
|
triggers an i3 key binding or not. Expects key bindings to be configured in the
|
||||||
|
form “bindsym <binding> nop <binding>”, e.g. “bindsym Mod4+Return nop
|
||||||
|
Mod4+Return”.
|
||||||
|
|
||||||
|
is(listen_for_binding(
|
||||||
|
sub {
|
||||||
|
xtest_key_press(133); # Super_L
|
||||||
|
xtest_key_press(36); # Return
|
||||||
|
xtest_key_release(36); # Return
|
||||||
|
xtest_key_release(133); # Super_L
|
||||||
|
xtest_sync_with_i3;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
'Mod4+Return',
|
||||||
|
'triggered the "Mod4+Return" keybinding');
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub listen_for_binding {
|
||||||
|
my ($cb) = @_;
|
||||||
|
my $triggered = AnyEvent->condvar;
|
||||||
|
my @events = events_for(
|
||||||
|
$cb,
|
||||||
|
'binding');
|
||||||
|
|
||||||
|
$tester->is_eq(scalar @events, 1, 'Received precisely one event');
|
||||||
|
$tester->is_eq($events[0]->{change}, 'run', 'change is "run"');
|
||||||
|
# We look at the command (which is “nop <binding>”) because that is easier
|
||||||
|
# than re-assembling the string representation of $event->{binding}.
|
||||||
|
my $command = $events[0]->{binding}->{command};
|
||||||
|
$command =~ s/^nop //g;
|
||||||
|
return $command;
|
||||||
|
}
|
||||||
|
|
||||||
=head1 AUTHOR
|
=head1 AUTHOR
|
||||||
|
|
||||||
Michael Stapelberg <michael@i3wm.org>
|
Michael Stapelberg <michael@i3wm.org>
|
||||||
|
|
|
@ -14,13 +14,13 @@ use ExtUtils::PkgConfig;
|
||||||
use Exporter ();
|
use Exporter ();
|
||||||
our @EXPORT = qw(
|
our @EXPORT = qw(
|
||||||
inlinec_connect
|
inlinec_connect
|
||||||
|
xtest_sync_with
|
||||||
|
xtest_sync_with_i3
|
||||||
set_xkb_group
|
set_xkb_group
|
||||||
xtest_key_press
|
xtest_key_press
|
||||||
xtest_key_release
|
xtest_key_release
|
||||||
xtest_button_press
|
xtest_button_press
|
||||||
xtest_button_release
|
xtest_button_release
|
||||||
listen_for_binding
|
|
||||||
start_binding_capture
|
|
||||||
binding_events
|
binding_events
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb
|
||||||
# ineffective.
|
# ineffective.
|
||||||
my %sn_config;
|
my %sn_config;
|
||||||
BEGIN {
|
BEGIN {
|
||||||
%sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest');
|
%sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest xcb-util');
|
||||||
}
|
}
|
||||||
|
|
||||||
use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
|
use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
|
||||||
|
@ -53,8 +53,12 @@ use Inline C => <<'END_OF_C_CODE';
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
#include <xcb/xkb.h>
|
#include <xcb/xkb.h>
|
||||||
#include <xcb/xtest.h>
|
#include <xcb/xtest.h>
|
||||||
|
#include <xcb/xcb_aux.h>
|
||||||
|
|
||||||
static xcb_connection_t *conn = NULL;
|
static xcb_connection_t *conn = NULL;
|
||||||
|
static xcb_window_t sync_window;
|
||||||
|
static xcb_window_t root_window;
|
||||||
|
static xcb_atom_t i3_sync_atom;
|
||||||
|
|
||||||
bool inlinec_connect() {
|
bool inlinec_connect() {
|
||||||
int screen;
|
int screen;
|
||||||
|
@ -89,9 +93,94 @@ bool inlinec_connect() {
|
||||||
}
|
}
|
||||||
free(usereply);
|
free(usereply);
|
||||||
|
|
||||||
|
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, xcb_intern_atom(conn, 0, strlen("I3_SYNC"), "I3_SYNC"), NULL);
|
||||||
|
i3_sync_atom = reply->atom;
|
||||||
|
free(reply);
|
||||||
|
|
||||||
|
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
|
||||||
|
root_window = root_screen->root;
|
||||||
|
sync_window = xcb_generate_id(conn);
|
||||||
|
xcb_create_window(conn,
|
||||||
|
XCB_COPY_FROM_PARENT, // depth
|
||||||
|
sync_window, // window
|
||||||
|
root_window, // parent
|
||||||
|
-15, // x
|
||||||
|
-15, // y
|
||||||
|
1, // width
|
||||||
|
1, // height
|
||||||
|
0, // border_width
|
||||||
|
XCB_WINDOW_CLASS_INPUT_OUTPUT, // class
|
||||||
|
XCB_COPY_FROM_PARENT, // visual
|
||||||
|
XCB_CW_OVERRIDE_REDIRECT, // value_mask
|
||||||
|
(uint32_t[]){
|
||||||
|
1, // override_redirect
|
||||||
|
}); // value_list
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void xtest_sync_with(int window) {
|
||||||
|
xcb_client_message_event_t ev;
|
||||||
|
memset(&ev, '\0', sizeof(xcb_client_message_event_t));
|
||||||
|
|
||||||
|
const int nonce = rand() % 255;
|
||||||
|
|
||||||
|
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||||
|
ev.window = sync_window;
|
||||||
|
ev.type = i3_sync_atom;
|
||||||
|
ev.format = 32;
|
||||||
|
ev.data.data32[0] = sync_window;
|
||||||
|
ev.data.data32[1] = nonce;
|
||||||
|
|
||||||
|
xcb_send_event(conn, false, (xcb_window_t)window, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev);
|
||||||
|
xcb_flush(conn);
|
||||||
|
|
||||||
|
xcb_generic_event_t *event = NULL;
|
||||||
|
while (1) {
|
||||||
|
free(event);
|
||||||
|
if ((event = xcb_wait_for_event(conn)) == NULL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (event->response_type == 0) {
|
||||||
|
fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Strip off the highest bit (set if the event is generated) */
|
||||||
|
const int type = (event->response_type & 0x7F);
|
||||||
|
switch (type) {
|
||||||
|
case XCB_CLIENT_MESSAGE: {
|
||||||
|
xcb_client_message_event_t *ev = (xcb_client_message_event_t *)event;
|
||||||
|
{
|
||||||
|
const uint32_t got = ev->data.data32[0];
|
||||||
|
const uint32_t want = sync_window;
|
||||||
|
if (got != want) {
|
||||||
|
fprintf(stderr, "Ignoring ClientMessage: unknown window: got %d, want %d\n", got, want);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const uint32_t got = ev->data.data32[1];
|
||||||
|
const uint32_t want = nonce;
|
||||||
|
if (got != want) {
|
||||||
|
fprintf(stderr, "Ignoring ClientMessage: unknown nonce: got %d, want %d\n", got, want);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Unexpected X11 event of type %d received (XCB_CLIENT_MESSAGE = %d)\n", type, XCB_CLIENT_MESSAGE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void xtest_sync_with_i3() {
|
||||||
|
xtest_sync_with((int)root_window);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: while |group| should be a uint8_t, Inline::C will not define the
|
// NOTE: while |group| should be a uint8_t, Inline::C will not define the
|
||||||
// function unless we use an int.
|
// function unless we use an int.
|
||||||
bool set_xkb_group(int group) {
|
bool set_xkb_group(int group) {
|
||||||
|
@ -170,86 +259,6 @@ sub import {
|
||||||
|
|
||||||
=cut
|
=cut
|
||||||
|
|
||||||
my $i3;
|
|
||||||
our @binding_events;
|
|
||||||
|
|
||||||
=head2 start_binding_capture()
|
|
||||||
|
|
||||||
Captures all binding events sent by i3 in the C<@binding_events> symbol, so
|
|
||||||
that you can verify the correct number of binding events was generated.
|
|
||||||
|
|
||||||
my $pid = launch_with_config($config);
|
|
||||||
start_binding_capture;
|
|
||||||
# …
|
|
||||||
sync_with_i3;
|
|
||||||
is(scalar @i3test::XTEST::binding_events, 2, 'Received exactly 2 binding events');
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub start_binding_capture {
|
|
||||||
# Store a copy of each binding event so that we can count the expected
|
|
||||||
# events in test cases.
|
|
||||||
$i3 = i3(get_socket_path());
|
|
||||||
$i3->connect()->recv;
|
|
||||||
$i3->subscribe({
|
|
||||||
binding => sub {
|
|
||||||
my ($event) = @_;
|
|
||||||
@binding_events = (@binding_events, $event);
|
|
||||||
},
|
|
||||||
})->recv;
|
|
||||||
}
|
|
||||||
|
|
||||||
=head2 listen_for_binding($cb)
|
|
||||||
|
|
||||||
Helper function to evaluate whether sending KeyPress/KeyRelease events via
|
|
||||||
XTEST triggers an i3 key binding or not (with a timeout of 0.5s). Expects key
|
|
||||||
bindings to be configured in the form “bindsym <binding> nop <binding>”, e.g.
|
|
||||||
“bindsym Mod4+Return nop Mod4+Return”.
|
|
||||||
|
|
||||||
is(listen_for_binding(
|
|
||||||
sub {
|
|
||||||
xtest_key_press(133); # Super_L
|
|
||||||
xtest_key_press(36); # Return
|
|
||||||
xtest_key_release(36); # Return
|
|
||||||
xtest_key_release(133); # Super_L
|
|
||||||
},
|
|
||||||
),
|
|
||||||
'Mod4+Return',
|
|
||||||
'triggered the "Mod4+Return" keybinding');
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub listen_for_binding {
|
|
||||||
my ($cb) = @_;
|
|
||||||
my $triggered = AnyEvent->condvar;
|
|
||||||
my $i3 = i3(get_socket_path());
|
|
||||||
$i3->connect()->recv;
|
|
||||||
$i3->subscribe({
|
|
||||||
binding => sub {
|
|
||||||
my ($event) = @_;
|
|
||||||
return unless $event->{change} eq 'run';
|
|
||||||
# We look at the command (which is “nop <binding>”) because that is
|
|
||||||
# easier than re-assembling the string representation of
|
|
||||||
# $event->{binding}.
|
|
||||||
$triggered->send($event->{binding}->{command});
|
|
||||||
},
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
my $t;
|
|
||||||
$t = AnyEvent->timer(
|
|
||||||
after => 0.5,
|
|
||||||
cb => sub {
|
|
||||||
$triggered->send('timeout');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
$cb->();
|
|
||||||
|
|
||||||
my $recv = $triggered->recv;
|
|
||||||
$recv =~ s/^nop //g;
|
|
||||||
return $recv;
|
|
||||||
}
|
|
||||||
|
|
||||||
=head2 set_xkb_group($group)
|
=head2 set_xkb_group($group)
|
||||||
|
|
||||||
Changes the current XKB group from the default of 1 to C<$group>, which must be
|
Changes the current XKB group from the default of 1 to C<$group>, which must be
|
||||||
|
@ -283,6 +292,15 @@ Sends a ButtonRelease event via XTEST, with the specified C<$button>.
|
||||||
|
|
||||||
Returns false when there was an X11 error, true otherwise.
|
Returns false when there was an X11 error, true otherwise.
|
||||||
|
|
||||||
|
=head2 xtest_sync_with($window)
|
||||||
|
|
||||||
|
Ensures the specified window has processed all X11 events which were triggered
|
||||||
|
by this module, provided the window response to the i3 sync protocol.
|
||||||
|
|
||||||
|
=head2 xtest_sync_with_i3()
|
||||||
|
|
||||||
|
Ensures i3 has processed all X11 events which were triggered by this module.
|
||||||
|
|
||||||
=head1 AUTHOR
|
=head1 AUTHOR
|
||||||
|
|
||||||
Michael Stapelberg <michael@i3wm.org>
|
Michael Stapelberg <michael@i3wm.org>
|
||||||
|
|
|
@ -16,61 +16,25 @@
|
||||||
|
|
||||||
use i3test;
|
use i3test;
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
|
||||||
$i3->connect()->recv;
|
|
||||||
|
|
||||||
################################
|
|
||||||
# Workspaces requests and events
|
|
||||||
################################
|
|
||||||
|
|
||||||
my $old_ws = get_ws(focused_ws());
|
my $old_ws = get_ws(focused_ws());
|
||||||
|
|
||||||
# Events
|
|
||||||
|
|
||||||
# We are switching to an empty workpspace from an empty workspace, so we expect
|
# We are switching to an empty workpspace from an empty workspace, so we expect
|
||||||
# to receive "init", "focus", and "empty".
|
# to receive "init", "focus", and "empty".
|
||||||
my $init = AnyEvent->condvar;
|
my @events = events_for(
|
||||||
my $focus = AnyEvent->condvar;
|
sub { cmd 'workspace 2' },
|
||||||
my $empty = AnyEvent->condvar;
|
'workspace');
|
||||||
$i3->subscribe({
|
|
||||||
workspace => sub {
|
|
||||||
my ($event) = @_;
|
|
||||||
if ($event->{change} eq 'init') {
|
|
||||||
$init->send($event);
|
|
||||||
} elsif ($event->{change} eq 'focus') {
|
|
||||||
$focus->send($event);
|
|
||||||
} elsif ($event->{change} eq 'empty') {
|
|
||||||
$empty->send($event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
cmd 'workspace 2';
|
|
||||||
|
|
||||||
my $t;
|
|
||||||
$t = AnyEvent->timer(
|
|
||||||
after => 0.5,
|
|
||||||
cb => sub {
|
|
||||||
$init->send(0);
|
|
||||||
$focus->send(0);
|
|
||||||
$empty->send(0);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
my $init_event = $init->recv;
|
|
||||||
my $focus_event = $focus->recv;
|
|
||||||
my $empty_event = $empty->recv;
|
|
||||||
|
|
||||||
my $current_ws = get_ws(focused_ws());
|
my $current_ws = get_ws(focused_ws());
|
||||||
|
|
||||||
ok($init_event, 'workspace "init" event received');
|
is(scalar @events, 3, 'Received 3 events');
|
||||||
is($init_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the initted workspace con');
|
is($events[0]->{change}, 'init', 'First event has change = init');
|
||||||
|
is($events[0]->{current}->{id}, $current_ws->{id}, 'the "current" property contains the initted workspace con');
|
||||||
|
|
||||||
ok($focus_event, 'workspace "focus" event received');
|
is($events[1]->{change}, 'focus', 'Second event has change = focus');
|
||||||
is($focus_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con');
|
is($events[1]->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con');
|
||||||
is($focus_event->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last');
|
is($events[1]->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last');
|
||||||
|
|
||||||
ok($empty_event, 'workspace "empty" event received');
|
is($events[2]->{change}, 'empty', 'Third event has change = empty');
|
||||||
is($empty_event->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con');
|
is($events[2]->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con');
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -28,24 +28,11 @@ mode "with spaces" {
|
||||||
}
|
}
|
||||||
EOT
|
EOT
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path(0));
|
my @events = events_for(
|
||||||
$i3->connect->recv;
|
sub { cmd 'mode "m1"' },
|
||||||
|
'mode');
|
||||||
|
|
||||||
my $cv = AnyEvent->condvar;
|
my @changes = map { $_->{change} } @events;
|
||||||
|
is_deeply(\@changes, [ 'm1' ], 'Mode event received');
|
||||||
$i3->subscribe({
|
|
||||||
mode => sub {
|
|
||||||
my ($event) = @_;
|
|
||||||
$cv->send($event->{change} eq 'm1');
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
cmd 'mode "m1"';
|
|
||||||
|
|
||||||
# Timeout after 0.5s
|
|
||||||
my $t;
|
|
||||||
$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); });
|
|
||||||
|
|
||||||
ok($cv->recv, 'Mode event received');
|
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -16,46 +16,15 @@
|
||||||
|
|
||||||
use i3test;
|
use i3test;
|
||||||
|
|
||||||
SKIP: {
|
|
||||||
|
|
||||||
skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
|
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
|
||||||
$i3->connect()->recv;
|
|
||||||
|
|
||||||
################################
|
|
||||||
# Window event
|
|
||||||
################################
|
|
||||||
|
|
||||||
# Events
|
|
||||||
|
|
||||||
my $new = AnyEvent->condvar;
|
my $new = AnyEvent->condvar;
|
||||||
my $focus = AnyEvent->condvar;
|
my $focus = AnyEvent->condvar;
|
||||||
$i3->subscribe({
|
|
||||||
window => sub {
|
|
||||||
my ($event) = @_;
|
|
||||||
if ($event->{change} eq 'new') {
|
|
||||||
$new->send($event);
|
|
||||||
} elsif ($event->{change} eq 'focus') {
|
|
||||||
$focus->send($event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
open_window;
|
my @events = events_for(
|
||||||
|
sub { open_window },
|
||||||
|
'window');
|
||||||
|
|
||||||
my $t;
|
is(scalar @events, 2, 'Received 2 events');
|
||||||
$t = AnyEvent->timer(
|
is($events[0]->{container}->{focused}, 0, 'Window "new" event received');
|
||||||
after => 0.5,
|
is($events[1]->{container}->{focused}, 1, 'Window "focus" event received');
|
||||||
cb => sub {
|
|
||||||
$new->send(0);
|
|
||||||
$focus->send(0);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
is($new->recv->{container}->{focused}, 0, 'Window "new" event received');
|
|
||||||
is($focus->recv->{container}->{focused}, 1, 'Window "focus" event received');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -16,13 +16,6 @@
|
||||||
|
|
||||||
use i3test;
|
use i3test;
|
||||||
|
|
||||||
SKIP: {
|
|
||||||
|
|
||||||
skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
|
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
|
||||||
$i3->connect()->recv;
|
|
||||||
|
|
||||||
################################
|
################################
|
||||||
# Window focus event
|
# Window focus event
|
||||||
################################
|
################################
|
||||||
|
@ -33,62 +26,29 @@ my $win0 = open_window;
|
||||||
my $win1 = open_window;
|
my $win1 = open_window;
|
||||||
my $win2 = open_window;
|
my $win2 = open_window;
|
||||||
|
|
||||||
my $focus = AnyEvent->condvar;
|
|
||||||
|
|
||||||
$i3->subscribe({
|
|
||||||
window => sub {
|
|
||||||
my ($event) = @_;
|
|
||||||
$focus->send($event);
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
my $t;
|
|
||||||
$t = AnyEvent->timer(
|
|
||||||
after => 0.5,
|
|
||||||
cb => sub {
|
|
||||||
$focus->send(0);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
# ensure the rightmost window contains input focus
|
# ensure the rightmost window contains input focus
|
||||||
$i3->command('[id="' . $win2->id . '"] focus')->recv;
|
cmd '[id="' . $win2->id . '"] focus';
|
||||||
is($x->input_focus, $win2->id, "Window 2 focused");
|
is($x->input_focus, $win2->id, "Window 2 focused");
|
||||||
|
|
||||||
cmd 'focus left';
|
sub focus_subtest {
|
||||||
my $event = $focus->recv;
|
my ($cmd, $name) = @_;
|
||||||
is($event->{change}, 'focus', 'Focus event received');
|
|
||||||
is($focus->recv->{container}->{name}, 'Window 1', 'Window 1 focused');
|
|
||||||
|
|
||||||
$focus = AnyEvent->condvar;
|
my $focus = AnyEvent->condvar;
|
||||||
cmd 'focus left';
|
|
||||||
$event = $focus->recv;
|
|
||||||
is($event->{change}, 'focus', 'Focus event received');
|
|
||||||
is($event->{container}->{name}, 'Window 0', 'Window 0 focused');
|
|
||||||
|
|
||||||
$focus = AnyEvent->condvar;
|
my @events = events_for(
|
||||||
cmd 'focus right';
|
sub { cmd $cmd },
|
||||||
$event = $focus->recv;
|
'window');
|
||||||
is($event->{change}, 'focus', 'Focus event received');
|
|
||||||
is($event->{container}->{name}, 'Window 1', 'Window 1 focused');
|
|
||||||
|
|
||||||
$focus = AnyEvent->condvar;
|
|
||||||
cmd 'focus right';
|
|
||||||
$event = $focus->recv;
|
|
||||||
is($event->{change}, 'focus', 'Focus event received');
|
|
||||||
is($event->{container}->{name}, 'Window 2', 'Window 2 focused');
|
|
||||||
|
|
||||||
$focus = AnyEvent->condvar;
|
|
||||||
cmd 'focus right';
|
|
||||||
$event = $focus->recv;
|
|
||||||
is($event->{change}, 'focus', 'Focus event received');
|
|
||||||
is($event->{container}->{name}, 'Window 0', 'Window 0 focused');
|
|
||||||
|
|
||||||
$focus = AnyEvent->condvar;
|
|
||||||
cmd 'focus left';
|
|
||||||
$event = $focus->recv;
|
|
||||||
is($event->{change}, 'focus', 'Focus event received');
|
|
||||||
is($event->{container}->{name}, 'Window 2', 'Window 2 focused');
|
|
||||||
|
|
||||||
|
is(scalar @events, 1, 'Received 1 event');
|
||||||
|
is($events[0]->{change}, 'focus', 'Focus event received');
|
||||||
|
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';
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -16,42 +16,17 @@
|
||||||
|
|
||||||
use i3test;
|
use i3test;
|
||||||
|
|
||||||
SKIP: {
|
|
||||||
|
|
||||||
skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
|
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
|
||||||
$i3->connect()->recv;
|
|
||||||
|
|
||||||
################################
|
|
||||||
# Window title event
|
|
||||||
################################
|
|
||||||
|
|
||||||
my $window = open_window(name => 'Window 0');
|
my $window = open_window(name => 'Window 0');
|
||||||
|
|
||||||
my $title = AnyEvent->condvar;
|
my @events = events_for(
|
||||||
|
sub {
|
||||||
|
$window->name('New Window Title');
|
||||||
|
sync_with_i3;
|
||||||
|
},
|
||||||
|
'window');
|
||||||
|
|
||||||
$i3->subscribe({
|
is(scalar @events, 1, 'Received 1 event');
|
||||||
window => sub {
|
is($events[0]->{change}, 'title', 'Window title change event received');
|
||||||
my ($event) = @_;
|
is($events[0]->{container}->{name}, 'New Window Title', 'Window title changed');
|
||||||
$title->send($event);
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
$window->name('New Window Title');
|
|
||||||
|
|
||||||
my $t;
|
|
||||||
$t = AnyEvent->timer(
|
|
||||||
after => 0.5,
|
|
||||||
cb => sub {
|
|
||||||
$title->send(0);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
my $event = $title->recv;
|
|
||||||
is($event->{change}, 'title', 'Window title change event received');
|
|
||||||
is($event->{container}->{name}, 'New Window Title', 'Window title changed');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -19,41 +19,19 @@
|
||||||
# Bug still in: 4.7.2-135-g7deb23c
|
# Bug still in: 4.7.2-135-g7deb23c
|
||||||
use i3test;
|
use i3test;
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
open_window;
|
||||||
$i3->connect()->recv;
|
|
||||||
|
|
||||||
my $cv;
|
sub fullscreen_subtest {
|
||||||
my $t;
|
my ($want) = @_;
|
||||||
|
my @events = events_for(
|
||||||
|
sub { cmd 'fullscreen' },
|
||||||
|
'window');
|
||||||
|
|
||||||
sub reset_test {
|
is(scalar @events, 1, 'Received 1 event');
|
||||||
$cv = AE::cv;
|
is($events[0]->{container}->{fullscreen_mode}, $want, "fullscreen_mode now $want");
|
||||||
$t = AE::timer(0.5, 0, sub { $cv->send(0); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_test;
|
subtest 'fullscreen on', \&fullscreen_subtest, 1;
|
||||||
|
subtest 'fullscreen off', \&fullscreen_subtest, 0;
|
||||||
$i3->subscribe({
|
|
||||||
window => sub {
|
|
||||||
my ($e) = @_;
|
|
||||||
if ($e->{change} eq 'fullscreen_mode') {
|
|
||||||
$cv->send($e->{container});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
my $window = open_window;
|
|
||||||
|
|
||||||
cmd 'fullscreen';
|
|
||||||
my $con = $cv->recv;
|
|
||||||
|
|
||||||
ok($con, 'got fullscreen window event (on)');
|
|
||||||
is($con->{fullscreen_mode}, 1, 'window is fullscreen');
|
|
||||||
|
|
||||||
reset_test;
|
|
||||||
cmd 'fullscreen';
|
|
||||||
$con = $cv->recv;
|
|
||||||
|
|
||||||
ok($con, 'got fullscreen window event (off)');
|
|
||||||
is($con->{fullscreen_mode}, 0, 'window is not fullscreen');
|
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -18,12 +18,8 @@
|
||||||
#
|
#
|
||||||
use i3test;
|
use i3test;
|
||||||
|
|
||||||
SKIP: {
|
|
||||||
|
|
||||||
skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# check that the workspace empty event is send upon workspace switch when the
|
# check that the workspace empty event is sent upon workspace switch when the
|
||||||
# old workspace is empty
|
# old workspace is empty
|
||||||
################################################################################
|
################################################################################
|
||||||
subtest 'Workspace empty event upon switch', sub {
|
subtest 'Workspace empty event upon switch', sub {
|
||||||
|
@ -35,26 +31,17 @@ subtest 'Workspace empty event upon switch', sub {
|
||||||
cmd '[id="' . $w1->id . '"] kill';
|
cmd '[id="' . $w1->id . '"] kill';
|
||||||
|
|
||||||
my $cond = AnyEvent->condvar;
|
my $cond = AnyEvent->condvar;
|
||||||
my $client = i3(get_socket_path(0));
|
my @events = events_for(
|
||||||
$client->connect()->recv;
|
sub { cmd "workspace $ws2" },
|
||||||
$client->subscribe({
|
'workspace');
|
||||||
workspace => sub {
|
|
||||||
my ($event) = @_;
|
|
||||||
$cond->send($event);
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
cmd "workspace $ws2";
|
is(scalar @events, 2, 'Received 2 event');
|
||||||
|
is($events[1]->{change}, 'empty', '"Empty" event received upon workspace switch');
|
||||||
sync_with_i3;
|
is($events[1]->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
|
||||||
|
|
||||||
my $event = $cond->recv;
|
|
||||||
is($event->{change}, 'empty', '"Empty" event received upon workspace switch');
|
|
||||||
is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# check that no workspace empty event is send upon workspace switch if the
|
# check that no workspace empty event is sent upon workspace switch if the
|
||||||
# workspace is not empty
|
# workspace is not empty
|
||||||
################################################################################
|
################################################################################
|
||||||
subtest 'No workspace empty event', sub {
|
subtest 'No workspace empty event', sub {
|
||||||
|
@ -63,36 +50,16 @@ subtest 'No workspace empty event', sub {
|
||||||
my $ws1 = fresh_workspace;
|
my $ws1 = fresh_workspace;
|
||||||
my $w1 = open_window();
|
my $w1 = open_window();
|
||||||
|
|
||||||
my @events;
|
my @events = events_for(
|
||||||
my $cond = AnyEvent->condvar;
|
sub { cmd "workspace $ws2" },
|
||||||
my $client = i3(get_socket_path(0));
|
'workspace');
|
||||||
$client->connect()->recv;
|
|
||||||
$client->subscribe({
|
|
||||||
workspace => sub {
|
|
||||||
my ($event) = @_;
|
|
||||||
push @events, $event;
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
# Wait for the workspace event on a new connection. Events will be delivered
|
is(scalar @events, 1, 'Received 1 event');
|
||||||
# to older connections earlier, so by the time it arrives here, it should be
|
is($events[0]->{change}, 'focus', 'Event change is "focus"');
|
||||||
# in @events already.
|
|
||||||
my $ws_event_block_conn = i3(get_socket_path(0));
|
|
||||||
$ws_event_block_conn->connect()->recv;
|
|
||||||
$ws_event_block_conn->subscribe({ workspace => sub { $cond->send(1) }});
|
|
||||||
|
|
||||||
cmd "workspace $ws2";
|
|
||||||
|
|
||||||
sync_with_i3;
|
|
||||||
|
|
||||||
my @expected_events = grep { $_->{change} eq 'focus' } @events;
|
|
||||||
my @empty_events = grep { $_->{change} eq 'empty' } @events;
|
|
||||||
is(@expected_events, 1, '"Focus" event received');
|
|
||||||
is(@empty_events, 0, 'No "empty" events received');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# check that workspace empty event is send when the last window has been closed
|
# check that workspace empty event is sent when the last window has been closed
|
||||||
# on invisible workspace
|
# on invisible workspace
|
||||||
################################################################################
|
################################################################################
|
||||||
subtest 'Workspace empty event upon window close', sub {
|
subtest 'Workspace empty event upon window close', sub {
|
||||||
|
@ -101,25 +68,16 @@ subtest 'Workspace empty event upon window close', sub {
|
||||||
my $ws2 = fresh_workspace;
|
my $ws2 = fresh_workspace;
|
||||||
my $w2 = open_window();
|
my $w2 = open_window();
|
||||||
|
|
||||||
my $cond = AnyEvent->condvar;
|
my @events = events_for(
|
||||||
my $client = i3(get_socket_path(0));
|
sub {
|
||||||
$client->connect()->recv;
|
$w1->unmap;
|
||||||
$client->subscribe({
|
sync_with_i3;
|
||||||
workspace => sub {
|
},
|
||||||
my ($event) = @_;
|
'workspace');
|
||||||
$cond->send($event);
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
cmd '[id="' . $w1->id . '"] kill';
|
is(scalar @events, 1, 'Received 1 event');
|
||||||
|
is($events[0]->{change}, 'empty', '"Empty" event received upon window close');
|
||||||
sync_with_i3;
|
is($events[0]->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
|
||||||
|
|
||||||
my $event = $cond->recv;
|
|
||||||
is($event->{change}, 'empty', '"Empty" event received upon window close');
|
|
||||||
is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -19,41 +19,22 @@
|
||||||
# Bug still in: 4.8-7-gf4a8253
|
# Bug still in: 4.8-7-gf4a8253
|
||||||
use i3test;
|
use i3test;
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
sub floating_subtest {
|
||||||
$i3->connect->recv;
|
my ($win, $cmd, $want) = @_;
|
||||||
|
|
||||||
my $cv = AnyEvent->condvar;
|
my @events = events_for(
|
||||||
|
sub { cmd $cmd },
|
||||||
|
'window');
|
||||||
|
|
||||||
$i3->subscribe({
|
my @floating = grep { $_->{change} eq 'floating' } @events;
|
||||||
window => sub {
|
is(scalar @floating, 1, 'Received 1 floating event');
|
||||||
my ($event) = @_;
|
is($floating[0]->{container}->{window}, $win->{id}, "window id matches");
|
||||||
$cv->send($event) if $event->{change} eq 'floating';
|
is($floating[0]->{container}->{floating}, $want, "floating is $want");
|
||||||
}
|
}
|
||||||
})->recv;
|
|
||||||
|
|
||||||
my $t;
|
|
||||||
$t = AnyEvent->timer(
|
|
||||||
after => 0.5,
|
|
||||||
cb => sub {
|
|
||||||
$cv->send(0);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
my $win = open_window();
|
my $win = open_window();
|
||||||
|
|
||||||
cmd '[id="' . $win->{id} . '"] floating enable';
|
subtest 'floating enable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating enable', 'user_on';
|
||||||
my $e = $cv->recv;
|
subtest 'floating disable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating disable', 'user_off';
|
||||||
|
|
||||||
isnt($e, 0, 'floating a container should send an ipc window event');
|
|
||||||
is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window');
|
|
||||||
is($e->{container}->{floating}, 'user_on', 'the container should be floating');
|
|
||||||
|
|
||||||
$cv = AnyEvent->condvar;
|
|
||||||
cmd '[id="' . $win->{id} . '"] floating disable';
|
|
||||||
$e = $cv->recv;
|
|
||||||
|
|
||||||
isnt($e, 0, 'disabling floating on a container should send an ipc window event');
|
|
||||||
is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window');
|
|
||||||
is($e->{container}->{floating}, 'user_off', 'the container should not be floating');
|
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -35,51 +35,38 @@ SKIP: {
|
||||||
|
|
||||||
skip 'xdotool is required to test the binding event. `[apt-get install|pacman -S] xdotool`', 1 if $?;
|
skip 'xdotool is required to test the binding event. `[apt-get install|pacman -S] xdotool`', 1 if $?;
|
||||||
|
|
||||||
skip "AnyEvent::I3 too old (need >= 0.16)", 1 if $AnyEvent::I3::VERSION < 0.16;
|
|
||||||
|
|
||||||
my $pid = launch_with_config($config);
|
my $pid = launch_with_config($config);
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
my $cv = AnyEvent->condvar;
|
||||||
$i3->connect->recv;
|
|
||||||
|
|
||||||
my $cv = AE::cv;
|
my @events = events_for(
|
||||||
my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
|
sub {
|
||||||
|
# TODO: this is still flaky: we need to synchronize every X11
|
||||||
|
# connection with i3. Move to XTEST and synchronize that connection.
|
||||||
|
qx(xdotool key $binding_symbol);
|
||||||
|
},
|
||||||
|
'binding');
|
||||||
|
|
||||||
$i3->subscribe({
|
is(scalar @events, 1, 'Received 1 event');
|
||||||
binding => sub {
|
|
||||||
$cv->send(shift);
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
qx(xdotool key $binding_symbol);
|
is($events[0]->{change}, 'run',
|
||||||
|
|
||||||
my $e = $cv->recv;
|
|
||||||
|
|
||||||
does_i3_live;
|
|
||||||
|
|
||||||
diag "Event:\n", Dumper($e);
|
|
||||||
|
|
||||||
ok($e,
|
|
||||||
'the binding event should emit when user input triggers an i3 binding event');
|
|
||||||
|
|
||||||
is($e->{change}, 'run',
|
|
||||||
'the `change` field should indicate this binding has run');
|
'the `change` field should indicate this binding has run');
|
||||||
|
|
||||||
ok($e->{binding},
|
ok($events[0]->{binding},
|
||||||
'the `binding` field should be a hash that contains information about the binding');
|
'the `binding` field should be a hash that contains information about the binding');
|
||||||
|
|
||||||
is($e->{binding}->{input_type}, 'keyboard',
|
is($events[0]->{binding}->{input_type}, 'keyboard',
|
||||||
'the input_type field should be the input type of the binding (keyboard or mouse)');
|
'the input_type field should be the input type of the binding (keyboard or mouse)');
|
||||||
|
|
||||||
note 'the `mods` field should contain the symbols for the modifiers of the binding';
|
note 'the `mods` field should contain the symbols for the modifiers of the binding';
|
||||||
foreach (@mods) {
|
foreach (@mods) {
|
||||||
ok(grep(/$_/i, @{$e->{binding}->{mods}}), "`mods` contains the modifier $_");
|
ok(grep(/$_/i, @{$events[0]->{binding}->{mods}}), "`mods` contains the modifier $_");
|
||||||
}
|
}
|
||||||
|
|
||||||
is($e->{binding}->{command}, $command,
|
is($events[0]->{binding}->{command}, $command,
|
||||||
'the `command` field should contain the command the binding ran');
|
'the `command` field should contain the command the binding ran');
|
||||||
|
|
||||||
is($e->{binding}->{input_code}, 0,
|
is($events[0]->{binding}->{input_code}, 0,
|
||||||
'the input_code should be the specified code if the key was bound with bindcode, and otherwise zero');
|
'the input_code should be the specified code if the key was bound with bindcode, and otherwise zero');
|
||||||
|
|
||||||
exit_gracefully($pid);
|
exit_gracefully($pid);
|
||||||
|
|
|
@ -36,14 +36,13 @@ SKIP: {
|
||||||
skip "setxkbmap not found", 1 if
|
skip "setxkbmap not found", 1 if
|
||||||
system(q|setxkbmap -print >/dev/null|) != 0;
|
system(q|setxkbmap -print >/dev/null|) != 0;
|
||||||
|
|
||||||
start_binding_capture;
|
|
||||||
|
|
||||||
system(q|setxkbmap us,ru -option grp:alt_shift_toggle|);
|
system(q|setxkbmap us,ru -option grp:alt_shift_toggle|);
|
||||||
|
|
||||||
is(listen_for_binding(
|
is(listen_for_binding(
|
||||||
sub {
|
sub {
|
||||||
xtest_key_press(107);
|
xtest_key_press(107);
|
||||||
xtest_key_release(107);
|
xtest_key_release(107);
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Print',
|
'Print',
|
||||||
|
@ -55,6 +54,7 @@ is(listen_for_binding(
|
||||||
xtest_key_press(36); # Return
|
xtest_key_press(36); # Return
|
||||||
xtest_key_release(36); # Return
|
xtest_key_release(36); # Return
|
||||||
xtest_key_release(133); # Super_L
|
xtest_key_release(133); # Super_L
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Mod4+Return',
|
'Mod4+Return',
|
||||||
|
@ -67,6 +67,7 @@ is(listen_for_binding(
|
||||||
sub {
|
sub {
|
||||||
xtest_key_press(107);
|
xtest_key_press(107);
|
||||||
xtest_key_release(107);
|
xtest_key_release(107);
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Print',
|
'Print',
|
||||||
|
@ -78,14 +79,12 @@ is(listen_for_binding(
|
||||||
xtest_key_press(36); # Return
|
xtest_key_press(36); # Return
|
||||||
xtest_key_release(36); # Return
|
xtest_key_release(36); # Return
|
||||||
xtest_key_release(133); # Super_L
|
xtest_key_release(133); # Super_L
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Mod4+Return',
|
'Mod4+Return',
|
||||||
'triggered the "Mod4+Return" keybinding');
|
'triggered the "Mod4+Return" keybinding');
|
||||||
|
|
||||||
sync_with_i3;
|
|
||||||
is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events');
|
|
||||||
|
|
||||||
# Disable the grp:alt_shift_toggle option, as we use Alt+Shift in other testcases.
|
# Disable the grp:alt_shift_toggle option, as we use Alt+Shift in other testcases.
|
||||||
system(q|setxkbmap us -option|);
|
system(q|setxkbmap us -option|);
|
||||||
|
|
||||||
|
|
|
@ -37,12 +37,11 @@ SKIP: {
|
||||||
skip "libxcb-xkb too old (need >= 1.11)", 1 unless
|
skip "libxcb-xkb too old (need >= 1.11)", 1 unless
|
||||||
ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11');
|
ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11');
|
||||||
|
|
||||||
start_binding_capture;
|
|
||||||
|
|
||||||
is(listen_for_binding(
|
is(listen_for_binding(
|
||||||
sub {
|
sub {
|
||||||
xtest_key_press(107); # Print
|
xtest_key_press(107); # Print
|
||||||
xtest_key_release(107); # Print
|
xtest_key_release(107); # Print
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Print',
|
'Print',
|
||||||
|
@ -54,6 +53,7 @@ is(listen_for_binding(
|
||||||
xtest_key_press(107); # Print
|
xtest_key_press(107); # Print
|
||||||
xtest_key_release(107); # Print
|
xtest_key_release(107); # Print
|
||||||
xtest_key_release(37); # Control_L
|
xtest_key_release(37); # Control_L
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Control+Print',
|
'Control+Print',
|
||||||
|
@ -65,6 +65,7 @@ is(listen_for_binding(
|
||||||
xtest_key_press(56); # b
|
xtest_key_press(56); # b
|
||||||
xtest_key_release(56); # b
|
xtest_key_release(56); # b
|
||||||
xtest_key_release(64); # Alt_L
|
xtest_key_release(64); # Alt_L
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Mod1+b',
|
'Mod1+b',
|
||||||
|
@ -78,14 +79,12 @@ is(listen_for_binding(
|
||||||
xtest_key_release(56); # b
|
xtest_key_release(56); # b
|
||||||
xtest_key_release(50); # Shift_L
|
xtest_key_release(50); # Shift_L
|
||||||
xtest_key_release(64); # Alt_L
|
xtest_key_release(64); # Alt_L
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Mod1+Shift+b release',
|
'Mod1+Shift+b release',
|
||||||
'triggered the "Mod1+Shift+b" release keybinding');
|
'triggered the "Mod1+Shift+b" release keybinding');
|
||||||
|
|
||||||
sync_with_i3;
|
|
||||||
is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events');
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -28,23 +28,11 @@ EOT
|
||||||
|
|
||||||
cmd 'mode othermode';
|
cmd 'mode othermode';
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path(0));
|
my @events = events_for(
|
||||||
$i3->connect->recv;
|
sub { cmd 'reload' },
|
||||||
|
'mode');
|
||||||
|
|
||||||
my $cv = AnyEvent->condvar;
|
is(scalar @events, 1, 'Received 1 event');
|
||||||
$i3->subscribe({
|
is($events[0]->{change}, 'default', 'change is "default"');
|
||||||
mode => sub {
|
|
||||||
my ($event) = @_;
|
|
||||||
$cv->send($event->{change} eq 'default');
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
cmd 'reload';
|
|
||||||
|
|
||||||
# Timeout after 0.5s
|
|
||||||
my $t;
|
|
||||||
$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); });
|
|
||||||
|
|
||||||
ok($cv->recv, 'Mode event received');
|
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -18,27 +18,16 @@
|
||||||
# Ticket: #2501
|
# Ticket: #2501
|
||||||
use i3test;
|
use i3test;
|
||||||
|
|
||||||
my ($i3, $timer, $event, $mark);
|
sub mark_subtest {
|
||||||
|
my ($cmd) = @_;
|
||||||
|
|
||||||
$i3 = i3(get_socket_path());
|
my @events = events_for(
|
||||||
$i3->connect()->recv;
|
sub { cmd $cmd },
|
||||||
|
'window');
|
||||||
|
|
||||||
$i3->subscribe({
|
my @mark = grep { $_->{change} eq 'mark' } @events;
|
||||||
window => sub {
|
is(scalar @mark, 1, 'Received 1 window::mark event');
|
||||||
my ($event) = @_;
|
}
|
||||||
return unless defined $mark;
|
|
||||||
return unless $event->{change} eq 'mark';
|
|
||||||
|
|
||||||
$mark->send($event);
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
$timer = AnyEvent->timer(
|
|
||||||
after => 0.5,
|
|
||||||
cb => sub {
|
|
||||||
$mark->send(0);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Marking a container triggers a 'mark' event.
|
# Marking a container triggers a 'mark' event.
|
||||||
|
@ -46,11 +35,7 @@ $timer = AnyEvent->timer(
|
||||||
fresh_workspace;
|
fresh_workspace;
|
||||||
open_window;
|
open_window;
|
||||||
|
|
||||||
$mark = AnyEvent->condvar;
|
subtest 'mark', \&mark_subtest, 'mark x';
|
||||||
cmd 'mark x';
|
|
||||||
|
|
||||||
$event = $mark->recv;
|
|
||||||
ok($event, 'window::mark event has been received');
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Unmarking a container triggers a 'mark' event.
|
# Unmarking a container triggers a 'mark' event.
|
||||||
|
@ -59,11 +44,7 @@ fresh_workspace;
|
||||||
open_window;
|
open_window;
|
||||||
cmd 'mark x';
|
cmd 'mark x';
|
||||||
|
|
||||||
$mark = AnyEvent->condvar;
|
subtest 'unmark', \&mark_subtest, 'unmark x';
|
||||||
cmd 'unmark x';
|
|
||||||
|
|
||||||
$event = $mark->recv;
|
|
||||||
ok($event, 'window::mark event has been received');
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
|
@ -19,34 +19,17 @@
|
||||||
# Bug still in: 4.8-7-gf4a8253
|
# Bug still in: 4.8-7-gf4a8253
|
||||||
use i3test;
|
use i3test;
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
|
||||||
$i3->connect()->recv;
|
|
||||||
|
|
||||||
my $cv;
|
|
||||||
my $t;
|
|
||||||
|
|
||||||
sub reset_test {
|
|
||||||
$cv = AE::cv;
|
|
||||||
$t = AE::timer(0.5, 0, sub { $cv->send(0); });
|
|
||||||
}
|
|
||||||
|
|
||||||
reset_test;
|
|
||||||
|
|
||||||
$i3->subscribe({
|
|
||||||
window => sub {
|
|
||||||
my ($e) = @_;
|
|
||||||
if ($e->{change} eq 'close') {
|
|
||||||
$cv->send($e->{container});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
my $window = open_window;
|
my $window = open_window;
|
||||||
|
|
||||||
cmd 'kill';
|
my @events = events_for(
|
||||||
my $con = $cv->recv;
|
sub {
|
||||||
|
$window->unmap;
|
||||||
|
sync_with_i3;
|
||||||
|
},
|
||||||
|
'window');
|
||||||
|
|
||||||
ok($con, 'closing a window should send the window::close event');
|
my @close = grep { $_->{change} eq 'close' } @events;
|
||||||
is($con->{window}, $window->{id}, 'the event should contain information about the window');
|
is(scalar @close, 1, 'Received 1 window::close event');
|
||||||
|
is($close[0]->{container}->{window}, $window->{id}, 'the event should contain information about the window');
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -19,43 +19,22 @@
|
||||||
# Bug still in: 4.8-7-gf4a8253
|
# Bug still in: 4.8-7-gf4a8253
|
||||||
use i3test;
|
use i3test;
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
|
||||||
$i3->connect()->recv;
|
|
||||||
|
|
||||||
my $cv;
|
|
||||||
my $t;
|
|
||||||
|
|
||||||
sub reset_test {
|
|
||||||
$cv = AE::cv;
|
|
||||||
$t = AE::timer(0.5, 0, sub { $cv->send(0); });
|
|
||||||
}
|
|
||||||
|
|
||||||
reset_test;
|
|
||||||
|
|
||||||
$i3->subscribe({
|
|
||||||
window => sub {
|
|
||||||
my ($e) = @_;
|
|
||||||
if ($e->{change} eq 'move') {
|
|
||||||
$cv->send($e->{container});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
my $dummy_window = open_window;
|
my $dummy_window = open_window;
|
||||||
my $window = open_window;
|
my $window = open_window;
|
||||||
|
|
||||||
cmd 'move right';
|
sub move_subtest {
|
||||||
my $con = $cv->recv;
|
my ($cmd) = @_;
|
||||||
|
my $cv = AnyEvent->condvar;
|
||||||
|
my @events = events_for(
|
||||||
|
sub { cmd $cmd },
|
||||||
|
'window');
|
||||||
|
|
||||||
ok($con, 'moving a window should emit the window::move event');
|
my @move = grep { $_->{change} eq 'move' } @events;
|
||||||
is($con->{window}, $window->{id}, 'the event should contain info about the window');
|
is(scalar @move, 1, 'Received 1 window::move event');
|
||||||
|
is($move[0]->{container}->{window}, $window->{id}, 'window id matches');
|
||||||
|
}
|
||||||
|
|
||||||
reset_test;
|
subtest 'move right', \&move_subtest, 'move right';
|
||||||
|
subtest 'move to workspace', \&move_subtest, 'move to workspace ws_new';
|
||||||
cmd 'move to workspace ws_new';
|
|
||||||
$con = $cv->recv;
|
|
||||||
|
|
||||||
ok($con, 'moving a window to a different workspace should emit the window::move event');
|
|
||||||
is($con->{window}, $window->{id}, 'the event should contain info about the window');
|
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -19,50 +19,37 @@
|
||||||
#
|
#
|
||||||
use i3test;
|
use i3test;
|
||||||
|
|
||||||
my $config = <<EOT;
|
|
||||||
# i3 config file (v4)
|
|
||||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
|
||||||
|
|
||||||
force_display_urgency_hint 0ms
|
|
||||||
EOT
|
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
|
||||||
$i3->connect()->recv;
|
|
||||||
|
|
||||||
my $cv;
|
|
||||||
$i3->subscribe({
|
|
||||||
window => sub {
|
|
||||||
my ($event) = @_;
|
|
||||||
$cv->send($event) if $event->{change} eq 'urgent';
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
my $t;
|
|
||||||
$t = AnyEvent->timer(
|
|
||||||
after => 0.5,
|
|
||||||
cb => sub {
|
|
||||||
$cv->send(0);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
$cv = AnyEvent->condvar;
|
|
||||||
fresh_workspace;
|
fresh_workspace;
|
||||||
my $win = open_window;
|
my $win = open_window;
|
||||||
my $dummy_win = open_window;
|
my $dummy_win = open_window;
|
||||||
|
|
||||||
$win->add_hint('urgency');
|
sub urgency_subtest {
|
||||||
my $event = $cv->recv;
|
my ($subscribecb, $win, $want) = @_;
|
||||||
|
|
||||||
isnt($event, 0, 'an urgent con should emit the window::urgent event');
|
my @events = events_for(
|
||||||
is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window');
|
$subscribecb,
|
||||||
is($event->{container}->{urgent}, 1, 'the container should be urgent');
|
'window');
|
||||||
|
|
||||||
$cv = AnyEvent->condvar;
|
my @urgent = grep { $_->{change} eq 'urgent' } @events;
|
||||||
$win->delete_hint('urgency');
|
is(scalar @urgent, 1, 'Received 1 window::urgent event');
|
||||||
$event = $cv->recv;
|
is($urgent[0]->{container}->{window}, $win->{id}, "window id matches");
|
||||||
|
is($urgent[0]->{container}->{urgent}, $want, "urgent is $want");
|
||||||
|
}
|
||||||
|
|
||||||
isnt($event, 0, 'an urgent con should emit the window::urgent event');
|
subtest "urgency set", \&urgency_subtest,
|
||||||
is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window');
|
sub {
|
||||||
is($event->{container}->{urgent}, 0, 'the container should not be urgent');
|
$win->add_hint('urgency');
|
||||||
|
sync_with_i3;
|
||||||
|
},
|
||||||
|
$win,
|
||||||
|
1;
|
||||||
|
|
||||||
|
subtest "urgency unset", \&urgency_subtest,
|
||||||
|
sub {
|
||||||
|
$win->delete_hint('urgency');
|
||||||
|
sync_with_i3;
|
||||||
|
},
|
||||||
|
$win,
|
||||||
|
0;
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -30,7 +30,7 @@ fresh_workspace;
|
||||||
|
|
||||||
xtest_button_press(4, 50, 50);
|
xtest_button_press(4, 50, 50);
|
||||||
xtest_button_release(4, 50, 50);
|
xtest_button_release(4, 50, 50);
|
||||||
sync_with_i3;
|
xtest_sync_with_i3;
|
||||||
|
|
||||||
is(focused_ws(), 'special', 'the binding was triggered');
|
is(focused_ws(), 'special', 'the binding was triggered');
|
||||||
|
|
||||||
|
|
|
@ -23,13 +23,13 @@
|
||||||
# Bug still in: 4.12-46-g2123888
|
# Bug still in: 4.12-46-g2123888
|
||||||
use i3test;
|
use i3test;
|
||||||
|
|
||||||
SKIP: {
|
# We cannot use events_for in this test as we cannot send events after
|
||||||
skip "AnyEvent::I3 too old (need >= 0.17)", 1 if $AnyEvent::I3::VERSION < 0.17;
|
# issuing the restart/shutdown command.
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
my $i3 = i3(get_socket_path());
|
||||||
$i3->connect->recv;
|
$i3->connect->recv;
|
||||||
|
|
||||||
my $cv = AE::cv;
|
my $cv = AnyEvent->condvar;
|
||||||
my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
|
my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
|
||||||
|
|
||||||
$i3->subscribe({
|
$i3->subscribe({
|
||||||
|
@ -50,7 +50,7 @@ is($e->{change}, 'restart', 'the `change` field should tell the reason for the s
|
||||||
$i3 = i3(get_socket_path());
|
$i3 = i3(get_socket_path());
|
||||||
$i3->connect->recv;
|
$i3->connect->recv;
|
||||||
|
|
||||||
$cv = AE::cv;
|
$cv = AnyEvent->condvar;
|
||||||
$timer = AE::timer 0.5, 0, sub { $cv->send(0); };
|
$timer = AE::timer 0.5, 0, sub { $cv->send(0); };
|
||||||
|
|
||||||
$i3->subscribe({
|
$i3->subscribe({
|
||||||
|
@ -66,6 +66,5 @@ $e = $cv->recv;
|
||||||
diag "Event:\n", Dumper($e);
|
diag "Event:\n", Dumper($e);
|
||||||
ok($e, 'the shutdown event should emit when the ipc is exited by command');
|
ok($e, 'the shutdown event should emit when the ipc is exited by command');
|
||||||
is($e->{change}, 'exit', 'the `change` field should tell the reason for the shutdown');
|
is($e->{change}, 'exit', 'the `change` field should tell the reason for the shutdown');
|
||||||
}
|
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -51,12 +51,11 @@ EOT
|
||||||
|
|
||||||
my $pid = launch_with_config($config);
|
my $pid = launch_with_config($config);
|
||||||
|
|
||||||
start_binding_capture;
|
|
||||||
|
|
||||||
is(listen_for_binding(
|
is(listen_for_binding(
|
||||||
sub {
|
sub {
|
||||||
xtest_key_press(87); # KP_End
|
xtest_key_press(87); # KP_End
|
||||||
xtest_key_release(87); # KP_End
|
xtest_key_release(87); # KP_End
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'KP_End',
|
'KP_End',
|
||||||
|
@ -70,6 +69,7 @@ is(listen_for_binding(
|
||||||
xtest_key_release(87); # KP_1
|
xtest_key_release(87); # KP_1
|
||||||
xtest_key_press(77); # disable Num_Lock
|
xtest_key_press(77); # disable Num_Lock
|
||||||
xtest_key_release(77); # disable Num_Lock
|
xtest_key_release(77); # disable Num_Lock
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'KP_1',
|
'KP_1',
|
||||||
|
@ -81,6 +81,7 @@ is(listen_for_binding(
|
||||||
xtest_key_press(38); # a
|
xtest_key_press(38); # a
|
||||||
xtest_key_release(38); # a
|
xtest_key_release(38); # a
|
||||||
xtest_key_release(133); # Super_L
|
xtest_key_release(133); # Super_L
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'a',
|
'a',
|
||||||
|
@ -96,6 +97,7 @@ is(listen_for_binding(
|
||||||
xtest_key_release(133); # Super_L
|
xtest_key_release(133); # Super_L
|
||||||
xtest_key_press(77); # disable Num_Lock
|
xtest_key_press(77); # disable Num_Lock
|
||||||
xtest_key_release(77); # disable Num_Lock
|
xtest_key_release(77); # disable Num_Lock
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'a',
|
'a',
|
||||||
|
@ -105,6 +107,7 @@ is(listen_for_binding(
|
||||||
sub {
|
sub {
|
||||||
xtest_key_press(9); # Escape
|
xtest_key_press(9); # Escape
|
||||||
xtest_key_release(9); # Escape
|
xtest_key_release(9); # Escape
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Escape',
|
'Escape',
|
||||||
|
@ -118,6 +121,7 @@ is(listen_for_binding(
|
||||||
xtest_key_release(9); # Escape
|
xtest_key_release(9); # Escape
|
||||||
xtest_key_press(77); # disable Num_Lock
|
xtest_key_press(77); # disable Num_Lock
|
||||||
xtest_key_release(77); # disable Num_Lock
|
xtest_key_release(77); # disable Num_Lock
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Escape',
|
'Escape',
|
||||||
|
@ -129,6 +133,7 @@ is(listen_for_binding(
|
||||||
xtest_key_press(9); # Escape
|
xtest_key_press(9); # Escape
|
||||||
xtest_key_release(9); # Escape
|
xtest_key_release(9); # Escape
|
||||||
xtest_key_release(50); # Shift_L
|
xtest_key_release(50); # Shift_L
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Shift+Escape',
|
'Shift+Escape',
|
||||||
|
@ -144,6 +149,7 @@ is(listen_for_binding(
|
||||||
xtest_key_release(50); # Shift_L
|
xtest_key_release(50); # Shift_L
|
||||||
xtest_key_press(77); # disable Num_Lock
|
xtest_key_press(77); # disable Num_Lock
|
||||||
xtest_key_release(77); # disable Num_Lock
|
xtest_key_release(77); # disable Num_Lock
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Shift+Escape',
|
'Shift+Escape',
|
||||||
|
@ -157,6 +163,7 @@ is(listen_for_binding(
|
||||||
xtest_key_release(24); # q
|
xtest_key_release(24); # q
|
||||||
xtest_key_release(64); # Alt_L
|
xtest_key_release(64); # Alt_L
|
||||||
xtest_key_release(50); # Shift_L
|
xtest_key_release(50); # Shift_L
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Mod1+Shift+q',
|
'Mod1+Shift+q',
|
||||||
|
@ -174,6 +181,7 @@ is(listen_for_binding(
|
||||||
xtest_key_release(50); # Shift_L
|
xtest_key_release(50); # Shift_L
|
||||||
xtest_key_press(77); # disable Num_Lock
|
xtest_key_press(77); # disable Num_Lock
|
||||||
xtest_key_release(77); # disable Num_Lock
|
xtest_key_release(77); # disable Num_Lock
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Mod1+Shift+q',
|
'Mod1+Shift+q',
|
||||||
|
@ -183,6 +191,7 @@ is(listen_for_binding(
|
||||||
sub {
|
sub {
|
||||||
xtest_key_press(39); # s
|
xtest_key_press(39); # s
|
||||||
xtest_key_release(39); # s
|
xtest_key_release(39); # s
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
's',
|
's',
|
||||||
|
@ -196,14 +205,12 @@ is(listen_for_binding(
|
||||||
xtest_key_release(39); # s
|
xtest_key_release(39); # s
|
||||||
xtest_key_press(77); # disable Num_Lock
|
xtest_key_press(77); # disable Num_Lock
|
||||||
xtest_key_release(77); # disable Num_Lock
|
xtest_key_release(77); # disable Num_Lock
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
's',
|
's',
|
||||||
'triggered the "s" keybinding with Num_Lock');
|
'triggered the "s" keybinding with Num_Lock');
|
||||||
|
|
||||||
sync_with_i3;
|
|
||||||
is(scalar @i3test::XTEST::binding_events, 12, 'Received exactly 12 binding events');
|
|
||||||
|
|
||||||
exit_gracefully($pid);
|
exit_gracefully($pid);
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
@ -222,12 +229,11 @@ EOT
|
||||||
|
|
||||||
$pid = launch_with_config($config);
|
$pid = launch_with_config($config);
|
||||||
|
|
||||||
start_binding_capture;
|
|
||||||
|
|
||||||
is(listen_for_binding(
|
is(listen_for_binding(
|
||||||
sub {
|
sub {
|
||||||
xtest_key_press(133); # Super_L
|
xtest_key_press(133); # Super_L
|
||||||
xtest_key_release(133); # Super_L
|
xtest_key_release(133); # Super_L
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Super_L',
|
'Super_L',
|
||||||
|
@ -241,6 +247,7 @@ is(listen_for_binding(
|
||||||
xtest_key_release(133); # Super_L
|
xtest_key_release(133); # Super_L
|
||||||
xtest_key_press(77); # disable Num_Lock
|
xtest_key_press(77); # disable Num_Lock
|
||||||
xtest_key_release(77); # disable Num_Lock
|
xtest_key_release(77); # disable Num_Lock
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Super_L',
|
'Super_L',
|
||||||
|
@ -252,6 +259,7 @@ is(listen_for_binding(
|
||||||
xtest_key_press(36); # Return
|
xtest_key_press(36); # Return
|
||||||
xtest_key_release(36); # Return
|
xtest_key_release(36); # Return
|
||||||
xtest_key_release(133); # Super_L
|
xtest_key_release(133); # Super_L
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Return',
|
'Return',
|
||||||
|
@ -267,14 +275,12 @@ is(listen_for_binding(
|
||||||
xtest_key_release(133); # Super_L
|
xtest_key_release(133); # Super_L
|
||||||
xtest_key_press(77); # disable Num_Lock
|
xtest_key_press(77); # disable Num_Lock
|
||||||
xtest_key_release(77); # disable Num_Lock
|
xtest_key_release(77); # disable Num_Lock
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'Return',
|
'Return',
|
||||||
'triggered the "Return" keybinding with Num_Lock');
|
'triggered the "Return" keybinding with Num_Lock');
|
||||||
|
|
||||||
sync_with_i3;
|
|
||||||
is(scalar @i3test::XTEST::binding_events, 16, 'Received exactly 16 binding events');
|
|
||||||
|
|
||||||
exit_gracefully($pid);
|
exit_gracefully($pid);
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
@ -291,12 +297,11 @@ EOT
|
||||||
|
|
||||||
$pid = launch_with_config($config);
|
$pid = launch_with_config($config);
|
||||||
|
|
||||||
start_binding_capture;
|
|
||||||
|
|
||||||
is(listen_for_binding(
|
is(listen_for_binding(
|
||||||
sub {
|
sub {
|
||||||
xtest_key_press(87); # KP_End
|
xtest_key_press(87); # KP_End
|
||||||
xtest_key_release(87); # KP_End
|
xtest_key_release(87); # KP_End
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'KP_End',
|
'KP_End',
|
||||||
|
@ -306,12 +311,13 @@ is(listen_for_binding(
|
||||||
sub {
|
sub {
|
||||||
xtest_key_press(88); # KP_Down
|
xtest_key_press(88); # KP_Down
|
||||||
xtest_key_release(88); # KP_Down
|
xtest_key_release(88); # KP_Down
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'KP_Down',
|
'KP_Down',
|
||||||
'triggered the "KP_Down" keybinding');
|
'triggered the "KP_Down" keybinding');
|
||||||
|
|
||||||
is(listen_for_binding(
|
my @unexpected = events_for(
|
||||||
sub {
|
sub {
|
||||||
xtest_key_press(77); # enable Num_Lock
|
xtest_key_press(77); # enable Num_Lock
|
||||||
xtest_key_release(77); # enable Num_Lock
|
xtest_key_release(77); # enable Num_Lock
|
||||||
|
@ -319,12 +325,12 @@ is(listen_for_binding(
|
||||||
xtest_key_release(87); # KP_1
|
xtest_key_release(87); # KP_1
|
||||||
xtest_key_press(77); # disable Num_Lock
|
xtest_key_press(77); # disable Num_Lock
|
||||||
xtest_key_release(77); # disable Num_Lock
|
xtest_key_release(77); # disable Num_Lock
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
'binding');
|
||||||
'timeout',
|
is(scalar @unexpected, 0, 'Did not trigger the KP_End keybinding with KP_1');
|
||||||
'Did not trigger the KP_End keybinding with KP_1');
|
|
||||||
|
|
||||||
is(listen_for_binding(
|
my @unexpected2 = events_for(
|
||||||
sub {
|
sub {
|
||||||
xtest_key_press(77); # enable Num_Lock
|
xtest_key_press(77); # enable Num_Lock
|
||||||
xtest_key_release(77); # enable Num_Lock
|
xtest_key_release(77); # enable Num_Lock
|
||||||
|
@ -332,16 +338,14 @@ is(listen_for_binding(
|
||||||
xtest_key_release(88); # KP_2
|
xtest_key_release(88); # KP_2
|
||||||
xtest_key_press(77); # disable Num_Lock
|
xtest_key_press(77); # disable Num_Lock
|
||||||
xtest_key_release(77); # disable Num_Lock
|
xtest_key_release(77); # disable Num_Lock
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
'binding');
|
||||||
'timeout',
|
|
||||||
'Did not trigger the KP_Down keybinding with KP_2');
|
is(scalar @unexpected2, 0, 'Did not trigger the KP_Down keybinding with KP_2');
|
||||||
|
|
||||||
# TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2.
|
# TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2.
|
||||||
|
|
||||||
sync_with_i3;
|
|
||||||
is(scalar @i3test::XTEST::binding_events, 18, 'Received exactly 18 binding events');
|
|
||||||
|
|
||||||
exit_gracefully($pid);
|
exit_gracefully($pid);
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
@ -359,8 +363,6 @@ $pid = launch_with_config($config);
|
||||||
|
|
||||||
my $win = open_window;
|
my $win = open_window;
|
||||||
|
|
||||||
start_binding_capture;
|
|
||||||
|
|
||||||
is(listen_for_binding(
|
is(listen_for_binding(
|
||||||
sub {
|
sub {
|
||||||
xtest_key_press(77); # enable Num_Lock
|
xtest_key_press(77); # enable Num_Lock
|
||||||
|
@ -369,6 +371,7 @@ is(listen_for_binding(
|
||||||
xtest_button_release(4, 50, 50);
|
xtest_button_release(4, 50, 50);
|
||||||
xtest_key_press(77); # disable Num_Lock
|
xtest_key_press(77); # disable Num_Lock
|
||||||
xtest_key_release(77); # disable Num_Lock
|
xtest_key_release(77); # disable Num_Lock
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'button4',
|
'button4',
|
||||||
|
@ -376,8 +379,9 @@ is(listen_for_binding(
|
||||||
|
|
||||||
is(listen_for_binding(
|
is(listen_for_binding(
|
||||||
sub {
|
sub {
|
||||||
xtest_button_press(4, 50, 50);
|
xtest_button_press(4, 50, 50);
|
||||||
xtest_button_release(4, 50, 50);
|
xtest_button_release(4, 50, 50);
|
||||||
|
xtest_sync_with_i3;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'button4',
|
'button4',
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
#
|
#
|
||||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||||
# (unless you are already familiar with Perl)
|
# (unless you are already familiar with Perl)
|
||||||
#
|
#
|
||||||
# Ticket: #990
|
# Ticket: #990
|
||||||
# Bug still in: 4.5.1-23-g82b5978
|
# Bug still in: 4.5.1-23-g82b5978
|
||||||
|
|
||||||
|
@ -23,46 +23,17 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||||
fake-outputs 1024x768+0+0,1024x768+1024+0
|
fake-outputs 1024x768+0+0,1024x768+1024+0
|
||||||
EOT
|
EOT
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
|
||||||
|
|
||||||
$i3->connect()->recv;
|
|
||||||
|
|
||||||
################################
|
|
||||||
# Workspaces requests and events
|
|
||||||
################################
|
|
||||||
|
|
||||||
my $old_ws = get_ws(focused_ws);
|
my $old_ws = get_ws(focused_ws);
|
||||||
|
|
||||||
# Events
|
|
||||||
|
|
||||||
# We are switching to an empty workpspace on the output to the right from an empty workspace on the output on the left, so we expect
|
|
||||||
# to receive "init", "focus", and "empty".
|
|
||||||
my $focus = AnyEvent->condvar;
|
my $focus = AnyEvent->condvar;
|
||||||
$i3->subscribe({
|
my @events = events_for(
|
||||||
workspace => sub {
|
sub { cmd 'focus output right' },
|
||||||
my ($event) = @_;
|
'workspace');
|
||||||
if ($event->{change} eq 'focus') {
|
|
||||||
$focus->send($event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
my $t;
|
|
||||||
$t = AnyEvent->timer(
|
|
||||||
after => 0.5,
|
|
||||||
cb => sub {
|
|
||||||
$focus->send(0);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
cmd 'focus output right';
|
|
||||||
|
|
||||||
my $event = $focus->recv;
|
|
||||||
|
|
||||||
my $current_ws = get_ws(focused_ws);
|
my $current_ws = get_ws(focused_ws);
|
||||||
|
|
||||||
ok($event, 'Workspace "focus" event received');
|
is(scalar @events, 1, 'Received 1 event');
|
||||||
is($event->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace');
|
is($events[0]->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace');
|
||||||
is($event->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace');
|
is($events[0]->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace');
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -27,51 +27,29 @@ workspace ws-left output fake-0
|
||||||
workspace ws-right output fake-1
|
workspace ws-right output fake-1
|
||||||
EOT
|
EOT
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
|
||||||
$i3->connect()->recv;
|
|
||||||
|
|
||||||
# subscribe to the 'focus' ipc event
|
|
||||||
my $focus = AnyEvent->condvar;
|
|
||||||
$i3->subscribe({
|
|
||||||
workspace => sub {
|
|
||||||
my ($event) = @_;
|
|
||||||
if ($event->{change} eq 'focus') {
|
|
||||||
$focus->send($event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})->recv;
|
|
||||||
|
|
||||||
# give up after 0.5 seconds
|
|
||||||
my $timer = AnyEvent->timer(
|
|
||||||
after => 0.5,
|
|
||||||
cb => sub {
|
|
||||||
$focus->send(0);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
# open two windows on the left output
|
# open two windows on the left output
|
||||||
cmd 'workspace ws-left';
|
cmd 'workspace ws-left';
|
||||||
open_window;
|
open_window;
|
||||||
open_window;
|
open_window;
|
||||||
|
|
||||||
|
sub focus_subtest {
|
||||||
|
my ($cmd, $want) = @_;
|
||||||
|
|
||||||
|
my @events = events_for(
|
||||||
|
sub { cmd $cmd },
|
||||||
|
'workspace');
|
||||||
|
|
||||||
|
my @focus = grep { $_->{change} eq 'focus' } @events;
|
||||||
|
is(scalar @focus, 1, 'Received 1 workspace::focus event');
|
||||||
|
is($focus[0]->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
|
||||||
|
is(@{$focus[0]->{current}->{nodes}}, $want, 'focus event gave the right number of windows on the workspace');
|
||||||
|
}
|
||||||
|
|
||||||
# move a window over to the right output
|
# move a window over to the right output
|
||||||
cmd 'move right';
|
subtest 'move right (1)', \&focus_subtest, 'move right', 1;
|
||||||
my $event = $focus->recv;
|
|
||||||
|
|
||||||
ok($event, 'moving from workspace with two windows triggered focus ipc event');
|
# move another window
|
||||||
is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
|
|
||||||
is(@{$event->{current}->{nodes}}, 1, 'focus event gave the right number of windows on the workspace');
|
|
||||||
|
|
||||||
# reset and try again
|
|
||||||
$focus = AnyEvent->condvar;
|
|
||||||
cmd 'workspace ws-left';
|
cmd 'workspace ws-left';
|
||||||
$focus->recv;
|
subtest 'move right (2)', \&focus_subtest, 'move right', 2;
|
||||||
|
|
||||||
$focus = AnyEvent->condvar;
|
|
||||||
cmd 'move right';
|
|
||||||
$event = $focus->recv;
|
|
||||||
ok($event, 'moving from workspace with one window triggered focus ipc event');
|
|
||||||
is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
|
|
||||||
is(@{$event->{current}->{nodes}}, 2, 'focus event gave the right number of windows on the workspace');
|
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -34,17 +34,12 @@ bar {
|
||||||
EOT
|
EOT
|
||||||
use i3test::XTEST;
|
use i3test::XTEST;
|
||||||
|
|
||||||
my ($cv, $timer);
|
|
||||||
sub reset_test {
|
|
||||||
$cv = AE::cv;
|
|
||||||
$timer = AE::timer(1, 0, sub { $cv->send(0); });
|
|
||||||
}
|
|
||||||
|
|
||||||
my $i3 = i3(get_socket_path());
|
my $i3 = i3(get_socket_path());
|
||||||
$i3->connect()->recv;
|
$i3->connect()->recv;
|
||||||
my $ws = fresh_workspace;
|
my $ws = fresh_workspace;
|
||||||
|
|
||||||
reset_test;
|
my $cv = AnyEvent->condvar;
|
||||||
|
my $timer = AnyEvent->timer(1, 0, sub { $cv->send(0) });
|
||||||
$i3->subscribe({
|
$i3->subscribe({
|
||||||
window => sub {
|
window => sub {
|
||||||
my ($event) = @_;
|
my ($event) = @_;
|
||||||
|
@ -60,15 +55,13 @@ $i3->subscribe({
|
||||||
},
|
},
|
||||||
})->recv;
|
})->recv;
|
||||||
|
|
||||||
my $con;
|
|
||||||
|
|
||||||
sub i3bar_present {
|
sub i3bar_present {
|
||||||
my ($nodes) = @_;
|
my ($nodes) = @_;
|
||||||
|
|
||||||
for my $node (@{$nodes}) {
|
for my $node (@{$nodes}) {
|
||||||
my $props = $node->{window_properties};
|
my $props = $node->{window_properties};
|
||||||
if (defined($props) && $props->{class} eq 'i3bar') {
|
if (defined($props) && $props->{class} eq 'i3bar') {
|
||||||
return 1;
|
return $node->{window};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,53 +73,75 @@ sub i3bar_present {
|
||||||
return i3bar_present(\@children);
|
return i3bar_present(\@children);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i3bar_present($i3->get_tree->recv->{nodes})) {
|
my $i3bar_window = i3bar_present($i3->get_tree->recv->{nodes});
|
||||||
|
if ($i3bar_window) {
|
||||||
ok(1, 'i3bar present');
|
ok(1, 'i3bar present');
|
||||||
} else {
|
} else {
|
||||||
$con = $cv->recv;
|
my $con = $cv->recv;
|
||||||
ok($con, 'i3bar appeared');
|
ok($con, 'i3bar appeared');
|
||||||
|
$i3bar_window = $con->{window};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diag('i3bar window = ' . $i3bar_window);
|
||||||
|
|
||||||
my $left = open_window;
|
my $left = open_window;
|
||||||
my $right = open_window;
|
my $right = open_window;
|
||||||
sync_with_i3;
|
sync_with_i3;
|
||||||
$con = $cv->recv;
|
my $con = $cv->recv;
|
||||||
is($con->{window}, $right->{id}, 'focus is initially on the right container');
|
is($con->{window}, $right->{id}, 'focus is initially on the right container');
|
||||||
reset_test;
|
|
||||||
|
|
||||||
xtest_button_press(1, 3, 3);
|
sub focus_subtest {
|
||||||
xtest_button_release(1, 3, 3);
|
my ($subscribecb, $want, $msg) = @_;
|
||||||
sync_with_i3;
|
my @events = events_for(
|
||||||
$con = $cv->recv;
|
$subscribecb,
|
||||||
is($con->{window}, $left->{id}, 'button 1 moves focus left');
|
'window');
|
||||||
reset_test;
|
my @focus = map { $_->{container}->{window} } grep { $_->{change} eq 'focus' } @events;
|
||||||
|
is_deeply(\@focus, $want, $msg);
|
||||||
|
}
|
||||||
|
|
||||||
xtest_button_press(2, 3, 3);
|
subtest 'button 1 moves focus left', \&focus_subtest,
|
||||||
xtest_button_release(2, 3, 3);
|
sub {
|
||||||
sync_with_i3;
|
xtest_button_press(1, 3, 3);
|
||||||
$con = $cv->recv;
|
xtest_button_release(1, 3, 3);
|
||||||
is($con->{window}, $right->{id}, 'button 2 moves focus right');
|
xtest_sync_with($i3bar_window);
|
||||||
reset_test;
|
},
|
||||||
|
[ $left->{id} ],
|
||||||
|
'button 1 moves focus left';
|
||||||
|
|
||||||
xtest_button_press(3, 3, 3);
|
subtest 'button 2 moves focus right', \&focus_subtest,
|
||||||
xtest_button_release(3, 3, 3);
|
sub {
|
||||||
sync_with_i3;
|
xtest_button_press(2, 3, 3);
|
||||||
$con = $cv->recv;
|
xtest_button_release(2, 3, 3);
|
||||||
is($con->{window}, $left->{id}, 'button 3 moves focus left');
|
xtest_sync_with($i3bar_window);
|
||||||
reset_test;
|
},
|
||||||
|
[ $right->{id} ],
|
||||||
|
'button 2 moves focus right';
|
||||||
|
|
||||||
xtest_button_press(4, 3, 3);
|
subtest 'button 3 moves focus left', \&focus_subtest,
|
||||||
xtest_button_release(4, 3, 3);
|
sub {
|
||||||
sync_with_i3;
|
xtest_button_press(3, 3, 3);
|
||||||
$con = $cv->recv;
|
xtest_button_release(3, 3, 3);
|
||||||
is($con->{window}, $right->{id}, 'button 4 moves focus right');
|
xtest_sync_with($i3bar_window);
|
||||||
reset_test;
|
},
|
||||||
|
[ $left->{id} ],
|
||||||
|
'button 3 moves focus left';
|
||||||
|
|
||||||
xtest_button_press(5, 3, 3);
|
subtest 'button 4 moves focus right', \&focus_subtest,
|
||||||
xtest_button_release(5, 3, 3);
|
sub {
|
||||||
sync_with_i3;
|
xtest_button_press(4, 3, 3);
|
||||||
$con = $cv->recv;
|
xtest_button_release(4, 3, 3);
|
||||||
is($con->{window}, $left->{id}, 'button 5 moves focus left');
|
xtest_sync_with($i3bar_window);
|
||||||
reset_test;
|
},
|
||||||
|
[ $right->{id} ],
|
||||||
|
'button 4 moves focus right';
|
||||||
|
|
||||||
|
subtest 'button 5 moves focus left', \&focus_subtest,
|
||||||
|
sub {
|
||||||
|
xtest_button_press(5, 3, 3);
|
||||||
|
xtest_button_release(5, 3, 3);
|
||||||
|
xtest_sync_with($i3bar_window);
|
||||||
|
},
|
||||||
|
[ $left->{id} ],
|
||||||
|
'button 5 moves focus left';
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
Loading…
Reference in New Issue