Skip to content

Commit

Permalink
Add server event infrastructure
Browse files Browse the repository at this point in the history
Includes "client-connect" and "client-disconnect" events as
proof-of-concept.

Signed-off-by: Jim Ramsay <[email protected]>
  • Loading branch information
lack committed Nov 12, 2022
1 parent dfc45da commit db9c8d5
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 4 deletions.
12 changes: 12 additions & 0 deletions include/ctl-server.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,15 @@ void* ctl_server_userdata(struct ctl*);

struct cmd_response* cmd_ok(void);
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);
37 changes: 35 additions & 2 deletions src/ctl-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <poll.h>
#include <signal.h>
#include <jansson.h>

#include "json-ipc.h"
Expand All @@ -46,6 +47,8 @@ struct ctl_client {

char read_buffer[512];
size_t read_len;

bool wait_for_events;

int fd;
};
Expand Down Expand Up @@ -197,8 +200,6 @@ static json_t* read_one_object(struct ctl_client* self, int timeout_ms)
while (root == NULL) {
int n = poll(&pfd, 1, timeout_ms);
if (n == -1) {
if (errno == EINTR)
continue;
WARN("Error waiting for a response: %m");
break;
} else if (n == 0) {
Expand Down Expand Up @@ -342,6 +343,35 @@ static int ctl_client_print_response(struct ctl_client* self,
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 argc, char* argv[], unsigned flags)
{
Expand All @@ -359,6 +389,9 @@ int ctl_client_run_command(struct ctl_client* self,

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);
receive_failure:
send_failure:
Expand Down
100 changes: 98 additions & 2 deletions src/ctl-server.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ enum send_priority {
enum cmd_type {
CMD_HELP,
CMD_VERSION,
CMD_EVENT_RECEIVE,
CMD_SET_OUTPUT,
CMD_UNKNOWN,
};
Expand Down Expand Up @@ -70,6 +71,11 @@ static struct cmd_info cmd_list[] = {
"Query the version of the wayvnc process",
{{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",
"Switch the actively captured output",
{
Expand Down Expand Up @@ -112,6 +118,7 @@ struct ctl_client {
char* write_ptr;
size_t write_len;
bool drop_after_next_send;
bool accept_events;
};

struct ctl {
Expand Down Expand Up @@ -222,6 +229,7 @@ static struct cmd* parse_command(struct jsonipc_request* ipc,
cmd = (struct cmd*)cmd_set_output_new(ipc->params, err);
break;
case CMD_VERSION:
case CMD_EVENT_RECEIVE:
cmd = calloc(1, sizeof(*cmd));
cmd->type = cmd_type;
break;
Expand Down Expand Up @@ -344,7 +352,8 @@ static struct cmd_response* generate_version_object()
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);
const struct cmd_info* info = &cmd_list[cmd->type];
Expand All @@ -367,6 +376,10 @@ static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self, struct cmd
case CMD_VERSION:
response = generate_version_object();
break;
case CMD_EVENT_RECEIVE:
client->accept_events = true;
response = cmd_ok();
break;
case CMD_UNKNOWN:
break;
}
Expand Down Expand Up @@ -410,6 +423,7 @@ static int client_enqueue_jsonipc(struct ctl_client* self,
goto failure;
}
result = client_enqueue(self, packed_response, priority);
json_decref(packed_response);
if (result != 0)
nvnc_log(NVNC_LOG_WARNING, "Append failed");
failure:
Expand Down Expand Up @@ -553,7 +567,7 @@ static void recv_ready(struct ctl_client* client)
// handled by the main loop instead of doing the
// dispatch here
struct cmd_response* response =
ctl_server_dispatch_cmd(server, cmd);
ctl_server_dispatch_cmd(server, client, cmd);
if (!response)
goto no_response;
client_enqueue_response(client, response, request->id);
Expand Down Expand Up @@ -736,3 +750,85 @@ struct cmd_response* cmd_failed(const char* fmt, ...)
va_end(ap);
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 changes: 15 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,14 @@ static void on_client_cleanup(struct nvnc_client* client)
self->nr_clients--;
nvnc_log(NVNC_LOG_DEBUG, "Client disconnected, new client count: %d",
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) {
nvnc_log(NVNC_LOG_INFO, "Stopping screen capture");
screencopy_stop(&self->screencopy);
Expand All @@ -865,6 +873,13 @@ static void on_client_new(struct nvnc_client* client)
nvnc_set_client_cleanup_fn(client, on_client_cleanup);
nvnc_log(NVNC_LOG_DEBUG, "Client connected, new client count: %d",
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)
Expand Down
73 changes: 73 additions & 0 deletions wayvnc.scd
Original file line number Diff line number Diff line change
Expand Up @@ -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
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_

For multi-output wayland displays, this command switches which output is
Expand All @@ -189,6 +199,45 @@ which parameters are supplied:
*switch-to=output-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

The *wayvncctl(1)* command line utility will construct properly-formatted json
Expand Down Expand Up @@ -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
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

The following environment variables have an effect on wayvnc:
Expand Down
8 changes: 8 additions & 0 deletions wayvncctl.scd
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ a list of the available commands.
Running *wayvncctl command-name --help* returns a description of the server-side
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

Query the server for all available IPC command names:
Expand Down

0 comments on commit db9c8d5

Please sign in to comment.