Add server event infrastructure
Includes "client-connect" and "client-disconnect" events as proof-of-concept. Signed-off-by: Jim Ramsay <i.am@jimramsay.com>pull/183/head
parent
8d32dfaead
commit
c75b64eae8
|
@ -37,3 +37,15 @@ void* ctl_server_userdata(struct ctl*);
|
||||||
|
|
||||||
struct cmd_response* cmd_ok(void);
|
struct cmd_response* cmd_ok(void);
|
||||||
struct cmd_response* cmd_failed(const char* fmt, ...);
|
struct cmd_response* cmd_failed(const char* fmt, ...);
|
||||||
|
|
||||||
|
void ctl_server_event_connected(struct ctl*,
|
||||||
|
const char* client_id,
|
||||||
|
const char* client_hostname,
|
||||||
|
const char* client_username,
|
||||||
|
int new_connection_count);
|
||||||
|
|
||||||
|
void ctl_server_event_disconnected(struct ctl*,
|
||||||
|
const char* client_id,
|
||||||
|
const char* client_hostname,
|
||||||
|
const char* client_username,
|
||||||
|
int new_connection_count);
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
#include <signal.h>
|
||||||
#include <jansson.h>
|
#include <jansson.h>
|
||||||
|
|
||||||
#include "json-ipc.h"
|
#include "json-ipc.h"
|
||||||
|
@ -46,6 +47,8 @@ struct ctl_client {
|
||||||
|
|
||||||
char read_buffer[512];
|
char read_buffer[512];
|
||||||
size_t read_len;
|
size_t read_len;
|
||||||
|
|
||||||
|
bool wait_for_events;
|
||||||
|
|
||||||
int fd;
|
int fd;
|
||||||
};
|
};
|
||||||
|
@ -197,8 +200,6 @@ static json_t* read_one_object(struct ctl_client* self, int timeout_ms)
|
||||||
while (root == NULL) {
|
while (root == NULL) {
|
||||||
int n = poll(&pfd, 1, timeout_ms);
|
int n = poll(&pfd, 1, timeout_ms);
|
||||||
if (n == -1) {
|
if (n == -1) {
|
||||||
if (errno == EINTR)
|
|
||||||
continue;
|
|
||||||
WARN("Error waiting for a response: %m");
|
WARN("Error waiting for a response: %m");
|
||||||
break;
|
break;
|
||||||
} else if (n == 0) {
|
} else if (n == 0) {
|
||||||
|
@ -342,6 +343,35 @@ static int ctl_client_print_response(struct ctl_client* self,
|
||||||
return response->code;
|
return response->code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct ctl_client* sig_target = NULL;
|
||||||
|
static void stop_loop(int signal)
|
||||||
|
{
|
||||||
|
sig_target->wait_for_events = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setup_signals(struct ctl_client* self)
|
||||||
|
{
|
||||||
|
sig_target = self;
|
||||||
|
struct sigaction sa = { 0 };
|
||||||
|
sa.sa_handler = stop_loop;
|
||||||
|
sigaction(SIGINT, &sa, NULL);
|
||||||
|
sigaction(SIGTERM, &sa, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ctl_client_event_loop(struct ctl_client* self)
|
||||||
|
{
|
||||||
|
self->wait_for_events = true;
|
||||||
|
setup_signals(self);
|
||||||
|
while (self->wait_for_events) {
|
||||||
|
DEBUG("Waiting for an event");
|
||||||
|
json_t* root = read_one_object(self, -1);
|
||||||
|
json_dumpf(root, stdout, 0);
|
||||||
|
printf("\n");
|
||||||
|
fflush(stdout);
|
||||||
|
json_decref(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int ctl_client_run_command(struct ctl_client* self,
|
int ctl_client_run_command(struct ctl_client* self,
|
||||||
int argc, char* argv[], unsigned flags)
|
int argc, char* argv[], unsigned flags)
|
||||||
{
|
{
|
||||||
|
@ -359,6 +389,9 @@ int ctl_client_run_command(struct ctl_client* self,
|
||||||
|
|
||||||
result = ctl_client_print_response(self, request, response, flags);
|
result = ctl_client_print_response(self, request, response, flags);
|
||||||
|
|
||||||
|
if (result == 0 && strcmp(request->method, "event-receive") == 0)
|
||||||
|
ctl_client_event_loop(self);
|
||||||
|
|
||||||
jsonipc_response_destroy(response);
|
jsonipc_response_destroy(response);
|
||||||
receive_failure:
|
receive_failure:
|
||||||
send_failure:
|
send_failure:
|
||||||
|
|
100
src/ctl-server.c
100
src/ctl-server.c
|
@ -43,6 +43,7 @@ enum send_priority {
|
||||||
enum cmd_type {
|
enum cmd_type {
|
||||||
CMD_HELP,
|
CMD_HELP,
|
||||||
CMD_VERSION,
|
CMD_VERSION,
|
||||||
|
CMD_EVENT_RECEIVE,
|
||||||
CMD_SET_OUTPUT,
|
CMD_SET_OUTPUT,
|
||||||
CMD_UNKNOWN,
|
CMD_UNKNOWN,
|
||||||
};
|
};
|
||||||
|
@ -70,6 +71,11 @@ static struct cmd_info cmd_list[] = {
|
||||||
"Query the version of the wayvnc process",
|
"Query the version of the wayvnc process",
|
||||||
{{NULL, NULL}}
|
{{NULL, NULL}}
|
||||||
},
|
},
|
||||||
|
[CMD_EVENT_RECEIVE] = { "event-receive",
|
||||||
|
"Register to begin receiving asynchronous events from wayvnc",
|
||||||
|
// TODO: Event type filtering?
|
||||||
|
{{NULL, NULL}}
|
||||||
|
},
|
||||||
[CMD_SET_OUTPUT] = { "set-output",
|
[CMD_SET_OUTPUT] = { "set-output",
|
||||||
"Switch the actively captured output",
|
"Switch the actively captured output",
|
||||||
{
|
{
|
||||||
|
@ -112,6 +118,7 @@ struct ctl_client {
|
||||||
char* write_ptr;
|
char* write_ptr;
|
||||||
size_t write_len;
|
size_t write_len;
|
||||||
bool drop_after_next_send;
|
bool drop_after_next_send;
|
||||||
|
bool accept_events;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ctl {
|
struct ctl {
|
||||||
|
@ -222,6 +229,7 @@ static struct cmd* parse_command(struct jsonipc_request* ipc,
|
||||||
cmd = (struct cmd*)cmd_set_output_new(ipc->params, err);
|
cmd = (struct cmd*)cmd_set_output_new(ipc->params, err);
|
||||||
break;
|
break;
|
||||||
case CMD_VERSION:
|
case CMD_VERSION:
|
||||||
|
case CMD_EVENT_RECEIVE:
|
||||||
cmd = calloc(1, sizeof(*cmd));
|
cmd = calloc(1, sizeof(*cmd));
|
||||||
cmd->type = cmd_type;
|
cmd->type = cmd_type;
|
||||||
break;
|
break;
|
||||||
|
@ -344,7 +352,8 @@ static struct cmd_response* generate_version_object()
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self, struct cmd* cmd)
|
static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self,
|
||||||
|
struct ctl_client* client, struct cmd* cmd)
|
||||||
{
|
{
|
||||||
assert(cmd->type != CMD_UNKNOWN);
|
assert(cmd->type != CMD_UNKNOWN);
|
||||||
const struct cmd_info* info = &cmd_list[cmd->type];
|
const struct cmd_info* info = &cmd_list[cmd->type];
|
||||||
|
@ -367,6 +376,10 @@ static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self, struct cmd
|
||||||
case CMD_VERSION:
|
case CMD_VERSION:
|
||||||
response = generate_version_object();
|
response = generate_version_object();
|
||||||
break;
|
break;
|
||||||
|
case CMD_EVENT_RECEIVE:
|
||||||
|
client->accept_events = true;
|
||||||
|
response = cmd_ok();
|
||||||
|
break;
|
||||||
case CMD_UNKNOWN:
|
case CMD_UNKNOWN:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -410,6 +423,7 @@ static int client_enqueue_jsonipc(struct ctl_client* self,
|
||||||
goto failure;
|
goto failure;
|
||||||
}
|
}
|
||||||
result = client_enqueue(self, packed_response, priority);
|
result = client_enqueue(self, packed_response, priority);
|
||||||
|
json_decref(packed_response);
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
nvnc_log(NVNC_LOG_WARNING, "Append failed");
|
nvnc_log(NVNC_LOG_WARNING, "Append failed");
|
||||||
failure:
|
failure:
|
||||||
|
@ -553,7 +567,7 @@ static void recv_ready(struct ctl_client* client)
|
||||||
// handled by the main loop instead of doing the
|
// handled by the main loop instead of doing the
|
||||||
// dispatch here
|
// dispatch here
|
||||||
struct cmd_response* response =
|
struct cmd_response* response =
|
||||||
ctl_server_dispatch_cmd(server, cmd);
|
ctl_server_dispatch_cmd(server, client, cmd);
|
||||||
if (!response)
|
if (!response)
|
||||||
goto no_response;
|
goto no_response;
|
||||||
client_enqueue_response(client, response, request->id);
|
client_enqueue_response(client, response, request->id);
|
||||||
|
@ -736,3 +750,85 @@ struct cmd_response* cmd_failed(const char* fmt, ...)
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
json_t* pack_connection_event_params(
|
||||||
|
const char* client_id,
|
||||||
|
const char* client_hostname,
|
||||||
|
const char* client_username,
|
||||||
|
int new_connection_count)
|
||||||
|
{
|
||||||
|
return json_pack("{s:s, s:s?, s:s?, s:i}",
|
||||||
|
"id", client_id,
|
||||||
|
"hostname", client_hostname,
|
||||||
|
"username", client_username,
|
||||||
|
"connection_count", new_connection_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ctl_server_enqueue_event(struct ctl* self, const char* event_name,
|
||||||
|
json_t* params)
|
||||||
|
{
|
||||||
|
char* param_str = json_dumps(params, JSON_COMPACT);
|
||||||
|
nvnc_log(NVNC_LOG_DEBUG, "Enqueueing %s event: {%s", event_name, param_str);
|
||||||
|
free(param_str);
|
||||||
|
struct jsonipc_request* event = jsonipc_event_new(event_name, params);
|
||||||
|
json_decref(params);
|
||||||
|
json_error_t err;
|
||||||
|
json_t* packed_event = jsonipc_request_pack(event, &err);
|
||||||
|
jsonipc_request_destroy(event);
|
||||||
|
if (!packed_event) {
|
||||||
|
nvnc_log(NVNC_LOG_WARNING, "Could not pack %s event json: %s", event_name, err.text);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int enqueued = 0;
|
||||||
|
struct ctl_client* client;
|
||||||
|
wl_list_for_each(client, &self->clients, link) {
|
||||||
|
if (!client->accept_events) {
|
||||||
|
nvnc_trace("Skipping event send to control client %p", client);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (client_enqueue(client, packed_event, false) == 0) {
|
||||||
|
nvnc_trace("Enqueued event for control client %p", client);
|
||||||
|
enqueued++;
|
||||||
|
} else {
|
||||||
|
nvnc_trace("Failed to enqueue event for control client %p", client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json_decref(packed_event);
|
||||||
|
nvnc_log(NVNC_LOG_DEBUG, "Enqueued %s event for %d clients", event_name, enqueued);
|
||||||
|
return enqueued;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ctl_server_event_connect(struct ctl* self,
|
||||||
|
bool connected,
|
||||||
|
const char* client_id,
|
||||||
|
const char* client_hostname,
|
||||||
|
const char* client_username,
|
||||||
|
int new_connection_count)
|
||||||
|
{
|
||||||
|
json_t* params = pack_connection_event_params(client_id, client_hostname,
|
||||||
|
client_username, new_connection_count);
|
||||||
|
ctl_server_enqueue_event(self,
|
||||||
|
connected ? "client-connected" : "client-disconnected",
|
||||||
|
params);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ctl_server_event_connected(struct ctl* self,
|
||||||
|
const char* client_id,
|
||||||
|
const char* client_hostname,
|
||||||
|
const char* client_username,
|
||||||
|
int new_connection_count)
|
||||||
|
{
|
||||||
|
ctl_server_event_connect(self, true, client_id, client_hostname,
|
||||||
|
client_username, new_connection_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ctl_server_event_disconnected(struct ctl* self,
|
||||||
|
const char* client_id,
|
||||||
|
const char* client_hostname,
|
||||||
|
const char* client_username,
|
||||||
|
int new_connection_count)
|
||||||
|
{
|
||||||
|
ctl_server_event_connect(self, false, client_id, client_hostname,
|
||||||
|
client_username, new_connection_count);
|
||||||
|
}
|
||||||
|
|
15
src/main.c
15
src/main.c
|
@ -844,6 +844,14 @@ static void on_client_cleanup(struct nvnc_client* client)
|
||||||
self->nr_clients--;
|
self->nr_clients--;
|
||||||
nvnc_log(NVNC_LOG_DEBUG, "Client disconnected, new client count: %d",
|
nvnc_log(NVNC_LOG_DEBUG, "Client disconnected, new client count: %d",
|
||||||
self->nr_clients);
|
self->nr_clients);
|
||||||
|
|
||||||
|
char id[11];
|
||||||
|
snprintf(id, 11, "%p", nvnc);
|
||||||
|
ctl_server_event_disconnected(self->ctl, id,
|
||||||
|
nvnc_client_get_hostname(client),
|
||||||
|
nvnc_client_get_auth_username(client),
|
||||||
|
self->nr_clients);
|
||||||
|
|
||||||
if (self->nr_clients == 0) {
|
if (self->nr_clients == 0) {
|
||||||
nvnc_log(NVNC_LOG_INFO, "Stopping screen capture");
|
nvnc_log(NVNC_LOG_INFO, "Stopping screen capture");
|
||||||
screencopy_stop(&self->screencopy);
|
screencopy_stop(&self->screencopy);
|
||||||
|
@ -865,6 +873,13 @@ static void on_client_new(struct nvnc_client* client)
|
||||||
nvnc_set_client_cleanup_fn(client, on_client_cleanup);
|
nvnc_set_client_cleanup_fn(client, on_client_cleanup);
|
||||||
nvnc_log(NVNC_LOG_DEBUG, "Client connected, new client count: %d",
|
nvnc_log(NVNC_LOG_DEBUG, "Client connected, new client count: %d",
|
||||||
self->nr_clients);
|
self->nr_clients);
|
||||||
|
|
||||||
|
char id[11];
|
||||||
|
snprintf(id, 11, "%p", nvnc);
|
||||||
|
ctl_server_event_connected(self->ctl, id,
|
||||||
|
nvnc_client_get_hostname(client),
|
||||||
|
nvnc_client_get_auth_username(client),
|
||||||
|
self->nr_clients);
|
||||||
}
|
}
|
||||||
|
|
||||||
void parse_keyboard_option(struct wayvnc* self, char* arg)
|
void parse_keyboard_option(struct wayvnc* self, char* arg)
|
||||||
|
|
73
wayvnc.scd
73
wayvnc.scd
|
@ -176,6 +176,16 @@ information. Much like the _-V_ option, the response data will contain the
|
||||||
version numbers of wayvnc, as well as the versions of the neatvnc and aml
|
version numbers of wayvnc, as well as the versions of the neatvnc and aml
|
||||||
components.
|
components.
|
||||||
|
|
||||||
|
_EVENT-RECEIVE_
|
||||||
|
|
||||||
|
The *event-receive* command registers for asynchronous server events. See the
|
||||||
|
_EVENTS_ section below for details on the event message format, and the _IPC
|
||||||
|
EVENTS_ section below for a description of all possible server events.
|
||||||
|
|
||||||
|
Event registration registers for all available server eve ts and is scoped to
|
||||||
|
the current connection only. If a client disconnects and reconnects, it must
|
||||||
|
re-register for events.
|
||||||
|
|
||||||
_SET-OUTPUT_
|
_SET-OUTPUT_
|
||||||
|
|
||||||
For multi-output wayland displays, this command switches which output is
|
For multi-output wayland displays, this command switches which output is
|
||||||
|
@ -189,6 +199,45 @@ which parameters are supplied:
|
||||||
*switch-to=output-name*
|
*switch-to=output-name*
|
||||||
Switch to a specific output by name.
|
Switch to a specific output by name.
|
||||||
|
|
||||||
|
## IPC EVENTS
|
||||||
|
|
||||||
|
_CLIENT-CONNECTED_
|
||||||
|
|
||||||
|
The *client-connected* event is sent when a new VNC client connects to wayvnc.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
*id=...*
|
||||||
|
A unique identifier for this client.
|
||||||
|
|
||||||
|
*connection_count=...*
|
||||||
|
The total number of connected VNC clients including this one.
|
||||||
|
|
||||||
|
*hostname=...*
|
||||||
|
The hostname or IP of this client. May be null.
|
||||||
|
|
||||||
|
*username=...*
|
||||||
|
The username used to authenticate this client. May be null.
|
||||||
|
|
||||||
|
_CLIENT-DISCONNECTED_
|
||||||
|
|
||||||
|
The *client-disconnected* event is sent when a VNC cliwnt disconnects from
|
||||||
|
wayvnc.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
*id=...*
|
||||||
|
A unique identifier for this client.
|
||||||
|
|
||||||
|
*connection_count=...*
|
||||||
|
The total number of connected VNC clients not including this one.
|
||||||
|
|
||||||
|
*hostname=...*
|
||||||
|
The hostname or IP of this client. May be null.
|
||||||
|
|
||||||
|
*username=...*
|
||||||
|
The username used to authenticate this client. May be null.
|
||||||
|
|
||||||
## IPC MESSAGE FORMAT
|
## IPC MESSAGE FORMAT
|
||||||
|
|
||||||
The *wayvncctl(1)* command line utility will construct properly-formatted json
|
The *wayvncctl(1)* command line utility will construct properly-formatted json
|
||||||
|
@ -247,6 +296,30 @@ The *data* object contains method-specific return data. This may be structured
|
||||||
data in response to a query, a simple error string in the case of a failed
|
data in response to a query, a simple error string in the case of a failed
|
||||||
request, or it may be omitted entirely if the error code alone is sufficient.
|
request, or it may be omitted entirely if the error code alone is sufficient.
|
||||||
|
|
||||||
|
_EVENTS_
|
||||||
|
|
||||||
|
Events are aaynchronous messages sent from a server to all registered clients.
|
||||||
|
The message format is identical to a _REQUEST_, but without an "id" field, and a
|
||||||
|
client must not send a response.
|
||||||
|
|
||||||
|
Example event message:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"method": "event-name",
|
||||||
|
"params": {
|
||||||
|
"key1": "value1",
|
||||||
|
"key2": "value2",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to receive any events, a client must first register to receive them by
|
||||||
|
sending a _event-receive_ request IPC. Once the success response has been sent
|
||||||
|
by the server, the client must expect that asynchronous event messages may be
|
||||||
|
sent by the server at any time, even between a request and the associated
|
||||||
|
response.
|
||||||
|
|
||||||
# ENVIRONMENT
|
# ENVIRONMENT
|
||||||
|
|
||||||
The following environment variables have an effect on wayvnc:
|
The following environment variables have an effect on wayvnc:
|
||||||
|
|
|
@ -42,6 +42,14 @@ a list of the available commands.
|
||||||
Running *wayvncctl command-name --help* returns a description of the server-side
|
Running *wayvncctl command-name --help* returns a description of the server-side
|
||||||
command and its available parameters.
|
command and its available parameters.
|
||||||
|
|
||||||
|
# ASYNCHRONOUS EVENTS
|
||||||
|
|
||||||
|
While *wayvncctl* normally terminates after sending one request and receiving
|
||||||
|
the corresponding reply, the *event-receive* command acts differently. Instead
|
||||||
|
of exiting immediately, *wayvncctl* waits for any events fr the server, printing
|
||||||
|
each to stdout as they arrive. This mode of operation will block until either
|
||||||
|
it receives a signal to terminate, or until the wayvnc server terminates.
|
||||||
|
|
||||||
# EXAMPLES
|
# EXAMPLES
|
||||||
|
|
||||||
Query the server for all available IPC command names:
|
Query the server for all available IPC command names:
|
||||||
|
|
Loading…
Reference in New Issue