Add ctl control socket and initial command infrastructure

This implements the first wayvncctl command: set-output

Signed-off-by: Jim Ramsay <i.am@jimramsay.com>
pull/178/head
Jim Ramsay 2022-10-17 08:23:40 -04:00 committed by Andri Yngvason
parent 19e1e14eab
commit 1a0e8aae97
5 changed files with 721 additions and 4 deletions

View File

@ -42,6 +42,7 @@ xbps-install wayvnc
* neatvnc * neatvnc
* pam (optional) * pam (optional)
* pixman * pixman
* jansson
### Build Dependencies ### Build Dependencies
* GCC * GCC
@ -51,7 +52,7 @@ xbps-install wayvnc
#### For Arch Linux #### For Arch Linux
``` ```
pacman -S base-devel libglvnd libxkbcommon pixman gnutls pacman -S base-devel libglvnd libxkbcommon pixman gnutls jansson
``` ```
#### For Fedora 31 #### For Fedora 31
@ -60,7 +61,7 @@ dnf install -y meson gcc ninja-build pkg-config egl-wayland egl-wayland-devel \
mesa-libEGL-devel mesa-libEGL libwayland-egl libglvnd-devel \ mesa-libEGL-devel mesa-libEGL libwayland-egl libglvnd-devel \
libglvnd-core-devel libglvnd mesa-libGLES-devel mesa-libGLES \ libglvnd-core-devel libglvnd mesa-libGLES-devel mesa-libGLES \
libxkbcommon-devel libxkbcommon libwayland-client libwayland \ libxkbcommon-devel libxkbcommon libwayland-client libwayland \
wayland-devel gnutls-devel wayland-devel gnutls-devel jansson-devel
``` ```
#### For Debian (unstable / testing) #### For Debian (unstable / testing)

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2022 Jim Ramsay
*
* 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.
*/
#pragma once
#include "output.h"
#include <wayland-client.h>
struct ctl;
struct cmd_response;
struct ctl_server_actions {
void* userdata;
struct cmd_response* (*on_output_cycle)(struct ctl*,
enum output_cycle_direction direction);
struct cmd_response* (*on_output_switch)(struct ctl*,
const char* output_name);
};
const char* default_ctl_socket_path();
struct ctl* ctl_server_new(const char* socket_path,
const struct ctl_server_actions* actions);
void ctl_server_destroy(struct ctl*);
void* ctl_server_userdata(struct ctl*);
struct cmd_response* cmd_ok(void);
struct cmd_response* cmd_failed(const char* fmt, ...);

View File

@ -91,6 +91,7 @@ sources = [
'src/pixels.c', 'src/pixels.c',
'src/transform-util.c', 'src/transform-util.c',
'src/json-ipc.c', 'src/json-ipc.c',
'src/ctl-server.c',
] ]
dependencies = [ dependencies = [

625
src/ctl-server.c 100644
View File

@ -0,0 +1,625 @@
/*
* Copyright (c) 2022 Jim Ramsay
*
* 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 <netdb.h>
#include <neatvnc.h>
#include <aml.h>
#include <jansson.h>
#include "output.h"
#include "ctl-server.h"
#include "json-ipc.h"
#include "util.h"
#include "strlcpy.h"
#define FAILED_TO(action) \
nvnc_log(NVNC_LOG_WARNING, "Failed to " action ": %m");
enum cmd_type {
CMD_SET_OUTPUT,
CMD_UNKNOWN,
};
static char* cmd_name[] = {
[CMD_SET_OUTPUT] = "set-output",
};
struct cmd {
enum cmd_type type;
};
struct cmd_set_output {
struct cmd cmd;
char target[64];
enum output_cycle_direction cycle;
};
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;
};
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 enum cmd_type parse_command_name(const char* name)
{
for (int i = 0; i < CMD_UNKNOWN; ++i) {
if (strcmp(name, cmd_name[i]) == 0) {
return i;
}
}
return CMD_UNKNOWN;
}
static struct cmd_set_output* cmd_set_output_new(json_t* args,
struct jsonipc_error* err)
{
const char* target = NULL;
const char* cycle = NULL;
if (json_unpack(args, "{s?s,s?s}",
"switch-to", &target,
"cycle", &cycle) == -1) {
jsonipc_error_printf(err, EINVAL,
"expecting \"switch-to\" or \"cycle\"");
return NULL;
}
if ((!target && !cycle) || (target && cycle)) {
jsonipc_error_printf(err, EINVAL,
"expecting exactly one of \"switch-to\" or \"cycle\"");
return NULL;
}
struct cmd_set_output* cmd = calloc(1, sizeof(*cmd));
cmd->cmd.type = CMD_SET_OUTPUT;
if (target) {
strlcpy(cmd->target, target, sizeof(cmd->target));
} else if (cycle) {
if (strncmp(cycle, "prev", 4) == 0)
cmd->cycle = OUTPUT_CYCLE_REVERSE;
else if (strcmp(cycle, "next") == 0)
cmd->cycle = OUTPUT_CYCLE_FORWARD;
else {
jsonipc_error_printf(err, EINVAL,
"cycle must either be \"next\" or \"prev\"");
free(cmd);
return NULL;
}
}
return cmd;
}
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 = parse_command_name(ipc->method);
struct cmd* cmd = NULL;
switch (cmd_type) {
case CMD_SET_OUTPUT:
cmd = (struct cmd*)cmd_set_output_new(ipc->params, err);
break;
default: {
json_t* allowed = json_array();
for (int i = 0; i < CMD_UNKNOWN; ++i)
json_array_append_new(allowed, json_string(cmd_name[i]));
jsonipc_error_set_new(err, ENOENT,
json_pack("{s:o, s:o}",
"error",
jprintf("Unknown command \"%s\"",
ipc->method),
"commands", allowed));
}
}
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 void client_advance_buffer(struct ctl_client* self, size_t len)
{
size_t remainder = self->read_len - len;
if (remainder > 0)
memmove(self->read_buffer, self->read_buffer + len, remainder);
self->read_len = remainder;
}
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);
client_advance_buffer(self, 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* ctl_server_dispatch_cmd(struct ctl* self, struct cmd* cmd)
{
assert(cmd->type != CMD_UNKNOWN);
const char* name = cmd_name[cmd->type];
nvnc_log(NVNC_LOG_INFO, "Dispatching control client command '%s'", name);
struct cmd_response* response = NULL;
switch (cmd->type) {
case CMD_SET_OUTPUT: {
struct cmd_set_output* c = (struct cmd_set_output*)cmd;
if (c->target[0] != '\0')
response = self->actions.on_output_switch(self, c->target);
else
response = self->actions.on_output_cycle(self, c->cycle);
}
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_jsonipc(struct ctl_client* self,
struct jsonipc_response* resp)
{
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 = json_array_append_new(self->response_queue, packed_response);
if (result != 0) {
nvnc_log(NVNC_LOG_WARNING, "Append failed");
goto failure;
}
client_set_aml_event_mask(self);
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);
}
static int client_enqueue_response(struct ctl_client* self,
struct cmd_response* response, json_t* id)
{
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);
}
static int client_enqueue_internal_error(struct ctl_client* self,
struct cmd_response* err)
{
int result = client_enqueue_response(self, err, NULL);
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, 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);
}
const char* default_ctl_socket_path()
{
static char buffer[128];
char* xdg_runtime = getenv("XDG_RUNTIME_DIR");
if (xdg_runtime)
snprintf(buffer, sizeof(buffer),
"%s/wayvncctl", xdg_runtime);
else
snprintf(buffer, sizeof(buffer),
"/tmp/wayvncctl-%d", getuid());
return buffer;
}
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 (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:
close(self->fd);
unlink(self->socket_path);
bind_failure:
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;
}

View File

@ -50,6 +50,7 @@
#include "cfg.h" #include "cfg.h"
#include "transform-util.h" #include "transform-util.h"
#include "usdt.h" #include "usdt.h"
#include "ctl-server.h"
#ifdef ENABLE_PAM #ifdef ENABLE_PAM
#include "pam_auth.h" #include "pam_auth.h"
@ -102,11 +103,14 @@ struct wayvnc {
int nr_clients; int nr_clients;
struct aml_ticker* performance_ticker; struct aml_ticker* performance_ticker;
struct ctl* ctl;
}; };
void wayvnc_exit(struct wayvnc* self); void wayvnc_exit(struct wayvnc* self);
void on_capture_done(struct screencopy* sc); void on_capture_done(struct screencopy* sc);
static void on_client_new(struct nvnc_client* client); static void on_client_new(struct nvnc_client* client);
void switch_to_output(struct wayvnc*, struct output*);
void switch_to_next_output(struct wayvnc*); void switch_to_next_output(struct wayvnc*);
void switch_to_prev_output(struct wayvnc*); void switch_to_prev_output(struct wayvnc*);
@ -438,6 +442,32 @@ void on_signal(void* obj)
wayvnc_exit(self); wayvnc_exit(self);
} }
struct cmd_response* on_output_cycle(struct ctl* ctl, enum output_cycle_direction direction)
{
struct wayvnc* self = ctl_server_userdata(ctl);
nvnc_log(NVNC_LOG_INFO, "ctl command: Rotating to %s output",
direction == OUTPUT_CYCLE_FORWARD ? "next" : "previous");
struct output* next = output_cycle(&self->outputs,
self->selected_output, direction);
switch_to_output(self, next);
return cmd_ok();
}
struct cmd_response* on_output_switch(struct ctl* ctl,
const char* output_name)
{
nvnc_log(NVNC_LOG_INFO, "ctl command: Switch to output \"%s\"", output_name);
struct wayvnc* self = ctl_server_userdata(ctl);
if (!output_name || output_name[0] == '\0')
return cmd_failed("Output name is required");
struct output* output = output_find_by_name(&self->outputs, output_name);
if (!output) {
return cmd_failed("No such output \"%s\"", output_name);
}
switch_to_output(self, output);
return cmd_ok();
}
int init_main_loop(struct wayvnc* self) int init_main_loop(struct wayvnc* self)
{ {
struct aml* loop = aml_get_default(); struct aml* loop = aml_get_default();
@ -726,6 +756,8 @@ int wayvnc_usage(FILE* stream, int rc)
" -k,--keyboard=<layout>[-<variant>] Select keyboard layout with an\n" " -k,--keyboard=<layout>[-<variant>] Select keyboard layout with an\n"
" optional variant.\n" " optional variant.\n"
" -s,--seat=<name> Select seat by name.\n" " -s,--seat=<name> Select seat by name.\n"
" -S,--socket=<path> Wayvnc control socket path.\n"
" Default: $XDG_RUNTIME_DIR/wayvncctl\n"
" -r,--render-cursor Enable overlay cursor rendering.\n" " -r,--render-cursor Enable overlay cursor rendering.\n"
" -f,--max-fps=<fps> Set the rate limit (default 30).\n" " -f,--max-fps=<fps> Set the rate limit (default 30).\n"
" -p,--show-performance Show performance counters.\n" " -p,--show-performance Show performance counters.\n"
@ -905,7 +937,7 @@ void set_selected_output(struct wayvnc* self, struct output* output) {
void switch_to_output(struct wayvnc* self, struct output* output) void switch_to_output(struct wayvnc* self, struct output* output)
{ {
if (self->selected_output == output) { if (self->selected_output == output) {
nvnc_log(NVNC_LOG_DEBUG, "No-op: Already selected output %s", nvnc_log(NVNC_LOG_INFO, "Already selected output %s",
output->name); output->name);
return; return;
} }
@ -920,6 +952,7 @@ void switch_to_next_output(struct wayvnc* self)
nvnc_log(NVNC_LOG_INFO, "Rotating to next output"); nvnc_log(NVNC_LOG_INFO, "Rotating to next output");
struct output* next = output_cycle(&self->outputs, struct output* next = output_cycle(&self->outputs,
self->selected_output, OUTPUT_CYCLE_FORWARD); self->selected_output, OUTPUT_CYCLE_FORWARD);
switch_to_output(self, next); switch_to_output(self, next);
} }
@ -963,13 +996,14 @@ int main(int argc, char* argv[])
const char* output_name = NULL; const char* output_name = NULL;
const char* seat_name = NULL; const char* seat_name = NULL;
const char* socket_path = NULL;
bool overlay_cursor = false; bool overlay_cursor = false;
bool show_performance = false; bool show_performance = false;
int max_rate = 30; int max_rate = 30;
bool disable_input = false; bool disable_input = false;
static const char* shortopts = "C:go:k:s:rf:hpudVvL:"; static const char* shortopts = "C:go:k:s:S:rf:hpudVvL:";
int drm_fd MAYBE_UNUSED = -1; int drm_fd MAYBE_UNUSED = -1;
int log_level = NVNC_LOG_WARNING; int log_level = NVNC_LOG_WARNING;
@ -980,6 +1014,7 @@ int main(int argc, char* argv[])
{ "output", required_argument, NULL, 'o' }, { "output", required_argument, NULL, 'o' },
{ "keyboard", required_argument, NULL, 'k' }, { "keyboard", required_argument, NULL, 'k' },
{ "seat", required_argument, NULL, 's' }, { "seat", required_argument, NULL, 's' },
{ "socket", required_argument, NULL, 'S' },
{ "render-cursor", no_argument, NULL, 'r' }, { "render-cursor", no_argument, NULL, 'r' },
{ "max-fps", required_argument, NULL, 'f' }, { "max-fps", required_argument, NULL, 'f' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
@ -1013,6 +1048,9 @@ int main(int argc, char* argv[])
case 's': case 's':
seat_name = optarg; seat_name = optarg;
break; break;
case 'S':
socket_path = optarg;
break;
case 'r': case 'r':
overlay_cursor = true; overlay_cursor = true;
break; break;
@ -1191,6 +1229,15 @@ int main(int argc, char* argv[])
self.performance_ticker = aml_ticker_new(1000000, on_perf_tick, self.performance_ticker = aml_ticker_new(1000000, on_perf_tick,
&self, NULL); &self, NULL);
const struct ctl_server_actions ctl_actions = {
.userdata = &self,
.on_output_cycle = on_output_cycle,
.on_output_switch = on_output_switch,
};
self.ctl = ctl_server_new(socket_path, &ctl_actions);
if (!self.ctl)
goto ctl_server_failure;
wl_display_dispatch_pending(self.display); wl_display_dispatch_pending(self.display);
while (!self.do_exit) { while (!self.do_exit) {
@ -1202,6 +1249,7 @@ int main(int argc, char* argv[])
nvnc_log(NVNC_LOG_INFO, "Exiting..."); nvnc_log(NVNC_LOG_INFO, "Exiting...");
screencopy_stop(&self.screencopy); screencopy_stop(&self.screencopy);
ctl_server_destroy(self.ctl);
nvnc_display_unref(self.nvnc_display); nvnc_display_unref(self.nvnc_display);
nvnc_close(self.nvnc); nvnc_close(self.nvnc);
@ -1222,6 +1270,7 @@ int main(int argc, char* argv[])
return 0; return 0;
ctl_server_failure:
capture_failure: capture_failure:
nvnc_display_unref(self.nvnc_display); nvnc_display_unref(self.nvnc_display);
nvnc_close(self.nvnc); nvnc_close(self.nvnc);