wayvnc/src/ctl-server.c

981 lines
25 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright (c) 2022-2023 Jim Ramsay
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <netdb.h>
#include <neatvnc.h>
#include <aml.h>
#include <jansson.h>
#include "output.h"
#include "ctl-commands.h"
#include "ctl-server.h"
#include "json-ipc.h"
#include "util.h"
#include "strlcpy.h"
#define FAILED_TO(action) \
nvnc_log(NVNC_LOG_ERROR, "Failed to " action ": %m");
enum send_priority {
SEND_FIFO,
SEND_IMMEDIATE,
};
struct cmd {
enum cmd_type type;
};
struct cmd_attach {
struct cmd cmd;
char display[128];
};
struct cmd_help {
struct cmd cmd;
char id[64];
bool id_is_command;
};
struct cmd_set_output {
struct cmd cmd;
char target[64];
enum output_cycle_direction cycle;
};
struct cmd_disconnect_client {
struct cmd cmd;
char id[64];
};
struct cmd_response {
int code;
json_t* data;
};
struct ctl_client {
int fd;
struct wl_list link;
struct ctl* server;
struct aml_handler* handler;
char read_buffer[512];
size_t read_len;
json_t* response_queue;
char* write_buffer;
char* write_ptr;
size_t write_len;
bool drop_after_next_send;
bool accept_events;
};
struct ctl {
char socket_path[255];
struct ctl_server_actions actions;
int fd;
struct aml_handler* handler;
struct wl_list clients;
};
static struct cmd_response* cmd_response_new(int code, json_t* data)
{
struct cmd_response* new = calloc(1, sizeof(struct cmd_response));
new->code = code;
new->data = data;
return new;
}
static void cmd_response_destroy(struct cmd_response* self)
{
json_decref(self->data);
free(self);
}
static struct cmd_attach* cmd_attach_new(json_t* args,
struct jsonipc_error* err)
{
const char* display = NULL;
if (json_unpack(args, "{s:s}", "display", &display) == -1) {
jsonipc_error_printf(err, EINVAL, "Missing display name");
return NULL;
}
struct cmd_attach* cmd = calloc(1, sizeof(*cmd));
strlcpy(cmd->display, display, sizeof(cmd->display));
return cmd;
}
static struct cmd_help* cmd_help_new(json_t* args,
struct jsonipc_error* err)
{
const char* command = NULL;
const char* event = NULL;
if (args && json_unpack(args, "{s?s, s?s}",
"command", &command,
"event", &event) == -1) {
jsonipc_error_printf(err, EINVAL,
"expecting \"command\" or \"event\" (optional)");
return NULL;
}
if (command && event) {
jsonipc_error_printf(err, EINVAL,
"expecting exacly one of \"command\" or \"event\"");
return NULL;
}
struct cmd_help* cmd = calloc(1, sizeof(*cmd));
if (command) {
strlcpy(cmd->id, command, sizeof(cmd->id));
cmd->id_is_command = true;
} else if (event) {
strlcpy(cmd->id, event, sizeof(cmd->id));
cmd->id_is_command = false;
}
return cmd;
}
static struct cmd_set_output* cmd_set_output_new(json_t* args,
struct jsonipc_error* err)
{
const char* target = NULL;
if (json_unpack(args, "{s:s}", "output-name", &target) == -1) {
jsonipc_error_printf(err, EINVAL, "Missing output name");
return NULL;
}
struct cmd_set_output* cmd = calloc(1, sizeof(*cmd));
strlcpy(cmd->target, target, sizeof(cmd->target));
return cmd;
}
static struct cmd_disconnect_client* cmd_disconnect_client_new(json_t* args,
struct jsonipc_error* err)
{
const char* id = NULL;
if (json_unpack(args, "{s:s}", "id", &id) == -1) {
jsonipc_error_printf(err, EINVAL, "Missing client id");
return NULL;
}
struct cmd_disconnect_client* cmd = calloc(1, sizeof(*cmd));
strlcpy(cmd->id, id, sizeof(cmd->id));
return cmd;
}
static json_t* list_allowed(struct cmd_info (*list)[], size_t len)
{
json_t* allowed = json_array();
for (size_t i = 0; i < len; ++i) {
json_array_append_new(allowed, json_string((*list)[i].name));
}
return allowed;
}
static json_t* list_allowed_commands()
{
return list_allowed(&ctl_command_list, CMD_LIST_LEN);
}
static json_t* list_allowed_events()
{
return list_allowed(&ctl_event_list, EVT_LIST_LEN);
}
static struct cmd* parse_command(struct jsonipc_request* ipc,
struct jsonipc_error* err)
{
nvnc_trace("Parsing command %s", ipc->method);
enum cmd_type cmd_type = ctl_command_parse_name(ipc->method);
struct cmd* cmd = NULL;
switch (cmd_type) {
case CMD_ATTACH:
cmd = (struct cmd*)cmd_attach_new(ipc->params, err);
break;
case CMD_HELP:
cmd = (struct cmd*)cmd_help_new(ipc->params, err);
break;
case CMD_OUTPUT_SET:
cmd = (struct cmd*)cmd_set_output_new(ipc->params, err);
break;
case CMD_CLIENT_DISCONNECT:
cmd = (struct cmd*)cmd_disconnect_client_new(ipc->params, err);
break;
case CMD_DETACH:
case CMD_VERSION:
case CMD_EVENT_RECEIVE:
case CMD_CLIENT_LIST:
case CMD_OUTPUT_LIST:
case CMD_OUTPUT_CYCLE:
case CMD_WAYVNC_EXIT:
cmd = calloc(1, sizeof(*cmd));
break;
case CMD_UNKNOWN:
jsonipc_error_set_new(err, ENOENT,
json_pack("{s:o, s:o}",
"error",
jprintf("Unknown command \"%s\"",
ipc->method),
"commands", list_allowed_commands()));
break;
}
if (cmd)
cmd->type = cmd_type;
return cmd;
}
static void client_destroy(struct ctl_client* self)
{
nvnc_trace("Destroying client %p", self);
aml_stop(aml_get_default(), self->handler);
aml_unref(self->handler);
close(self->fd);
json_array_clear(self->response_queue);
json_decref(self->response_queue);
wl_list_remove(&self->link);
free(self);
}
static void set_internal_error(struct cmd_response** err, int code,
const char* fmt, ...)
{
char msg[256];
va_list ap;
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
nvnc_log(NVNC_LOG_WARNING, msg);
*err = cmd_response_new(code, json_pack("{s:s}", "error", msg));
}
// Return values:
// >0: Number of bytes read
// 0: No bytes read (EAGAIN)
// -1: Fatal error. Check 'err' for details, or if 'err' is null, terminate the connection.
static ssize_t client_read(struct ctl_client* self, struct cmd_response** err)
{
size_t bufferspace = sizeof(self->read_buffer) - self->read_len;
if (bufferspace == 0) {
set_internal_error(err, EIO, "Buffer overflow");
return -1;
}
ssize_t n = recv(self->fd, self->read_buffer + self->read_len, bufferspace,
MSG_DONTWAIT);
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
nvnc_trace("recv: EAGAIN");
return 0;
}
set_internal_error(err, EIO, "Read failed: %m");
return -1;
} else if (n == 0) {
nvnc_log(NVNC_LOG_INFO, "Control socket client disconnected: %p", self);
errno = ENOTCONN;
return -1;
}
self->read_len += n;
nvnc_trace("Read %d bytes, total is now %d", n, self->read_len);
return n;
}
static json_t* client_next_object(struct ctl_client* self, struct cmd_response** ierr)
{
if (self->read_len == 0)
return NULL;
json_error_t err;
json_t* root = json_loadb(self->read_buffer, self->read_len,
JSON_DISABLE_EOF_CHECK, &err);
if (root) {
nvnc_log(NVNC_LOG_DEBUG, "<< %.*s", err.position, self->read_buffer);
advance_read_buffer(&self->read_buffer, &self->read_len, err.position);
} else if (json_error_code(&err) == json_error_premature_end_of_input) {
nvnc_trace("Awaiting more data");
} else {
set_internal_error(ierr, EINVAL, err.text);
}
return root;
}
static struct cmd_response* generate_help_object(const char* id, bool id_is_command)
{
struct cmd_info* info = id_is_command ?
ctl_command_by_name(id) :
ctl_event_by_name(id);
json_t* data;
if (!info) {
data = json_pack("{s:o, s:o}",
"commands", list_allowed_commands(),
"events", list_allowed_events());
} else {
json_t* param_list = NULL;
if (info->params[0].name) {
param_list = json_object();
for (struct cmd_param_info* param = info->params;
param->name; ++param)
json_object_set_new(param_list, param->name,
json_string(param->description));
}
data = json_pack("{s:{s:s, s:o*}}",
info->name,
"description", info->description,
"params", param_list);
}
struct cmd_response* response = cmd_ok();
response->data = data;
return response;
}
static struct cmd_response* generate_version_object()
{
struct cmd_response* response = cmd_ok();
response->data = json_pack("{s:s, s:s, s:s}",
"wayvnc", wayvnc_version,
"neatvnc", nvnc_version,
"aml", aml_version);
return response;
}
static struct ctl_server_client* ctl_server_client_first(struct ctl* self)
{
return self->actions.client_next(self, NULL);
}
static struct ctl_server_client* ctl_server_client_next(struct ctl* self,
struct ctl_server_client* prev)
{
return self->actions.client_next(self, prev);
}
static void ctl_server_client_get_info(struct ctl* self,
const struct ctl_server_client* client,
struct ctl_server_client_info* info)
{
return self->actions.client_info(client, info);
}
static struct cmd_response* generate_vnc_client_list(struct ctl* self)
{
struct cmd_response* response = cmd_ok();
response->data = json_array();
struct ctl_server_client* client;
for (client = ctl_server_client_first(self); client;
client = ctl_server_client_next(self, client)) {
struct ctl_server_client_info info = {};
ctl_server_client_get_info(self, client, &info);
char id_str[64];
snprintf(id_str, sizeof(id_str), "%d", info.id);
json_t* packed = json_pack("{s:s}", "id", id_str);
if (info.hostname)
json_object_set_new(packed, "hostname",
json_string(info.hostname));
if (info.username)
json_object_set_new(packed, "username",
json_string(info.username));
if (info.seat)
json_object_set_new(packed, "seat",
json_string(info.seat));
json_array_append_new(response->data, packed);
}
return response;
}
static struct cmd_response* generate_output_list(struct ctl* self)
{
struct ctl_server_output* outputs;
size_t num_outputs = self->actions.get_output_list(self, &outputs);
struct cmd_response* response = cmd_ok();
response->data = json_array();
for (size_t i = 0; i < num_outputs; ++i)
json_array_append_new(response->data, json_pack(
"{s:s, s:s, s:i, s:i, s:b, s:s}",
"name", outputs[i].name,
"description", outputs[i].description,
"height", outputs[i].height,
"width", outputs[i].width,
"captured", outputs[i].captured,
"power", outputs[i].power));
free(outputs);
return response;
}
static struct cmd_response* ctl_server_dispatch_cmd(struct ctl* self,
struct ctl_client* client, struct cmd* cmd)
{
const struct cmd_info* info = ctl_command_by_type(cmd->type);
assert(info);
nvnc_log(NVNC_LOG_INFO, "Dispatching control client command '%s'", info->name);
struct cmd_response* response = NULL;
switch (cmd->type) {
case CMD_ATTACH:{
struct cmd_attach* c = (struct cmd_attach*)cmd;
response = self->actions.on_attach(self, c->display);
break;
}
case CMD_HELP:{
struct cmd_help* c = (struct cmd_help*)cmd;
response = generate_help_object(c->id, c->id_is_command);
break;
}
case CMD_OUTPUT_SET: {
struct cmd_set_output* c = (struct cmd_set_output*)cmd;
response = self->actions.on_output_switch(self, c->target);
break;
}
case CMD_CLIENT_DISCONNECT: {
struct cmd_disconnect_client* c =
(struct cmd_disconnect_client*)cmd;
response = self->actions.on_disconnect_client(self, c->id);
break;
}
case CMD_DETACH:
response = self->actions.on_detach(self);
break;
case CMD_WAYVNC_EXIT:
response = self->actions.on_wayvnc_exit(self);
break;
case CMD_VERSION:
response = generate_version_object();
break;
case CMD_EVENT_RECEIVE:
client->accept_events = true;
response = cmd_ok();
break;
case CMD_CLIENT_LIST:
response = generate_vnc_client_list(self);
break;
case CMD_OUTPUT_LIST:
response = generate_output_list(self);
break;
case CMD_OUTPUT_CYCLE:
response = self->actions.on_output_cycle(self, OUTPUT_CYCLE_FORWARD);
break;
case CMD_UNKNOWN:
break;
}
return response;
}
static void client_set_aml_event_mask(struct ctl_client* self)
{
int mask = AML_EVENT_READ;
if (json_array_size(self->response_queue) > 0 ||
self->write_len)
mask |= AML_EVENT_WRITE;
aml_set_event_mask(self->handler, mask);
}
static int client_enqueue(struct ctl_client* self, json_t* message,
enum send_priority priority)
{
int result;
switch(priority) {
case SEND_IMMEDIATE:
result = json_array_insert(self->response_queue, 0, message);
break;
case SEND_FIFO:
result = json_array_append(self->response_queue, message);
break;
}
client_set_aml_event_mask(self);
return result;
}
static int client_enqueue_jsonipc(struct ctl_client* self,
struct jsonipc_response* resp, enum send_priority priority)
{
int result = 0;
json_error_t err;
json_t* packed_response = jsonipc_response_pack(resp, &err);
if (!packed_response) {
nvnc_log(NVNC_LOG_WARNING, "Pack failed: %s", err.text);
result = -1;
goto failure;
}
result = client_enqueue(self, packed_response, priority);
json_decref(packed_response);
if (result != 0)
nvnc_log(NVNC_LOG_WARNING, "Append failed");
failure:
jsonipc_response_destroy(resp);
return result;
}
static int client_enqueue_error(struct ctl_client* self,
struct jsonipc_error* err, json_t* id)
{
struct jsonipc_response* resp = jsonipc_error_response_new(err, id);
return client_enqueue_jsonipc(self, resp, SEND_FIFO);
}
static int client_enqueue__response(struct ctl_client* self,
struct cmd_response* response, json_t* id,
enum send_priority priority)
{
nvnc_log(NVNC_LOG_INFO, "Enqueueing response: %s (%d)",
response->code == 0 ? "OK" : "FAILED", response->code);
char* str = NULL;
if (response->data)
str = json_dumps(response->data, 0);
nvnc_log(NVNC_LOG_DEBUG, "Response data: %s", str);
if(str)
free(str);
struct jsonipc_response* resp =
jsonipc_response_new(response->code, response->data, id);
cmd_response_destroy(response);
return client_enqueue_jsonipc(self, resp, priority);
}
static int client_enqueue_response(struct ctl_client* self,
struct cmd_response* response, json_t* id)
{
return client_enqueue__response(self, response, id, SEND_FIFO);
}
static int client_enqueue_internal_error(struct ctl_client* self,
struct cmd_response* err)
{
int result = client_enqueue__response(self, err, NULL, SEND_IMMEDIATE);
if (result != 0)
client_destroy(self);
self->drop_after_next_send = true;
return result;
}
static void send_ready(struct ctl_client* client)
{
if (client->write_buffer) {
nvnc_trace("Continuing partial write (%d left)", client->write_len);
} else if (json_array_size(client->response_queue) > 0){
nvnc_trace("Sending new queued message");
json_t* item = json_array_get(client->response_queue, 0);
client->write_len = json_dumpb(item, NULL, 0, JSON_COMPACT);
client->write_buffer = calloc(1, client->write_len);
client->write_ptr = client->write_buffer;
json_dumpb(item, client->write_buffer, client->write_len,
JSON_COMPACT);
nvnc_log(NVNC_LOG_DEBUG, ">> %.*s", client->write_len, client->write_buffer);
json_array_remove(client->response_queue, 0);
} else {
nvnc_trace("Nothing to send");
}
if (!client->write_ptr)
goto no_data;
ssize_t n = send(client->fd, client->write_ptr, client->write_len,
MSG_NOSIGNAL|MSG_DONTWAIT);
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
nvnc_trace("send: EAGAIN");
goto send_eagain;
}
nvnc_log(NVNC_LOG_ERROR, "Could not send response: %m");
client_destroy(client);
return;
}
nvnc_trace("sent %d/%d bytes", n, client->write_len);
client->write_ptr += n;
client->write_len -= n;
send_eagain:
if (client->write_len == 0) {
nvnc_trace("Write buffer empty!");
free(client->write_buffer);
client->write_buffer = NULL;
client->write_ptr = NULL;
if (client->drop_after_next_send) {
nvnc_log(NVNC_LOG_WARNING, "Intentional disconnect");
client_destroy(client);
return;
}
} else {
nvnc_trace("Write buffer has %d remaining", client->write_len);
}
no_data:
client_set_aml_event_mask(client);
}
static void recv_ready(struct ctl_client* client)
{
struct ctl* server = client->server;
struct cmd_response* details = NULL;
switch (client_read(client, &details)) {
case 0: // Needs more data
return;
case -1: // Fatal error
if (details)
client_enqueue_internal_error(client, details);
else
client_destroy(client);
return;
default: // Read some data; check it
break;
}
json_t* root;
while (true) {
root = client_next_object(client, &details);
if (root == NULL)
break;
struct jsonipc_error jipc_err = JSONIPC_ERR_INIT;
struct jsonipc_request* request =
jsonipc_request_parse_new(root, &jipc_err);
if (!request) {
client_enqueue_error(client, &jipc_err,
NULL);
goto request_parse_failed;
}
struct cmd* cmd = parse_command(request, &jipc_err);
if (!cmd) {
client_enqueue_error(client, &jipc_err,
request->id);
goto cmdparse_failed;
}
// TODO: Enqueue the command (and request ID) to be
// handled by the main loop instead of doing the
// dispatch here
struct cmd_response* response =
ctl_server_dispatch_cmd(server, client, cmd);
if (!response)
goto no_response;
client_enqueue_response(client, response, request->id);
no_response:
free(cmd);
cmdparse_failed:
jsonipc_request_destroy(request);
request_parse_failed:
jsonipc_error_cleanup(&jipc_err);
json_decref(root);
}
if (details)
client_enqueue_internal_error(client, details);
}
static void on_ready(void* obj)
{
struct ctl_client* client = aml_get_userdata(obj);
uint32_t events = aml_get_revents(obj);
nvnc_trace("Client %p ready: 0x%x", client, events);
if (events & AML_EVENT_WRITE)
send_ready(client);
else if (events & AML_EVENT_READ)
recv_ready(client);
}
static void on_connection(void* obj)
{
nvnc_log(NVNC_LOG_DEBUG, "New connection");
struct ctl* server = aml_get_userdata(obj);
struct ctl_client* client = calloc(1, sizeof(*client));
if (!client) {
FAILED_TO("allocate a client object");
return;
}
client->server = server;
client->response_queue = json_array();
client->fd = accept(server->fd, NULL, 0);
if (client->fd < 0) {
FAILED_TO("accept a connection");
goto accept_failure;
}
client->handler = aml_handler_new(client->fd, on_ready, client, NULL);
if (!client->handler) {
FAILED_TO("create a loop handler");
goto handle_failure;
}
if (aml_start(aml_get_default(), client->handler) < 0) {
FAILED_TO("register for client events");
goto poll_start_failure;
}
wl_list_insert(&server->clients, &client->link);
nvnc_log(NVNC_LOG_INFO, "New control socket client connected: %p", client);
return;
poll_start_failure:
aml_unref(client->handler);
handle_failure:
close(client->fd);
accept_failure:
json_decref(client->response_queue);
free(client);
}
static int cleanup_old_socket(struct ctl* self, struct sockaddr* addr,
size_t addr_size)
{
struct stat sb;
if (stat(self->socket_path, &sb) == -1)
// Doesn't exist: safe to proceed.
return 0;
if (!S_ISSOCK(sb.st_mode)) {
nvnc_log(NVNC_LOG_ERROR, "Socket path '%s exists already and is not a socket.");
goto manual_intervention;
}
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
FAILED_TO("open a temporary socket");
goto manual_intervention;
}
nvnc_log(NVNC_LOG_DEBUG, "Connecting to existing socket in case it's stale");
if (connect(fd, addr, addr_size) == 0) {
close(fd);
nvnc_log(NVNC_LOG_ERROR, "Another wayvnc process is already running.");
nvnc_log(NVNC_LOG_ERROR, "Use the '-S' option to choose an alternate control socket location");
return -1;
}
nvnc_log(NVNC_LOG_DEBUG, "Connect failed: %m");
close(fd);
nvnc_log(NVNC_LOG_WARNING, "Deleting stale control socket path \"%s\"", self->socket_path);
if (unlink(self->socket_path) == -1) {
FAILED_TO("remove stale unix socket");
goto manual_intervention;
}
return 0;
manual_intervention:
nvnc_log(NVNC_LOG_ERROR, "Manually remove \"%s\" or use the '-S' option to choose an alternate socket location", self->socket_path);
return -1;
}
int ctl_server_init(struct ctl* self, const char* socket_path)
{
if (!socket_path) {
socket_path = default_ctl_socket_path();
if (!getenv("XDG_RUNTIME_DIR"))
nvnc_log(NVNC_LOG_WARNING, "$XDG_RUNTIME_DIR is not set. Falling back to control socket \"%s\"", socket_path);
}
strlcpy(self->socket_path, socket_path, sizeof(self->socket_path));
nvnc_log(NVNC_LOG_DEBUG, "Initializing wayvncctl socket: %s", self->socket_path);
wl_list_init(&self->clients);
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
};
if (strlen(self->socket_path) >= sizeof(addr.sun_path)) {
errno = ENAMETOOLONG;
FAILED_TO("create unix socket");
goto socket_failure;
}
strcpy(addr.sun_path, self->socket_path);
self->fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (self->fd < 0) {
FAILED_TO("create unix socket");
goto socket_failure;
}
if (cleanup_old_socket(self, (struct sockaddr*)&addr, sizeof(addr)) != 0)
goto bind_failure;
if (bind(self->fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
FAILED_TO("bind unix socket");
goto bind_failure;
}
if (listen(self->fd, 16) < 0) {
FAILED_TO("listen to unix socket");
goto listen_failure;
}
self->handler = aml_handler_new(self->fd, on_connection, self, NULL);
if (!self->handler) {
FAILED_TO("create a main loop handler");
goto handle_failure;
}
if (aml_start(aml_get_default(), self->handler) < 0) {
FAILED_TO("Register for server events");
goto poll_start_failure;
}
return 0;
poll_start_failure:
aml_unref(self->handler);
handle_failure:
listen_failure:
unlink(self->socket_path);
bind_failure:
close(self->fd);
socket_failure:
return -1;
}
static void ctl_server_stop(struct ctl* self)
{
aml_stop(aml_get_default(), self->handler);
aml_unref(self->handler);
struct ctl_client* client;
struct ctl_client* tmp;
wl_list_for_each_safe(client, tmp, &self->clients, link)
client_destroy(client);
close(self->fd);
unlink(self->socket_path);
}
struct ctl* ctl_server_new(const char* socket_path,
const struct ctl_server_actions* actions)
{
struct ctl* ctl = calloc(1, sizeof(*ctl));
memcpy(&ctl->actions, actions, sizeof(*actions));
if (ctl_server_init(ctl, socket_path) != 0) {
free(ctl);
return NULL;
}
return ctl;
}
void ctl_server_destroy(struct ctl* self)
{
ctl_server_stop(self);
free(self);
}
void* ctl_server_userdata(struct ctl* self)
{
return self->actions.userdata;
}
struct cmd_response* cmd_ok()
{
return cmd_response_new(0, NULL);
}
struct cmd_response* cmd_failed(const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
struct cmd_response* resp = cmd_response_new(1, json_pack("{s:o}",
"error", jvprintf(fmt, ap)));
va_end(ap);
return resp;
}
json_t* pack_connection_event_params(
const struct ctl_server_client_info *info,
int new_connection_count)
{
// TODO: Why is the id a string?
char id_str[64];
snprintf(id_str, sizeof(id_str), "%d", info->id);
return json_pack("{s:s, s:s?, s:s?, s:s?, s:i}",
"id", id_str,
"hostname", info->hostname,
"username", info->username,
"seat", info->seat,
"connection_count", new_connection_count);
}
int ctl_server_enqueue_event(struct ctl* self, enum event_type evt_type,
json_t* params)
{
const char* event_name = ctl_event_list[evt_type].name;
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;
}
static void ctl_server_event_connect(struct ctl* self,
enum event_type evt_type,
const struct ctl_server_client_info *info,
int new_connection_count)
{
json_t* params =
pack_connection_event_params(info, new_connection_count);
ctl_server_enqueue_event(self, evt_type, params);
}
void ctl_server_event_connected(struct ctl* self,
const struct ctl_server_client_info *info,
int new_connection_count)
{
ctl_server_event_connect(self, EVT_CLIENT_CONNECTED, info,
new_connection_count);
}
void ctl_server_event_disconnected(struct ctl* self,
const struct ctl_server_client_info *info,
int new_connection_count)
{
ctl_server_event_connect(self, EVT_CLIENT_DISCONNECTED, info,
new_connection_count);
}
void ctl_server_event_capture_changed(struct ctl* self,
const char* captured_output)
{
ctl_server_enqueue_event(self, EVT_CAPTURE_CHANGED,
json_pack("{s:s}", "output", captured_output));
}
void ctl_server_event_detached(struct ctl* self)
{
ctl_server_enqueue_event(self, EVT_DETACHED, json_object());
}