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_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/un.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <jansson.h>
|
||||
|
||||
#include "json-ipc.h"
|
||||
|
@ -47,6 +48,8 @@ struct ctl_client {
|
|||
char read_buffer[512];
|
||||
size_t read_len;
|
||||
|
||||
bool wait_for_events;
|
||||
|
||||
int fd;
|
||||
};
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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:
|
||||
|
|
100
src/ctl-server.c
100
src/ctl-server.c
|
@ -43,6 +43,7 @@ enum send_priority {
|
|||
enum cmd_type {
|
||||
CMD_HELP,
|
||||
CMD_VERSION,
|
||||
CMD_EVENT_RECEIVE,
|
||||
CMD_SET_OUTPUT,
|
||||
CMD_UNKNOWN,
|
||||
};
|
||||
|
@ -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",
|
||||
{
|
||||
|
@ -112,6 +118,7 @@ struct ctl_client {
|
|||
char* write_ptr;
|
||||
size_t write_len;
|
||||
bool drop_after_next_send;
|
||||
bool accept_events;
|
||||
};
|
||||
|
||||
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);
|
||||
break;
|
||||
case CMD_VERSION:
|
||||
case CMD_EVENT_RECEIVE:
|
||||
cmd = calloc(1, sizeof(*cmd));
|
||||
cmd->type = cmd_type;
|
||||
break;
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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
src/main.c
15
src/main.c
|
@ -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);
|
||||
|
@ -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)
|
||||
|
|
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
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue