diff --git a/examples/event-watcher b/examples/event-watcher index a31b06b..5a7db4a 100755 --- a/examples/event-watcher +++ b/examples/event-watcher @@ -13,4 +13,4 @@ while IFS= read -r EVT; do connection_count_now "$count" ;; esac -done < <("$WAYVNCCTL" --json event-receive) +done < <("$WAYVNCCTL" --wait --reconnect --json event-receive) diff --git a/include/ctl-client.h b/include/ctl-client.h index 4287799..912d8d4 100644 --- a/include/ctl-client.h +++ b/include/ctl-client.h @@ -27,6 +27,7 @@ void ctl_client_destroy(struct ctl_client*); void* ctl_client_userdata(struct ctl_client*); #define PRINT_JSON 0x00000001 +#define RECONNECT 0x00000002 int ctl_client_connect(struct ctl_client* self, int timeout); int ctl_client_run_command(struct ctl_client* self, diff --git a/src/ctl-client.c b/src/ctl-client.c index dbd155e..96a652f 100644 --- a/src/ctl-client.c +++ b/src/ctl-client.c @@ -67,6 +67,7 @@ struct ctl_client* ctl_client_new(const char* socket_path, void* userdata) socket_path = default_ctl_socket_path(); struct ctl_client* new = calloc(1, sizeof(*new)); new->userdata = userdata; + new->fd = -1; if (strlen(socket_path) >= sizeof(new->addr.sun_path)) { 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); 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; socket_failure: @@ -88,42 +84,50 @@ socket_failure: 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; struct stat sb; - while (stat(self->addr.sun_path, &sb) != 0) { + while (stat(socket_path, &sb) != 0) { if (timeout == 0) { FAILED_TO("find socket path \"%s\"", - self->addr.sun_path); - goto stat_failure; + socket_path); + return 1; } if (needs_log) { needs_log = false; DEBUG("Waiting for socket path \"%s\" to appear", - self->addr.sun_path); + socket_path); } if (usleep(50000) == -1) { FAILED_TO("wait for socket path"); - goto stat_failure; + return -1; } } if (S_ISSOCK(sb.st_mode)) { - DEBUG("Found socket \"%s\"", self->addr.sun_path); + DEBUG("Found socket \"%s\"", socket_path); } else { WARN("Path \"%s\" exists but is not a socket (0x%x)", - self->addr.sun_path, sb.st_mode); - goto stat_failure; + socket_path, sb.st_mode); + 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, sizeof(self->addr)) != 0) { if (timeout == 0 || errno != ENOENT) { FAILED_TO("connect to unix socket \"%s\"", self->addr.sun_path); - goto stat_failure; return 1; } if (usleep(50000) == -1) { @@ -131,11 +135,21 @@ int ctl_client_connect(struct ctl_client* self, int timeout) 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; - -stat_failure: - return 1; } void ctl_client_destroy(struct ctl_client* self) @@ -190,22 +204,6 @@ failure: 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) { if (self->read_len == 0) { @@ -540,46 +538,105 @@ static void print_event(struct jsonipc_request* event, unsigned flags) 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; setup_signals(self); while (self->wait_for_events) { DEBUG("Waiting for an event"); 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; + } 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); } + 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 argc, char* argv[], unsigned flags) { - int result = -1; - struct jsonipc_request* request = ctl_client_parse_args(self, argc, argv); + int result = 1; + struct jsonipc_request* request = ctl_client_parse_args(self, argc, + argv); if (!request) goto parse_failure; - if (ctl_client_send_request(self, request) < 0) - goto send_failure; + if (strcmp(request->method, "event-receive") == 0) + 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); parse_failure: return result; diff --git a/src/wayvncctl.c b/src/wayvncctl.c index 87a983c..21b8d0b 100644 --- a/src/wayvncctl.c +++ b/src/wayvncctl.c @@ -55,6 +55,8 @@ static int wayvncctl_usage(FILE* stream, int rc) " Default: $XDG_RUNTIME_DIR/wayvncctl\n" " -w,--wait Wait for wayvnc to start up if it's\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" " -V,--version Show version info.\n" " -v,--verbose Be more verbose.\n" @@ -76,7 +78,7 @@ int main(int argc, char* argv[]) { struct wayvncctl self = { 0 }; - static const char* shortopts = "+S:hVvjw"; + static const char* shortopts = "+S:hVvjwr"; bool verbose = false; const char* socket_path = NULL; @@ -87,6 +89,7 @@ int main(int argc, char* argv[]) static const struct option longopts[] = { { "socket", required_argument, NULL, 'S' }, { "wait", no_argument, NULL, 'w' }, + { "reconnect", no_argument, NULL, 'r' }, { "json", no_argument, NULL, 'j' }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, @@ -106,6 +109,9 @@ int main(int argc, char* argv[]) case 'w': wait_for_socket = -1; break; + case 'r': + flags |= RECONNECT; + break; case 'j': flags |= PRINT_JSON; break; diff --git a/wayvncctl.scd b/wayvncctl.scd index cfdacbe..6c6ebeb 100644 --- a/wayvncctl.scd +++ b/wayvncctl.scd @@ -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 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* Produce json output to stdout. @@ -142,7 +146,7 @@ while IFS= read -r EVT; do connection_count_now "$count" ;; esac -done < <(wayvncctl --json event-receive) +done < <(wayvncctl --wait --reconnect --json event-receive) ``` # ENVIRONMENT