diff --git a/docs/ipc b/docs/ipc index fda289a0..5d47bcbd 100644 --- a/docs/ipc +++ b/docs/ipc @@ -671,6 +671,8 @@ barconfig_update (4):: binding (5):: Sent when a configured command binding is triggered with the keyboard or mouse +shutdown (6):: + Sent when the ipc shuts down because of a restart or exit by user command *Example:* -------------------------------------------------------------------- @@ -829,6 +831,20 @@ input_type (string):: } --------------------------- +=== shutdown event + +This event is triggered when the connection to the ipc is about to shutdown +because of a user action such as a +restart+ or +exit+ command. The +change +(string)+ field indicates why the ipc is shutting down. It can be either ++"restart"+ or +"exit"+. + +*Example:* +--------------------------- +{ + "change": "restart" +} +--------------------------- + == See also (existing libraries) [[libraries]] diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 98ac35b0..249cc32e 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -91,3 +91,6 @@ typedef struct i3_ipc_header { /** The binding event will be triggered when bindings run */ #define I3_IPC_EVENT_BINDING (I3_IPC_EVENT_MASK | 5) + +/** The shutdown event will be triggered when the ipc shuts down */ +#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6) diff --git a/include/ipc.h b/include/ipc.h index 7ff4704c..7ffbf7a8 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -77,11 +77,18 @@ int ipc_create_socket(const char *filename); void ipc_send_event(const char *event, uint32_t message_type, const char *payload); /** - * Calls shutdown() on each socket and closes it. This function to be called - * when exiting or restarting only! + * Calls to ipc_shutdown() should provide a reason for the shutdown. + */ +typedef enum { + SHUTDOWN_REASON_RESTART, + SHUTDOWN_REASON_EXIT +} shutdown_reason_t; + +/** + * Calls shutdown() on each socket and closes it. * */ -void ipc_shutdown(void); +void ipc_shutdown(shutdown_reason_t reason); void dump_node(yajl_gen gen, Con *con, bool inplace_restart); diff --git a/src/commands.c b/src/commands.c index 2387ddd7..56c07b6a 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1562,7 +1562,7 @@ void cmd_exit(I3_CMD) { #ifdef I3_ASAN_ENABLED __lsan_do_leak_check(); #endif - ipc_shutdown(); + ipc_shutdown(SHUTDOWN_REASON_EXIT); unlink(config.ipc_socket_path); xcb_disconnect(conn); exit(0); @@ -1595,7 +1595,7 @@ void cmd_reload(I3_CMD) { */ void cmd_restart(I3_CMD) { LOG("restarting i3\n"); - ipc_shutdown(); + ipc_shutdown(SHUTDOWN_REASON_RESTART); unlink(config.ipc_socket_path); /* We need to call this manually since atexit handlers don’t get called * when exec()ing */ diff --git a/src/ipc.c b/src/ipc.c index db2fa362..bb20b340 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -62,11 +62,39 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa } /* - * Calls shutdown() on each socket and closes it. This function to be called + * For shutdown events, we send the reason for the shutdown. + */ +static void ipc_send_shutdown_event(shutdown_reason_t reason) { + yajl_gen gen = ygenalloc(); + y(map_open); + + ystr("change"); + + if (reason == SHUTDOWN_REASON_RESTART) { + ystr("restart"); + } else if (reason == SHUTDOWN_REASON_EXIT) { + ystr("exit"); + } + + y(map_close); + + const unsigned char *payload; + ylength length; + + y(get_buf, &payload, &length); + ipc_send_event("shutdown", I3_IPC_EVENT_SHUTDOWN, (const char *)payload); + + y(free); +} + +/* + * Calls shutdown() on each socket and closes it. This function is to be called * when exiting or restarting only! * */ -void ipc_shutdown(void) { +void ipc_shutdown(shutdown_reason_t reason) { + ipc_send_shutdown_event(reason); + ipc_client *current; while (!TAILQ_EMPTY(&all_clients)) { current = TAILQ_FIRST(&all_clients); diff --git a/src/util.c b/src/util.c index cfe4c953..5c8fc774 100644 --- a/src/util.c +++ b/src/util.c @@ -259,7 +259,7 @@ void i3_restart(bool forget_layout) { restore_geometry(); - ipc_shutdown(); + ipc_shutdown(SHUTDOWN_REASON_RESTART); LOG("restarting \"%s\"...\n", start_argv[0]); /* make sure -a is in the argument list or add it */ diff --git a/testcases/t/264-ipc-shutdown-event.t b/testcases/t/264-ipc-shutdown-event.t new file mode 100644 index 00000000..379b9bf2 --- /dev/null +++ b/testcases/t/264-ipc-shutdown-event.t @@ -0,0 +1,71 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test the ipc shutdown event. This event is triggered when the connection to +# the ipc is about to shutdown because of a user action such as with a +# `restart` or `exit` command. The `change` field indicates why the ipc is +# shutting down. It can be either "restart" or "exit". +# +# Ticket: #2318 +# Bug still in: 4.12-46-g2123888 +use i3test; + +SKIP: { + skip "AnyEvent::I3 too old (need >= 0.17)", 1 if $AnyEvent::I3::VERSION < 0.17; + +my $i3 = i3(get_socket_path()); +$i3->connect->recv; + +my $cv = AE::cv; +my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; + +$i3->subscribe({ + shutdown => sub { + $cv->send(shift); + } + })->recv; + +cmd 'restart'; + +my $e = $cv->recv; + +diag "Event:\n", Dumper($e); +ok($e, 'the shutdown event should emit when the ipc is restarted by command'); +is($e->{change}, 'restart', 'the `change` field should tell the reason for the shutdown'); + +# restarting kills the ipc client so we have to make a new one +$i3 = i3(get_socket_path()); +$i3->connect->recv; + +$cv = AE::cv; +$timer = AE::timer 0.5, 0, sub { $cv->send(0); }; + +$i3->subscribe({ + shutdown => sub { + $cv->send(shift); + } + })->recv; + +cmd 'exit'; + +$e = $cv->recv; + +diag "Event:\n", Dumper($e); +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'); +} + +done_testing;