Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wayvncctl events #178

Merged
merged 6 commits into from
Nov 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions examples/event-watcher
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

WAYVNCCTL=${WAYVNCCTL:-wayvncctl}

connection_count_now() {
echo "Total clients: $count"
}

while IFS= read -r EVT; do
case "$(jq -r '.method' <<<"$EVT")" in
client-*onnected)
count=$(jq -r '.params.connection_count' <<<"$EVT")
connection_count_now "$count"
;;
esac
done < <("$WAYVNCCTL" --json event-receive)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be convenient to have an option to write the response to an environment variable so you can skip the read.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to think of how we can do that.

One idea I had was to introduce a "script mode" where a user could run wayvncctl --event-script=/path/to/executable and then we would run that executable once for every event, with some reasonable calling convention that puts the event details in the env and/or cmdline. But I think I'd want to do that in a separate PR.

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);
3 changes: 3 additions & 0 deletions include/json-ipc.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ void jsonipc_error_cleanup(struct jsonipc_error*);
struct jsonipc_request* jsonipc_request_parse_new(json_t* root,
struct jsonipc_error* err);
struct jsonipc_request* jsonipc_request_new(const char* method, json_t* params);
struct jsonipc_request* jsonipc_event_new(const char* method, json_t* params);
struct jsonipc_request* jsonipc_event_parse_new(json_t* root,
struct jsonipc_error* err);
json_t* jsonipc_request_pack(struct jsonipc_request*, json_error_t* err);
void jsonipc_request_destroy(struct jsonipc_request*);

Expand Down
224 changes: 200 additions & 24 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 @@ -47,6 +48,8 @@ struct ctl_client {
char read_buffer[512];
size_t read_len;

bool wait_for_events;

int fd;
};

Expand Down Expand Up @@ -197,7 +200,7 @@ 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)
lack marked this conversation as resolved.
Show resolved Hide resolved
if (errno == EINTR && self->wait_for_events)
continue;
WARN("Error waiting for a response: %m");
break;
Expand Down Expand Up @@ -262,41 +265,76 @@ static void print_error(struct jsonipc_response* response, const char* method)
printf("\n");
}

static void print_help(json_t* data)
static void print_command_usage(const char* name, json_t* data)
{
char* desc = NULL;
json_t* params = NULL;
json_unpack(data, "{s:s, s?o}", "description", &desc,
"params", &params);
printf("Usage: wayvncctl [options] %s%s\n\n%s\n", name,
params ? " [params]" : "",
desc);
if (params) {
printf("\nParameters:");
const char* param_name;
json_t* param_value;
json_object_foreach(params, param_name, param_value) {
printf("\n --%s=...\n %s\n", param_name,
json_string_value(param_value));
}
}
printf("\nRun 'wayvncctl --help' for allowed Options\n");
}

static void print_event_details(const char* name, json_t* data)
{
char* desc = NULL;
json_t* params = NULL;
json_unpack(data, "{s:s, s?o}", "description", &desc,
"params", &params);
printf("Event: %s\n\n%s\n", name,
desc);
if (params) {
printf("\nParameters:");
const char* param_name;
json_t* param_value;
json_object_foreach(params, param_name, param_value) {
printf("\n %s:...\n %s\n", param_name,
json_string_value(param_value));
}
}
}

static void print_help(json_t* data, json_t* request)
{
if (json_object_get(data, "commands")) {
printf("Allowed commands:\n");
json_t* cmd_list = json_object_get(data, "commands");

size_t index;
json_t* value;

json_array_foreach(cmd_list, index, value) {
printf(" - %s\n", json_string_value(value));
}
printf("\nRun 'wayvncctl command-name --help' for command-specific details.\n");

printf("\nSupported events:\n");
json_t* evt_list = json_object_get(data, "events");
json_array_foreach(evt_list, index, value) {
printf(" - %s\n", json_string_value(value));
}
printf("\nRun 'wayvncctl help --event=event-name' for event-specific details.\n");
return;
}

bool is_command = json_object_get(request, "command");
const char* key;
json_t* value;

json_object_foreach(data, key, value) {
char* desc = NULL;
json_t* params = NULL;
json_unpack(value, "{s:s, s?o}", "description", &desc,
"params", &params);
printf("Usage: wayvncctl [options] %s%s\n\n%s\n", key,
params ? " [params]" : "",
desc);
if (params) {
printf("\nParameters:");
const char* param_name;
json_t* param_value;
json_object_foreach(params, param_name, param_value) {
printf("\n --%s=...\n %s\n", param_name,
json_string_value(param_value));
}
}
printf("\nRun 'wayvncctl --help' for allowed Options\n");
if (is_command)
print_command_usage(key, value);
else
print_event_details(key, value);
}
}

Expand All @@ -309,10 +347,12 @@ static void pretty_version(json_t* data)
printf(" %s: %s\n", key, json_string_value(value));
}

static void pretty_print(json_t* data, const char* method)
static void pretty_print(json_t* data,
struct jsonipc_request* request)
{
const char* method = request->method;
if (strcmp(method, "help") == 0)
print_help(data);
print_help(data, request->params);
else if (strcmp(method, "version") == 0)
pretty_version(data);
else
Expand All @@ -335,13 +375,146 @@ static int ctl_client_print_response(struct ctl_client* self,
if (flags & PRINT_JSON)
print_compact_json(response->data);
else if (response->code == 0)
pretty_print(response->data, request->method);
pretty_print(response->data, request);
else
print_error(response, request->method);
}
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 print_indent(int level)
{
for (int i = 0; i < level; ++i)
printf(" ");
}

static bool json_has_content(json_t* root)
{
if (!root)
return false;
size_t i;
const char* key;
json_t* value;
switch (json_typeof(root)) {
case JSON_NULL:
return false;
case JSON_INTEGER:
case JSON_REAL:
case JSON_TRUE:
case JSON_FALSE:
return true;
case JSON_STRING:
return json_string_value(root)[0] != '\0';
case JSON_OBJECT:
json_object_foreach(root, key, value)
if (json_has_content(value))
return true;
return false;
case JSON_ARRAY:
json_array_foreach(root, i, value)
if (json_has_content(value))
return true;
return false;
}
return false;
}

static void print_as_yaml(json_t* data, int level, bool needs_leading_newline)
{
size_t i;
const char* key;
json_t* value;
bool needs_indent = needs_leading_newline;
switch(json_typeof(data)) {
case JSON_NULL:
printf("<null>\n");
break;
case JSON_OBJECT:
if (json_object_size(data) > 0 && needs_leading_newline)
printf("\n");
json_object_foreach(data, key, value) {
if (!json_has_content(value))
continue;
if (needs_indent)
print_indent(level);
else
needs_indent = true;
printf("%s: ", key);
print_as_yaml(value, level + 1, true);
}
break;
case JSON_ARRAY:
if (json_array_size(data) > 0 && needs_leading_newline)
printf("\n");
json_array_foreach(data, i, value) {
if (!json_has_content(value))
continue;
print_indent(level);
printf("- ");
print_as_yaml(value, level + 1, json_is_array(value));
}
break;
case JSON_STRING:
printf("%s\n", json_string_value(data));
break;
case JSON_INTEGER:
printf("%" JSON_INTEGER_FORMAT "\n", json_integer_value(data));
break;
case JSON_REAL:
printf("%f\n", json_real_value(data));
break;
case JSON_TRUE:
printf("true\n");
break;
case JSON_FALSE:
printf("false\n");
break;
}
}

static void print_event(struct jsonipc_request* event, unsigned flags)
{
if (flags & PRINT_JSON) {
print_compact_json(event->json);
} else {
printf("\n%s:", event->method);
print_as_yaml(event->params, 1, true);
}
fflush(stdout);
}

static void ctl_client_event_loop(struct ctl_client* self, unsigned flags)
{
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);
if (!root)
break;
struct jsonipc_error err = JSONIPC_ERR_INIT;
struct jsonipc_request* event = jsonipc_event_parse_new(root, &err);
json_decref(root);
print_event(event, flags);
jsonipc_request_destroy(event);
}
}

int ctl_client_run_command(struct ctl_client* self,
int argc, char* argv[], unsigned flags)
{
Expand All @@ -359,6 +532,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, flags);

jsonipc_response_destroy(response);
receive_failure:
send_failure:
Expand Down
Loading