int ipc_handle_connection(int fd, uint32_t mask, void *data) { (void) fd; (void) data; sway_log(L_DEBUG, "Event on IPC listening socket"); assert(mask == WLC_EVENT_READABLE); int client_fd = accept(ipc_socket, NULL, NULL); if (client_fd == -1) { sway_log_errno(L_INFO, "Unable to accept IPC client connection"); return 0; } int flags; if ((flags=fcntl(client_fd, F_GETFD)) == -1 || fcntl(client_fd, F_SETFD, flags|FD_CLOEXEC) == -1) { sway_log_errno(L_INFO, "Unable to set CLOEXEC on IPC client socket"); return 0; } struct ipc_client* client = malloc(sizeof(struct ipc_client)); client->payload_length = 0; client->fd = client_fd; client->event_source = wlc_event_loop_add_fd(client_fd, WLC_EVENT_READABLE, ipc_client_handle_readable, client); list_add(ipc_client_list, client); return 0; }
int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) { struct ipc_client *client = data; if (mask & WLC_EVENT_ERROR) { sway_log(L_INFO, "IPC Client socket error, removing client"); client->fd = -1; ipc_client_disconnect(client); return 0; } if (mask & WLC_EVENT_HANGUP) { client->fd = -1; ipc_client_disconnect(client); return 0; } int read_available; if (ioctl(client_fd, FIONREAD, &read_available) == -1) { sway_log_errno(L_INFO, "Unable to read IPC socket buffer size"); ipc_client_disconnect(client); return 0; } // Wait for the rest of the command payload in case the header has already been read if (client->payload_length > 0) { if ((uint32_t)read_available >= client->payload_length) { ipc_client_handle_command(client); } return 0; } if (read_available < ipc_header_size) { return 0; } uint8_t buf[ipc_header_size]; uint32_t *buf32 = (uint32_t*)(buf + sizeof(ipc_magic)); ssize_t received = recv(client_fd, buf, ipc_header_size, 0); if (received == -1) { sway_log_errno(L_INFO, "Unable to receive header from IPC client"); ipc_client_disconnect(client); return 0; } if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) { sway_log(L_DEBUG, "IPC header check failed"); ipc_client_disconnect(client); return 0; } client->payload_length = buf32[0]; client->current_command = (enum ipc_command_type)buf32[1]; if (read_available - received >= (long)client->payload_length) { ipc_client_handle_command(client); } return 0; }
int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) { struct ipc_client *client = data; sway_log(L_DEBUG, "Event on IPC client socket %d", client_fd); if (mask & WLC_EVENT_ERROR) { sway_log(L_INFO, "IPC Client socket error, removing client"); ipc_client_disconnect(client); return 0; } if (mask & WLC_EVENT_HANGUP) { ipc_client_disconnect(client); return 0; } int read_available; ioctl(client_fd, FIONREAD, &read_available); // Wait for the rest of the command payload in case the header has already been read if (client->payload_length > 0) { if (read_available >= client->payload_length) { ipc_client_handle_command(client); } else { sway_log(L_DEBUG, "Too little data to read payload on IPC Client socket, waiting for more (%d < %d)", read_available, client->payload_length); } return 0; } if (read_available < ipc_header_size) { sway_log(L_DEBUG, "Too little data to read header on IPC Client socket, waiting for more (%d < %d)", read_available, ipc_header_size); return 0; } char buf[ipc_header_size]; ssize_t received = recv(client_fd, buf, ipc_header_size, 0); if (received == -1) { sway_log_errno(L_INFO, "Unable to receive header from IPC client"); ipc_client_disconnect(client); return 0; } if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) { sway_log(L_DEBUG, "IPC header check failed"); ipc_client_disconnect(client); return 0; } client->payload_length = *(uint32_t *)&buf[sizeof(ipc_magic)]; client->current_command = (enum ipc_command_type) *(uint32_t *)&buf[sizeof(ipc_magic)+4]; if (read_available - received >= client->payload_length) { ipc_client_handle_command(client); } return 0; }
void ipc_client_handle_command(struct ipc_client *client) { if (!sway_assert(client != NULL, "client != NULL")) { return; } char buf[client->payload_length + 1]; if (client->payload_length > 0) { ssize_t received = recv(client->fd, buf, client->payload_length, 0); if (received == -1) { sway_log_errno(L_INFO, "Unable to receive payload from IPC client"); ipc_client_disconnect(client); return; } } switch (client->current_command) { case IPC_COMMAND: { buf[client->payload_length] = '\0'; bool success = handle_command(config, buf); char reply[64]; int length = snprintf(reply, sizeof(reply), "{\"success\":%s}", success ? "true" : "false"); ipc_send_reply(client, reply, (uint32_t) length); break; } case IPC_GET_WORKSPACES: { list_t *workspaces = create_list(); container_map(&root_container, ipc_get_workspaces_callback, workspaces); char *json = json_list(workspaces); free_flat_list(workspaces); ipc_send_reply(client, json, strlen(json)); free(json); break; } case IPC_GET_OUTPUTS: { list_t *outputs = create_list(); container_map(&root_container, ipc_get_outputs_callback, outputs); char *json = json_list(outputs); free_flat_list(outputs); ipc_send_reply(client, json, strlen(json)); free(json); break; } default: sway_log(L_INFO, "Unknown IPC command type %i", client->current_command); ipc_client_disconnect(client); break; } client->payload_length = 0; }
bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length) { assert(payload); char data[ipc_header_size]; uint32_t *data32 = (uint32_t*)(data + sizeof(ipc_magic)); memcpy(data, ipc_magic, sizeof(ipc_magic)); data32[0] = payload_length; data32[1] = client->current_command; if (write(client->fd, data, ipc_header_size) == -1) { sway_log_errno(L_INFO, "Unable to send header to IPC client"); return false; } if (write(client->fd, payload, payload_length) == -1) { sway_log_errno(L_INFO, "Unable to send payload to IPC client"); return false; } return true; }
int ipc_handle_connection(int fd, uint32_t mask, void *data) { (void) fd; (void) data; sway_log(L_DEBUG, "Event on IPC listening socket"); assert(mask == WLC_EVENT_READABLE); int client_fd = accept(ipc_socket, NULL, NULL); if (client_fd == -1) { sway_log_errno(L_INFO, "Unable to accept IPC client connection"); return 0; } int flags; if ((flags=fcntl(client_fd, F_GETFD)) == -1 || fcntl(client_fd, F_SETFD, flags|FD_CLOEXEC) == -1) { sway_log_errno(L_INFO, "Unable to set CLOEXEC on IPC client socket"); close(client_fd); return 0; } pid_t pid = get_client_pid(client_fd); if (!(get_feature_policy(pid) & FEATURE_IPC)) { sway_log(L_INFO, "Permission to connect to IPC socket denied to %d", pid); const char *error = "{\"success\": false, \"message\": \"Permission denied\"}"; if (write(client_fd, &error, sizeof(error)) < (int)sizeof(error)) { sway_log(L_DEBUG, "Failed to write entire error"); } close(client_fd); return 0; } struct ipc_client* client = malloc(sizeof(struct ipc_client)); client->payload_length = 0; client->fd = client_fd; client->subscribed_events = 0; client->event_source = wlc_event_loop_add_fd(client_fd, WLC_EVENT_READABLE, ipc_client_handle_readable, client); list_add(ipc_client_list, client); return 0; }
void ipc_send_event(const char *json_string, enum ipc_command_type event) { int i; struct ipc_client *client; for (i = 0; i < ipc_client_list->length; i++) { client = ipc_client_list->items[i]; if ((client->subscribed_events & event_mask(event)) == 0) { continue; } client->current_command = event; if (!ipc_send_reply(client, json_string, (uint32_t) strlen(json_string))) { sway_log_errno(L_INFO, "Unable to send reply to IPC client"); ipc_client_disconnect(client); } } }
void ipc_client_handle_command(struct ipc_client *client) { if (!sway_assert(client != NULL, "client != NULL")) { return; } char buf[client->payload_length + 1]; if (client->payload_length > 0) { ssize_t received = recv(client->fd, buf, client->payload_length, 0); if (received == -1) { sway_log_errno(L_INFO, "Unable to receive payload from IPC client"); ipc_client_disconnect(client); return; } } switch (client->current_command) { case IPC_COMMAND: { buf[client->payload_length] = '\0'; struct cmd_results *results = handle_command(buf); const char *json = cmd_results_to_json(results); char reply[256]; int length = snprintf(reply, sizeof(reply), "%s", json); ipc_send_reply(client, reply, (uint32_t) length); free_cmd_results(results); break; } case IPC_SUBSCRIBE: { buf[client->payload_length] = '\0'; struct json_object *request = json_tokener_parse(buf); if (request == NULL) { ipc_send_reply(client, "{\"success\": false}", 18); ipc_client_disconnect(client); return; } // parse requested event types for (int i = 0; i < json_object_array_length(request); i++) { const char *event_type = json_object_get_string(json_object_array_get_idx(request, i)); if (strcmp(event_type, "workspace") == 0) { client->subscribed_events |= IPC_GET_WORKSPACES; } else { ipc_send_reply(client, "{\"success\": false}", 18); ipc_client_disconnect(client); json_object_put(request); return; } } json_object_put(request); ipc_send_reply(client, "{\"success\": true}", 17); break; } case IPC_GET_WORKSPACES: { json_object *workspaces = json_object_new_array(); container_map(&root_container, ipc_get_workspaces_callback, workspaces); const char *json_string = json_object_to_json_string(workspaces); ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); json_object_put(workspaces); // free break; } case IPC_GET_OUTPUTS: { json_object *outputs = json_object_new_array(); container_map(&root_container, ipc_get_outputs_callback, outputs); const char *json_string = json_object_to_json_string(outputs); ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); json_object_put(outputs); // free break; } case IPC_GET_VERSION: { #if defined SWAY_GIT_VERSION && defined SWAY_GIT_BRANCH && defined SWAY_VERSION_DATE char *full_version = calloc(strlen(SWAY_GIT_VERSION) + strlen(SWAY_GIT_BRANCH) + strlen(SWAY_VERSION_DATE) + 20, 1); strcat(full_version, SWAY_GIT_VERSION); strcat(full_version, " ("); strcat(full_version, SWAY_VERSION_DATE); strcat(full_version, ", branch \""); strcat(full_version, SWAY_GIT_BRANCH); strcat(full_version, "\")"); json_object *json = json_object_new_object(); json_object_object_add(json, "human_readable", json_object_new_string(full_version)); // Todo once we actually release a version json_object_object_add(json, "major", json_object_new_int(0)); json_object_object_add(json, "minor", json_object_new_int(0)); json_object_object_add(json, "patch", json_object_new_int(1)); #else json_object_object_add(json, "human_readable", json_object_new_string("version not found")); json_object_object_add(json, "major", json_object_new_int(0)); json_object_object_add(json, "minor", json_object_new_int(0)); json_object_object_add(json, "patch", json_object_new_int(0)); #endif const char *json_string = json_object_to_json_string(json); ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); json_object_put(json); // free free(full_version); break; } default: sway_log(L_INFO, "Unknown IPC command type %i", client->current_command); ipc_client_disconnect(client); return; } client->payload_length = 0; }
void ipc_client_handle_command(struct ipc_client *client) { if (!sway_assert(client != NULL, "client != NULL")) { return; } char *buf = malloc(client->payload_length + 1); if (!buf) { sway_log_errno(L_INFO, "Out of memory"); ipc_client_disconnect(client); return; } if (client->payload_length > 0) { ssize_t received = recv(client->fd, buf, client->payload_length, 0); if (received == -1) { sway_log_errno(L_INFO, "Unable to receive payload from IPC client"); ipc_client_disconnect(client); free(buf); return; } } buf[client->payload_length] = '\0'; const char *error_denied = "{ \"success\": false, \"error\": \"Permission denied\" }"; switch (client->current_command) { case IPC_COMMAND: { if (!(config->ipc_policy & IPC_FEATURE_COMMAND)) { goto exit_denied; } struct cmd_results *results = handle_command(buf, CONTEXT_IPC); const char *json = cmd_results_to_json(results); char reply[256]; int length = snprintf(reply, sizeof(reply), "%s", json); ipc_send_reply(client, reply, (uint32_t) length); free_cmd_results(results); goto exit_cleanup; } case IPC_SUBSCRIBE: { struct json_object *request = json_tokener_parse(buf); if (request == NULL) { ipc_send_reply(client, "{\"success\": false}", 18); sway_log_errno(L_INFO, "Failed to read request"); goto exit_cleanup; } // parse requested event types for (int i = 0; i < json_object_array_length(request); i++) { const char *event_type = json_object_get_string(json_object_array_get_idx(request, i)); if (strcmp(event_type, "workspace") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_WORKSPACE); } else if (strcmp(event_type, "barconfig_update") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_BARCONFIG_UPDATE); } else if (strcmp(event_type, "mode") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_MODE); } else if (strcmp(event_type, "window") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_WINDOW); } else if (strcmp(event_type, "modifier") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_MODIFIER); } else if (strcmp(event_type, "binding") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_BINDING); } else { ipc_send_reply(client, "{\"success\": false}", 18); json_object_put(request); sway_log_errno(L_INFO, "Failed to parse request"); goto exit_cleanup; } } json_object_put(request); ipc_send_reply(client, "{\"success\": true}", 17); goto exit_cleanup; } case IPC_GET_WORKSPACES: { if (!(config->ipc_policy & IPC_FEATURE_GET_WORKSPACES)) { goto exit_denied; } json_object *workspaces = json_object_new_array(); container_map(&root_container, ipc_get_workspaces_callback, workspaces); const char *json_string = json_object_to_json_string(workspaces); ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); json_object_put(workspaces); // free goto exit_cleanup; } case IPC_GET_INPUTS: { if (!(config->ipc_policy & IPC_FEATURE_GET_INPUTS)) { goto exit_denied; } json_object *inputs = json_object_new_array(); if (input_devices) { for(int i=0; i<input_devices->length; i++) { struct libinput_device *device = input_devices->items[i]; char* identifier = libinput_dev_unique_id(device); json_object *device_object = json_object_new_object(); json_object_object_add(device_object, "identifier", json_object_new_string(identifier)); json_object_array_add(inputs, device_object); free(identifier); } } const char *json_string = json_object_to_json_string(inputs); ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); json_object_put(inputs); goto exit_cleanup; } case IPC_GET_OUTPUTS: { if (!(config->ipc_policy & IPC_FEATURE_GET_OUTPUTS)) { goto exit_denied; } json_object *outputs = json_object_new_array(); container_map(&root_container, ipc_get_outputs_callback, outputs); const char *json_string = json_object_to_json_string(outputs); ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); json_object_put(outputs); // free goto exit_cleanup; } case IPC_GET_TREE: { if (!(config->ipc_policy & IPC_FEATURE_GET_TREE)) { goto exit_denied; } json_object *tree = ipc_json_describe_container_recursive(&root_container); const char *json_string = json_object_to_json_string(tree); ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); json_object_put(tree); goto exit_cleanup; } case IPC_GET_VERSION: { json_object *version = ipc_json_get_version(); const char *json_string = json_object_to_json_string(version); ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); json_object_put(version); // free goto exit_cleanup; } case IPC_SWAY_GET_PIXELS: { char response_header[9]; memset(response_header, 0, sizeof(response_header)); json_object *obj = json_tokener_parse(buf); json_object *o, *x, *y, *w, *h; json_object_object_get_ex(obj, "output", &o); json_object_object_get_ex(obj, "x", &x); json_object_object_get_ex(obj, "y", &y); json_object_object_get_ex(obj, "w", &w); json_object_object_get_ex(obj, "h", &h); struct wlc_geometry g = { .origin = { .x = json_object_get_int(x), .y = json_object_get_int(y) }, .size = { .w = json_object_get_int(w), .h = json_object_get_int(h) } }; swayc_t *output = swayc_by_test(&root_container, output_by_name_test, (void *)json_object_get_string(o)); json_object_put(obj); if (!output) { sway_log(L_ERROR, "IPC GET_PIXELS request with unknown output name"); ipc_send_reply(client, response_header, sizeof(response_header)); goto exit_cleanup; } struct get_pixels_request *req = malloc(sizeof(struct get_pixels_request)); req->client = client; req->output = output->handle; req->geo = g; list_add(ipc_get_pixel_requests, req); wlc_output_schedule_render(output->handle); goto exit_cleanup; } case IPC_GET_BAR_CONFIG: { if (!(config->ipc_policy & IPC_FEATURE_GET_BAR_CONFIG)) { goto exit_denied; } if (!buf[0]) { // Send list of configured bar IDs json_object *bars = json_object_new_array(); int i; for (i = 0; i < config->bars->length; ++i) { struct bar_config *bar = config->bars->items[i]; json_object_array_add(bars, json_object_new_string(bar->id)); } const char *json_string = json_object_to_json_string(bars); ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); json_object_put(bars); // free } else { // Send particular bar's details struct bar_config *bar = NULL; int i; for (i = 0; i < config->bars->length; ++i) { bar = config->bars->items[i]; if (strcmp(buf, bar->id) == 0) { break; } bar = NULL; } if (!bar) { const char *error = "{ \"success\": false, \"error\": \"No bar with that ID\" }"; ipc_send_reply(client, error, (uint32_t)strlen(error)); goto exit_cleanup; } json_object *json = ipc_json_describe_bar_config(bar); const char *json_string = json_object_to_json_string(json); ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); json_object_put(json); // free } goto exit_cleanup; } default: sway_log(L_INFO, "Unknown IPC command type %i", client->current_command); goto exit_cleanup; }