2025-11-15 23:38:41 +01:00
/**************************************************************************/
/* 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 <sys/stat.h>
# ifdef __FreeBSD__
# include <dev/evdev/input-event-codes.h>
# else
// Assume Linux.
# include <linux/input-event-codes.h>
# endif
# include "core/os/os.h"
# include <fcntl.h>
# include <sys/file.h>
# include <unistd.h>
# 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 < union wl_argument > 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 < uint32_t , WaylandObject > & 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 < uint32_t > : : 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 < union wl_argument > 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 < struct iovec > p_vecs , const LocalVector < int > & 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 < union wl_argument > & 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 < uint32_t > 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 < union wl_argument > 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 ;
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 < uint32_t , RegistryGlobalInfo > & 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 < union wl_argument > 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 ] ) ;
2025-11-24 22:38:56 +01:00
send_wayland_message ( compositor_socket , global_id , p_opcode , { body [ 0 ] , body [ 1 ] , body [ 2 ] , body [ 3 ] } ) ;
2025-11-15 23:38:41 +01:00
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 < wl_argument > 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 < int , Client > & 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 < uint32_t , RegistryGlobalInfo > & 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 < wl_argument > args ;
args . push_back ( wl_arg_uint ( local_name ) ) ;
for ( KeyValue < int , Client > & 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 < int > sockets ;
for ( KeyValue < int , Client > & pair : clients ) {
sockets . push_back ( pair . key ) ;
}
for ( int socket : sockets ) {
cleanup_socket ( socket ) ;
}
}
close ( compositor_socket ) ;
compositor_socket = - 1 ;
for ( KeyValue < uint32_t , RegistryGlobalInfo > & 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 < int > sent_fds ;
if ( * fds_requested > 0 ) {
DEBUG_LOG_WAYLAND_EMBED ( vformat ( " Requested %d FDs. " , * fds_requested ) ) ;
List < int > & 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 < int , Client > & 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 < int , Client > & 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 ( ) ;
2025-11-26 11:16:08 -03:00
if ( proxy_thread . is_started ( ) ) {
proxy_thread . wait_to_finish ( ) ;
}
2025-11-15 23:38:41 +01:00
}
# endif // TOOLS_ENABLED
# endif // WAYLAND_ENABLED