/**************************************************************************/ /* wayland_embedder.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ #include "wayland_embedder.h" #ifdef WAYLAND_ENABLED #ifdef TOOLS_ENABLED #include #ifdef __FreeBSD__ #include #else // Assume Linux. #include #endif #include "core/os/os.h" #include #include #include #define WAYLAND_EMBED_ID_MAX 1000 //#define WAYLAND_EMBED_DEBUG_LOGS_ENABLED #ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED // Gotta flush as we're doing this mess from a thread without any // synchronization. It's awful, I know, but the `print_*` utilities hang for // some reason during editor startup and I need some quick and dirty debugging. #define DEBUG_LOG_WAYLAND_EMBED(...) \ if (1) { \ printf("[PROXY] %s\n", vformat(__VA_ARGS__).utf8().ptr()); \ fflush(stdout); \ } else \ ((void)0) #else #define DEBUG_LOG_WAYLAND_EMBED(...) #endif // Wayland messages are structured with 32-bit words. #define WL_WORD_SIZE (sizeof(uint32_t)) // Event opcodes. Request opcodes are defined in the generated client headers. // We could generate server headers but they would clash (without modifications) // and we use just a few constants anyways. #define WL_DISPLAY_ERROR 0 #define WL_DISPLAY_DELETE_ID 1 #define WL_REGISTRY_GLOBAL 0 #define WL_REGISTRY_GLOBAL_REMOVE 1 #define WL_CALLBACK_DONE 0 #define WL_KEYBOARD_ENTER 1 #define WL_KEYBOARD_LEAVE 2 #define WL_KEYBOARD_KEY 3 #define WL_POINTER_ENTER 0 #define WL_POINTER_LEAVE 1 #define WL_POINTER_BUTTON 3 #define WL_SHM_FORMAT 0 #define WL_DRM_DEVICE 0 #define WL_DRM_FORMAT 1 #define WL_DRM_AUTHENTICATED 2 #define WL_DRM_CAPABILITIES 3 #define XDG_POPUP_CONFIGURE 0 size_t WaylandEmbedder::wl_array_word_offset(uint32_t p_size) { uint32_t pad = (WL_WORD_SIZE - (p_size % WL_WORD_SIZE)) % WL_WORD_SIZE; return (p_size + pad) / WL_WORD_SIZE; } const struct wl_interface *WaylandEmbedder::wl_interface_from_string(const char *name, size_t size) { for (size_t i = 0; i < (sizeof interfaces / sizeof *interfaces); ++i) { if (strncmp(name, interfaces[i]->name, size) == 0) { return interfaces[i]; } } return nullptr; } int WaylandEmbedder::wl_interface_get_destructor_opcode(const struct wl_interface *p_iface, uint32_t version) { ERR_FAIL_NULL_V(p_iface, -1); // FIXME: Figure out how to extract the destructor from the XML files. This // value is not currently exposed by wayland-scanner. for (int i = 0; i < p_iface->method_count; ++i) { const struct wl_message &m = p_iface->methods[i]; uint32_t destructor_version = String::to_int(m.signature); if (destructor_version <= version && (strcmp(m.name, "destroy") == 0 || strcmp(m.name, "release") == 0)) { return i; } } return -1; } struct WaylandEmbedder::WaylandObject *WaylandEmbedder::get_object(uint32_t p_global_id) { if (p_global_id == 0) { return nullptr; } // Server-allocated stuff starts at 0xff000000. bool is_server = p_global_id & 0xff000000; if (is_server) { p_global_id &= ~(0xff000000); } #ifdef DEV_ENABLED if (p_global_id >= WAYLAND_EMBED_ID_MAX) { // Oh no. Time for debug info! #ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED for (uint32_t id = 1; id < objects.reserved_size(); ++id) { WaylandObject &object = objects[id]; DEBUG_LOG_WAYLAND_EMBED(vformat(" - g0x%x (#%d): %s version %d, data 0x%x", id, id, object.interface->name, object.version, (uintptr_t)object.data)); } #endif // WAYLAND_EMBED_DEBUG_LOGS_ENABLED CRASH_NOW_MSG(vformat("Tried to access ID bigger than debug cap (%d > %d).", p_global_id, WAYLAND_EMBED_ID_MAX)); } #endif // DEV_ENABLED if (is_server) { if (server_objects.size() <= p_global_id) { return nullptr; } return &server_objects[p_global_id]; } else { if (objects.reserved_size() <= p_global_id) { return nullptr; } return &objects[p_global_id]; } } Error WaylandEmbedder::delete_object(uint32_t p_global_id) { WaylandObject *object = get_object(p_global_id); ERR_FAIL_NULL_V(object, ERR_DOES_NOT_EXIST); if (object->shared) { ERR_FAIL_V_MSG(FAILED, vformat("Tried to delete shared object g0x%x.", p_global_id)); } DEBUG_LOG_WAYLAND_EMBED(vformat("Deleting object %s g0x%x", object->interface ? object->interface->name : "UNKNOWN", p_global_id)); if (object->data) { memdelete(object->data); object->data = nullptr; } bool is_server = p_global_id & 0xff000000; if (is_server) { server_objects[p_global_id & ~(0xff000000)] = WaylandObject(); } else { objects.free(p_global_id); } registry_globals_names.erase(p_global_id); return OK; } uint32_t WaylandEmbedder::Client::allocate_server_id() { uint32_t new_id = INVALID_ID; if (free_server_ids.size() > 0) { int new_size = free_server_ids.size() - 1; new_id = free_server_ids[new_size] | 0xff000000; free_server_ids.resize_uninitialized(new_size); } else { new_id = allocated_server_ids | 0xff000000; ++allocated_server_ids; #ifdef DEV_ENABLED CRASH_COND_MSG(allocated_server_ids > WAYLAND_EMBED_ID_MAX, "Max server ID reached. This might indicate a leak."); #endif // DEV_ENABLED } DEBUG_LOG_WAYLAND_EMBED(vformat("Allocated server-side id 0x%x.", new_id)); return new_id; } struct WaylandEmbedder::WaylandObject *WaylandEmbedder::Client::get_object(uint32_t p_local_id) { if (p_local_id == INVALID_ID) { return nullptr; } if (global_instances.has(p_local_id)) { return &global_instances[p_local_id]; } if (fake_objects.has(p_local_id)) { return &fake_objects[p_local_id]; } if (!global_ids.has(p_local_id)) { return nullptr; } ERR_FAIL_NULL_V(embedder, nullptr); return embedder->get_object(get_global_id(p_local_id)); } Error WaylandEmbedder::Client::bind_global_id(uint32_t p_global_id, uint32_t p_local_id) { ERR_FAIL_COND_V(local_ids.has(p_global_id), ERR_ALREADY_EXISTS); ERR_FAIL_COND_V(global_ids.has(p_local_id), ERR_ALREADY_EXISTS); GlobalIdInfo gid_info; gid_info.id = p_global_id; DEBUG_LOG_WAYLAND_EMBED(vformat("Pushing g0x%x in the global id history", p_global_id)); gid_info.history_elem = global_id_history.push_back(p_global_id); global_ids[p_local_id] = gid_info; local_ids[p_global_id] = p_local_id; return OK; } Error WaylandEmbedder::Client::delete_object(uint32_t p_local_id) { if (fake_objects.has(p_local_id)) { #ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED WaylandObject *object = &fake_objects[p_local_id]; DEBUG_LOG_WAYLAND_EMBED(vformat("Deleting fake object %s l0x%x", object->interface ? object->interface->name : "UNKNOWN", p_local_id)); #endif if (!(p_local_id & 0xff000000)) { // wl_display::delete_id send_wayland_message(socket, DISPLAY_ID, 1, { p_local_id }); } fake_objects.erase(p_local_id); // We can skip everything else below, as fake objects don't have a global id. return OK; } ERR_FAIL_COND_V(!global_ids.has(p_local_id), ERR_DOES_NOT_EXIST); GlobalIdInfo gid_info = global_ids[p_local_id]; uint32_t global_id = gid_info.id; DEBUG_LOG_WAYLAND_EMBED(vformat("Erasing g0x%x from the global id history", global_id)); global_id_history.erase(gid_info.history_elem); if (global_instances.has(p_local_id)) { #ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED WaylandObject *object = &global_instances[p_local_id]; DEBUG_LOG_WAYLAND_EMBED(vformat("Deleting global instance %s l0x%x", object->interface ? object->interface->name : "UNKNOWN", p_local_id)); #endif // wl_display::delete_id send_wayland_message(socket, DISPLAY_ID, 1, { p_local_id }); // We don't want to delete the global object tied to this instance, so we'll only get rid of the local stuff. global_instances.erase(p_local_id); global_ids.erase(p_local_id); if (global_id != INVALID_ID) { local_ids.erase(global_id); } // We're done here. return OK; } if (wl_registry_instances.has(p_local_id)) { wl_registry_instances.erase(p_local_id); } WaylandObject *object = embedder->get_object(global_id); ERR_FAIL_NULL_V(object, ERR_DOES_NOT_EXIST); ERR_FAIL_COND_V_MSG(object->shared, ERR_INVALID_PARAMETER, vformat("Tried to delete shared object g0x%x.", global_id)); global_ids.erase(p_local_id); local_ids.erase(global_id); if (p_local_id & 0xff000000) { free_server_ids.push_back(p_local_id & ~(0xff000000)); } uint32_t *global_name = embedder->registry_globals_names.getptr(global_id); if (global_name) { { RegistryGlobalInfo &info = embedder->registry_globals[*global_name]; ERR_FAIL_COND_V_MSG(info.instance_counter == 0, ERR_BUG, "Instance counter inconsistency."); --info.instance_counter; if (info.destroyed && info.instance_counter == 0) { embedder->registry_globals.erase(*global_name); } } registry_globals_instances[*global_name].erase(p_local_id); } return embedder->delete_object(global_id); } // Returns INVALID_ID if the creation fails. In that case, the user can assume // that the client got kicked out. uint32_t WaylandEmbedder::Client::new_object(uint32_t p_local_id, const struct wl_interface *p_interface, int p_version, WaylandObjectData *p_data) { if (embedder == nullptr) { socket_error(socket, p_local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, "No embedder set."); ERR_FAIL_V(INVALID_ID); } if (get_object(p_local_id) != nullptr) { socket_error(socket, p_local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, vformat("Tried to create %s l0x%x but it already exists as %s", p_interface->name, p_local_id, get_object(p_local_id)->interface->name)); ERR_FAIL_V(INVALID_ID); } uint32_t new_global_id = embedder->new_object(p_interface, p_version, p_data); bind_global_id(new_global_id, p_local_id); return new_global_id; } uint32_t WaylandEmbedder::Client::new_server_object(uint32_t p_global_id, const struct wl_interface *p_interface, int p_version, WaylandObjectData *p_data) { if (embedder == nullptr) { socket_error(socket, get_local_id(p_global_id), WL_DISPLAY_ERROR_IMPLEMENTATION, "No embedder set."); ERR_FAIL_V(INVALID_ID); } uint32_t new_local_id = allocate_server_id(); embedder->new_server_object(p_global_id, p_interface, p_version, p_data); bind_global_id(p_global_id, new_local_id); return new_local_id; } WaylandEmbedder::WaylandObject *WaylandEmbedder::Client::new_fake_object(uint32_t p_local_id, const struct wl_interface *p_interface, int p_version, WaylandObjectData *p_data) { if (embedder == nullptr) { socket_error(socket, p_local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, "No embedder set."); ERR_FAIL_V(nullptr); } if (get_object(p_local_id) != nullptr) { socket_error(socket, p_local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, vformat("Object l0x%x already exists", p_local_id)); ERR_FAIL_V(nullptr); } WaylandObject &new_object = fake_objects[p_local_id]; new_object.interface = p_interface; new_object.version = p_version; new_object.data = p_data; return &new_object; } WaylandEmbedder::WaylandObject *WaylandEmbedder::Client::new_global_instance(uint32_t p_local_id, uint32_t p_global_id, const struct wl_interface *p_interface, int p_version, WaylandObjectData *p_data) { if (embedder == nullptr) { socket_error(socket, p_local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, "No embedder set."); ERR_FAIL_V(nullptr); } if (get_object(p_local_id) != nullptr) { socket_error(socket, p_local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, vformat("Object l0x%x already exists", p_local_id)); ERR_FAIL_V(nullptr); } WaylandObject &new_object = global_instances[p_local_id]; new_object.interface = p_interface; new_object.version = p_version; new_object.data = p_data; // FIXME: Track each instance properly. Global instances (the compatibility // mechanism) are particular as they're the only case where a global ID might // map to multiple local objects. In that case we need to mirror each event // which passes a registry object as an argument for each instance. GlobalIdInfo gid_info; gid_info.id = p_global_id; gid_info.history_elem = global_id_history.push_back(p_global_id); global_ids[p_local_id] = gid_info; // NOTE: Normally, for each client, there's a single local object per global // object, but global instances break this expectation. This is technically // wrong but should work fine, as we have special logic whenever needed. // // TODO: it might be nice to enforce that this table is never looked up for // global instances or even just log attempts. local_ids[p_global_id] = p_local_id; return &new_object; } Error WaylandEmbedder::Client::send_wl_drm_state(uint32_t p_id, WaylandDrmGlobalData *p_state) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); if (p_state->device.is_empty()) { // Not yet initialized. return OK; } LocalVector args; args.push_back(wl_arg_string(p_state->device.utf8().get_data())); send_wayland_event(socket, p_id, wl_drm_interface, WL_DRM_DEVICE, args); for (uint32_t format : p_state->formats) { Error err = send_wayland_message(socket, p_id, WL_DRM_FORMAT, { format }); ERR_FAIL_COND_V(err != OK, err); } if (p_state->authenticated) { Error err = send_wayland_message(socket, p_id, WL_DRM_AUTHENTICATED, {}); ERR_FAIL_COND_V(err != OK, err); } Error err = send_wayland_message(socket, p_id, WL_DRM_CAPABILITIES, { p_state->capabilities }); ERR_FAIL_COND_V(err != OK, err); return OK; } void WaylandEmbedder::cleanup_socket(int p_socket) { DEBUG_LOG_WAYLAND_EMBED(vformat("Cleaning up socket %d.", p_socket)); close(p_socket); for (size_t i = 0; i < pollfds.size(); ++i) { if (pollfds[i].fd == p_socket) { pollfds.remove_at_unordered(i); break; } } ERR_FAIL_COND(!clients.has(p_socket)); Client &client = clients[p_socket]; for (KeyValue &pair : client.fake_objects) { WaylandObject &object = pair.value; if (object.interface == &xdg_toplevel_interface) { XdgToplevelData *data = (XdgToplevelData *)object.data; CRASH_COND(data == nullptr); if (data->wl_subsurface_id != INVALID_ID) { // wl_subsurface::destroy() - xdg_toplevels are mapped to subsurfaces. send_wayland_message(compositor_socket, data->wl_subsurface_id, 0, {}); } if (!data->xdg_surface_handle.get()) { continue; } XdgSurfaceData *xdg_surf_data = (XdgSurfaceData *)data->xdg_surface_handle.get()->data; if (xdg_surf_data == nullptr) { continue; } if (!data->parent_handle.get()) { continue; } XdgToplevelData *parent_data = (XdgToplevelData *)data->parent_handle.get()->data; if (parent_data == nullptr) { continue; } if (!parent_data->xdg_surface_handle.get()) { continue; } XdgSurfaceData *parent_xdg_surf_data = (XdgSurfaceData *)parent_data->xdg_surface_handle.get()->data; if (parent_xdg_surf_data == nullptr) { continue; } for (uint32_t wl_seat_name : wl_seat_names) { WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)registry_globals[wl_seat_name].data; if (global_seat_data == nullptr) { continue; } if (global_seat_data->focused_surface_id == xdg_surf_data->wl_surface_id) { seat_name_leave_surface(wl_seat_name, xdg_surf_data->wl_surface_id); seat_name_enter_surface(wl_seat_name, parent_xdg_surf_data->wl_surface_id); } } } } for (List::Element *E = client.global_id_history.back(); E;) { uint32_t global_id = E->get(); E = E->prev(); WaylandObject *object = get_object(global_id); if (object == nullptr) { DEBUG_LOG_WAYLAND_EMBED(vformat("Skipping deletability check of object g0x%x as it's null.", global_id)); continue; } if (object->interface == nullptr) { DEBUG_LOG_WAYLAND_EMBED(vformat("Skipping deletability check of object g0x%x as it's invalid.", global_id)); continue; } DEBUG_LOG_WAYLAND_EMBED(vformat("Checking deletability of %s#g0x%x version %s", object->interface->name, global_id, object->version)); if (object->shared) { DEBUG_LOG_WAYLAND_EMBED("Shared, skipping."); continue; } if (object->interface == &wl_callback_interface) { // Those things self-destruct. DEBUG_LOG_WAYLAND_EMBED("wl_callback self destructs."); continue; } if (object->destroyed) { DEBUG_LOG_WAYLAND_EMBED("Already destroyed, skipping."); continue; } int destructor = wl_interface_get_destructor_opcode(object->interface, object->version); if (destructor >= 0) { DEBUG_LOG_WAYLAND_EMBED(vformat("Destroying %s#g0x%x", object->interface->name, global_id)); if (object->interface == &wl_surface_interface) { for (uint32_t wl_seat_name : wl_seat_names) { WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)registry_globals[wl_seat_name].data; if (global_seat_data) { if (global_seat_data->pointed_surface_id == global_id) { global_seat_data->pointed_surface_id = INVALID_ID; } if (global_seat_data->focused_surface_id == global_id) { global_seat_data->focused_surface_id = INVALID_ID; } } } } send_wayland_message(compositor_socket, global_id, destructor, {}); object->destroyed = true; if (global_id & 0xff000000) { delete_object(global_id); object = nullptr; } } if (object && !object->destroyed) { ERR_PRINT(vformat("Unreferenced object %s g0x%x (leak!)", object->interface->name, global_id)); } } uint32_t eclient_id = client.embedded_client_id; clients.erase(client.socket); WaylandObject *eclient = main_client->get_object(eclient_id); if (eclient) { EmbeddedClientData *eclient_data = (EmbeddedClientData *)eclient->data; ERR_FAIL_NULL(eclient_data); if (!eclient_data->disconnected) { // godot_embedded_client::disconnected send_wayland_message(main_client->socket, eclient_id, 0, {}); } eclient_data->disconnected = true; } } void WaylandEmbedder::socket_error(int p_socket, uint32_t p_object_id, uint32_t p_code, const String &p_message) { const char *err_name = "unknown"; switch (p_code) { case WL_DISPLAY_ERROR_INVALID_OBJECT: { err_name = "invalid_object"; } break; case WL_DISPLAY_ERROR_INVALID_METHOD: { err_name = "invalid_method"; } break; case WL_DISPLAY_ERROR_NO_MEMORY: { err_name = "no_memory"; } break; case WL_DISPLAY_ERROR_IMPLEMENTATION: { err_name = "implementation"; } break; } ERR_PRINT(vformat("Socket %d %s error: %s", p_socket, err_name, p_message)); LocalVector args; args.push_back(wl_arg_object(p_object_id)); args.push_back(wl_arg_uint(p_code)); args.push_back(wl_arg_string(vformat("[Godot Embedder] %s", p_message).utf8().get_data())); send_wayland_event(p_socket, DISPLAY_ID, wl_display_interface, WL_DISPLAY_ERROR, args); // So, here's the deal: from some extensive research I did, there are // absolutely zero safeguards for ensuring that the error message ends to the // client. It's absolutely tiny and takes _nothing_ to get there (less than // 4µs with a debug build on my machine), but still enough to get truncated in // the distance between `send_wayland_event` and `close`. // // Because of this we're going to give the client some slack: we're going to // wait for its socket to close (or whatever) or 1s, whichever happens first. // // Hopefully it's good enough for <1000 bytes :P struct pollfd pollfd = {}; pollfd.fd = p_socket; int ret = poll(&pollfd, 1, 1'000); if (ret == 0) { ERR_PRINT("Client timeout while disconnecting."); } if (ret < 0) { ERR_PRINT(vformat("Client error while disconnecting: %s", strerror(errno))); } close(p_socket); } void WaylandEmbedder::poll_sockets() { if (poll(pollfds.ptr(), pollfds.size(), -1) == -1) { CRASH_NOW_MSG(vformat("poll() failed, errno %d.", errno)); } // First handle everything but the listening socket (which is always the first // element), so that we can cleanup closed sockets before accidentally reusing // them (and breaking everything). for (size_t i = 1; i < pollfds.size(); ++i) { handle_fd(pollfds[i].fd, pollfds[i].revents); } handle_fd(pollfds[0].fd, pollfds[0].revents); } Error WaylandEmbedder::send_raw_message(int p_socket, std::initializer_list p_vecs, const LocalVector &p_fds) { struct msghdr msg = {}; msg.msg_iov = (struct iovec *)p_vecs.begin(); msg.msg_iovlen = p_vecs.size(); if (!p_fds.is_empty()) { size_t data_size = p_fds.size() * sizeof(int); msg.msg_control = Memory::alloc_aligned_static(CMSG_SPACE(data_size), CMSG_ALIGN(1)); msg.msg_controllen = CMSG_SPACE(data_size); struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(data_size); // NOTE: According to the linux man page cmsg(5), we shall not access the // pointer returned CMSG_DATA directly, due to alignment concerns. We should // copy data from a suitably aligned object instead. memcpy(CMSG_DATA(cmsg), p_fds.ptr(), data_size); } #ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED printf("[PROXY] Sending: "); for (const struct iovec &vec : p_vecs) { for (size_t i = 0; i < vec.iov_len; ++i) { printf("%.2x", ((const uint8_t *)vec.iov_base)[i]); } } printf("\n"); #endif sendmsg(p_socket, &msg, MSG_NOSIGNAL); if (msg.msg_control) { Memory::free_aligned_static(msg.msg_control); } return OK; } Error WaylandEmbedder::send_wayland_message(int p_socket, uint32_t p_id, uint32_t p_opcode, const uint32_t *p_args, const size_t p_args_words) { ERR_FAIL_COND_V(p_socket < 0, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_id == INVALID_ID, ERR_INVALID_PARAMETER); uint32_t args_size = p_args_words * sizeof *p_args; // Header is always 8 bytes long. uint32_t total_size = 8 + (args_size); uint32_t header[2] = { p_id, (total_size << 16) + p_opcode }; struct iovec vecs[2] = { { header, 8 }, // According to the sendmsg manual, these buffers should never be written to, // so this cast should be safe. { (void *)p_args, args_size }, }; struct msghdr msg = {}; msg.msg_iov = vecs; msg.msg_iovlen = std_size(vecs); #ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED printf("[PROXY] Sending: "); for (struct iovec &vec : vecs) { for (size_t i = 0; i < vec.iov_len; ++i) { printf("%.2x", ((const uint8_t *)vec.iov_base)[i]); } } printf("\n"); #endif if (sendmsg(p_socket, &msg, MSG_NOSIGNAL) < 0) { return FAILED; } return OK; } Error WaylandEmbedder::send_wayland_message(ProxyDirection p_direction, int p_socket, uint32_t p_id, const struct wl_interface &p_interface, uint32_t p_opcode, const LocalVector &p_args) { ERR_FAIL_COND_V(p_direction == ProxyDirection::CLIENT && p_opcode >= (uint32_t)p_interface.event_count, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_direction == ProxyDirection::COMPOSITOR && p_opcode >= (uint32_t)p_interface.method_count, ERR_INVALID_PARAMETER); const struct wl_message &msg = p_direction == ProxyDirection::CLIENT ? p_interface.events[p_opcode] : p_interface.methods[p_opcode]; LocalVector arg_buf; size_t arg_idx = 0; for (size_t sig_idx = 0; sig_idx < strlen(msg.signature); ++sig_idx) { if (arg_idx >= p_args.size()) { String err_msg = vformat("Not enough arguments for r0x%d %s.%s(%s) (only got %d)", p_id, p_interface.name, msg.name, msg.signature, p_args.size()); ERR_FAIL_COND_V_MSG(arg_idx >= p_args.size(), ERR_INVALID_PARAMETER, err_msg); } char sym = msg.signature[sig_idx]; if (sym >= '0' && sym <= '?') { // We don't care about version notices and nullability symbols. We can skip // those. continue; } const union wl_argument &arg = p_args[arg_idx]; switch (sym) { case 'i': { arg_buf.push_back((uint32_t)arg.i); } break; case 'u': { arg_buf.push_back(arg.u); } break; case 'f': { arg_buf.push_back((uint32_t)arg.f); } break; case 'o': { // We're encoding object arguments as uints because I don't think we can // reuse the whole opaque struct thing. arg_buf.push_back(arg.u); } break; case 'n': { arg_buf.push_back(arg.n); } break; case 's': { const char *str = p_args[arg_idx].s; // Wayland requires the string length to include the null terminator. uint32_t str_len = strlen(str) + 1; arg_buf.push_back(str_len); size_t data_begin_idx = arg_buf.size(); uint32_t str_words = wl_array_word_offset(str_len); arg_buf.resize(arg_buf.size() + str_words); strcpy((char *)(arg_buf.ptr() + data_begin_idx), str); } break; case 'a': { const wl_array *arr = p_args[arg_idx].a; arg_buf.push_back(arr->size); size_t data_begin_idx = arg_buf.size(); uint32_t words = wl_array_word_offset(arr->size); arg_buf.resize(arg_buf.size() + words); memcpy(arg_buf.ptr() + data_begin_idx, arr->data, arr->size); } break; // FDs (h) are encoded out-of-band. } ++arg_idx; } send_wayland_message(p_socket, p_id, p_opcode, arg_buf.ptr(), arg_buf.size()); return OK; } uint32_t WaylandEmbedder::new_object(const struct wl_interface *p_interface, int p_version, WaylandObjectData *p_data) { uint32_t new_global_id = allocate_global_id(); DEBUG_LOG_WAYLAND_EMBED(vformat("New object g0x%x %s", new_global_id, p_interface->name)); WaylandObject *new_object = get_object(new_global_id); new_object->interface = p_interface; new_object->version = p_version; new_object->data = p_data; return new_global_id; } WaylandEmbedder::WaylandObject *WaylandEmbedder::new_server_object(uint32_t p_global_id, const struct wl_interface *p_interface, int p_version, WaylandObjectData *p_data) { // The max ID will never increment more than one at a time, due to the // packed nature of IDs. libwayland already does similar assertions so it // just makes sense to double-check to avoid messing memory up or // allocating a huge buffer for nothing. uint32_t stripped_id = p_global_id & ~(0xff000000); ERR_FAIL_COND_V_MSG(stripped_id > server_objects.size(), nullptr, "Invalid new server id requested."); ERR_FAIL_COND_V_MSG(get_object(p_global_id) && get_object(p_global_id)->interface, nullptr, vformat("Tried to create %s g0x%x but it already exists as %s.", p_interface->name, p_global_id, get_object(p_global_id)->interface->name)); if (stripped_id == server_objects.size()) { server_objects.resize(server_objects.size() + 1); } DEBUG_LOG_WAYLAND_EMBED(vformat("New server object %s g0x%x", p_interface->name, p_global_id)); WaylandObject *new_object = get_object(p_global_id); new_object->interface = p_interface; new_object->version = p_version; new_object->data = p_data; return new_object; } void WaylandEmbedder::sync() { CRASH_COND_MSG(sync_callback_id, "Sync already in progress."); sync_callback_id = allocate_global_id(); get_object(sync_callback_id)->interface = &wl_callback_interface; get_object(sync_callback_id)->version = 1; send_wayland_message(compositor_socket, DISPLAY_ID, 0, { sync_callback_id }); DEBUG_LOG_WAYLAND_EMBED("Synchronizing"); while (true) { poll_sockets(); if (!sync_callback_id) { // Obj got deleted - sync is done. return; } } } // Returns the gid for the newly bound object, or an existing shared object if // necessary. uint32_t WaylandEmbedder::wl_registry_bind(uint32_t p_registry_id, uint32_t p_name, int p_version) { RegistryGlobalInfo &info = registry_globals[p_name]; uint32_t id = INVALID_ID; if (wl_interface_get_destructor_opcode(info.interface, p_version) < 0) { DEBUG_LOG_WAYLAND_EMBED(vformat("Binding instanced global %s %d", info.interface->name, p_version)); // Reusable object. if (info.reusable_objects.has(p_version) && info.reusable_objects[p_version] != INVALID_ID) { DEBUG_LOG_WAYLAND_EMBED("Already bound."); return info.reusable_objects[p_version]; } id = new_object(info.interface, p_version); ERR_FAIL_COND_V(id == INVALID_ID, INVALID_ID); info.reusable_objects[p_version] = id; get_object(id)->shared = true; } else { DEBUG_LOG_WAYLAND_EMBED(vformat("Binding global %s as g0x%x version %d", info.interface->name, id, p_version)); id = new_object(info.interface, p_version); } ERR_FAIL_COND_V(id == INVALID_ID, INVALID_ID); registry_globals_names[id] = p_name; LocalVector args; args.push_back(wl_arg_uint(info.compositor_name)); args.push_back(wl_arg_string(info.interface->name)); args.push_back(wl_arg_int(p_version)); args.push_back(wl_arg_new_id(id)); Error err = send_wayland_method(compositor_socket, p_registry_id, wl_registry_interface, WL_REGISTRY_BIND, args); ERR_FAIL_COND_V_MSG(err != OK, INVALID_ID, "Error while sending bind request."); return id; } void WaylandEmbedder::seat_name_enter_surface(uint32_t p_seat_name, uint32_t p_wl_surface_id) { WaylandSurfaceData *surf_data = (WaylandSurfaceData *)get_object(p_wl_surface_id)->data; CRASH_COND(surf_data == nullptr); Client *client = surf_data->client; CRASH_COND(client == nullptr); if (!client->local_ids.has(p_wl_surface_id)) { DEBUG_LOG_WAYLAND_EMBED("Called seat_name_enter_surface with an unknown surface"); return; } uint32_t local_surface_id = client->get_local_id(p_wl_surface_id); DEBUG_LOG_WAYLAND_EMBED(vformat("KB: Entering surface g0x%x", p_wl_surface_id)); for (uint32_t local_seat_id : client->registry_globals_instances[p_seat_name]) { WaylandSeatInstanceData *seat_data = (WaylandSeatInstanceData *)client->get_object(local_seat_id)->data; CRASH_COND(seat_data == nullptr); uint32_t local_keyboard_id = client->get_local_id(seat_data->wl_keyboard_id); if (local_keyboard_id != INVALID_ID) { // TODO: track keys. Not super important at the time of writing, since we // don't use that in the engine, although we should. // wl_keyboard::enter(serial, surface, keys) - keys will be empty for now send_wayland_message(client->socket, local_keyboard_id, 1, { serial_counter++, local_surface_id, 0 }); } } if (client->socket != main_client->socket) { // godot_embedded_client::window_focus_in send_wayland_message(main_client->socket, client->embedded_client_id, 2, {}); } } void WaylandEmbedder::seat_name_leave_surface(uint32_t p_seat_name, uint32_t p_wl_surface_id) { WaylandSurfaceData *surf_data = (WaylandSurfaceData *)get_object(p_wl_surface_id)->data; CRASH_COND(surf_data == nullptr); Client *client = surf_data->client; CRASH_COND(client == nullptr); if (!client->local_ids.has(p_wl_surface_id)) { DEBUG_LOG_WAYLAND_EMBED("Called seat_name_leave_surface with an unknown surface!"); return; } uint32_t local_surface_id = client->get_local_id(p_wl_surface_id); DEBUG_LOG_WAYLAND_EMBED(vformat("KB: Leaving surface g0x%x", p_wl_surface_id)); for (uint32_t local_seat_id : client->registry_globals_instances[p_seat_name]) { WaylandSeatInstanceData *seat_data = (WaylandSeatInstanceData *)client->get_object(local_seat_id)->data; CRASH_COND(seat_data == nullptr); uint32_t local_keyboard_id = client->get_local_id(seat_data->wl_keyboard_id); if (local_keyboard_id != INVALID_ID) { // wl_keyboard::enter(serial, surface, keys) - keys will be empty for now send_wayland_message(client->socket, local_keyboard_id, 2, { serial_counter++, local_surface_id }); } } if (client != main_client) { // godot_embedded_client::window_focus_out send_wayland_message(main_client->socket, client->embedded_client_id, 3, {}); } } int WaylandEmbedder::allocate_global_id() { uint32_t id = INVALID_ID; objects.request(id); objects[id] = WaylandObject(); DEBUG_LOG_WAYLAND_EMBED(vformat("Allocated new global id g0x%x", id)); #ifdef DEV_ENABLED if (id > WAYLAND_EMBED_ID_MAX) { // Oh no. Time for debug info! #ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED for (uint32_t i = 1; i < objects.reserved_size(); ++i) { WaylandObject &object = objects[id]; DEBUG_LOG_WAYLAND_EMBED(vformat(" - g0x%x (#%d): %s version %d, data 0x%x", i, i, object.interface->name, object.version, (uintptr_t)object.data)); } #endif // WAYLAND_EMBED_DEBUG_LOGS_ENABLED CRASH_NOW_MSG("Max ID reached. This might indicate a leak."); } #endif // DEV_ENABLED return id; } bool WaylandEmbedder::global_surface_is_window(uint32_t p_wl_surface_id) { WaylandObject *surface_object = get_object(p_wl_surface_id); ERR_FAIL_NULL_V(surface_object, false); if (surface_object->interface != &wl_surface_interface || surface_object->data == nullptr) { return false; } WaylandSurfaceData *surface_data = (WaylandSurfaceData *)surface_object->data; if (!surface_data->role_object_handle.get()) { return false; } WaylandObject *role_object = surface_data->role_object_handle.get(); return (role_object && role_object->interface == &xdg_toplevel_interface); } bool WaylandEmbedder::handle_generic_msg(Client *client, const WaylandObject *p_object, const struct wl_message *message, const struct msg_info *info, uint32_t *buf, uint32_t instance_id) { // We allow client-less events. CRASH_COND(client == nullptr && info->direction == ProxyDirection::COMPOSITOR); ERR_FAIL_NULL_V(p_object, false); bool valid = true; // Let's strip the header. uint32_t *body = buf + 2; size_t arg_idx = 0; size_t buf_idx = 0; size_t last_str_buf_idx = -1; uint32_t last_str_len = 0; for (size_t i = 0; i < strlen(message->signature); ++i) { ERR_FAIL_COND_V(buf_idx > (info->size / sizeof *body), false); char sym = message->signature[i]; if (sym >= '0' && sym <= '?') { // We don't care about version notices and nullability symbols. We can skip // those. continue; } switch (sym) { case 'a': { uint32_t array_len = body[buf_idx]; // We can't obviously go forward by just one byte. Let's skip to the end of // the array. buf_idx += wl_array_word_offset(array_len); } break; case 's': { uint32_t string_len = body[buf_idx]; last_str_buf_idx = buf_idx; last_str_len = string_len; // Same as the array. buf_idx += wl_array_word_offset(string_len); } break; case 'n': { uint32_t arg = body[buf_idx]; const struct wl_interface *new_interface = message->types[arg_idx]; uint32_t new_version = p_object->version; if (!new_interface && last_str_len != 0) { // When the protocol definition does not define an interface it reports a // string and an unsigned integer representing the interface and the // version requested. new_interface = wl_interface_from_string((char *)(body + last_str_buf_idx + 1), last_str_len); new_version = body[arg_idx - 1]; } if (new_interface == nullptr) { if (last_str_len > 0) { DEBUG_LOG_WAYLAND_EMBED(vformat("Unknown interface %s, marking packet as invalid.", (char *)(body + last_str_buf_idx + 1))); } else { DEBUG_LOG_WAYLAND_EMBED("Unknown interface, marking packet as invalid."); } valid = false; break; } if (info->direction == ProxyDirection::COMPOSITOR) { // FIXME: Create objects only if the packet is valid. uint32_t new_local_id = arg; body[buf_idx] = client->new_object(new_local_id, new_interface, new_version); if (body[buf_idx] == INVALID_ID) { valid = false; break; } } else if (info->direction == ProxyDirection::CLIENT) { uint32_t new_global_id = arg; if (client) { body[buf_idx] = client->new_server_object(new_global_id, new_interface, new_version); } else { new_server_object(new_global_id, new_interface, new_version); } if (body[buf_idx] == INVALID_ID) { valid = false; break; } } } break; case 'o': { if (!client) { break; } uint32_t obj_id = body[buf_idx]; if (obj_id == 0) { // Object arguments can be nil. break; } if (info->direction == ProxyDirection::CLIENT) { if (!client->local_ids.has(obj_id)) { DEBUG_LOG_WAYLAND_EMBED(vformat("Object argument g0x%x not found, marking packet as invalid.", obj_id)); valid = false; break; } body[buf_idx] = instance_id != INVALID_ID ? instance_id : client->get_local_id(obj_id); } else if (info->direction == ProxyDirection::COMPOSITOR) { if (!client->global_ids.has(obj_id)) { DEBUG_LOG_WAYLAND_EMBED(vformat("Object argument l0x%x not found, marking packet as invalid.", obj_id)); valid = false; break; } body[buf_idx] = client->get_global_id(obj_id); } } break; } ++arg_idx; ++buf_idx; } return valid; } WaylandEmbedder::MessageStatus WaylandEmbedder::handle_request(LocalObjectHandle p_object, uint32_t p_opcode, const uint32_t *msg_data, size_t msg_len) { ERR_FAIL_COND_V(!p_object.is_valid(), MessageStatus::HANDLED); WaylandObject *object = p_object.get(); Client *client = p_object.get_client(); ERR_FAIL_NULL_V(object, MessageStatus::HANDLED); // NOTE: Global ID may be null. uint32_t global_id = p_object.get_global_id(); uint32_t local_id = p_object.get_local_id(); ERR_FAIL_NULL_V(object->interface, MessageStatus::ERROR); const struct wl_interface *interface = object->interface; ERR_FAIL_COND_V((int)p_opcode >= interface->method_count, MessageStatus::ERROR); const struct wl_message message = interface->methods[p_opcode]; DEBUG_LOG_WAYLAND_EMBED(vformat("Client #%d -> %s::%s(%s) l0x%x g0x%x", client->socket, interface->name, message.name, message.signature, local_id, global_id)); const uint32_t *body = msg_data + 2; size_t body_len = msg_len - (WL_WORD_SIZE * 2); if (registry_globals_names.has(global_id)) { int global_name = registry_globals_names[global_id]; ERR_FAIL_COND_V(!registry_globals.has(global_name), MessageStatus::ERROR); RegistryGlobalInfo &global_info = registry_globals[global_name]; if (global_info.destroyed) { DEBUG_LOG_WAYLAND_EMBED("Skipping request for destroyed global object"); return MessageStatus::HANDLED; } } if (object->interface == &wl_display_interface && p_opcode == WL_DISPLAY_GET_REGISTRY) { // The gist of this is that the registry is a global and the compositor can // quite simply take for granted that a single client can access any global // bound from any registry. Let's remove all doubts by using a single // registry (also for efficiency) and doing fancy remaps. uint32_t local_registry_id = body[0]; // Note that the registry has already been allocated in the initialization // routine. for (KeyValue &pair : registry_globals) { uint32_t global_name = pair.key; RegistryGlobalInfo &global_info = pair.value; if (global_info.destroyed) { continue; } const struct wl_interface *global_interface = global_info.interface; if (client != main_client && embedded_interface_deny_list.has(global_interface)) { DEBUG_LOG_WAYLAND_EMBED(vformat("Skipped global announcement %s for embedded client.", global_interface->name)); continue; } LocalVector args; args.push_back(wl_arg_uint(global_name)); args.push_back(wl_arg_string(global_interface->name)); args.push_back(wl_arg_uint(global_info.version)); send_wayland_event(client->socket, local_registry_id, wl_registry_interface, WL_REGISTRY_GLOBAL, args); } client->wl_registry_instances.insert(local_registry_id); client->new_global_instance(local_registry_id, REGISTRY_ID, &wl_registry_interface, 1); return MessageStatus::HANDLED; } if (object->interface == &wl_registry_interface) { if (p_opcode == WL_REGISTRY_BIND) { // [Request] wl_registry::bind(usun) uint32_t global_name = body[0]; uint32_t interface_name_len = body[1]; //const char *interface_name = (const char *)(body + 2); uint32_t version = body[2 + wl_array_word_offset(interface_name_len)]; uint32_t new_local_id_idx = 2 + wl_array_word_offset(interface_name_len) + 1; uint32_t new_local_id = body[new_local_id_idx]; if (!registry_globals.has(global_name)) { socket_error(client->socket, local_id, WL_DISPLAY_ERROR_INVALID_METHOD, vformat("Invalid global object #%d", global_name)); return MessageStatus::HANDLED; } RegistryGlobalInfo &global_info = registry_globals[global_name]; ERR_FAIL_NULL_V(global_info.interface, MessageStatus::ERROR); version = MIN(global_info.version, version); if (global_info.interface == &godot_embedding_compositor_interface) { if (!client->registry_globals_instances.has(global_name)) { client->registry_globals_instances[global_name] = {}; } client->registry_globals_instances[global_name].insert(new_local_id); ++global_info.instance_counter; DEBUG_LOG_WAYLAND_EMBED("Bound embedded compositor interface."); client->new_fake_object(new_local_id, &godot_embedding_compositor_interface, 1); return MessageStatus::HANDLED; } WaylandObject *instance = nullptr; client->registry_globals_instances[global_name].insert(new_local_id); ++global_info.instance_counter; if (!client->registry_globals_instances.has(global_name)) { client->registry_globals_instances[global_name] = {}; } uint32_t bind_gid = wl_registry_bind(REGISTRY_ID, global_name, version); if (bind_gid == INVALID_ID) { socket_error(client->socket, local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, "Bind failed."); return MessageStatus::HANDLED; } WaylandObject *bind_obj = get_object(bind_gid); if (bind_obj == nullptr) { socket_error(client->socket, local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, "Bind failed."); return MessageStatus::HANDLED; } if (!bind_obj->shared) { client->bind_global_id(bind_gid, new_local_id); instance = bind_obj; } else { instance = client->new_global_instance(new_local_id, global_info.reusable_objects[version], global_info.interface, version); DEBUG_LOG_WAYLAND_EMBED(vformat("Instancing global #%d iface %s ver %d new id l0x%x g0x%x", global_name, global_info.interface->name, version, new_local_id, global_info.reusable_objects[version])); // Some interfaces report their state as soon as they're bound. Since // instances are handled by us, we need to track and report the relevant // data ourselves. if (global_info.interface == &wl_drm_interface) { Error err = client->send_wl_drm_state(new_local_id, (WaylandDrmGlobalData *)global_info.data); if (err != OK) { return MessageStatus::ERROR; } } else if (global_info.interface == &wl_shm_interface) { WaylandShmGlobalData *global_data = (WaylandShmGlobalData *)global_info.data; ERR_FAIL_NULL_V(global_data, MessageStatus::ERROR); for (uint32_t format : global_data->formats) { send_wayland_message(client->socket, new_local_id, WL_SHM_FORMAT, { format }); } } } ERR_FAIL_NULL_V(instance, MessageStatus::UNHANDLED); if (global_info.interface == &wl_seat_interface) { WaylandSeatInstanceData *new_data = memnew(WaylandSeatInstanceData); instance->data = new_data; } return MessageStatus::HANDLED; } } if (object->interface == &wl_compositor_interface && p_opcode == WL_COMPOSITOR_CREATE_SURFACE) { uint32_t new_local_id = body[0]; WaylandSurfaceData *data = memnew(WaylandSurfaceData); data->client = client; uint32_t new_global_id = client->new_object(new_local_id, &wl_surface_interface, object->version, data); ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); DEBUG_LOG_WAYLAND_EMBED(vformat("Keeping track of surface l0x%x g0x%x.", new_local_id, new_global_id)); send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id }); return MessageStatus::HANDLED; } if (object->interface == &wl_surface_interface) { WaylandSurfaceData *surface_data = (WaylandSurfaceData *)object->data; ERR_FAIL_NULL_V(surface_data, MessageStatus::ERROR); if (p_opcode == WL_SURFACE_DESTROY) { for (uint32_t wl_seat_name : wl_seat_names) { WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)registry_globals[wl_seat_name].data; ERR_FAIL_NULL_V(global_seat_data, MessageStatus::ERROR); if (global_seat_data->pointed_surface_id == global_id) { global_seat_data->pointed_surface_id = INVALID_ID; } if (global_seat_data->focused_surface_id == global_id) { global_seat_data->focused_surface_id = INVALID_ID; } } } else if (p_opcode == WL_SURFACE_COMMIT) { if (surface_data->role_object_handle.is_valid()) { WaylandObject *role_object = surface_data->role_object_handle.get(); if (role_object && role_object->interface) { DEBUG_LOG_WAYLAND_EMBED(vformat("!!!!! Committed surface g0x%x with role object %s id l0x%x", global_id, role_object->interface->name, surface_data->role_object_handle.get_local_id())); } if (role_object && role_object->interface == &xdg_toplevel_interface) { XdgToplevelData *toplevel_data = (XdgToplevelData *)role_object->data; ERR_FAIL_NULL_V(toplevel_data, MessageStatus::ERROR); // xdg shell spec requires clients to first send data and then commit the // surface. if (toplevel_data->is_embedded() && !toplevel_data->configured) { toplevel_data->configured = true; // xdg_surface::configure send_wayland_message(client->socket, toplevel_data->xdg_surface_handle.get_local_id(), 0, { serial_counter++ }); } } } send_wayland_message(compositor_socket, global_id, p_opcode, {}); return MessageStatus::HANDLED; } } if (object->interface == &wl_seat_interface) { uint32_t global_seat_name = registry_globals_names[global_id]; RegistryGlobalInfo &seat_global_info = registry_globals[global_seat_name]; WaylandSeatGlobalData *global_data = (WaylandSeatGlobalData *)seat_global_info.data; ERR_FAIL_NULL_V(global_data, MessageStatus::ERROR); WaylandSeatInstanceData *instance_data = (WaylandSeatInstanceData *)object->data; ERR_FAIL_NULL_V(instance_data, MessageStatus::ERROR); if (p_opcode == WL_SEAT_GET_POINTER) { ERR_FAIL_COND_V(global_id == INVALID_ID, MessageStatus::ERROR); // [Request] wl_seat::get_pointer(n); uint32_t new_local_id = body[0]; WaylandPointerData *new_data = memnew(WaylandPointerData); new_data->wl_seat_id = global_id; uint32_t new_global_id = client->new_object(new_local_id, &wl_pointer_interface, object->version, new_data); ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); instance_data->wl_pointer_id = new_global_id; send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id }); return MessageStatus::HANDLED; } if (p_opcode == WL_SEAT_GET_KEYBOARD) { ERR_FAIL_COND_V(global_id == INVALID_ID, MessageStatus::ERROR); // [Request] wl_seat::get_pointer(n); uint32_t new_local_id = body[0]; WaylandKeyboardData *new_data = memnew(WaylandKeyboardData); new_data->wl_seat_id = global_id; uint32_t new_global_id = client->new_object(new_local_id, &wl_keyboard_interface, object->version, new_data); ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); instance_data->wl_keyboard_id = new_global_id; send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id }); return MessageStatus::HANDLED; } } if (object->interface == &xdg_wm_base_interface) { if (p_opcode == XDG_WM_BASE_CREATE_POSITIONER) { uint32_t new_local_id = body[0]; uint32_t new_global_id = client->new_object(new_local_id, &xdg_positioner_interface, object->version, memnew(XdgPositionerData)); ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id }); return MessageStatus::HANDLED; } if (p_opcode == XDG_WM_BASE_GET_XDG_SURFACE) { // [Request] xdg_wm_base::get_xdg_surface(no). uint32_t new_local_id = body[0]; uint32_t surface_id = body[1]; uint32_t global_surface_id = client->get_global_id(surface_id); bool fake = (client != main_client); XdgSurfaceData *data = memnew(XdgSurfaceData); data->wl_surface_id = global_surface_id; if (fake) { client->new_fake_object(new_local_id, &xdg_surface_interface, object->version, data); DEBUG_LOG_WAYLAND_EMBED(vformat("Created fake xdg_surface l0x%x for surface l0x%x", new_local_id, surface_id)); } else { uint32_t new_global_id = client->new_object(new_local_id, &xdg_surface_interface, object->version, data); ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); DEBUG_LOG_WAYLAND_EMBED(vformat("Created real xdg_surface l0x%x g0x%x for surface l0x%x", new_local_id, new_global_id, surface_id)); send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id, global_surface_id }); } return MessageStatus::HANDLED; } } if (object->interface == &xdg_surface_interface) { XdgSurfaceData *xdg_surf_data = (XdgSurfaceData *)object->data; ERR_FAIL_NULL_V(xdg_surf_data, MessageStatus::ERROR); WaylandSurfaceData *surface_data = (WaylandSurfaceData *)get_object(xdg_surf_data->wl_surface_id)->data; ERR_FAIL_NULL_V(surface_data, MessageStatus::ERROR); bool is_embedded = client->fake_objects.has(local_id); if (p_opcode == XDG_SURFACE_GET_POPUP) { // [Request] xdg_surface::get_popup(no?o). uint32_t new_local_id = body[0]; uint32_t local_parent_id = body[1]; uint32_t local_positioner_id = body[2]; surface_data->role_object_handle = LocalObjectHandle(client, new_local_id); XdgPopupData *popup_data = memnew(XdgPopupData); popup_data->parent_handle = LocalObjectHandle(client, local_parent_id); if (!is_embedded) { uint32_t new_global_id = client->new_object(new_local_id, &xdg_popup_interface, object->version, popup_data); ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); uint32_t global_parent_id = client->get_global_id(local_parent_id); uint32_t global_positioner_id = client->get_global_id(local_positioner_id); send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id, global_parent_id, global_positioner_id }); return MessageStatus::HANDLED; } { // Popups are real, time to actually instantiate an xdg_surface. WaylandObject copy = *object; client->fake_objects.erase(local_id); global_id = client->new_object(local_id, copy.interface, copy.version, copy.data); ERR_FAIL_COND_V(global_id == INVALID_ID, MessageStatus::HANDLED); object = get_object(global_id); // xdg_wm_base::get_xdg_surface(no); send_wayland_message(compositor_socket, xdg_wm_base_id, 2, { global_id, xdg_surf_data->wl_surface_id }); } uint32_t new_global_id = client->new_object(new_local_id, &xdg_popup_interface, object->version, popup_data); ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); uint32_t global_parent_id = INVALID_ID; if (local_parent_id != INVALID_ID) { XdgSurfaceData *parent_xdg_surf_data = (XdgSurfaceData *)client->get_object(local_parent_id)->data; ERR_FAIL_NULL_V(parent_xdg_surf_data, MessageStatus::ERROR); WaylandSurfaceData *parent_surface_data = (WaylandSurfaceData *)get_object(parent_xdg_surf_data->wl_surface_id)->data; ERR_FAIL_NULL_V(parent_surface_data, MessageStatus::ERROR); WaylandObject *parent_role_obj = parent_surface_data->role_object_handle.get(); ERR_FAIL_NULL_V(parent_role_obj, MessageStatus::ERROR); XdgPositionerData *pos_data = (XdgPositionerData *)client->get_object(local_positioner_id)->data; ERR_FAIL_NULL_V(pos_data, MessageStatus::ERROR); if (parent_role_obj->interface == &xdg_toplevel_interface) { XdgToplevelData *parent_toplevel_data = (XdgToplevelData *)parent_role_obj->data; ERR_FAIL_NULL_V(parent_toplevel_data, MessageStatus::ERROR); if (parent_toplevel_data->is_embedded()) { // Embedded windows are subsurfaces of a parent window. We need to // "redirect" the popup request on the parent window and adjust the // positioner properly if needed. XdgToplevelData *main_parent_toplevel_data = (XdgToplevelData *)parent_toplevel_data->parent_handle.get()->data; ERR_FAIL_NULL_V(main_parent_toplevel_data, MessageStatus::ERROR); global_parent_id = main_parent_toplevel_data->xdg_surface_handle.get_global_id(); WaylandSubsurfaceData *subsurf_data = (WaylandSubsurfaceData *)get_object(parent_toplevel_data->wl_subsurface_id)->data; ERR_FAIL_NULL_V(subsurf_data, MessageStatus::ERROR); Point2i adj_pos = subsurf_data->position + pos_data->anchor_rect.position; // xdg_positioner::set_anchor_rect send_wayland_message(compositor_socket, client->get_global_id(local_positioner_id), 2, { (uint32_t)adj_pos.x, (uint32_t)adj_pos.y, (uint32_t)pos_data->anchor_rect.size.width, (uint32_t)pos_data->anchor_rect.size.height }); } } else { global_parent_id = client->get_global_id(local_parent_id); } } send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id, global_parent_id, client->get_global_id(local_positioner_id) }); return MessageStatus::HANDLED; } if (p_opcode == XDG_SURFACE_GET_TOPLEVEL) { // [Request] xdg_surface::get_toplevel(n). uint32_t new_local_id = body[0]; surface_data->role_object_handle = LocalObjectHandle(client, new_local_id); XdgToplevelData *data = memnew(XdgToplevelData); data->xdg_surface_handle = LocalObjectHandle(client, local_id); if (is_embedded) { client->new_fake_object(new_local_id, &xdg_toplevel_interface, object->version, data); client->embedded_window_id = new_local_id; // godot_embedded_client::window_embedded() send_wayland_message(main_client->socket, client->embedded_client_id, 1, {}); } else { uint32_t new_global_id = client->new_object(new_local_id, &xdg_toplevel_interface, object->version, data); ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); if (main_toplevel_id == 0) { main_toplevel_id = new_global_id; DEBUG_LOG_WAYLAND_EMBED(vformat("main toplevel set to gx0%x.", main_toplevel_id)); } send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id }); } return MessageStatus::HANDLED; } } if (object->interface == &xdg_positioner_interface) { XdgPositionerData *pos_data = (XdgPositionerData *)object->data; ERR_FAIL_NULL_V(pos_data, MessageStatus::ERROR); if (p_opcode == XDG_POSITIONER_SET_ANCHOR_RECT) { // Args: int x, int y, int width, int height. pos_data->anchor_rect = Rect2i(body[0], body[1], body[2], body[3]); send_wayland_message(compositor_socket, global_id, p_opcode, body, body_len); return MessageStatus::HANDLED; } } if (object->interface == &xdg_toplevel_interface && p_opcode == XDG_TOPLEVEL_DESTROY) { if (client->fake_objects.has(local_id)) { XdgToplevelData *data = (XdgToplevelData *)object->data; ERR_FAIL_NULL_V(data, MessageStatus::ERROR); XdgSurfaceData *xdg_surf_data = nullptr; if (data->xdg_surface_handle.is_valid()) { xdg_surf_data = (XdgSurfaceData *)data->xdg_surface_handle.get()->data; ERR_FAIL_NULL_V(xdg_surf_data, MessageStatus::ERROR); } ERR_FAIL_NULL_V(xdg_surf_data, MessageStatus::ERROR); XdgSurfaceData *parent_xdg_surf_data = nullptr; { XdgToplevelData *parent_data = nullptr; if (data->parent_handle.get()) { parent_data = (XdgToplevelData *)data->parent_handle.get()->data; ERR_FAIL_NULL_V(parent_data, MessageStatus::ERROR); } if (parent_data && parent_data->xdg_surface_handle.get()) { parent_xdg_surf_data = (XdgSurfaceData *)parent_data->xdg_surface_handle.get()->data; ERR_FAIL_NULL_V(parent_xdg_surf_data, MessageStatus::ERROR); } } for (uint32_t wl_seat_name : wl_seat_names) { WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)registry_globals[wl_seat_name].data; ERR_FAIL_NULL_V(global_seat_data, MessageStatus::ERROR); if (global_seat_data->focused_surface_id == xdg_surf_data->wl_surface_id) { if (xdg_surf_data) { seat_name_leave_surface(wl_seat_name, xdg_surf_data->wl_surface_id); } if (parent_xdg_surf_data) { seat_name_enter_surface(wl_seat_name, parent_xdg_surf_data->wl_surface_id); } } } // wl_display::delete_id send_wayland_message(client->socket, local_id, p_opcode, {}); if (local_id == client->embedded_window_id) { client->embedded_window_id = 0; } if (data->wl_subsurface_id != INVALID_ID) { send_wayland_message(compositor_socket, data->wl_subsurface_id, WL_SUBSURFACE_DESTROY, {}); } client->delete_object(local_id); return MessageStatus::HANDLED; } } if (interface == &zwp_pointer_constraints_v1_interface) { // FIXME: This implementation leaves no way of unlocking the pointer when // embedded into the main window. We might need to be a bit more invasive. if (p_opcode == ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER) { // [Request] zwp_pointer_constraints_v1::lock_pointer(nooou). uint32_t new_local_id = body[0]; uint32_t local_surface_id = body[1]; uint32_t local_pointer_id = body[2]; uint32_t lifetime = body[4]; WaylandSurfaceData *surf_data = (WaylandSurfaceData *)client->get_object(local_surface_id)->data; ERR_FAIL_NULL_V(surf_data, MessageStatus::ERROR); WaylandObject *role_obj = surf_data->role_object_handle.get(); ERR_FAIL_NULL_V(role_obj, MessageStatus::ERROR); if (role_obj->interface == &xdg_toplevel_interface) { XdgToplevelData *toplevel_data = (XdgToplevelData *)role_obj->data; ERR_FAIL_NULL_V(toplevel_data, MessageStatus::ERROR); if (!toplevel_data->is_embedded()) { // Passthrough. return MessageStatus::UNHANDLED; } // Subsurfaces don't normally work, at least on sway, as the locking // condition might rely on focus, which they don't get. We can remap them to // the parent surface and set a region though. XdgToplevelData *parent_data = (XdgToplevelData *)toplevel_data->parent_handle.get()->data; ERR_FAIL_NULL_V(parent_data, MessageStatus::ERROR); XdgSurfaceData *parent_xdg_surf_data = (XdgSurfaceData *)parent_data->xdg_surface_handle.get()->data; ERR_FAIL_NULL_V(parent_xdg_surf_data, MessageStatus::ERROR); WaylandSubsurfaceData *subsurf_data = (WaylandSubsurfaceData *)get_object(toplevel_data->wl_subsurface_id)->data; ERR_FAIL_NULL_V(subsurf_data, MessageStatus::ERROR); uint32_t new_global_id = client->new_object(new_local_id, &zwp_locked_pointer_v1_interface, object->version); ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); uint32_t x = subsurf_data->position.x; uint32_t y = subsurf_data->position.y; uint32_t width = toplevel_data->size.width; uint32_t height = toplevel_data->size.height; // NOTE: At least on sway I can't seem to be able to get this region // working but the calls check out. DEBUG_LOG_WAYLAND_EMBED(vformat("Creating custom region x%d y%d w%d h%d", x, y, width, height)); uint32_t new_region_id = allocate_global_id(); get_object(new_region_id)->interface = &wl_region_interface; get_object(new_region_id)->version = get_object(wl_compositor_id)->version; // wl_compostor::create_region(n). send_wayland_message(compositor_socket, wl_compositor_id, 1, { new_region_id }); // wl_region::add(iiii). send_wayland_message(compositor_socket, new_region_id, 1, { x, y, width, height }); send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id, parent_xdg_surf_data->wl_surface_id, client->get_global_id(local_pointer_id), new_region_id, lifetime }); // wl_region::destroy(). send_wayland_message(compositor_socket, new_region_id, 0, {}); return MessageStatus::HANDLED; } } } if (interface == &godot_embedded_client_interface) { EmbeddedClientData *eclient_data = (EmbeddedClientData *)object->data; ERR_FAIL_NULL_V(eclient_data, MessageStatus::ERROR); Client *eclient = eclient_data->client; ERR_FAIL_NULL_V(eclient, MessageStatus::ERROR); if (p_opcode == GODOT_EMBEDDED_CLIENT_DESTROY) { if (!eclient_data->disconnected) { close(eclient->socket); } client->delete_object(local_id); return MessageStatus::HANDLED; } if (eclient_data->disconnected) { // Object is inert. return MessageStatus::HANDLED; } ERR_FAIL_COND_V(eclient->embedded_window_id == 0, MessageStatus::ERROR); XdgToplevelData *toplevel_data = (XdgToplevelData *)eclient->get_object(eclient->embedded_window_id)->data; ERR_FAIL_NULL_V(toplevel_data, MessageStatus::ERROR); if (p_opcode == GODOT_EMBEDDED_CLIENT_SET_EMBEDDED_WINDOW_RECT && toplevel_data->wl_subsurface_id != INVALID_ID) { uint32_t x = body[0]; uint32_t y = body[1]; uint32_t width = body[2]; uint32_t height = body[3]; WaylandSubsurfaceData *subsurf_data = (WaylandSubsurfaceData *)get_object(toplevel_data->wl_subsurface_id)->data; ERR_FAIL_NULL_V(subsurf_data, MessageStatus::ERROR); toplevel_data->size.width = width; toplevel_data->size.height = height; subsurf_data->position.x = x; subsurf_data->position.y = y; // wl_subsurface::set_position send_wayland_message(compositor_socket, toplevel_data->wl_subsurface_id, 1, { x, y }); // xdg_toplevel::configure send_wayland_message(eclient->socket, eclient->embedded_window_id, 0, { width, height, 0 }); // xdg_surface::configure send_wayland_message(eclient->socket, toplevel_data->xdg_surface_handle.get_local_id(), 0, { configure_serial_counter++ }); return MessageStatus::HANDLED; } else if (p_opcode == GODOT_EMBEDDED_CLIENT_SET_EMBEDDED_WINDOW_PARENT) { uint32_t main_client_parent_id = body[0]; if (toplevel_data->parent_handle.get_local_id() == main_client_parent_id) { return MessageStatus::HANDLED; } if (main_client_parent_id == INVALID_ID && toplevel_data->wl_subsurface_id != INVALID_ID) { // Window hiding logic. // wl_subsurface::destroy() send_wayland_message(compositor_socket, toplevel_data->wl_subsurface_id, 0, {}); toplevel_data->parent_handle.invalidate(); toplevel_data->wl_subsurface_id = INVALID_ID; return MessageStatus::HANDLED; } XdgToplevelData *parent_toplevel_data = (XdgToplevelData *)client->get_object(main_client_parent_id)->data; ERR_FAIL_NULL_V(parent_toplevel_data, MessageStatus::ERROR); XdgSurfaceData *parent_xdg_surf_data = (XdgSurfaceData *)parent_toplevel_data->xdg_surface_handle.get()->data; ERR_FAIL_NULL_V(parent_xdg_surf_data, MessageStatus::ERROR); XdgSurfaceData *xdg_surf_data = (XdgSurfaceData *)toplevel_data->xdg_surface_handle.get()->data; ERR_FAIL_NULL_V(xdg_surf_data, MessageStatus::ERROR); if (toplevel_data->wl_subsurface_id != INVALID_ID) { // wl_subsurface::destroy() send_wayland_message(compositor_socket, toplevel_data->wl_subsurface_id, 0, {}); } uint32_t new_sub_id = allocate_global_id(); WaylandObject *new_sub_object = get_object(new_sub_id); new_sub_object->interface = &wl_subsurface_interface; new_sub_object->data = memnew(WaylandSubsurfaceData); new_sub_object->version = get_object(wl_subcompositor_id)->version; toplevel_data->wl_subsurface_id = new_sub_id; toplevel_data->parent_handle = LocalObjectHandle(main_client, main_client_parent_id); DEBUG_LOG_WAYLAND_EMBED(vformat("Binding subsurface g0x%x.", new_sub_id)); // wl_subcompositor::get_subsurface send_wayland_message(compositor_socket, wl_subcompositor_id, 1, { new_sub_id, xdg_surf_data->wl_surface_id, parent_xdg_surf_data->wl_surface_id }); // wl_subsurface::set_desync send_wayland_message(compositor_socket, new_sub_id, 5, {}); return MessageStatus::HANDLED; } else if (p_opcode == GODOT_EMBEDDED_CLIENT_FOCUS_WINDOW) { XdgSurfaceData *xdg_surf_data = (XdgSurfaceData *)toplevel_data->xdg_surface_handle.get()->data; ERR_FAIL_NULL_V(xdg_surf_data, MessageStatus::ERROR); for (uint32_t wl_seat_name : wl_seat_names) { RegistryGlobalInfo &global_seat_info = registry_globals[wl_seat_name]; WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)global_seat_info.data; if (global_seat_data->focused_surface_id != INVALID_ID) { seat_name_leave_surface(wl_seat_name, global_seat_data->focused_surface_id); } global_seat_data->focused_surface_id = xdg_surf_data->wl_surface_id; seat_name_enter_surface(wl_seat_name, xdg_surf_data->wl_surface_id); } } else if (p_opcode == GODOT_EMBEDDED_CLIENT_EMBEDDED_WINDOW_REQUEST_CLOSE) { // xdg_toplevel::close send_wayland_message(eclient->socket, eclient->embedded_window_id, 1, {}); return MessageStatus::HANDLED; } } // Server-allocated objects are a bit annoying to handle for us. Right now we // use a heuristic. See: https://ppaalanen.blogspot.com/2014/07/wayland-protocol-design-object-lifespan.html if (strcmp(message.name, "destroy") == 0 || strcmp(message.name, "release") == 0) { if (object->shared) { // We must not delete shared objects. client->delete_object(local_id); return MessageStatus::HANDLED; } if (global_id != INVALID_ID) { send_wayland_message(compositor_socket, global_id, p_opcode, {}); object->destroyed = true; } if (local_id & 0xff000000) { DEBUG_LOG_WAYLAND_EMBED(vformat("!!!!!! Deallocating server object l0x%x", local_id)); client->delete_object(local_id); } return MessageStatus::HANDLED; } if (client->fake_objects.has(local_id)) { // Object is fake, we're done. DEBUG_LOG_WAYLAND_EMBED("Dropping unhandled request for fake object."); return MessageStatus::HANDLED; } if (global_id == INVALID_ID) { DEBUG_LOG_WAYLAND_EMBED("Dropping request with invalid global object id"); return MessageStatus::HANDLED; } return MessageStatus::UNHANDLED; } WaylandEmbedder::MessageStatus WaylandEmbedder::handle_event(uint32_t p_global_id, LocalObjectHandle p_local_handle, uint32_t p_opcode, const uint32_t *msg_data, size_t msg_len) { WaylandObject *global_object = get_object(p_global_id); ERR_FAIL_NULL_V_MSG(global_object, MessageStatus::ERROR, "Compositor messages must always have a global object."); #ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED ERR_FAIL_NULL_V(global_object->interface, MessageStatus::ERROR); const struct wl_interface *interface = global_object->interface; ERR_FAIL_COND_V((int)p_opcode >= interface->event_count, MessageStatus::ERROR); const struct wl_message message = interface->events[p_opcode]; if (p_local_handle.is_valid()) { int socket = p_local_handle.get_client()->socket; DEBUG_LOG_WAYLAND_EMBED(vformat("Client #%d <- %s::%s(%s) g0x%x", socket, interface->name, message.name, message.signature, p_global_id)); } else { DEBUG_LOG_WAYLAND_EMBED(vformat("Client N/A <- %s::%s(%s) g0x%x", interface->name, message.name, message.signature, p_global_id)); } #endif //WAYLAND_EMBED_DEBUG_LOGS_ENABLED const uint32_t *body = msg_data + 2; //size_t body_len = msg_len - (WL_WORD_SIZE * 2); // FIXME: Make sure that it makes sense to track this protocol. Not only is it // old and getting deprecated, but I can't even get this code branch to hit // probably because, at the time of writing, we only get the "main" display // through the proxy. if (global_object->interface == &wl_drm_interface) { // wl_drm can't ever be destroyed, so we need to track its state as it's going // to be instanced at least few times. uint32_t global_name = registry_globals_names[p_global_id]; WaylandDrmGlobalData *global_data = (WaylandDrmGlobalData *)registry_globals[global_name].data; ERR_FAIL_NULL_V(global_data, MessageStatus::ERROR); if (p_opcode == WL_DRM_DEVICE) { // signature: s uint32_t name_len = body[0]; uint8_t *name = (uint8_t *)(body + 1); global_data->device = String::utf8((const char *)name, name_len); return MessageStatus::UNHANDLED; } if (p_opcode == WL_DRM_FORMAT) { // signature: u uint32_t format = body[0]; global_data->formats.push_back(format); return MessageStatus::UNHANDLED; } if (p_opcode == WL_DRM_AUTHENTICATED) { // signature: N/A global_data->authenticated = true; return MessageStatus::UNHANDLED; } if (p_opcode == WL_DRM_CAPABILITIES) { // signature: u uint32_t capabilities = body[0]; global_data->capabilities = capabilities; } return MessageStatus::UNHANDLED; } if (global_object->interface == &wl_shm_interface) { uint32_t global_name = registry_globals_names[p_global_id]; WaylandShmGlobalData *global_data = (WaylandShmGlobalData *)registry_globals[global_name].data; ERR_FAIL_NULL_V(global_data, MessageStatus::ERROR); if (p_opcode == WL_SHM_FORMAT) { // Signature: u uint32_t format = body[0]; global_data->formats.push_back(format); } } if (!p_local_handle.is_valid()) { // Some requests might not have a valid local object handle for various // reasons, such as when certain events are directed to this proxy or when the // destination client of a message disconnected in the meantime. if (global_object->interface == &wl_display_interface) { if (p_opcode == WL_DISPLAY_DELETE_ID) { // [Event] wl_display::delete_id(u) uint32_t global_delete_id = body[0]; DEBUG_LOG_WAYLAND_EMBED(vformat("Compositor requested deletion of g0x%x (no client)", global_delete_id)); delete_object(global_delete_id); return MessageStatus::HANDLED; } else if (p_opcode == WL_DISPLAY_ERROR) { // [Event] wl_display::error(ous) uint32_t obj_id = body[0]; uint32_t err_code = body[1]; CRASH_NOW_MSG(vformat("Error obj g0x%x code %d: %s", obj_id, err_code, (const char *)(body + 3))); } } if (global_object->interface == &wl_callback_interface && p_opcode == WL_CALLBACK_DONE) { if (sync_callback_id != INVALID_ID && p_global_id == sync_callback_id) { sync_callback_id = 0; DEBUG_LOG_WAYLAND_EMBED("Sync response received"); return MessageStatus::HANDLED; } } if (global_object->interface == &wl_registry_interface) { if (p_opcode == WL_REGISTRY_GLOBAL) { // [Event] wl_registry::global(usu). uint32_t global_name = body[0]; uint32_t interface_name_len = body[1]; const char *interface_name = (const char *)(body + 2); uint32_t global_version = body[2 + wl_array_word_offset(interface_name_len)]; DEBUG_LOG_WAYLAND_EMBED("Global c#%d %s %d", global_name, interface_name, global_version); const struct wl_interface *global_interface = wl_interface_from_string(interface_name, interface_name_len); if (global_interface) { RegistryGlobalInfo global_info = {}; global_info.interface = global_interface; global_info.version = MIN(global_version, (uint32_t)global_interface->version); DEBUG_LOG_WAYLAND_EMBED("Clamped global %s to version %d.", interface_name, global_info.version); global_info.compositor_name = global_name; int new_global_name = registry_globals_counter++; if (global_info.interface == &wl_shm_interface) { DEBUG_LOG_WAYLAND_EMBED("Allocating global wl_shm data."); global_info.data = memnew(WaylandShmGlobalData); } if (global_info.interface == &wl_seat_interface) { DEBUG_LOG_WAYLAND_EMBED("Allocating global wl_seat data."); global_info.data = memnew(WaylandSeatGlobalData); wl_seat_names.push_back(new_global_name); } if (global_info.interface == &wl_drm_interface) { DEBUG_LOG_WAYLAND_EMBED("Allocating global wl_drm data."); global_info.data = memnew(WaylandDrmGlobalData); } registry_globals[new_global_name] = global_info; // We need some interfaces directly. It's better to bind a "copy" ourselves // than to wait for the client to ask one. if (global_interface == &xdg_wm_base_interface && xdg_wm_base_id == 0) { xdg_wm_base_id = wl_registry_bind(p_global_id, new_global_name, global_info.version); ERR_FAIL_COND_V(xdg_wm_base_id == INVALID_ID, MessageStatus::ERROR); } else if (global_interface == &wl_compositor_interface && wl_compositor_id == 0) { wl_compositor_id = wl_registry_bind(p_global_id, new_global_name, global_info.version); ERR_FAIL_COND_V(wl_compositor_id == INVALID_ID, MessageStatus::ERROR); } else if (global_interface == &wl_subcompositor_interface && wl_subcompositor_id == 0) { wl_subcompositor_id = wl_registry_bind(p_global_id, new_global_name, global_info.version); ERR_FAIL_COND_V(wl_subcompositor_id == INVALID_ID, MessageStatus::ERROR); } DEBUG_LOG_WAYLAND_EMBED(vformat("Local registry object name: l#%d", new_global_name)); if (clients.is_empty()) { // Let's not waste time. return MessageStatus::HANDLED; } // Notify all clients. LocalVector args; args.push_back(wl_arg_uint(new_global_name)); args.push_back(wl_arg_string(interface_name)); args.push_back(wl_arg_uint(global_info.version)); for (KeyValue &pair : clients) { Client &client = pair.value; for (uint32_t local_registry_id : client.wl_registry_instances) { send_wayland_event(client.socket, local_registry_id, wl_registry_interface, WL_REGISTRY_GLOBAL, args); } } return MessageStatus::HANDLED; } else { DEBUG_LOG_WAYLAND_EMBED("Skipping unknown global %s version %d.", interface_name, global_version); return MessageStatus::HANDLED; } } else if (p_opcode == WL_REGISTRY_GLOBAL_REMOVE) { uint32_t compositor_name = body[0]; uint32_t local_name = 0; RegistryGlobalInfo *global_info = nullptr; // FIXME: Use a map or something. for (KeyValue &pair : registry_globals) { uint32_t name = pair.key; RegistryGlobalInfo &info = pair.value; if (info.compositor_name == compositor_name) { local_name = name; global_info = &info; break; } } ERR_FAIL_NULL_V(global_info, MessageStatus::ERROR); if (global_info->instance_counter == 0) { memdelete(global_info->data); registry_globals.erase(local_name); } else { global_info->destroyed = true; } // Notify all clients. LocalVector args; args.push_back(wl_arg_uint(local_name)); for (KeyValue &pair : clients) { Client &client = pair.value; for (uint32_t local_registry_id : client.wl_registry_instances) { send_wayland_event(client.socket, local_registry_id, wl_registry_interface, WL_REGISTRY_GLOBAL_REMOVE, args); } } return MessageStatus::HANDLED; } } DEBUG_LOG_WAYLAND_EMBED("No valid local object handle, falling back to generic handler."); return MessageStatus::UNHANDLED; } Client *client = p_local_handle.get_client(); ERR_FAIL_NULL_V(client, MessageStatus::ERROR); WaylandObject *object = p_local_handle.get(); uint32_t local_id = p_local_handle.get_local_id(); if (global_object->interface == &wl_display_interface) { if (p_opcode == WL_DISPLAY_DELETE_ID) { // [Event] wl_display::delete_id(u) uint32_t global_delete_id = body[0]; uint32_t local_delete_id = client->get_local_id(global_delete_id); DEBUG_LOG_WAYLAND_EMBED(vformat("Compositor requested delete of g0x%x l0x%x", global_delete_id, local_delete_id)); if (local_delete_id == INVALID_ID) { // No idea what this object is, might be of the other client. This // definitely does not make sense to us, so we're done. return MessageStatus::INVALID; } client->delete_object(local_delete_id); send_wayland_message(client->socket, DISPLAY_ID, WL_DISPLAY_DELETE_ID, { local_delete_id }); return MessageStatus::HANDLED; } return MessageStatus::UNHANDLED; } if (object->interface == &wl_keyboard_interface) { WaylandKeyboardData *data = (WaylandKeyboardData *)object->data; ERR_FAIL_NULL_V(data, MessageStatus::ERROR); uint32_t global_seat_name = registry_globals_names[data->wl_seat_id]; RegistryGlobalInfo &global_seat_info = registry_globals[global_seat_name]; WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)global_seat_info.data; ERR_FAIL_NULL_V(global_seat_data, MessageStatus::ERROR); if (p_opcode == WL_KEYBOARD_ENTER) { // [Event] wl_keyboard::enter(uoa) uint32_t surface = body[1]; if (global_seat_data->focused_surface_id != surface) { DEBUG_LOG_WAYLAND_EMBED(vformat("Focused g0x%x", surface)); global_seat_data->focused_surface_id = surface; } } else if (p_opcode == WL_KEYBOARD_LEAVE) { // [Event] wl_keyboard::leave(uo) uint32_t surface = body[1]; if (global_seat_data->focused_surface_id == surface) { global_seat_data->focused_surface_id = INVALID_ID; } } else if (p_opcode == WL_KEYBOARD_KEY) { // NOTE: modifiers event can be sent even without focus, according to the // spec, so there's no need to skip it. if (global_seat_data->focused_surface_id != INVALID_ID && !client->local_ids.has(global_seat_data->focused_surface_id)) { DEBUG_LOG_WAYLAND_EMBED(vformat("Skipped wl_keyboard event due to unfocused surface 0x%x", global_seat_data->focused_surface_id)); return MessageStatus::HANDLED; } } return MessageStatus::UNHANDLED; } if (object->interface == &wl_pointer_interface) { WaylandPointerData *data = (WaylandPointerData *)object->data; ERR_FAIL_NULL_V(data, MessageStatus::ERROR); uint32_t global_seat_name = registry_globals_names[data->wl_seat_id]; RegistryGlobalInfo &global_seat_info = registry_globals[global_seat_name]; WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)global_seat_info.data; ERR_FAIL_NULL_V(global_seat_data, MessageStatus::ERROR); WaylandSeatInstanceData *seat_data = (WaylandSeatInstanceData *)object->data; ERR_FAIL_NULL_V(seat_data, MessageStatus::ERROR); if (p_opcode == WL_POINTER_BUTTON && global_seat_data->pointed_surface_id != INVALID_ID) { // [Event] wl_pointer::button(uuuu); uint32_t button = body[2]; uint32_t state = body[3]; DEBUG_LOG_WAYLAND_EMBED(vformat("Button %d state %d on surface g0x%x (focused g0x%x)", button, state, global_seat_data->pointed_surface_id, global_seat_data->focused_surface_id)); bool client_pointed = client->local_ids.has(global_seat_data->pointed_surface_id); if (button != BTN_LEFT || state != WL_POINTER_BUTTON_STATE_RELEASED) { return client_pointed ? MessageStatus::UNHANDLED : MessageStatus::HANDLED; } if (global_seat_data->focused_surface_id == global_seat_data->pointed_surface_id) { return client_pointed ? MessageStatus::UNHANDLED : MessageStatus::HANDLED; } if (!global_surface_is_window(global_seat_data->pointed_surface_id)) { return client_pointed ? MessageStatus::UNHANDLED : MessageStatus::HANDLED; } if (global_seat_data->focused_surface_id != INVALID_ID) { seat_name_leave_surface(global_seat_name, global_seat_data->focused_surface_id); } global_seat_data->focused_surface_id = global_seat_data->pointed_surface_id; seat_name_enter_surface(global_seat_name, global_seat_data->focused_surface_id); } else if (p_opcode == WL_POINTER_ENTER) { // [Event] wl_pointer::enter(uoff). uint32_t surface = body[1]; WaylandSurfaceData *surface_data = (WaylandSurfaceData *)get_object(surface)->data; ERR_FAIL_NULL_V(surface_data, MessageStatus::ERROR); if (global_seat_data->pointed_surface_id != surface) { DEBUG_LOG_WAYLAND_EMBED(vformat("Pointer (g0x%x seat g0x%x): pointed surface old g0x%x new g0x%x", p_global_id, data->wl_seat_id, global_seat_data->pointed_surface_id, surface)); global_seat_data->pointed_surface_id = surface; } } else if (p_opcode == WL_POINTER_LEAVE) { // [Event] wl_pointer::leave(uo). uint32_t surface = body[1]; WaylandSurfaceData *surface_data = (WaylandSurfaceData *)get_object(surface)->data; ERR_FAIL_NULL_V(surface_data, MessageStatus::ERROR); if (global_seat_data->pointed_surface_id == surface) { DEBUG_LOG_WAYLAND_EMBED(vformat("Pointer (g0x%x seat g0x%x): g0x%x -> g0x%x", p_global_id, data->wl_seat_id, global_seat_data->pointed_surface_id, INVALID_ID)); global_seat_data->pointed_surface_id = INVALID_ID; } } return MessageStatus::UNHANDLED; } if (object->interface == &xdg_popup_interface) { if (p_opcode == XDG_POPUP_CONFIGURE) { // [Event] xdg_popup::configure(iiii); int32_t x = body[0]; int32_t y = body[1]; int32_t width = body[2]; int32_t height = body[3]; XdgPopupData *data = (XdgPopupData *)object->data; ERR_FAIL_NULL_V(data, MessageStatus::ERROR); XdgSurfaceData *parent_xdg_surf_data = (XdgSurfaceData *)data->parent_handle.get()->data; ERR_FAIL_NULL_V(parent_xdg_surf_data, MessageStatus::ERROR); WaylandSurfaceData *parent_surface_data = (WaylandSurfaceData *)get_object(parent_xdg_surf_data->wl_surface_id)->data; ERR_FAIL_NULL_V(parent_surface_data, MessageStatus::ERROR); WaylandObject *parent_role_obj = parent_surface_data->role_object_handle.get(); ERR_FAIL_NULL_V(parent_role_obj, MessageStatus::ERROR); if (parent_role_obj->interface == &xdg_toplevel_interface) { XdgToplevelData *parent_toplevel_data = (XdgToplevelData *)parent_role_obj->data; ERR_FAIL_NULL_V(parent_toplevel_data, MessageStatus::ERROR); if (parent_toplevel_data->is_embedded()) { WaylandSubsurfaceData *subsurf_data = (WaylandSubsurfaceData *)get_object(parent_toplevel_data->wl_subsurface_id)->data; ERR_FAIL_NULL_V(subsurf_data, MessageStatus::ERROR); // The coordinates passed will be shifted by the embedded window position, // so we need to fix them back. Point2i fixed_position = Point2i(x, y) - subsurf_data->position; DEBUG_LOG_WAYLAND_EMBED(vformat("Correcting popup configure position to %s", fixed_position)); send_wayland_message(client->socket, local_id, p_opcode, { (uint32_t)fixed_position.x, (uint32_t)fixed_position.y, (uint32_t)width, (uint32_t)height }); return MessageStatus::HANDLED; } } } } return MessageStatus::UNHANDLED; } void WaylandEmbedder::shutdown() { thread_done.set(); { // First making a list of all clients so that we can iteratively delete them. LocalVector sockets; for (KeyValue &pair : clients) { sockets.push_back(pair.key); } for (int socket : sockets) { cleanup_socket(socket); } } close(compositor_socket); compositor_socket = -1; for (KeyValue &pair : registry_globals) { RegistryGlobalInfo &info = pair.value; if (info.data) { memdelete(info.data); info.data = nullptr; } } } Error WaylandEmbedder::handle_msg_info(Client *client, const struct msg_info *info, uint32_t *buf, int *fds_requested) { ERR_FAIL_NULL_V(info, ERR_BUG); ERR_FAIL_NULL_V(fds_requested, ERR_BUG); ERR_FAIL_NULL_V_MSG(info->direction == ProxyDirection::COMPOSITOR && client, ERR_BUG, "Wait, where did this message come from?"); *fds_requested = 0; WaylandObject *object = nullptr; uint32_t global_id = INVALID_ID; if (info->direction == ProxyDirection::CLIENT) { global_id = info->raw_id; } else if (info->direction == ProxyDirection::COMPOSITOR) { global_id = client->get_global_id(info->raw_id); } if (global_id != INVALID_ID) { object = get_object(global_id); } else if (client) { object = client->get_object(info->raw_id); } if (object == nullptr) { if (info->direction == ProxyDirection::COMPOSITOR) { uint32_t local_id = info->raw_id; ERR_PRINT(vformat("Couldn't find requested object l0x%x for client %d, disconnecting.", local_id, client->socket)); socket_error(client->socket, local_id, WL_DISPLAY_ERROR_INVALID_OBJECT, vformat("Object l0x%x not found.", local_id)); return OK; } else { CRASH_NOW_MSG(vformat("No object found for r0x%x", info->raw_id)); } } const struct wl_interface *interface = nullptr; interface = object->interface; if (interface == nullptr && info->raw_id & 0xff000000) { // Regular clients have no confirmation about deleted server objects (why // should they?) but since we share connections there's the risk of receiving // messages about deleted server objects. The simplest solution is to ignore // unknown server-side objects. Not the safest thing, I know, but it should do // the job. DEBUG_LOG_WAYLAND_EMBED(vformat("Ignoring unknown server-side object r0x%x", info->raw_id)); return OK; } ERR_FAIL_NULL_V_MSG(interface, ERR_BUG, vformat("Object r0x%x has no interface", info->raw_id)); const struct wl_message *message = nullptr; if (info->direction == ProxyDirection::CLIENT) { ERR_FAIL_COND_V(info->opcode >= interface->event_count, ERR_BUG); message = &interface->events[info->opcode]; } else { ERR_FAIL_COND_V(info->opcode >= interface->method_count, ERR_BUG); message = &interface->methods[info->opcode]; } ERR_FAIL_NULL_V(message, ERR_BUG); *fds_requested = String(message->signature).count("h"); LocalVector sent_fds; if (*fds_requested > 0) { DEBUG_LOG_WAYLAND_EMBED(vformat("Requested %d FDs.", *fds_requested)); List &fd_queue = info->direction == ProxyDirection::COMPOSITOR ? client->fds : compositor_fds; for (int i = 0; i < *fds_requested; ++i) { ERR_FAIL_COND_V_MSG(fd_queue.is_empty(), ERR_BUG, "Out of FDs."); DEBUG_LOG_WAYLAND_EMBED(vformat("Fetching FD %d.", fd_queue.front()->get())); sent_fds.push_back(fd_queue.front()->get()); fd_queue.pop_front(); } DEBUG_LOG_WAYLAND_EMBED(vformat("Remaining FDs: %d.", fd_queue.size())); } if (object->destroyed) { DEBUG_LOG_WAYLAND_EMBED("Ignoring message for inert object."); // Inert object. return OK; } if (info->direction == ProxyDirection::COMPOSITOR) { MessageStatus request_status = handle_request(LocalObjectHandle(client, info->raw_id), info->opcode, buf, info->size); if (request_status == MessageStatus::ERROR) { return ERR_BUG; } if (request_status == MessageStatus::HANDLED) { DEBUG_LOG_WAYLAND_EMBED("Custom handler success."); return OK; } if (global_id != INVALID_ID) { buf[0] = global_id; } DEBUG_LOG_WAYLAND_EMBED("Falling back to generic handler."); if (handle_generic_msg(client, object, message, info, buf)) { send_raw_message(compositor_socket, { { buf, info->size } }, sent_fds); } } else { uint32_t global_name = 0; bool is_global = false; if (registry_globals_names.has(global_id)) { global_name = registry_globals_names[global_id]; is_global = true; } // FIXME: For compatibility, mirror events with instanced registry globals as // object arguments. For example, `wl_surface.enter` returns a `wl_output`. If // said `wl_output` has been instanced multiple times, we need to resend the // same event with each instance as the argument, or the client might miss the // event by looking for the "wrong" instance. // // Note that this missing behavior is exclusively a compatibility mechanism // for old compositors which only implement undestroyable globals. We // otherwise passthrough every bind request and then the compositor takes care // of everything. // See: https://lore.freedesktop.org/wayland-devel/20190326121421.06732fd2@eldfell.localdomain/ if (object->shared) { bool handled = false; for (KeyValue &pair : clients) { Client &c = pair.value; if (c.socket < 0) { continue; } if (!c.local_ids.has(global_id)) { DEBUG_LOG_WAYLAND_EMBED("!!!!!!!!!!! Instance missing?"); continue; } if (is_global) { if (!c.registry_globals_instances.has(global_name)) { continue; } DEBUG_LOG_WAYLAND_EMBED(vformat("Broadcasting to all global instances for client %d (socket %d)", c.pid, c.socket)); for (uint32_t instance_id : c.registry_globals_instances[global_name]) { DEBUG_LOG_WAYLAND_EMBED(vformat("Global instance l0x%x", instance_id)); LocalObjectHandle local_obj = LocalObjectHandle(&c, instance_id); if (!local_obj.is_valid()) { continue; } MessageStatus event_status = handle_event(global_id, local_obj, info->opcode, buf, info->size); if (event_status == MessageStatus::ERROR) { return ERR_BUG; } if (event_status == MessageStatus::HANDLED) { DEBUG_LOG_WAYLAND_EMBED("Custom handler success."); handled = true; continue; } if (event_status == MessageStatus::INVALID) { continue; } DEBUG_LOG_WAYLAND_EMBED("Falling back to generic handler."); buf[0] = instance_id; if (handle_generic_msg(&c, local_obj.get(), message, info, buf, instance_id)) { send_raw_message(c.socket, { { buf, info->size } }, sent_fds); } handled = true; } } else if (interface == &wl_display_interface) { // NOTE: The only shared non-global objects are `wl_display` and // `wl_registry`, both of which require custom handlers. Additionally, of // those only `wl_display` has client-specific handlers, which is what this // branch manages. LocalObjectHandle local_obj = LocalObjectHandle(&c, c.get_local_id(global_id)); if (!local_obj.is_valid()) { continue; } DEBUG_LOG_WAYLAND_EMBED(vformat("Shared non-global l0x%x g0x%x", c.get_local_id(global_id), global_id)); MessageStatus event_status = handle_event(global_id, local_obj, info->opcode, buf, info->size); if (event_status == MessageStatus::ERROR) { return ERR_BUG; } if (event_status == MessageStatus::HANDLED) { DEBUG_LOG_WAYLAND_EMBED("Custom handler success."); handled = true; continue; } if (event_status == MessageStatus::INVALID) { continue; } DEBUG_LOG_WAYLAND_EMBED("Falling back to generic handler."); if (handle_generic_msg(&c, local_obj.get(), message, info, buf)) { send_raw_message(c.socket, { { buf, info->size } }, sent_fds); } handled = true; } } if (!handled) { // No client handled this, it's going to be handled as a client-less event. // We do this only at the end to avoid handling certain events (e.g. // deletion) twice. handle_event(global_id, LocalObjectHandle(nullptr, INVALID_ID), info->opcode, buf, info->size); } } else { LocalObjectHandle local_obj = LocalObjectHandle(client, client ? client->get_local_id(global_id) : INVALID_ID); MessageStatus event_status = handle_event(global_id, local_obj, info->opcode, buf, info->size); if (event_status == MessageStatus::ERROR) { return ERR_BUG; } if (event_status == MessageStatus::HANDLED || event_status == MessageStatus::INVALID) { // We're done. return OK; } // Generic passthrough. if (client) { uint32_t local_id = client->get_local_id(global_id); ERR_FAIL_COND_V(local_id == INVALID_ID, OK); DEBUG_LOG_WAYLAND_EMBED(vformat("%s::%s(%s) g0x%x -> l0x%x", interface->name, message->name, message->signature, global_id, local_id)); buf[0] = local_id; if (handle_generic_msg(client, local_obj.get(), message, info, buf)) { send_raw_message(client->socket, { { buf, info->size } }, sent_fds); } } else { WARN_PRINT_ONCE(vformat("[Wayland Embedder] Unexpected client-less event from %s#g0x%x. Object has probably leaked.", object->interface->name, global_id)); handle_generic_msg(nullptr, object, message, info, buf); } } } for (int fd : sent_fds) { DEBUG_LOG_WAYLAND_EMBED(vformat("Closing fd %d.", fd)); close(fd); } return OK; } Error WaylandEmbedder::handle_sock(int p_fd) { ERR_FAIL_COND_V(p_fd < 0, ERR_INVALID_PARAMETER); struct msg_info info = {}; { struct msghdr head_msg = {}; uint32_t header[2]; struct iovec vec = { header, sizeof header }; head_msg.msg_iov = &vec; head_msg.msg_iovlen = 1; ssize_t head_rec = recvmsg(p_fd, &head_msg, MSG_PEEK); if (head_rec == 0) { // Client disconnected. return ERR_CONNECTION_ERROR; } if (head_rec == -1) { if (errno == ECONNRESET) { // No need to print the error, the client forcefully disconnected, that's // fine. return ERR_CONNECTION_ERROR; } ERR_FAIL_V_MSG(FAILED, vformat("Can't read message header: %s", strerror(errno))); } ERR_FAIL_COND_V_MSG(((size_t)head_rec) != vec.iov_len, ERR_CONNECTION_ERROR, vformat("Should've received %d bytes, instead got %d bytes", vec.iov_len, head_rec)); // Header is two 32-bit words: first is ID, second has size in most significant // half and opcode in the other half. info.raw_id = header[0]; info.size = header[1] >> 16; info.opcode = header[1] & 0xFFFF; info.direction = p_fd != compositor_socket ? ProxyDirection::COMPOSITOR : ProxyDirection::CLIENT; } if (msg_buf.size() < info.words()) { msg_buf.resize(info.words()); } ERR_FAIL_COND_V_MSG(info.size % WL_WORD_SIZE != 0, ERR_CONNECTION_ERROR, "Invalid message length."); struct msghdr full_msg = {}; struct iovec vec = { msg_buf.ptr(), info.size }; { full_msg.msg_iov = &vec; full_msg.msg_iovlen = 1; full_msg.msg_control = ancillary_buf.ptr(); full_msg.msg_controllen = ancillary_buf.size(); ssize_t full_rec = recvmsg(p_fd, &full_msg, 0); if (full_rec == -1) { if (errno == ECONNRESET) { // No need to print the error, the client forcefully disconnected, that's // fine. return ERR_CONNECTION_ERROR; } ERR_FAIL_V_MSG(FAILED, vformat("Can't read message: %s", strerror(errno))); } ERR_FAIL_COND_V_MSG(((size_t)full_rec) != info.size, ERR_CONNECTION_ERROR, "Invalid message length."); DEBUG_LOG_WAYLAND_EMBED(" === START PACKET === "); #ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED printf("[PROXY] Received bytes: "); for (ssize_t i = 0; i < full_rec; ++i) { printf("%.2x", ((const uint8_t *)msg_buf.ptr())[i]); } printf("\n"); #endif } if (full_msg.msg_controllen > 0) { struct cmsghdr *cmsg = CMSG_FIRSTHDR(&full_msg); while (cmsg) { // TODO: Check for validity of message fields. size_t data_len = cmsg->cmsg_len - sizeof *cmsg; if (cmsg->cmsg_type == SCM_RIGHTS) { // NOTE: Linux docs say that we can't just cast data to pointer type because // of alignment concerns. So we have to memcpy into a new buffer. int *cmsg_fds = (int *)malloc(data_len); memcpy(cmsg_fds, CMSG_DATA(cmsg), data_len); size_t cmsg_fds_count = data_len / sizeof *cmsg_fds; for (size_t i = 0; i < cmsg_fds_count; ++i) { int fd = cmsg_fds[i]; if (info.direction == ProxyDirection::COMPOSITOR) { clients[p_fd].fds.push_back(fd); } else { compositor_fds.push_back(fd); } } #ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED printf("[PROXY] Received %ld file descriptors: ", cmsg_fds_count); for (size_t i = 0; i < cmsg_fds_count; ++i) { printf("%d ", cmsg_fds[i]); } printf("\n"); #endif free(cmsg_fds); } cmsg = CMSG_NXTHDR(&full_msg, cmsg); } } full_msg.msg_control = nullptr; full_msg.msg_controllen = 0; int fds_requested = 0; Client *client = nullptr; if (p_fd == compositor_socket) { // Let's figure out the recipient of the message. for (KeyValue &pair : clients) { Client &c = pair.value; if (c.local_ids.has(info.raw_id)) { client = &c; } } } else { CRASH_COND(!clients.has(p_fd)); client = &clients[p_fd]; } if (handle_msg_info(client, &info, msg_buf.ptr(), &fds_requested) != OK) { return ERR_BUG; } DEBUG_LOG_WAYLAND_EMBED(" === END PACKET === "); return OK; } void WaylandEmbedder::_thread_loop(void *p_data) { Thread::set_name("Wayland Embed"); ERR_FAIL_NULL(p_data); WaylandEmbedder *proxy = (WaylandEmbedder *)p_data; DEBUG_LOG_WAYLAND_EMBED("Proxy thread started"); while (!proxy->thread_done.is_set()) { proxy->poll_sockets(); } } Error WaylandEmbedder::init() { ancillary_buf.resize(EMBED_ANCILLARY_BUF_SIZE); proxy_socket = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un addr = {}; addr.sun_family = AF_UNIX; String runtime_dir_path = OS::get_singleton()->get_environment("XDG_RUNTIME_DIR"); ERR_FAIL_COND_V_MSG(runtime_dir_path.is_empty(), ERR_DOES_NOT_EXIST, "XDG_RUNTIME_DIR is not set or empty."); runtime_dir = DirAccess::create_for_path(runtime_dir_path); ERR_FAIL_COND_V(!runtime_dir.is_valid(), ERR_BUG); ERR_FAIL_COND_V_MSG(!runtime_dir->is_writable(runtime_dir_path), ERR_FILE_CANT_WRITE, "XDG_RUNTIME_DIR points to an invalid directory."); int socket_id = 0; while (socket_path.is_empty()) { String test_socket_path = runtime_dir_path + "/godot-wayland-" + itos(socket_id); String test_socket_lock_path = test_socket_path + ".lock"; print_verbose(vformat("Trying to get socket %s", test_socket_path)); print_verbose(vformat("Opening lock %s", test_socket_lock_path)); int test_lock_fd = open(test_socket_lock_path.utf8().get_data(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (flock(test_lock_fd, LOCK_EX | LOCK_NB) == -1) { print_verbose(vformat("Can't lock %s", test_socket_lock_path)); close(test_lock_fd); ++socket_id; continue; } else { lock_fd = test_lock_fd; socket_path = test_socket_path; socket_lock_path = test_socket_lock_path; break; } } DirAccess::remove_absolute(socket_path); strncpy(addr.sun_path, socket_path.utf8().get_data(), sizeof(addr.sun_path) - 1); if (bind(proxy_socket, (struct sockaddr *)&addr, sizeof(addr)) == -1) { ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't bind embedding socket."); } if (listen(proxy_socket, 1) == -1) { ERR_FAIL_V_MSG(ERR_CANT_OPEN, "Can't listen embedding socket."); } struct wl_display *display = wl_display_connect(nullptr); ERR_FAIL_NULL_V(display, ERR_CANT_OPEN); compositor_socket = wl_display_get_fd(display); pollfds.push_back({ proxy_socket, POLLIN, 0 }); pollfds.push_back({ compositor_socket, POLLIN, 0 }); RegistryGlobalInfo control_global_info = {}; control_global_info.interface = &godot_embedding_compositor_interface; control_global_info.version = godot_embedding_compositor_interface.version; godot_embedding_compositor_name = registry_globals_counter++; registry_globals[godot_embedding_compositor_name] = control_global_info; { uint32_t invalid_id = INVALID_ID; objects.request(invalid_id); CRASH_COND(invalid_id != INVALID_ID); } { uint32_t display_id = new_object(&wl_display_interface); CRASH_COND(display_id != DISPLAY_ID); get_object(DISPLAY_ID)->shared = true; } { uint32_t registry_id = new_object(&wl_registry_interface); CRASH_COND(registry_id != REGISTRY_ID); get_object(REGISTRY_ID)->shared = true; } // wl_display::get_registry(n) send_wayland_message(compositor_socket, DISPLAY_ID, 1, { REGISTRY_ID }); sync(); proxy_thread.start(_thread_loop, this); return OK; } void WaylandEmbedder::handle_fd(int p_fd, int p_revents) { if (p_fd == proxy_socket && p_revents & POLLIN) { // Client init. int new_fd = accept(proxy_socket, nullptr, nullptr); ERR_FAIL_COND_MSG(new_fd == -1, "Failed to accept client."); struct ucred cred = {}; socklen_t cred_size = sizeof cred; getsockopt(new_fd, SOL_SOCKET, SO_PEERCRED, &cred, &cred_size); Client &client = clients.insert_new(new_fd, {})->value; client.embedder = this; client.socket = new_fd; client.pid = cred.pid; client.global_ids[DISPLAY_ID] = Client::GlobalIdInfo(DISPLAY_ID, nullptr); client.local_ids[DISPLAY_ID] = DISPLAY_ID; pollfds.push_back({ new_fd, POLLIN, 0 }); if (main_client == nullptr) { main_client = &client; } if (new_fd != main_client->socket && main_client->registry_globals_instances.has(godot_embedding_compositor_name)) { uint32_t new_local_id = main_client->allocate_server_id(); client.embedded_client_id = new_local_id; for (uint32_t local_id : main_client->registry_globals_instances[godot_embedding_compositor_name]) { EmbeddedClientData *eclient_data = memnew(EmbeddedClientData); eclient_data->client = &client; main_client->new_fake_object(new_local_id, &godot_embedded_client_interface, 1, eclient_data); // godot_embedding_compositor::client(nu) send_wayland_message(main_client->socket, local_id, 0, { new_local_id, (uint32_t)cred.pid }); } } DEBUG_LOG_WAYLAND_EMBED(vformat("New client %d (pid %d) initialized.", client.socket, cred.pid)); return; } if (p_fd == compositor_socket && p_revents & POLLIN) { Error err = handle_sock(p_fd); if (err == ERR_BUG) { ERR_PRINT("Unexpected error while handling socket, shutting down."); shutdown(); return; } return; } const Client *client = clients.getptr(p_fd); if (client) { if (main_client && client == main_client && p_revents & (POLLHUP | POLLERR)) { DEBUG_LOG_WAYLAND_EMBED("Main client disconnected, shutting down."); shutdown(); return; } if (p_revents & POLLIN) { Error err = handle_sock(p_fd); if (err == ERR_BUG) { ERR_PRINT("Unexpected error while handling socket, shutting down."); shutdown(); return; } if (err != OK) { DEBUG_LOG_WAYLAND_EMBED("disconnecting"); cleanup_socket(p_fd); return; } return; } else if (p_revents & (POLLHUP | POLLERR | POLLNVAL)) { if (p_revents & POLLHUP) { DEBUG_LOG_WAYLAND_EMBED(vformat("Socket %d hangup.", p_fd)); } if (p_revents & POLLERR) { DEBUG_LOG_WAYLAND_EMBED(vformat("Socket %d error.", p_fd)); } if (p_revents & POLLNVAL) { DEBUG_LOG_WAYLAND_EMBED(vformat("Socket %d invalid FD.", p_fd)); } cleanup_socket(p_fd); return; } } } WaylandEmbedder::~WaylandEmbedder() { shutdown(); proxy_thread.wait_to_finish(); } #endif // TOOLS_ENABLED #endif // WAYLAND_ENABLED