Add wayvncctl --reconnect option

Signed-off-by: Jim Ramsay <i.am@jimramsay.com>
pull/186/head
Jim Ramsay 2022-11-15 19:57:16 -05:00 committed by Andri Yngvason
parent 6e13974b27
commit 1d25535e7a
5 changed files with 126 additions and 58 deletions

View File

@ -13,4 +13,4 @@ while IFS= read -r EVT; do
connection_count_now "$count" connection_count_now "$count"
;; ;;
esac esac
done < <("$WAYVNCCTL" --json event-receive) done < <("$WAYVNCCTL" --wait --reconnect --json event-receive)

View File

@ -27,6 +27,7 @@ void ctl_client_destroy(struct ctl_client*);
void* ctl_client_userdata(struct ctl_client*); void* ctl_client_userdata(struct ctl_client*);
#define PRINT_JSON 0x00000001 #define PRINT_JSON 0x00000001
#define RECONNECT 0x00000002
int ctl_client_connect(struct ctl_client* self, int timeout); int ctl_client_connect(struct ctl_client* self, int timeout);
int ctl_client_run_command(struct ctl_client* self, int ctl_client_run_command(struct ctl_client* self,

View File

@ -67,6 +67,7 @@ struct ctl_client* ctl_client_new(const char* socket_path, void* userdata)
socket_path = default_ctl_socket_path(); socket_path = default_ctl_socket_path();
struct ctl_client* new = calloc(1, sizeof(*new)); struct ctl_client* new = calloc(1, sizeof(*new));
new->userdata = userdata; new->userdata = userdata;
new->fd = -1;
if (strlen(socket_path) >= sizeof(new->addr.sun_path)) { if (strlen(socket_path) >= sizeof(new->addr.sun_path)) {
errno = ENAMETOOLONG; errno = ENAMETOOLONG;
@ -76,11 +77,6 @@ struct ctl_client* ctl_client_new(const char* socket_path, void* userdata)
strcpy(new->addr.sun_path, socket_path); strcpy(new->addr.sun_path, socket_path);
new->addr.sun_family = AF_UNIX; new->addr.sun_family = AF_UNIX;
new->fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (new->fd < 0) {
FAILED_TO("create unix socket");
goto socket_failure;
}
return new; return new;
socket_failure: socket_failure:
@ -88,42 +84,50 @@ socket_failure:
return NULL; return NULL;
} }
int ctl_client_connect(struct ctl_client* self, int timeout) static int wait_for_socket(const char* socket_path, int timeout)
{ {
// TODO: Support arbitrary timeouts?
assert(timeout == 0 || timeout == -1);
// TODO: Use inotify instead of polling stat()
bool needs_log = true; bool needs_log = true;
struct stat sb; struct stat sb;
while (stat(self->addr.sun_path, &sb) != 0) { while (stat(socket_path, &sb) != 0) {
if (timeout == 0) { if (timeout == 0) {
FAILED_TO("find socket path \"%s\"", FAILED_TO("find socket path \"%s\"",
self->addr.sun_path); socket_path);
goto stat_failure; return 1;
} }
if (needs_log) { if (needs_log) {
needs_log = false; needs_log = false;
DEBUG("Waiting for socket path \"%s\" to appear", DEBUG("Waiting for socket path \"%s\" to appear",
self->addr.sun_path); socket_path);
} }
if (usleep(50000) == -1) { if (usleep(50000) == -1) {
FAILED_TO("wait for socket path"); FAILED_TO("wait for socket path");
goto stat_failure; return -1;
} }
} }
if (S_ISSOCK(sb.st_mode)) { if (S_ISSOCK(sb.st_mode)) {
DEBUG("Found socket \"%s\"", self->addr.sun_path); DEBUG("Found socket \"%s\"", socket_path);
} else { } else {
WARN("Path \"%s\" exists but is not a socket (0x%x)", WARN("Path \"%s\" exists but is not a socket (0x%x)",
self->addr.sun_path, sb.st_mode); socket_path, sb.st_mode);
goto stat_failure; return -1;
}
return 0;
}
static int try_connect(struct ctl_client* self, int timeout)
{
if (self->fd != -1)
close(self->fd);
self->fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (self->fd < 0) {
FAILED_TO("create unix socket");
return 1;
} }
while (connect(self->fd, (struct sockaddr*)&self->addr, while (connect(self->fd, (struct sockaddr*)&self->addr,
sizeof(self->addr)) != 0) { sizeof(self->addr)) != 0) {
if (timeout == 0 || errno != ENOENT) { if (timeout == 0 || errno != ENOENT) {
FAILED_TO("connect to unix socket \"%s\"", FAILED_TO("connect to unix socket \"%s\"",
self->addr.sun_path); self->addr.sun_path);
goto stat_failure;
return 1; return 1;
} }
if (usleep(50000) == -1) { if (usleep(50000) == -1) {
@ -131,11 +135,21 @@ int ctl_client_connect(struct ctl_client* self, int timeout)
return 1; return 1;
} }
} }
return 0;
}
int ctl_client_connect(struct ctl_client* self, int timeout)
{
// TODO: Support arbitrary timeouts?
assert(timeout == 0 || timeout == -1);
if (wait_for_socket(self->addr.sun_path, timeout) != 0)
return 1;
if (try_connect(self, timeout) != 0)
return 1;
return 0; return 0;
stat_failure:
return 1;
} }
void ctl_client_destroy(struct ctl_client* self) void ctl_client_destroy(struct ctl_client* self)
@ -190,22 +204,6 @@ failure:
return request; return request;
} }
static ssize_t ctl_client_send_request(struct ctl_client* self,
struct jsonipc_request* request)
{
json_error_t err;
json_t* packed = jsonipc_request_pack(request, &err);
if (!packed) {
WARN("Could not encode json: %s", err.text);
return -1;
}
char buffer[512];
int len = json_dumpb(packed, buffer, sizeof(buffer), JSON_COMPACT);
json_decref(packed);
DEBUG(">> %.*s", len, buffer);
return send(self->fd, buffer, len, MSG_NOSIGNAL);
}
static json_t* json_from_buffer(struct ctl_client* self) static json_t* json_from_buffer(struct ctl_client* self)
{ {
if (self->read_len == 0) { if (self->read_len == 0) {
@ -540,46 +538,105 @@ static void print_event(struct jsonipc_request* event, unsigned flags)
fflush(stdout); fflush(stdout);
} }
static void ctl_client_event_loop(struct ctl_client* self, unsigned flags) static ssize_t ctl_client_send_request(struct ctl_client* self,
struct jsonipc_request* request)
{ {
json_error_t err;
json_t* packed = jsonipc_request_pack(request, &err);
if (!packed) {
WARN("Could not encode json: %s", err.text);
return -1;
}
char buffer[512];
int len = json_dumpb(packed, buffer, sizeof(buffer), JSON_COMPACT);
json_decref(packed);
DEBUG(">> %.*s", len, buffer);
return send(self->fd, buffer, len, MSG_NOSIGNAL);
}
static struct jsonipc_response* ctl_client_run_single_command(struct ctl_client* self,
struct jsonipc_request *request)
{
if (ctl_client_send_request(self, request) < 0)
return NULL;
return ctl_client_wait_for_response(self);
}
static int ctl_client_register_for_events(struct ctl_client* self,
struct jsonipc_request* request)
{
struct jsonipc_response* response = ctl_client_run_single_command(self, request);
if (!response)
return -1;
int result = response->code;
jsonipc_response_destroy(response);
return result;
}
static int ctl_client_reconnect_event_loop(struct ctl_client* self,
struct jsonipc_request* request, int timeout)
{
if (ctl_client_connect(self, timeout) != 0)
return -1;
return ctl_client_register_for_events(self, request);
}
static int ctl_client_event_loop(struct ctl_client* self,
struct jsonipc_request* request, unsigned flags)
{
int result = ctl_client_register_for_events(self, request);
if (result != 0)
return result;
self->wait_for_events = true; self->wait_for_events = true;
setup_signals(self); setup_signals(self);
while (self->wait_for_events) { while (self->wait_for_events) {
DEBUG("Waiting for an event"); DEBUG("Waiting for an event");
json_t* root = read_one_object(self, -1); json_t* root = read_one_object(self, -1);
if (!root) if (!root) {
if (errno == ECONNRESET && flags & RECONNECT &&
ctl_client_reconnect_event_loop(self,
request, -1) == 0)
continue;
break; break;
}
struct jsonipc_error err = JSONIPC_ERR_INIT; struct jsonipc_error err = JSONIPC_ERR_INIT;
struct jsonipc_request* event = jsonipc_event_parse_new(root, &err); struct jsonipc_request* event = jsonipc_event_parse_new(root, &err);
json_decref(root); json_decref(root);
print_event(event, flags); print_event(event, flags);
jsonipc_request_destroy(event); jsonipc_request_destroy(event);
} }
return 0;
}
static int ctl_client_print_single_command(struct ctl_client* self,
struct jsonipc_request* request, unsigned flags)
{
struct jsonipc_response* response = ctl_client_run_single_command(self,
request);
if (!response)
return 1;
int result = ctl_client_print_response(self, request, response, flags);
jsonipc_response_destroy(response);
return result;
} }
int ctl_client_run_command(struct ctl_client* self, int ctl_client_run_command(struct ctl_client* self,
int argc, char* argv[], unsigned flags) int argc, char* argv[], unsigned flags)
{ {
int result = -1; int result = 1;
struct jsonipc_request* request = ctl_client_parse_args(self, argc, argv); struct jsonipc_request* request = ctl_client_parse_args(self, argc,
argv);
if (!request) if (!request)
goto parse_failure; goto parse_failure;
if (ctl_client_send_request(self, request) < 0) if (strcmp(request->method, "event-receive") == 0)
goto send_failure; result = ctl_client_event_loop(self, request, flags);
else
result = ctl_client_print_single_command(self, request, flags);
struct jsonipc_response* response = ctl_client_wait_for_response(self);
if (!response)
goto receive_failure;
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:
jsonipc_request_destroy(request); jsonipc_request_destroy(request);
parse_failure: parse_failure:
return result; return result;

View File

@ -55,6 +55,8 @@ static int wayvncctl_usage(FILE* stream, int rc)
" Default: $XDG_RUNTIME_DIR/wayvncctl\n" " Default: $XDG_RUNTIME_DIR/wayvncctl\n"
" -w,--wait Wait for wayvnc to start up if it's\n" " -w,--wait Wait for wayvnc to start up if it's\n"
" not already running.\n" " not already running.\n"
" -r,--reconnect If disconnected while waiting for\n"
" events, wait for wayvnc to restart.\n"
" -j,--json Output json on stdout.\n" " -j,--json Output json on stdout.\n"
" -V,--version Show version info.\n" " -V,--version Show version info.\n"
" -v,--verbose Be more verbose.\n" " -v,--verbose Be more verbose.\n"
@ -76,7 +78,7 @@ int main(int argc, char* argv[])
{ {
struct wayvncctl self = { 0 }; struct wayvncctl self = { 0 };
static const char* shortopts = "+S:hVvjw"; static const char* shortopts = "+S:hVvjwr";
bool verbose = false; bool verbose = false;
const char* socket_path = NULL; const char* socket_path = NULL;
@ -87,6 +89,7 @@ int main(int argc, char* argv[])
static const struct option longopts[] = { static const struct option longopts[] = {
{ "socket", required_argument, NULL, 'S' }, { "socket", required_argument, NULL, 'S' },
{ "wait", no_argument, NULL, 'w' }, { "wait", no_argument, NULL, 'w' },
{ "reconnect", no_argument, NULL, 'r' },
{ "json", no_argument, NULL, 'j' }, { "json", no_argument, NULL, 'j' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' }, { "version", no_argument, NULL, 'V' },
@ -106,6 +109,9 @@ int main(int argc, char* argv[])
case 'w': case 'w':
wait_for_socket = -1; wait_for_socket = -1;
break; break;
case 'r':
flags |= RECONNECT;
break;
case 'j': case 'j':
flags |= PRINT_JSON; flags |= PRINT_JSON;
break; break;

View File

@ -18,6 +18,10 @@ wayvncctl - A command line control client for wayvnc(1)
Wait for wayvnc to start up if it's not already running. Default: Exit Wait for wayvnc to start up if it's not already running. Default: Exit
immediately with an error if wayvnc is not running. immediately with an error if wayvnc is not running.
*-r,--reconnect*
If disconnected while waiting for events, wait for wayvnc to restart and
re-register for events. Default: Exit when wayvnc exits.
*-j, --json* *-j, --json*
Produce json output to stdout. Produce json output to stdout.
@ -142,7 +146,7 @@ while IFS= read -r EVT; do
connection_count_now "$count" connection_count_now "$count"
;; ;;
esac esac
done < <(wayvncctl --json event-receive) done < <(wayvncctl --wait --reconnect --json event-receive)
``` ```
# ENVIRONMENT # ENVIRONMENT